item toggling giga rework + full ninja refactor (#28039)

* item toggle refactoring and some new systems

* add ToggleClothing component/system

* unhardcode magboots gravity logic

* make magboots and speedboots use ItemToggle and stuff

* remove now useless clothing components

* update client/server magboots systems

* add note to use ItemToggledEvent in ToggleActionEvent doc

* refactor PowerCellDraw to use ItemToggle for ui open/close control

* add TryUseCharges, refactor charges system

* update magboot trigger code

* make borg use ItemToggle, network SelectedModule instead of now removed Activated

* add AccessToggle for borg

* the giga ninja refactor

* update ninja yml

* update ItemToggle usage for some stuff

* fix activatableui requires power

* random fixing

* yaml fixing

* nuke ItemToggleDisarmMalus

* make defib use ItemToggle

* make things that use power not turn on if missing use charge

* pro

* fix sound prediction

* bruh

* proximity detector use ItemToggle

* oop

* big idiot syndrome

* fix ninja spawn rule and make it generic

* fix ninja spawn rule yml

* move loading profiles into AntagLoadProfileRule

* more ninja refactor

* ninja yml fixes

* the dreaded copy paste ops

* remove useless NinjaRuleComponent and ue AntagSelection for greeting

* fix invisibility

* move IsCompleted to SharedObjectivesSystem

* ability fixes

* oop fix powercell instantly draining itself

* sentient speedboots gaming

* make reflect use ItemToggle

* fix other test

* loadprofilerule moved into its own pr

* remove conflict with dragon refactor

* remove all GenericAntag code from ninja

* )

* probably

* remove old enabled

* great language bravo vince

* GREAT LANGUAGE

* who made this language

* because it stinks

* reparent blood-red magboots to magboots probbbly works

* most of the review stuff

* hasGrav doesnt mean what i thought it did

* make health analyzer use itemtoggle, not fail test

* fix mag/speed boots being wacky

* UNTROLL

* add ItemToggle to the random health analyzers

* a

* remove unused obsolete borg func

* untrolling

* :trollface:

* fix test

* fix

* g

* untroll

---------

Co-authored-by: deltanedas <@deltanedas:kde.org>
This commit is contained in:
deltanedas
2024-07-11 05:55:56 +00:00
committed by GitHub
parent e45f55e36d
commit 02636386b5
121 changed files with 1629 additions and 2014 deletions

View File

@@ -1,9 +0,0 @@
using Content.Shared.Item.ItemToggle;
namespace Content.Shared.Item;
/// <inheritdoc/>
public sealed class ItemToggleSystem : SharedItemToggleSystem
{
}

View File

@@ -0,0 +1,5 @@
using Content.Shared.Ninja.Systems;
namespace Content.Client.Ninja.Systems;
public sealed class ItemCreatorSystem : SharedItemCreatorSystem;

View File

@@ -2,9 +2,4 @@ using Content.Shared.Ninja.Systems;
namespace Content.Client.Ninja.Systems;
/// <summary>
/// Does nothing special, only exists to provide a client implementation.
/// </summary>
public sealed class NinjaGlovesSystem : SharedNinjaGlovesSystem
{
}
public sealed class NinjaGlovesSystem : SharedNinjaGlovesSystem;

View File

@@ -1,24 +1,5 @@
using Content.Shared.Clothing.EntitySystems;
using Content.Shared.Ninja.Components;
using Content.Shared.Ninja.Systems;
namespace Content.Client.Ninja.Systems;
/// <summary>
/// Disables cloak prediction since client has no knowledge of battery power.
/// Cloak will still be enabled after server tells it.
/// </summary>
public sealed class NinjaSuitSystem : SharedNinjaSuitSystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<NinjaSuitComponent, AttemptStealthEvent>(OnAttemptStealth);
}
private void OnAttemptStealth(EntityUid uid, NinjaSuitComponent comp, AttemptStealthEvent args)
{
args.Cancel();
}
}
public sealed class NinjaSuitSystem : SharedNinjaSuitSystem;

View File

@@ -2,11 +2,4 @@ using Content.Shared.Ninja.Systems;
namespace Content.Client.Ninja.Systems;
/// <summary>
/// Currently does nothing special clientside.
/// All functionality is in shared and server.
/// Only exists to prevent crashing.
/// </summary>
public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem
{
}
public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem;

View File

@@ -0,0 +1,5 @@
using Content.Shared.Ninja.Systems;
namespace Content.Client.Ninja.Systems;
public sealed class SpiderChargeSystem : SharedSpiderChargeSystem;

View File

@@ -171,7 +171,7 @@ public abstract partial class InteractionTest
// turn on welders
if (enableToggleable && SEntMan.TryGetComponent(item, out itemToggle) && !itemToggle.Activated)
{
Assert.That(ItemToggleSys.TryActivate(item, playerEnt, itemToggle: itemToggle));
Assert.That(ItemToggleSys.TryActivate((item, itemToggle), user: playerEnt));
}
});

View File

@@ -104,7 +104,7 @@ public abstract partial class InteractionTest
protected Content.Server.Construction.ConstructionSystem SConstruction = default!;
protected SharedDoAfterSystem DoAfterSys = default!;
protected ToolSystem ToolSys = default!;
protected SharedItemToggleSystem ItemToggleSys = default!;
protected ItemToggleSystem ItemToggleSys = default!;
protected InteractionTestSystem STestSystem = default!;
protected SharedTransformSystem Transform = default!;
protected SharedMapSystem MapSystem = default!;
@@ -165,7 +165,7 @@ public abstract partial class InteractionTest
HandSys = SEntMan.System<HandsSystem>();
InteractSys = SEntMan.System<SharedInteractionSystem>();
ToolSys = SEntMan.System<ToolSystem>();
ItemToggleSys = SEntMan.System<SharedItemToggleSystem>();
ItemToggleSys = SEntMan.System<ItemToggleSystem>();
DoAfterSys = SEntMan.System<SharedDoAfterSystem>();
Transform = SEntMan.System<SharedTransformSystem>();
MapSystem = SEntMan.System<SharedMapSystem>();

View File

@@ -7,6 +7,7 @@ namespace Content.Server.Charges.Components;
/// Something with limited charges that can be recharged automatically.
/// Requires LimitedChargesComponent to function.
/// </summary>
// TODO: no reason this cant be predicted and server system deleted
[RegisterComponent, AutoGenerateComponentPause]
[Access(typeof(ChargesSystem))]
public sealed partial class AutoRechargeComponent : Component

View File

@@ -37,15 +37,17 @@ public sealed class ChargesSystem : SharedChargesSystem
args.PushMarkup(Loc.GetString("limited-charges-recharging", ("seconds", timeRemaining)));
}
public override void UseCharge(EntityUid uid, LimitedChargesComponent? comp = null)
public override void AddCharges(EntityUid uid, int change, LimitedChargesComponent? comp = null)
{
if (!Resolve(uid, ref comp, false))
if (!Query.Resolve(uid, ref comp, false))
return;
var startRecharge = comp.Charges == comp.MaxCharges;
base.UseCharge(uid, comp);
// start the recharge time after first use at full charge
if (startRecharge && TryComp<AutoRechargeComponent>(uid, out var recharge))
base.AddCharges(uid, change, comp);
// if a charge was just used from full, start the recharge timer
// TODO: probably make this an event instead of having le server system that just does this
if (change < 0 && startRecharge && TryComp<AutoRechargeComponent>(uid, out var recharge))
recharge.NextChargeTime = _timing.CurTime + recharge.RechargeDuration;
}
}

View File

@@ -1,27 +0,0 @@
using Content.Server.Ninja.Systems;
using Content.Shared.Communications;
using Content.Shared.Random;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
namespace Content.Server.GameTicking.Rules.Components;
/// <summary>
/// Stores some configuration used by the ninja system.
/// Objectives and roundend summary are handled by <see cref="GenericAntagRuleComponent"/>.
/// </summary>
[RegisterComponent, Access(typeof(SpaceNinjaSystem))]
public sealed partial class NinjaRuleComponent : Component
{
/// <summary>
/// List of threats that can be called in. Copied onto <see cref="CommsHackerComponent"/> when gloves are enabled.
/// </summary>
[DataField(required: true)]
public ProtoId<WeightedRandomPrototype> Threats = string.Empty;
/// <summary>
/// Sound played when making the player a ninja via antag control or ghost role
/// </summary>
[DataField]
public SoundSpecifier? GreetingSound = new SoundPathSpecifier("/Audio/Misc/ninja_greeting.ogg");
}

View File

@@ -1,22 +0,0 @@
namespace Content.Server.Item;
/// <summary>
/// Handles whether this item applies a disarm malus when active.
/// </summary>
[RegisterComponent]
public sealed partial class ItemToggleDisarmMalusComponent : Component
{
/// <summary>
/// Item has this modifier to the chance to disarm when activated.
/// If null, the value will be inferred from the current malus just before the malus is first deactivated.
/// </summary>
[ViewVariables(VVAccess.ReadOnly), DataField]
public float? ActivatedDisarmMalus = null;
/// <summary>
/// Item has this modifier to the chance to disarm when deactivated. If none is mentioned, it uses the item's default disarm modifier.
/// If null, the value will be inferred from the current malus just before the malus is first activated.
/// </summary>
[ViewVariables(VVAccess.ReadOnly), DataField]
public float? DeactivatedDisarmMalus = null;
}

View File

@@ -1,9 +0,0 @@
namespace Content.Server.Item;
/// <summary>
/// Handles whether this item is sharp when toggled on.
/// </summary>
[RegisterComponent]
public sealed partial class ItemToggleSharpComponent : Component
{
}

View File

@@ -1,44 +0,0 @@
using Content.Server.CombatMode.Disarm;
using Content.Server.Kitchen.Components;
using Content.Shared.Item.ItemToggle;
using Content.Shared.Item.ItemToggle.Components;
namespace Content.Server.Item;
public sealed class ItemToggleSystem : SharedItemToggleSystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ItemToggleSharpComponent, ItemToggledEvent>(ToggleSharp);
SubscribeLocalEvent<ItemToggleDisarmMalusComponent, ItemToggledEvent>(ToggleMalus);
}
private void ToggleSharp(Entity<ItemToggleSharpComponent> ent, ref ItemToggledEvent args)
{
// TODO generalize this into a "ToggleComponentComponent", though probably with a better name
if (args.Activated)
EnsureComp<SharpComponent>(ent);
else
RemCompDeferred<SharpComponent>(ent);
}
private void ToggleMalus(Entity<ItemToggleDisarmMalusComponent> ent, ref ItemToggledEvent args)
{
if (!TryComp<DisarmMalusComponent>(ent, out var malus))
return;
if (args.Activated)
{
ent.Comp.DeactivatedDisarmMalus ??= malus.Malus;
if (ent.Comp.ActivatedDisarmMalus is {} activatedMalus)
malus.Malus = activatedMalus;
return;
}
ent.Comp.ActivatedDisarmMalus ??= malus.Malus;
if (ent.Comp.DeactivatedDisarmMalus is {} deactivatedMalus)
malus.Malus = deactivatedMalus;
}
}

View File

@@ -6,6 +6,9 @@ namespace Content.Server.Medical.Components;
/// <summary>
/// After scanning, retrieves the target Uid to use with its related UI.
/// </summary>
/// <remarks>
/// Requires <c>ItemToggleComponent</c>.
/// </remarks>
[RegisterComponent, AutoGenerateComponentPause]
[Access(typeof(HealthAnalyzerSystem), typeof(CryoPodSystem))]
public sealed partial class HealthAnalyzerComponent : Component

View File

@@ -12,6 +12,7 @@ using Content.Shared.DoAfter;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Components;
using Content.Shared.Interaction.Events;
using Content.Shared.Item.ItemToggle;
using Content.Shared.Medical;
using Content.Shared.Mind;
using Content.Shared.Mobs;
@@ -37,6 +38,7 @@ public sealed class DefibrillatorSystem : EntitySystem
[Dependency] private readonly DoAfterSystem _doAfter = default!;
[Dependency] private readonly ElectrocutionSystem _electrocution = default!;
[Dependency] private readonly EuiManager _euiManager = default!;
[Dependency] private readonly ItemToggleSystem _toggle = default!;
[Dependency] private readonly RottingSystem _rotting = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly MobThresholdSystem _mobThreshold = default!;
@@ -50,30 +52,10 @@ public sealed class DefibrillatorSystem : EntitySystem
/// <inheritdoc/>
public override void Initialize()
{
SubscribeLocalEvent<DefibrillatorComponent, UseInHandEvent>(OnUseInHand);
SubscribeLocalEvent<DefibrillatorComponent, PowerCellSlotEmptyEvent>(OnPowerCellSlotEmpty);
SubscribeLocalEvent<DefibrillatorComponent, AfterInteractEvent>(OnAfterInteract);
SubscribeLocalEvent<DefibrillatorComponent, DefibrillatorZapDoAfterEvent>(OnDoAfter);
}
private void OnUseInHand(EntityUid uid, DefibrillatorComponent component, UseInHandEvent args)
{
if (args.Handled || !TryComp(uid, out UseDelayComponent? useDelay) || _useDelay.IsDelayed((uid, useDelay)))
return;
if (!TryToggle(uid, component, args.User))
return;
args.Handled = true;
_useDelay.TryResetDelay((uid, useDelay));
}
private void OnPowerCellSlotEmpty(EntityUid uid, DefibrillatorComponent component, ref PowerCellSlotEmptyEvent args)
{
if (!TerminatingOrDeleted(uid))
TryDisable(uid, component);
}
private void OnAfterInteract(EntityUid uid, DefibrillatorComponent component, AfterInteractEvent args)
{
if (args.Handled || args.Target is not { } target)
@@ -96,54 +78,12 @@ public sealed class DefibrillatorSystem : EntitySystem
Zap(uid, target, args.User, component);
}
public bool TryToggle(EntityUid uid, DefibrillatorComponent? component = null, EntityUid? user = null)
{
if (!Resolve(uid, ref component))
return false;
return component.Enabled
? TryDisable(uid, component)
: TryEnable(uid, component, user);
}
public bool TryEnable(EntityUid uid, DefibrillatorComponent? component = null, EntityUid? user = null)
{
if (!Resolve(uid, ref component))
return false;
if (component.Enabled)
return false;
if (!_powerCell.HasActivatableCharge(uid))
return false;
component.Enabled = true;
_appearance.SetData(uid, ToggleVisuals.Toggled, true);
_audio.PlayPvs(component.PowerOnSound, uid);
return true;
}
public bool TryDisable(EntityUid uid, DefibrillatorComponent? component = null)
{
if (!Resolve(uid, ref component))
return false;
if (!component.Enabled)
return false;
component.Enabled = false;
_appearance.SetData(uid, ToggleVisuals.Toggled, false);
_audio.PlayPvs(component.PowerOffSound, uid);
return true;
}
public bool CanZap(EntityUid uid, EntityUid target, EntityUid? user = null, DefibrillatorComponent? component = null)
{
if (!Resolve(uid, ref component))
return false;
if (!component.Enabled)
if (!_toggle.IsActivated(uid))
{
if (user != null)
_popup.PopupEntity(Loc.GetString("defibrillator-not-on"), uid, user.Value);
@@ -257,7 +197,7 @@ public sealed class DefibrillatorSystem : EntitySystem
// if we don't have enough power left for another shot, turn it off
if (!_powerCell.HasActivatableCharge(uid))
TryDisable(uid, component);
_toggle.TryDeactivate(uid);
// TODO clean up this clown show above
var ev = new TargetDefibrillatedEvent(user, (uid, component));

View File

@@ -8,6 +8,8 @@ using Content.Shared.DoAfter;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Item.ItemToggle;
using Content.Shared.Item.ItemToggle.Components;
using Content.Shared.MedicalScanner;
using Content.Shared.Mobs.Components;
using Content.Shared.Popups;
@@ -26,6 +28,7 @@ public sealed class HealthAnalyzerSystem : EntitySystem
[Dependency] private readonly PowerCellSystem _cell = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly ItemToggleSystem _toggle = default!;
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
[Dependency] private readonly TransformSystem _transformSystem = default!;
@@ -36,7 +39,7 @@ public sealed class HealthAnalyzerSystem : EntitySystem
SubscribeLocalEvent<HealthAnalyzerComponent, AfterInteractEvent>(OnAfterInteract);
SubscribeLocalEvent<HealthAnalyzerComponent, HealthAnalyzerDoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<HealthAnalyzerComponent, EntGotInsertedIntoContainerMessage>(OnInsertedIntoContainer);
SubscribeLocalEvent<HealthAnalyzerComponent, PowerCellSlotEmptyEvent>(OnPowerCellSlotEmpty);
SubscribeLocalEvent<HealthAnalyzerComponent, ItemToggledEvent>(OnToggled);
SubscribeLocalEvent<HealthAnalyzerComponent, DroppedEvent>(OnDropped);
}
@@ -111,16 +114,16 @@ public sealed class HealthAnalyzerSystem : EntitySystem
private void OnInsertedIntoContainer(Entity<HealthAnalyzerComponent> uid, ref EntGotInsertedIntoContainerMessage args)
{
if (uid.Comp.ScannedEntity is { } patient)
StopAnalyzingEntity(uid, patient);
_toggle.TryDeactivate(uid.Owner);
}
/// <summary>
/// Disable continuous updates once battery is dead
/// Disable continuous updates once turned off
/// </summary>
private void OnPowerCellSlotEmpty(Entity<HealthAnalyzerComponent> uid, ref PowerCellSlotEmptyEvent args)
private void OnToggled(Entity<HealthAnalyzerComponent> ent, ref ItemToggledEvent args)
{
if (uid.Comp.ScannedEntity is { } patient)
StopAnalyzingEntity(uid, patient);
if (!args.Activated && ent.Comp.ScannedEntity is { } patient)
StopAnalyzingEntity(ent, patient);
}
/// <summary>
@@ -129,7 +132,7 @@ public sealed class HealthAnalyzerSystem : EntitySystem
private void OnDropped(Entity<HealthAnalyzerComponent> uid, ref DroppedEvent args)
{
if (uid.Comp.ScannedEntity is { } patient)
StopAnalyzingEntity(uid, patient);
_toggle.TryDeactivate(uid.Owner);
}
private void OpenUserInterface(EntityUid user, EntityUid analyzer)
@@ -150,7 +153,7 @@ public sealed class HealthAnalyzerSystem : EntitySystem
//Link the health analyzer to the scanned entity
healthAnalyzer.Comp.ScannedEntity = target;
_cell.SetPowerCellDrawEnabled(healthAnalyzer, true);
_toggle.TryActivate(healthAnalyzer.Owner);
UpdateScannedUser(healthAnalyzer, target, true);
}
@@ -165,7 +168,7 @@ public sealed class HealthAnalyzerSystem : EntitySystem
//Unlink the analyzer
healthAnalyzer.Comp.ScannedEntity = null;
_cell.SetPowerCellDrawEnabled(target, false);
_toggle.TryDeactivate(healthAnalyzer.Owner);
UpdateScannedUser(healthAnalyzer, target, false);
}

View File

@@ -1,7 +1,7 @@
namespace Content.Server.Ninja.Events;
/// <summary>
/// Raised on the ninja when the suit has its powercell changed.
/// Raised on the ninja and suit when the suit has its powercell changed.
/// </summary>
[ByRefEvent]
public record struct NinjaBatteryChangedEvent(EntityUid Battery, EntityUid BatteryHolder);

View File

@@ -33,16 +33,17 @@ public sealed class BatteryDrainerSystem : SharedBatteryDrainerSystem
/// Start do after for draining a power source.
/// Can't predict PNBC existing so only done on server.
/// </summary>
private void OnBeforeInteractHand(EntityUid uid, BatteryDrainerComponent comp, BeforeInteractHandEvent args)
private void OnBeforeInteractHand(Entity<BatteryDrainerComponent> ent, ref BeforeInteractHandEvent args)
{
var (uid, comp) = ent;
var target = args.Target;
if (args.Handled || comp.BatteryUid == null || !HasComp<PowerNetworkBatteryComponent>(target))
if (args.Handled || comp.BatteryUid is not {} battery || !HasComp<PowerNetworkBatteryComponent>(target))
return;
// handles even if battery is full so you can actually see the poup
args.Handled = true;
if (_battery.IsFull(comp.BatteryUid.Value))
if (_battery.IsFull(battery))
{
_popup.PopupEntity(Loc.GetString("battery-drainer-full"), uid, uid, PopupType.Medium);
return;
@@ -59,23 +60,24 @@ public sealed class BatteryDrainerSystem : SharedBatteryDrainerSystem
_doAfter.TryStartDoAfter(doAfterArgs);
}
private void OnBatteryChanged(EntityUid uid, BatteryDrainerComponent comp, ref NinjaBatteryChangedEvent args)
private void OnBatteryChanged(Entity<BatteryDrainerComponent> ent, ref NinjaBatteryChangedEvent args)
{
SetBattery(uid, args.Battery, comp);
SetBattery((ent, ent.Comp), args.Battery);
}
/// <inheritdoc/>
protected override void OnDoAfterAttempt(EntityUid uid, BatteryDrainerComponent comp, DoAfterAttemptEvent<DrainDoAfterEvent> args)
protected override void OnDoAfterAttempt(Entity<BatteryDrainerComponent> ent, ref DoAfterAttemptEvent<DrainDoAfterEvent> args)
{
base.OnDoAfterAttempt(uid, comp, args);
base.OnDoAfterAttempt(ent, ref args);
if (comp.BatteryUid == null || _battery.IsFull(comp.BatteryUid.Value))
if (ent.Comp.BatteryUid is not {} battery || _battery.IsFull(battery))
args.Cancel();
}
/// <inheritdoc/>
protected override bool TryDrainPower(EntityUid uid, BatteryDrainerComponent comp, EntityUid target)
protected override bool TryDrainPower(Entity<BatteryDrainerComponent> ent, EntityUid target)
{
var (uid, comp) = ent;
if (comp.BatteryUid == null || !TryComp<BatteryComponent>(comp.BatteryUid.Value, out var battery))
return false;
@@ -98,6 +100,7 @@ public sealed class BatteryDrainerSystem : SharedBatteryDrainerSystem
var output = input * comp.DrainEfficiency;
_battery.SetCharge(comp.BatteryUid.Value, battery.CurrentCharge + output, battery);
// TODO: create effect message or something
Spawn("EffectSparks", Transform(target).Coordinates);
_audio.PlayPvs(comp.SparkSound, target);
_popup.PopupEntity(Loc.GetString("battery-drainer-success", ("battery", target)), uid, uid);

View File

@@ -0,0 +1,57 @@
using Content.Server.Ninja.Events;
using Content.Server.Power.EntitySystems;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Ninja.Components;
using Content.Shared.Ninja.Systems;
using Content.Shared.Popups;
namespace Content.Server.Ninja.Systems;
public sealed class ItemCreatorSystem : SharedItemCreatorSystem
{
[Dependency] private readonly BatterySystem _battery = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ItemCreatorComponent, CreateItemEvent>(OnCreateItem);
SubscribeLocalEvent<ItemCreatorComponent, NinjaBatteryChangedEvent>(OnBatteryChanged);
}
private void OnCreateItem(Entity<ItemCreatorComponent> ent, ref CreateItemEvent args)
{
var (uid, comp) = ent;
if (comp.Battery is not {} battery)
return;
args.Handled = true;
var user = args.Performer;
if (!_battery.TryUseCharge(battery, comp.Charge))
{
_popup.PopupEntity(Loc.GetString(comp.NoPowerPopup), user, user);
return;
}
var ev = new CreateItemAttemptEvent(user);
RaiseLocalEvent(uid, ref ev);
if (ev.Cancelled)
return;
// try to put throwing star in hand, otherwise it goes on the ground
var star = Spawn(comp.SpawnedPrototype, Transform(user).Coordinates);
_hands.TryPickupAnyHand(user, star);
}
private void OnBatteryChanged(Entity<ItemCreatorComponent> ent, ref NinjaBatteryChangedEvent args)
{
if (ent.Comp.Battery == args.Battery)
return;
ent.Comp.Battery = args.Battery;
Dirty(ent, ent.Comp);
}
}

View File

@@ -1,13 +1,8 @@
using Content.Server.Communications;
using Content.Server.Mind;
using Content.Server.Ninja.Events;
using Content.Server.Objectives.Systems;
using Content.Shared.Communications;
using Content.Shared.CriminalRecords.Components;
using Content.Shared.Mind;
using Content.Shared.Objectives.Systems;
using Content.Shared.Ninja.Components;
using Content.Shared.Ninja.Systems;
using Content.Shared.Research.Components;
using Content.Shared.Toggleable;
namespace Content.Server.Ninja.Systems;
@@ -16,89 +11,44 @@ namespace Content.Server.Ninja.Systems;
/// </summary>
public sealed class NinjaGlovesSystem : SharedNinjaGlovesSystem
{
[Dependency] private readonly EmagProviderSystem _emagProvider = default!;
[Dependency] private readonly CodeConditionSystem _codeCondition = default!;
[Dependency] private readonly CommsHackerSystem _commsHacker = default!;
[Dependency] private readonly SharedStunProviderSystem _stunProvider = default!;
[Dependency] private readonly SharedMindSystem _mind = default!;
[Dependency] private readonly SharedObjectivesSystem _objectives = default!;
[Dependency] private readonly SpaceNinjaSystem _ninja = default!;
public override void Initialize()
protected override void EnableGloves(Entity<NinjaGlovesComponent> ent, Entity<SpaceNinjaComponent> user)
{
base.Initialize();
base.EnableGloves(ent, user);
SubscribeLocalEvent<NinjaGlovesComponent, ToggleActionEvent>(OnToggleAction);
}
/// <summary>
/// Toggle gloves, if the user is a ninja wearing a ninja suit.
/// </summary>
private void OnToggleAction(EntityUid uid, NinjaGlovesComponent comp, ToggleActionEvent args)
{
if (args.Handled)
return;
args.Handled = true;
var user = args.Performer;
// need to wear suit to enable gloves
if (!TryComp<SpaceNinjaComponent>(user, out var ninja)
|| ninja.Suit == null
|| !HasComp<NinjaSuitComponent>(ninja.Suit.Value))
{
Popup.PopupEntity(Loc.GetString("ninja-gloves-not-wearing-suit"), user, user);
return;
}
// show its state to the user
var enabling = comp.User == null;
Appearance.SetData(uid, ToggleVisuals.Toggled, enabling);
var message = Loc.GetString(enabling ? "ninja-gloves-on" : "ninja-gloves-off");
Popup.PopupEntity(message, user, user);
if (enabling)
{
EnableGloves(uid, comp, user, ninja);
}
else
{
DisableGloves(uid, comp);
}
}
private void EnableGloves(EntityUid uid, NinjaGlovesComponent comp, EntityUid user, SpaceNinjaComponent ninja)
{
// can't use abilities if suit is not equipped, this is checked elsewhere but just making sure to satisfy nullability
if (ninja.Suit == null)
if (user.Comp.Suit is not {} suit)
return;
comp.User = user;
Dirty(uid, comp);
_ninja.AssignGloves(user, uid, ninja);
if (!_mind.TryGetMind(user, out var mindId, out var mind))
return;
var drainer = EnsureComp<BatteryDrainerComponent>(user);
var stun = EnsureComp<StunProviderComponent>(user);
_stunProvider.SetNoPowerPopup(user, "ninja-no-power", stun);
foreach (var ability in ent.Comp.Abilities)
{
// non-objective abilities are added in shared already
if (ability.Objective is not {} objId)
continue;
// prevent doing an objective multiple times by toggling gloves after doing them
// if it's not tied to an objective always add them anyway
if (!_mind.TryFindObjective((mindId, mind), objId, out var obj))
{
Log.Error($"Ninja glove ability of {ent} referenced missing objective {ability.Objective} of {_mind.MindOwnerLoggingString(mind)}");
continue;
}
if (!_objectives.IsCompleted(obj.Value, (mindId, mind)))
EntityManager.AddComponents(user, ability.Components);
}
// let abilities that use battery power work
if (_ninja.GetNinjaBattery(user, out var battery, out var _))
{
var ev = new NinjaBatteryChangedEvent(battery.Value, ninja.Suit.Value);
var ev = new NinjaBatteryChangedEvent(battery.Value, suit);
RaiseLocalEvent(user, ref ev);
}
var emag = EnsureComp<EmagProviderComponent>(user);
_emagProvider.SetWhitelist(user, comp.DoorjackWhitelist, emag);
EnsureComp<ResearchStealerComponent>(user);
// prevent calling in multiple threats by toggling gloves after
if (!_codeCondition.IsCompleted(user, ninja.TerrorObjective))
{
var hacker = EnsureComp<CommsHackerComponent>(user);
var rule = _ninja.NinjaRule(user);
if (rule != null)
_commsHacker.SetThreats(user, rule.Threats, hacker);
}
if (!_codeCondition.IsCompleted(user, ninja.MassArrestObjective))
{
EnsureComp<CriminalRecordsHackerComponent>(user);
}
}
}

View File

@@ -2,7 +2,6 @@ using Content.Server.Emp;
using Content.Server.Ninja.Events;
using Content.Server.Power.Components;
using Content.Server.PowerCell;
using Content.Shared.Clothing.EntitySystems;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Ninja.Components;
using Content.Shared.Ninja.Systems;
@@ -29,15 +28,13 @@ public sealed class NinjaSuitSystem : SharedNinjaSuitSystem
SubscribeLocalEvent<NinjaSuitComponent, ContainerIsInsertingAttemptEvent>(OnSuitInsertAttempt);
SubscribeLocalEvent<NinjaSuitComponent, EmpAttemptEvent>(OnEmpAttempt);
SubscribeLocalEvent<NinjaSuitComponent, AttemptStealthEvent>(OnAttemptStealth);
SubscribeLocalEvent<NinjaSuitComponent, CreateThrowingStarEvent>(OnCreateThrowingStar);
SubscribeLocalEvent<NinjaSuitComponent, RecallKatanaEvent>(OnRecallKatana);
SubscribeLocalEvent<NinjaSuitComponent, NinjaEmpEvent>(OnEmp);
}
protected override void NinjaEquippedSuit(EntityUid uid, NinjaSuitComponent comp, EntityUid user, SpaceNinjaComponent ninja)
protected override void NinjaEquipped(Entity<NinjaSuitComponent> ent, Entity<SpaceNinjaComponent> user)
{
base.NinjaEquippedSuit(uid, comp, user, ninja);
base.NinjaEquipped(ent, user);
_ninja.SetSuitPowerAlert(user);
}
@@ -57,16 +54,15 @@ public sealed class NinjaSuitSystem : SharedNinjaSuitSystem
// can only upgrade power cell, not swap to recharge instantly otherwise ninja could just swap batteries with flashlights in maints for easy power
if (!TryComp<BatteryComponent>(args.EntityUid, out var inserting) || inserting.MaxCharge <= battery.MaxCharge)
{
args.Cancel();
}
// tell ninja abilities that use battery to update it so they don't use charge from the old one
var user = Transform(uid).ParentUid;
if (!HasComp<SpaceNinjaComponent>(user))
if (!_ninja.IsNinja(user))
return;
var ev = new NinjaBatteryChangedEvent(args.EntityUid, uid);
RaiseLocalEvent(uid, ref ev);
RaiseLocalEvent(user, ref ev);
}
@@ -77,64 +73,22 @@ public sealed class NinjaSuitSystem : SharedNinjaSuitSystem
args.Cancel();
}
protected override void UserUnequippedSuit(EntityUid uid, NinjaSuitComponent comp, EntityUid user)
protected override void UserUnequippedSuit(Entity<NinjaSuitComponent> ent, Entity<SpaceNinjaComponent> user)
{
base.UserUnequippedSuit(uid, comp, user);
base.UserUnequippedSuit(ent, user);
// remove power indicator
_ninja.SetSuitPowerAlert(user);
}
private void OnAttemptStealth(EntityUid uid, NinjaSuitComponent comp, AttemptStealthEvent args)
private void OnRecallKatana(Entity<NinjaSuitComponent> ent, ref RecallKatanaEvent args)
{
var user = args.User;
// need 1 second of charge to turn on stealth
var chargeNeeded = SuitWattage(uid, comp);
// being attacked while cloaked gives no power message since it overloads the power supply or something
if (!_ninja.GetNinjaBattery(user, out _, out var battery) || battery.CurrentCharge < chargeNeeded)
{
Popup.PopupEntity(Loc.GetString("ninja-no-power"), user, user);
args.Cancel();
return;
}
if (comp.DisableCooldown > GameTiming.CurTime)
{
Popup.PopupEntity(Loc.GetString("ninja-suit-cooldown"), user, user, PopupType.Medium);
args.Cancel();
return;
}
StealthClothing.SetEnabled(uid, user, true);
}
private void OnCreateThrowingStar(EntityUid uid, NinjaSuitComponent comp, CreateThrowingStarEvent args)
{
args.Handled = true;
var (uid, comp) = ent;
var user = args.Performer;
if (!_ninja.TryUseCharge(user, comp.ThrowingStarCharge))
{
Popup.PopupEntity(Loc.GetString("ninja-no-power"), user, user);
if (!_ninja.NinjaQuery.TryComp(user, out var ninja) || ninja.Katana == null)
return;
}
if (comp.DisableCooldown > GameTiming.CurTime)
{
Popup.PopupEntity(Loc.GetString("ninja-suit-cooldown"), user, user, PopupType.Medium);
return;
}
// try to put throwing star in hand, otherwise it goes on the ground
var star = Spawn(comp.ThrowingStarPrototype, Transform(user).Coordinates);
_hands.TryPickupAnyHand(user, star);
}
private void OnRecallKatana(EntityUid uid, NinjaSuitComponent comp, RecallKatanaEvent args)
{
args.Handled = true;
var user = args.Performer;
if (!TryComp<SpaceNinjaComponent>(user, out var ninja) || ninja.Katana == null)
return;
var katana = ninja.Katana.Value;
var coords = _transform.GetWorldPosition(katana);
@@ -146,11 +100,8 @@ public sealed class NinjaSuitSystem : SharedNinjaSuitSystem
return;
}
if (comp.DisableCooldown > GameTiming.CurTime)
{
Popup.PopupEntity(Loc.GetString("ninja-suit-cooldown"), user, user, PopupType.Medium);
if (CheckDisabled(ent, user))
return;
}
// TODO: teleporting into belt slot
var message = _hands.TryPickupAnyHand(user, katana)
@@ -159,9 +110,11 @@ public sealed class NinjaSuitSystem : SharedNinjaSuitSystem
Popup.PopupEntity(Loc.GetString(message), user, user);
}
private void OnEmp(EntityUid uid, NinjaSuitComponent comp, NinjaEmpEvent args)
private void OnEmp(Entity<NinjaSuitComponent> ent, ref NinjaEmpEvent args)
{
var (uid, comp) = ent;
args.Handled = true;
var user = args.Performer;
if (!_ninja.TryUseCharge(user, comp.EmpCharge))
{
@@ -169,13 +122,9 @@ public sealed class NinjaSuitSystem : SharedNinjaSuitSystem
return;
}
if (comp.DisableCooldown > GameTiming.CurTime)
{
Popup.PopupEntity(Loc.GetString("ninja-suit-cooldown"), user, user, PopupType.Medium);
if (CheckDisabled(ent, user))
return;
}
// I don't think this affects the suit battery, but if it ever does in the future add a blacklist for it
var coords = _transform.GetMapCoordinates(user);
_emp.EmpPulse(coords, comp.EmpRange, comp.EmpConsumption, comp.EmpDuration);
}

View File

@@ -2,7 +2,6 @@ using Content.Server.Communications;
using Content.Server.Chat.Managers;
using Content.Server.CriminalRecords.Systems;
using Content.Server.GameTicking.Rules.Components;
using Content.Server.GenericAntag;
using Content.Server.Objectives.Components;
using Content.Server.Objectives.Systems;
using Content.Server.Power.Components;
@@ -11,7 +10,6 @@ using Content.Server.PowerCell;
using Content.Server.Research.Systems;
using Content.Server.Roles;
using Content.Shared.Alert;
using Content.Shared.Clothing.EntitySystems;
using Content.Shared.Doors.Components;
using Content.Shared.IdentityManagement;
using Content.Shared.Mind;
@@ -26,11 +24,6 @@ using Robust.Shared.Audio.Systems;
namespace Content.Server.Ninja.Systems;
// TODO: when syndiborgs are a thing have a borg converter with 6 second doafter
// engi -> saboteur
// medi -> idk reskin it
// other -> assault
/// <summary>
/// Main ninja system that handles ninja setup, provides helper methods for the rest of the code to use.
/// </summary>
@@ -44,13 +37,11 @@ public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem
[Dependency] private readonly RoleSystem _role = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedMindSystem _mind = default!;
[Dependency] private readonly StealthClothingSystem _stealthClothing = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SpaceNinjaComponent, GenericAntagCreatedEvent>(OnNinjaCreated);
SubscribeLocalEvent<SpaceNinjaComponent, EmaggedSomethingEvent>(OnDoorjack);
SubscribeLocalEvent<SpaceNinjaComponent, ResearchStolenEvent>(OnResearchStolen);
SubscribeLocalEvent<SpaceNinjaComponent, ThreatCalledInEvent>(OnThreatCalledIn);
@@ -62,7 +53,7 @@ public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem
var query = EntityQueryEnumerator<SpaceNinjaComponent>();
while (query.MoveNext(out var uid, out var ninja))
{
UpdateNinja(uid, ninja, frameTime);
SetSuitPowerAlert((uid, ninja));
}
}
@@ -80,31 +71,13 @@ public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem
return newCount - oldCount;
}
/// <summary>
/// Returns a ninja's gamerule config data.
/// If the gamerule was not started then it will be started automatically.
/// </summary>
public NinjaRuleComponent? NinjaRule(EntityUid uid, GenericAntagComponent? comp = null)
{
if (!Resolve(uid, ref comp))
return null;
// mind not added yet so no rule
if (comp.RuleEntity == null)
return null;
return CompOrNull<NinjaRuleComponent>(comp.RuleEntity);
}
// TODO: can probably copy paste borg code here
/// <summary>
/// Update the alert for the ninja's suit power indicator.
/// </summary>
public void SetSuitPowerAlert(EntityUid uid, SpaceNinjaComponent? comp = null)
public void SetSuitPowerAlert(Entity<SpaceNinjaComponent> ent)
{
if (!Resolve(uid, ref comp, false))
return;
var (uid, comp) = ent;
if (comp.Deleted || comp.Suit == null)
{
_alerts.ClearAlert(uid, comp.SuitPowerAlert);
@@ -145,53 +118,6 @@ public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem
return GetNinjaBattery(user, out var uid, out var battery) && _battery.TryUseCharge(uid.Value, charge, battery);
}
/// <summary>
/// Set up everything for ninja to work and send the greeting message/sound.
/// Objectives are added by <see cref="GenericAntagSystem"/>.
/// </summary>
private void OnNinjaCreated(EntityUid uid, SpaceNinjaComponent comp, ref GenericAntagCreatedEvent args)
{
var mindId = args.MindId;
var mind = args.Mind;
if (mind.Session == null)
return;
var config = NinjaRule(uid);
if (config == null)
return;
var role = new NinjaRoleComponent
{
PrototypeId = "SpaceNinja"
};
_role.MindAddRole(mindId, role, mind);
_role.MindPlaySound(mindId, config.GreetingSound, mind);
var session = mind.Session;
_audio.PlayGlobal(config.GreetingSound, Filter.Empty().AddPlayer(session), false, AudioParams.Default);
_chatMan.DispatchServerMessage(session, Loc.GetString("ninja-role-greeting"));
}
// TODO: PowerCellDraw, modify when cloak enabled
/// <summary>
/// Handle constant power drains from passive usage and cloak.
/// </summary>
private void UpdateNinja(EntityUid uid, SpaceNinjaComponent ninja, float frameTime)
{
if (ninja.Suit == null)
return;
float wattage = Suit.SuitWattage(ninja.Suit.Value);
SetSuitPowerAlert(uid, ninja);
if (!TryUseCharge(uid, wattage * frameTime))
{
// ran out of power, uncloak ninja
_stealthClothing.SetEnabled(ninja.Suit.Value, uid, false);
}
}
/// <summary>
/// Increment greentext when emagging a door.
/// </summary>

View File

@@ -7,6 +7,7 @@ using Content.Server.Roles;
using Content.Server.Sticky.Events;
using Content.Shared.Interaction;
using Content.Shared.Ninja.Components;
using Content.Shared.Ninja.Systems;
using Robust.Shared.GameObjects;
namespace Content.Server.Ninja.Systems;
@@ -14,7 +15,7 @@ namespace Content.Server.Ninja.Systems;
/// <summary>
/// Prevents planting a spider charge outside of its location and handles greentext.
/// </summary>
public sealed class SpiderChargeSystem : EntitySystem
public sealed class SpiderChargeSystem : SharedSpiderChargeSystem
{
[Dependency] private readonly MindSystem _mind = default!;
[Dependency] private readonly PopupSystem _popup = default!;

View File

@@ -6,10 +6,11 @@ using Content.Shared.Ninja.Components;
using Content.Shared.Ninja.Systems;
using Content.Shared.Popups;
using Content.Shared.Stunnable;
using Robust.Shared.Prototypes;
using Content.Shared.Timing;
using Content.Shared.Whitelist;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Timing;
using Content.Shared.Whitelist;
using Robust.Shared.Prototypes;
namespace Content.Server.Ninja.Systems;
@@ -20,12 +21,12 @@ public sealed class StunProviderSystem : SharedStunProviderSystem
{
[Dependency] private readonly BatterySystem _battery = default!;
[Dependency] private readonly DamageableSystem _damageable = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedNinjaGlovesSystem _gloves = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedStunSystem _stun = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
[Dependency] private readonly UseDelaySystem _useDelay = default!;
public override void Initialize()
{
@@ -38,16 +39,18 @@ public sealed class StunProviderSystem : SharedStunProviderSystem
/// <summary>
/// Stun clicked mobs on the whitelist, if there is enough power.
/// </summary>
private void OnBeforeInteractHand(EntityUid uid, StunProviderComponent comp, BeforeInteractHandEvent args)
private void OnBeforeInteractHand(Entity<StunProviderComponent> ent, ref BeforeInteractHandEvent args)
{
// TODO: generic check
var (uid, comp) = ent;
if (args.Handled || comp.BatteryUid == null || !_gloves.AbilityCheck(uid, args, out var target))
return;
if (target == uid || _whitelistSystem.IsWhitelistFail(comp.Whitelist, target))
if (target == uid || _whitelist.IsWhitelistFail(comp.Whitelist, target))
return;
if (_timing.CurTime < comp.NextStun)
var useDelay = EnsureComp<UseDelayComponent>(uid);
if (_useDelay.IsDelayed((uid, useDelay), id: comp.DelayId))
return;
// take charge from battery
@@ -63,13 +66,14 @@ public sealed class StunProviderSystem : SharedStunProviderSystem
_stun.TryParalyze(target, comp.StunTime, refresh: false);
// short cooldown to prevent instant stunlocking
comp.NextStun = _timing.CurTime + comp.Cooldown;
_useDelay.SetLength((uid, useDelay), comp.Cooldown, id: comp.DelayId);
_useDelay.TryResetDelay((uid, useDelay), id: comp.DelayId);
args.Handled = true;
}
private void OnBatteryChanged(EntityUid uid, StunProviderComponent comp, ref NinjaBatteryChangedEvent args)
private void OnBatteryChanged(Entity<StunProviderComponent> ent, ref NinjaBatteryChangedEvent args)
{
SetBattery(uid, args.Battery, comp);
SetBattery((ent, ent.Comp), args.Battery);
}
}

View File

@@ -35,20 +35,6 @@ public sealed class CodeConditionSystem : EntitySystem
return ent.Comp.Completed;
}
/// <summary>
/// Returns true if a mob's objective with a certain prototype is completed.
/// </summary>
public bool IsCompleted(Entity<MindContainerComponent?> mob, string prototype)
{
if (_mind.GetMind(mob, mob.Comp) is not {} mindId)
return false;
if (!_mind.TryFindObjective(mindId, prototype, out var obj))
return false;
return IsCompleted(obj.Value);
}
/// <summary>
/// Sets an objective's completed field.
/// </summary>

View File

@@ -1,4 +1,5 @@
using Content.Server.Power.Components;
using Content.Shared.Item.ItemToggle.Components;
using Content.Shared.PowerCell;
using Content.Shared.PowerCell.Components;
@@ -10,22 +11,20 @@ public sealed partial class PowerCellSystem
* Handles PowerCellDraw
*/
private static readonly TimeSpan Delay = TimeSpan.FromSeconds(1);
public override void Update(float frameTime)
{
base.Update(frameTime);
var query = EntityQueryEnumerator<PowerCellDrawComponent, PowerCellSlotComponent>();
var query = EntityQueryEnumerator<PowerCellDrawComponent, PowerCellSlotComponent, ItemToggleComponent>();
while (query.MoveNext(out var uid, out var comp, out var slot))
while (query.MoveNext(out var uid, out var comp, out var slot, out var toggle))
{
if (!comp.Drawing)
if (!comp.Enabled || !toggle.Activated)
continue;
if (Timing.CurTime < comp.NextUpdateTime)
continue;
comp.NextUpdateTime += Delay;
comp.NextUpdateTime += comp.Delay;
if (!TryGetBatteryFromSlot(uid, out var batteryEnt, out var battery, slot))
continue;
@@ -33,7 +32,8 @@ public sealed partial class PowerCellSystem
if (_battery.TryUseCharge(batteryEnt.Value, comp.DrawRate, battery))
continue;
comp.Drawing = false;
Toggle.TryDeactivate((uid, toggle));
var ev = new PowerCellSlotEmptyEvent();
RaiseLocalEvent(uid, ref ev);
}
@@ -42,26 +42,9 @@ public sealed partial class PowerCellSystem
private void OnDrawChargeChanged(EntityUid uid, PowerCellDrawComponent component, ref ChargeChangedEvent args)
{
// Update the bools for client prediction.
bool canDraw;
bool canUse;
var canUse = component.UseRate <= 0f || args.Charge > component.UseRate;
if (component.UseRate > 0f)
{
canUse = args.Charge > component.UseRate;
}
else
{
canUse = true;
}
if (component.DrawRate > 0f)
{
canDraw = args.Charge > 0f;
}
else
{
canDraw = true;
}
var canDraw = component.DrawRate <= 0f || args.Charge > 0f;
if (canUse != component.CanUse || canDraw != component.CanDraw)
{
@@ -76,6 +59,9 @@ public sealed partial class PowerCellSystem
var canDraw = !args.Ejected && HasCharge(uid, float.MinValue);
var canUse = !args.Ejected && HasActivatableCharge(uid, component);
if (!canDraw)
Toggle.TryDeactivate(uid);
if (canUse != component.CanUse || canDraw != component.CanDraw)
{
component.CanDraw = canDraw;

View File

@@ -39,8 +39,8 @@ public sealed partial class PowerCellSystem : SharedPowerCellSystem
SubscribeLocalEvent<PowerCellDrawComponent, ChargeChangedEvent>(OnDrawChargeChanged);
SubscribeLocalEvent<PowerCellDrawComponent, PowerCellChangedEvent>(OnDrawCellChanged);
// funny
SubscribeLocalEvent<PowerCellSlotComponent, ExaminedEvent>(OnCellSlotExamined);
// funny
SubscribeLocalEvent<PowerCellSlotComponent, BeingMicrowavedEvent>(OnSlotMicrowaved);
}

View File

@@ -29,7 +29,7 @@ public sealed partial class BorgSystem
if (!TryComp<BorgChassisComponent>(chassis, out var chassisComp) ||
args.Container != chassisComp.ModuleContainer ||
!chassisComp.Activated)
!Toggle.IsActivated(chassis))
return;
if (!_powerCell.HasDrawCharge(uid))
@@ -143,6 +143,7 @@ public sealed partial class BorgSystem
var ev = new BorgModuleSelectedEvent(chassis);
RaiseLocalEvent(moduleUid, ref ev);
chassisComp.SelectedModule = moduleUid;
Dirty(chassis, chassisComp);
}
/// <summary>
@@ -162,6 +163,7 @@ public sealed partial class BorgSystem
var ev = new BorgModuleUnselectedEvent(chassis);
RaiseLocalEvent(chassisComp.SelectedModule.Value, ref ev);
chassisComp.SelectedModule = null;
Dirty(chassis, chassisComp);
}
private void OnItemModuleSelected(EntityUid uid, ItemBorgModuleComponent component, ref BorgModuleSelectedEvent args)

View File

@@ -10,6 +10,7 @@ using Content.Shared.Alert;
using Content.Shared.Database;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Item.ItemToggle.Components;
using Content.Shared.Mind;
using Content.Shared.Mind.Components;
using Content.Shared.Mobs;
@@ -73,6 +74,7 @@ public sealed partial class BorgSystem : SharedBorgSystem
SubscribeLocalEvent<BorgChassisComponent, PowerCellChangedEvent>(OnPowerCellChanged);
SubscribeLocalEvent<BorgChassisComponent, PowerCellSlotEmptyEvent>(OnPowerCellSlotEmpty);
SubscribeLocalEvent<BorgChassisComponent, GetCharactedDeadIcEvent>(OnGetDeadIC);
SubscribeLocalEvent<BorgChassisComponent, ItemToggledEvent>(OnToggled);
SubscribeLocalEvent<BorgBrainComponent, MindAddedMessage>(OnBrainMindAdded);
SubscribeLocalEvent<BorgBrainComponent, PointAttemptEvent>(OnBrainPointAttempt);
@@ -173,11 +175,11 @@ public sealed partial class BorgSystem : SharedBorgSystem
if (args.NewMobState == MobState.Alive)
{
if (_mind.TryGetMind(uid, out _, out _))
_powerCell.SetPowerCellDrawEnabled(uid, true);
_powerCell.SetDrawEnabled(uid, true);
}
else
{
_powerCell.SetPowerCellDrawEnabled(uid, false);
_powerCell.SetDrawEnabled(uid, false);
}
}
@@ -185,24 +187,10 @@ public sealed partial class BorgSystem : SharedBorgSystem
{
UpdateBatteryAlert((uid, component));
if (!TryComp<PowerCellDrawComponent>(uid, out var draw))
return;
// if we eject the battery or run out of charge, then disable
if (args.Ejected || !_powerCell.HasDrawCharge(uid))
{
DisableBorgAbilities(uid, component);
return;
}
// if we aren't drawing and suddenly get enough power to draw again, reeanble.
if (_powerCell.HasDrawCharge(uid, draw))
if (_powerCell.HasDrawCharge(uid))
{
// only reenable the powerdraw if a player has the role.
if (!draw.Drawing && _mind.TryGetMind(uid, out _, out _) && _mobState.IsAlive(uid))
_powerCell.SetPowerCellDrawEnabled(uid, true);
EnableBorgAbilities(uid, component);
Toggle.TryActivate(uid);
}
UpdateUI(uid, component);
@@ -210,7 +198,7 @@ public sealed partial class BorgSystem : SharedBorgSystem
private void OnPowerCellSlotEmpty(EntityUid uid, BorgChassisComponent component, ref PowerCellSlotEmptyEvent args)
{
DisableBorgAbilities(uid, component);
Toggle.TryDeactivate(uid);
UpdateUI(uid, component);
}
@@ -219,6 +207,23 @@ public sealed partial class BorgSystem : SharedBorgSystem
args.Dead = true;
}
private void OnToggled(Entity<BorgChassisComponent> ent, ref ItemToggledEvent args)
{
var (uid, comp) = ent;
if (args.Activated)
InstallAllModules(uid, comp);
else
DisableAllModules(uid, comp);
// only enable the powerdraw if there is a player in the chassis
var drawing = _mind.TryGetMind(uid, out _, out _) && _mobState.IsAlive(ent);
_powerCell.SetDrawEnabled(uid, drawing);
UpdateUI(uid, comp);
_movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
}
private void OnBrainMindAdded(EntityUid uid, BorgBrainComponent component, MindAddedMessage args)
{
if (!Container.TryGetOuterContainer(uid, Transform(uid), out var container))
@@ -271,44 +276,14 @@ public sealed partial class BorgSystem : SharedBorgSystem
_alerts.ShowAlert(ent, ent.Comp.BatteryAlert, chargePercent);
}
/// <summary>
/// Activates the borg, enabling all of its modules.
/// </summary>
public void EnableBorgAbilities(EntityUid uid, BorgChassisComponent component, PowerCellDrawComponent? powerCell = null)
{
if (component.Activated)
return;
component.Activated = true;
InstallAllModules(uid, component);
Dirty(uid, component);
_movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
}
/// <summary>
/// Deactivates the borg, disabling all of its modules and decreasing its speed.
/// </summary>
public void DisableBorgAbilities(EntityUid uid, BorgChassisComponent component)
{
if (!component.Activated)
return;
component.Activated = false;
DisableAllModules(uid, component);
Dirty(uid, component);
_movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
}
/// <summary>
/// Activates a borg when a player occupies it
/// </summary>
public void BorgActivate(EntityUid uid, BorgChassisComponent component)
{
Popup.PopupEntity(Loc.GetString("borg-mind-added", ("name", Identity.Name(uid, EntityManager))), uid);
_powerCell.SetPowerCellDrawEnabled(uid, true);
_access.SetAccessEnabled(uid, true);
Toggle.TryActivate(uid);
_appearance.SetData(uid, BorgVisuals.HasPlayer, true);
Dirty(uid, component);
}
/// <summary>
@@ -317,10 +292,8 @@ public sealed partial class BorgSystem : SharedBorgSystem
public void BorgDeactivate(EntityUid uid, BorgChassisComponent component)
{
Popup.PopupEntity(Loc.GetString("borg-mind-removed", ("name", Identity.Name(uid, EntityManager))), uid);
_powerCell.SetPowerCellDrawEnabled(uid, false);
_access.SetAccessEnabled(uid, false);
Toggle.TryDeactivate(uid);
_appearance.SetData(uid, BorgVisuals.HasPlayer, false);
Dirty(uid, component);
}
/// <summary>

View File

@@ -1,16 +0,0 @@
using Content.Server.StationEvents.Events;
namespace Content.Server.StationEvents.Components;
/// <summary>
/// Configuration component for the Space Ninja antag.
/// </summary>
[RegisterComponent, Access(typeof(NinjaSpawnRule))]
public sealed partial class NinjaSpawnRuleComponent : Component
{
/// <summary>
/// Distance that the ninja spawns from the station's half AABB radius
/// </summary>
[DataField("spawnDistance")]
public float SpawnDistance = 20f;
}

View File

@@ -0,0 +1,25 @@
using Content.Server.StationEvents.Events;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
namespace Content.Server.StationEvents.Components;
/// <summary>
/// Component for spawning antags in space around a station.
/// Requires <c>AntagSelectionComponent</c>.
/// </summary>
[RegisterComponent, Access(typeof(SpaceSpawnRule))]
public sealed partial class SpaceSpawnRuleComponent : Component
{
/// <summary>
/// Distance that the entity spawns from the station's half AABB radius
/// </summary>
[DataField]
public float SpawnDistance = 20f;
/// <summary>
/// Location that was picked.
/// </summary>
[DataField]
public MapCoordinates? Coords;
}

View File

@@ -1,5 +1,5 @@
using Content.Server.Antag;
using Content.Server.GameTicking.Rules.Components;
using Content.Server.Ninja.Systems;
using Content.Server.Station.Components;
using Content.Server.StationEvents.Components;
using Content.Shared.GameTicking.Components;
@@ -9,18 +9,28 @@ using Robust.Shared.Map.Components;
namespace Content.Server.StationEvents.Events;
/// <summary>
/// Event for spawning a Space Ninja mid-game.
/// Station event component for spawning this rules antags in space around a station.
/// </summary>
public sealed class NinjaSpawnRule : StationEventSystem<NinjaSpawnRuleComponent>
public sealed class SpaceSpawnRule : StationEventSystem<SpaceSpawnRuleComponent>
{
[Dependency] private readonly SharedTransformSystem _transform = default!;
protected override void Started(EntityUid uid, NinjaSpawnRuleComponent comp, GameRuleComponent gameRule, GameRuleStartedEvent args)
public override void Initialize()
{
base.Started(uid, comp, gameRule, args);
base.Initialize();
SubscribeLocalEvent<SpaceSpawnRuleComponent, AntagSelectLocationEvent>(OnSelectLocation);
}
protected override void Added(EntityUid uid, SpaceSpawnRuleComponent comp, GameRuleComponent gameRule, GameRuleAddedEvent args)
{
base.Added(uid, comp, gameRule, args);
if (!TryGetRandomStation(out var station))
{
ForceEndSelf(uid, gameRule);
return;
}
var stationData = Comp<StationDataComponent>(station.Value);
@@ -28,22 +38,28 @@ public sealed class NinjaSpawnRule : StationEventSystem<NinjaSpawnRuleComponent>
var gridUid = StationSystem.GetLargestGrid(stationData);
if (gridUid == null || !TryComp<MapGridComponent>(gridUid, out var grid))
{
Sawmill.Warning("Chosen station has no grids, cannot spawn space ninja!");
Sawmill.Warning("Chosen station has no grids, cannot pick location for {ToPrettyString(uid):rule}");
ForceEndSelf(uid, gameRule);
return;
}
// figure out its AABB size and use that as a guide to how far ninja should be
// figure out its AABB size and use that as a guide to how far the spawner should be
var size = grid.LocalAABB.Size.Length() / 2;
var distance = size + comp.SpawnDistance;
var angle = RobustRandom.NextAngle();
// position relative to station center
var location = angle.ToVec() * distance;
// create the spawner, the ninja will appear when a ghost has picked the role
// create the spawner!
var xform = Transform(gridUid.Value);
var position = _transform.GetWorldPosition(xform) + location;
var coords = new MapCoordinates(position, xform.MapID);
Sawmill.Info($"Creating ninja spawnpoint at {coords}");
Spawn("SpawnPointGhostSpaceNinja", coords);
comp.Coords = new MapCoordinates(position, xform.MapID);
Sawmill.Info($"Picked location {comp.Coords} for {ToPrettyString(uid):rule}");
}
private void OnSelectLocation(Entity<SpaceSpawnRuleComponent> ent, ref AntagSelectLocationEvent args)
{
if (ent.Comp.Coords is {} coords)
args.Coordinates.Add(coords);
}
}

View File

@@ -19,7 +19,7 @@ namespace Content.Server.Stunnable.Systems
[Dependency] private readonly RiggableSystem _riggableSystem = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly BatterySystem _battery = default!;
[Dependency] private readonly SharedItemToggleSystem _itemToggle = default!;
[Dependency] private readonly ItemToggleSystem _itemToggle = default!;
public override void Initialize()
{

View File

@@ -1,4 +1,5 @@
using Content.Server.PowerCell;
using Content.Shared.Item.ItemToggle;
using Content.Shared.PowerCell;
using Content.Shared.Weapons.Misc;
using Robust.Shared.Physics.Components;
@@ -8,6 +9,7 @@ namespace Content.Server.Weapons.Misc;
public sealed class TetherGunSystem : SharedTetherGunSystem
{
[Dependency] private readonly PowerCellSystem _cell = default!;
[Dependency] private readonly ItemToggleSystem _toggle = default!;
public override void Initialize()
{
@@ -36,12 +38,12 @@ public sealed class TetherGunSystem : SharedTetherGunSystem
PhysicsComponent? targetPhysics = null, TransformComponent? targetXform = null)
{
base.StartTether(gunUid, component, target, user, targetPhysics, targetXform);
_cell.SetPowerCellDrawEnabled(gunUid, true);
_toggle.TryActivate(gunUid);
}
protected override void StopTether(EntityUid gunUid, BaseForceGunComponent component, bool land = true, bool transfer = false)
{
base.StopTether(gunUid, component, land, transfer);
_cell.SetPowerCellDrawEnabled(gunUid, false);
_toggle.TryDeactivate(gunUid);
}
}

View File

@@ -2,6 +2,7 @@ using System.Linq;
using Content.Server.Salvage;
using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
using Content.Shared.Clothing;
using Content.Shared.Item.ItemToggle.Components;
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
@@ -29,11 +30,11 @@ public sealed class ArtifactMagnetTriggerSystem : EntitySystem
_toActivate.Clear();
//assume that there's more instruments than artifacts
var query = EntityQueryEnumerator<MagbootsComponent, TransformComponent>();
while (query.MoveNext(out _, out var magboot, out var magXform))
//assume that there's more magboots than artifacts
var query = EntityQueryEnumerator<MagbootsComponent, TransformComponent, ItemToggleComponent>();
while (query.MoveNext(out _, out var magboot, out var magXform, out var toggle))
{
if (!magboot.On)
if (!toggle.Activated)
continue;
var artiQuery = EntityQueryEnumerator<ArtifactMagnetTriggerComponent, TransformComponent>();

View File

@@ -0,0 +1,11 @@
using Content.Shared.Access.Systems;
using Robust.Shared.GameStates;
namespace Content.Shared.Access.Components;
/// <summary>
/// Toggles an access provider with <c>ItemToggle</c>.
/// Requires <see cref="AccessComponent"/>.
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(AccessToggleSystem))]
public sealed partial class AccessToggleComponent : Component;

View File

@@ -0,0 +1,21 @@
using Content.Shared.Access.Components;
using Content.Shared.Item.ItemToggle.Components;
namespace Content.Shared.Access.Systems;
public sealed class AccessToggleSystem : EntitySystem
{
[Dependency] private readonly SharedAccessSystem _access = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AccessToggleComponent, ItemToggledEvent>(OnToggled);
}
private void OnToggled(Entity<AccessToggleComponent> ent, ref ItemToggledEvent args)
{
_access.SetAccessEnabled(ent, args.Activated);
}
}

View File

@@ -10,15 +10,12 @@ namespace Content.Shared.Beeper.Components;
/// This is used for an item that beeps based on
/// proximity to a specified component.
/// </summary>
/// <remarks>
/// Requires <c>ItemToggleComponent</c> to control it.
/// </remarks>
[RegisterComponent, NetworkedComponent, Access(typeof(BeeperSystem)), AutoGenerateComponentState]
public sealed partial class BeeperComponent : Component
{
/// <summary>
/// Whether or not it's on.
/// </summary>
[DataField, AutoNetworkedField]
public bool Enabled = true;
/// <summary>
/// How much to scale the interval by (< 0 = min, > 1 = max)
/// </summary>
@@ -56,7 +53,7 @@ public sealed partial class BeeperComponent : Component
/// Is the beep muted
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
public bool IsMuted = false;
public bool IsMuted;
/// <summary>
/// The sound played when the locator beeps.

View File

@@ -1,5 +1,7 @@
using Content.Shared.Beeper.Components;
using Content.Shared.FixedPoint;
using Content.Shared.Item.ItemToggle;
using Content.Shared.Item.ItemToggle.Components;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Network;
using Robust.Shared.Timing;
@@ -11,34 +13,20 @@ namespace Content.Shared.Beeper.Systems;
public sealed class BeeperSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly INetManager _net = default!;
public override void Initialize()
{
}
[Dependency] private readonly ItemToggleSystem _toggle = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
public override void Update(float frameTime)
{
var query = EntityQueryEnumerator<BeeperComponent>();
while (query.MoveNext(out var uid, out var beeper))
var query = EntityQueryEnumerator<BeeperComponent, ItemToggleComponent>();
while (query.MoveNext(out var uid, out var beeper, out var toggle))
{
if (!beeper.Enabled)
continue;
if (toggle.Activated)
RunUpdate_Internal(uid, beeper);
}
}
public void SetEnable(EntityUid owner, bool isEnabled, BeeperComponent? beeper = null)
{
if (!Resolve(owner, ref beeper) || beeper.Enabled == isEnabled)
return;
beeper.Enabled = isEnabled;
RunUpdate_Internal(owner, beeper);
Dirty(owner, beeper);
}
public void SetIntervalScaling(EntityUid owner, BeeperComponent beeper, FixedPoint2 newScaling)
{
newScaling = FixedPoint2.Clamp(newScaling, 0, 1);
@@ -70,6 +58,7 @@ public sealed class BeeperSystem : EntitySystem
if (!Resolve(owner, ref comp))
return;
comp.IsMuted = isMuted;
Dirty(owner, comp);
}
private void UpdateBeepInterval(EntityUid owner, BeeperComponent beeper)
@@ -91,19 +80,17 @@ public sealed class BeeperSystem : EntitySystem
private void RunUpdate_Internal(EntityUid owner, BeeperComponent beeper)
{
if (!beeper.Enabled)
{
if (!_toggle.IsActivated(owner))
return;
}
UpdateBeepInterval(owner, beeper);
if (beeper.NextBeep >= _timing.CurTime)
return;
var beepEvent = new BeepPlayedEvent(beeper.IsMuted);
RaiseLocalEvent(owner, ref beepEvent);
if (!beeper.IsMuted && _net.IsServer)
{
_audio.PlayPvs(beeper.BeepSound, owner);
}
beeper.LastBeepTime = _timing.CurTime;
}
}

View File

@@ -1,7 +1,6 @@
using Content.Shared.Beeper.Components;
using Content.Shared.Interaction.Events;
using Content.Shared.Item.ItemToggle;
using Content.Shared.Pinpointer;
using Content.Shared.PowerCell;
using Content.Shared.ProximityDetection;
using Content.Shared.ProximityDetection.Components;
using Content.Shared.ProximityDetection.Systems;
@@ -9,20 +8,17 @@ using Content.Shared.ProximityDetection.Systems;
namespace Content.Shared.Beeper.Systems;
/// <summary>
/// This handles logic for implementing proximity beeper as a handheld tool />
/// This handles controlling a beeper from proximity detector events.
/// </summary>
public sealed class ProximityBeeperSystem : EntitySystem
{
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedPowerCellSystem _powerCell = default!;
[Dependency] private readonly ProximityDetectionSystem _proximity = default!;
[Dependency] private readonly BeeperSystem _beeper = default!;
/// <inheritdoc/>
public override void Initialize()
{
SubscribeLocalEvent<ProximityBeeperComponent, UseInHandEvent>(OnUseInHand);
SubscribeLocalEvent<ProximityBeeperComponent, PowerCellSlotEmptyEvent>(OnPowerCellSlotEmpty);
SubscribeLocalEvent<ProximityBeeperComponent, NewProximityTargetEvent>(OnNewProximityTarget);
SubscribeLocalEvent<ProximityBeeperComponent, ProximityTargetUpdatedEvent>(OnProximityTargetUpdate);
}
@@ -33,82 +29,16 @@ public sealed class ProximityBeeperSystem : EntitySystem
return;
if (args.Target == null)
{
_beeper.SetEnable(owner, false, beeper);
_beeper.SetMute(owner, true, beeper);
return;
}
_beeper.SetIntervalScaling(owner,args.Distance/args.Detector.Range, beeper);
_beeper.SetEnable(owner, true, beeper);
_beeper.SetIntervalScaling(owner, args.Distance / args.Detector.Range, beeper);
_beeper.SetMute(owner, false, beeper);
}
private void OnNewProximityTarget(EntityUid owner, ProximityBeeperComponent proxBeeper, ref NewProximityTargetEvent args)
{
_beeper.SetEnable(owner, args.Target != null);
}
private void OnUseInHand(EntityUid uid, ProximityBeeperComponent proxBeeper, UseInHandEvent args)
{
if (args.Handled)
return;
args.Handled = TryToggle(uid, proxBeeper, user: args.User);
}
private void OnPowerCellSlotEmpty(EntityUid uid, ProximityBeeperComponent beeper, ref PowerCellSlotEmptyEvent args)
{
if (_proximity.GetEnable(uid))
TryDisable(uid);
}
public bool TryEnable(EntityUid owner, BeeperComponent? beeper = null, ProximityDetectorComponent? detector = null,
PowerCellDrawComponent? draw = null,EntityUid? user = null)
{
if (!Resolve(owner, ref beeper, ref detector))
return false;
if (Resolve(owner, ref draw, false) && !_powerCell.HasActivatableCharge(owner, battery: draw, user: user))
return false;
Enable(owner, beeper, detector, draw);
return true;
}
private void Enable(EntityUid owner, BeeperComponent beeper,
ProximityDetectorComponent detector, PowerCellDrawComponent? draw)
{
_proximity.SetEnable(owner, true, detector);
_appearance.SetData(owner, ProximityBeeperVisuals.Enabled, true);
_powerCell.SetPowerCellDrawEnabled(owner, true, draw);
}
/// <summary>
/// Disables the proximity beeper
/// </summary>
public bool TryDisable(EntityUid owner,BeeperComponent? beeper = null, ProximityDetectorComponent? detector = null, PowerCellDrawComponent? draw = null)
{
if (!Resolve(owner, ref beeper, ref detector))
return false;
if (!detector.Enabled)
return false;
Disable(owner, beeper, detector, draw);
return true;
}
private void Disable(EntityUid owner, BeeperComponent beeper,
ProximityDetectorComponent detector, PowerCellDrawComponent? draw)
{
_proximity.SetEnable(owner, false, detector);
_appearance.SetData(owner, ProximityBeeperVisuals.Enabled, false);
_beeper.SetEnable(owner, false, beeper);
_powerCell.SetPowerCellDrawEnabled(owner, false, draw);
}
/// <summary>
/// toggles the proximity beeper
/// </summary>
public bool TryToggle(EntityUid owner, ProximityBeeperComponent? proxBeeper = null, BeeperComponent? beeper = null, ProximityDetectorComponent? detector = null,
PowerCellDrawComponent? draw = null, EntityUid? user = null)
{
if (!Resolve(owner, ref proxBeeper, ref beeper, ref detector))
return false;
return detector.Enabled
? TryDisable(owner, beeper, detector, draw)
: TryEnable(owner, beeper, detector, draw,user);
_beeper.SetMute(owner, args.Target != null);
}
}

View File

@@ -5,10 +5,14 @@ namespace Content.Shared.Charges.Systems;
public abstract class SharedChargesSystem : EntitySystem
{
protected EntityQuery<LimitedChargesComponent> Query;
public override void Initialize()
{
base.Initialize();
Query = GetEntityQuery<LimitedChargesComponent>();
SubscribeLocalEvent<LimitedChargesComponent, ExaminedEvent>(OnExamine);
}
@@ -30,9 +34,9 @@ public abstract class SharedChargesSystem : EntitySystem
/// <summary>
/// Tries to add a number of charges. If it over or underflows it will be clamped, wasting the extra charges.
/// </summary>
public void AddCharges(EntityUid uid, int change, LimitedChargesComponent? comp = null)
public virtual void AddCharges(EntityUid uid, int change, LimitedChargesComponent? comp = null)
{
if (!Resolve(uid, ref comp, false))
if (!Query.Resolve(uid, ref comp, false))
return;
var old = comp.Charges;
@@ -47,7 +51,7 @@ public abstract class SharedChargesSystem : EntitySystem
public bool IsEmpty(EntityUid uid, LimitedChargesComponent? comp = null)
{
// can't be empty if there are no limited charges
if (!Resolve(uid, ref comp, false))
if (!Query.Resolve(uid, ref comp, false))
return false;
return comp.Charges <= 0;
@@ -56,12 +60,26 @@ public abstract class SharedChargesSystem : EntitySystem
/// <summary>
/// Uses a single charge. Must check IsEmpty beforehand to prevent using with 0 charge.
/// </summary>
public virtual void UseCharge(EntityUid uid, LimitedChargesComponent? comp = null)
public void UseCharge(EntityUid uid, LimitedChargesComponent? comp = null)
{
if (Resolve(uid, ref comp, false))
AddCharges(uid, -1, comp);
}
/// <summary>
/// Checks IsEmpty and uses a charge if it isn't empty.
/// </summary>
public bool TryUseCharge(Entity<LimitedChargesComponent?> ent)
{
if (!Query.Resolve(ent, ref ent.Comp, false))
return true;
if (IsEmpty(ent, ent.Comp))
return false;
UseCharge(ent, ent.Comp);
return true;
}
/// <summary>
/// Gets the limited charges component and returns true if the number of charges remaining is less than the specified value.
/// Will return false if there is no limited charges component.
@@ -80,7 +98,6 @@ public abstract class SharedChargesSystem : EntitySystem
/// </summary>
public virtual void UseCharges(EntityUid uid, int chargesUsed, LimitedChargesComponent? comp = null)
{
if (Resolve(uid, ref comp, false))
AddCharges(uid, -chargesUsed, comp);
}
}

View File

@@ -3,20 +3,18 @@ using Robust.Shared.Serialization;
namespace Content.Shared.Clothing;
/// <summary>
/// Modifies speed when worn and activated.
/// Supports <c>ItemToggleComponent</c>.
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(ClothingSpeedModifierSystem))]
public sealed partial class ClothingSpeedModifierComponent : Component
{
[DataField("walkModifier", required: true)] [ViewVariables(VVAccess.ReadWrite)]
[DataField]
public float WalkModifier = 1.0f;
[DataField("sprintModifier", required: true)] [ViewVariables(VVAccess.ReadWrite)]
[DataField]
public float SprintModifier = 1.0f;
/// <summary>
/// Is this clothing item currently 'actively' slowing you down?
/// e.g. magboots can be turned on and off.
/// </summary>
[DataField("enabled")] public bool Enabled = true;
}
[Serializable, NetSerializable]
@@ -25,12 +23,9 @@ public sealed class ClothingSpeedModifierComponentState : ComponentState
public float WalkModifier;
public float SprintModifier;
public bool Enabled;
public ClothingSpeedModifierComponentState(float walkModifier, float sprintModifier, bool enabled)
public ClothingSpeedModifierComponentState(float walkModifier, float sprintModifier)
{
WalkModifier = walkModifier;
SprintModifier = sprintModifier;
Enabled = enabled;
}
}

View File

@@ -1,11 +1,10 @@
using Content.Shared.Actions;
using Content.Shared.Clothing.Components;
using Content.Shared.Examine;
using Content.Shared.IdentityManagement;
using Content.Shared.Inventory;
using Content.Shared.Item.ItemToggle;
using Content.Shared.Item.ItemToggle.Components;
using Content.Shared.Movement.Systems;
using Content.Shared.PowerCell;
using Content.Shared.Toggleable;
using Content.Shared.Verbs;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
@@ -15,12 +14,12 @@ namespace Content.Shared.Clothing;
public sealed class ClothingSpeedModifierSystem : EntitySystem
{
[Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly ClothingSpeedModifierSystem _clothingSpeedModifier = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly ExamineSystemShared _examine = default!;
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeed = default!;
[Dependency] private readonly ItemToggleSystem _toggle = default!;
[Dependency] private readonly SharedPowerCellSystem _powerCell = default!;
public override void Initialize()
@@ -31,39 +30,12 @@ public sealed class ClothingSpeedModifierSystem : EntitySystem
SubscribeLocalEvent<ClothingSpeedModifierComponent, ComponentHandleState>(OnHandleState);
SubscribeLocalEvent<ClothingSpeedModifierComponent, InventoryRelayedEvent<RefreshMovementSpeedModifiersEvent>>(OnRefreshMoveSpeed);
SubscribeLocalEvent<ClothingSpeedModifierComponent, GetVerbsEvent<ExamineVerb>>(OnClothingVerbExamine);
SubscribeLocalEvent<ToggleClothingSpeedComponent, GetVerbsEvent<ActivationVerb>>(AddToggleVerb);
SubscribeLocalEvent<ToggleClothingSpeedComponent, GetItemActionsEvent>(OnGetActions);
SubscribeLocalEvent<ToggleClothingSpeedComponent, ToggleClothingSpeedEvent>(OnToggleSpeed);
SubscribeLocalEvent<ToggleClothingSpeedComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<ToggleClothingSpeedComponent, PowerCellSlotEmptyEvent>(OnPowerCellSlotEmpty);
SubscribeLocalEvent<ClothingSpeedModifierComponent, ItemToggledEvent>(OnToggled);
}
// Public API
public void SetClothingSpeedModifierEnabled(EntityUid uid, bool enabled, ClothingSpeedModifierComponent? component = null)
{
if (!Resolve(uid, ref component, false))
return;
if (component.Enabled != enabled)
{
component.Enabled = enabled;
Dirty(uid, component);
// inventory system will automatically hook into the event raised by this and update accordingly
if (_container.TryGetContainingContainer(uid, out var container))
{
_movementSpeed.RefreshMovementSpeedModifiers(container.Owner);
}
}
}
// Event handlers
private void OnGetState(EntityUid uid, ClothingSpeedModifierComponent component, ref ComponentGetState args)
{
args.State = new ClothingSpeedModifierComponentState(component.WalkModifier, component.SprintModifier, component.Enabled);
args.State = new ClothingSpeedModifierComponentState(component.WalkModifier, component.SprintModifier);
}
private void OnHandleState(EntityUid uid, ClothingSpeedModifierComponent component, ref ComponentHandleState args)
@@ -71,13 +43,11 @@ public sealed class ClothingSpeedModifierSystem : EntitySystem
if (args.Current is not ClothingSpeedModifierComponentState state)
return;
var diff = component.Enabled != state.Enabled ||
!MathHelper.CloseTo(component.SprintModifier, state.SprintModifier) ||
var diff = !MathHelper.CloseTo(component.SprintModifier, state.SprintModifier) ||
!MathHelper.CloseTo(component.WalkModifier, state.WalkModifier);
component.WalkModifier = state.WalkModifier;
component.SprintModifier = state.SprintModifier;
component.Enabled = state.Enabled;
// Avoid raising the event for the container if nothing changed.
// We'll still set the values in case they're slightly different but within tolerance.
@@ -89,9 +59,7 @@ public sealed class ClothingSpeedModifierSystem : EntitySystem
private void OnRefreshMoveSpeed(EntityUid uid, ClothingSpeedModifierComponent component, InventoryRelayedEvent<RefreshMovementSpeedModifiersEvent> args)
{
if (!component.Enabled)
return;
if (_toggle.IsActivated(uid))
args.Args.ModifySpeed(component.WalkModifier, component.SprintModifier);
}
@@ -142,60 +110,15 @@ public sealed class ClothingSpeedModifierSystem : EntitySystem
_examine.AddDetailedExamineVerb(args, component, msg, Loc.GetString("clothing-speed-examinable-verb-text"), "/Textures/Interface/VerbIcons/outfit.svg.192dpi.png", Loc.GetString("clothing-speed-examinable-verb-message"));
}
private void OnMapInit(Entity<ToggleClothingSpeedComponent> uid, ref MapInitEvent args)
private void OnToggled(Entity<ClothingSpeedModifierComponent> ent, ref ItemToggledEvent args)
{
_actions.AddAction(uid, ref uid.Comp.ToggleActionEntity, uid.Comp.ToggleAction);
// make sentient boots slow or fast too
_movementSpeed.RefreshMovementSpeedModifiers(ent);
if (_container.TryGetContainingContainer(ent.Owner, out var container))
{
// inventory system will automatically hook into the event raised by this and update accordingly
_movementSpeed.RefreshMovementSpeedModifiers(container.Owner);
}
private void OnToggleSpeed(Entity<ToggleClothingSpeedComponent> uid, ref ToggleClothingSpeedEvent args)
{
if (args.Handled)
return;
args.Handled = true;
SetSpeedToggleEnabled(uid, !uid.Comp.Enabled, args.Performer);
}
private void SetSpeedToggleEnabled(Entity<ToggleClothingSpeedComponent> uid, bool value, EntityUid? user)
{
if (uid.Comp.Enabled == value)
return;
TryComp<PowerCellDrawComponent>(uid, out var draw);
if (value && !_powerCell.HasDrawCharge(uid, draw, user: user))
return;
uid.Comp.Enabled = value;
_appearance.SetData(uid, ToggleVisuals.Toggled, uid.Comp.Enabled);
_actions.SetToggled(uid.Comp.ToggleActionEntity, uid.Comp.Enabled);
_clothingSpeedModifier.SetClothingSpeedModifierEnabled(uid.Owner, uid.Comp.Enabled);
_powerCell.SetPowerCellDrawEnabled(uid, uid.Comp.Enabled, draw);
Dirty(uid, uid.Comp);
}
private void AddToggleVerb(Entity<ToggleClothingSpeedComponent> uid, ref GetVerbsEvent<ActivationVerb> args)
{
if (!args.CanAccess || !args.CanInteract)
return;
var user = args.User;
ActivationVerb verb = new()
{
Text = Loc.GetString("toggle-clothing-verb-text",
("entity", Identity.Entity(uid, EntityManager))),
Act = () => SetSpeedToggleEnabled(uid, !uid.Comp.Enabled, user)
};
args.Verbs.Add(verb);
}
private void OnGetActions(Entity<ToggleClothingSpeedComponent> uid, ref GetItemActionsEvent args)
{
args.AddAction(ref uid.Comp.ToggleActionEntity, uid.Comp.ToggleAction);
}
private void OnPowerCellSlotEmpty(Entity<ToggleClothingSpeedComponent> uid, ref PowerCellSlotEmptyEvent args)
{
SetSpeedToggleEnabled(uid, false, null);
}
}

View File

@@ -1,43 +0,0 @@
using Content.Shared.Actions;
using Content.Shared.Clothing.EntitySystems;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Shared.Clothing.Components;
/// <summary>
/// Adds StealthComponent to the user when enabled, either by an action or the system's SetEnabled method.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true), Access(typeof(StealthClothingSystem))]
public sealed partial class StealthClothingComponent : Component
{
/// <summary>
/// Whether stealth effect is enabled.
/// </summary>
[DataField("enabled"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
public bool Enabled;
/// <summary>
/// Number added to MinVisibility when stealthed, to make the user not fully invisible.
/// </summary>
[DataField("visibility"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
public float Visibility;
[DataField("toggleAction", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string ToggleAction = "ActionTogglePhaseCloak";
/// <summary>
/// The action for enabling and disabling stealth.
/// </summary>
[DataField, AutoNetworkedField] public EntityUid? ToggleActionEntity;
}
/// <summary>
/// When stealth is enabled, disables it.
/// When it is disabled, raises <see cref="AttemptStealthEvent"/> before enabling.
/// Put any checks in a handler for that event to cancel it.
/// </summary>
public sealed partial class ToggleStealthEvent : InstantActionEvent
{
}

View File

@@ -0,0 +1,40 @@
using Content.Shared.Actions;
using Content.Shared.Clothing.EntitySystems;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared.Clothing.Components;
/// <summary>
/// Clothing that can be enabled and disabled with an action.
/// Requires <see cref="ItemToggleComponent"/>.
/// </summary>
/// <remarks>
/// Not to be confused with <see cref="ToggleableClothingComponent"/> for hardsuit helmets and such.
/// </remarks>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
[Access(typeof(ToggleClothingSystem))]
public sealed partial class ToggleClothingComponent : Component
{
/// <summary>
/// The action to add when equipped, even if not worn.
/// This must raise <see cref="ToggleActionEvent"/> to then get handled.
/// </summary>
[DataField(required: true)]
public EntProtoId<InstantActionComponent> Action = string.Empty;
[DataField, AutoNetworkedField]
public EntityUid? ActionEntity;
/// <summary>
/// If true, automatically disable the clothing after unequipping it.
/// </summary>
[DataField]
public bool DisableOnUnequip;
}
/// <summary>
/// Raised on the clothing when being equipped to see if it should add the action.
/// </summary>
[ByRefEvent]
public record struct ToggleClothingCheckEvent(EntityUid User, bool Cancelled = false);

View File

@@ -1,35 +0,0 @@
using Content.Shared.Actions;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared.Clothing.Components;
/// <summary>
/// This is used for a clothing item that gives a speed modification that is toggleable.
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(ClothingSpeedModifierSystem)), AutoGenerateComponentState]
public sealed partial class ToggleClothingSpeedComponent : Component
{
/// <summary>
/// The action for toggling the clothing.
/// </summary>
[DataField]
public EntProtoId ToggleAction = "ActionToggleSpeedBoots";
/// <summary>
/// The action entity
/// </summary>
[DataField, AutoNetworkedField]
public EntityUid? ToggleActionEntity;
/// <summary>
/// The state of the toggle.
/// </summary>
[DataField, AutoNetworkedField]
public bool Enabled;
}
public sealed partial class ToggleClothingSpeedEvent : InstantActionEvent
{
}

View File

@@ -1,144 +0,0 @@
using Content.Shared.Actions;
using Content.Shared.Clothing.Components;
using Content.Shared.Inventory.Events;
using Content.Shared.Stealth;
using Content.Shared.Stealth.Components;
namespace Content.Shared.Clothing.EntitySystems;
/// <summary>
/// Handles the toggle action and disables stealth when clothing is unequipped.
/// </summary>
public sealed class StealthClothingSystem : EntitySystem
{
[Dependency] private readonly SharedStealthSystem _stealth = default!;
[Dependency] private readonly ActionContainerSystem _actionContainer = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<StealthClothingComponent, GetItemActionsEvent>(OnGetItemActions);
SubscribeLocalEvent<StealthClothingComponent, ToggleStealthEvent>(OnToggleStealth);
SubscribeLocalEvent<StealthClothingComponent, AfterAutoHandleStateEvent>(OnHandleState);
SubscribeLocalEvent<StealthClothingComponent, GotUnequippedEvent>(OnUnequipped);
SubscribeLocalEvent<StealthClothingComponent, MapInitEvent>(OnMapInit);
}
private void OnMapInit(EntityUid uid, StealthClothingComponent component, MapInitEvent args)
{
_actionContainer.EnsureAction(uid, ref component.ToggleActionEntity, component.ToggleAction);
Dirty(uid, component);
}
/// <summary>
/// Sets the clothing's stealth effect for the user.
/// </summary>
/// <returns>True if it was changed, false otherwise</returns>
public bool SetEnabled(EntityUid uid, EntityUid user, bool enabled, StealthClothingComponent? comp = null)
{
if (!Resolve(uid, ref comp) || comp.Enabled == enabled)
return false;
// TODO remove this when clothing unequip on delete is less sus
// prevent debug assert when ending round and its disabled
if (MetaData(user).EntityLifeStage >= EntityLifeStage.Terminating)
return false;
comp.Enabled = enabled;
Dirty(uid, comp);
var stealth = EnsureComp<StealthComponent>(user);
// slightly visible, but doesn't change when moving so it's ok
var visibility = enabled ? stealth.MinVisibility + comp.Visibility : stealth.MaxVisibility;
_stealth.SetVisibility(user, visibility, stealth);
_stealth.SetEnabled(user, enabled, stealth);
return true;
}
/// <summary>
/// Raise <see cref="AddStealthActionEvent"/> then add the toggle action if it was not cancelled.
/// </summary>
private void OnGetItemActions(EntityUid uid, StealthClothingComponent comp, GetItemActionsEvent args)
{
var ev = new AddStealthActionEvent(args.User);
RaiseLocalEvent(uid, ev);
if (ev.Cancelled)
return;
args.AddAction(ref comp.ToggleActionEntity, comp.ToggleAction);
}
/// <summary>
/// Raises <see cref="AttemptStealthEvent"/> if enabling.
/// </summary>
private void OnToggleStealth(EntityUid uid, StealthClothingComponent comp, ToggleStealthEvent args)
{
args.Handled = true;
var user = args.Performer;
if (comp.Enabled)
{
SetEnabled(uid, user, false, comp);
return;
}
var ev = new AttemptStealthEvent(user);
RaiseLocalEvent(uid, ev);
if (ev.Cancelled)
return;
SetEnabled(uid, user, true, comp);
}
/// <summary>
/// Calls <see cref="SetEnabled"/> when server sends new state.
/// </summary>
private void OnHandleState(EntityUid uid, StealthClothingComponent comp, ref AfterAutoHandleStateEvent args)
{
// SetEnabled checks if it is the same, so change it to before state was received from the server
var enabled = comp.Enabled;
comp.Enabled = !enabled;
var user = Transform(uid).ParentUid;
SetEnabled(uid, user, enabled, comp);
}
/// <summary>
/// Force unstealths the user, doesnt remove StealthComponent since other things might use it
/// </summary>
private void OnUnequipped(EntityUid uid, StealthClothingComponent comp, GotUnequippedEvent args)
{
SetEnabled(uid, args.Equipee, false, comp);
}
}
/// <summary>
/// Raised on the stealth clothing when attempting to add an action.
/// </summary>
public sealed class AddStealthActionEvent : CancellableEntityEventArgs
{
/// <summary>
/// User that equipped the stealth clothing.
/// </summary>
public EntityUid User;
public AddStealthActionEvent(EntityUid user)
{
User = user;
}
}
/// <summary>
/// Raised on the stealth clothing when the user is attemping to enable it.
/// </summary>
public sealed class AttemptStealthEvent : CancellableEntityEventArgs
{
/// <summary>
/// User that is attempting to enable the stealth clothing.
/// </summary>
public EntityUid User;
public AttemptStealthEvent(EntityUid user)
{
User = user;
}
}

View File

@@ -0,0 +1,58 @@
using Content.Shared.Actions;
using Content.Shared.Clothing;
using Content.Shared.Clothing.Components;
using Content.Shared.Inventory;
using Content.Shared.Item.ItemToggle;
using Content.Shared.Toggleable;
namespace Content.Shared.Clothing.EntitySystems;
/// <summary>
/// Handles adding and using a toggle action for <see cref="ToggleClothingComponent"/>.
/// </summary>
public sealed class ToggleClothingSystem : EntitySystem
{
[Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly ItemToggleSystem _toggle = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ToggleClothingComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<ToggleClothingComponent, GetItemActionsEvent>(OnGetActions);
SubscribeLocalEvent<ToggleClothingComponent, ToggleActionEvent>(OnToggleAction);
SubscribeLocalEvent<ToggleClothingComponent, ClothingGotUnequippedEvent>(OnUnequipped);
}
private void OnMapInit(Entity<ToggleClothingComponent> ent, ref MapInitEvent args)
{
var (uid, comp) = ent;
// test funny
if (string.IsNullOrEmpty(comp.Action))
return;
_actions.AddAction(uid, ref comp.ActionEntity, comp.Action);
_actions.SetToggled(comp.ActionEntity, _toggle.IsActivated(ent.Owner));
Dirty(uid, comp);
}
private void OnGetActions(Entity<ToggleClothingComponent> ent, ref GetItemActionsEvent args)
{
var ev = new ToggleClothingCheckEvent(args.User);
RaiseLocalEvent(ent, ref ev);
if (!ev.Cancelled)
args.AddAction(ent.Comp.ActionEntity);
}
private void OnToggleAction(Entity<ToggleClothingComponent> ent, ref ToggleActionEvent args)
{
args.Handled = _toggle.Toggle(ent.Owner, args.Performer);
}
private void OnUnequipped(Entity<ToggleClothingComponent> ent, ref ClothingGotUnequippedEvent args)
{
if (ent.Comp.DisableOnUnequip)
_toggle.TryDeactivate(ent.Owner, args.Wearer);
}
}

View File

@@ -1,23 +1,13 @@
using Content.Shared.Alert;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Shared.Clothing;
[RegisterComponent, NetworkedComponent(), AutoGenerateComponentState]
[RegisterComponent, NetworkedComponent]
[Access(typeof(SharedMagbootsSystem))]
public sealed partial class MagbootsComponent : Component
{
[DataField]
public EntProtoId ToggleAction = "ActionToggleMagboots";
[DataField, AutoNetworkedField]
public EntityUid? ToggleActionEntity;
[DataField("on"), AutoNetworkedField]
public bool On;
[DataField]
public ProtoId<AlertPrototype> MagbootsAlert = "Magboots";
@@ -26,4 +16,10 @@ public sealed partial class MagbootsComponent : Component
/// </summary>
[DataField]
public bool RequiresGrid = true;
/// <summary>
/// Slot the clothing has to be worn in to work.
/// </summary>
[DataField]
public string Slot = "shoes";
}

View File

@@ -0,0 +1,90 @@
using Content.Shared.Actions;
using Content.Shared.Alert;
using Content.Shared.Atmos.Components;
using Content.Shared.Clothing.EntitySystems;
using Content.Shared.Gravity;
using Content.Shared.Inventory;
using Content.Shared.Item;
using Content.Shared.Item.ItemToggle;
using Content.Shared.Item.ItemToggle.Components;
using Robust.Shared.Containers;
namespace Content.Shared.Clothing;
public sealed class SharedMagbootsSystem : EntitySystem
{
[Dependency] private readonly AlertsSystem _alerts = default!;
[Dependency] private readonly ClothingSystem _clothing = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly ItemToggleSystem _toggle = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly SharedGravitySystem _gravity = default!;
[Dependency] private readonly SharedItemSystem _item = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<MagbootsComponent, ItemToggledEvent>(OnToggled);
SubscribeLocalEvent<MagbootsComponent, ClothingGotEquippedEvent>(OnGotEquipped);
SubscribeLocalEvent<MagbootsComponent, ClothingGotUnequippedEvent>(OnGotUnequipped);
SubscribeLocalEvent<MagbootsComponent, IsWeightlessEvent>(OnIsWeightless);
SubscribeLocalEvent<MagbootsComponent, InventoryRelayedEvent<IsWeightlessEvent>>(OnIsWeightless);
}
private void OnToggled(Entity<MagbootsComponent> ent, ref ItemToggledEvent args)
{
var (uid, comp) = ent;
// only stick to the floor if being worn in the correct slot
if (_container.TryGetContainingContainer(uid, out var container) &&
_inventory.TryGetSlotEntity(container.Owner, comp.Slot, out var worn)
&& uid == worn)
{
UpdateMagbootEffects(container.Owner, ent, args.Activated);
}
var prefix = args.Activated ? "on" : null;
_item.SetHeldPrefix(ent, prefix);
_clothing.SetEquippedPrefix(ent, prefix);
}
private void OnGotUnequipped(Entity<MagbootsComponent> ent, ref ClothingGotUnequippedEvent args)
{
UpdateMagbootEffects(args.Wearer, ent, false);
}
private void OnGotEquipped(Entity<MagbootsComponent> ent, ref ClothingGotEquippedEvent args)
{
UpdateMagbootEffects(args.Wearer, ent, _toggle.IsActivated(ent.Owner));
}
public void UpdateMagbootEffects(EntityUid user, Entity<MagbootsComponent> ent, bool state)
{
// TODO: public api for this and add access
if (TryComp<MovedByPressureComponent>(user, out var moved))
moved.Enabled = !state;
if (state)
_alerts.ShowAlert(user, ent.Comp.MagbootsAlert);
else
_alerts.ClearAlert(user, ent.Comp.MagbootsAlert);
}
private void OnIsWeightless(Entity<MagbootsComponent> ent, ref IsWeightlessEvent args)
{
if (args.Handled || !_toggle.IsActivated(ent.Owner))
return;
// do not cancel weightlessness if the person is in off-grid.
if (ent.Comp.RequiresGrid && !_gravity.EntityOnGravitySupportingGridOrMap(ent.Owner))
return;
args.IsWeightless = false;
args.Handled = true;
}
private void OnIsWeightless(Entity<MagbootsComponent> ent, ref InventoryRelayedEvent<IsWeightlessEvent> args)
{
OnIsWeightless(ent, ref args.Args);
}
}

View File

@@ -1,160 +0,0 @@
using Content.Shared.Actions;
using Content.Shared.Alert;
using Content.Shared.Atmos.Components;
using Content.Shared.Clothing.EntitySystems;
using Content.Shared.Gravity;
using Content.Shared.Inventory;
using Content.Shared.Item;
using Content.Shared.Slippery;
using Content.Shared.Toggleable;
using Content.Shared.Verbs;
using Robust.Shared.Containers;
namespace Content.Shared.Clothing;
public sealed class SharedMagbootsSystem : EntitySystem
{
[Dependency] private readonly AlertsSystem _alerts = default!;
[Dependency] private readonly ClothingSpeedModifierSystem _clothingSpeedModifier = default!;
[Dependency] private readonly ClothingSystem _clothing = default!;
[Dependency] private readonly SharedGravitySystem _gravity = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly SharedActionsSystem _sharedActions = default!;
[Dependency] private readonly SharedActionsSystem _actionContainer = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedContainerSystem _sharedContainer = default!;
[Dependency] private readonly SharedItemSystem _item = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<MagbootsComponent, GetVerbsEvent<ActivationVerb>>(AddToggleVerb);
SubscribeLocalEvent<MagbootsComponent, InventoryRelayedEvent<SlipAttemptEvent>>(OnSlipAttempt);
SubscribeLocalEvent<MagbootsComponent, GetItemActionsEvent>(OnGetActions);
SubscribeLocalEvent<MagbootsComponent, ToggleMagbootsEvent>(OnToggleMagboots);
SubscribeLocalEvent<MagbootsComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<MagbootsComponent, ClothingGotEquippedEvent>(OnGotEquipped);
SubscribeLocalEvent<MagbootsComponent, ClothingGotUnequippedEvent>(OnGotUnequipped);
SubscribeLocalEvent<MagbootsComponent, InventoryRelayedEvent<IsWeightlessEvent>>(OnIsWeightless);
}
private void OnMapInit(EntityUid uid, MagbootsComponent component, MapInitEvent args)
{
_actionContainer.AddAction(uid, ref component.ToggleActionEntity, component.ToggleAction);
Dirty(uid, component);
}
private void OnGotUnequipped(EntityUid uid, MagbootsComponent component, ref ClothingGotUnequippedEvent args)
{
UpdateMagbootEffects(args.Wearer, uid, false, component);
}
private void OnGotEquipped(EntityUid uid, MagbootsComponent component, ref ClothingGotEquippedEvent args)
{
UpdateMagbootEffects(args.Wearer, uid, true, component);
}
private void OnToggleMagboots(EntityUid uid, MagbootsComponent component, ToggleMagbootsEvent args)
{
if (args.Handled)
return;
args.Handled = true;
ToggleMagboots(uid, component);
}
private void ToggleMagboots(EntityUid uid, MagbootsComponent magboots)
{
magboots.On = !magboots.On;
if (_sharedContainer.TryGetContainingContainer((uid, Transform(uid)), out var container) &&
_inventory.TryGetSlotEntity(container.Owner, "shoes", out var entityUid) && entityUid == uid)
{
UpdateMagbootEffects(container.Owner, uid, true, magboots);
}
if (TryComp<ItemComponent>(uid, out var item))
{
_item.SetHeldPrefix(uid, magboots.On ? "on" : null, component: item);
_clothing.SetEquippedPrefix(uid, magboots.On ? "on" : null);
}
_appearance.SetData(uid, ToggleVisuals.Toggled, magboots.On);
OnChanged(uid, magboots);
Dirty(uid, magboots);
}
public void UpdateMagbootEffects(EntityUid parent, EntityUid uid, bool state, MagbootsComponent? component)
{
if (!Resolve(uid, ref component))
return;
state = state && component.On;
if (TryComp(parent, out MovedByPressureComponent? movedByPressure))
{
movedByPressure.Enabled = !state;
}
if (state)
{
_alerts.ShowAlert(parent, component.MagbootsAlert);
}
else
{
_alerts.ClearAlert(parent, component.MagbootsAlert);
}
}
private void OnChanged(EntityUid uid, MagbootsComponent component)
{
_sharedActions.SetToggled(component.ToggleActionEntity, component.On);
_clothingSpeedModifier.SetClothingSpeedModifierEnabled(uid, component.On);
}
private void AddToggleVerb(EntityUid uid, MagbootsComponent component, GetVerbsEvent<ActivationVerb> args)
{
if (!args.CanAccess || !args.CanInteract)
return;
ActivationVerb verb = new()
{
Text = Loc.GetString("toggle-magboots-verb-get-data-text"),
Act = () => ToggleMagboots(uid, component),
// TODO VERB ICON add toggle icon? maybe a computer on/off symbol?
};
args.Verbs.Add(verb);
}
private void OnSlipAttempt(EntityUid uid, MagbootsComponent component, InventoryRelayedEvent<SlipAttemptEvent> args)
{
if (component.On)
args.Args.Cancel();
}
private void OnGetActions(EntityUid uid, MagbootsComponent component, GetItemActionsEvent args)
{
args.AddAction(ref component.ToggleActionEntity, component.ToggleAction);
}
private void OnIsWeightless(Entity<MagbootsComponent> ent, ref InventoryRelayedEvent<IsWeightlessEvent> args)
{
if (args.Args.Handled)
return;
if (!ent.Comp.On)
return;
// do not cancel weightlessness if the person is in off-grid.
if (ent.Comp.RequiresGrid && !_gravity.EntityOnGravitySupportingGridOrMap(ent.Owner))
return;
args.Args.IsWeightless = false;
args.Args.Handled = true;
}
}
public sealed partial class ToggleMagbootsEvent : InstantActionEvent;

View File

@@ -0,0 +1,26 @@
using Content.Shared.Item.ItemToggle.Components;
namespace Content.Shared.Item.ItemToggle;
/// <summary>
/// Handles <see cref="ComponentTogglerComponent"/> component manipulation.
/// </summary>
public sealed class ComponentTogglerSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ComponentTogglerComponent, ItemToggledEvent>(OnToggled);
}
private void OnToggled(Entity<ComponentTogglerComponent> ent, ref ItemToggledEvent args)
{
var target = ent.Comp.Parent ? Transform(ent).ParentUid : ent.Owner;
if (args.Activated)
EntityManager.AddComponents(target, ent.Comp.Components);
else
EntityManager.RemoveComponents(target, ent.Comp.RemoveComponents ?? ent.Comp.Components);
}
}

View File

@@ -0,0 +1,32 @@
using Content.Shared.Item.ItemToggle;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared.Item.ItemToggle.Components;
/// <summary>
/// Adds or removes components when toggled.
/// Requires <see cref="ItemToggleComponent"/>.
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(ComponentTogglerSystem))]
public sealed partial class ComponentTogglerComponent : Component
{
/// <summary>
/// The components to add when activated.
/// </summary>
[DataField(required: true)]
public ComponentRegistry Components = new();
/// <summary>
/// The components to remove when deactivated.
/// If this is null <see cref="Components"/> is reused.
/// </summary>
[DataField]
public ComponentRegistry? RemoveComponents;
/// <summary>
/// If true, adds components on the entity's parent instead of the entity itself.
/// </summary>
[DataField]
public bool Parent;
}

View File

@@ -12,12 +12,12 @@ public sealed partial class ItemToggleActiveSoundComponent : Component
/// <summary>
/// The continuous noise this item makes when it's activated (like an e-sword's hum).
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
[DataField(required: true), AutoNetworkedField]
public SoundSpecifier? ActiveSound;
/// <summary>
/// Used when the item emits sound while active.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField]
[DataField]
public EntityUid? PlayingStream;
}

View File

@@ -8,7 +8,7 @@ namespace Content.Shared.Item.ItemToggle.Components;
/// </summary>
/// <remarks>
/// If you need extended functionality (e.g. requiring power) then add a new component and use events:
/// ItemToggleActivateAttemptEvent, ItemToggleDeactivateAttemptEvent or ItemToggleForceToggleEvent.
/// ItemToggleActivateAttemptEvent, ItemToggleDeactivateAttemptEvent, ItemToggledEvent.
/// </remarks>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class ItemToggleComponent : Component
@@ -19,6 +19,13 @@ public sealed partial class ItemToggleComponent : Component
[DataField, AutoNetworkedField]
public bool Activated = false;
/// <summary>
/// If this is set to false then the item can't be toggled by pressing Z.
/// Use another system to do it then.
/// </summary>
[DataField]
public bool OnUse = true;
/// <summary>
/// Whether the item's toggle can be predicted by the client.
/// </summary>

View File

@@ -0,0 +1,18 @@
using Content.Shared.Item.ItemToggle;
using Robust.Shared.GameStates;
namespace Content.Shared.Item.ItemToggle.Components;
/// <summary>
/// Adds a verb for toggling something, requires <see cref="ItemToggleComponent"/>.
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(ToggleVerbSystem))]
public sealed partial class ToggleVerbComponent : Component
{
/// <summary>
/// Text the verb will have.
/// Gets passed "entity" as the entity's identity string.
/// </summary>
[DataField(required: true)]
public LocId Text = string.Empty;
}

View File

@@ -15,12 +15,12 @@ namespace Content.Shared.Item.ItemToggle;
/// <remarks>
/// If you need extended functionality (e.g. requiring power) then add a new component and use events.
/// </remarks>
public abstract class SharedItemToggleSystem : EntitySystem
public sealed class ItemToggleSystem : EntitySystem
{
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedPointLightSystem _light = default!;
[Dependency] private readonly INetManager _netManager = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedPointLightSystem _light = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
public override void Initialize()
@@ -28,8 +28,9 @@ public abstract class SharedItemToggleSystem : EntitySystem
base.Initialize();
SubscribeLocalEvent<ItemToggleComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<ItemToggleComponent, ItemUnwieldedEvent>(TurnOffonUnwielded);
SubscribeLocalEvent<ItemToggleComponent, ItemWieldedEvent>(TurnOnonWielded);
SubscribeLocalEvent<ItemToggleComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<ItemToggleComponent, ItemUnwieldedEvent>(TurnOffOnUnwielded);
SubscribeLocalEvent<ItemToggleComponent, ItemWieldedEvent>(TurnOnOnWielded);
SubscribeLocalEvent<ItemToggleComponent, UseInHandEvent>(OnUseInHand);
SubscribeLocalEvent<ItemToggleHotComponent, IsHotEvent>(OnIsHotEvent);
@@ -42,57 +43,76 @@ public abstract class SharedItemToggleSystem : EntitySystem
UpdateVisuals(ent);
}
private void OnUseInHand(EntityUid uid, ItemToggleComponent itemToggle, UseInHandEvent args)
private void OnMapInit(Entity<ItemToggleComponent> ent, ref MapInitEvent args)
{
if (args.Handled)
if (!ent.Comp.Activated)
return;
var ev = new ItemToggledEvent(Predicted: ent.Comp.Predictable, Activated: ent.Comp.Activated, User: null);
RaiseLocalEvent(ent, ref ev);
}
private void OnUseInHand(Entity<ItemToggleComponent> ent, ref UseInHandEvent args)
{
if (args.Handled || !ent.Comp.OnUse)
return;
args.Handled = true;
Toggle(uid, args.User, predicted: itemToggle.Predictable, itemToggle: itemToggle);
Toggle((ent, ent.Comp), args.User, predicted: ent.Comp.Predictable);
}
/// <summary>
/// Used when an item is attempted to be toggled.
/// Sets its state to the opposite of what it is.
/// </summary>
public void Toggle(EntityUid uid, EntityUid? user = null, bool predicted = true, ItemToggleComponent? itemToggle = null)
/// <returns>Same as <see cref="TrySetActive"/></returns>
public bool Toggle(Entity<ItemToggleComponent?> ent, EntityUid? user = null, bool predicted = true)
{
if (!Resolve(uid, ref itemToggle))
return;
if (!Resolve(ent, ref ent.Comp))
return false;
if (itemToggle.Activated)
{
TryDeactivate(uid, user, itemToggle: itemToggle, predicted: predicted);
return TrySetActive(ent, !ent.Comp.Activated, user, predicted);
}
/// <summary>
/// Tries to set the activated bool from a value.
/// </summary>
/// <returns>false if the attempt fails for any reason</returns>
public bool TrySetActive(Entity<ItemToggleComponent?> ent, bool active, EntityUid? user = null, bool predicted = true)
{
if (active)
return TryActivate(ent, user, predicted: predicted);
else
{
TryActivate(uid, user, itemToggle: itemToggle, predicted: predicted);
}
return TryDeactivate(ent, user, predicted: predicted);
}
/// <summary>
/// Used when an item is attempting to be activated. It returns false if the attempt fails any reason, interrupting the activation.
/// </summary>
public bool TryActivate(EntityUid uid, EntityUid? user = null, bool predicted = true, ItemToggleComponent? itemToggle = null)
public bool TryActivate(Entity<ItemToggleComponent?> ent, EntityUid? user = null, bool predicted = true)
{
if (!Resolve(uid, ref itemToggle))
if (!Resolve(ent, ref ent.Comp))
return false;
if (itemToggle.Activated)
var uid = ent.Owner;
var comp = ent.Comp;
if (comp.Activated)
return true;
if (!itemToggle.Predictable && _netManager.IsClient)
if (!comp.Predictable && _netManager.IsClient)
return true;
var attempt = new ItemToggleActivateAttemptEvent(user);
RaiseLocalEvent(uid, ref attempt);
if (!comp.Predictable) predicted = false;
if (attempt.Cancelled)
{
if (predicted)
_audio.PlayPredicted(itemToggle.SoundFailToActivate, uid, user);
_audio.PlayPredicted(comp.SoundFailToActivate, uid, user);
else
_audio.PlayPvs(itemToggle.SoundFailToActivate, uid);
_audio.PlayPvs(comp.SoundFailToActivate, uid);
if (attempt.Popup != null && user != null)
{
@@ -105,7 +125,7 @@ public abstract class SharedItemToggleSystem : EntitySystem
return false;
}
Activate(uid, itemToggle, predicted, user);
Activate((uid, comp), predicted, user);
return true;
}
@@ -113,75 +133,65 @@ public abstract class SharedItemToggleSystem : EntitySystem
/// <summary>
/// Used when an item is attempting to be deactivated. It returns false if the attempt fails any reason, interrupting the deactivation.
/// </summary>
public bool TryDeactivate(EntityUid uid, EntityUid? user = null, bool predicted = true, ItemToggleComponent? itemToggle = null)
public bool TryDeactivate(Entity<ItemToggleComponent?> ent, EntityUid? user = null, bool predicted = true)
{
if (!Resolve(uid, ref itemToggle))
if (!Resolve(ent, ref ent.Comp))
return false;
if (!itemToggle.Predictable && _netManager.IsClient)
var uid = ent.Owner;
var comp = ent.Comp;
if (!comp.Activated)
return true;
if (!itemToggle.Activated)
if (!comp.Predictable && _netManager.IsClient)
return true;
var attempt = new ItemToggleDeactivateAttemptEvent(user);
RaiseLocalEvent(uid, ref attempt);
if (attempt.Cancelled)
{
return false;
}
Deactivate(uid, itemToggle, predicted, user);
if (!comp.Predictable) predicted = false;
Deactivate((uid, comp), predicted, user);
return true;
}
private void Activate(EntityUid uid, ItemToggleComponent itemToggle, bool predicted, EntityUid? user = null)
private void Activate(Entity<ItemToggleComponent> ent, bool predicted, EntityUid? user = null)
{
// TODO: Fix this hardcoding
TryComp(uid, out AppearanceComponent? appearance);
_appearance.SetData(uid, ToggleableLightVisuals.Enabled, true, appearance);
_appearance.SetData(uid, ToggleVisuals.Toggled, true, appearance);
if (_light.TryGetLight(uid, out var light))
{
_light.SetEnabled(uid, true, light);
}
var soundToPlay = itemToggle.SoundActivate;
var (uid, comp) = ent;
var soundToPlay = comp.SoundActivate;
if (predicted)
_audio.PlayPredicted(soundToPlay, uid, user);
else
_audio.PlayPvs(soundToPlay, uid);
// END FIX HARDCODING
comp.Activated = true;
UpdateVisuals((uid, comp));
Dirty(uid, comp);
var toggleUsed = new ItemToggledEvent(predicted, Activated: true, user);
RaiseLocalEvent(uid, ref toggleUsed);
itemToggle.Activated = true;
UpdateVisuals((uid, itemToggle));
Dirty(uid, itemToggle);
}
/// <summary>
/// Used to make the actual changes to the item's components on deactivation.
/// </summary>
private void Deactivate(EntityUid uid, ItemToggleComponent itemToggle, bool predicted, EntityUid? user = null)
private void Deactivate(Entity<ItemToggleComponent> ent, bool predicted, EntityUid? user = null)
{
var soundToPlay = itemToggle.SoundDeactivate;
var (uid, comp) = ent;
var soundToPlay = comp.SoundDeactivate;
if (predicted)
_audio.PlayPredicted(soundToPlay, uid, user);
else
_audio.PlayPvs(soundToPlay, uid);
// END FIX HARDCODING
comp.Activated = false;
UpdateVisuals((uid, comp));
Dirty(uid, comp);
var toggleUsed = new ItemToggledEvent(predicted, Activated: false, user);
RaiseLocalEvent(uid, ref toggleUsed);
itemToggle.Activated = false;
UpdateVisuals((uid, itemToggle));
Dirty(uid, itemToggle);
}
private void UpdateVisuals(Entity<ItemToggleComponent> ent)
@@ -204,55 +214,56 @@ public abstract class SharedItemToggleSystem : EntitySystem
/// <summary>
/// Used for items that require to be wielded in both hands to activate. For instance the dual energy sword will turn off if not wielded.
/// </summary>
private void TurnOffonUnwielded(EntityUid uid, ItemToggleComponent itemToggle, ItemUnwieldedEvent args)
private void TurnOffOnUnwielded(Entity<ItemToggleComponent> ent, ref ItemUnwieldedEvent args)
{
if (itemToggle.Activated)
TryDeactivate(uid, args.User, itemToggle: itemToggle);
TryDeactivate((ent, ent.Comp), args.User);
}
/// <summary>
/// Wieldable items will automatically turn on when wielded.
/// </summary>
private void TurnOnonWielded(EntityUid uid, ItemToggleComponent itemToggle, ref ItemWieldedEvent args)
private void TurnOnOnWielded(Entity<ItemToggleComponent> ent, ref ItemWieldedEvent args)
{
if (!itemToggle.Activated)
TryActivate(uid, itemToggle: itemToggle);
// FIXME: for some reason both client and server play sound
TryActivate((ent, ent.Comp));
}
public bool IsActivated(EntityUid uid, ItemToggleComponent? comp = null)
public bool IsActivated(Entity<ItemToggleComponent?> ent)
{
if (!Resolve(uid, ref comp, false))
if (!Resolve(ent, ref ent.Comp, false))
return true; // assume always activated if no component
return comp.Activated;
return ent.Comp.Activated;
}
/// <summary>
/// Used to make the item hot when activated.
/// </summary>
private void OnIsHotEvent(EntityUid uid, ItemToggleHotComponent itemToggleHot, IsHotEvent args)
private void OnIsHotEvent(Entity<ItemToggleHotComponent> ent, ref IsHotEvent args)
{
args.IsHot |= IsActivated(uid);
args.IsHot |= IsActivated(ent.Owner);
}
/// <summary>
/// Used to update the looping active sound linked to the entity.
/// </summary>
private void UpdateActiveSound(EntityUid uid, ItemToggleActiveSoundComponent activeSound, ref ItemToggledEvent args)
private void UpdateActiveSound(Entity<ItemToggleActiveSoundComponent> ent, ref ItemToggledEvent args)
{
if (args.Activated)
var (uid, comp) = ent;
if (!args.Activated)
{
if (activeSound.ActiveSound != null && activeSound.PlayingStream == null)
{
if (args.Predicted)
activeSound.PlayingStream = _audio.PlayPredicted(activeSound.ActiveSound, uid, args.User, AudioParams.Default.WithLoop(true)).Value.Entity;
else
activeSound.PlayingStream = _audio.PlayPvs(activeSound.ActiveSound, uid, AudioParams.Default.WithLoop(true)).Value.Entity;
comp.PlayingStream = _audio.Stop(comp.PlayingStream);
return;
}
}
else
if (comp.ActiveSound != null && comp.PlayingStream == null)
{
activeSound.PlayingStream = _audio.Stop(activeSound.PlayingStream);
var loop = AudioParams.Default.WithLoop(true);
var stream = args.Predicted
? _audio.PlayPredicted(comp.ActiveSound, uid, args.User, loop)
: _audio.PlayPvs(comp.ActiveSound, uid, loop);
if (stream?.Entity is {} entity)
comp.PlayingStream = entity;
}
}
}

View File

@@ -0,0 +1,34 @@
using Content.Shared.IdentityManagement;
using Content.Shared.Item.ItemToggle.Components;
using Content.Shared.Verbs;
namespace Content.Shared.Item.ItemToggle;
/// <summary>
/// Adds a verb for toggling something with <see cref="ToggleVerbComponent"/>.
/// </summary>
public sealed class ToggleVerbSystem : EntitySystem
{
[Dependency] private readonly ItemToggleSystem _toggle = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ToggleVerbComponent, GetVerbsEvent<ActivationVerb>>(OnGetVerbs);
}
private void OnGetVerbs(Entity<ToggleVerbComponent> ent, ref GetVerbsEvent<ActivationVerb> args)
{
if (!args.CanAccess || !args.CanInteract)
return;
var name = Identity.Entity(ent, EntityManager);
var user = args.User;
args.Verbs.Add(new ActivationVerb()
{
Text = Loc.GetString(ent.Comp.Text, ("entity", name)),
Act = () => _toggle.Toggle(ent.Owner, user)
});
}
}

View File

@@ -10,16 +10,11 @@ namespace Content.Shared.Medical;
/// <summary>
/// This is used for defibrillators; a machine that shocks a dead
/// person back into the world of the living.
/// Uses <c>ItemToggleComponent</c>
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentPause]
public sealed partial class DefibrillatorComponent : Component
{
/// <summary>
/// Whether or not it's turned on and able to be used.
/// </summary>
[DataField("enabled"), ViewVariables(VVAccess.ReadWrite)]
public bool Enabled;
/// <summary>
/// The time at which the zap cooldown will be completed
/// </summary>
@@ -72,15 +67,6 @@ public sealed partial class DefibrillatorComponent : Component
[ViewVariables(VVAccess.ReadWrite), DataField("zapSound")]
public SoundSpecifier? ZapSound = new SoundPathSpecifier("/Audio/Items/Defib/defib_zap.ogg");
/// <summary>
/// The sound when the defib is powered on.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("powerOnSound")]
public SoundSpecifier? PowerOnSound = new SoundPathSpecifier("/Audio/Items/Defib/defib_safety_on.ogg");
[ViewVariables(VVAccess.ReadWrite), DataField("powerOffSound")]
public SoundSpecifier? PowerOffSound = new SoundPathSpecifier("/Audio/Items/Defib/defib_safety_off.ogg");
[ViewVariables(VVAccess.ReadWrite), DataField("chargeSound")]
public SoundSpecifier? ChargeSound = new SoundPathSpecifier("/Audio/Items/Defib/defib_charge.ogg");

View File

@@ -1,5 +1,6 @@
using Content.Shared.Ninja.Systems;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
namespace Content.Shared.Ninja.Components;
@@ -7,32 +8,33 @@ namespace Content.Shared.Ninja.Components;
/// Component for draining power from APCs/substations/SMESes, when ProviderUid is set to a battery cell.
/// Does not rely on relay, simply being on the user and having BatteryUid set is enough.
/// </summary>
[RegisterComponent, Access(typeof(SharedBatteryDrainerSystem))]
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
[Access(typeof(SharedBatteryDrainerSystem))]
public sealed partial class BatteryDrainerComponent : Component
{
/// <summary>
/// The powercell entity to drain power into.
/// Determines whether draining is possible.
/// </summary>
[DataField("batteryUid"), ViewVariables(VVAccess.ReadWrite)]
[DataField, AutoNetworkedField]
public EntityUid? BatteryUid;
/// <summary>
/// Conversion rate between joules in a device and joules added to battery.
/// Should be very low since powercells store nothing compared to even an APC.
/// </summary>
[DataField("drainEfficiency"), ViewVariables(VVAccess.ReadWrite)]
[DataField]
public float DrainEfficiency = 0.001f;
/// <summary>
/// Time that the do after takes to drain charge from a battery, in seconds
/// </summary>
[DataField("drainTime"), ViewVariables(VVAccess.ReadWrite)]
[DataField]
public float DrainTime = 1f;
/// <summary>
/// Sound played after the doafter ends.
/// </summary>
[DataField("sparkSound")]
[DataField]
public SoundSpecifier SparkSound = new SoundCollectionSpecifier("sparks");
}

View File

@@ -4,6 +4,4 @@ namespace Content.Shared.Ninja.Components;
/// Makes this warp point a valid bombing target for ninja's spider charge.
/// </summary>
[RegisterComponent]
public sealed partial class BombingTargetComponent : Component
{
}
public sealed partial class BombingTargetComponent : Component;

View File

@@ -8,6 +8,7 @@ namespace Content.Shared.Ninja.Components;
/// <summary>
/// Adds an action to dash, teleport to clicked position, when this item is held.
/// Cancel <see cref="CheckDashEvent"/> to prevent using it.
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(DashAbilitySystem)), AutoGenerateComponentState]
public sealed partial class DashAbilityComponent : Component
@@ -16,19 +17,10 @@ public sealed partial class DashAbilityComponent : Component
/// The action id for dashing.
/// </summary>
[DataField]
public EntProtoId DashAction = "ActionEnergyKatanaDash";
public EntProtoId<WorldTargetActionComponent> DashAction = "ActionEnergyKatanaDash";
[DataField, AutoNetworkedField]
public EntityUid? DashActionEntity;
/// <summary>
/// Sound played when using dash action.
/// </summary>
[DataField("blinkSound"), ViewVariables(VVAccess.ReadWrite)]
public SoundSpecifier BlinkSound = new SoundPathSpecifier("/Audio/Magic/blink.ogg")
{
Params = AudioParams.Default.WithVolume(5f)
};
}
public sealed partial class DashEvent : WorldTargetActionEvent { }
public sealed partial class DashEvent : WorldTargetActionEvent;

View File

@@ -2,7 +2,7 @@ using Content.Shared.Ninja.Systems;
using Content.Shared.Tag;
using Content.Shared.Whitelist;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Prototypes;
namespace Content.Shared.Ninja.Components;
@@ -10,19 +10,18 @@ namespace Content.Shared.Ninja.Components;
/// Component for emagging things on click.
/// No charges but checks against a whitelist.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
[Access(typeof(EmagProviderSystem))]
[RegisterComponent, NetworkedComponent, Access(typeof(EmagProviderSystem))]
public sealed partial class EmagProviderComponent : Component
{
/// <summary>
/// The tag that marks an entity as immune to emagging.
/// </summary>
[DataField("emagImmuneTag", customTypeSerializer: typeof(PrototypeIdSerializer<TagPrototype>))]
public string EmagImmuneTag = "EmagImmune";
[DataField]
public ProtoId<TagPrototype> EmagImmuneTag = "EmagImmune";
/// <summary>
/// Whitelist that entities must be on to work.
/// </summary>
[DataField("whitelist"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
public EntityWhitelist? Whitelist = null;
[DataField]
public EntityWhitelist? Whitelist;
}

View File

@@ -7,6 +7,4 @@ namespace Content.Shared.Ninja.Components;
/// Requires a ninja with a suit for abilities to work.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class EnergyKatanaComponent : Component
{
}
public sealed partial class EnergyKatanaComponent : Component;

View File

@@ -0,0 +1,52 @@
using Content.Shared.Actions;
using Content.Shared.Ninja.Systems;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared.Ninja.Components;
/// <summary>
/// Uses battery charge to spawn an item and place it in the user's hands.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
[Access(typeof(SharedItemCreatorSystem))]
public sealed partial class ItemCreatorComponent : Component
{
/// <summary>
/// The battery entity to use charge from
/// </summary>
[DataField, AutoNetworkedField]
public EntityUid? Battery;
/// <summary>
/// The action id for creating an item.
/// </summary>
[DataField(required: true)]
public EntProtoId<InstantActionComponent> Action = string.Empty;
[DataField, AutoNetworkedField]
public EntityUid? ActionEntity;
/// <summary>
/// Battery charge used to create an item.
/// </summary>
[DataField(required: true)]
public float Charge = 14.4f;
/// <summary>
/// Item to create with the action
/// </summary>
[DataField(required: true)]
public EntProtoId SpawnedPrototype = string.Empty;
/// <summary>
/// Popup shown to the user when there isn't enough power to create an item.
/// </summary>
[DataField(required: true)]
public LocId NoPowerPopup = string.Empty;
}
/// <summary>
/// Action event to use an <see cref="ItemCreator"/>.
/// </summary>
public sealed partial class CreateItemEvent : InstantActionEvent;

View File

@@ -1,20 +1,17 @@
using Content.Shared.DoAfter;
using Content.Shared.Ninja.Systems;
using Content.Shared.Toggleable;
using Content.Shared.Whitelist;
using Content.Shared.Objectives.Components;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Utility;
namespace Content.Shared.Ninja.Components;
/// <summary>
/// Component for toggling glove powers.
/// Powers being enabled is controlled by User not being null.
/// </summary>
/// <remarks>
/// Requires <c>ItemToggleComponent</c>.
/// </remarks>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
[Access(typeof(SharedNinjaGlovesSystem))]
public sealed partial class NinjaGlovesComponent : Component
@@ -22,24 +19,33 @@ public sealed partial class NinjaGlovesComponent : Component
/// <summary>
/// Entity of the ninja using these gloves, usually means enabled
/// </summary>
[DataField("user"), AutoNetworkedField]
[DataField, AutoNetworkedField]
public EntityUid? User;
/// <summary>
/// The action id for toggling ninja gloves abilities
/// Abilities to give to the user when enabled.
/// </summary>
[DataField("toggleAction", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string ToggleAction = "ActionToggleNinjaGloves";
[DataField(required: true)]
public List<NinjaGloveAbility> Abilities = new();
}
[DataField, AutoNetworkedField]
public EntityUid? ToggleActionEntity;
/// <summary>
/// An ability that adds components to the user when the gloves are enabled.
/// </summary>
[DataRecord]
public record struct NinjaGloveAbility()
{
/// <summary>
/// If not null, checks if an objective with this prototype has been completed.
/// If it has, the ability components are skipped to prevent doing the objective twice.
/// The objective must have <c>CodeConditionComponent</c> to be checked.
/// </summary>
[DataField]
public EntProtoId<ObjectiveComponent>? Objective;
/// <summary>
/// The whitelist used for the emag provider to emag airlocks only (not regular doors).
/// Components to add and remove.
/// </summary>
[DataField("doorjackWhitelist")]
public EntityWhitelist DoorjackWhitelist = new()
{
Components = new[] {"Airlock"}
};
[DataField(required: true)]
public ComponentRegistry Components = new();
}

View File

@@ -3,9 +3,6 @@ using Content.Shared.Ninja.Systems;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Utility;
namespace Content.Shared.Ninja.Components;
@@ -14,68 +11,27 @@ namespace Content.Shared.Ninja.Components;
/// Component for ninja suit abilities and power consumption.
/// As an implementation detail, dashing with katana is a suit action which isn't ideal.
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(SharedNinjaSuitSystem)), AutoGenerateComponentState]
[AutoGenerateComponentPause]
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
[Access(typeof(SharedNinjaSuitSystem))]
public sealed partial class NinjaSuitComponent : Component
{
/// <summary>
/// Battery charge used passively, in watts. Will last 1000 seconds on a small-capacity power cell.
/// </summary>
[DataField("passiveWattage")]
public float PassiveWattage = 0.36f;
/// <summary>
/// Battery charge used while cloaked, stacks with passive. Will last 200 seconds while cloaked on a small-capacity power cell.
/// </summary>
[DataField("cloakWattage")]
public float CloakWattage = 1.44f;
/// <summary>
/// Sound played when a ninja is hit while cloaked.
/// </summary>
[DataField("revealSound")]
[DataField]
public SoundSpecifier RevealSound = new SoundPathSpecifier("/Audio/Effects/chime.ogg");
/// <summary>
/// How long to disable all abilities when revealed.
/// Normally, ninjas are revealed when attacking or getting damaged.
/// ID of the use delay to disable all ninja abilities.
/// </summary>
[DataField("disableTime")]
public TimeSpan DisableTime = TimeSpan.FromSeconds(5);
/// <summary>
/// Time at which we will be able to use our abilities again
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
[AutoPausedField]
public TimeSpan DisableCooldown;
/// <summary>
/// The action id for creating throwing stars.
/// </summary>
[DataField("createThrowingStarAction", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string CreateThrowingStarAction = "ActionCreateThrowingStar";
[DataField, AutoNetworkedField]
public EntityUid? CreateThrowingStarActionEntity;
/// <summary>
/// Battery charge used to create a throwing star. Can do it 25 times on a small-capacity power cell.
/// </summary>
[DataField("throwingStarCharge")]
public float ThrowingStarCharge = 14.4f;
/// <summary>
/// Throwing star item to create with the action
/// </summary>
[DataField("throwingStarPrototype", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string ThrowingStarPrototype = "ThrowingStarNinja";
[DataField]
public string DisableDelayId = "suit_powers";
/// <summary>
/// The action id for recalling a bound energy katana
/// </summary>
[DataField("recallKatanaAction", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string RecallKatanaAction = "ActionRecallKatana";
[DataField]
public EntProtoId RecallKatanaAction = "ActionRecallKatana";
[DataField, AutoNetworkedField]
public EntityUid? RecallKatanaActionEntity;
@@ -84,14 +40,14 @@ public sealed partial class NinjaSuitComponent : Component
/// Battery charge used per tile the katana teleported.
/// Uses 1% of a default battery per tile.
/// </summary>
[DataField("recallCharge")]
[DataField]
public float RecallCharge = 3.6f;
/// <summary>
/// The action id for creating an EMP burst
/// </summary>
[DataField("empAction", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string EmpAction = "ActionNinjaEmp";
[DataField]
public EntProtoId EmpAction = "ActionNinjaEmp";
[DataField, AutoNetworkedField]
public EntityUid? EmpActionEntity;
@@ -99,36 +55,29 @@ public sealed partial class NinjaSuitComponent : Component
/// <summary>
/// Battery charge used to create an EMP burst. Can do it 2 times on a small-capacity power cell.
/// </summary>
[DataField("empCharge")]
[DataField]
public float EmpCharge = 180f;
// TODO: EmpOnTrigger bruh
/// <summary>
/// Range of the EMP in tiles.
/// </summary>
[DataField("empRange")]
[DataField]
public float EmpRange = 6f;
/// <summary>
/// Power consumed from batteries by the EMP
/// </summary>
[DataField("empConsumption")]
[DataField]
public float EmpConsumption = 100000f;
/// <summary>
/// How long the EMP effects last for, in seconds
/// </summary>
[DataField("empDuration")]
[DataField]
public float EmpDuration = 60f;
}
public sealed partial class CreateThrowingStarEvent : InstantActionEvent
{
}
public sealed partial class RecallKatanaEvent : InstantActionEvent;
public sealed partial class RecallKatanaEvent : InstantActionEvent
{
}
public sealed partial class NinjaEmpEvent : InstantActionEvent
{
}
public sealed partial class NinjaEmpEvent : InstantActionEvent;

View File

@@ -7,34 +7,28 @@ namespace Content.Shared.Ninja.Components;
/// <summary>
/// Component placed on a mob to make it a space ninja, able to use suit and glove powers.
/// Contains ids of all ninja equipment and the game rule.
/// Contains ids of all ninja equipment.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
[Access(typeof(SharedSpaceNinjaSystem))]
public sealed partial class SpaceNinjaComponent : Component
{
/// <summary>
/// The ninja game rule that spawned this ninja.
/// </summary>
[DataField("rule")]
public EntityUid? Rule;
/// <summary>
/// Currently worn suit
/// </summary>
[DataField("suit"), AutoNetworkedField]
[DataField, AutoNetworkedField]
public EntityUid? Suit;
/// <summary>
/// Currently worn gloves
/// Currently worn gloves, if enabled.
/// </summary>
[DataField("gloves"), AutoNetworkedField]
[DataField, AutoNetworkedField]
public EntityUid? Gloves;
/// <summary>
/// Bound katana, set once picked up and never removed
/// </summary>
[DataField("katana"), AutoNetworkedField]
[DataField, AutoNetworkedField]
public EntityUid? Katana;
/// <summary>
@@ -55,6 +49,9 @@ public sealed partial class SpaceNinjaComponent : Component
[DataField]
public EntProtoId SpiderChargeObjective = "SpiderChargeObjective";
/// <summary>
/// Alert to show for suit power.
/// </summary>
[DataField]
public ProtoId<AlertPrototype> SuitPowerAlert = "SuitPower";
}

View File

@@ -1,3 +1,4 @@
using Content.Shared.Ninja.Systems;
using Robust.Shared.GameStates;
namespace Content.Shared.Ninja.Components;
@@ -6,14 +7,14 @@ namespace Content.Shared.Ninja.Components;
/// Component for the Space Ninja's unique Spider Charge.
/// Only this component detonating can trigger the ninja's objective.
/// </summary>
[RegisterComponent, NetworkedComponent]
[RegisterComponent, NetworkedComponent, Access(typeof(SharedSpiderChargeSystem))]
public sealed partial class SpiderChargeComponent : Component
{
/// Range for planting within the target area
[DataField("range")]
[DataField]
public float Range = 10f;
/// The ninja that planted this charge
[DataField("planter")]
public EntityUid? Planter = null;
[DataField]
public EntityUid? Planter;
}

View File

@@ -3,7 +3,6 @@ using Content.Shared.Ninja.Systems;
using Content.Shared.Whitelist;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Shared.Ninja.Components;
@@ -11,32 +10,33 @@ namespace Content.Shared.Ninja.Components;
/// Component for stunning mobs on click outside of harm mode.
/// Knocks them down for a bit and deals shock damage.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedStunProviderSystem))]
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
[Access(typeof(SharedStunProviderSystem))]
public sealed partial class StunProviderComponent : Component
{
/// <summary>
/// The powercell entity to take power from.
/// Determines whether stunning is possible.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
[DataField, AutoNetworkedField]
public EntityUid? BatteryUid;
/// <summary>
/// Sound played when stunning someone.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
[DataField]
public SoundSpecifier Sound = new SoundCollectionSpecifier("sparks");
/// <summary>
/// Joules required in the battery to stun someone. Defaults to 10 uses on a small battery.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
[DataField]
public float StunCharge = 36f;
/// <summary>
/// Damage dealt when stunning someone
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
[DataField]
public DamageSpecifier StunDamage = new()
{
DamageDict = new()
@@ -48,34 +48,30 @@ public sealed partial class StunProviderComponent : Component
/// <summary>
/// Time that someone is stunned for, stacks if done multiple times.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
[DataField]
public TimeSpan StunTime = TimeSpan.FromSeconds(5);
/// <summary>
/// How long stunning is disabled after stunning something.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
[DataField]
public TimeSpan Cooldown = TimeSpan.FromSeconds(2);
/// <summary>
/// ID of the cooldown use delay.
/// </summary>
[DataField]
public string DelayId = "stun_cooldown";
/// <summary>
/// Locale string to popup when there is no power
/// </summary>
[DataField(required: true), ViewVariables(VVAccess.ReadWrite)]
public string NoPowerPopup = string.Empty;
[DataField(required: true)]
public LocId NoPowerPopup = string.Empty;
/// <summary>
/// Whitelist for what counts as a mob.
/// </summary>
[DataField]
public EntityWhitelist Whitelist = new()
{
Components = new[] {"Stamina"}
};
/// <summary>
/// When someone can next be stunned.
/// Essentially a UseDelay unique to this component.
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
public TimeSpan NextStun = TimeSpan.Zero;
[DataField(required: true)]
public EntityWhitelist Whitelist = new();
}

View File

@@ -16,6 +16,7 @@ namespace Content.Shared.Ninja.Systems;
/// </summary>
public sealed class DashAbilitySystem : EntitySystem
{
[Dependency] private readonly ActionContainerSystem _actionContainer = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedChargesSystem _charges = default!;
@@ -23,48 +24,40 @@ public sealed class DashAbilitySystem : EntitySystem
[Dependency] private readonly ExamineSystemShared _examine = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly ActionContainerSystem _actionContainer = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DashAbilityComponent, GetItemActionsEvent>(OnGetItemActions);
SubscribeLocalEvent<DashAbilityComponent, GetItemActionsEvent>(OnGetActions);
SubscribeLocalEvent<DashAbilityComponent, DashEvent>(OnDash);
SubscribeLocalEvent<DashAbilityComponent, MapInitEvent>(OnMapInit);
}
private void OnMapInit(EntityUid uid, DashAbilityComponent component, MapInitEvent args)
private void OnMapInit(Entity<DashAbilityComponent> ent, ref MapInitEvent args)
{
_actionContainer.EnsureAction(uid, ref component.DashActionEntity, component.DashAction);
Dirty(uid, component);
var (uid, comp) = ent;
_actionContainer.EnsureAction(uid, ref comp.DashActionEntity, comp.DashAction);
Dirty(uid, comp);
}
private void OnGetItemActions(EntityUid uid, DashAbilityComponent comp, GetItemActionsEvent args)
private void OnGetActions(Entity<DashAbilityComponent> ent, ref GetItemActionsEvent args)
{
var ev = new AddDashActionEvent(args.User);
RaiseLocalEvent(uid, ev);
if (ev.Cancelled)
return;
args.AddAction(ref comp.DashActionEntity, comp.DashAction);
if (CheckDash(ent, args.User))
args.AddAction(ent.Comp.DashActionEntity);
}
/// <summary>
/// Handle charges and teleport to a visible location.
/// </summary>
private void OnDash(EntityUid uid, DashAbilityComponent comp, DashEvent args)
private void OnDash(Entity<DashAbilityComponent> ent, ref DashEvent args)
{
if (!_timing.IsFirstTimePredicted)
return;
var (uid, comp) = ent;
var user = args.Performer;
args.Handled = true;
var ev = new DashAttemptEvent(user);
RaiseLocalEvent(uid, ev);
if (ev.Cancelled)
if (!CheckDash(uid, user))
return;
if (!_hands.IsHolding(user, uid, out var _))
@@ -73,15 +66,8 @@ public sealed class DashAbilitySystem : EntitySystem
return;
}
TryComp<LimitedChargesComponent>(uid, out var charges);
if (_charges.IsEmpty(uid, charges))
{
_popup.PopupClient(Loc.GetString("dash-ability-no-charges", ("item", uid)), user, user);
return;
}
var origin = _transform.GetMapCoordinates(user);
var target = args.Target.ToMap(EntityManager, _transform);
// prevent collision with the user duh
if (!_examine.InRangeUnOccluded(origin, target, SharedInteractionSystem.MaxRaycastRange, null))
{
// can only dash if the destination is visible on screen
@@ -89,36 +75,28 @@ public sealed class DashAbilitySystem : EntitySystem
return;
}
_transform.SetCoordinates(user, args.Target);
_transform.AttachToGridOrMap(user);
_audio.PlayPredicted(comp.BlinkSound, user, user);
if (charges != null)
_charges.UseCharge(uid, charges);
if (!_charges.TryUseCharge(uid))
{
_popup.PopupClient(Loc.GetString("dash-ability-no-charges", ("item", uid)), user, user);
return;
}
var xform = Transform(user);
_transform.SetCoordinates(user, xform, args.Target);
_transform.AttachToGridOrMap(user, xform);
args.Handled = true;
}
public bool CheckDash(EntityUid uid, EntityUid user)
{
var ev = new CheckDashEvent(user);
RaiseLocalEvent(uid, ref ev);
return !ev.Cancelled;
}
}
/// <summary>
/// Raised on the item before adding the dash action
/// Raised on the item before adding the dash action and when using the action.
/// </summary>
public sealed class AddDashActionEvent : CancellableEntityEventArgs
{
public EntityUid User;
public AddDashActionEvent(EntityUid user)
{
User = user;
}
}
/// <summary>
/// Raised on the item before dashing is done.
/// </summary>
public sealed class DashAttemptEvent : CancellableEntityEventArgs
{
public EntityUid User;
public DashAttemptEvent(EntityUid user)
{
User = user;
}
}
[ByRefEvent]
public record struct CheckDashEvent(EntityUid User, bool Cancelled = false);

View File

@@ -1,6 +1,6 @@
using Content.Shared.Administration.Logs;
using Content.Shared.Emag.Systems;
using Content.Shared.Database;
using Content.Shared.Emag.Systems;
using Content.Shared.Interaction;
using Content.Shared.Ninja.Components;
using Content.Shared.Tag;
@@ -14,10 +14,10 @@ namespace Content.Shared.Ninja.Systems;
public sealed class EmagProviderSystem : EntitySystem
{
[Dependency] private readonly EmagSystem _emag = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly SharedNinjaGlovesSystem _gloves = default!;
[Dependency] private readonly TagSystem _tags = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
[Dependency] private readonly TagSystem _tag = default!;
public override void Initialize()
{
@@ -29,18 +29,20 @@ public sealed class EmagProviderSystem : EntitySystem
/// <summary>
/// Emag clicked entities that are on the whitelist.
/// </summary>
private void OnBeforeInteractHand(EntityUid uid, EmagProviderComponent comp, BeforeInteractHandEvent args)
private void OnBeforeInteractHand(Entity<EmagProviderComponent> ent, ref BeforeInteractHandEvent args)
{
// TODO: change this into a generic check event thing
if (args.Handled || !_gloves.AbilityCheck(uid, args, out var target))
if (args.Handled || !_gloves.AbilityCheck(ent, args, out var target))
return;
var (uid, comp) = ent;
// only allowed to emag entities on the whitelist
if (_whitelistSystem.IsWhitelistFail(comp.Whitelist, target))
if (_whitelist.IsWhitelistFail(comp.Whitelist, target))
return;
// only allowed to emag non-immune entities
if (_tags.HasTag(target, comp.EmagImmuneTag))
if (_tag.HasTag(target, comp.EmagImmuneTag))
return;
var handled = _emag.DoEmagEffect(uid, target);
@@ -52,18 +54,6 @@ public sealed class EmagProviderSystem : EntitySystem
RaiseLocalEvent(uid, ref ev);
args.Handled = true;
}
/// <summary>
/// Set the whitelist for emagging something outside of yaml.
/// </summary>
public void SetWhitelist(EntityUid uid, EntityWhitelist? whitelist, EmagProviderComponent? comp = null)
{
if (!Resolve(uid, ref comp))
return;
comp.Whitelist = whitelist;
Dirty(uid, comp);
}
}
/// <summary>

View File

@@ -15,33 +15,20 @@ public sealed class EnergyKatanaSystem : EntitySystem
base.Initialize();
SubscribeLocalEvent<EnergyKatanaComponent, GotEquippedEvent>(OnEquipped);
SubscribeLocalEvent<EnergyKatanaComponent, AddDashActionEvent>(OnAddDashAction);
SubscribeLocalEvent<EnergyKatanaComponent, DashAttemptEvent>(OnDashAttempt);
SubscribeLocalEvent<EnergyKatanaComponent, CheckDashEvent>(OnCheckDash);
}
/// <summary>
/// When equipped by a ninja, try to bind it.
/// </summary>
private void OnEquipped(EntityUid uid, EnergyKatanaComponent comp, GotEquippedEvent args)
private void OnEquipped(Entity<EnergyKatanaComponent> ent, ref GotEquippedEvent args)
{
// check if user isnt a ninja or already has a katana bound
var user = args.Equipee;
if (!TryComp<SpaceNinjaComponent>(user, out var ninja) || ninja.Katana != null)
return;
// bind it since its unbound
_ninja.BindKatana(user, uid, ninja);
_ninja.BindKatana(args.Equipee, ent);
}
private void OnAddDashAction(EntityUid uid, EnergyKatanaComponent comp, AddDashActionEvent args)
private void OnCheckDash(Entity<EnergyKatanaComponent> ent, ref CheckDashEvent args)
{
if (!HasComp<SpaceNinjaComponent>(args.User))
args.Cancel();
}
private void OnDashAttempt(EntityUid uid, EnergyKatanaComponent comp, DashAttemptEvent args)
{
if (!TryComp<SpaceNinjaComponent>(args.User, out var ninja) || ninja.Katana != uid)
args.Cancel();
if (!_ninja.IsNinja(args.User))
args.Cancelled = true;
}
}

View File

@@ -0,0 +1,56 @@
using Content.Shared.Actions;
using Content.Shared.Ninja.Components;
namespace Content.Shared.Ninja.Systems;
/// <summary>
/// Handles predicting that the action exists, creating items is done serverside.
/// </summary>
public abstract class SharedItemCreatorSystem : EntitySystem
{
[Dependency] private readonly ActionContainerSystem _actionContainer = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ItemCreatorComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<ItemCreatorComponent, GetItemActionsEvent>(OnGetActions);
}
private void OnMapInit(Entity<ItemCreatorComponent> ent, ref MapInitEvent args)
{
var (uid, comp) = ent;
// test funny dont mind me
if (string.IsNullOrEmpty(comp.Action))
return;
_actionContainer.EnsureAction(uid, ref comp.ActionEntity, comp.Action);
Dirty(uid, comp);
}
private void OnGetActions(Entity<ItemCreatorComponent> ent, ref GetItemActionsEvent args)
{
if (CheckItemCreator(ent, args.User))
args.AddAction(ent.Comp.ActionEntity);
}
public bool CheckItemCreator(EntityUid uid, EntityUid user)
{
var ev = new CheckItemCreatorEvent(user);
RaiseLocalEvent(uid, ref ev);
return !ev.Cancelled;
}
}
/// <summary>
/// Raised on the item creator before adding the action.
/// </summary>
[ByRefEvent]
public record struct CheckItemCreatorEvent(EntityUid User, bool Cancelled = false);
/// <summary>
/// Raised on the item creator before creating an item.
/// </summary>
[ByRefEvent]
public record struct CreateItemAttemptEvent(EntityUid User, bool Cancelled = false);

View File

@@ -18,34 +18,32 @@ public abstract class SharedBatteryDrainerSystem : EntitySystem
}
/// <summary>
/// Cancel any drain doafters if the battery is removed or gets filled.
/// Cancel any drain doafters if the battery is removed or, on the server, gets filled.
/// </summary>
protected virtual void OnDoAfterAttempt(EntityUid uid, BatteryDrainerComponent comp, DoAfterAttemptEvent<DrainDoAfterEvent> args)
{
if (comp.BatteryUid == null)
protected virtual void OnDoAfterAttempt(Entity<BatteryDrainerComponent> ent, ref DoAfterAttemptEvent<DrainDoAfterEvent> args)
{
if (ent.Comp.BatteryUid == null)
args.Cancel();
}
}
/// <summary>
/// Drain power from a power source (on server) and repeat if it succeeded.
/// Client will predict always succeeding since power is serverside.
/// </summary>
private void OnDoAfter(EntityUid uid, BatteryDrainerComponent comp, DrainDoAfterEvent args)
private void OnDoAfter(Entity<BatteryDrainerComponent> ent, ref DrainDoAfterEvent args)
{
if (args.Cancelled || args.Handled || args.Target == null)
if (args.Cancelled || args.Handled || args.Target is not {} target)
return;
// repeat if there is still power to drain
args.Repeat = TryDrainPower(uid, comp, args.Target.Value);
args.Repeat = TryDrainPower(ent, target);
}
/// <summary>
/// Attempt to drain as much power as possible into the powercell.
/// Client always predicts this as succeeding since power is serverside and it can only fail once, when the powercell is filled or the target is emptied.
/// </summary>
protected virtual bool TryDrainPower(EntityUid uid, BatteryDrainerComponent comp, EntityUid target)
protected virtual bool TryDrainPower(Entity<BatteryDrainerComponent> ent, EntityUid target)
{
return true;
}
@@ -53,12 +51,13 @@ public abstract class SharedBatteryDrainerSystem : EntitySystem
/// <summary>
/// Sets the battery field on the drainer.
/// </summary>
public void SetBattery(EntityUid uid, EntityUid? battery, BatteryDrainerComponent? comp = null)
public void SetBattery(Entity<BatteryDrainerComponent?> ent, EntityUid? battery)
{
if (!Resolve(uid, ref comp))
if (!Resolve(ent, ref ent.Comp) || ent.Comp.BatteryUid == battery)
return;
comp.BatteryUid = battery;
ent.Comp.BatteryUid = battery;
Dirty(ent, ent.Comp);
}
}
@@ -66,4 +65,4 @@ public abstract class SharedBatteryDrainerSystem : EntitySystem
/// DoAfter event for <see cref="BatteryDrainerComponent"/>.
/// </summary>
[Serializable, NetSerializable]
public sealed partial class DrainDoAfterEvent : SimpleDoAfterEvent { }
public sealed partial class DrainDoAfterEvent : SimpleDoAfterEvent;

View File

@@ -1,15 +1,13 @@
using Content.Shared.Actions;
using Content.Shared.Clothing.Components;
using Content.Shared.CombatMode;
using Content.Shared.Communications;
using Content.Shared.CriminalRecords.Components;
using Content.Shared.Examine;
using Content.Shared.Hands.Components;
using Content.Shared.Interaction;
using Content.Shared.Inventory.Events;
using Content.Shared.Item.ItemToggle;
using Content.Shared.Item.ItemToggle.Components;
using Content.Shared.Ninja.Components;
using Content.Shared.Popups;
using Content.Shared.Research.Components;
using Content.Shared.Toggleable;
using Robust.Shared.Timing;
namespace Content.Shared.Ninja.Systems;
@@ -20,85 +18,105 @@ namespace Content.Shared.Ninja.Systems;
public abstract class SharedNinjaGlovesSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] protected readonly SharedAppearanceSystem Appearance = default!;
[Dependency] private readonly SharedCombatModeSystem _combatMode = default!;
[Dependency] protected readonly SharedInteractionSystem Interaction = default!;
[Dependency] protected readonly SharedPopupSystem Popup = default!;
[Dependency] private readonly ActionContainerSystem _actionContainer = default!;
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
[Dependency] private readonly ItemToggleSystem _toggle = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedSpaceNinjaSystem _ninja = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<NinjaGlovesComponent, GetItemActionsEvent>(OnGetItemActions);
SubscribeLocalEvent<NinjaGlovesComponent, ToggleClothingCheckEvent>(OnToggleCheck);
SubscribeLocalEvent<NinjaGlovesComponent, ItemToggleActivateAttemptEvent>(OnActivateAttempt);
SubscribeLocalEvent<NinjaGlovesComponent, ItemToggledEvent>(OnToggled);
SubscribeLocalEvent<NinjaGlovesComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<NinjaGlovesComponent, GotUnequippedEvent>(OnUnequipped);
SubscribeLocalEvent<NinjaGlovesComponent, MapInitEvent>(OnMapInit);
}
private void OnMapInit(EntityUid uid, NinjaGlovesComponent component, MapInitEvent args)
{
_actionContainer.EnsureAction(uid, ref component.ToggleActionEntity, component.ToggleAction);
Dirty(uid, component);
}
/// <summary>
/// Disable glove abilities and show the popup if they were enabled previously.
/// </summary>
public void DisableGloves(EntityUid uid, NinjaGlovesComponent? comp = null)
private void DisableGloves(Entity<NinjaGlovesComponent> ent)
{
var (uid, comp) = ent;
// already disabled?
if (!Resolve(uid, ref comp) || comp.User == null)
if (comp.User is not {} user)
return;
var user = comp.User.Value;
comp.User = null;
Dirty(uid, comp);
Appearance.SetData(uid, ToggleVisuals.Toggled, false);
Popup.PopupClient(Loc.GetString("ninja-gloves-off"), user, user);
RemComp<BatteryDrainerComponent>(user);
RemComp<EmagProviderComponent>(user);
RemComp<StunProviderComponent>(user);
RemComp<ResearchStealerComponent>(user);
RemComp<CommsHackerComponent>(user);
RemComp<CriminalRecordsHackerComponent>(user);
foreach (var ability in comp.Abilities)
{
EntityManager.RemoveComponents(user, ability.Components);
}
}
/// <summary>
/// Adds the toggle action when equipped.
/// Adds the toggle action when equipped by a ninja only.
/// </summary>
private void OnGetItemActions(EntityUid uid, NinjaGlovesComponent comp, GetItemActionsEvent args)
private void OnToggleCheck(Entity<NinjaGlovesComponent> ent, ref ToggleClothingCheckEvent args)
{
if (HasComp<SpaceNinjaComponent>(args.User))
args.AddAction(ref comp.ToggleActionEntity, comp.ToggleAction);
if (!_ninja.IsNinja(args.User))
args.Cancelled = true;
}
/// <summary>
/// Show if the gloves are enabled when examining.
/// </summary>
private void OnExamined(EntityUid uid, NinjaGlovesComponent comp, ExaminedEvent args)
private void OnExamined(Entity<NinjaGlovesComponent> ent, ref ExaminedEvent args)
{
if (!args.IsInDetailsRange)
return;
args.PushText(Loc.GetString(comp.User != null ? "ninja-gloves-examine-on" : "ninja-gloves-examine-off"));
var on = _toggle.IsActivated(ent.Owner) ? "on" : "off";
args.PushText(Loc.GetString($"ninja-gloves-examine-{on}"));
}
/// <summary>
/// Disable gloves when unequipped and clean up ninja's gloves reference
/// </summary>
private void OnUnequipped(EntityUid uid, NinjaGlovesComponent comp, GotUnequippedEvent args)
private void OnActivateAttempt(Entity<NinjaGlovesComponent> ent, ref ItemToggleActivateAttemptEvent args)
{
if (comp.User != null)
if (args.User is not {} user
|| !_ninja.NinjaQuery.TryComp(user, out var ninja)
// need to wear suit to enable gloves
|| !HasComp<NinjaSuitComponent>(ninja.Suit))
{
var user = comp.User.Value;
Popup.PopupClient(Loc.GetString("ninja-gloves-off"), user, user);
DisableGloves(uid, comp);
args.Cancelled = true;
args.Popup = Loc.GetString("ninja-gloves-not-wearing-suit");
return;
}
}
private void OnToggled(Entity<NinjaGlovesComponent> ent, ref ItemToggledEvent args)
{
if ((args.User ?? ent.Comp.User) is not {} user)
return;
var message = Loc.GetString(args.Activated ? "ninja-gloves-on" : "ninja-gloves-off");
_popup.PopupClient(message, user, user);
if (args.Activated && _ninja.NinjaQuery.TryComp(user, out var ninja))
EnableGloves(ent, (user, ninja));
else
DisableGloves(ent);
}
protected virtual void EnableGloves(Entity<NinjaGlovesComponent> ent, Entity<SpaceNinjaComponent> user)
{
var (uid, comp) = ent;
comp.User = user;
Dirty(uid, comp);
_ninja.AssignGloves(user, uid);
// yeah this is just ComponentToggler but with objective checking
foreach (var ability in comp.Abilities)
{
// can't predict the objective related abilities
if (ability.Objective == null)
EntityManager.AddComponents(user, ability.Components);
}
}
// TODO: generic event thing
/// <summary>
@@ -112,6 +130,6 @@ public abstract class SharedNinjaGlovesSystem : EntitySystem
&& !_combatMode.IsInCombatMode(uid)
&& TryComp<HandsComponent>(uid, out var hands)
&& hands.ActiveHandEntity == null
&& Interaction.InRangeUnobstructed(uid, target);
&& _interaction.InRangeUnobstructed(uid, target);
}
}

View File

@@ -1,11 +1,14 @@
using Content.Shared.Actions;
using Content.Shared.Clothing;
using Content.Shared.Clothing.Components;
using Content.Shared.Clothing.EntitySystems;
using Content.Shared.Inventory.Events;
using Content.Shared.Item.ItemToggle;
using Content.Shared.Item.ItemToggle.Components;
using Content.Shared.Ninja.Components;
using Content.Shared.Popups;
using Content.Shared.Timing;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Timing;
namespace Content.Shared.Ninja.Systems;
@@ -14,137 +17,158 @@ namespace Content.Shared.Ninja.Systems;
/// </summary>
public abstract class SharedNinjaSuitSystem : EntitySystem
{
[Dependency] protected readonly IGameTiming GameTiming = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedNinjaGlovesSystem _gloves = default!;
[Dependency] private readonly SharedSpaceNinjaSystem _ninja = default!;
[Dependency] private readonly ActionContainerSystem _actionContainer = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly ItemToggleSystem _toggle = default!;
[Dependency] protected readonly SharedPopupSystem Popup = default!;
[Dependency] protected readonly StealthClothingSystem StealthClothing = default!;
[Dependency] private readonly SharedSpaceNinjaSystem _ninja = default!;
[Dependency] private readonly UseDelaySystem _useDelay = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<NinjaSuitComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<NinjaSuitComponent, GotEquippedEvent>(OnEquipped);
SubscribeLocalEvent<NinjaSuitComponent, ClothingGotEquippedEvent>(OnEquipped);
SubscribeLocalEvent<NinjaSuitComponent, GetItemActionsEvent>(OnGetItemActions);
SubscribeLocalEvent<NinjaSuitComponent, AddStealthActionEvent>(OnAddStealthAction);
SubscribeLocalEvent<NinjaSuitComponent, ToggleClothingCheckEvent>(OnCloakCheck);
SubscribeLocalEvent<NinjaSuitComponent, CheckItemCreatorEvent>(OnStarCheck);
SubscribeLocalEvent<NinjaSuitComponent, CreateItemAttemptEvent>(OnCreateStarAttempt);
SubscribeLocalEvent<NinjaSuitComponent, ItemToggleActivateAttemptEvent>(OnActivateAttempt);
SubscribeLocalEvent<NinjaSuitComponent, GotUnequippedEvent>(OnUnequipped);
}
private void OnMapInit(EntityUid uid, NinjaSuitComponent component, MapInitEvent args)
private void OnEquipped(Entity<NinjaSuitComponent> ent, ref ClothingGotEquippedEvent args)
{
_actionContainer.EnsureAction(uid, ref component.RecallKatanaActionEntity, component.RecallKatanaAction);
_actionContainer.EnsureAction(uid, ref component.CreateThrowingStarActionEntity, component.CreateThrowingStarAction);
_actionContainer.EnsureAction(uid, ref component.EmpActionEntity, component.EmpAction);
Dirty(uid, component);
var user = args.Wearer;
if (_ninja.NinjaQuery.TryComp(user, out var ninja))
NinjaEquipped(ent, (user, ninja));
}
/// <summary>
/// Call the shared and serverside code for when a ninja equips the suit.
/// </summary>
private void OnEquipped(EntityUid uid, NinjaSuitComponent comp, GotEquippedEvent args)
protected virtual void NinjaEquipped(Entity<NinjaSuitComponent> ent, Entity<SpaceNinjaComponent> user)
{
var user = args.Equipee;
if (!TryComp<SpaceNinjaComponent>(user, out var ninja))
return;
// mark the user as wearing this suit, used when being attacked among other things
_ninja.AssignSuit(user, ent);
}
NinjaEquippedSuit(uid, comp, user, ninja);
private void OnMapInit(Entity<NinjaSuitComponent> ent, ref MapInitEvent args)
{
var (uid, comp) = ent;
_actionContainer.EnsureAction(uid, ref comp.RecallKatanaActionEntity, comp.RecallKatanaAction);
_actionContainer.EnsureAction(uid, ref comp.EmpActionEntity, comp.EmpAction);
Dirty(uid, comp);
}
/// <summary>
/// Add all the actions when a suit is equipped by a ninja.
/// </summary>
private void OnGetItemActions(EntityUid uid, NinjaSuitComponent comp, GetItemActionsEvent args)
private void OnGetItemActions(Entity<NinjaSuitComponent> ent, ref GetItemActionsEvent args)
{
if (!HasComp<SpaceNinjaComponent>(args.User))
if (!_ninja.IsNinja(args.User))
return;
var comp = ent.Comp;
args.AddAction(ref comp.RecallKatanaActionEntity, comp.RecallKatanaAction);
args.AddAction(ref comp.CreateThrowingStarActionEntity, comp.CreateThrowingStarAction);
args.AddAction(ref comp.EmpActionEntity, comp.EmpAction);
}
/// <summary>
/// Only add stealth clothing's toggle action when equipped by a ninja.
/// Only add toggle cloak action when equipped by a ninja.
/// </summary>
private void OnAddStealthAction(EntityUid uid, NinjaSuitComponent comp, AddStealthActionEvent args)
private void OnCloakCheck(Entity<NinjaSuitComponent> ent, ref ToggleClothingCheckEvent args)
{
if (!HasComp<SpaceNinjaComponent>(args.User))
args.Cancel();
if (!_ninja.IsNinja(args.User))
args.Cancelled = true;
}
private void OnStarCheck(Entity<NinjaSuitComponent> ent, ref CheckItemCreatorEvent args)
{
if (!_ninja.IsNinja(args.User))
args.Cancelled = true;
}
private void OnCreateStarAttempt(Entity<NinjaSuitComponent> ent, ref CreateItemAttemptEvent args)
{
if (CheckDisabled(ent, args.User))
args.Cancelled = true;
}
/// <summary>
/// Call the shared and serverside code for when anyone unequips a suit.
/// </summary>
private void OnUnequipped(EntityUid uid, NinjaSuitComponent comp, GotUnequippedEvent args)
private void OnUnequipped(Entity<NinjaSuitComponent> ent, ref GotUnequippedEvent args)
{
UserUnequippedSuit(uid, comp, args.Equipee);
}
/// <summary>
/// Called when a suit is equipped by a space ninja.
/// In the future it might be changed to an explicit activation toggle/verb like gloves are.
/// </summary>
protected virtual void NinjaEquippedSuit(EntityUid uid, NinjaSuitComponent comp, EntityUid user, SpaceNinjaComponent ninja)
{
// mark the user as wearing this suit, used when being attacked among other things
_ninja.AssignSuit(user, uid, ninja);
// initialize phase cloak, but keep it off
StealthClothing.SetEnabled(uid, user, false);
var user = args.Equipee;
if (_ninja.NinjaQuery.TryComp(user, out var ninja))
UserUnequippedSuit(ent, (user, ninja));
}
/// <summary>
/// Force uncloaks the user and disables suit abilities.
/// </summary>
public void RevealNinja(EntityUid uid, EntityUid user, bool disable = true, NinjaSuitComponent? comp = null, StealthClothingComponent? stealthClothing = null)
public void RevealNinja(Entity<NinjaSuitComponent?> ent, EntityUid user, bool disable = true)
{
if (!Resolve(uid, ref comp, ref stealthClothing))
if (!Resolve(ent, ref ent.Comp))
return;
if (!StealthClothing.SetEnabled(uid, user, false, stealthClothing))
return;
if (!disable)
var uid = ent.Owner;
var comp = ent.Comp;
if (_toggle.TryDeactivate(uid, user) || !disable)
return;
// previously cloaked, disable abilities for a short time
_audio.PlayPredicted(comp.RevealSound, uid, user);
Popup.PopupClient(Loc.GetString("ninja-revealed"), user, user, PopupType.MediumCaution);
comp.DisableCooldown = GameTiming.CurTime + comp.DisableTime;
_useDelay.TryResetDelay(uid, id: comp.DisableDelayId);
}
// TODO: modify PowerCellDrain
/// <summary>
/// Returns the power used by a suit
/// </summary>
public float SuitWattage(EntityUid uid, NinjaSuitComponent? suit = null)
private void OnActivateAttempt(Entity<NinjaSuitComponent> ent, ref ItemToggleActivateAttemptEvent args)
{
if (!Resolve(uid, ref suit))
return 0f;
if (!_ninja.IsNinja(args.User))
{
args.Cancelled = true;
return;
}
float wattage = suit.PassiveWattage;
if (TryComp<StealthClothingComponent>(uid, out var stealthClothing) && stealthClothing.Enabled)
wattage += suit.CloakWattage;
return wattage;
if (IsDisabled((ent, ent.Comp, null)))
{
args.Cancelled = true;
args.Popup = Loc.GetString("ninja-suit-cooldown");
}
}
/// <summary>
/// Returns true if the suit is currently disabled
/// </summary>
public bool IsDisabled(Entity<NinjaSuitComponent?, UseDelayComponent?> ent)
{
if (!Resolve(ent, ref ent.Comp1, ref ent.Comp2))
return false;
return _useDelay.IsDelayed((ent, ent.Comp2), ent.Comp1.DisableDelayId);
}
protected bool CheckDisabled(Entity<NinjaSuitComponent> ent, EntityUid user)
{
if (IsDisabled((ent, ent.Comp, null)))
{
Popup.PopupEntity(Loc.GetString("ninja-suit-cooldown"), user, user, PopupType.Medium);
return true;
}
return false;
}
/// <summary>
/// Called when a suit is unequipped, not necessarily by a space ninja.
/// In the future it might be changed to also have explicit deactivation via toggle.
/// </summary>
protected virtual void UserUnequippedSuit(EntityUid uid, NinjaSuitComponent comp, EntityUid user)
protected virtual void UserUnequippedSuit(Entity<NinjaSuitComponent> ent, Entity<SpaceNinjaComponent> user)
{
if (!TryComp<SpaceNinjaComponent>(user, out var ninja))
return;
// mark the user as not wearing a suit
_ninja.AssignSuit(user, null, ninja);
_ninja.AssignSuit(user, null);
// disable glove abilities
if (ninja.Gloves != null && TryComp<NinjaGlovesComponent>(ninja.Gloves.Value, out var gloves))
_gloves.DisableGloves(ninja.Gloves.Value, gloves);
if (user.Comp.Gloves is {} uid)
_toggle.TryDeactivate(uid, user: user);
}
}

View File

@@ -3,6 +3,7 @@ using Content.Shared.Ninja.Components;
using Content.Shared.Weapons.Melee.Events;
using Content.Shared.Weapons.Ranged.Events;
using Content.Shared.Popups;
using System.Diagnostics.CodeAnalysis;
namespace Content.Shared.Ninja.Systems;
@@ -14,49 +15,59 @@ public abstract class SharedSpaceNinjaSystem : EntitySystem
[Dependency] protected readonly SharedNinjaSuitSystem Suit = default!;
[Dependency] protected readonly SharedPopupSystem Popup = default!;
public EntityQuery<SpaceNinjaComponent> NinjaQuery;
public override void Initialize()
{
base.Initialize();
NinjaQuery = GetEntityQuery<SpaceNinjaComponent>();
SubscribeLocalEvent<SpaceNinjaComponent, AttackedEvent>(OnNinjaAttacked);
SubscribeLocalEvent<SpaceNinjaComponent, MeleeAttackEvent>(OnNinjaAttack);
SubscribeLocalEvent<SpaceNinjaComponent, ShotAttemptedEvent>(OnShotAttempted);
}
public bool IsNinja([NotNullWhen(true)] EntityUid? uid)
{
return NinjaQuery.HasComp(uid);
}
/// <summary>
/// Set the ninja's worn suit entity
/// </summary>
public void AssignSuit(EntityUid uid, EntityUid? suit, SpaceNinjaComponent? comp = null)
public void AssignSuit(Entity<SpaceNinjaComponent> ent, EntityUid? suit)
{
if (!Resolve(uid, ref comp) || comp.Suit == suit)
if (ent.Comp.Suit == suit)
return;
comp.Suit = suit;
Dirty(uid, comp);
ent.Comp.Suit = suit;
Dirty(ent, ent.Comp);
}
/// <summary>
/// Set the ninja's worn gloves entity
/// </summary>
public void AssignGloves(EntityUid uid, EntityUid? gloves, SpaceNinjaComponent? comp = null)
public void AssignGloves(Entity<SpaceNinjaComponent> ent, EntityUid? gloves)
{
if (!Resolve(uid, ref comp) || comp.Gloves == gloves)
if (ent.Comp.Gloves == gloves)
return;
comp.Gloves = gloves;
Dirty(uid, comp);
ent.Comp.Gloves = gloves;
Dirty(ent, ent.Comp);
}
/// <summary>
/// Bind a katana entity to a ninja, letting it be recalled and dash.
/// Does nothing if the player is not a ninja or already has a katana bound.
/// </summary>
public void BindKatana(EntityUid uid, EntityUid? katana, SpaceNinjaComponent? comp = null)
public void BindKatana(Entity<SpaceNinjaComponent?> ent, EntityUid katana)
{
if (!Resolve(uid, ref comp) || comp.Katana == katana)
if (!NinjaQuery.Resolve(ent, ref ent.Comp) || ent.Comp.Katana != null)
return;
comp.Katana = katana;
Dirty(uid, comp);
ent.Comp.Katana = katana;
Dirty(ent, ent.Comp);
}
/// <summary>
@@ -71,32 +82,32 @@ public abstract class SharedSpaceNinjaSystem : EntitySystem
/// <summary>
/// Handle revealing ninja if cloaked when attacked.
/// </summary>
private void OnNinjaAttacked(EntityUid uid, SpaceNinjaComponent comp, AttackedEvent args)
private void OnNinjaAttacked(Entity<SpaceNinjaComponent> ent, ref AttackedEvent args)
{
if (comp.Suit != null && TryComp<StealthClothingComponent>(comp.Suit, out var stealthClothing) && stealthClothing.Enabled)
{
Suit.RevealNinja(comp.Suit.Value, uid, true, null, stealthClothing);
}
TryRevealNinja(ent, disable: true);
}
/// <summary>
/// Handle revealing ninja if cloaked when attacking.
/// Only reveals, there is no cooldown.
/// </summary>
private void OnNinjaAttack(EntityUid uid, SpaceNinjaComponent comp, ref MeleeAttackEvent args)
private void OnNinjaAttack(Entity<SpaceNinjaComponent> ent, ref MeleeAttackEvent args)
{
if (comp.Suit != null && TryComp<StealthClothingComponent>(comp.Suit, out var stealthClothing) && stealthClothing.Enabled)
{
Suit.RevealNinja(comp.Suit.Value, uid, false, null, stealthClothing);
TryRevealNinja(ent, disable: false);
}
private void TryRevealNinja(Entity<SpaceNinjaComponent> ent, bool disable)
{
if (ent.Comp.Suit is {} uid && TryComp<NinjaSuitComponent>(ent.Comp.Suit, out var suit))
Suit.RevealNinja((uid, suit), ent, disable: disable);
}
/// <summary>
/// Require ninja to fight with HONOR, no guns!
/// </summary>
private void OnShotAttempted(EntityUid uid, SpaceNinjaComponent comp, ref ShotAttemptedEvent args)
private void OnShotAttempted(Entity<SpaceNinjaComponent> ent, ref ShotAttemptedEvent args)
{
Popup.PopupClient(Loc.GetString("gun-disabled"), uid, uid);
Popup.PopupClient(Loc.GetString("gun-disabled"), ent, ent);
args.Cancel();
}
}

View File

@@ -0,0 +1,6 @@
namespace Content.Shared.Ninja.Systems;
/// <summary>
/// Sticking triggering and exploding are all in server so this is just for access.
/// </summary>
public abstract class SharedSpiderChargeSystem : EntitySystem;

View File

@@ -11,22 +11,12 @@ public abstract class SharedStunProviderSystem : EntitySystem
/// <summary>
/// Set the battery field on the stun provider.
/// </summary>
public void SetBattery(EntityUid uid, EntityUid? battery, StunProviderComponent? comp = null)
public void SetBattery(Entity<StunProviderComponent?> ent, EntityUid? battery)
{
if (!Resolve(uid, ref comp))
if (!Resolve(ent, ref ent.Comp) || ent.Comp.BatteryUid == battery)
return;
comp.BatteryUid = battery;
}
/// <summary>
/// Set the no power popup field on the stun provider.
/// </summary>
public void SetNoPowerPopup(EntityUid uid, string popup, StunProviderComponent? comp = null)
{
if (!Resolve(uid, ref comp))
return;
comp.NoPowerPopup = popup;
ent.Comp.BatteryUid = battery;
Dirty(ent, ent.Comp);
}
}

View File

@@ -92,7 +92,7 @@ public abstract class SharedObjectivesSystem : EntitySystem
}
/// <summary>
/// Get the title, description, icon and progress of an objective using <see cref="ObjectiveGetInfoEvent"/>.
/// Get the title, description, icon and progress of an objective using <see cref="ObjectiveGetProgressEvent"/>.
/// If any of them are null it is logged and null is returned.
/// </summary>
/// <param name="uid"/>ID of the condition entity</param>
@@ -103,20 +103,43 @@ public abstract class SharedObjectivesSystem : EntitySystem
if (!Resolve(mindId, ref mind))
return null;
var ev = new ObjectiveGetProgressEvent(mindId, mind);
RaiseLocalEvent(uid, ref ev);
if (GetProgress(uid, (mindId, mind)) is not {} progress)
return null;
var comp = Comp<ObjectiveComponent>(uid);
var meta = MetaData(uid);
var title = meta.EntityName;
var description = meta.EntityDescription;
if (comp.Icon == null || ev.Progress == null)
if (comp.Icon == null)
{
Log.Error($"An objective {ToPrettyString(uid):objective} of {_mind.MindOwnerLoggingString(mind)} is missing icon or progress ({ev.Progress})");
Log.Error($"An objective {ToPrettyString(uid):objective} of {_mind.MindOwnerLoggingString(mind)} is missing an icon!");
return null;
}
return new ObjectiveInfo(title, description, comp.Icon, ev.Progress.Value);
return new ObjectiveInfo(title, description, comp.Icon, progress);
}
/// <summary>
/// Gets the progress of an objective using <see cref="ObjectiveGetProgressEvent"/>.
/// Returning null is a programmer error.
/// </summary>
public float? GetProgress(EntityUid uid, Entity<MindComponent> mind)
{
var ev = new ObjectiveGetProgressEvent(mind, mind.Comp);
RaiseLocalEvent(uid, ref ev);
if (ev.Progress != null)
return ev.Progress;
Log.Error($"Objective {ToPrettyString(uid):objective} of {_mind.MindOwnerLoggingString(mind.Comp)} didn't set a progress value!");
return null;
}
/// <summary>
/// Returns true if an objective is completed.
/// </summary>
public bool IsCompleted(EntityUid uid, Entity<MindComponent> mind)
{
return (GetProgress(uid, mind) ?? 0f) >= 0.999f;
}
/// <summary>

View File

@@ -1,9 +0,0 @@
using Robust.Shared.Serialization;
namespace Content.Shared.Pinpointer;
[Serializable, NetSerializable]
public enum ProximityBeeperVisuals : byte
{
Enabled
}

View File

@@ -6,6 +6,10 @@ namespace Content.Shared.PowerCell;
/// <summary>
/// Indicates that the entity's ActivatableUI requires power or else it closes.
/// </summary>
/// <remarks>
/// With ActivatableUI it will activate and deactivate when the ui is opened and closed, drawing power inbetween.
/// Requires <see cref="ItemToggleComponent"/> to work.
/// </remarks>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
public sealed partial class PowerCellDrawComponent : Component
{
@@ -26,10 +30,12 @@ public sealed partial class PowerCellDrawComponent : Component
#endregion
/// <summary>
/// Is this power cell currently drawing power every tick.
/// Whether drawing is enabled, regardless of ItemToggle.
/// Having no cell will still disable it.
/// Only use this if you really don't want it to use power for some time.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("enabled")]
public bool Drawing;
[DataField, AutoNetworkedField]
public bool Enabled = true;
/// <summary>
/// How much the entity draws while the UI is open.
@@ -51,4 +57,10 @@ public sealed partial class PowerCellDrawComponent : Component
[DataField("nextUpdate", customTypeSerializer: typeof(TimeOffsetSerializer))]
[AutoPausedField]
public TimeSpan NextUpdateTime;
/// <summary>
/// How long to wait between power drawing.
/// </summary>
[DataField]
public TimeSpan Delay = TimeSpan.FromSeconds(1);
}

View File

@@ -1,4 +1,6 @@
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Item.ItemToggle;
using Content.Shared.Item.ItemToggle.Components;
using Content.Shared.PowerCell.Components;
using Content.Shared.Rejuvenate;
using Robust.Shared.Containers;
@@ -11,14 +13,19 @@ public abstract class SharedPowerCellSystem : EntitySystem
[Dependency] protected readonly IGameTiming Timing = default!;
[Dependency] private readonly ItemSlotsSystem _itemSlots = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] protected readonly ItemToggleSystem Toggle = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<PowerCellSlotComponent, RejuvenateEvent>(OnRejuvenate);
SubscribeLocalEvent<PowerCellSlotComponent, EntInsertedIntoContainerMessage>(OnCellInserted);
SubscribeLocalEvent<PowerCellSlotComponent, EntRemovedFromContainerMessage>(OnCellRemoved);
SubscribeLocalEvent<PowerCellSlotComponent, ContainerIsInsertingAttemptEvent>(OnCellInsertAttempt);
SubscribeLocalEvent<PowerCellDrawComponent, ItemToggleActivateAttemptEvent>(OnActivateAttempt);
SubscribeLocalEvent<PowerCellDrawComponent, ItemToggledEvent>(OnToggled);
}
private void OnRejuvenate(EntityUid uid, PowerCellSlotComponent component, RejuvenateEvent args)
@@ -63,13 +70,25 @@ public abstract class SharedPowerCellSystem : EntitySystem
RaiseLocalEvent(uid, new PowerCellChangedEvent(true), false);
}
public void SetPowerCellDrawEnabled(EntityUid uid, bool enabled, PowerCellDrawComponent? component = null)
private void OnActivateAttempt(Entity<PowerCellDrawComponent> ent, ref ItemToggleActivateAttemptEvent args)
{
if (!Resolve(uid, ref component, false) || enabled == component.Drawing)
if (!HasDrawCharge(ent, ent.Comp, user: args.User)
|| !HasActivatableCharge(ent, ent.Comp, user: args.User))
args.Cancelled = true;
}
private void OnToggled(Entity<PowerCellDrawComponent> ent, ref ItemToggledEvent args)
{
ent.Comp.NextUpdateTime = Timing.CurTime;
}
public void SetDrawEnabled(Entity<PowerCellDrawComponent?> ent, bool enabled)
{
if (!Resolve(ent, ref ent.Comp, false) || ent.Comp.Enabled == enabled)
return;
component.Drawing = enabled;
component.NextUpdateTime = Timing.CurTime;
ent.Comp.Enabled = enabled;
Dirty(ent, ent.Comp);
}
/// <summary>

View File

@@ -10,12 +10,6 @@ namespace Content.Shared.ProximityDetection.Components;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState ,Access(typeof(ProximityDetectionSystem))]
public sealed partial class ProximityDetectorComponent : Component
{
/// <summary>
/// Whether or not it's on.
/// </summary>
[DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)]
public bool Enabled = true;
/// <summary>
/// The criteria used to filter entities
/// Note: RequireAll is only supported for tags, all components are required to count as a match!
@@ -35,13 +29,13 @@ public sealed partial class ProximityDetectorComponent : Component
[ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
public FixedPoint2 Distance = -1;
/// <summary>
/// The farthest distance to search for targets
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
public FixedPoint2 Range = 10f;
// TODO: use timespans not this
public float AccumulatedFrameTime;
[DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]

View File

@@ -1,4 +1,6 @@
using Content.Shared.ProximityDetection.Components;
using Content.Shared.Item.ItemToggle;
using Content.Shared.Item.ItemToggle.Components;
using Content.Shared.ProximityDetection.Components;
using Content.Shared.Tag;
using Robust.Shared.Network;
@@ -9,6 +11,7 @@ namespace Content.Shared.ProximityDetection.Systems;
public sealed class ProximityDetectionSystem : EntitySystem
{
[Dependency] private readonly EntityLookupSystem _entityLookup = default!;
[Dependency] private readonly ItemToggleSystem _toggle = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly TagSystem _tagSystem = default!;
[Dependency] private readonly INetManager _net = default!;
@@ -17,10 +20,10 @@ public sealed class ProximityDetectionSystem : EntitySystem
public override void Initialize()
{
SubscribeLocalEvent<ProximityDetectorComponent, EntityPausedEvent>(OnPaused);
SubscribeLocalEvent<ProximityDetectorComponent, EntityUnpausedEvent>(OnUnpaused);
SubscribeLocalEvent<ProximityDetectorComponent, ComponentInit>(OnCompInit);
base.Initialize();
SubscribeLocalEvent<ProximityDetectorComponent, ComponentInit>(OnCompInit);
SubscribeLocalEvent<ProximityDetectorComponent, ItemToggledEvent>(OnToggled);
}
private void OnCompInit(EntityUid uid, ProximityDetectorComponent component, ComponentInit args)
@@ -30,57 +33,39 @@ public sealed class ProximityDetectionSystem : EntitySystem
Log.Debug("DetectorComponent only supports requireAll = false for tags. All components are required for a match!");
}
private void OnPaused(EntityUid owner, ProximityDetectorComponent component, EntityPausedEvent args)
{
SetEnable_Internal(owner,component,false);
}
private void OnUnpaused(EntityUid owner, ProximityDetectorComponent detector, ref EntityUnpausedEvent args)
{
SetEnable_Internal(owner, detector,true);
}
public void SetEnable(EntityUid owner, bool enabled, ProximityDetectorComponent? detector = null)
{
if (!Resolve(owner, ref detector) || detector.Enabled == enabled)
return;
SetEnable_Internal(owner ,detector, enabled);
}
public override void Update(float frameTime)
{
if (_net.IsClient)
return;
var query = EntityQueryEnumerator<ProximityDetectorComponent>();
while (query.MoveNext(out var owner, out var detector))
{
if (!detector.Enabled)
if (!_toggle.IsActivated(owner))
continue;
detector.AccumulatedFrameTime += frameTime;
if (detector.AccumulatedFrameTime < detector.UpdateRate)
continue;
detector.AccumulatedFrameTime -= detector.UpdateRate;
RunUpdate_Internal(owner, detector);
}
}
public bool GetEnable(EntityUid owner, ProximityDetectorComponent? detector = null)
private void OnToggled(Entity<ProximityDetectorComponent> ent, ref ItemToggledEvent args)
{
return Resolve(owner, ref detector, false) && detector.Enabled;
}
private void SetEnable_Internal(EntityUid owner,ProximityDetectorComponent detector, bool enabled)
if (args.Activated)
{
detector.Enabled = enabled;
var noDetectEvent = new ProximityTargetUpdatedEvent(detector, detector.TargetEnt, detector.Distance);
RaiseLocalEvent(owner, ref noDetectEvent);
if (!enabled)
{
detector.AccumulatedFrameTime = 0;
RunUpdate_Internal(owner, detector);
Dirty(owner, detector);
RunUpdate_Internal(ent, ent.Comp);
return;
}
RunUpdate_Internal(owner, detector);
var noDetectEvent = new ProximityTargetUpdatedEvent(ent.Comp, Target: null, ent.Comp.Distance);
RaiseLocalEvent(ent, ref noDetectEvent);
ent.Comp.AccumulatedFrameTime = 0;
Dirty(ent, ent.Comp);
}
public void ForceUpdate(EntityUid owner, ProximityDetectorComponent? detector = null)
@@ -90,11 +75,31 @@ public sealed class ProximityDetectionSystem : EntitySystem
RunUpdate_Internal(owner, detector);
}
private void ClearTarget(Entity<ProximityDetectorComponent> ent)
{
var (uid, comp) = ent;
if (comp.TargetEnt == null)
return;
comp.Distance = -1;
comp.TargetEnt = null;
var noDetectEvent = new ProximityTargetUpdatedEvent(comp, null, -1);
RaiseLocalEvent(uid, ref noDetectEvent);
var newTargetEvent = new NewProximityTargetEvent(comp, null);
RaiseLocalEvent(uid, ref newTargetEvent);
Dirty(uid, comp);
}
private void RunUpdate_Internal(EntityUid owner,ProximityDetectorComponent detector)
{
if (!_net.IsServer) //only run detection checks on the server!
return;
if (Deleted(detector.TargetEnt))
{
ClearTarget((owner, detector));
}
var xformQuery = GetEntityQuery<TransformComponent>();
var xform = xformQuery.GetComponent(owner);
List<(EntityUid TargetEnt, float Distance)> detections = new();
@@ -173,15 +178,7 @@ public sealed class ProximityDetectionSystem : EntitySystem
{
if (detections.Count == 0)
{
if (detector.TargetEnt == null)
return;
detector.Distance = -1;
detector.TargetEnt = null;
var noDetectEvent = new ProximityTargetUpdatedEvent(detector, null, -1);
RaiseLocalEvent(owner, ref noDetectEvent);
var newTargetEvent = new NewProximityTargetEvent(detector, null);
RaiseLocalEvent(owner, ref newTargetEvent);
Dirty(owner, detector);
ClearTarget((owner, detector));
return;
}
var closestDistance = detections[0].Distance;
@@ -198,6 +195,7 @@ public sealed class ProximityDetectionSystem : EntitySystem
var newData = newTarget || detector.Distance != closestDistance;
detector.TargetEnt = closestEnt;
detector.Distance = closestDistance;
Dirty(owner, detector);
if (newTarget)
{
var newTargetEvent = new NewProximityTargetEvent(detector, closestEnt);

View File

@@ -15,12 +15,6 @@ namespace Content.Shared.Silicons.Borgs.Components;
[RegisterComponent, NetworkedComponent, Access(typeof(SharedBorgSystem)), AutoGenerateComponentState]
public sealed partial class BorgChassisComponent : Component
{
/// <summary>
/// Whether or not the borg is activated, meaning it has access to modules and a heightened movement speed
/// </summary>
[DataField("activated"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
public bool Activated;
#region Brain
/// <summary>
/// A whitelist for which entities count as valid brains
@@ -68,7 +62,7 @@ public sealed partial class BorgChassisComponent : Component
/// <summary>
/// The currently selected module
/// </summary>
[DataField("selectedModule")]
[DataField("selectedModule"), AutoNetworkedField]
public EntityUid? SelectedModule;
#region Visuals

View File

@@ -1,5 +1,6 @@
using Content.Shared.Access.Components;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Item.ItemToggle;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems;
using Content.Shared.Popups;
@@ -18,6 +19,7 @@ public abstract partial class SharedBorgSystem : EntitySystem
{
[Dependency] protected readonly SharedContainerSystem Container = default!;
[Dependency] protected readonly ItemSlotsSystem ItemSlots = default!;
[Dependency] protected readonly ItemToggleSystem Toggle = default!;
[Dependency] protected readonly SharedPopupSystem Popup = default!;
/// <inheritdoc/>
@@ -96,7 +98,7 @@ public abstract partial class SharedBorgSystem : EntitySystem
private void OnRefreshMovementSpeedModifiers(EntityUid uid, BorgChassisComponent component, RefreshMovementSpeedModifiersEvent args)
{
if (component.Activated)
if (Toggle.IsActivated(uid))
return;
if (!TryComp<MovementSpeedModifierComponent>(uid, out var movement))

View File

@@ -6,7 +6,10 @@ namespace Content.Shared.Toggleable;
/// <summary>
/// Generic action-event for toggle-able components.
/// </summary>
public sealed partial class ToggleActionEvent : InstantActionEvent { }
/// <remarks>
/// If you are using <c>ItemToggleComponent</c> subscribe to <c>ItemToggledEvent</c> instead.
/// </remarks>
public sealed partial class ToggleActionEvent : InstantActionEvent;
/// <summary>
/// Generic enum keys for toggle-visualizer appearance data & sprite layers.

View File

@@ -24,7 +24,7 @@ public abstract partial class SharedToolSystem : EntitySystem
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] protected readonly SharedInteractionSystem InteractionSystem = default!;
[Dependency] protected readonly SharedItemToggleSystem ItemToggle = default!;
[Dependency] protected readonly ItemToggleSystem ItemToggle = default!;
[Dependency] private readonly SharedMapSystem _maps = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] protected readonly SharedSolutionContainerSystem SolutionContainerSystem = default!;

View File

@@ -1,3 +1,5 @@
using Content.Shared.Item.ItemToggle;
using Content.Shared.Item.ItemToggle.Components;
using Content.Shared.PowerCell;
using Robust.Shared.Containers;
@@ -5,6 +7,7 @@ namespace Content.Shared.UserInterface;
public sealed partial class ActivatableUISystem
{
[Dependency] private readonly ItemToggleSystem _toggle = default!;
[Dependency] private readonly SharedPowerCellSystem _cell = default!;
private void InitializePower()
@@ -12,27 +15,22 @@ public sealed partial class ActivatableUISystem
SubscribeLocalEvent<ActivatableUIRequiresPowerCellComponent, ActivatableUIOpenAttemptEvent>(OnBatteryOpenAttempt);
SubscribeLocalEvent<ActivatableUIRequiresPowerCellComponent, BoundUIOpenedEvent>(OnBatteryOpened);
SubscribeLocalEvent<ActivatableUIRequiresPowerCellComponent, BoundUIClosedEvent>(OnBatteryClosed);
SubscribeLocalEvent<PowerCellDrawComponent, EntRemovedFromContainerMessage>(OnPowerCellRemoved);
SubscribeLocalEvent<ActivatableUIRequiresPowerCellComponent, ItemToggledEvent>(OnToggled);
}
private void OnPowerCellRemoved(EntityUid uid, PowerCellDrawComponent component, EntRemovedFromContainerMessage args)
{
_cell.SetPowerCellDrawEnabled(uid, false);
if (!HasComp<ActivatableUIRequiresPowerCellComponent>(uid) ||
!TryComp(uid, out ActivatableUIComponent? activatable))
private void OnToggled(Entity<ActivatableUIRequiresPowerCellComponent> ent, ref ItemToggledEvent args)
{
// only close ui when losing power
if (!TryComp<ActivatableUIComponent>(ent, out var activatable) || args.Activated)
return;
}
if (activatable.Key == null)
{
Log.Error($"Encountered null key in activatable ui on entity {ToPrettyString(uid)}");
Log.Error($"Encountered null key in activatable ui on entity {ToPrettyString(ent)}");
return;
}
_uiSystem.CloseUi(uid, activatable.Key);
_uiSystem.CloseUi(ent.Owner, activatable.Key);
}
private void OnBatteryOpened(EntityUid uid, ActivatableUIRequiresPowerCellComponent component, BoundUIOpenedEvent args)
@@ -42,7 +40,7 @@ public sealed partial class ActivatableUISystem
if (!args.UiKey.Equals(activatable.Key))
return;
_cell.SetPowerCellDrawEnabled(uid, true);
_toggle.TryActivate(uid);
}
private void OnBatteryClosed(EntityUid uid, ActivatableUIRequiresPowerCellComponent component, BoundUIClosedEvent args)
@@ -54,7 +52,7 @@ public sealed partial class ActivatableUISystem
// Stop drawing power if this was the last person with the UI open.
if (!_uiSystem.IsUiOpen(uid, activatable.Key))
_cell.SetPowerCellDrawEnabled(uid, false);
_toggle.TryDeactivate(uid);
}
/// <summary>

View File

@@ -5,16 +5,11 @@ namespace Content.Shared.Weapons.Reflect;
/// <summary>
/// Entities with this component have a chance to reflect projectiles and hitscan shots
/// Uses <c>ItemToggleComponent</c> to control reflection.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class ReflectComponent : Component
{
/// <summary>
/// Can only reflect when enabled
/// </summary>
[DataField("enabled"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
public bool Enabled = true;
/// <summary>
/// What we reflect.
/// </summary>

View File

@@ -7,6 +7,7 @@ using Content.Shared.Database;
using Content.Shared.Hands;
using Content.Shared.Inventory;
using Content.Shared.Inventory.Events;
using Content.Shared.Item.ItemToggle;
using Content.Shared.Item.ItemToggle.Components;
using Content.Shared.Popups;
using Content.Shared.Projectiles;
@@ -27,10 +28,11 @@ namespace Content.Shared.Weapons.Reflect;
/// </summary>
public sealed class ReflectSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly INetManager _netManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly ItemToggleSystem _toggle = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
@@ -93,7 +95,7 @@ public sealed class ReflectSystem : EntitySystem
private bool TryReflectProjectile(EntityUid user, EntityUid reflector, EntityUid projectile, ProjectileComponent? projectileComp = null, ReflectComponent? reflect = null)
{
if (!Resolve(reflector, ref reflect, false) ||
!reflect.Enabled ||
!_toggle.IsActivated(reflector) ||
!TryComp<ReflectiveComponent>(projectile, out var reflective) ||
(reflect.Reflects & reflective.Reflective) == 0x0 ||
!_random.Prob(reflect.ReflectProb) ||
@@ -162,7 +164,7 @@ public sealed class ReflectSystem : EntitySystem
[NotNullWhen(true)] out Vector2? newDirection)
{
if (!TryComp<ReflectComponent>(reflector, out var reflect) ||
!reflect.Enabled ||
!_toggle.IsActivated(reflector) ||
!_random.Prob(reflect.ReflectProb))
{
newDirection = null;
@@ -214,8 +216,8 @@ public sealed class ReflectSystem : EntitySystem
private void OnToggleReflect(EntityUid uid, ReflectComponent comp, ref ItemToggledEvent args)
{
comp.Enabled = args.Activated;
Dirty(uid, comp);
if (args.User is {} user)
RefreshReflectUser(user);
}
/// <summary>
@@ -225,7 +227,7 @@ public sealed class ReflectSystem : EntitySystem
{
foreach (var ent in _inventorySystem.GetHandOrInventoryEntities(user, SlotFlags.All & ~SlotFlags.POCKET))
{
if (!HasComp<ReflectComponent>(ent))
if (!HasComp<ReflectComponent>(ent) || !_toggle.IsActivated(ent))
continue;
EnsureComp<ReflectUserComponent>(user);

View File

@@ -2,7 +2,7 @@
- type: entity
id: ActionToggleNinjaGloves
name: Toggle ninja gloves
description: Toggles all glove actions on left click. Includes your doorjack, draining power, stunning enemies, downloading research and calling in a threat.
description: Toggles all glove actions on left click. Includes your doorjack, draining power, stunning enemies and hacking certain computers.
components:
- type: InstantAction
priority: -13
@@ -21,7 +21,7 @@
state: icon
itemIconStyle: NoItem
priority: -10
event: !type:CreateThrowingStarEvent {}
event: !type:CreateItemEvent {}
- type: entity
id: ActionRecallKatana
@@ -59,7 +59,7 @@
# have to plan (un)cloaking ahead of time
useDelay: 5
priority: -9
event: !type:ToggleStealthEvent
event: !type:ToggleActionEvent
# katana
- type: entity
@@ -72,6 +72,10 @@
sprite: Objects/Magic/magicactions.rsi
state: blink
itemIconStyle: NoItem
sound:
path: /Audio/Magic/blink.ogg
params:
volume: 5
priority: -12
event: !type:DashEvent
checkCanAccess: false

View File

@@ -206,7 +206,7 @@
- type: FingerprintMask
- type: entity
parent: ClothingHandsBase
parent: [ClothingHandsBase, BaseToggleClothing]
id: ClothingHandsGlovesSpaceNinja
name: space ninja gloves
description: These black nano-enhanced gloves insulate from electricity and provide fire resistance.
@@ -234,7 +234,31 @@
- type: Thieving
stripTimeReduction: 1
stealthy: true
- type: ToggleClothing
action: ActionToggleNinjaGloves
- type: NinjaGloves
abilities:
- components:
- type: BatteryDrainer
- type: StunProvider
noPowerPopup: ninja-no-power
whitelist:
components:
- Stamina
- type: EmagProvider
whitelist:
components:
- Airlock
- objective: StealResearchObjective
components:
- type: ResearchStealer
- objective: TerrorObjective
components:
- type: CommsHacker
threats: NinjaThreats
- objective: MassArrestObjective
components:
- type: CriminalRecordsHacker
- type: entity
parent: ClothingHandsGlovesColorBlack

View File

@@ -183,6 +183,36 @@
- type: AddAccentClothing
accent: OwOAccent
- type: entity
parent: [ClothingHeadHatCatEars, BaseToggleClothing]
id: ClothingHeadHatCatEarsValid
suffix: Valid, DO NOT MAP
components:
- type: ToggleClothing
action: ActionBecomeValid
disableOnUnequip: true
- type: ComponentToggler
parent: true
components:
- type: KillSign
- type: Tag
tags: [] # ignore "WhitelistChameleon" tag
- type: Sprite
sprite: Clothing/Head/Hats/catears.rsi
- type: Clothing
sprite: Clothing/Head/Hats/catears.rsi
- type: AddAccentClothing
accent: OwOAccent
- type: entity
noSpawn: true
id: ActionBecomeValid
name: Become Valid
description: "*notices your killsign* owo whats this"
components:
- type: InstantAction
event: !type:ToggleActionEvent
- type: entity
parent: ClothingHeadBase
id: ClothingHeadHatDogEars

View File

@@ -127,7 +127,7 @@
slots: WITHOUT_POCKET
- type: entity
parent: [ClothingOuterBaseLarge, AllowSuitStorageClothing]
parent: [ClothingOuterBaseLarge, AllowSuitStorageClothing, BaseToggleClothing]
id: ClothingOuterSuitSpaceNinja
name: space ninja suit
description: This black technologically advanced, cybernetically-enhanced suit provides many abilities like invisibility or teleportation.
@@ -136,9 +136,7 @@
sprite: Clothing/OuterClothing/Suits/spaceninja.rsi
- type: Clothing
sprite: Clothing/OuterClothing/Suits/spaceninja.rsi
- type: StealthClothing
visibility: 1.1
toggleAction: ActionTogglePhaseCloak
# hardsuit stuff
- type: PressureProtection
highPressureMultiplier: 0.6
lowPressureMultiplier: 1000
@@ -151,7 +149,27 @@
Slash: 0.8
Piercing: 0.8
Heat: 0.8
# phase cloak
- type: ToggleClothing
action: ActionTogglePhaseCloak
- type: ComponentToggler
parent: true
components:
- type: Stealth
minVisibility: 0.1
lastVisibility: 0.1
- type: PowerCellDraw
drawRate: 1.8 # 200 seconds on the default cell
# throwing star ability
- type: ItemCreator
action: ActionCreateThrowingStar
charge: 14.4
spawnedPrototype: ThrowingStarNinja
noPowerPopup: ninja-no-power
# core ninja suit stuff
- type: NinjaSuit
- type: UseDelay
delay: 5 # disable time
- type: PowerCellSlot
cellSlotId: cell_slot
# throwing in a recharger would bypass glove charging mechanic

View File

@@ -1,5 +1,5 @@
- type: entity
parent: ClothingShoesBase
parent: [ClothingShoesBase, BaseToggleClothing]
id: ClothingShoesBootsMag
name: magboots
description: Magnetic boots, often used during extravehicular activity to ensure the user remains safely attached to the vehicle.
@@ -11,11 +11,17 @@
map: [ "enum.ToggleVisuals.Layer" ]
- type: Clothing
sprite: Clothing/Shoes/Boots/magboots.rsi
- type: ToggleClothing
action: ActionToggleMagboots
- type: ToggleVerb
text: toggle-magboots-verb-get-data-text
- type: ComponentToggler
components:
- type: NoSlip
- type: Magboots
- type: ClothingSpeedModifier
walkModifier: 0.85
sprintModifier: 0.8
enabled: false
- type: Appearance
- type: GenericVisualizer
visuals:
@@ -40,13 +46,9 @@
state: icon
- type: Clothing
sprite: Clothing/Shoes/Boots/magboots-advanced.rsi
- type: Magboots
toggleAction: ActionToggleMagbootsAdvanced
- type: ClothingSpeedModifier
walkModifier: 1
sprintModifier: 1
enabled: false
- type: NoSlip
- type: Tag
tags:
- WhitelistChameleon
@@ -64,8 +66,6 @@
sprite: Clothing/Shoes/Boots/magboots-science.rsi
- type: Clothing
sprite: Clothing/Shoes/Boots/magboots-science.rsi
- type: Magboots
toggleAction: ActionToggleMagbootsSci
- type: entity
parent: ClothingShoesBootsMag
@@ -76,7 +76,6 @@
- type: ClothingSpeedModifier
walkModifier: 1.10 #PVS isn't too much of an issue when you are blind...
sprintModifier: 1.10
enabled: false
- type: StaticPrice
price: 3000
@@ -91,12 +90,9 @@
state: icon
- type: Clothing
sprite: Clothing/Shoes/Boots/magboots-syndicate.rsi
- type: Magboots
toggleAction: ActionToggleMagbootsSyndie
- type: ClothingSpeedModifier
walkModifier: 0.95
sprintModifier: 0.9
enabled: false
- type: GasTank
outputPressure: 42.6
air:
@@ -111,42 +107,10 @@
size: Normal
- type: entity
id: ActionBaseToggleMagboots
id: ActionToggleMagboots
name: Toggle Magboots
description: Toggles the magboots on and off.
components:
- type: InstantAction
itemIconStyle: NoItem
event: !type:ToggleMagbootsEvent
- type: entity
id: ActionToggleMagboots
parent: ActionBaseToggleMagboots
components:
- type: InstantAction
icon: { sprite: Clothing/Shoes/Boots/magboots.rsi, state: icon }
iconOn: { sprite : Clothing/Shoes/Boots/magboots.rsi, state: icon-on }
- type: entity
id: ActionToggleMagbootsAdvanced
parent: ActionBaseToggleMagboots
components:
- type: InstantAction
icon: { sprite: Clothing/Shoes/Boots/magboots-advanced.rsi, state: icon }
iconOn: Clothing/Shoes/Boots/magboots-advanced.rsi/icon-on.png
- type: entity
id: ActionToggleMagbootsSci
parent: ActionBaseToggleMagboots
components:
- type: InstantAction
icon: { sprite: Clothing/Shoes/Boots/magboots-science.rsi, state: icon }
iconOn: Clothing/Shoes/Boots/magboots-science.rsi/icon-on.png
- type: entity
id: ActionToggleMagbootsSyndie
parent: ActionBaseToggleMagboots
components:
- type: InstantAction
icon: { sprite: Clothing/Shoes/Boots/magboots-syndicate.rsi, state: icon }
iconOn: Clothing/Shoes/Boots/magboots-syndicate.rsi/icon-on.png
itemIconStyle: BigItem
event: !type:ToggleActionEvent

View File

@@ -88,7 +88,7 @@
- type: NoSlip
- type: entity
parent: [ClothingShoesBase, PowerCellSlotSmallItem]
parent: [ClothingShoesBase, PowerCellSlotSmallItem, BaseToggleClothing]
id: ClothingShoesBootsSpeed
name: speed boots
description: High-tech boots woven with quantum fibers, able to convert electricity into pure speed!
@@ -100,12 +100,11 @@
map: [ "enum.ToggleVisuals.Layer" ]
- type: Clothing
sprite: Clothing/Shoes/Boots/speedboots.rsi
- type: ToggleClothingSpeed
toggleAction: ActionToggleSpeedBoots
- type: ToggleClothing
action: ActionToggleSpeedBoots
- type: ClothingSpeedModifier
walkModifier: 1.5
sprintModifier: 1.5
enabled: false
- type: Appearance
- type: GenericVisualizer
visuals:
@@ -130,10 +129,8 @@
description: Toggles the speed boots on and off.
components:
- type: InstantAction
itemIconStyle: NoItem
event: !type:ToggleClothingSpeedEvent
icon: { sprite: Clothing/Shoes/Boots/speedboots.rsi, state: icon }
iconOn: { sprite: Clothing/Shoes/Boots/speedboots.rsi, state: icon-on }
itemIconStyle: BigItem
event: !type:ToggleActionEvent
- type: entity
parent: ClothingShoesBase

Some files were not shown because too many files have changed in this diff Show More