Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// SPDX-FileCopyrightText: 2026
//
// SPDX-License-Identifier: AGPL-3.0-or-later

using Content.Shared._Pirate.Weapons.Ranged.Upgrades;
using Content.Shared._Pirate.Weapons.Ranged.Upgrades.Components;
using Robust.Client.GameObjects;
using Robust.Shared.GameObjects;

namespace Content.Client._Pirate.Weapons.Ranged.Upgrades;

public sealed class GunFlashlightAttachmentVisualizerSystem : EntitySystem
{
[Dependency] private readonly SpriteSystem _sprite = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;

private const string LayerKey = "gun_flashlight";

public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GunFlashlightAttachmentComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<GunFlashlightAttachmentComponent, AppearanceChangeEvent>(OnAppearanceChange);
}

private void OnStartup(EntityUid uid, GunFlashlightAttachmentComponent component, ComponentStartup args)
{
if (!TryComp<SpriteComponent>(uid, out var sprite))
return;

EnsureLayer((uid, sprite), component);
UpdateLayer((uid, sprite), component, attached: false, lightOn: false);
}

private void OnAppearanceChange(EntityUid uid, GunFlashlightAttachmentComponent component, ref AppearanceChangeEvent args)
{
if (!TryComp<SpriteComponent>(uid, out var sprite))
return;

if (!_appearance.TryGetData<bool>(uid, GunFlashlightVisuals.Attached, out var attached, args.Component))
attached = false;

if (!_appearance.TryGetData<bool>(uid, GunFlashlightVisuals.LightOn, out var lightOn, args.Component))
lightOn = false;

EnsureLayer((uid, sprite), component);
UpdateLayer((uid, sprite), component, attached, lightOn);
}

private void EnsureLayer(Entity<SpriteComponent?> ent, GunFlashlightAttachmentComponent component)
{
if (_sprite.LayerMapTryGet(ent, LayerKey, out _, false))
return;

var index = _sprite.LayerMapReserve(ent, LayerKey);
_sprite.LayerSetData(ent, index, new PrototypeLayerData
{
RsiPath = component.Sprite.ToString(),
State = component.StateOff,
Offset = component.Offset,
Visible = false
});
}

private void UpdateLayer(Entity<SpriteComponent?> ent, GunFlashlightAttachmentComponent component, bool attached, bool lightOn)
{
if (!_sprite.LayerMapTryGet(ent, LayerKey, out var index, false))
return;

_sprite.LayerSetData(ent, index, new PrototypeLayerData
{
RsiPath = component.Sprite.ToString(),
State = lightOn ? component.StateOn : component.StateOff,
Offset = component.Offset,
Visible = attached
});
}
}
135 changes: 135 additions & 0 deletions Content.Server/_Lavaland/Weapons/Ranged/Upgrades/GunUpgradeSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,28 @@
using Content.Shared.Weapons.Melee.Events;
using Content.Shared.Weapons.Ranged.Systems;
using Robust.Shared.Containers;
#region DOWNSTREAM-TPirates: gun flashlights
using Content.Shared.Actions;
using Content.Shared.Actions.Components;
using Content.Shared.Hands.Components;
using Content.Shared.Light.Components;
using Content.Shared._Pirate.Weapons.Ranged.Upgrades;
using Content.Shared.Toggleable;
using Robust.Shared.GameObjects;
#endregion

namespace Content.Server._Lavaland.Weapons.Ranged.Upgrades;

public sealed class GunUpgradeSystem : SharedGunUpgradeSystem
{
[Dependency] private readonly PressureEfficiencyChangeSystem _pressure = default!;
[Dependency] private readonly EntityEffectSystem _entityEffect = default!;
#region DOWNSTREAM-TPirates: gun flashlights
[Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly ActionContainerSystem _actionContainer = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedContainerSystem _containers = default!;
#endregion

public override void Initialize()
{
Expand All @@ -47,6 +62,12 @@ public override void Initialize()
SubscribeLocalEvent<GunUpgradeDamageComponent, ProjectileShotEvent>(OnProjectileShot);
SubscribeLocalEvent<GunUpgradePressureComponent, EntGotInsertedIntoContainerMessage>(OnPressureUpgradeInserted);
SubscribeLocalEvent<GunUpgradePressureComponent, EntGotRemovedFromContainerMessage>(OnPressureUpgradeRemoved);
#region DOWNSTREAM-TPirates: gun flashlights
SubscribeLocalEvent<GunUpgradeFlashlightComponent, EntGotInsertedIntoContainerMessage>(OnFlashlightInserted);
SubscribeLocalEvent<GunUpgradeFlashlightComponent, EntGotRemovedFromContainerMessage>(OnFlashlightRemoved);
SubscribeLocalEvent<GunUpgradeFlashlightComponent, ToggleActionEvent>(OnFlashlightToggled,
after: new[] { typeof(Content.Server.Light.EntitySystems.HandheldLightSystem) });
#endregion

SubscribeLocalEvent<WeaponUpgradeEffectsComponent, MeleeHitEvent>(OnEffectsUpgradeHit);
}
Expand Down Expand Up @@ -131,4 +152,118 @@ private void OnEffectsUpgradeHit(Entity<WeaponUpgradeEffectsComponent> ent, ref
}
}
}

#region DOWNSTREAM-TPirates: gun flashlights
private void OnFlashlightInserted(Entity<GunUpgradeFlashlightComponent> ent, ref EntGotInsertedIntoContainerMessage args)
{
if (args.Container.ID != "flashlight")
return;

if (!TryComp<UpgradeableWeaponComponent>(args.Container.Owner, out _))
return;

if (!TryComp<HandheldLightComponent>(ent, out var handheld)
|| handheld.ToggleActionEntity == null)
return;

if (!TryComp<ActionComponent>(handheld.ToggleActionEntity.Value, out var action))
return;

ent.Comp.OriginalItemIconStyle = action.ItemIconStyle;
ent.Comp.OriginalUseDelay = action.UseDelay;
ent.Comp.HasSavedActionDefaults = true;

_actions.SetEntityIcon((handheld.ToggleActionEntity.Value, action), args.Container.Owner);
_actions.SetItemIconStyle((handheld.ToggleActionEntity.Value, action), ItemActionIconStyle.BigItem);
_actions.SetUseDelay((handheld.ToggleActionEntity.Value, action), null);
SetGunFlashlightVisuals(args.Container.Owner, attached: true, on: handheld.Activated);
GrantUpgradeActionsIfHeld(args.Container.Owner);
}

private void OnFlashlightRemoved(Entity<GunUpgradeFlashlightComponent> ent, ref EntGotRemovedFromContainerMessage args)
{
if (args.Container.ID != "flashlight")
return;

if (TryComp<HandheldLightComponent>(ent, out var handheld)
&& handheld.ToggleActionEntity is { } toggleAction
&& TryComp<ActionComponent>(toggleAction, out var action))
{
if (ent.Comp.HasSavedActionDefaults)
{
_actions.SetItemIconStyle((toggleAction, action), ent.Comp.OriginalItemIconStyle);
_actions.SetUseDelay((toggleAction, action), ent.Comp.OriginalUseDelay);
ent.Comp.HasSavedActionDefaults = false;
}

if (action.EntIcon == args.Container.Owner)
_actions.SetEntityIcon((toggleAction, action), null);

if (_containers.TryGetContainingContainer((args.Container.Owner, Transform(args.Container.Owner), MetaData(args.Container.Owner)), out var gunContainer)
&& TryComp<HandsComponent>(gunContainer.Owner, out _)
&& action.AttachedEntity == gunContainer.Owner)
{
_actions.RemoveAction((gunContainer.Owner, CompOrNull<ActionsComponent>(gunContainer.Owner)), (toggleAction, action));
}
}

SetGunFlashlightVisuals(args.Container.Owner, attached: false, on: false);
}

private void OnFlashlightToggled(Entity<GunUpgradeFlashlightComponent> ent, ref ToggleActionEvent args)
{
if (!TryComp<HandheldLightComponent>(ent, out var handheld))
return;

if (!TryGetContainingGun(ent.Owner, out var gun))
return;

SetGunFlashlightVisuals(gun, attached: true, on: handheld.Activated);
}

private bool TryGetContainingGun(EntityUid flashlight, out EntityUid gun)
{
gun = default;

if (!_containers.TryGetContainingContainer((flashlight, Transform(flashlight), MetaData(flashlight)), out var container))
return false;

if (container.ID != "flashlight")
return false;

if (!TryComp<UpgradeableWeaponComponent>(container.Owner, out _))
return false;

gun = container.Owner;
return true;
}

private void SetGunFlashlightVisuals(EntityUid gun, bool attached, bool on)
{
if (!TryComp<AppearanceComponent>(gun, out var appearance))
return;

_appearance.SetData(gun, GunFlashlightVisuals.Attached, attached, appearance);
_appearance.SetData(gun, GunFlashlightVisuals.LightOn, on, appearance);
}

private void GrantUpgradeActionsIfHeld(EntityUid gun)
{
if (!_containers.TryGetContainingContainer((gun, Transform(gun), MetaData(gun)), out var container))
return;

var holder = container.Owner;
if (!TryComp<HandsComponent>(holder, out _))
return;

var ev = new GetItemActionsEvent(_actionContainer, holder, gun);
RaiseLocalEvent(gun, ev);
if (ev.Actions.Count == 0)
return;

EnsureComp<ActionsContainerComponent>(gun);
_actions.GrantActions((holder, CompOrNull<ActionsComponent>(holder)), ev.Actions, gun);
_actions.LoadActions(holder);
}
#endregion
}
12 changes: 12 additions & 0 deletions Content.Shared/Actions/SharedActionsSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1199,6 +1199,18 @@ public void SetIconColor(Entity<ActionComponent?> ent, Color color)
DirtyField(ent, ent.Comp, nameof(ActionComponent.IconColor));
}


#region DOWNSTREAM-TPirates: gun flashlights
public void SetItemIconStyle(Entity<ActionComponent?> ent, ItemActionIconStyle style)
{
if (!_actionQuery.Resolve(ent, ref ent.Comp) || ent.Comp.ItemIconStyle == style)
return;

ent.Comp.ItemIconStyle = style;
DirtyField(ent, ent.Comp, nameof(ActionComponent.ItemIconStyle));
}
#endregion

/// <summary>
/// Set the event of an action.
/// Since the event isn't required to be serializable this is not networked.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later

using System;
using Content.Shared.Actions.Components;
using Robust.Shared.GameStates;

namespace Content.Shared._Lavaland.Weapons.Ranged.Upgrades.Components;
Expand All @@ -27,4 +29,20 @@ namespace Content.Shared._Lavaland.Weapons.Ranged.Upgrades.Components;
/// Component to indicate a valid flashlight for weapon attachment
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class GunUpgradeFlashlightComponent : Component;
public sealed partial class GunUpgradeFlashlightComponent : Component
{
/// <summary>
/// Original action icon style before this flashlight was attached to a gun.
/// </summary>
public ItemActionIconStyle OriginalItemIconStyle = ItemActionIconStyle.NoItem;

/// <summary>
/// Original use delay before this flashlight was attached to a gun.
/// </summary>
public TimeSpan? OriginalUseDelay;

/// <summary>
/// True after original action visual/use settings were captured on insert.
/// </summary>
public bool HasSavedActionDefaults;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-FileCopyrightText: 2026
//
// SPDX-License-Identifier: AGPL-3.0-or-later

using System.Numerics;
using Robust.Shared.Utility;

namespace Content.Shared._Pirate.Weapons.Ranged.Upgrades.Components;

/// <summary>
/// Configures flashlight overlay visuals for a gun.
/// </summary>
[RegisterComponent]
public sealed partial class GunFlashlightAttachmentComponent : Component
{
[DataField("sprite")]
public ResPath Sprite = new("/Textures/_Pirate/Objects/Weapons/Guns/Upgrades/gun_flashlight_attachment.rsi");

[DataField("stateOff")]
public string StateOff = "flight";

[DataField("stateOn")]
public string StateOn = "flight-on";

[DataField("offset")]
public Vector2 Offset = Vector2.Zero;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-FileCopyrightText: 2026
//
// SPDX-License-Identifier: AGPL-3.0-or-later

using Robust.Shared.Serialization;

namespace Content.Shared._Pirate.Weapons.Ranged.Upgrades;

[Serializable, NetSerializable]
public enum GunFlashlightVisuals : byte
{
Attached,
LightOn
}
Loading
Loading