+++ /dev/null
-using Content.Shared.Item.ItemToggle;
-
-namespace Content.Shared.Item;
-
-/// <inheritdoc/>
-public sealed class ItemToggleSystem : SharedItemToggleSystem
-{
-
-}
--- /dev/null
+using Content.Shared.Ninja.Systems;
+
+namespace Content.Client.Ninja.Systems;
+
+public sealed class ItemCreatorSystem : SharedItemCreatorSystem;
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;
-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;
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;
--- /dev/null
+using Content.Shared.Ninja.Systems;
+
+namespace Content.Client.Ninja.Systems;
+
+public sealed class SpiderChargeSystem : SharedSpiderChargeSystem;
// 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));
}
});
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!;
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>();
/// 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
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;
}
}
+++ /dev/null
-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");
-}
+++ /dev/null
-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;
-}
+++ /dev/null
-namespace Content.Server.Item;
-
-/// <summary>
-/// Handles whether this item is sharp when toggled on.
-/// </summary>
-[RegisterComponent]
-public sealed partial class ItemToggleSharpComponent : Component
-{
-}
+++ /dev/null
-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;
- }
-}
/// <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
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;
[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!;
/// <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)
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);
// 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));
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;
[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!;
SubscribeLocalEvent<HealthAnalyzerComponent, AfterInteractEvent>(OnAfterInteract);
SubscribeLocalEvent<HealthAnalyzerComponent, HealthAnalyzerDoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<HealthAnalyzerComponent, EntGotInsertedIntoContainerMessage>(OnInsertedIntoContainer);
- SubscribeLocalEvent<HealthAnalyzerComponent, PowerCellSlotEmptyEvent>(OnPowerCellSlotEmpty);
+ SubscribeLocalEvent<HealthAnalyzerComponent, ItemToggledEvent>(OnToggled);
SubscribeLocalEvent<HealthAnalyzerComponent, DroppedEvent>(OnDropped);
}
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>
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)
//Link the health analyzer to the scanned entity
healthAnalyzer.Comp.ScannedEntity = target;
- _cell.SetPowerCellDrawEnabled(healthAnalyzer, true);
+ _toggle.TryActivate(healthAnalyzer.Owner);
UpdateScannedUser(healthAnalyzer, target, true);
}
//Unlink the analyzer
healthAnalyzer.Comp.ScannedEntity = null;
- _cell.SetPowerCellDrawEnabled(target, false);
+ _toggle.TryDeactivate(healthAnalyzer.Owner);
UpdateScannedUser(healthAnalyzer, target, false);
}
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);
/// 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;
_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;
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);
--- /dev/null
+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);
+ }
+}
-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;
/// </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)
+ // can't use abilities if suit is not equipped, this is checked elsewhere but just making sure to satisfy nullability
+ if (user.Comp.Suit is not {} suit)
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);
+ if (!_mind.TryGetMind(user, out var mindId, out var mind))
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)
+ foreach (var ability in ent.Comp.Abilities)
{
- EnableGloves(uid, comp, user, ninja);
+ // 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);
}
- 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)
- return;
-
- comp.User = user;
- Dirty(uid, comp);
- _ninja.AssignGloves(user, uid, ninja);
-
- var drainer = EnsureComp<BatteryDrainerComponent>(user);
- var stun = EnsureComp<StunProviderComponent>(user);
- _stunProvider.SetNoPowerPopup(user, "ninja-no-power", stun);
+ // 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);
- }
}
}
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;
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);
}
// 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);
}
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);
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)
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))
{
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);
}
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;
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;
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>
[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);
var query = EntityQueryEnumerator<SpaceNinjaComponent>();
while (query.MoveNext(out var uid, out var ninja))
{
- UpdateNinja(uid, ninja, frameTime);
+ SetSuitPowerAlert((uid, ninja));
}
}
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);
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>
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;
/// <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!;
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;
{
[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()
{
/// <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
_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);
}
}
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>
using Content.Server.Power.Components;
+using Content.Shared.Item.ItemToggle.Components;
using Content.Shared.PowerCell;
using Content.Shared.PowerCell.Components;
* 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;
if (_battery.TryUseCharge(batteryEnt.Value, comp.DrawRate, battery))
continue;
- comp.Drawing = false;
+ Toggle.TryDeactivate((uid, toggle));
+
var ev = new PowerCellSlotEmptyEvent();
RaiseLocalEvent(uid, ref ev);
}
private void OnDrawChargeChanged(EntityUid uid, PowerCellDrawComponent component, ref ChargeChangedEvent args)
{
// Update the bools for client prediction.
- bool canDraw;
- bool canUse;
-
- if (component.UseRate > 0f)
- {
- canUse = args.Charge > component.UseRate;
- }
- else
- {
- canUse = true;
- }
+ var canUse = component.UseRate <= 0f || args.Charge > component.UseRate;
- 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)
{
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;
SubscribeLocalEvent<PowerCellDrawComponent, ChargeChangedEvent>(OnDrawChargeChanged);
SubscribeLocalEvent<PowerCellDrawComponent, PowerCellChangedEvent>(OnDrawCellChanged);
- // funny
SubscribeLocalEvent<PowerCellSlotComponent, ExaminedEvent>(OnCellSlotExamined);
+ // funny
SubscribeLocalEvent<PowerCellSlotComponent, BeingMicrowavedEvent>(OnSlotMicrowaved);
}
if (!TryComp<BorgChassisComponent>(chassis, out var chassisComp) ||
args.Container != chassisComp.ModuleContainer ||
- !chassisComp.Activated)
+ !Toggle.IsActivated(chassis))
return;
if (!_powerCell.HasDrawCharge(uid))
var ev = new BorgModuleSelectedEvent(chassis);
RaiseLocalEvent(moduleUid, ref ev);
chassisComp.SelectedModule = moduleUid;
+ Dirty(chassis, chassisComp);
}
/// <summary>
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)
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;
SubscribeLocalEvent<BorgChassisComponent, PowerCellChangedEvent>(OnPowerCellChanged);
SubscribeLocalEvent<BorgChassisComponent, PowerCellSlotEmptyEvent>(OnPowerCellSlotEmpty);
SubscribeLocalEvent<BorgChassisComponent, GetCharactedDeadIcEvent>(OnGetDeadIC);
+ SubscribeLocalEvent<BorgChassisComponent, ItemToggledEvent>(OnToggled);
SubscribeLocalEvent<BorgBrainComponent, MindAddedMessage>(OnBrainMindAdded);
SubscribeLocalEvent<BorgBrainComponent, PointAttemptEvent>(OnBrainPointAttempt);
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);
}
}
{
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);
private void OnPowerCellSlotEmpty(EntityUid uid, BorgChassisComponent component, ref PowerCellSlotEmptyEvent args)
{
- DisableBorgAbilities(uid, component);
+ Toggle.TryDeactivate(uid);
UpdateUI(uid, component);
}
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))
_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>
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>
+++ /dev/null
-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;
-}
--- /dev/null
+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;
+}
+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;
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);
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);
}
}
[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()
{
using Content.Server.PowerCell;
+using Content.Shared.Item.ItemToggle;
using Content.Shared.PowerCell;
using Content.Shared.Weapons.Misc;
using Robust.Shared.Physics.Components;
public sealed class TetherGunSystem : SharedTetherGunSystem
{
[Dependency] private readonly PowerCellSystem _cell = default!;
+ [Dependency] private readonly ItemToggleSystem _toggle = default!;
public override void Initialize()
{
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);
}
}
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;
_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>();
--- /dev/null
+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;
--- /dev/null
+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);
+ }
+}
/// 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>
/// 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.
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;
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;
- RunUpdate_Internal(uid, beeper);
+ 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);
if (!Resolve(owner, ref comp))
return;
comp.IsMuted = isMuted;
+ Dirty(owner, comp);
}
private void UpdateBeepInterval(EntityUid owner, BeeperComponent beeper)
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;
}
}
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;
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);
}
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);
- }
-
- 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);
+ _beeper.SetIntervalScaling(owner, args.Distance / args.Detector.Range, beeper);
+ _beeper.SetMute(owner, false, beeper);
}
- 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)
+ private void OnNewProximityTarget(EntityUid owner, ProximityBeeperComponent proxBeeper, ref NewProximityTargetEvent args)
{
- 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);
}
}
public abstract class SharedChargesSystem : EntitySystem
{
+ protected EntityQuery<LimitedChargesComponent> Query;
+
public override void Initialize()
{
base.Initialize();
+ Query = GetEntityQuery<LimitedChargesComponent>();
+
SubscribeLocalEvent<LimitedChargesComponent, ExaminedEvent>(OnExamine);
}
/// <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;
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;
/// <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)
+ {
+ AddCharges(uid, -1, comp);
+ }
+
+ /// <summary>
+ /// Checks IsEmpty and uses a charge if it isn't empty.
+ /// </summary>
+ public bool TryUseCharge(Entity<LimitedChargesComponent?> ent)
{
- if (Resolve(uid, ref comp, false))
- AddCharges(uid, -1, comp);
+ if (!Query.Resolve(ent, ref ent.Comp, false))
+ return true;
+
+ if (IsEmpty(ent, ent.Comp))
+ return false;
+
+ UseCharge(ent, ent.Comp);
+ return true;
}
/// <summary>
/// </summary>
public virtual void UseCharges(EntityUid uid, int chargesUsed, LimitedChargesComponent? comp = null)
{
- if (Resolve(uid, ref comp, false))
- AddCharges(uid, -chargesUsed, comp);
+ AddCharges(uid, -chargesUsed, comp);
}
}
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]
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;
}
}
-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;
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()
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)
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.
private void OnRefreshMoveSpeed(EntityUid uid, ClothingSpeedModifierComponent component, InventoryRelayedEvent<RefreshMovementSpeedModifiersEvent> args)
{
- if (!component.Enabled)
- return;
-
- args.Args.ModifySpeed(component.WalkModifier, component.SprintModifier);
+ if (_toggle.IsActivated(uid))
+ args.Args.ModifySpeed(component.WalkModifier, component.SprintModifier);
}
private void OnClothingVerbExamine(EntityUid uid, ClothingSpeedModifierComponent component, GetVerbsEvent<ExamineVerb> args)
_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)
- {
- _actions.AddAction(uid, ref uid.Comp.ToggleActionEntity, uid.Comp.ToggleAction);
- }
-
- 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)
+ private void OnToggled(Entity<ClothingSpeedModifierComponent> ent, ref ItemToggledEvent args)
{
- if (uid.Comp.Enabled == value)
- return;
-
- TryComp<PowerCellDrawComponent>(uid, out var draw);
- if (value && !_powerCell.HasDrawCharge(uid, draw, user: user))
- return;
+ // make sentient boots slow or fast too
+ _movementSpeed.RefreshMovementSpeedModifiers(ent);
- 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()
+ if (_container.TryGetContainingContainer(ent.Owner, out var container))
{
- 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);
+ // inventory system will automatically hook into the event raised by this and update accordingly
+ _movementSpeed.RefreshMovementSpeedModifiers(container.Owner);
+ }
}
}
+++ /dev/null
-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
-{
-}
--- /dev/null
+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);
+++ /dev/null
-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
-{
-
-}
+++ /dev/null
-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;
- }
-}
--- /dev/null
+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);
+ }
+}
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";
/// </summary>
[DataField]
public bool RequiresGrid = true;
+
+ /// <summary>
+ /// Slot the clothing has to be worn in to work.
+ /// </summary>
+ [DataField]
+ public string Slot = "shoes";
}
--- /dev/null
+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);
+ }
+}
+++ /dev/null
-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;
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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;
+}
/// <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;
}
/// </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
[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>
--- /dev/null
+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;
+}
/// <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 INetManager _netManager = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedPointLightSystem _light = default!;
- [Dependency] private readonly INetManager _netManager = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
public override void Initialize()
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);
UpdateVisuals(ent);
}
- private void OnUseInHand(EntityUid uid, ItemToggleComponent itemToggle, UseInHandEvent args)
+ private void OnMapInit(Entity<ItemToggleComponent> ent, ref MapInitEvent args)
+ {
+ 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)
+ 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)
{
return false;
}
- Activate(uid, itemToggle, predicted, user);
+ Activate((uid, comp), predicted, user);
return true;
}
/// <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)
/// <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;
}
}
}
--- /dev/null
+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)
+ });
+ }
+}
/// <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>
[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");
using Content.Shared.Ninja.Systems;
using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
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");
}
/// 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;
/// <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
/// 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;
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;
/// 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;
}
/// 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;
--- /dev/null
+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;
-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
/// <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();
}
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;
/// 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.
- /// </summary>
- [DataField("disableTime")]
- public TimeSpan DisableTime = TimeSpan.FromSeconds(5);
-
- /// <summary>
- /// Time at which we will be able to use our abilities again
+ /// ID of the use delay to disable all ninja abilities.
/// </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;
/// 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;
/// <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;
/// <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>
[DataField]
public EntProtoId SpiderChargeObjective = "SpiderChargeObjective";
+ /// <summary>
+ /// Alert to show for suit power.
+ /// </summary>
[DataField]
public ProtoId<AlertPrototype> SuitPowerAlert = "SuitPower";
}
+using Content.Shared.Ninja.Systems;
using Robust.Shared.GameStates;
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;
}
using Content.Shared.Whitelist;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
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()
/// <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>
- /// Locale string to popup when there is no power
+ /// ID of the cooldown use delay.
/// </summary>
- [DataField(required: true), ViewVariables(VVAccess.ReadWrite)]
- public string NoPowerPopup = string.Empty;
+ [DataField]
+ public string DelayId = "stun_cooldown";
/// <summary>
- /// Whitelist for what counts as a mob.
+ /// Locale string to popup when there is no power
/// </summary>
- [DataField]
- public EntityWhitelist Whitelist = new()
- {
- Components = new[] {"Stamina"}
- };
+ [DataField(required: true)]
+ public LocId NoPowerPopup = string.Empty;
/// <summary>
- /// When someone can next be stunned.
- /// Essentially a UseDelay unique to this component.
+ /// Whitelist for what counts as a mob.
/// </summary>
- [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
- public TimeSpan NextStun = TimeSpan.Zero;
+ [DataField(required: true)]
+ public EntityWhitelist Whitelist = new();
}
/// </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!;
[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 _))
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
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;
+ }
-/// <summary>
-/// Raised on the item before adding the dash action
-/// </summary>
-public sealed class AddDashActionEvent : CancellableEntityEventArgs
-{
- public EntityUid User;
+ var xform = Transform(user);
+ _transform.SetCoordinates(user, xform, args.Target);
+ _transform.AttachToGridOrMap(user, xform);
+ args.Handled = true;
+ }
- public AddDashActionEvent(EntityUid user)
+ public bool CheckDash(EntityUid uid, EntityUid user)
{
- User = user;
+ var ev = new CheckDashEvent(user);
+ RaiseLocalEvent(uid, ref ev);
+ return !ev.Cancelled;
}
}
/// <summary>
-/// Raised on the item before dashing is done.
+/// Raised on the item before adding the dash action and when using the action.
/// </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);
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;
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()
{
/// <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);
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>
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);
- }
-
- private void OnAddDashAction(EntityUid uid, EnergyKatanaComponent comp, AddDashActionEvent args)
- {
- if (!HasComp<SpaceNinjaComponent>(args.User))
- args.Cancel();
+ _ninja.BindKatana(args.Equipee, ent);
}
- private void OnDashAttempt(EntityUid uid, EnergyKatanaComponent comp, DashAttemptEvent args)
+ private void OnCheckDash(Entity<EnergyKatanaComponent> ent, ref CheckDashEvent args)
{
- if (!TryComp<SpaceNinjaComponent>(args.User, out var ninja) || ninja.Katana != uid)
- args.Cancel();
+ if (!_ninja.IsNinja(args.User))
+ args.Cancelled = true;
}
}
--- /dev/null
+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);
}
/// <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)
+ protected virtual void OnDoAfterAttempt(Entity<BatteryDrainerComponent> ent, ref DoAfterAttemptEvent<DrainDoAfterEvent> args)
{
- if (comp.BatteryUid == null)
- {
+ 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;
}
/// <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);
}
}
/// DoAfter event for <see cref="BatteryDrainerComponent"/>.
/// </summary>
[Serializable, NetSerializable]
-public sealed partial class DrainDoAfterEvent : SimpleDoAfterEvent { }
+public sealed partial class DrainDoAfterEvent : SimpleDoAfterEvent;
-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;
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>
&& !_combatMode.IsInCombatMode(uid)
&& TryComp<HandsComponent>(uid, out var hands)
&& hands.ActiveHandEntity == null
- && Interaction.InRangeUnobstructed(uid, target);
+ && _interaction.InRangeUnobstructed(uid, target);
}
}
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;
/// </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;
}
- /// <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 OnStarCheck(Entity<NinjaSuitComponent> ent, ref CheckItemCreatorEvent args)
+ {
+ if (!_ninja.IsNinja(args.User))
+ args.Cancelled = true;
+ }
+
+ private void OnCreateStarAttempt(Entity<NinjaSuitComponent> ent, ref CreateItemAttemptEvent args)
{
- UserUnequippedSuit(uid, comp, args.Equipee);
+ if (CheckDisabled(ent, args.User))
+ args.Cancelled = true;
}
/// <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.
+ /// Call the shared and serverside code for when anyone unequips a suit.
/// </summary>
- protected virtual void NinjaEquippedSuit(EntityUid uid, NinjaSuitComponent comp, EntityUid user, SpaceNinjaComponent ninja)
+ private void OnUnequipped(Entity<NinjaSuitComponent> ent, ref GotUnequippedEvent args)
{
- // 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);
+ }
+
+ private void OnActivateAttempt(Entity<NinjaSuitComponent> ent, ref ItemToggleActivateAttemptEvent args)
+ {
+ if (!_ninja.IsNinja(args.User))
+ {
+ args.Cancelled = true;
+ return;
+ }
+
+ if (IsDisabled((ent, ent.Comp, null)))
+ {
+ args.Cancelled = true;
+ args.Popup = Loc.GetString("ninja-suit-cooldown");
+ }
}
- // TODO: modify PowerCellDrain
/// <summary>
- /// Returns the power used by a suit
+ /// Returns true if the suit is currently disabled
/// </summary>
- public float SuitWattage(EntityUid uid, NinjaSuitComponent? suit = null)
+ 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 (!Resolve(uid, ref suit))
- return 0f;
+ if (IsDisabled((ent, ent.Comp, null)))
+ {
+ Popup.PopupEntity(Loc.GetString("ninja-suit-cooldown"), user, user, PopupType.Medium);
+ return true;
+ }
- float wattage = suit.PassiveWattage;
- if (TryComp<StealthClothingComponent>(uid, out var stealthClothing) && stealthClothing.Enabled)
- wattage += suit.CloakWattage;
- return wattage;
+ 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);
}
}
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;
[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>
/// <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)
+ {
+ TryRevealNinja(ent, disable: false);
+ }
+
+ private void TryRevealNinja(Entity<SpaceNinjaComponent> ent, bool disable)
{
- if (comp.Suit != null && TryComp<StealthClothingComponent>(comp.Suit, out var stealthClothing) && stealthClothing.Enabled)
- {
- Suit.RevealNinja(comp.Suit.Value, uid, false, null, stealthClothing);
- }
+ 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();
}
}
--- /dev/null
+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;
/// <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);
}
}
}
/// <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>
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>
+++ /dev/null
-using Robust.Shared.Serialization;
-
-namespace Content.Shared.Pinpointer;
-
-[Serializable, NetSerializable]
-public enum ProximityBeeperVisuals : byte
-{
- Enabled
-}
/// <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
{
#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.
[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);
}
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;
[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)
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 (!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(uid, ref component, false) || enabled == component.Drawing)
+ 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>
[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!
[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]
-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;
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!;
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)
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)
- {
- detector.Enabled = enabled;
- var noDetectEvent = new ProximityTargetUpdatedEvent(detector, detector.TargetEnt, detector.Distance);
- RaiseLocalEvent(owner, ref noDetectEvent);
- if (!enabled)
+ if (args.Activated)
{
- 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)
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();
{
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;
var newData = newTarget || detector.Distance != closestDistance;
detector.TargetEnt = closestEnt;
detector.Distance = closestDistance;
+ Dirty(owner, detector);
if (newTarget)
{
var newTargetEvent = new NewProximityTargetEvent(detector, closestEnt);
[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
/// <summary>
/// The currently selected module
/// </summary>
- [DataField("selectedModule")]
+ [DataField("selectedModule"), AutoNetworkedField]
public EntityUid? SelectedModule;
#region Visuals
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;
{
[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/>
private void OnRefreshMovementSpeedModifiers(EntityUid uid, BorgChassisComponent component, RefreshMovementSpeedModifiersEvent args)
{
- if (component.Activated)
+ if (Toggle.IsActivated(uid))
return;
if (!TryComp<MovementSpeedModifierComponent>(uid, out var movement))
namespace Content.Shared.Toggleable;
/// <summary>
-/// Generic action-event for toggle-able components.
+/// 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.
[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!;
+using Content.Shared.Item.ItemToggle;
+using Content.Shared.Item.ItemToggle.Components;
using Content.Shared.PowerCell;
using Robust.Shared.Containers;
public sealed partial class ActivatableUISystem
{
+ [Dependency] private readonly ItemToggleSystem _toggle = default!;
[Dependency] private readonly SharedPowerCellSystem _cell = default!;
private void InitializePower()
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)
+ private void OnToggled(Entity<ActivatableUIRequiresPowerCellComponent> ent, ref ItemToggledEvent args)
{
- _cell.SetPowerCellDrawEnabled(uid, false);
-
- if (!HasComp<ActivatableUIRequiresPowerCellComponent>(uid) ||
- !TryComp(uid, out ActivatableUIComponent? activatable))
- {
+ // 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)
if (!args.UiKey.Equals(activatable.Key))
return;
- _cell.SetPowerCellDrawEnabled(uid, true);
+ _toggle.TryActivate(uid);
}
private void OnBatteryClosed(EntityUid uid, ActivatableUIRequiresPowerCellComponent component, BoundUIClosedEvent args)
// 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>
/// <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>
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;
/// </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!;
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) ||
[NotNullWhen(true)] out Vector2? newDirection)
{
if (!TryComp<ReflectComponent>(reflector, out var reflect) ||
- !reflect.Enabled ||
+ !_toggle.IsActivated(reflector) ||
!_random.Prob(reflect.ReflectProb))
{
newDirection = null;
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>
{
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);
- 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
state: icon
itemIconStyle: NoItem
priority: -10
- event: !type:CreateThrowingStarEvent {}
+ event: !type:CreateItemEvent {}
- type: entity
id: ActionRecallKatana
# have to plan (un)cloaking ahead of time
useDelay: 5
priority: -9
- event: !type:ToggleStealthEvent
+ event: !type:ToggleActionEvent
# katana
- type: entity
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
- 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.
- 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
- 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
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.
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
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
- 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.
components:
- - type: Sprite
- sprite: Clothing/Shoes/Boots/magboots.rsi
- layers:
- - state: icon
- map: [ "enum.ToggleVisuals.Layer" ]
- - type: Clothing
- sprite: Clothing/Shoes/Boots/magboots.rsi
- - type: Magboots
- - type: ClothingSpeedModifier
- walkModifier: 0.85
- sprintModifier: 0.8
- enabled: false
- - type: Appearance
- - type: GenericVisualizer
- visuals:
- enum.ToggleVisuals.Toggled:
- enum.ToggleVisuals.Layer:
- True: {state: icon-on}
- False: {state: icon}
- - type: StaticPrice
- price: 200
- - type: Tag
- tags:
- - WhitelistChameleon
+ - type: Sprite
+ sprite: Clothing/Shoes/Boots/magboots.rsi
+ layers:
+ - state: icon
+ 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
+ - type: Appearance
+ - type: GenericVisualizer
+ visuals:
+ enum.ToggleVisuals.Toggled:
+ enum.ToggleVisuals.Layer:
+ True: {state: icon-on}
+ False: {state: icon}
+ - type: StaticPrice
+ price: 200
+ - type: Tag
+ tags:
+ - WhitelistChameleon
- type: entity
parent: ClothingShoesBootsMag
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
sprite: Clothing/Shoes/Boots/magboots-science.rsi
- type: Clothing
sprite: Clothing/Shoes/Boots/magboots-science.rsi
- - type: Magboots
- toggleAction: ActionToggleMagbootsSci
- type: entity
parent: ClothingShoesBootsMag
- 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
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:
volume: 0.75
temperature: 293.15
moles:
- - 0.153853429 # oxygen
- - 0.153853429 # nitrogen
+ - 0.153853429 # oxygen
+ - 0.153853429 # nitrogen
- type: Item
sprite: null
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
- 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!
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:
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
- type: GroupExamine
- type: Armor
modifiers: {}
+
+# for clothing that can be toggled, like magboots
+- type: entity
+ abstract: true
+ id: BaseToggleClothing
+ components:
+ - type: ItemToggle
+ onUse: false # can't really wear it like that
+ - type: ToggleClothing
state: alive
- type: entity
+ noSpawn: true
+ parent: BaseAntagSpawner
id: SpawnPointGhostSpaceNinja
- name: ghost role spawn point
- suffix: space ninja
- parent: MarkerBase
components:
- type: GhostRole
name: ghost-role-information-space-ninja-name
rules: ghost-role-information-antagonist-rules
raffle:
settings: default
- - type: GhostRoleMobSpawner
- prototype: MobHumanSpaceNinja
- type: Sprite
sprite: Markers/jobs.rsi
layers:
- type: PowerCellSlot
cellSlotId: cell_slot
fitsInCharger: true
+ - type: ItemToggle
+ onUse: false # no item-borg toggling sorry
+ - type: AccessToggle
+ # TODO: refactor movement to just be based on toggle like speedboots but for the boots themselves
+ # TODO: or just have sentient speedboots be fast idk
- type: PowerCellDraw
drawRate: 0.6
- type: ItemSlots
- type: NpcFactionMember
factions:
- Syndicate
-
-# Space Ninja
-- type: entity
- noSpawn: true
- name: Space Ninja
- parent: MobHuman
- id: MobHumanSpaceNinja
- components:
- - type: RandomHumanoidAppearance
- randomizeName: false
- - type: Loadout
- prototypes: [SpaceNinjaGear]
- - type: NpcFactionMember
- factions:
- - Syndicate
- - type: SpaceNinja
- - type: GenericAntag
- rule: Ninja
- - type: AutoImplant
- implants:
- - DeathAcidifierImplant
- - type: RandomMetadata
- nameSegments:
- - names_ninja_title
- - names_ninja
- Pacified
- StaminaModifier
- Flashed
- - type: Reflect
- enabled: false
- reflectProb: 0
- type: Body
prototype: Human
requiredLegs: 2
--- /dev/null
+- type: entity
+ abstract: true
+ parent: [BaseItem, PowerCellSlotSmallItem]
+ id: BaseHandheldComputer
+ components:
+ - type: ActivatableUIRequiresPowerCell
+ - type: ItemToggle
+ onUse: false # above component does the toggling
+ - type: PowerCellDraw
+ drawRate: 0
+ useRate: 20
id: BaseMedicalPDA
abstract: true
components:
+ - type: ItemToggle
+ toggleLight: false
+ onUse: false
- type: HealthAnalyzer
scanDelay: 1
scanningEndSound:
- type: entity
+ parent: BaseItem
id: BaseHandheldStationMap
name: station map
description: Displays a readout of the current station.
abstract: true
- parent: BaseItem
components:
- type: StationMap
- type: Sprite
- type: entity
id: HandheldStationMap
parent:
- - BaseHandheldStationMap
- - PowerCellSlotSmallItem
+ - BaseHandheldStationMap
+ - BaseHandheldComputer
suffix: Handheld, Powered
- components:
- - type: PowerCellDraw
- drawRate: 0
- useRate: 20
- - type: ActivatableUIRequiresPowerCell
- type: entity
id: HandheldStationMapEmpty
path: /Audio/Weapons/ebladehum.ogg
- type: ItemToggleSize
activatedSize: Huge
- - type: ItemToggleDisarmMalus
- activatedDisarmMalus: 0.6
+ - type: ComponentToggler
+ components:
+ - type: DisarmMalus
+ malus: 0.6
- type: Sprite
sprite: Objects/Weapons/Melee/e_shield.rsi
layers:
energy: 2
color: blue
- type: Reflect
- enabled: false
reflectProb: 0.95
reflects:
- Energy
path: /Audio/Weapons/telescopicoff.ogg
params:
volume: -5
- - type: ItemToggleDisarmMalus
- activatedDisarmMalus: 0.6
+ - type: ComponentToggler
+ components:
+ - type: DisarmMalus
+ malus: 0.6
- type: ItemToggleSize
activatedSize: Huge
- type: Sprite
size: Large
- type: Speech
speechVerb: Robotic
+ - type: ItemToggle
+ soundActivate:
+ path: /Audio/Items/Defib/defib_safety_on.ogg
+ soundDeactivate:
+ path: /Audio/Items/Defib/defib_safety_off.ogg
- type: Defibrillator
zapHeal:
types:
- type: entity
name: handheld crew monitor
suffix: DO NOT MAP
- parent:
- - BaseItem
- - PowerCellSlotSmallItem
+ parent: BaseHandheldComputer
# CMO-only bud, don't add more.
id: HandheldCrewMonitor
description: A hand-held crew monitor displaying the status of suit sensors.
- type: Sprite
sprite: Objects/Specific/Medical/handheldcrewmonitor.rsi
state: scanner
- - type: PowerCellDraw
- drawRate: 0
- useRate: 20
- - type: ActivatableUIRequiresPowerCell
- type: ActivatableUI
key: enum.CrewMonitoringUIKey.Key
- type: UserInterface
interfaces:
enum.HealthAnalyzerUiKey.Key:
type: HealthAnalyzerBoundUserInterface
+ - type: ItemToggle
+ onUse: false
- type: HealthAnalyzer
scanningEndSound:
path: "/Audio/Items/Medical/healthscanner.ogg"
- state: screen
shader: unshaded
visible: false
- map: ["enum.PowerDeviceVisualLayers.Powered"]
+ map: ["enum.ToggleVisuals.Layer"]
- type: Appearance
- type: GenericVisualizer
visuals:
- enum.ProximityBeeperVisuals.Enabled:
- enum.PowerDeviceVisualLayers.Powered:
+ enum.ToggleVisuals.Toggled:
+ enum.ToggleVisuals.Layer:
True: { visible: true }
False: { visible: false }
+ - type: ItemToggle
- type: ProximityBeeper
- type: ProximityDetector
- enabled: false
range: 20
criteria:
components:
- Anomaly
- type: Beeper
- enabled: false
minBeepInterval: 0.15
maxBeepInterval: 1.00
beepSound:
- type: entity
name: handheld mass scanner
- parent: [ BaseItem, PowerCellSlotSmallItem]
+ parent: BaseHandheldComputer
id: HandHeldMassScanner
description: A hand-held mass scanner.
components:
- type: PowerCellDraw
drawRate: 3
useRate: 100
- - type: ActivatableUIRequiresPowerCell
- type: ActivatableUI
key: enum.RadarConsoleUiKey.Key
inHandsOnly: true
- type: ItemToggleSize
activatedSize: Large
- type: ItemToggleHot
- - type: ItemToggleDisarmMalus
- activatedDisarmMalus: 0.6
+ - type: ComponentToggler
+ components:
+ - type: DisarmMalus
+ malus: 0.6
- type: ToggleableLightVisuals
spriteLayer: flame
inhandVisuals:
- type: TetherGun
frequency: 5
dampingRatio: 4
+ - type: ItemToggle
+ onUse: false
- type: PowerCellDraw
- type: Sprite
sprite: Objects/Weapons/Guns/Launchers/tether_gun.rsi
path: /Audio/Weapons/soup.ogg
params:
volume: 2
+ - type: ItemToggle
+ onUse: false
- type: PowerCellDraw
- type: Sprite
sprite: Objects/Weapons/Guns/Launchers/force_gun.rsi
- type: ItemToggleActiveSound
activeSound:
path: /Audio/Weapons/ebladehum.ogg
- - type: ItemToggleSharp
+ - type: ComponentToggler
+ components:
+ - type: Sharp
+ - type: DisarmMalus
+ malus: 0.6
- type: ItemToggleHot
- - type: ItemToggleDisarmMalus
- activatedDisarmMalus: 0.6
- type: ItemToggleSize
activatedSize: Huge
- type: ItemToggleMeleeWeapon
right:
- state: inhand-right-blade
shader: unshaded
- - type: DisarmMalus
- malus: 0
- type: Reflect
- enabled: false
- type: IgnitionSource
temperature: 700
suffix: E-Dagger
description: 'A dark ink pen.'
components:
- - type: EnergySword
- type: ItemToggle
soundActivate:
path: /Audio/Weapons/ebladeon.ogg
path: /Audio/Weapons/ebladehum.ogg
params:
volume: -6
- - type: ItemToggleDisarmMalus
- activatedDisarmMalus: 0.4
+ - type: ComponentToggler
+ components:
+ - type: Sharp
+ - type: DisarmMalus
+ malus: 0.4
- type: Sprite
sprite: Objects/Weapons/Melee/e_dagger.rsi
layers:
id: EnergyCutlass
description: An exotic energy weapon.
components:
- - type: EnergySword
- type: ItemToggleMeleeWeapon
activatedDamage:
types:
Slash: 10
Heat: 12
deactivatedSecret: true
- - type: ItemToggleDisarmMalus
- activatedDisarmMalus: 0.6
- type: Sprite
sprite: Objects/Weapons/Melee/e_cutlass.rsi
layers:
id: EnergySwordDouble
description: Syndicate Command Interns thought that having one blade on the energy sword was not enough. This can be stored in pockets.
components:
- - type: EnergySword
- type: ItemToggle
+ onUse: false # wielding events control it instead
soundActivate:
path: /Audio/Weapons/ebladeon.ogg
params:
path: /Audio/Weapons/ebladehum.ogg
params:
volume: 3
- - type: ItemToggleDisarmMalus
- activatedDisarmMalus: 0.7
+ - type: ComponentToggler
+ components:
+ - type: Sharp
+ - type: DisarmMalus
+ malus: 0.7
- type: Wieldable
+ wieldSound: null # esword light sound instead
- type: MeleeWeapon
wideAnimationRotation: -135
attackRate: 1.5
soundHit:
path: /Audio/Weapons/bladeslice.ogg
- type: Reflect
- enabled: true
reflectProb: .1
spread: 90
- type: Item
soundHit:
path: /Audio/Effects/explosion_small1.ogg
- type: Reflect
- enabled: true
reflectProb: .25
spread: 90
- type: Item
whitelist:
components:
- FitsInDispenser
+ - type: ItemToggle
- type: HealthAnalyzer
scanDelay: 0
- type: UserInterface
earliestStart: 30
reoccurrenceDelay: 20
minimumPlayers: 30
- - type: NinjaSpawnRule
+ - type: SpaceSpawnRule
+ - type: AntagLoadProfileRule
+ - type: AntagObjectives
+ objectives:
+ - StealResearchObjective
+ - DoorjackObjective
+ - SpiderChargeObjective
+ - TerrorObjective
+ - MassArrestObjective
+ - NinjaSurviveObjective
+ - type: AntagSelection
+ agentName: ninja-round-end-agent-name
+ definitions:
+ - spawnerPrototype: SpawnPointGhostSpaceNinja
+ min: 1
+ max: 1
+ pickPlayer: false
+ startingGear: SpaceNinjaGear
+ briefing:
+ text: ninja-role-greeting
+ color: Green
+ sound: /Audio/Misc/ninja_greeting.ogg
+ components:
+ - type: SpaceNinja
+ - type: NpcFactionMember
+ factions:
+ - Syndicate
+ - type: AutoImplant
+ implants:
+ - DeathAcidifierImplant
+ - type: RandomMetadata
+ nameSegments:
+ - names_ninja_title
+ - names_ninja
+ mindComponents:
+ - type: NinjaRole
+ prototype: SpaceNinja
- type: entity
parent: BaseGameRule
-# doesnt spawn a ninja or anything, just stores configuration for it
-# see NinjaSpawn event for spawning
-- type: entity
- id: Ninja
- parent: BaseGameRule
- components:
- - type: GenericAntagRule
- agentName: ninja-round-end-agent-name
- objectives:
- - StealResearchObjective
- - DoorjackObjective
- - SpiderChargeObjective
- - TerrorObjective
- - MassArrestObjective
- - NinjaSurviveObjective
- - type: NinjaRule
- threats: NinjaThreats
-
- type: entity
parent: BaseGameRule
id: Thief