From f5ea561bea76a95e3c5657849197c11ed7e9fb1d Mon Sep 17 00:00:00 2001
From: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
Date: Thu, 18 Jul 2024 02:41:15 +0200
Subject: [PATCH 001/366] minor SharedInteractionSystem cleanup (#30139)
cleanup SharedInteractionSystem
---
Content.Shared/Interaction/SharedInteractionSystem.cs | 10 ++++------
1 file changed, 4 insertions(+), 6 deletions(-)
diff --git a/Content.Shared/Interaction/SharedInteractionSystem.cs b/Content.Shared/Interaction/SharedInteractionSystem.cs
index cc548fd4b16..b54d2a41fe1 100644
--- a/Content.Shared/Interaction/SharedInteractionSystem.cs
+++ b/Content.Shared/Interaction/SharedInteractionSystem.cs
@@ -42,8 +42,6 @@
using Robust.Shared.Timing;
using Robust.Shared.Utility;
-#pragma warning disable 618
-
namespace Content.Shared.Interaction
{
///
@@ -556,11 +554,11 @@ public void InteractUsingRanged(EntityUid user, EntityUid used, EntityUid? targe
protected bool ValidateInteractAndFace(EntityUid user, EntityCoordinates coordinates)
{
// Verify user is on the same map as the entity they clicked on
- if (coordinates.GetMapId(EntityManager) != Transform(user).MapID)
+ if (_transform.GetMapId(coordinates) != Transform(user).MapID)
return false;
if (!HasComp(user))
- _rotateToFaceSystem.TryFaceCoordinates(user, coordinates.ToMapPos(EntityManager, _transform));
+ _rotateToFaceSystem.TryFaceCoordinates(user, _transform.ToMapCoordinates(coordinates).Position);
return true;
}
@@ -893,7 +891,7 @@ public bool InRangeUnobstructed(
Ignored? predicate = null,
bool popup = false)
{
- return InRangeUnobstructed(origin, other.ToMap(EntityManager, _transform), range, collisionMask, predicate, popup);
+ return InRangeUnobstructed(origin, _transform.ToMapCoordinates(other), range, collisionMask, predicate, popup);
}
///
@@ -1009,7 +1007,7 @@ public void InteractDoAfter(
bool checkDeletion = false
)
{
- if (target is {Valid: false})
+ if (target is { Valid: false })
target = null;
if (checkDeletion && (IsDeleted(user) || IsDeleted(used) || IsDeleted(target)))
From 803671acdabf3f3617a1563bf30faae0a2138190 Mon Sep 17 00:00:00 2001
From: themias <89101928+themias@users.noreply.github.com>
Date: Tue, 30 Jul 2024 04:35:30 -0400
Subject: [PATCH 002/366] Fix fingerprint transfer on weapon attack (#30257)
* Fix fingerprint transfer on weapon attack
* Switch to just not raising the event
* one more
---
Content.Shared/Interaction/SharedInteractionSystem.cs | 6 +++++-
Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs | 4 ++--
2 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/Content.Shared/Interaction/SharedInteractionSystem.cs b/Content.Shared/Interaction/SharedInteractionSystem.cs
index b54d2a41fe1..ab4e6c5b8cf 100644
--- a/Content.Shared/Interaction/SharedInteractionSystem.cs
+++ b/Content.Shared/Interaction/SharedInteractionSystem.cs
@@ -988,7 +988,7 @@ public void InteractUsing(
RaiseLocalEvent(target, interactUsingEvent, true);
DoContactInteraction(user, used, interactUsingEvent);
DoContactInteraction(user, target, interactUsingEvent);
-
+ // Contact interactions are currently only used for forensics, so we don't raise used -> target
if (interactUsingEvent.Handled)
return;
@@ -1018,6 +1018,8 @@ public void InteractDoAfter(
DoContactInteraction(user, used, afterInteractEvent);
if (canReach)
DoContactInteraction(user, target, afterInteractEvent);
+ // Contact interactions are currently only used for forensics, so we don't raise used -> target
+ }
if (afterInteractEvent.Handled)
return;
@@ -1031,6 +1033,8 @@ public void InteractDoAfter(
DoContactInteraction(user, used, afterInteractUsingEvent);
if (canReach)
DoContactInteraction(user, target, afterInteractUsingEvent);
+ // Contact interactions are currently only used for forensics, so we don't raise used -> target
+ }
}
#region ActivateItemInWorld
diff --git a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs
index aa15ecfb286..eefd6bb0535 100644
--- a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs
+++ b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs
@@ -468,7 +468,7 @@ protected virtual void DoLightAttack(EntityUid user, LightAttackEvent ev, Entity
var weapon = GetEntity(ev.Weapon);
- Interaction.DoContactInteraction(weapon, target);
+ // We skip weapon -> target interaction, as forensics system applies DNA on hit
Interaction.DoContactInteraction(user, weapon);
// If the user is using a long-range weapon, this probably shouldn't be happening? But I'll interpret melee as a
@@ -608,7 +608,7 @@ private bool DoHeavyAttack(EntityUid user, HeavyAttackEvent ev, EntityUid meleeU
// For stuff that cares about it being attacked.
foreach (var target in targets)
{
- Interaction.DoContactInteraction(weapon, target);
+ // We skip weapon -> target interaction, as forensics system applies DNA on hit
// If the user is using a long-range weapon, this probably shouldn't be happening? But I'll interpret melee as a
// somewhat messy scuffle. See also, light attacks.
From 56aee075b3363dca08bcb5369b9d76f920fcde93 Mon Sep 17 00:00:00 2001
From: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
Date: Fri, 2 Aug 2024 07:44:59 +0200
Subject: [PATCH 003/366] Fix borgs being unable to state laws or open other
UIs without modules (#30299)
borg law 2 state laws all day
---
Content.Shared/Interaction/SharedInteractionSystem.cs | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/Content.Shared/Interaction/SharedInteractionSystem.cs b/Content.Shared/Interaction/SharedInteractionSystem.cs
index ab4e6c5b8cf..32d69dc8e73 100644
--- a/Content.Shared/Interaction/SharedInteractionSystem.cs
+++ b/Content.Shared/Interaction/SharedInteractionSystem.cs
@@ -189,10 +189,7 @@ private void OnBoundInterfaceInteractAttempt(BoundUserInterfaceMessageAttempt ev
return;
}
- if (!uiComp.RequireHands)
- return;
-
- if (!_handsQuery.TryComp(ev.Actor, out var hands) || hands.Hands.Count == 0)
+ if (uiComp.RequireHands && !_handsQuery.HasComp(ev.Actor))
ev.Cancel();
}
From 8e4df8777323d3809c891a91b74fcb84273d36d8 Mon Sep 17 00:00:00 2001
From: deltanedas <39013340+deltanedas@users.noreply.github.com>
Date: Sat, 25 May 2024 20:14:48 +0000
Subject: [PATCH 004/366] antag objective issuing refactor (#28216)
---
Content.Server/Antag/AntagObjectivesSystem.cs | 35 +++++++++++++
.../Antag/AntagRandomObjectivesSystem.cs | 52 +++++++++++++++++++
.../Antag/AntagSelectionSystem.API.cs | 2 +-
Content.Server/Antag/AntagSelectionSystem.cs | 12 +++++
.../Components/AntagObjectivesComponent.cs | 18 +++++++
.../AntagRandomObjectivesComponent.cs | 52 +++++++++++++++++++
.../Components/AntagSelectionComponent.cs | 7 +++
.../Rules/Components/ThiefRuleComponent.cs | 21 +-------
.../Rules/Components/TraitorRuleComponent.cs | 6 ---
.../Rules/GenericAntagRuleSystem.cs | 5 +-
.../GameTicking/Rules/ThiefRuleSystem.cs | 39 --------------
.../GameTicking/Rules/TraitorRuleSystem.cs | 30 ++---------
Content.Server/Objectives/ObjectivesSystem.cs | 51 ++++++------------
.../Locale/en-US/objectives/round-end.ftl | 1 -
Resources/Prototypes/GameRules/events.yml | 5 +-
Resources/Prototypes/GameRules/midround.yml | 12 +++++
Resources/Prototypes/GameRules/roundstart.yml | 18 ++++++-
.../Prototypes/Objectives/objectiveGroups.yml | 5 --
18 files changed, 231 insertions(+), 140 deletions(-)
create mode 100644 Content.Server/Antag/AntagObjectivesSystem.cs
create mode 100644 Content.Server/Antag/AntagRandomObjectivesSystem.cs
create mode 100644 Content.Server/Antag/Components/AntagObjectivesComponent.cs
create mode 100644 Content.Server/Antag/Components/AntagRandomObjectivesComponent.cs
diff --git a/Content.Server/Antag/AntagObjectivesSystem.cs b/Content.Server/Antag/AntagObjectivesSystem.cs
new file mode 100644
index 00000000000..5aa31f66f67
--- /dev/null
+++ b/Content.Server/Antag/AntagObjectivesSystem.cs
@@ -0,0 +1,35 @@
+using Content.Server.Antag.Components;
+using Content.Server.Objectives;
+using Content.Shared.Mind;
+using Content.Shared.Objectives.Systems;
+
+namespace Content.Server.Antag;
+
+///
+/// Adds fixed objectives to an antag made with AntagObjectivesComponent.
+///
+public sealed class AntagObjectivesSystem : EntitySystem
+{
+ [Dependency] private readonly SharedMindSystem _mind = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnAntagSelected);
+ }
+
+ private void OnAntagSelected(Entity ent, ref AfterAntagEntitySelectedEvent args)
+ {
+ if (!_mind.TryGetMind(args.Session, out var mindId, out var mind))
+ {
+ Log.Error($"Antag {ToPrettyString(args.EntityUid):player} was selected by {ToPrettyString(ent):rule} but had no mind attached!");
+ return;
+ }
+
+ foreach (var id in ent.Comp.Objectives)
+ {
+ _mind.TryAddObjective(mindId, mind, id);
+ }
+ }
+}
diff --git a/Content.Server/Antag/AntagRandomObjectivesSystem.cs b/Content.Server/Antag/AntagRandomObjectivesSystem.cs
new file mode 100644
index 00000000000..c935b8c0648
--- /dev/null
+++ b/Content.Server/Antag/AntagRandomObjectivesSystem.cs
@@ -0,0 +1,52 @@
+using Content.Server.Antag.Components;
+using Content.Server.Objectives;
+using Content.Shared.Mind;
+using Content.Shared.Objectives.Components;
+using Content.Shared.Objectives.Systems;
+using Robust.Shared.Random;
+
+namespace Content.Server.Antag;
+
+///
+/// Adds fixed objectives to an antag made with AntagRandomObjectivesComponent.
+///
+public sealed class AntagRandomObjectivesSystem : EntitySystem
+{
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly SharedMindSystem _mind = default!;
+ [Dependency] private readonly ObjectivesSystem _objectives = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnAntagSelected);
+ }
+
+ private void OnAntagSelected(Entity ent, ref AfterAntagEntitySelectedEvent args)
+ {
+ if (!_mind.TryGetMind(args.Session, out var mindId, out var mind))
+ {
+ Log.Error($"Antag {ToPrettyString(args.EntityUid):player} was selected by {ToPrettyString(ent):rule} but had no mind attached!");
+ return;
+ }
+
+ var difficulty = 0f;
+ foreach (var set in ent.Comp.Sets)
+ {
+ if (!_random.Prob(set.Prob))
+ continue;
+
+ for (var pick = 0; pick < set.MaxPicks && ent.Comp.MaxDifficulty > difficulty; pick++)
+ {
+ if (_objectives.GetRandomObjective(mindId, mind, set.Groups) is not {} objective)
+ continue;
+
+ _mind.AddObjective(mindId, mind, objective);
+ var adding = Comp(objective).Difficulty;
+ difficulty += adding;
+ Log.Debug($"Added objective {ToPrettyString(objective):objective} to {ToPrettyString(args.EntityUid):player} with {adding} difficulty");
+ }
+ }
+ }
+}
diff --git a/Content.Server/Antag/AntagSelectionSystem.API.cs b/Content.Server/Antag/AntagSelectionSystem.API.cs
index 77f543cdcf1..8893d9a476d 100644
--- a/Content.Server/Antag/AntagSelectionSystem.API.cs
+++ b/Content.Server/Antag/AntagSelectionSystem.API.cs
@@ -147,7 +147,7 @@ public List> GetAntagMinds(Entity
- /// Helper specifically for
+ /// Helper to get just the mind entities and not names.
///
public List GetAntagMindEntityUids(Entity ent)
{
diff --git a/Content.Server/Antag/AntagSelectionSystem.cs b/Content.Server/Antag/AntagSelectionSystem.cs
index 8efdc2738b9..b23afe23103 100644
--- a/Content.Server/Antag/AntagSelectionSystem.cs
+++ b/Content.Server/Antag/AntagSelectionSystem.cs
@@ -6,6 +6,7 @@
using Content.Server.Ghost.Roles;
using Content.Server.Ghost.Roles.Components;
using Content.Server.Mind;
+using Content.Server.Objectives;
using Content.Server.Preferences.Managers;
using Content.Server.Roles;
using Content.Server.Roles.Jobs;
@@ -51,6 +52,8 @@ public override void Initialize()
SubscribeLocalEvent(OnTakeGhostRole);
+ SubscribeLocalEvent(OnObjectivesTextGetInfo);
+
SubscribeLocalEvent(OnPlayerSpawning);
SubscribeLocalEvent(OnJobsAssigned);
SubscribeLocalEvent(OnSpawnComplete);
@@ -416,6 +419,15 @@ public bool IsEntityValid(EntityUid? entity, AntagSelectionDefinition def)
return true;
}
+
+ private void OnObjectivesTextGetInfo(Entity ent, ref ObjectivesTextGetInfoEvent args)
+ {
+ if (ent.Comp.AgentName is not {} name)
+ return;
+
+ args.Minds = ent.Comp.SelectedMinds;
+ args.AgentName = Loc.GetString(name);
+ }
}
///
diff --git a/Content.Server/Antag/Components/AntagObjectivesComponent.cs b/Content.Server/Antag/Components/AntagObjectivesComponent.cs
new file mode 100644
index 00000000000..357c138f46b
--- /dev/null
+++ b/Content.Server/Antag/Components/AntagObjectivesComponent.cs
@@ -0,0 +1,18 @@
+using Content.Server.Antag;
+using Content.Shared.Objectives.Components;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.Antag.Components;
+
+///
+/// Gives antags selected by this rule a fixed list of objectives.
+///
+[RegisterComponent, Access(typeof(AntagObjectivesSystem))]
+public sealed partial class AntagObjectivesComponent : Component
+{
+ ///
+ /// List of static objectives to give.
+ ///
+ [DataField(required: true)]
+ public List> Objectives = new();
+}
diff --git a/Content.Server/Antag/Components/AntagRandomObjectivesComponent.cs b/Content.Server/Antag/Components/AntagRandomObjectivesComponent.cs
new file mode 100644
index 00000000000..9a551acc499
--- /dev/null
+++ b/Content.Server/Antag/Components/AntagRandomObjectivesComponent.cs
@@ -0,0 +1,52 @@
+using Content.Server.Antag;
+using Content.Shared.Random;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.Antag.Components;
+
+///
+/// Gives antags selected by this rule a random list of objectives.
+///
+[RegisterComponent, Access(typeof(AntagRandomObjectivesSystem))]
+public sealed partial class AntagRandomObjectivesComponent : Component
+{
+ ///
+ /// Each set of objectives to add.
+ ///
+ [DataField(required: true)]
+ public List Sets = new();
+
+ ///
+ /// If the total difficulty of the currently given objectives exceeds, no more will be given.
+ ///
+ [DataField(required: true)]
+ public float MaxDifficulty;
+}
+
+///
+/// A set of objectives to try picking.
+/// Difficulty is checked over all sets, but each set has its own probability and pick count.
+///
+[DataRecord]
+public record struct AntagObjectiveSet()
+{
+ ///
+ /// The grouping used by the objective system to pick random objectives.
+ /// First a group is picked from these, then an objective from that group.
+ ///
+ [DataField(required: true)]
+ public ProtoId Groups = string.Empty;
+
+ ///
+ /// Probability of this set being used.
+ ///
+ [DataField]
+ public float Prob = 1f;
+
+ ///
+ /// Number of times to try picking objectives from this set.
+ /// Even if there is enough difficulty remaining, no more will be given after this.
+ ///
+ [DataField]
+ public int MaxPicks = 20;
+}
diff --git a/Content.Server/Antag/Components/AntagSelectionComponent.cs b/Content.Server/Antag/Components/AntagSelectionComponent.cs
index 5b6699dab76..cae56c1a7bb 100644
--- a/Content.Server/Antag/Components/AntagSelectionComponent.cs
+++ b/Content.Server/Antag/Components/AntagSelectionComponent.cs
@@ -42,6 +42,13 @@ public sealed partial class AntagSelectionComponent : Component
/// Is not serialized.
///
public HashSet SelectedSessions = new();
+
+ ///
+ /// Locale id for the name of the antag.
+ /// If this is set then the antag is listed in the round-end summary.
+ ///
+ [DataField]
+ public LocId? AgentName;
}
[DataDefinition]
diff --git a/Content.Server/GameTicking/Rules/Components/ThiefRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/ThiefRuleComponent.cs
index 01a078625ae..6ad1e177755 100644
--- a/Content.Server/GameTicking/Rules/Components/ThiefRuleComponent.cs
+++ b/Content.Server/GameTicking/Rules/Components/ThiefRuleComponent.cs
@@ -8,23 +8,4 @@ namespace Content.Server.GameTicking.Rules.Components;
/// Stores data for .
///
[RegisterComponent, Access(typeof(ThiefRuleSystem))]
-public sealed partial class ThiefRuleComponent : Component
-{
- [DataField]
- public ProtoId BigObjectiveGroup = "ThiefBigObjectiveGroups";
-
- [DataField]
- public ProtoId SmallObjectiveGroup = "ThiefObjectiveGroups";
-
- [DataField]
- public ProtoId EscapeObjectiveGroup = "ThiefEscapeObjectiveGroups";
-
- [DataField]
- public float BigObjectiveChance = 0.7f;
-
- [DataField]
- public float MaxObjectiveDifficulty = 2.5f;
-
- [DataField]
- public int MaxStealObjectives = 10;
-}
+public sealed partial class ThiefRuleComponent : Component;
diff --git a/Content.Server/GameTicking/Rules/Components/TraitorRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/TraitorRuleComponent.cs
index 47a4adeaf34..cec635b4caa 100644
--- a/Content.Server/GameTicking/Rules/Components/TraitorRuleComponent.cs
+++ b/Content.Server/GameTicking/Rules/Components/TraitorRuleComponent.cs
@@ -22,9 +22,6 @@ public sealed partial class TraitorRuleComponent : Component
[DataField]
public ProtoId SyndicateFaction = "Syndicate";
- [DataField]
- public ProtoId ObjectiveGroup = "TraitorObjectiveGroups";
-
[DataField]
public ProtoId CodewordAdjectives = "adjectives";
@@ -72,7 +69,4 @@ public enum SelectionState
///
[DataField]
public int StartingBalance = 20;
-
- [DataField]
- public int MaxDifficulty = 5;
}
diff --git a/Content.Server/GameTicking/Rules/GenericAntagRuleSystem.cs b/Content.Server/GameTicking/Rules/GenericAntagRuleSystem.cs
index 81bdda706bd..0367aa1460c 100644
--- a/Content.Server/GameTicking/Rules/GenericAntagRuleSystem.cs
+++ b/Content.Server/GameTicking/Rules/GenericAntagRuleSystem.cs
@@ -1,6 +1,8 @@
using Content.Server.GameTicking.Rules.Components;
using Content.Server.Objectives;
+using Content.Shared.Mind;
using System.Diagnostics.CodeAnalysis;
+using System.Linq;
namespace Content.Server.GameTicking.Rules;
@@ -47,7 +49,8 @@ public bool StartRule(string rule, EntityUid mindId, [NotNullWhen(true)] out Ent
private void OnObjectivesTextGetInfo(EntityUid uid, GenericAntagRuleComponent comp, ref ObjectivesTextGetInfoEvent args)
{
- args.Minds = comp.Minds;
+ // just temporary until this is deleted
+ args.Minds = comp.Minds.Select(mindId => (mindId, Comp(mindId).CharacterName ?? "?")).ToList();
args.AgentName = Loc.GetString(comp.AgentName);
}
}
diff --git a/Content.Server/GameTicking/Rules/ThiefRuleSystem.cs b/Content.Server/GameTicking/Rules/ThiefRuleSystem.cs
index 083085fa0d8..faec4a9e9ca 100644
--- a/Content.Server/GameTicking/Rules/ThiefRuleSystem.cs
+++ b/Content.Server/GameTicking/Rules/ThiefRuleSystem.cs
@@ -24,7 +24,6 @@ public override void Initialize()
SubscribeLocalEvent(AfterAntagSelected);
SubscribeLocalEvent(OnGetBriefing);
- SubscribeLocalEvent(OnObjectivesTextGetInfo);
}
private void AfterAntagSelected(Entity ent, ref AfterAntagEntitySelectedEvent args)
@@ -33,41 +32,9 @@ private void AfterAntagSelected(Entity ent, ref AfterAntagEn
return;
//Generate objectives
- GenerateObjectives(mindId, mind, ent);
_antag.SendBriefing(args.EntityUid, MakeBriefing(args.EntityUid), null, null);
}
- private void GenerateObjectives(EntityUid mindId, MindComponent mind, ThiefRuleComponent thiefRule)
- {
- // Give thieves their objectives
- var difficulty = 0f;
-
- if (_random.Prob(thiefRule.BigObjectiveChance)) // 70% chance to 1 big objective (structure or animal)
- {
- var objective = _objectives.GetRandomObjective(mindId, mind, thiefRule.BigObjectiveGroup);
- if (objective != null)
- {
- _mindSystem.AddObjective(mindId, mind, objective.Value);
- difficulty += Comp(objective.Value).Difficulty;
- }
- }
-
- for (var i = 0; i < thiefRule.MaxStealObjectives && thiefRule.MaxObjectiveDifficulty > difficulty; i++) // Many small objectives
- {
- var objective = _objectives.GetRandomObjective(mindId, mind, thiefRule.SmallObjectiveGroup);
- if (objective == null)
- continue;
-
- _mindSystem.AddObjective(mindId, mind, objective.Value);
- difficulty += Comp(objective.Value).Difficulty;
- }
-
- //Escape target
- var escapeObjective = _objectives.GetRandomObjective(mindId, mind, thiefRule.EscapeObjectiveGroup);
- if (escapeObjective != null)
- _mindSystem.AddObjective(mindId, mind, escapeObjective.Value);
- }
-
//Add mind briefing
private void OnGetBriefing(Entity thief, ref GetBriefingEvent args)
{
@@ -87,10 +54,4 @@ private string MakeBriefing(EntityUid thief)
briefing += "\n \n" + Loc.GetString("thief-role-greeting-equipment") + "\n";
return briefing;
}
-
- private void OnObjectivesTextGetInfo(Entity ent, ref ObjectivesTextGetInfoEvent args)
- {
- args.Minds = _antag.GetAntagMindEntityUids(ent.Owner);
- args.AgentName = Loc.GetString("thief-round-end-agent-name");
- }
}
diff --git a/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs b/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs
index 3d0a02d6aa9..abf46b7b967 100644
--- a/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs
+++ b/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs
@@ -33,15 +33,12 @@ public sealed class TraitorRuleSystem : GameRuleSystem
[Dependency] private readonly SharedJobSystem _jobs = default!;
[Dependency] private readonly ObjectivesSystem _objectives = default!;
- public const int MaxPicks = 20;
-
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent(AfterEntitySelected);
- SubscribeLocalEvent(OnObjectivesTextGetInfo);
SubscribeLocalEvent(OnObjectivesTextPrepend);
}
@@ -69,7 +66,7 @@ private void MakeCodewords(TraitorRuleComponent component)
}
}
- public bool MakeTraitor(EntityUid traitor, TraitorRuleComponent component, bool giveUplink = true, bool giveObjectives = true)
+ public bool MakeTraitor(EntityUid traitor, TraitorRuleComponent component, bool giveUplink = true)
{
//Grab the mind if it wasnt provided
if (!_mindSystem.TryGetMind(traitor, out var mindId, out var mind))
@@ -122,37 +119,16 @@ public bool MakeTraitor(EntityUid traitor, TraitorRuleComponent component, bool
RaiseLocalEvent(traitor, new MoodEffectEvent("TraitorFocused"));
- // Give traitors their objectives
- if (giveObjectives)
- {
- var difficulty = 0f;
- for (var pick = 0; pick < MaxPicks && component.MaxDifficulty > difficulty; pick++)
- {
- var objective = _objectives.GetRandomObjective(mindId, mind, component.ObjectiveGroup);
- if (objective == null)
- continue;
-
- _mindSystem.AddObjective(mindId, mind, objective.Value);
- var adding = Comp(objective.Value).Difficulty;
- difficulty += adding;
- Log.Debug($"Added objective {ToPrettyString(objective):objective} with {adding} difficulty");
- }
- }
-
return true;
}
- private void OnObjectivesTextGetInfo(EntityUid uid, TraitorRuleComponent comp, ref ObjectivesTextGetInfoEvent args)
- {
- args.Minds = _antag.GetAntagMindEntityUids(uid);
- args.AgentName = Loc.GetString("traitor-round-end-agent-name");
- }
-
+ // TODO: AntagCodewordsComponent
private void OnObjectivesTextPrepend(EntityUid uid, TraitorRuleComponent comp, ref ObjectivesTextPrependEvent args)
{
args.Text += "\n" + Loc.GetString("traitor-round-end-codewords", ("codewords", string.Join(", ", comp.Codewords)));
}
+ // TODO: figure out how to handle this? add priority to briefing event?
private string GenerateBriefing(string[] codewords, Note[]? uplinkCode, string? objectiveIssuer = null)
{
var sb = new StringBuilder();
diff --git a/Content.Server/Objectives/ObjectivesSystem.cs b/Content.Server/Objectives/ObjectivesSystem.cs
index bf013bc0402..18077b413ad 100644
--- a/Content.Server/Objectives/ObjectivesSystem.cs
+++ b/Content.Server/Objectives/ObjectivesSystem.cs
@@ -36,14 +36,14 @@ public override void Initialize()
private void OnRoundEndText(RoundEndTextAppendEvent ev)
{
// go through each gamerule getting data for the roundend summary.
- var summaries = new Dictionary>>();
+ var summaries = new Dictionary>>();
var query = EntityQueryEnumerator();
while (query.MoveNext(out var uid, out var gameRule))
{
if (!_gameTicker.IsGameRuleAdded(uid, gameRule))
continue;
- var info = new ObjectivesTextGetInfoEvent(new List(), string.Empty);
+ var info = new ObjectivesTextGetInfoEvent(new List<(EntityUid, string)>(), string.Empty);
RaiseLocalEvent(uid, ref info);
if (info.Minds.Count == 0)
continue;
@@ -51,7 +51,7 @@ private void OnRoundEndText(RoundEndTextAppendEvent ev)
// first group the gamerules by their agents, for example 2 different dragons
var agent = info.AgentName;
if (!summaries.ContainsKey(agent))
- summaries[agent] = new Dictionary>();
+ summaries[agent] = new Dictionary>();
var prepend = new ObjectivesTextPrependEvent("");
RaiseLocalEvent(uid, ref prepend);
@@ -79,7 +79,7 @@ private void OnRoundEndText(RoundEndTextAppendEvent ev)
foreach (var (_, minds) in summary)
{
total += minds.Count;
- totalInCustody += minds.Where(m => IsInCustody(m)).Count();
+ totalInCustody += minds.Where(pair => IsInCustody(pair.Item1)).Count();
}
var result = new StringBuilder();
@@ -104,19 +104,16 @@ private void OnRoundEndText(RoundEndTextAppendEvent ev)
}
}
- private void AddSummary(StringBuilder result, string agent, List minds)
+ private void AddSummary(StringBuilder result, string agent, List<(EntityUid, string)> minds)
{
var agentSummaries = new List<(string summary, float successRate, int completedObjectives)>();
- foreach (var mindId in minds)
+ foreach (var (mindId, name) in minds)
{
- if (!TryComp(mindId, out MindComponent? mind))
- continue;
-
- var title = GetTitle(mindId, mind);
- if (title == null)
+ if (!TryComp(mindId, out var mind))
continue;
+ var title = GetTitle((mindId, mind), name);
var custody = IsInCustody(mindId, mind) ? Loc.GetString("objectives-in-custody") : string.Empty;
var objectives = mind.Objectives;
@@ -238,34 +235,18 @@ private bool IsInCustody(EntityUid mindId, MindComponent? mind = null)
///
/// Get the title for a player's mind used in round end.
+ /// Pass in the original entity name which is shown alongside username.
///
- public string? GetTitle(EntityUid mindId, MindComponent? mind = null)
+ public string GetTitle(Entity mind, string name)
{
- if (!Resolve(mindId, ref mind))
- return null;
-
- var name = mind.CharacterName;
- var username = (string?) null;
-
- if (mind.OriginalOwnerUserId != null &&
- _player.TryGetPlayerData(mind.OriginalOwnerUserId.Value, out var sessionData))
+ if (Resolve(mind, ref mind.Comp) &&
+ mind.Comp.OriginalOwnerUserId != null &&
+ _player.TryGetPlayerData(mind.Comp.OriginalOwnerUserId.Value, out var sessionData))
{
- username = sessionData.UserName;
+ var username = sessionData.UserName;
+ return Loc.GetString("objectives-player-user-named", ("user", username), ("name", name));
}
-
- if (username != null)
- {
- if (name != null)
- return Loc.GetString("objectives-player-user-named", ("user", username), ("name", name));
-
- return Loc.GetString("objectives-player-user", ("user", username));
- }
-
- // nothing to identify the player by, just give up
- if (name == null)
- return null;
-
return Loc.GetString("objectives-player-named", ("name", name));
}
}
@@ -279,7 +260,7 @@ private bool IsInCustody(EntityUid mindId, MindComponent? mind = null)
/// The objectives system already checks if the game rule is added so you don't need to check that in this event's handler.
///
[ByRefEvent]
-public record struct ObjectivesTextGetInfoEvent(List Minds, string AgentName);
+public record struct ObjectivesTextGetInfoEvent(List<(EntityUid, string)> Minds, string AgentName);
///
/// Raised on the game rule before text for each agent's objectives is added, letting you prepend something.
diff --git a/Resources/Locale/en-US/objectives/round-end.ftl b/Resources/Locale/en-US/objectives/round-end.ftl
index b4314b2caff..3da81fc9640 100644
--- a/Resources/Locale/en-US/objectives/round-end.ftl
+++ b/Resources/Locale/en-US/objectives/round-end.ftl
@@ -6,7 +6,6 @@ objectives-round-end-result = {$count ->
objectives-round-end-result-in-custody = {$custody} out of {$count} {MAKEPLURAL($agent)} were in custody.
objectives-player-user-named = [color=White]{$name}[/color] ([color=gray]{$user}[/color])
-objectives-player-user = [color=gray]{$user}[/color]
objectives-player-named = [color=White]{$name}[/color]
objectives-no-objectives = {$custody}{$title} was a {$agent}.
diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml
index 801dcc4b859..d4a58f961aa 100644
--- a/Resources/Prototypes/GameRules/events.yml
+++ b/Resources/Prototypes/GameRules/events.yml
@@ -402,9 +402,9 @@
prototype: Nukeops
- type: entity
+ noSpawn: true
+ parent: BaseTraitorRule
id: SleeperAgentsRule
- parent: BaseGameRule
- categories: [ HideSpawnMenu ]
components:
- type: StationEvent
earliestStart: 25
@@ -413,7 +413,6 @@
reoccurrenceDelay: 30
startAnnouncement: false
- type: AlertLevelInterceptionRule
- - type: TraitorRule
- type: AntagSelection
definitions:
- prefRoles: [ Traitor ]
diff --git a/Resources/Prototypes/GameRules/midround.yml b/Resources/Prototypes/GameRules/midround.yml
index fca0073b4e5..ba2cd530480 100644
--- a/Resources/Prototypes/GameRules/midround.yml
+++ b/Resources/Prototypes/GameRules/midround.yml
@@ -35,7 +35,19 @@
id: Thief
components:
- type: ThiefRule
+ - type: AntagObjectives
+ objectives:
+ - EscapeThiefShuttleObjective
+ - type: AntagRandomObjectives
+ sets:
+ - groups: ThiefBigObjectiveGroups
+ prob: 0.7
+ maxPicks: 1
+ - groups: ThiefObjectiveGroups
+ maxPicks: 10
+ maxDifficulty: 2.5
- type: AntagSelection
+ agentName: thief-round-end-agent-name
definitions:
- prefRoles: [ Thief ]
maxRange:
diff --git a/Resources/Prototypes/GameRules/roundstart.yml b/Resources/Prototypes/GameRules/roundstart.yml
index 4ae53c9b37a..a433f10d062 100644
--- a/Resources/Prototypes/GameRules/roundstart.yml
+++ b/Resources/Prototypes/GameRules/roundstart.yml
@@ -134,16 +134,30 @@
prototype: Nukeops
- type: entity
- id: Traitor
+ abstract: true
parent: BaseGameRule
+ id: BaseTraitorRule
+ components:
+ - type: TraitorRule
+ # TODO: codewords in yml
+ # TODO: uplink in yml
+ - type: AntagRandomObjectives
+ sets:
+ - groups: TraitorObjectiveGroups
+ maxDifficulty: 5
+ - type: AntagSelection
+ agentName: traitor-round-end-agent-name
+
+- type: entity
categories: [ HideSpawnMenu ]
+ parent: BaseTraitorRule
+ id: Traitor
components:
- type: GameRule
minPlayers: 5
delay:
min: 240
max: 420
- - type: TraitorRule
- type: AntagSelection
definitions:
- prefRoles: [ Traitor ]
diff --git a/Resources/Prototypes/Objectives/objectiveGroups.yml b/Resources/Prototypes/Objectives/objectiveGroups.yml
index fb4ce6f4a16..d7e08841307 100644
--- a/Resources/Prototypes/Objectives/objectiveGroups.yml
+++ b/Resources/Prototypes/Objectives/objectiveGroups.yml
@@ -61,11 +61,6 @@
ThiefObjectiveGroupStructure: 0 #Temporarily disabled until obvious ways to steal structures are added
ThiefObjectiveGroupAnimal: 2
-- type: weightedRandom
- id: ThiefEscapeObjectiveGroups
- weights:
- ThiefObjectiveGroupEscape: 1
-
- type: weightedRandom
id: ThiefObjectiveGroupCollection
weights:
From ef2ea95edefbd814a485a43e255122b837fa2045 Mon Sep 17 00:00:00 2001
From: sleepyyapril
Date: Sun, 15 Dec 2024 01:48:14 -0400
Subject: [PATCH 005/366] keep up
---
Resources/Prototypes/GameRules/events.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml
index d4a58f961aa..d2780011c96 100644
--- a/Resources/Prototypes/GameRules/events.yml
+++ b/Resources/Prototypes/GameRules/events.yml
@@ -402,7 +402,7 @@
prototype: Nukeops
- type: entity
- noSpawn: true
+ categories: [ HideSpawnMenu ]
parent: BaseTraitorRule
id: SleeperAgentsRule
components:
From 1fd02db8912480b2988aa7fd45929599e2119ebc Mon Sep 17 00:00:00 2001
From: deltanedas <39013340+deltanedas@users.noreply.github.com>
Date: Sat, 25 May 2024 20:15:56 +0000
Subject: [PATCH 006/366] move nukie profile loading into its own rule (#28208)
* move profile loading out of nukeops rule
* make BaseNukeopsRule and use AntagLoadProfileRule
* untroll
---------
Co-authored-by: deltanedas <@deltanedas:kde.org>
---
.../Rules/AntagLoadProfileRuleSystem.cs | 39 +++++++++++++++++++
.../AntagLoadProfileRuleCOmponent.cs | 7 ++++
.../GameTicking/Rules/NukeopsRuleSystem.cs | 29 --------------
Resources/Prototypes/GameRules/events.yml | 4 +-
Resources/Prototypes/GameRules/roundstart.yml | 16 ++++++--
5 files changed, 60 insertions(+), 35 deletions(-)
create mode 100644 Content.Server/GameTicking/Rules/AntagLoadProfileRuleSystem.cs
create mode 100644 Content.Server/GameTicking/Rules/Components/AntagLoadProfileRuleCOmponent.cs
diff --git a/Content.Server/GameTicking/Rules/AntagLoadProfileRuleSystem.cs b/Content.Server/GameTicking/Rules/AntagLoadProfileRuleSystem.cs
new file mode 100644
index 00000000000..fd3fb6cd655
--- /dev/null
+++ b/Content.Server/GameTicking/Rules/AntagLoadProfileRuleSystem.cs
@@ -0,0 +1,39 @@
+using Content.Server.Antag;
+using Content.Server.GameTicking.Rules.Components;
+using Content.Server.Humanoid;
+using Content.Server.Preferences.Managers;
+using Content.Shared.Humanoid;
+using Content.Shared.Humanoid.Prototypes;
+using Content.Shared.Preferences;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.GameTicking.Rules;
+
+public sealed class AntagLoadProfileRuleSystem : GameRuleSystem
+{
+ [Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
+ [Dependency] private readonly IPrototypeManager _proto = default!;
+ [Dependency] private readonly IServerPreferencesManager _prefs = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnSelectEntity);
+ }
+
+ private void OnSelectEntity(Entity ent, ref AntagSelectEntityEvent args)
+ {
+ if (args.Handled)
+ return;
+
+ var profile = args.Session != null
+ ? _prefs.GetPreferences(args.Session.UserId).SelectedCharacter as HumanoidCharacterProfile
+ : HumanoidCharacterProfile.RandomWithSpecies();
+ if (profile?.Species is not {} speciesId || !_proto.TryIndex(speciesId, out var species))
+ species = _proto.Index(SharedHumanoidAppearanceSystem.DefaultSpecies);
+
+ args.Entity = Spawn(species.Prototype);
+ _humanoid.LoadProfile(args.Entity.Value, profile);
+ }
+}
diff --git a/Content.Server/GameTicking/Rules/Components/AntagLoadProfileRuleCOmponent.cs b/Content.Server/GameTicking/Rules/Components/AntagLoadProfileRuleCOmponent.cs
new file mode 100644
index 00000000000..5e58fd14fc0
--- /dev/null
+++ b/Content.Server/GameTicking/Rules/Components/AntagLoadProfileRuleCOmponent.cs
@@ -0,0 +1,7 @@
+namespace Content.Server.GameTicking.Rules.Components;
+
+///
+/// Makes this rules antags spawn a humanoid, either from the player's profile or a random one.
+///
+[RegisterComponent]
+public sealed partial class AntagLoadProfileRuleComponent : Component;
diff --git a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs
index 7cc51db5766..c3e8e90f843 100644
--- a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs
+++ b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs
@@ -1,13 +1,9 @@
using Content.Server.Antag;
using Content.Server.Communications;
using Content.Server.GameTicking.Rules.Components;
-using Content.Server.Humanoid;
-using Content.Server.NPC.Components;
-using Content.Server.NPC.Systems;
using Content.Server.Nuke;
using Content.Server.NukeOps;
using Content.Server.Popups;
-using Content.Server.Preferences.Managers;
using Content.Server.Roles;
using Content.Server.RoundEnd;
using Content.Server.Shuttles.Events;
@@ -15,19 +11,14 @@
using Content.Server.Station.Components;
using Content.Server.Store.Components;
using Content.Server.Store.Systems;
-using Content.Shared.GameTicking.Components;
-using Content.Shared.Humanoid;
-using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Nuke;
using Content.Shared.NukeOps;
-using Content.Shared.Preferences;
using Content.Shared.Store;
using Content.Shared.Tag;
using Content.Shared.Zombies;
using Robust.Shared.Map;
-using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Utility;
using System.Linq;
@@ -36,10 +27,7 @@ namespace Content.Server.GameTicking.Rules;
public sealed class NukeopsRuleSystem : GameRuleSystem
{
- [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
- [Dependency] private readonly IServerPreferencesManager _prefs = default!;
[Dependency] private readonly EmergencyShuttleSystem _emergency = default!;
- [Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
[Dependency] private readonly NpcFactionSystem _npcFaction = default!;
[Dependency] private readonly AntagSelectionSystem _antag = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
@@ -71,7 +59,6 @@ public override void Initialize()
SubscribeLocalEvent(OnWarDeclared);
SubscribeLocalEvent(OnShuttleCallAttempt);
- SubscribeLocalEvent(OnAntagSelectEntity);
SubscribeLocalEvent(OnAfterAntagEntSelected);
}
@@ -471,22 +458,6 @@ private void CheckRoundShouldEnd(Entity ent)
nukeops.RoundEndBehavior = RoundEndBehavior.Nothing;
}
- // this should really go anywhere else but im tired.
- private void OnAntagSelectEntity(Entity ent, ref AntagSelectEntityEvent args)
- {
- if (args.Handled)
- return;
-
- var profile = args.Session != null
- ? _prefs.GetPreferences(args.Session.UserId).SelectedCharacter as HumanoidCharacterProfile
- : HumanoidCharacterProfile.RandomWithSpecies();
- if (!_prototypeManager.TryIndex(profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies, out SpeciesPrototype? species))
- species = _prototypeManager.Index(SharedHumanoidAppearanceSystem.DefaultSpecies);
-
- args.Entity = Spawn(species.Prototype);
- _humanoid.LoadProfile(args.Entity.Value, profile!);
- }
-
private void OnAfterAntagEntSelected(Entity ent, ref AfterAntagEntitySelectedEvent args)
{
if (ent.Comp.TargetStation is not { } station)
diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml
index d2780011c96..a0649eb2d6b 100644
--- a/Resources/Prototypes/GameRules/events.yml
+++ b/Resources/Prototypes/GameRules/events.yml
@@ -368,9 +368,9 @@
prototype: InitialInfected
- type: entity
- id: LoneOpsSpawn
- parent: BaseGameRule
categories: [ HideSpawnMenu ]
+ parent: BaseNukeopsRule
+ id: LoneOpsSpawn
components:
- type: StationEvent
earliestStart: 35
diff --git a/Resources/Prototypes/GameRules/roundstart.yml b/Resources/Prototypes/GameRules/roundstart.yml
index a433f10d062..80354861ade 100644
--- a/Resources/Prototypes/GameRules/roundstart.yml
+++ b/Resources/Prototypes/GameRules/roundstart.yml
@@ -64,17 +64,25 @@
roundEndDelay: 10
- type: entity
- id: Nukeops
+ abstract: true
parent: BaseGameRule
- categories: [ HideSpawnMenu ]
+ id: BaseNukeopsRule
components:
- - type: GameRule
- minPlayers: 35
- type: RandomMetadata #this generates the random operation name cuz it's cool.
nameSegments:
- operationPrefix
- operationSuffix
- type: NukeopsRule
+ - type: AntagSelection
+ - type: AntagLoadProfileRule
+
+- type: entity
+ categories: [ HideSpawnMenu ]
+ parent: BaseNukeopsRule
+ id: Nukeops
+ components:
+ - type: GameRule
+ minPlayers: 20
- type: LoadMapRule
gameMap: NukieOutpost
- type: AntagSelection
From 4939a31b158ca33295512acf314f6d0512e9ef5f Mon Sep 17 00:00:00 2001
From: deltanedas <39013340+deltanedas@users.noreply.github.com>
Date: Thu, 11 Jul 2024 05:55:56 +0000
Subject: [PATCH 007/366] item toggling giga rework + full ninja refactor
(#28039)
* item toggle refactoring and some new systems
* add ToggleClothing component/system
* unhardcode magboots gravity logic
* make magboots and speedboots use ItemToggle and stuff
* remove now useless clothing components
* update client/server magboots systems
* add note to use ItemToggledEvent in ToggleActionEvent doc
* refactor PowerCellDraw to use ItemToggle for ui open/close control
* add TryUseCharges, refactor charges system
* update magboot trigger code
* make borg use ItemToggle, network SelectedModule instead of now removed Activated
* add AccessToggle for borg
* the giga ninja refactor
* update ninja yml
* update ItemToggle usage for some stuff
* fix activatableui requires power
* random fixing
* yaml fixing
* nuke ItemToggleDisarmMalus
* make defib use ItemToggle
* make things that use power not turn on if missing use charge
* pro
* fix sound prediction
* bruh
* proximity detector use ItemToggle
* oop
* big idiot syndrome
* fix ninja spawn rule and make it generic
* fix ninja spawn rule yml
* move loading profiles into AntagLoadProfileRule
* more ninja refactor
* ninja yml fixes
* the dreaded copy paste ops
* remove useless NinjaRuleComponent and ue AntagSelection for greeting
* fix invisibility
* move IsCompleted to SharedObjectivesSystem
* ability fixes
* oop fix powercell instantly draining itself
* sentient speedboots gaming
* make reflect use ItemToggle
* fix other test
* loadprofilerule moved into its own pr
* remove conflict with dragon refactor
* remove all GenericAntag code from ninja
* )
* probably
* remove old enabled
* great language bravo vince
* GREAT LANGUAGE
* who made this language
* because it stinks
* reparent blood-red magboots to magboots probbbly works
* most of the review stuff
* hasGrav doesnt mean what i thought it did
* make health analyzer use itemtoggle, not fail test
* fix mag/speed boots being wacky
* UNTROLL
* add ItemToggle to the random health analyzers
* a
* remove unused obsolete borg func
* untrolling
* :trollface:
* fix test
* fix
* g
* untroll
---------
Co-authored-by: deltanedas <@deltanedas:kde.org>
---
.../Items/Systems/ItemToggleSystem.cs | 9 -
.../Ninja/Systems/ItemCreatorSystem.cs | 5 +
.../Ninja/Systems/NinjaGlovesSystem.cs | 7 +-
.../Ninja/Systems/NinjaSuitSystem.cs | 21 +-
Content.Client/Ninja/Systems/NinjaSystem.cs | 9 +-
.../Ninja/Systems/SpiderChargeSystem.cs | 5 +
.../Interaction/InteractionTest.Helpers.cs | 2 +-
.../Tests/Interaction/InteractionTest.cs | 4 +-
.../Components/AutoRechargeComponent.cs | 1 +
.../Charges/Systems/ChargesSystem.cs | 12 +-
.../Rules/Components/NinjaRuleComponent.cs | 27 ---
.../ItemToggleDisarmMalusComponent.cs | 22 ---
.../Components/ItemToggleSharpComponent.cs | 9 -
.../Item/ItemToggle/ItemToggleSystem.cs | 44 -----
.../Components/HealthAnalyzerComponent.cs | 3 +
Content.Server/Medical/DefibrillatorSystem.cs | 72 +------
.../Medical/HealthAnalyzerSystem.cs | 21 +-
.../Ninja/Events/BatteryChangedEvent.cs | 2 +-
.../Ninja/Systems/BatteryDrainerSystem.cs | 21 +-
.../Ninja/Systems/ItemCreatorSystem.cs | 57 ++++++
.../Ninja/Systems/NinjaGlovesSystem.cs | 102 +++-------
.../Ninja/Systems/NinjaSuitSystem.cs | 79 ++------
.../Ninja/Systems/SpaceNinjaSystem.cs | 80 +-------
.../Ninja/Systems/SpiderChargeSystem.cs | 3 +-
.../Ninja/Systems/StunProviderSystem.cs | 22 ++-
.../Objectives/Systems/CodeConditionSystem.cs | 14 --
.../PowerCell/PowerCellSystem.Draw.cs | 38 ++--
Content.Server/PowerCell/PowerCellSystem.cs | 2 +-
.../Silicons/Borgs/BorgSystem.Modules.cs | 4 +-
Content.Server/Silicons/Borgs/BorgSystem.cs | 79 +++-----
.../Components/NinjaSpawnRuleComponent.cs | 16 --
.../Components/SpaceSpawnRuleComponent.cs | 25 +++
.../{NinjaSpawnRule.cs => SpaceSpawnRule.cs} | 38 ++--
.../Stunnable/Systems/StunbatonSystem.cs | 2 +-
.../Weapons/Misc/TetherGunSystem.cs | 6 +-
.../Systems/ArtifactMagnetTriggerSystem.cs | 9 +-
.../Components/AccessToggleComponent.cs | 11 ++
.../Access/Systems/AccessToggleSystem.cs | 21 ++
.../Beeper/Components/BeeperComponent.cs | 11 +-
Content.Shared/Beeper/Systems/BeeperSystem.cs | 37 ++--
.../Beeper/Systems/ProximityBeeperSystem.cs | 84 +-------
.../Charges/Systems/SharedChargesSystem.cs | 33 +++-
.../ClothingSpeedModifierComponent.cs | 19 +-
.../Clothing/ClothingSpeedModifierSystem.cs | 107 ++---------
.../Components/StealthClothingComponent.cs | 43 -----
.../Components/ToggleClothingComponent.cs | 40 ++++
.../ToggleClothingSpeedComponent.cs | 35 ----
.../EntitySystems/StealthClothingSystem.cs | 144 --------------
.../EntitySystems/ToggleClothingSystem.cs | 58 ++++++
Content.Shared/Clothing/MagbootsComponent.cs | 20 +-
Content.Shared/Clothing/MagbootsSystem.cs | 90 +++++++++
.../Clothing/SharedMagbootsSystem.cs | 101 ----------
.../Item/ItemToggle/ComponentTogglerSystem.cs | 26 +++
.../Components/ComponentTogglerComponent.cs | 32 ++++
.../ItemToggleActiveSoundComponent.cs | 4 +-
.../Components/ItemToggleComponent.cs | 9 +-
.../Components/ToggleVerbComponent.cs | 18 ++
...temToggleSystem.cs => ItemToggleSystem.cs} | 180 +++++++++---------
.../Item/ItemToggle/ToggleVerbSystem.cs | 34 ++++
.../Medical/DefibrillatorComponent.cs | 16 +-
.../Components/BatteryDrainerComponent.cs | 12 +-
.../Components/BombingTargetComponent.cs | 4 +-
.../Ninja/Components/DashAbilityComponent.cs | 14 +-
.../Ninja/Components/EmagProviderComponent.cs | 13 +-
.../Ninja/Components/EnergyKatanaComponent.cs | 4 +-
.../Ninja/Components/ItemCreatorComponent.cs | 52 +++++
.../Ninja/Components/NinjaGlovesComponent.cs | 44 +++--
.../Ninja/Components/NinjaSuitComponent.cs | 87 ++-------
.../Ninja/Components/SpaceNinjaComponent.cs | 19 +-
.../Ninja/Components/SpiderChargeComponent.cs | 9 +-
.../Ninja/Components/StunProviderComponent.cs | 38 ++--
.../Ninja/Systems/DashAbilitySystem.cs | 80 +++-----
.../Ninja/Systems/EmagProviderSystem.cs | 27 +--
.../Ninja/Systems/EnergyKatanaSystem.cs | 25 +--
.../Ninja/Systems/ItemCreatorSystem.cs | 56 ++++++
.../Systems/SharedBatteryDrainerSystem.cs | 25 ++-
.../Ninja/Systems/SharedNinjaGlovesSystem.cs | 108 ++++++-----
.../Ninja/Systems/SharedNinjaSuitSystem.cs | 154 ++++++++-------
.../Ninja/Systems/SharedSpaceNinjaSystem.cs | 59 +++---
.../Ninja/Systems/SharedSpiderChargeSystem.cs | 6 +
.../Ninja/Systems/SharedStunProviderSystem.cs | 18 +-
.../Systems/SharedObjectivesSystem.cs | 35 +++-
.../Pinpointer/SharedProximityBeeper.cs | 9 -
.../PowerCell/PowerCellDrawComponent.cs | 18 +-
.../PowerCell/SharedPowerCellSystem.cs | 27 ++-
.../Components/ProximityDetectorComponent.cs | 8 +-
.../Systems/ProximityDetectionSystem.cs | 86 ++++-----
.../Borgs/Components/BorgChassisComponent.cs | 8 +-
.../Silicons/Borgs/SharedBorgSystem.cs | 4 +-
.../Toggleable/ToggleActionEvent.cs | 7 +-
.../Tools/Systems/SharedToolSystem.cs | 2 +-
.../ActivatableUISystem.Power.cs | 24 ++-
.../Weapons/Reflect/ReflectComponent.cs | 7 +-
.../Weapons/Reflect/ReflectSystem.cs | 32 ++--
Resources/Prototypes/Actions/ninja.yml | 10 +-
.../Entities/Clothing/Hands/gloves.yml | 27 ++-
.../Entities/Clothing/Head/misc.yml | 30 +++
.../Entities/Clothing/OuterClothing/suits.yml | 26 ++-
.../Entities/Clothing/Shoes/magboots.yml | 155 ++++-----------
.../Entities/Clothing/Shoes/misc.yml | 31 ++-
.../Entities/Clothing/base_clothing.yml | 9 +
.../Entities/Markers/Spawners/ghost_roles.yml | 7 +-
.../Mobs/Cyborgs/base_borg_chassis.yml | 5 +
.../Prototypes/Entities/Mobs/Player/human.yml | 25 ---
.../Objects/Devices/base_handheld.yml | 11 ++
.../Entities/Objects/Devices/pda.yml | 3 +
.../Entities/Objects/Devices/station_map.yml | 11 +-
.../Entities/Objects/Shields/shields.yml | 13 +-
.../Objects/Specific/Medical/defib.yml | 5 +
.../Medical/handheld_crew_monitor.yml | 9 +-
.../Specific/Medical/healthanalyzer.yml | 2 +
.../Objects/Specific/Research/anomaly.yml | 9 +-
.../Objects/Tools/handheld_mass_scanner.yml | 3 +-
.../Entities/Objects/Tools/welders.yml | 6 +-
.../Weapons/Guns/Launchers/launchers.yml | 4 +
.../Objects/Weapons/Melee/e_sword.yml | 43 ++---
.../Entities/Objects/Weapons/Melee/sword.yml | 14 +-
.../Structures/Machines/Medical/cryo_pod.yml | 1 +
Resources/Prototypes/GameRules/events.yml | 38 +++-
Resources/Prototypes/GameRules/midround.yml | 31 ---
120 files changed, 1653 insertions(+), 2041 deletions(-)
delete mode 100644 Content.Client/Items/Systems/ItemToggleSystem.cs
create mode 100644 Content.Client/Ninja/Systems/ItemCreatorSystem.cs
create mode 100644 Content.Client/Ninja/Systems/SpiderChargeSystem.cs
delete mode 100644 Content.Server/GameTicking/Rules/Components/NinjaRuleComponent.cs
delete mode 100644 Content.Server/Item/ItemToggle/Components/ItemToggleDisarmMalusComponent.cs
delete mode 100644 Content.Server/Item/ItemToggle/Components/ItemToggleSharpComponent.cs
delete mode 100644 Content.Server/Item/ItemToggle/ItemToggleSystem.cs
create mode 100644 Content.Server/Ninja/Systems/ItemCreatorSystem.cs
delete mode 100644 Content.Server/StationEvents/Components/NinjaSpawnRuleComponent.cs
create mode 100644 Content.Server/StationEvents/Components/SpaceSpawnRuleComponent.cs
rename Content.Server/StationEvents/Events/{NinjaSpawnRule.cs => SpaceSpawnRule.cs} (53%)
create mode 100644 Content.Shared/Access/Components/AccessToggleComponent.cs
create mode 100644 Content.Shared/Access/Systems/AccessToggleSystem.cs
delete mode 100644 Content.Shared/Clothing/Components/StealthClothingComponent.cs
create mode 100644 Content.Shared/Clothing/Components/ToggleClothingComponent.cs
delete mode 100644 Content.Shared/Clothing/Components/ToggleClothingSpeedComponent.cs
delete mode 100644 Content.Shared/Clothing/EntitySystems/StealthClothingSystem.cs
create mode 100644 Content.Shared/Clothing/EntitySystems/ToggleClothingSystem.cs
create mode 100644 Content.Shared/Clothing/MagbootsSystem.cs
delete mode 100644 Content.Shared/Clothing/SharedMagbootsSystem.cs
create mode 100644 Content.Shared/Item/ItemToggle/ComponentTogglerSystem.cs
create mode 100644 Content.Shared/Item/ItemToggle/Components/ComponentTogglerComponent.cs
create mode 100644 Content.Shared/Item/ItemToggle/Components/ToggleVerbComponent.cs
rename Content.Shared/Item/ItemToggle/{SharedItemToggleSystem.cs => ItemToggleSystem.cs} (67%)
create mode 100644 Content.Shared/Item/ItemToggle/ToggleVerbSystem.cs
create mode 100644 Content.Shared/Ninja/Components/ItemCreatorComponent.cs
create mode 100644 Content.Shared/Ninja/Systems/ItemCreatorSystem.cs
create mode 100644 Content.Shared/Ninja/Systems/SharedSpiderChargeSystem.cs
delete mode 100644 Content.Shared/Pinpointer/SharedProximityBeeper.cs
create mode 100644 Resources/Prototypes/Entities/Objects/Devices/base_handheld.yml
diff --git a/Content.Client/Items/Systems/ItemToggleSystem.cs b/Content.Client/Items/Systems/ItemToggleSystem.cs
deleted file mode 100644
index 46d6f1b464d..00000000000
--- a/Content.Client/Items/Systems/ItemToggleSystem.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using Content.Shared.Item.ItemToggle;
-
-namespace Content.Shared.Item;
-
-///
-public sealed class ItemToggleSystem : SharedItemToggleSystem
-{
-
-}
diff --git a/Content.Client/Ninja/Systems/ItemCreatorSystem.cs b/Content.Client/Ninja/Systems/ItemCreatorSystem.cs
new file mode 100644
index 00000000000..9ab62cc12db
--- /dev/null
+++ b/Content.Client/Ninja/Systems/ItemCreatorSystem.cs
@@ -0,0 +1,5 @@
+using Content.Shared.Ninja.Systems;
+
+namespace Content.Client.Ninja.Systems;
+
+public sealed class ItemCreatorSystem : SharedItemCreatorSystem;
diff --git a/Content.Client/Ninja/Systems/NinjaGlovesSystem.cs b/Content.Client/Ninja/Systems/NinjaGlovesSystem.cs
index 7758c3d7e2b..5b07b1588fd 100644
--- a/Content.Client/Ninja/Systems/NinjaGlovesSystem.cs
+++ b/Content.Client/Ninja/Systems/NinjaGlovesSystem.cs
@@ -2,9 +2,4 @@
namespace Content.Client.Ninja.Systems;
-///
-/// Does nothing special, only exists to provide a client implementation.
-///
-public sealed class NinjaGlovesSystem : SharedNinjaGlovesSystem
-{
-}
+public sealed class NinjaGlovesSystem : SharedNinjaGlovesSystem;
diff --git a/Content.Client/Ninja/Systems/NinjaSuitSystem.cs b/Content.Client/Ninja/Systems/NinjaSuitSystem.cs
index fde1801b37d..852ea8af46e 100644
--- a/Content.Client/Ninja/Systems/NinjaSuitSystem.cs
+++ b/Content.Client/Ninja/Systems/NinjaSuitSystem.cs
@@ -1,24 +1,5 @@
-using Content.Shared.Clothing.EntitySystems;
-using Content.Shared.Ninja.Components;
using Content.Shared.Ninja.Systems;
namespace Content.Client.Ninja.Systems;
-///
-/// Disables cloak prediction since client has no knowledge of battery power.
-/// Cloak will still be enabled after server tells it.
-///
-public sealed class NinjaSuitSystem : SharedNinjaSuitSystem
-{
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent(OnAttemptStealth);
- }
-
- private void OnAttemptStealth(EntityUid uid, NinjaSuitComponent comp, AttemptStealthEvent args)
- {
- args.Cancel();
- }
-}
+public sealed class NinjaSuitSystem : SharedNinjaSuitSystem;
diff --git a/Content.Client/Ninja/Systems/NinjaSystem.cs b/Content.Client/Ninja/Systems/NinjaSystem.cs
index aa2fa2047f1..958dc6a5d9a 100644
--- a/Content.Client/Ninja/Systems/NinjaSystem.cs
+++ b/Content.Client/Ninja/Systems/NinjaSystem.cs
@@ -2,11 +2,4 @@
namespace Content.Client.Ninja.Systems;
-///
-/// Currently does nothing special clientside.
-/// All functionality is in shared and server.
-/// Only exists to prevent crashing.
-///
-public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem
-{
-}
+public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem;
diff --git a/Content.Client/Ninja/Systems/SpiderChargeSystem.cs b/Content.Client/Ninja/Systems/SpiderChargeSystem.cs
new file mode 100644
index 00000000000..b107fd3867d
--- /dev/null
+++ b/Content.Client/Ninja/Systems/SpiderChargeSystem.cs
@@ -0,0 +1,5 @@
+using Content.Shared.Ninja.Systems;
+
+namespace Content.Client.Ninja.Systems;
+
+public sealed class SpiderChargeSystem : SharedSpiderChargeSystem;
diff --git a/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs b/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs
index a19b62cd70a..0f2c314ed01 100644
--- a/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs
+++ b/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs
@@ -171,7 +171,7 @@ await Server.WaitPost(() =>
// 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));
}
});
diff --git a/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs b/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs
index 457d3e31920..b3d684e01a0 100644
--- a/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs
+++ b/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs
@@ -104,7 +104,7 @@ public abstract partial class InteractionTest
protected Content.Server.Construction.ConstructionSystem SConstruction = default!;
protected SharedDoAfterSystem DoAfterSys = default!;
protected ToolSystem ToolSys = default!;
- protected SharedItemToggleSystem ItemToggleSys = default!;
+ protected ItemToggleSystem ItemToggleSys = default!;
protected InteractionTestSystem STestSystem = default!;
protected SharedTransformSystem Transform = default!;
protected SharedMapSystem MapSystem = default!;
@@ -165,7 +165,7 @@ public virtual async Task Setup()
HandSys = SEntMan.System();
InteractSys = SEntMan.System();
ToolSys = SEntMan.System();
- ItemToggleSys = SEntMan.System();
+ ItemToggleSys = SEntMan.System();
DoAfterSys = SEntMan.System();
Transform = SEntMan.System();
MapSystem = SEntMan.System();
diff --git a/Content.Server/Charges/Components/AutoRechargeComponent.cs b/Content.Server/Charges/Components/AutoRechargeComponent.cs
index 9dcf555ea93..165b181dcbc 100644
--- a/Content.Server/Charges/Components/AutoRechargeComponent.cs
+++ b/Content.Server/Charges/Components/AutoRechargeComponent.cs
@@ -7,6 +7,7 @@ namespace Content.Server.Charges.Components;
/// Something with limited charges that can be recharged automatically.
/// Requires LimitedChargesComponent to function.
///
+// TODO: no reason this cant be predicted and server system deleted
[RegisterComponent, AutoGenerateComponentPause]
[Access(typeof(ChargesSystem))]
public sealed partial class AutoRechargeComponent : Component
diff --git a/Content.Server/Charges/Systems/ChargesSystem.cs b/Content.Server/Charges/Systems/ChargesSystem.cs
index 03e192e680e..974928ee4bb 100644
--- a/Content.Server/Charges/Systems/ChargesSystem.cs
+++ b/Content.Server/Charges/Systems/ChargesSystem.cs
@@ -37,15 +37,17 @@ protected override void OnExamine(EntityUid uid, LimitedChargesComponent comp, E
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(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(uid, out var recharge))
recharge.NextChargeTime = _timing.CurTime + recharge.RechargeDuration;
}
}
diff --git a/Content.Server/GameTicking/Rules/Components/NinjaRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/NinjaRuleComponent.cs
deleted file mode 100644
index fa352eb320b..00000000000
--- a/Content.Server/GameTicking/Rules/Components/NinjaRuleComponent.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using Content.Server.Ninja.Systems;
-using Content.Shared.Communications;
-using Content.Shared.Random;
-using Robust.Shared.Audio;
-using Robust.Shared.Prototypes;
-
-namespace Content.Server.GameTicking.Rules.Components;
-
-///
-/// Stores some configuration used by the ninja system.
-/// Objectives and roundend summary are handled by .
-///
-[RegisterComponent, Access(typeof(SpaceNinjaSystem))]
-public sealed partial class NinjaRuleComponent : Component
-{
- ///
- /// List of threats that can be called in. Copied onto when gloves are enabled.
- ///
- [DataField(required: true)]
- public ProtoId Threats = string.Empty;
-
- ///
- /// Sound played when making the player a ninja via antag control or ghost role
- ///
- [DataField]
- public SoundSpecifier? GreetingSound = new SoundPathSpecifier("/Audio/Misc/ninja_greeting.ogg");
-}
diff --git a/Content.Server/Item/ItemToggle/Components/ItemToggleDisarmMalusComponent.cs b/Content.Server/Item/ItemToggle/Components/ItemToggleDisarmMalusComponent.cs
deleted file mode 100644
index 30fa84ed90b..00000000000
--- a/Content.Server/Item/ItemToggle/Components/ItemToggleDisarmMalusComponent.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-namespace Content.Server.Item;
-
-///
-/// Handles whether this item applies a disarm malus when active.
-///
-[RegisterComponent]
-public sealed partial class ItemToggleDisarmMalusComponent : Component
-{
- ///
- /// 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.
- ///
- [ViewVariables(VVAccess.ReadOnly), DataField]
- public float? ActivatedDisarmMalus = null;
-
- ///
- /// 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.
- ///
- [ViewVariables(VVAccess.ReadOnly), DataField]
- public float? DeactivatedDisarmMalus = null;
-}
diff --git a/Content.Server/Item/ItemToggle/Components/ItemToggleSharpComponent.cs b/Content.Server/Item/ItemToggle/Components/ItemToggleSharpComponent.cs
deleted file mode 100644
index 227491b16c2..00000000000
--- a/Content.Server/Item/ItemToggle/Components/ItemToggleSharpComponent.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace Content.Server.Item;
-
-///
-/// Handles whether this item is sharp when toggled on.
-///
-[RegisterComponent]
-public sealed partial class ItemToggleSharpComponent : Component
-{
-}
diff --git a/Content.Server/Item/ItemToggle/ItemToggleSystem.cs b/Content.Server/Item/ItemToggle/ItemToggleSystem.cs
deleted file mode 100644
index f98415eb08f..00000000000
--- a/Content.Server/Item/ItemToggle/ItemToggleSystem.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-using Content.Server.CombatMode.Disarm;
-using Content.Server.Kitchen.Components;
-using Content.Shared.Item.ItemToggle;
-using Content.Shared.Item.ItemToggle.Components;
-
-namespace Content.Server.Item;
-
-public sealed class ItemToggleSystem : SharedItemToggleSystem
-{
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent(ToggleSharp);
- SubscribeLocalEvent(ToggleMalus);
- }
-
- private void ToggleSharp(Entity ent, ref ItemToggledEvent args)
- {
- // TODO generalize this into a "ToggleComponentComponent", though probably with a better name
- if (args.Activated)
- EnsureComp(ent);
- else
- RemCompDeferred(ent);
- }
-
- private void ToggleMalus(Entity ent, ref ItemToggledEvent args)
- {
- if (!TryComp(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;
- }
-}
diff --git a/Content.Server/Medical/Components/HealthAnalyzerComponent.cs b/Content.Server/Medical/Components/HealthAnalyzerComponent.cs
index f0b56cbd195..9d25db680ec 100644
--- a/Content.Server/Medical/Components/HealthAnalyzerComponent.cs
+++ b/Content.Server/Medical/Components/HealthAnalyzerComponent.cs
@@ -6,6 +6,9 @@ namespace Content.Server.Medical.Components;
///
/// After scanning, retrieves the target Uid to use with its related UI.
///
+///
+/// Requires ItemToggleComponent.
+///
[RegisterComponent, AutoGenerateComponentPause]
[Access(typeof(HealthAnalyzerSystem), typeof(CryoPodSystem))]
public sealed partial class HealthAnalyzerComponent : Component
diff --git a/Content.Server/Medical/DefibrillatorSystem.cs b/Content.Server/Medical/DefibrillatorSystem.cs
index 7b0b4110201..d0c650c4e84 100644
--- a/Content.Server/Medical/DefibrillatorSystem.cs
+++ b/Content.Server/Medical/DefibrillatorSystem.cs
@@ -12,6 +12,7 @@
using Content.Shared.Interaction;
using Content.Shared.Interaction.Components;
using Content.Shared.Interaction.Events;
+using Content.Shared.Item.ItemToggle;
using Content.Shared.Medical;
using Content.Shared.Mind;
using Content.Shared.Mobs;
@@ -37,6 +38,7 @@ public sealed class DefibrillatorSystem : EntitySystem
[Dependency] private readonly DoAfterSystem _doAfter = default!;
[Dependency] private readonly ElectrocutionSystem _electrocution = default!;
[Dependency] private readonly EuiManager _euiManager = default!;
+ [Dependency] private readonly ItemToggleSystem _toggle = default!;
[Dependency] private readonly RottingSystem _rotting = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly MobThresholdSystem _mobThreshold = default!;
@@ -50,30 +52,10 @@ public sealed class DefibrillatorSystem : EntitySystem
///
public override void Initialize()
{
- SubscribeLocalEvent(OnUseInHand);
- SubscribeLocalEvent(OnPowerCellSlotEmpty);
SubscribeLocalEvent(OnAfterInteract);
SubscribeLocalEvent(OnDoAfter);
}
- private void OnUseInHand(EntityUid uid, DefibrillatorComponent component, UseInHandEvent args)
- {
- if (args.Handled || !TryComp(uid, out UseDelayComponent? useDelay) || _useDelay.IsDelayed((uid, useDelay)))
- return;
-
- if (!TryToggle(uid, component, args.User))
- return;
-
- args.Handled = true;
- _useDelay.TryResetDelay((uid, useDelay));
- }
-
- private void OnPowerCellSlotEmpty(EntityUid uid, DefibrillatorComponent component, ref PowerCellSlotEmptyEvent args)
- {
- if (!TerminatingOrDeleted(uid))
- TryDisable(uid, component);
- }
-
private void OnAfterInteract(EntityUid uid, DefibrillatorComponent component, AfterInteractEvent args)
{
if (args.Handled || args.Target is not { } target)
@@ -96,54 +78,12 @@ private void OnDoAfter(EntityUid uid, DefibrillatorComponent component, Defibril
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);
@@ -250,7 +190,11 @@ public void Zap(EntityUid uid, EntityUid target, EntityUid user, DefibrillatorCo
// 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));
+ RaiseLocalEvent(target, ref ev);
}
public override void Update(float frameTime)
diff --git a/Content.Server/Medical/HealthAnalyzerSystem.cs b/Content.Server/Medical/HealthAnalyzerSystem.cs
index 11d2758cdd8..f284a214e65 100644
--- a/Content.Server/Medical/HealthAnalyzerSystem.cs
+++ b/Content.Server/Medical/HealthAnalyzerSystem.cs
@@ -14,6 +14,8 @@
using Content.Shared.DoAfter;
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.PowerCell;
@@ -32,6 +34,7 @@ public sealed class HealthAnalyzerSystem : EntitySystem
[Dependency] private readonly PowerCellSystem _cell = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly ItemToggleSystem _toggle = default!;
[Dependency] private readonly SharedBodySystem _bodySystem = default!;
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
@@ -42,7 +45,7 @@ public override void Initialize()
SubscribeLocalEvent(OnAfterInteract);
SubscribeLocalEvent(OnDoAfter);
SubscribeLocalEvent(OnInsertedIntoContainer);
- SubscribeLocalEvent(OnPowerCellSlotEmpty);
+ SubscribeLocalEvent(OnToggled);
SubscribeLocalEvent(OnDropped);
// Start-Shitmed
Subs.BuiEvents(HealthAnalyzerUiKey.Key, subs =>
@@ -132,16 +135,16 @@ private void OnDoAfter(Entity uid, ref HealthAnalyzerDo
private void OnInsertedIntoContainer(Entity uid, ref EntGotInsertedIntoContainerMessage args)
{
if (uid.Comp.ScannedEntity is { } patient)
- StopAnalyzingEntity(uid, patient);
+ _toggle.TryDeactivate(uid.Owner);
}
///
- /// Disable continuous updates once battery is dead
+ /// Disable continuous updates once turned off
///
- private void OnPowerCellSlotEmpty(Entity uid, ref PowerCellSlotEmptyEvent args)
+ private void OnToggled(Entity ent, ref ItemToggledEvent args)
{
- if (uid.Comp.ScannedEntity is { } patient)
- StopAnalyzingEntity(uid, patient);
+ if (!args.Activated && ent.Comp.ScannedEntity is { } patient)
+ StopAnalyzingEntity(ent, patient);
}
///
@@ -150,7 +153,7 @@ private void OnPowerCellSlotEmpty(Entity uid, ref Power
private void OnDropped(Entity uid, ref DroppedEvent args)
{
if (uid.Comp.ScannedEntity is { } patient)
- StopAnalyzingEntity(uid, patient);
+ _toggle.TryDeactivate(uid.Owner);
}
private void OpenUserInterface(EntityUid user, EntityUid analyzer)
@@ -171,7 +174,7 @@ private void BeginAnalyzingEntity(Entity healthAnalyzer
//Link the health analyzer to the scanned entity
healthAnalyzer.Comp.ScannedEntity = target;
healthAnalyzer.Comp.CurrentBodyPart = part;
- _cell.SetPowerCellDrawEnabled(healthAnalyzer, true);
+ _toggle.TryActivate(healthAnalyzer.Owner);
UpdateScannedUser(healthAnalyzer, target, true, part);
}
@@ -187,7 +190,7 @@ private void StopAnalyzingEntity(Entity healthAnalyzer,
healthAnalyzer.Comp.ScannedEntity = null;
healthAnalyzer.Comp.CurrentBodyPart = null;
- _cell.SetPowerCellDrawEnabled(target, false);
+ _toggle.TryDeactivate(healthAnalyzer.Owner);
UpdateScannedUser(healthAnalyzer, target, false);
}
diff --git a/Content.Server/Ninja/Events/BatteryChangedEvent.cs b/Content.Server/Ninja/Events/BatteryChangedEvent.cs
index 45bfedfee76..1848e881868 100644
--- a/Content.Server/Ninja/Events/BatteryChangedEvent.cs
+++ b/Content.Server/Ninja/Events/BatteryChangedEvent.cs
@@ -1,7 +1,7 @@
namespace Content.Server.Ninja.Events;
///
-/// Raised on the ninja when the suit has its powercell changed.
+/// Raised on the ninja and suit when the suit has its powercell changed.
///
[ByRefEvent]
public record struct NinjaBatteryChangedEvent(EntityUid Battery, EntityUid BatteryHolder);
diff --git a/Content.Server/Ninja/Systems/BatteryDrainerSystem.cs b/Content.Server/Ninja/Systems/BatteryDrainerSystem.cs
index 552ee0397fb..c4b5c6dc2bf 100644
--- a/Content.Server/Ninja/Systems/BatteryDrainerSystem.cs
+++ b/Content.Server/Ninja/Systems/BatteryDrainerSystem.cs
@@ -33,16 +33,17 @@ public override void Initialize()
/// Start do after for draining a power source.
/// Can't predict PNBC existing so only done on server.
///
- private void OnBeforeInteractHand(EntityUid uid, BatteryDrainerComponent comp, BeforeInteractHandEvent args)
+ private void OnBeforeInteractHand(Entity ent, ref BeforeInteractHandEvent args)
{
+ var (uid, comp) = ent;
var target = args.Target;
- if (args.Handled || comp.BatteryUid == null || !HasComp(target))
+ if (args.Handled || comp.BatteryUid is not {} battery || !HasComp(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;
@@ -60,23 +61,24 @@ private void OnBeforeInteractHand(EntityUid uid, BatteryDrainerComponent comp, B
_doAfter.TryStartDoAfter(doAfterArgs);
}
- private void OnBatteryChanged(EntityUid uid, BatteryDrainerComponent comp, ref NinjaBatteryChangedEvent args)
+ private void OnBatteryChanged(Entity ent, ref NinjaBatteryChangedEvent args)
{
- SetBattery(uid, args.Battery, comp);
+ SetBattery((ent, ent.Comp), args.Battery);
}
///
- protected override void OnDoAfterAttempt(EntityUid uid, BatteryDrainerComponent comp, DoAfterAttemptEvent args)
+ protected override void OnDoAfterAttempt(Entity ent, ref DoAfterAttemptEvent 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();
}
///
- protected override bool TryDrainPower(EntityUid uid, BatteryDrainerComponent comp, EntityUid target)
+ protected override bool TryDrainPower(Entity ent, EntityUid target)
{
+ var (uid, comp) = ent;
if (comp.BatteryUid == null || !TryComp(comp.BatteryUid.Value, out var battery))
return false;
@@ -99,6 +101,7 @@ protected override bool TryDrainPower(EntityUid uid, BatteryDrainerComponent com
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);
diff --git a/Content.Server/Ninja/Systems/ItemCreatorSystem.cs b/Content.Server/Ninja/Systems/ItemCreatorSystem.cs
new file mode 100644
index 00000000000..d7a7be995db
--- /dev/null
+++ b/Content.Server/Ninja/Systems/ItemCreatorSystem.cs
@@ -0,0 +1,57 @@
+using Content.Server.Ninja.Events;
+using Content.Server.Power.EntitySystems;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Ninja.Components;
+using Content.Shared.Ninja.Systems;
+using Content.Shared.Popups;
+
+namespace Content.Server.Ninja.Systems;
+
+public sealed class ItemCreatorSystem : SharedItemCreatorSystem
+{
+ [Dependency] private readonly BatterySystem _battery = default!;
+ [Dependency] private readonly SharedHandsSystem _hands = default!;
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnCreateItem);
+ SubscribeLocalEvent(OnBatteryChanged);
+ }
+
+ private void OnCreateItem(Entity 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 ent, ref NinjaBatteryChangedEvent args)
+ {
+ if (ent.Comp.Battery == args.Battery)
+ return;
+
+ ent.Comp.Battery = args.Battery;
+ Dirty(ent, ent.Comp);
+ }
+}
diff --git a/Content.Server/Ninja/Systems/NinjaGlovesSystem.cs b/Content.Server/Ninja/Systems/NinjaGlovesSystem.cs
index ac76ae6b771..3aaf7c5d58e 100644
--- a/Content.Server/Ninja/Systems/NinjaGlovesSystem.cs
+++ b/Content.Server/Ninja/Systems/NinjaGlovesSystem.cs
@@ -1,13 +1,8 @@
-using Content.Server.Communications;
-using Content.Server.Mind;
using Content.Server.Ninja.Events;
-using Content.Server.Objectives.Systems;
-using Content.Shared.Communications;
-using Content.Shared.CriminalRecords.Components;
+using Content.Shared.Mind;
+using Content.Shared.Objectives.Systems;
using Content.Shared.Ninja.Components;
using Content.Shared.Ninja.Systems;
-using Content.Shared.Research.Components;
-using Content.Shared.Toggleable;
namespace Content.Server.Ninja.Systems;
@@ -16,89 +11,44 @@ namespace Content.Server.Ninja.Systems;
///
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 ent, Entity user)
{
- base.Initialize();
+ base.EnableGloves(ent, user);
- SubscribeLocalEvent(OnToggleAction);
- }
-
- ///
- /// Toggle gloves, if the user is a ninja wearing a ninja suit.
- ///
- 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(user, out var ninja)
- || ninja.Suit == null
- || !HasComp(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(user);
- var stun = EnsureComp(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(user);
- _emagProvider.SetWhitelist(user, comp.DoorjackWhitelist, emag);
-
- EnsureComp(user);
- // prevent calling in multiple threats by toggling gloves after
- if (!_codeCondition.IsCompleted(user, ninja.TerrorObjective))
- {
- var hacker = EnsureComp(user);
- var rule = _ninja.NinjaRule(user);
- if (rule != null)
- _commsHacker.SetThreats(user, rule.Threats, hacker);
- }
- if (!_codeCondition.IsCompleted(user, ninja.MassArrestObjective))
- {
- EnsureComp(user);
- }
}
}
diff --git a/Content.Server/Ninja/Systems/NinjaSuitSystem.cs b/Content.Server/Ninja/Systems/NinjaSuitSystem.cs
index 04095b549c6..63054eaad50 100644
--- a/Content.Server/Ninja/Systems/NinjaSuitSystem.cs
+++ b/Content.Server/Ninja/Systems/NinjaSuitSystem.cs
@@ -2,7 +2,6 @@
using Content.Server.Ninja.Events;
using Content.Server.Power.Components;
using Content.Server.PowerCell;
-using Content.Shared.Clothing.EntitySystems;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Ninja.Components;
using Content.Shared.Ninja.Systems;
@@ -29,15 +28,13 @@ public override void Initialize()
SubscribeLocalEvent(OnSuitInsertAttempt);
SubscribeLocalEvent(OnEmpAttempt);
- SubscribeLocalEvent(OnAttemptStealth);
- SubscribeLocalEvent(OnCreateThrowingStar);
SubscribeLocalEvent(OnRecallKatana);
SubscribeLocalEvent(OnEmp);
}
- protected override void NinjaEquippedSuit(EntityUid uid, NinjaSuitComponent comp, EntityUid user, SpaceNinjaComponent ninja)
+ protected override void NinjaEquipped(Entity ent, Entity user)
{
- base.NinjaEquippedSuit(uid, comp, user, ninja);
+ base.NinjaEquipped(ent, user);
_ninja.SetSuitPowerAlert(user);
}
@@ -57,16 +54,15 @@ private void OnSuitInsertAttempt(EntityUid uid, NinjaSuitComponent comp, Contain
// 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(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(user))
+ if (!_ninja.IsNinja(user))
return;
var ev = new NinjaBatteryChangedEvent(args.EntityUid, uid);
+ RaiseLocalEvent(uid, ref ev);
RaiseLocalEvent(user, ref ev);
}
@@ -77,64 +73,22 @@ private void OnEmpAttempt(EntityUid uid, NinjaSuitComponent comp, EmpAttemptEven
args.Cancel();
}
- protected override void UserUnequippedSuit(EntityUid uid, NinjaSuitComponent comp, EntityUid user)
+ protected override void UserUnequippedSuit(Entity ent, Entity 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 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(user, out var ninja) || ninja.Katana == null)
- return;
var katana = ninja.Katana.Value;
var coords = _transform.GetWorldPosition(katana);
@@ -146,11 +100,8 @@ private void OnRecallKatana(EntityUid uid, NinjaSuitComponent comp, RecallKatana
return;
}
- if (comp.DisableCooldown > GameTiming.CurTime)
- {
- Popup.PopupEntity(Loc.GetString("ninja-suit-cooldown"), user, user, PopupType.Medium);
+ if (CheckDisabled(ent, user))
return;
- }
// TODO: teleporting into belt slot
var message = _hands.TryPickupAnyHand(user, katana)
@@ -159,9 +110,11 @@ private void OnRecallKatana(EntityUid uid, NinjaSuitComponent comp, RecallKatana
Popup.PopupEntity(Loc.GetString(message), user, user);
}
- private void OnEmp(EntityUid uid, NinjaSuitComponent comp, NinjaEmpEvent args)
+ private void OnEmp(Entity ent, ref NinjaEmpEvent args)
{
+ var (uid, comp) = ent;
args.Handled = true;
+
var user = args.Performer;
if (!_ninja.TryUseCharge(user, comp.EmpCharge))
{
@@ -169,13 +122,9 @@ private void OnEmp(EntityUid uid, NinjaSuitComponent comp, NinjaEmpEvent args)
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);
}
diff --git a/Content.Server/Ninja/Systems/SpaceNinjaSystem.cs b/Content.Server/Ninja/Systems/SpaceNinjaSystem.cs
index 0c1e88653fa..28ab6332276 100644
--- a/Content.Server/Ninja/Systems/SpaceNinjaSystem.cs
+++ b/Content.Server/Ninja/Systems/SpaceNinjaSystem.cs
@@ -2,7 +2,6 @@
using Content.Server.Chat.Managers;
using Content.Server.CriminalRecords.Systems;
using Content.Server.GameTicking.Rules.Components;
-using Content.Server.GenericAntag;
using Content.Server.Objectives.Components;
using Content.Server.Objectives.Systems;
using Content.Server.Power.Components;
@@ -11,7 +10,6 @@
using Content.Server.Research.Systems;
using Content.Server.Roles;
using Content.Shared.Alert;
-using Content.Shared.Clothing.EntitySystems;
using Content.Shared.Doors.Components;
using Content.Shared.IdentityManagement;
using Content.Shared.Mind;
@@ -26,11 +24,6 @@
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
-
///
/// Main ninja system that handles ninja setup, provides helper methods for the rest of the code to use.
///
@@ -44,13 +37,11 @@ public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem
[Dependency] private readonly RoleSystem _role = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedMindSystem _mind = default!;
- [Dependency] private readonly StealthClothingSystem _stealthClothing = default!;
public override void Initialize()
{
base.Initialize();
- SubscribeLocalEvent(OnNinjaCreated);
SubscribeLocalEvent(OnDoorjack);
SubscribeLocalEvent(OnResearchStolen);
SubscribeLocalEvent(OnThreatCalledIn);
@@ -62,7 +53,7 @@ public override void Update(float frameTime)
var query = EntityQueryEnumerator();
while (query.MoveNext(out var uid, out var ninja))
{
- UpdateNinja(uid, ninja, frameTime);
+ SetSuitPowerAlert((uid, ninja));
}
}
@@ -80,31 +71,13 @@ private int Download(EntityUid uid, List ids)
return newCount - oldCount;
}
- ///
- /// Returns a ninja's gamerule config data.
- /// If the gamerule was not started then it will be started automatically.
- ///
- 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(comp.RuleEntity);
- }
-
// TODO: can probably copy paste borg code here
///
/// Update the alert for the ninja's suit power indicator.
///
- public void SetSuitPowerAlert(EntityUid uid, SpaceNinjaComponent? comp = null)
+ public void SetSuitPowerAlert(Entity ent)
{
- if (!Resolve(uid, ref comp, false))
- return;
-
+ var (uid, comp) = ent;
if (comp.Deleted || comp.Suit == null)
{
_alerts.ClearAlert(uid, comp.SuitPowerAlert);
@@ -145,53 +118,6 @@ public override bool TryUseCharge(EntityUid user, float charge)
return GetNinjaBattery(user, out var uid, out var battery) && _battery.TryUseCharge(uid.Value, charge, battery);
}
- ///
- /// Set up everything for ninja to work and send the greeting message/sound.
- /// Objectives are added by .
- ///
- 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
- ///
- /// Handle constant power drains from passive usage and cloak.
- ///
- 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);
- }
- }
-
///
/// Increment greentext when emagging a door.
///
diff --git a/Content.Server/Ninja/Systems/SpiderChargeSystem.cs b/Content.Server/Ninja/Systems/SpiderChargeSystem.cs
index 64c958d6f1a..c262651f27a 100644
--- a/Content.Server/Ninja/Systems/SpiderChargeSystem.cs
+++ b/Content.Server/Ninja/Systems/SpiderChargeSystem.cs
@@ -7,6 +7,7 @@
using Content.Server.Sticky.Events;
using Content.Shared.Interaction;
using Content.Shared.Ninja.Components;
+using Content.Shared.Ninja.Systems;
using Robust.Shared.GameObjects;
namespace Content.Server.Ninja.Systems;
@@ -14,7 +15,7 @@ namespace Content.Server.Ninja.Systems;
///
/// Prevents planting a spider charge outside of its location and handles greentext.
///
-public sealed class SpiderChargeSystem : EntitySystem
+public sealed class SpiderChargeSystem : SharedSpiderChargeSystem
{
[Dependency] private readonly MindSystem _mind = default!;
[Dependency] private readonly PopupSystem _popup = default!;
diff --git a/Content.Server/Ninja/Systems/StunProviderSystem.cs b/Content.Server/Ninja/Systems/StunProviderSystem.cs
index 970ca78e2cc..822486cff52 100644
--- a/Content.Server/Ninja/Systems/StunProviderSystem.cs
+++ b/Content.Server/Ninja/Systems/StunProviderSystem.cs
@@ -6,9 +6,11 @@
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 Robust.Shared.Prototypes;
namespace Content.Server.Ninja.Systems;
@@ -19,11 +21,12 @@ public sealed class StunProviderSystem : SharedStunProviderSystem
{
[Dependency] private readonly BatterySystem _battery = default!;
[Dependency] private readonly DamageableSystem _damageable = default!;
- [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedNinjaGlovesSystem _gloves = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedStunSystem _stun = default!;
+ [Dependency] private readonly UseDelaySystem _useDelay = default!;
public override void Initialize()
{
@@ -36,16 +39,18 @@ public override void Initialize()
///
/// Stun clicked mobs on the whitelist, if there is enough power.
///
- private void OnBeforeInteractHand(EntityUid uid, StunProviderComponent comp, BeforeInteractHandEvent args)
+ private void OnBeforeInteractHand(Entity 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 || !comp.Whitelist.IsValid(target, EntityManager))
+ if (target == uid || _whitelist.IsWhitelistFail(comp.Whitelist, target))
return;
- if (_timing.CurTime < comp.NextStun)
+ var useDelay = EnsureComp(uid);
+ if (_useDelay.IsDelayed((uid, useDelay), id: comp.DelayId))
return;
// take charge from battery
@@ -61,13 +66,14 @@ private void OnBeforeInteractHand(EntityUid uid, StunProviderComponent comp, Bef
_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 ent, ref NinjaBatteryChangedEvent args)
{
- SetBattery(uid, args.Battery, comp);
+ SetBattery((ent, ent.Comp), args.Battery);
}
}
diff --git a/Content.Server/Objectives/Systems/CodeConditionSystem.cs b/Content.Server/Objectives/Systems/CodeConditionSystem.cs
index 7ba312f4bb9..fbc58dafe82 100644
--- a/Content.Server/Objectives/Systems/CodeConditionSystem.cs
+++ b/Content.Server/Objectives/Systems/CodeConditionSystem.cs
@@ -35,20 +35,6 @@ public bool IsCompleted(Entity ent)
return ent.Comp.Completed;
}
- ///
- /// Returns true if a mob's objective with a certain prototype is completed.
- ///
- public bool IsCompleted(Entity 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);
- }
-
///
/// Sets an objective's completed field.
///
diff --git a/Content.Server/PowerCell/PowerCellSystem.Draw.cs b/Content.Server/PowerCell/PowerCellSystem.Draw.cs
index 4155a4f6bec..9ebd677f473 100644
--- a/Content.Server/PowerCell/PowerCellSystem.Draw.cs
+++ b/Content.Server/PowerCell/PowerCellSystem.Draw.cs
@@ -1,4 +1,5 @@
using Content.Server.Power.Components;
+using Content.Shared.Item.ItemToggle.Components;
using Content.Shared.PowerCell;
using Content.Shared.PowerCell.Components;
@@ -10,22 +11,20 @@ public sealed partial class PowerCellSystem
* Handles PowerCellDraw
*/
- private static readonly TimeSpan Delay = TimeSpan.FromSeconds(1);
-
public override void Update(float frameTime)
{
base.Update(frameTime);
- var query = EntityQueryEnumerator();
+ var query = EntityQueryEnumerator();
- while (query.MoveNext(out var uid, out var comp, out var slot))
+ while (query.MoveNext(out var uid, out var comp, out var slot, out var toggle))
{
- if (!comp.Drawing)
+ if (!comp.Enabled || !toggle.Activated)
continue;
if (Timing.CurTime < comp.NextUpdateTime)
continue;
- comp.NextUpdateTime += Delay;
+ comp.NextUpdateTime += comp.Delay;
if (!TryGetBatteryFromSlot(uid, out var batteryEnt, out var battery, slot))
continue;
@@ -33,7 +32,8 @@ public override void Update(float frameTime)
if (_battery.TryUseCharge(batteryEnt.Value, comp.DrawRate, battery))
continue;
- comp.Drawing = false;
+ Toggle.TryDeactivate((uid, toggle));
+
var ev = new PowerCellSlotEmptyEvent();
RaiseLocalEvent(uid, ref ev);
}
@@ -42,26 +42,9 @@ public override void Update(float frameTime)
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)
{
@@ -76,6 +59,9 @@ private void OnDrawCellChanged(EntityUid uid, PowerCellDrawComponent component,
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;
diff --git a/Content.Server/PowerCell/PowerCellSystem.cs b/Content.Server/PowerCell/PowerCellSystem.cs
index f45a01b2e1b..0d2d9012bc7 100644
--- a/Content.Server/PowerCell/PowerCellSystem.cs
+++ b/Content.Server/PowerCell/PowerCellSystem.cs
@@ -39,8 +39,8 @@ public override void Initialize()
SubscribeLocalEvent(OnDrawChargeChanged);
SubscribeLocalEvent(OnDrawCellChanged);
- // funny
SubscribeLocalEvent(OnCellSlotExamined);
+ // funny
SubscribeLocalEvent(OnSlotMicrowaved);
}
diff --git a/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs b/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs
index 623b6ba9787..0feecad4d08 100644
--- a/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs
+++ b/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs
@@ -30,7 +30,7 @@ private void OnModuleGotInserted(EntityUid uid, BorgModuleComponent component, E
if (!TryComp(chassis, out var chassisComp) ||
args.Container != chassisComp.ModuleContainer ||
- !chassisComp.Activated)
+ !Toggle.IsActivated(chassis))
return;
if (!_powerCell.HasDrawCharge(uid))
@@ -144,6 +144,7 @@ public void SelectModule(EntityUid chassis,
var ev = new BorgModuleSelectedEvent(chassis);
RaiseLocalEvent(moduleUid, ref ev);
chassisComp.SelectedModule = moduleUid;
+ Dirty(chassis, chassisComp);
}
///
@@ -163,6 +164,7 @@ public void UnselectModule(EntityUid chassis, BorgChassisComponent? chassisComp
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)
diff --git a/Content.Server/Silicons/Borgs/BorgSystem.cs b/Content.Server/Silicons/Borgs/BorgSystem.cs
index b6e5adbe2d4..b8546f30e7d 100644
--- a/Content.Server/Silicons/Borgs/BorgSystem.cs
+++ b/Content.Server/Silicons/Borgs/BorgSystem.cs
@@ -11,6 +11,7 @@
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;
@@ -75,6 +76,7 @@ public override void Initialize()
SubscribeLocalEvent(OnPowerCellSlotEmpty);
SubscribeLocalEvent(OnUIOpenAttempt);
SubscribeLocalEvent(OnGetDeadIC);
+ SubscribeLocalEvent(OnToggled);
SubscribeLocalEvent(OnBrainMindAdded);
SubscribeLocalEvent(OnBrainPointAttempt);
@@ -175,11 +177,11 @@ private void OnMobStateChanged(EntityUid uid, BorgChassisComponent component, Mo
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);
}
}
@@ -187,24 +189,10 @@ private void OnPowerCellChanged(EntityUid uid, BorgChassisComponent component, P
{
UpdateBatteryAlert((uid, component));
- if (!TryComp(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);
@@ -212,7 +200,7 @@ private void OnPowerCellChanged(EntityUid uid, BorgChassisComponent component, P
private void OnPowerCellSlotEmpty(EntityUid uid, BorgChassisComponent component, ref PowerCellSlotEmptyEvent args)
{
- DisableBorgAbilities(uid, component);
+ Toggle.TryDeactivate(uid);
UpdateUI(uid, component);
}
@@ -228,6 +216,23 @@ private void OnGetDeadIC(EntityUid uid, BorgChassisComponent component, ref GetC
args.Dead = true;
}
+ private void OnToggled(Entity 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))
@@ -280,44 +285,14 @@ private void UpdateBatteryAlert(Entity ent, PowerCellSlotC
_alerts.ShowAlert(ent, ent.Comp.BatteryAlert, chargePercent);
}
- ///
- /// Activates the borg, enabling all of its modules.
- ///
- 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);
- }
-
- ///
- /// Deactivates the borg, disabling all of its modules and decreasing its speed.
- ///
- public void DisableBorgAbilities(EntityUid uid, BorgChassisComponent component)
- {
- if (!component.Activated)
- return;
-
- component.Activated = false;
- DisableAllModules(uid, component);
- Dirty(uid, component);
- _movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
- }
-
///
/// Activates a borg when a player occupies it
///
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);
}
///
@@ -326,10 +301,8 @@ public void BorgActivate(EntityUid uid, BorgChassisComponent component)
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);
}
///
diff --git a/Content.Server/StationEvents/Components/NinjaSpawnRuleComponent.cs b/Content.Server/StationEvents/Components/NinjaSpawnRuleComponent.cs
deleted file mode 100644
index d758247eca2..00000000000
--- a/Content.Server/StationEvents/Components/NinjaSpawnRuleComponent.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using Content.Server.StationEvents.Events;
-
-namespace Content.Server.StationEvents.Components;
-
-///
-/// Configuration component for the Space Ninja antag.
-///
-[RegisterComponent, Access(typeof(NinjaSpawnRule))]
-public sealed partial class NinjaSpawnRuleComponent : Component
-{
- ///
- /// Distance that the ninja spawns from the station's half AABB radius
- ///
- [DataField("spawnDistance")]
- public float SpawnDistance = 20f;
-}
diff --git a/Content.Server/StationEvents/Components/SpaceSpawnRuleComponent.cs b/Content.Server/StationEvents/Components/SpaceSpawnRuleComponent.cs
new file mode 100644
index 00000000000..a0168077fd6
--- /dev/null
+++ b/Content.Server/StationEvents/Components/SpaceSpawnRuleComponent.cs
@@ -0,0 +1,25 @@
+using Content.Server.StationEvents.Events;
+using Robust.Shared.Map;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.StationEvents.Components;
+
+///
+/// Component for spawning antags in space around a station.
+/// Requires AntagSelectionComponent.
+///
+[RegisterComponent, Access(typeof(SpaceSpawnRule))]
+public sealed partial class SpaceSpawnRuleComponent : Component
+{
+ ///
+ /// Distance that the entity spawns from the station's half AABB radius
+ ///
+ [DataField]
+ public float SpawnDistance = 20f;
+
+ ///
+ /// Location that was picked.
+ ///
+ [DataField]
+ public MapCoordinates? Coords;
+}
diff --git a/Content.Server/StationEvents/Events/NinjaSpawnRule.cs b/Content.Server/StationEvents/Events/SpaceSpawnRule.cs
similarity index 53%
rename from Content.Server/StationEvents/Events/NinjaSpawnRule.cs
rename to Content.Server/StationEvents/Events/SpaceSpawnRule.cs
index 9cbc193ce61..6fccaaa5cfc 100644
--- a/Content.Server/StationEvents/Events/NinjaSpawnRule.cs
+++ b/Content.Server/StationEvents/Events/SpaceSpawnRule.cs
@@ -1,5 +1,5 @@
+using Content.Server.Antag;
using Content.Server.GameTicking.Rules.Components;
-using Content.Server.Ninja.Systems;
using Content.Server.Station.Components;
using Content.Server.StationEvents.Components;
using Content.Shared.GameTicking.Components;
@@ -9,18 +9,28 @@
namespace Content.Server.StationEvents.Events;
///
-/// Event for spawning a Space Ninja mid-game.
+/// Station event component for spawning this rules antags in space around a station.
///
-public sealed class NinjaSpawnRule : StationEventSystem
+public sealed class SpaceSpawnRule : StationEventSystem
{
[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(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(station.Value);
@@ -28,22 +38,28 @@ protected override void Started(EntityUid uid, NinjaSpawnRuleComponent comp, Gam
var gridUid = StationSystem.GetLargestGrid(stationData);
if (gridUid == null || !TryComp(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 ent, ref AntagSelectLocationEvent args)
+ {
+ if (ent.Comp.Coords is {} coords)
+ args.Coordinates.Add(coords);
}
}
diff --git a/Content.Server/Stunnable/Systems/StunbatonSystem.cs b/Content.Server/Stunnable/Systems/StunbatonSystem.cs
index c1782efabaf..97dd2c7e735 100644
--- a/Content.Server/Stunnable/Systems/StunbatonSystem.cs
+++ b/Content.Server/Stunnable/Systems/StunbatonSystem.cs
@@ -19,7 +19,7 @@ public sealed class StunbatonSystem : SharedStunbatonSystem
[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()
{
diff --git a/Content.Server/Weapons/Misc/TetherGunSystem.cs b/Content.Server/Weapons/Misc/TetherGunSystem.cs
index f6aafe376d6..2bf53d46f4b 100644
--- a/Content.Server/Weapons/Misc/TetherGunSystem.cs
+++ b/Content.Server/Weapons/Misc/TetherGunSystem.cs
@@ -1,4 +1,5 @@
using Content.Server.PowerCell;
+using Content.Shared.Item.ItemToggle;
using Content.Shared.PowerCell;
using Content.Shared.Weapons.Misc;
using Robust.Shared.Physics.Components;
@@ -8,6 +9,7 @@ namespace Content.Server.Weapons.Misc;
public sealed class TetherGunSystem : SharedTetherGunSystem
{
[Dependency] private readonly PowerCellSystem _cell = default!;
+ [Dependency] private readonly ItemToggleSystem _toggle = default!;
public override void Initialize()
{
@@ -36,12 +38,12 @@ protected override void StartTether(EntityUid gunUid, BaseForceGunComponent comp
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);
}
}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactMagnetTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactMagnetTriggerSystem.cs
index c6f56a27508..a585a9ef452 100644
--- a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactMagnetTriggerSystem.cs
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactMagnetTriggerSystem.cs
@@ -2,6 +2,7 @@
using Content.Server.Salvage;
using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
using Content.Shared.Clothing;
+using Content.Shared.Item.ItemToggle.Components;
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
@@ -29,11 +30,11 @@ public override void Update(float frameTime)
_toActivate.Clear();
- //assume that there's more instruments than artifacts
- var query = EntityQueryEnumerator();
- while (query.MoveNext(out _, out var magboot, out var magXform))
+ //assume that there's more magboots than artifacts
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out _, out var magboot, out var magXform, out var toggle))
{
- if (!magboot.On)
+ if (!toggle.Activated)
continue;
var artiQuery = EntityQueryEnumerator();
diff --git a/Content.Shared/Access/Components/AccessToggleComponent.cs b/Content.Shared/Access/Components/AccessToggleComponent.cs
new file mode 100644
index 00000000000..60a606ac7ea
--- /dev/null
+++ b/Content.Shared/Access/Components/AccessToggleComponent.cs
@@ -0,0 +1,11 @@
+using Content.Shared.Access.Systems;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Access.Components;
+
+///
+/// Toggles an access provider with ItemToggle.
+/// Requires .
+///
+[RegisterComponent, NetworkedComponent, Access(typeof(AccessToggleSystem))]
+public sealed partial class AccessToggleComponent : Component;
diff --git a/Content.Shared/Access/Systems/AccessToggleSystem.cs b/Content.Shared/Access/Systems/AccessToggleSystem.cs
new file mode 100644
index 00000000000..564aca06812
--- /dev/null
+++ b/Content.Shared/Access/Systems/AccessToggleSystem.cs
@@ -0,0 +1,21 @@
+using Content.Shared.Access.Components;
+using Content.Shared.Item.ItemToggle.Components;
+
+namespace Content.Shared.Access.Systems;
+
+public sealed class AccessToggleSystem : EntitySystem
+{
+ [Dependency] private readonly SharedAccessSystem _access = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnToggled);
+ }
+
+ private void OnToggled(Entity ent, ref ItemToggledEvent args)
+ {
+ _access.SetAccessEnabled(ent, args.Activated);
+ }
+}
diff --git a/Content.Shared/Beeper/Components/BeeperComponent.cs b/Content.Shared/Beeper/Components/BeeperComponent.cs
index 54d242709c4..f6efbb10f3e 100644
--- a/Content.Shared/Beeper/Components/BeeperComponent.cs
+++ b/Content.Shared/Beeper/Components/BeeperComponent.cs
@@ -10,15 +10,12 @@ namespace Content.Shared.Beeper.Components;
/// This is used for an item that beeps based on
/// proximity to a specified component.
///
+///
+/// Requires ItemToggleComponent to control it.
+///
[RegisterComponent, NetworkedComponent, Access(typeof(BeeperSystem)), AutoGenerateComponentState]
public sealed partial class BeeperComponent : Component
{
- ///
- /// Whether or not it's on.
- ///
- [DataField, AutoNetworkedField]
- public bool Enabled = true;
-
///
/// How much to scale the interval by (< 0 = min, > 1 = max)
///
@@ -56,7 +53,7 @@ public sealed partial class BeeperComponent : Component
/// Is the beep muted
///
[DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
- public bool IsMuted = false;
+ public bool IsMuted;
///
/// The sound played when the locator beeps.
diff --git a/Content.Shared/Beeper/Systems/BeeperSystem.cs b/Content.Shared/Beeper/Systems/BeeperSystem.cs
index c51eef4da9d..a52e19f7552 100644
--- a/Content.Shared/Beeper/Systems/BeeperSystem.cs
+++ b/Content.Shared/Beeper/Systems/BeeperSystem.cs
@@ -1,5 +1,7 @@
using Content.Shared.Beeper.Components;
using Content.Shared.FixedPoint;
+using Content.Shared.Item.ItemToggle;
+using Content.Shared.Item.ItemToggle.Components;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Network;
using Robust.Shared.Timing;
@@ -11,34 +13,20 @@ namespace Content.Shared.Beeper.Systems;
public sealed class BeeperSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
- [Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly INetManager _net = default!;
-
- public override void Initialize()
- {
- }
+ [Dependency] private readonly ItemToggleSystem _toggle = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
public override void Update(float frameTime)
{
- var query = EntityQueryEnumerator();
- while (query.MoveNext(out var uid, out var beeper))
+ var query = EntityQueryEnumerator();
+ 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);
@@ -70,6 +58,7 @@ public void SetMute(EntityUid owner, bool isMuted, BeeperComponent? comp = null)
if (!Resolve(owner, ref comp))
return;
comp.IsMuted = isMuted;
+ Dirty(owner, comp);
}
private void UpdateBeepInterval(EntityUid owner, BeeperComponent beeper)
@@ -91,19 +80,17 @@ public void ForceUpdate(EntityUid owner, BeeperComponent? beeper = null)
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;
}
}
diff --git a/Content.Shared/Beeper/Systems/ProximityBeeperSystem.cs b/Content.Shared/Beeper/Systems/ProximityBeeperSystem.cs
index bd857d4c29b..ed3c6366c13 100644
--- a/Content.Shared/Beeper/Systems/ProximityBeeperSystem.cs
+++ b/Content.Shared/Beeper/Systems/ProximityBeeperSystem.cs
@@ -1,7 +1,6 @@
using Content.Shared.Beeper.Components;
-using Content.Shared.Interaction.Events;
+using Content.Shared.Item.ItemToggle;
using Content.Shared.Pinpointer;
-using Content.Shared.PowerCell;
using Content.Shared.ProximityDetection;
using Content.Shared.ProximityDetection.Components;
using Content.Shared.ProximityDetection.Systems;
@@ -9,20 +8,17 @@
namespace Content.Shared.Beeper.Systems;
///
-/// This handles logic for implementing proximity beeper as a handheld tool />
+/// This handles controlling a beeper from proximity detector events.
///
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!;
///
public override void Initialize()
{
- SubscribeLocalEvent(OnUseInHand);
- SubscribeLocalEvent(OnPowerCellSlotEmpty);
SubscribeLocalEvent(OnNewProximityTarget);
SubscribeLocalEvent(OnProximityTargetUpdate);
}
@@ -33,82 +29,16 @@ private void OnProximityTargetUpdate(EntityUid owner, ProximityBeeperComponent p
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);
- }
-
-
- ///
- /// Disables the proximity beeper
- ///
- 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);
- }
-
- ///
- /// toggles the proximity beeper
- ///
- 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);
}
}
diff --git a/Content.Shared/Charges/Systems/SharedChargesSystem.cs b/Content.Shared/Charges/Systems/SharedChargesSystem.cs
index 5de1383cde0..7f95ef184e4 100644
--- a/Content.Shared/Charges/Systems/SharedChargesSystem.cs
+++ b/Content.Shared/Charges/Systems/SharedChargesSystem.cs
@@ -5,10 +5,14 @@ namespace Content.Shared.Charges.Systems;
public abstract class SharedChargesSystem : EntitySystem
{
+ protected EntityQuery Query;
+
public override void Initialize()
{
base.Initialize();
+ Query = GetEntityQuery();
+
SubscribeLocalEvent(OnExamine);
}
@@ -30,9 +34,9 @@ protected virtual void OnExamine(EntityUid uid, LimitedChargesComponent comp, Ex
///
/// Tries to add a number of charges. If it over or underflows it will be clamped, wasting the extra charges.
///
- public void AddCharges(EntityUid uid, int change, LimitedChargesComponent? comp = null)
+ public virtual void AddCharges(EntityUid uid, int change, LimitedChargesComponent? comp = null)
{
- if (!Resolve(uid, ref comp, false))
+ if (!Query.Resolve(uid, ref comp, false))
return;
var old = comp.Charges;
@@ -47,7 +51,7 @@ public void AddCharges(EntityUid uid, int change, LimitedChargesComponent? comp
public bool IsEmpty(EntityUid uid, LimitedChargesComponent? comp = null)
{
// can't be empty if there are no limited charges
- if (!Resolve(uid, ref comp, false))
+ if (!Query.Resolve(uid, ref comp, false))
return false;
return comp.Charges <= 0;
@@ -56,10 +60,24 @@ public bool IsEmpty(EntityUid uid, LimitedChargesComponent? comp = null)
///
/// Uses a single charge. Must check IsEmpty beforehand to prevent using with 0 charge.
///
- public virtual void UseCharge(EntityUid uid, LimitedChargesComponent? comp = null)
+ public void UseCharge(EntityUid uid, LimitedChargesComponent? comp = null)
+ {
+ AddCharges(uid, -1, comp);
+ }
+
+ ///
+ /// Checks IsEmpty and uses a charge if it isn't empty.
+ ///
+ public bool TryUseCharge(Entity 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;
}
///
@@ -80,7 +98,6 @@ public bool HasInsufficientCharges(EntityUid uid, int requiredCharges, LimitedCh
///
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);
}
}
diff --git a/Content.Shared/Clothing/ClothingSpeedModifierComponent.cs b/Content.Shared/Clothing/ClothingSpeedModifierComponent.cs
index c3c4baf19d0..866ce38a572 100644
--- a/Content.Shared/Clothing/ClothingSpeedModifierComponent.cs
+++ b/Content.Shared/Clothing/ClothingSpeedModifierComponent.cs
@@ -3,20 +3,18 @@
namespace Content.Shared.Clothing;
+///
+/// Modifies speed when worn and activated.
+/// Supports ItemToggleComponent.
+///
[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;
-
- ///
- /// Is this clothing item currently 'actively' slowing you down?
- /// e.g. magboots can be turned on and off.
- ///
- [DataField("enabled")] public bool Enabled = true;
}
[Serializable, NetSerializable]
@@ -25,12 +23,9 @@ public sealed class ClothingSpeedModifierComponentState : ComponentState
public float WalkModifier;
public float SprintModifier;
- public bool Enabled;
-
- public ClothingSpeedModifierComponentState(float walkModifier, float sprintModifier, bool enabled)
+ public ClothingSpeedModifierComponentState(float walkModifier, float sprintModifier)
{
WalkModifier = walkModifier;
SprintModifier = sprintModifier;
- Enabled = enabled;
}
}
diff --git a/Content.Shared/Clothing/ClothingSpeedModifierSystem.cs b/Content.Shared/Clothing/ClothingSpeedModifierSystem.cs
index 66ff5c624a4..c89ab827e67 100644
--- a/Content.Shared/Clothing/ClothingSpeedModifierSystem.cs
+++ b/Content.Shared/Clothing/ClothingSpeedModifierSystem.cs
@@ -1,11 +1,10 @@
-using Content.Shared.Actions;
using Content.Shared.Clothing.Components;
using Content.Shared.Examine;
-using Content.Shared.IdentityManagement;
using Content.Shared.Inventory;
+using Content.Shared.Item.ItemToggle;
+using Content.Shared.Item.ItemToggle.Components;
using Content.Shared.Movement.Systems;
using Content.Shared.PowerCell;
-using Content.Shared.Toggleable;
using Content.Shared.Verbs;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
@@ -15,12 +14,12 @@ namespace Content.Shared.Clothing;
public sealed class ClothingSpeedModifierSystem : EntitySystem
{
- [Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly ClothingSpeedModifierSystem _clothingSpeedModifier = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly ExamineSystemShared _examine = default!;
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeed = default!;
+ [Dependency] private readonly ItemToggleSystem _toggle = default!;
[Dependency] private readonly SharedPowerCellSystem _powerCell = default!;
public override void Initialize()
@@ -31,39 +30,12 @@ public override void Initialize()
SubscribeLocalEvent(OnHandleState);
SubscribeLocalEvent>(OnRefreshMoveSpeed);
SubscribeLocalEvent>(OnClothingVerbExamine);
-
- SubscribeLocalEvent>(AddToggleVerb);
- SubscribeLocalEvent(OnGetActions);
- SubscribeLocalEvent(OnToggleSpeed);
- SubscribeLocalEvent(OnMapInit);
- SubscribeLocalEvent(OnPowerCellSlotEmpty);
+ SubscribeLocalEvent(OnToggled);
}
- // Public API
-
- public void SetClothingSpeedModifierEnabled(EntityUid uid, bool enabled, ClothingSpeedModifierComponent? component = null)
- {
- if (!Resolve(uid, ref component, false))
- return;
-
- if (component.Enabled != enabled)
- {
- component.Enabled = enabled;
- Dirty(uid, component);
-
- // inventory system will automatically hook into the event raised by this and update accordingly
- if (_container.TryGetContainingContainer(uid, out var container))
- {
- _movementSpeed.RefreshMovementSpeedModifiers(container.Owner);
- }
- }
- }
-
- // Event handlers
-
private void OnGetState(EntityUid uid, ClothingSpeedModifierComponent component, ref ComponentGetState args)
{
- args.State = new ClothingSpeedModifierComponentState(component.WalkModifier, component.SprintModifier, component.Enabled);
+ args.State = new ClothingSpeedModifierComponentState(component.WalkModifier, component.SprintModifier);
}
private void OnHandleState(EntityUid uid, ClothingSpeedModifierComponent component, ref ComponentHandleState args)
@@ -71,13 +43,11 @@ private void OnHandleState(EntityUid uid, ClothingSpeedModifierComponent compone
if (args.Current is not ClothingSpeedModifierComponentState state)
return;
- var diff = component.Enabled != state.Enabled ||
- !MathHelper.CloseTo(component.SprintModifier, state.SprintModifier) ||
+ var diff = !MathHelper.CloseTo(component.SprintModifier, state.SprintModifier) ||
!MathHelper.CloseTo(component.WalkModifier, state.WalkModifier);
component.WalkModifier = state.WalkModifier;
component.SprintModifier = state.SprintModifier;
- component.Enabled = state.Enabled;
// Avoid raising the event for the container if nothing changed.
// We'll still set the values in case they're slightly different but within tolerance.
@@ -89,10 +59,8 @@ private void OnHandleState(EntityUid uid, ClothingSpeedModifierComponent compone
private void OnRefreshMoveSpeed(EntityUid uid, ClothingSpeedModifierComponent component, InventoryRelayedEvent 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 args)
@@ -142,60 +110,15 @@ private void OnClothingVerbExamine(EntityUid uid, ClothingSpeedModifierComponent
_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 uid, ref MapInitEvent args)
- {
- _actions.AddAction(uid, ref uid.Comp.ToggleActionEntity, uid.Comp.ToggleAction);
- }
-
- private void OnToggleSpeed(Entity uid, ref ToggleClothingSpeedEvent args)
- {
- if (args.Handled)
- return;
-
- args.Handled = true;
- SetSpeedToggleEnabled(uid, !uid.Comp.Enabled, args.Performer);
- }
-
- private void SetSpeedToggleEnabled(Entity uid, bool value, EntityUid? user)
+ private void OnToggled(Entity ent, ref ItemToggledEvent args)
{
- if (uid.Comp.Enabled == value)
- return;
-
- TryComp(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 uid, ref GetVerbsEvent 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 uid, ref GetItemActionsEvent args)
- {
- args.AddAction(ref uid.Comp.ToggleActionEntity, uid.Comp.ToggleAction);
- }
-
- private void OnPowerCellSlotEmpty(Entity 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);
+ }
}
}
diff --git a/Content.Shared/Clothing/Components/StealthClothingComponent.cs b/Content.Shared/Clothing/Components/StealthClothingComponent.cs
deleted file mode 100644
index fedf48b36ed..00000000000
--- a/Content.Shared/Clothing/Components/StealthClothingComponent.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-using Content.Shared.Actions;
-using Content.Shared.Clothing.EntitySystems;
-using Robust.Shared.GameStates;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-
-namespace Content.Shared.Clothing.Components;
-
-///
-/// Adds StealthComponent to the user when enabled, either by an action or the system's SetEnabled method.
-///
-[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true), Access(typeof(StealthClothingSystem))]
-public sealed partial class StealthClothingComponent : Component
-{
- ///
- /// Whether stealth effect is enabled.
- ///
- [DataField("enabled"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
- public bool Enabled;
-
- ///
- /// Number added to MinVisibility when stealthed, to make the user not fully invisible.
- ///
- [DataField("visibility"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
- public float Visibility;
-
- [DataField("toggleAction", customTypeSerializer: typeof(PrototypeIdSerializer))]
- public string ToggleAction = "ActionTogglePhaseCloak";
-
- ///
- /// The action for enabling and disabling stealth.
- ///
- [DataField, AutoNetworkedField] public EntityUid? ToggleActionEntity;
-}
-
-///
-/// When stealth is enabled, disables it.
-/// When it is disabled, raises before enabling.
-/// Put any checks in a handler for that event to cancel it.
-///
-public sealed partial class ToggleStealthEvent : InstantActionEvent
-{
-}
diff --git a/Content.Shared/Clothing/Components/ToggleClothingComponent.cs b/Content.Shared/Clothing/Components/ToggleClothingComponent.cs
new file mode 100644
index 00000000000..c77aa03475f
--- /dev/null
+++ b/Content.Shared/Clothing/Components/ToggleClothingComponent.cs
@@ -0,0 +1,40 @@
+using Content.Shared.Actions;
+using Content.Shared.Clothing.EntitySystems;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Clothing.Components;
+
+///
+/// Clothing that can be enabled and disabled with an action.
+/// Requires .
+///
+///
+/// Not to be confused with for hardsuit helmets and such.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+[Access(typeof(ToggleClothingSystem))]
+public sealed partial class ToggleClothingComponent : Component
+{
+ ///
+ /// The action to add when equipped, even if not worn.
+ /// This must raise to then get handled.
+ ///
+ [DataField(required: true)]
+ public EntProtoId Action = string.Empty;
+
+ [DataField, AutoNetworkedField]
+ public EntityUid? ActionEntity;
+
+ ///
+ /// If true, automatically disable the clothing after unequipping it.
+ ///
+ [DataField]
+ public bool DisableOnUnequip;
+}
+
+///
+/// Raised on the clothing when being equipped to see if it should add the action.
+///
+[ByRefEvent]
+public record struct ToggleClothingCheckEvent(EntityUid User, bool Cancelled = false);
diff --git a/Content.Shared/Clothing/Components/ToggleClothingSpeedComponent.cs b/Content.Shared/Clothing/Components/ToggleClothingSpeedComponent.cs
deleted file mode 100644
index 90b2d7322e0..00000000000
--- a/Content.Shared/Clothing/Components/ToggleClothingSpeedComponent.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using Content.Shared.Actions;
-using Robust.Shared.GameStates;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.Clothing.Components;
-
-///
-/// This is used for a clothing item that gives a speed modification that is toggleable.
-///
-[RegisterComponent, NetworkedComponent, Access(typeof(ClothingSpeedModifierSystem)), AutoGenerateComponentState]
-public sealed partial class ToggleClothingSpeedComponent : Component
-{
- ///
- /// The action for toggling the clothing.
- ///
- [DataField]
- public EntProtoId ToggleAction = "ActionToggleSpeedBoots";
-
- ///
- /// The action entity
- ///
- [DataField, AutoNetworkedField]
- public EntityUid? ToggleActionEntity;
-
- ///
- /// The state of the toggle.
- ///
- [DataField, AutoNetworkedField]
- public bool Enabled;
-}
-
-public sealed partial class ToggleClothingSpeedEvent : InstantActionEvent
-{
-
-}
diff --git a/Content.Shared/Clothing/EntitySystems/StealthClothingSystem.cs b/Content.Shared/Clothing/EntitySystems/StealthClothingSystem.cs
deleted file mode 100644
index e96d9f866aa..00000000000
--- a/Content.Shared/Clothing/EntitySystems/StealthClothingSystem.cs
+++ /dev/null
@@ -1,144 +0,0 @@
-using Content.Shared.Actions;
-using Content.Shared.Clothing.Components;
-using Content.Shared.Inventory.Events;
-using Content.Shared.Stealth;
-using Content.Shared.Stealth.Components;
-
-namespace Content.Shared.Clothing.EntitySystems;
-
-///
-/// Handles the toggle action and disables stealth when clothing is unequipped.
-///
-public sealed class StealthClothingSystem : EntitySystem
-{
- [Dependency] private readonly SharedStealthSystem _stealth = default!;
- [Dependency] private readonly ActionContainerSystem _actionContainer = default!;
-
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent(OnGetItemActions);
- SubscribeLocalEvent(OnToggleStealth);
- SubscribeLocalEvent(OnHandleState);
- SubscribeLocalEvent(OnUnequipped);
- SubscribeLocalEvent(OnMapInit);
- }
-
- private void OnMapInit(EntityUid uid, StealthClothingComponent component, MapInitEvent args)
- {
- _actionContainer.EnsureAction(uid, ref component.ToggleActionEntity, component.ToggleAction);
- Dirty(uid, component);
- }
-
- ///
- /// Sets the clothing's stealth effect for the user.
- ///
- /// True if it was changed, false otherwise
- 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(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;
- }
-
- ///
- /// Raise then add the toggle action if it was not cancelled.
- ///
- 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);
- }
-
- ///
- /// Raises if enabling.
- ///
- 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);
- }
-
- ///
- /// Calls when server sends new state.
- ///
- 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);
- }
-
- ///
- /// Force unstealths the user, doesnt remove StealthComponent since other things might use it
- ///
- private void OnUnequipped(EntityUid uid, StealthClothingComponent comp, GotUnequippedEvent args)
- {
- SetEnabled(uid, args.Equipee, false, comp);
- }
-}
-
-///
-/// Raised on the stealth clothing when attempting to add an action.
-///
-public sealed class AddStealthActionEvent : CancellableEntityEventArgs
-{
- ///
- /// User that equipped the stealth clothing.
- ///
- public EntityUid User;
-
- public AddStealthActionEvent(EntityUid user)
- {
- User = user;
- }
-}
-
-///
-/// Raised on the stealth clothing when the user is attemping to enable it.
-///
-public sealed class AttemptStealthEvent : CancellableEntityEventArgs
-{
- ///
- /// User that is attempting to enable the stealth clothing.
- ///
- public EntityUid User;
-
- public AttemptStealthEvent(EntityUid user)
- {
- User = user;
- }
-}
diff --git a/Content.Shared/Clothing/EntitySystems/ToggleClothingSystem.cs b/Content.Shared/Clothing/EntitySystems/ToggleClothingSystem.cs
new file mode 100644
index 00000000000..9889376c9d4
--- /dev/null
+++ b/Content.Shared/Clothing/EntitySystems/ToggleClothingSystem.cs
@@ -0,0 +1,58 @@
+using Content.Shared.Actions;
+using Content.Shared.Clothing;
+using Content.Shared.Clothing.Components;
+using Content.Shared.Inventory;
+using Content.Shared.Item.ItemToggle;
+using Content.Shared.Toggleable;
+
+namespace Content.Shared.Clothing.EntitySystems;
+
+///
+/// Handles adding and using a toggle action for .
+///
+public sealed class ToggleClothingSystem : EntitySystem
+{
+ [Dependency] private readonly SharedActionsSystem _actions = default!;
+ [Dependency] private readonly ItemToggleSystem _toggle = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnMapInit);
+ SubscribeLocalEvent(OnGetActions);
+ SubscribeLocalEvent(OnToggleAction);
+ SubscribeLocalEvent(OnUnequipped);
+ }
+
+ private void OnMapInit(Entity 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 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 ent, ref ToggleActionEvent args)
+ {
+ args.Handled = _toggle.Toggle(ent.Owner, args.Performer);
+ }
+
+ private void OnUnequipped(Entity ent, ref ClothingGotUnequippedEvent args)
+ {
+ if (ent.Comp.DisableOnUnequip)
+ _toggle.TryDeactivate(ent.Owner, args.Wearer);
+ }
+}
diff --git a/Content.Shared/Clothing/MagbootsComponent.cs b/Content.Shared/Clothing/MagbootsComponent.cs
index 0d074ff38b6..4bef74fd335 100644
--- a/Content.Shared/Clothing/MagbootsComponent.cs
+++ b/Content.Shared/Clothing/MagbootsComponent.cs
@@ -1,23 +1,25 @@
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;
+ public ProtoId MagbootsAlert = "Magboots";
- [DataField("on"), AutoNetworkedField]
- public bool On;
+ ///
+ /// If true, the user must be standing on a grid or planet map to experience the weightlessness-canceling effect
+ ///
+ [DataField]
+ public bool RequiresGrid = true;
+ ///
+ /// Slot the clothing has to be worn in to work.
+ ///
[DataField]
- public ProtoId MagbootsAlert = "Magboots";
+ public string Slot = "shoes";
}
diff --git a/Content.Shared/Clothing/MagbootsSystem.cs b/Content.Shared/Clothing/MagbootsSystem.cs
new file mode 100644
index 00000000000..88d987aae18
--- /dev/null
+++ b/Content.Shared/Clothing/MagbootsSystem.cs
@@ -0,0 +1,90 @@
+using Content.Shared.Actions;
+using Content.Shared.Alert;
+using Content.Shared.Atmos.Components;
+using Content.Shared.Clothing.EntitySystems;
+using Content.Shared.Gravity;
+using Content.Shared.Inventory;
+using Content.Shared.Item;
+using Content.Shared.Item.ItemToggle;
+using Content.Shared.Item.ItemToggle.Components;
+using Robust.Shared.Containers;
+
+namespace Content.Shared.Clothing;
+
+public sealed class SharedMagbootsSystem : EntitySystem
+{
+ [Dependency] private readonly AlertsSystem _alerts = default!;
+ [Dependency] private readonly ClothingSystem _clothing = default!;
+ [Dependency] private readonly InventorySystem _inventory = default!;
+ [Dependency] private readonly ItemToggleSystem _toggle = default!;
+ [Dependency] private readonly SharedContainerSystem _container = default!;
+ [Dependency] private readonly SharedGravitySystem _gravity = default!;
+ [Dependency] private readonly SharedItemSystem _item = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnToggled);
+ SubscribeLocalEvent(OnGotEquipped);
+ SubscribeLocalEvent(OnGotUnequipped);
+ SubscribeLocalEvent(OnIsWeightless);
+ SubscribeLocalEvent>(OnIsWeightless);
+ }
+
+ private void OnToggled(Entity 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 ent, ref ClothingGotUnequippedEvent args)
+ {
+ UpdateMagbootEffects(args.Wearer, ent, false);
+ }
+
+ private void OnGotEquipped(Entity ent, ref ClothingGotEquippedEvent args)
+ {
+ UpdateMagbootEffects(args.Wearer, ent, _toggle.IsActivated(ent.Owner));
+ }
+
+ public void UpdateMagbootEffects(EntityUid user, Entity ent, bool state)
+ {
+ // TODO: public api for this and add access
+ if (TryComp(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 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 ent, ref InventoryRelayedEvent args)
+ {
+ OnIsWeightless(ent, ref args.Args);
+ }
+}
diff --git a/Content.Shared/Clothing/SharedMagbootsSystem.cs b/Content.Shared/Clothing/SharedMagbootsSystem.cs
deleted file mode 100644
index 27fb0c1a502..00000000000
--- a/Content.Shared/Clothing/SharedMagbootsSystem.cs
+++ /dev/null
@@ -1,101 +0,0 @@
-using Content.Shared.Actions;
-using Content.Shared.Clothing.EntitySystems;
-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 abstract class SharedMagbootsSystem : EntitySystem
-{
- [Dependency] private readonly ClothingSpeedModifierSystem _clothingSpeedModifier = default!;
- [Dependency] private readonly ClothingSystem _clothing = 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>(AddToggleVerb);
- SubscribeLocalEvent>(OnSlipAttempt);
- SubscribeLocalEvent(OnGetActions);
- SubscribeLocalEvent(OnToggleMagboots);
- SubscribeLocalEvent(OnMapInit);
- }
-
- private void OnMapInit(EntityUid uid, MagbootsComponent component, MapInitEvent args)
- {
- _actionContainer.AddAction(uid, ref component.ToggleActionEntity, component.ToggleAction);
- Dirty(uid, 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, out var container) &&
- _inventory.TryGetSlotEntity(container.Owner, "shoes", out var entityUid) && entityUid == uid)
- UpdateMagbootEffects(container.Owner, uid, true, magboots);
-
- if (TryComp(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);
- }
-
- protected virtual void UpdateMagbootEffects(EntityUid parent, EntityUid uid, bool state, MagbootsComponent? component) { }
-
- protected 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 args)
- {
- if (!args.CanAccess || !args.CanInteract)
- return;
-
- ActivationVerb verb = new();
- verb.Text = Loc.GetString("toggle-magboots-verb-get-data-text");
- verb.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 args)
- {
- if (component.On)
- args.Args.Cancel();
- }
-
- private void OnGetActions(EntityUid uid, MagbootsComponent component, GetItemActionsEvent args)
- {
- args.AddAction(ref component.ToggleActionEntity, component.ToggleAction);
- }
-}
-
-public sealed partial class ToggleMagbootsEvent : InstantActionEvent {}
diff --git a/Content.Shared/Item/ItemToggle/ComponentTogglerSystem.cs b/Content.Shared/Item/ItemToggle/ComponentTogglerSystem.cs
new file mode 100644
index 00000000000..760cefe27d4
--- /dev/null
+++ b/Content.Shared/Item/ItemToggle/ComponentTogglerSystem.cs
@@ -0,0 +1,26 @@
+using Content.Shared.Item.ItemToggle.Components;
+
+namespace Content.Shared.Item.ItemToggle;
+
+///
+/// Handles component manipulation.
+///
+public sealed class ComponentTogglerSystem : EntitySystem
+{
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnToggled);
+ }
+
+ private void OnToggled(Entity 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);
+ }
+}
diff --git a/Content.Shared/Item/ItemToggle/Components/ComponentTogglerComponent.cs b/Content.Shared/Item/ItemToggle/Components/ComponentTogglerComponent.cs
new file mode 100644
index 00000000000..20ef0a02315
--- /dev/null
+++ b/Content.Shared/Item/ItemToggle/Components/ComponentTogglerComponent.cs
@@ -0,0 +1,32 @@
+using Content.Shared.Item.ItemToggle;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Item.ItemToggle.Components;
+
+///
+/// Adds or removes components when toggled.
+/// Requires .
+///
+[RegisterComponent, NetworkedComponent, Access(typeof(ComponentTogglerSystem))]
+public sealed partial class ComponentTogglerComponent : Component
+{
+ ///
+ /// The components to add when activated.
+ ///
+ [DataField(required: true)]
+ public ComponentRegistry Components = new();
+
+ ///
+ /// The components to remove when deactivated.
+ /// If this is null is reused.
+ ///
+ [DataField]
+ public ComponentRegistry? RemoveComponents;
+
+ ///
+ /// If true, adds components on the entity's parent instead of the entity itself.
+ ///
+ [DataField]
+ public bool Parent;
+}
diff --git a/Content.Shared/Item/ItemToggle/Components/ItemToggleActiveSoundComponent.cs b/Content.Shared/Item/ItemToggle/Components/ItemToggleActiveSoundComponent.cs
index 6d534713578..cdac49ae6d6 100644
--- a/Content.Shared/Item/ItemToggle/Components/ItemToggleActiveSoundComponent.cs
+++ b/Content.Shared/Item/ItemToggle/Components/ItemToggleActiveSoundComponent.cs
@@ -12,12 +12,12 @@ public sealed partial class ItemToggleActiveSoundComponent : Component
///
/// The continuous noise this item makes when it's activated (like an e-sword's hum).
///
- [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
+ [DataField(required: true), AutoNetworkedField]
public SoundSpecifier? ActiveSound;
///
/// Used when the item emits sound while active.
///
- [ViewVariables(VVAccess.ReadWrite), DataField]
+ [DataField]
public EntityUid? PlayingStream;
}
diff --git a/Content.Shared/Item/ItemToggle/Components/ItemToggleComponent.cs b/Content.Shared/Item/ItemToggle/Components/ItemToggleComponent.cs
index 620ddfd1942..46249fdd0de 100644
--- a/Content.Shared/Item/ItemToggle/Components/ItemToggleComponent.cs
+++ b/Content.Shared/Item/ItemToggle/Components/ItemToggleComponent.cs
@@ -8,7 +8,7 @@ namespace Content.Shared.Item.ItemToggle.Components;
///
///
/// If you need extended functionality (e.g. requiring power) then add a new component and use events:
-/// ItemToggleActivateAttemptEvent, ItemToggleDeactivateAttemptEvent or ItemToggleForceToggleEvent.
+/// ItemToggleActivateAttemptEvent, ItemToggleDeactivateAttemptEvent, ItemToggledEvent.
///
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class ItemToggleComponent : Component
@@ -19,6 +19,13 @@ public sealed partial class ItemToggleComponent : Component
[DataField, AutoNetworkedField]
public bool Activated = false;
+ ///
+ /// If this is set to false then the item can't be toggled by pressing Z.
+ /// Use another system to do it then.
+ ///
+ [DataField]
+ public bool OnUse = true;
+
///
/// Whether the item's toggle can be predicted by the client.
///
diff --git a/Content.Shared/Item/ItemToggle/Components/ToggleVerbComponent.cs b/Content.Shared/Item/ItemToggle/Components/ToggleVerbComponent.cs
new file mode 100644
index 00000000000..b673c55e0f1
--- /dev/null
+++ b/Content.Shared/Item/ItemToggle/Components/ToggleVerbComponent.cs
@@ -0,0 +1,18 @@
+using Content.Shared.Item.ItemToggle;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Item.ItemToggle.Components;
+
+///
+/// Adds a verb for toggling something, requires .
+///
+[RegisterComponent, NetworkedComponent, Access(typeof(ToggleVerbSystem))]
+public sealed partial class ToggleVerbComponent : Component
+{
+ ///
+ /// Text the verb will have.
+ /// Gets passed "entity" as the entity's identity string.
+ ///
+ [DataField(required: true)]
+ public LocId Text = string.Empty;
+}
diff --git a/Content.Shared/Item/ItemToggle/SharedItemToggleSystem.cs b/Content.Shared/Item/ItemToggle/ItemToggleSystem.cs
similarity index 67%
rename from Content.Shared/Item/ItemToggle/SharedItemToggleSystem.cs
rename to Content.Shared/Item/ItemToggle/ItemToggleSystem.cs
index d07fd5a735e..c4e4150659b 100644
--- a/Content.Shared/Item/ItemToggle/SharedItemToggleSystem.cs
+++ b/Content.Shared/Item/ItemToggle/ItemToggleSystem.cs
@@ -18,12 +18,12 @@ namespace Content.Shared.Item.ItemToggle;
///
/// If you need extended functionality (e.g. requiring power) then add a new component and use events.
///
-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()
@@ -31,8 +31,9 @@ public override void Initialize()
base.Initialize();
SubscribeLocalEvent(OnStartup);
- SubscribeLocalEvent(TurnOffonUnwielded);
- SubscribeLocalEvent(TurnOnonWielded);
+ SubscribeLocalEvent(OnMapInit);
+ SubscribeLocalEvent(TurnOffOnUnwielded);
+ SubscribeLocalEvent(TurnOnOnWielded);
SubscribeLocalEvent(OnUseInHand);
SubscribeLocalEvent(OnIsHotEvent);
@@ -47,57 +48,76 @@ private void OnStartup(Entity ent, ref ComponentStartup arg
UpdateVisuals(ent);
}
- private void OnUseInHand(EntityUid uid, ItemToggleComponent itemToggle, UseInHandEvent args)
+ private void OnMapInit(Entity 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 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);
}
///
/// Used when an item is attempted to be toggled.
+ /// Sets its state to the opposite of what it is.
///
- public void Toggle(EntityUid uid, EntityUid? user = null, bool predicted = true, ItemToggleComponent? itemToggle = null)
+ /// Same as
+ public bool Toggle(Entity 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);
+ }
+
+ ///
+ /// Tries to set the activated bool from a value.
+ ///
+ /// false if the attempt fails for any reason
+ public bool TrySetActive(Entity 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);
}
///
/// Used when an item is attempting to be activated. It returns false if the attempt fails any reason, interrupting the activation.
///
- public bool TryActivate(EntityUid uid, EntityUid? user = null, bool predicted = true, ItemToggleComponent? itemToggle = null)
+ public bool TryActivate(Entity 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)
{
@@ -110,7 +130,7 @@ public bool TryActivate(EntityUid uid, EntityUid? user = null, bool predicted =
return false;
}
- Activate(uid, itemToggle, predicted, user);
+ Activate((uid, comp), predicted, user);
return true;
}
@@ -118,75 +138,65 @@ public bool TryActivate(EntityUid uid, EntityUid? user = null, bool predicted =
///
/// Used when an item is attempting to be deactivated. It returns false if the attempt fails any reason, interrupting the deactivation.
///
- public bool TryDeactivate(EntityUid uid, EntityUid? user = null, bool predicted = true, ItemToggleComponent? itemToggle = null)
+ public bool TryDeactivate(Entity 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 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);
}
///
/// Used to make the actual changes to the item's components on deactivation.
///
- private void Deactivate(EntityUid uid, ItemToggleComponent itemToggle, bool predicted, EntityUid? user = null)
+ private void Deactivate(Entity 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 ent)
@@ -209,68 +219,56 @@ private void UpdateVisuals(Entity ent)
///
/// 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.
///
- private void TurnOffonUnwielded(EntityUid uid, ItemToggleComponent itemToggle, ItemUnwieldedEvent args)
+ private void TurnOffOnUnwielded(Entity ent, ref ItemUnwieldedEvent args)
{
- if (itemToggle.Activated)
- TryDeactivate(uid, args.User, itemToggle: itemToggle);
+ TryDeactivate((ent, ent.Comp), args.User);
}
///
/// Wieldable items will automatically turn on when wielded.
///
- private void TurnOnonWielded(EntityUid uid, ItemToggleComponent itemToggle, ref ItemWieldedEvent args)
+ private void TurnOnOnWielded(Entity 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 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;
}
///
/// Used to make the item hot when activated.
///
- private void OnIsHotEvent(EntityUid uid, ItemToggleHotComponent itemToggleHot, IsHotEvent args)
+ private void OnIsHotEvent(Entity ent, ref IsHotEvent args)
{
- args.IsHot |= IsActivated(uid);
+ args.IsHot |= IsActivated(ent.Owner);
}
///
/// Used to update the looping active sound linked to the entity.
///
- private void UpdateActiveSound(EntityUid uid, ItemToggleActiveSoundComponent activeSound, ref ItemToggledEvent args)
+ private void UpdateActiveSound(Entity ent, ref ItemToggledEvent args)
{
- if (args.Activated)
+ var (uid, comp) = ent;
+ if (!args.Activated)
{
- if (activeSound.ActiveSound != null && activeSound.PlayingStream == null)
- {
- if (args.Predicted)
- {
- var playingStream = _audio.PlayPredicted(activeSound.ActiveSound, uid, args.User, AudioParams.Default.WithLoop(true));
-
- if (playingStream == null)
- return;
-
- activeSound.PlayingStream = playingStream!.Value.Entity;
- } else
- {
- var playingStream = _audio.PlayPvs(activeSound.ActiveSound, uid, AudioParams.Default.WithLoop(true));
-
- if (playingStream == null)
- return;
-
- activeSound.PlayingStream = playingStream!.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;
}
}
diff --git a/Content.Shared/Item/ItemToggle/ToggleVerbSystem.cs b/Content.Shared/Item/ItemToggle/ToggleVerbSystem.cs
new file mode 100644
index 00000000000..858cd9bc111
--- /dev/null
+++ b/Content.Shared/Item/ItemToggle/ToggleVerbSystem.cs
@@ -0,0 +1,34 @@
+using Content.Shared.IdentityManagement;
+using Content.Shared.Item.ItemToggle.Components;
+using Content.Shared.Verbs;
+
+namespace Content.Shared.Item.ItemToggle;
+
+///
+/// Adds a verb for toggling something with .
+///
+public sealed class ToggleVerbSystem : EntitySystem
+{
+ [Dependency] private readonly ItemToggleSystem _toggle = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent>(OnGetVerbs);
+ }
+
+ private void OnGetVerbs(Entity ent, ref GetVerbsEvent 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)
+ });
+ }
+}
diff --git a/Content.Shared/Medical/DefibrillatorComponent.cs b/Content.Shared/Medical/DefibrillatorComponent.cs
index 2da52852854..1d6f690bc3b 100644
--- a/Content.Shared/Medical/DefibrillatorComponent.cs
+++ b/Content.Shared/Medical/DefibrillatorComponent.cs
@@ -10,16 +10,11 @@ namespace Content.Shared.Medical;
///
/// This is used for defibrillators; a machine that shocks a dead
/// person back into the world of the living.
+/// Uses ItemToggleComponent
///
[RegisterComponent, NetworkedComponent, AutoGenerateComponentPause]
public sealed partial class DefibrillatorComponent : Component
{
- ///
- /// Whether or not it's turned on and able to be used.
- ///
- [DataField("enabled"), ViewVariables(VVAccess.ReadWrite)]
- public bool Enabled;
-
///
/// The time at which the zap cooldown will be completed
///
@@ -66,15 +61,6 @@ public sealed partial class DefibrillatorComponent : Component
[ViewVariables(VVAccess.ReadWrite), DataField("zapSound")]
public SoundSpecifier? ZapSound = new SoundPathSpecifier("/Audio/Items/Defib/defib_zap.ogg");
- ///
- /// The sound when the defib is powered on.
- ///
- [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");
diff --git a/Content.Shared/Ninja/Components/BatteryDrainerComponent.cs b/Content.Shared/Ninja/Components/BatteryDrainerComponent.cs
index 55bcdd0f0a5..9c39c4724ce 100644
--- a/Content.Shared/Ninja/Components/BatteryDrainerComponent.cs
+++ b/Content.Shared/Ninja/Components/BatteryDrainerComponent.cs
@@ -1,5 +1,6 @@
using Content.Shared.Ninja.Systems;
using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
namespace Content.Shared.Ninja.Components;
@@ -7,32 +8,33 @@ namespace Content.Shared.Ninja.Components;
/// Component for draining power from APCs/substations/SMESes, when ProviderUid is set to a battery cell.
/// Does not rely on relay, simply being on the user and having BatteryUid set is enough.
///
-[RegisterComponent, Access(typeof(SharedBatteryDrainerSystem))]
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+[Access(typeof(SharedBatteryDrainerSystem))]
public sealed partial class BatteryDrainerComponent : Component
{
///
/// The powercell entity to drain power into.
/// Determines whether draining is possible.
///
- [DataField("batteryUid"), ViewVariables(VVAccess.ReadWrite)]
+ [DataField, AutoNetworkedField]
public EntityUid? BatteryUid;
///
/// 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.
///
- [DataField("drainEfficiency"), ViewVariables(VVAccess.ReadWrite)]
+ [DataField]
public float DrainEfficiency = 0.001f;
///
/// Time that the do after takes to drain charge from a battery, in seconds
///
- [DataField("drainTime"), ViewVariables(VVAccess.ReadWrite)]
+ [DataField]
public float DrainTime = 1f;
///
/// Sound played after the doafter ends.
///
- [DataField("sparkSound")]
+ [DataField]
public SoundSpecifier SparkSound = new SoundCollectionSpecifier("sparks");
}
diff --git a/Content.Shared/Ninja/Components/BombingTargetComponent.cs b/Content.Shared/Ninja/Components/BombingTargetComponent.cs
index bf0eaec84be..c429eb6880e 100644
--- a/Content.Shared/Ninja/Components/BombingTargetComponent.cs
+++ b/Content.Shared/Ninja/Components/BombingTargetComponent.cs
@@ -4,6 +4,4 @@ namespace Content.Shared.Ninja.Components;
/// Makes this warp point a valid bombing target for ninja's spider charge.
///
[RegisterComponent]
-public sealed partial class BombingTargetComponent : Component
-{
-}
+public sealed partial class BombingTargetComponent : Component;
diff --git a/Content.Shared/Ninja/Components/DashAbilityComponent.cs b/Content.Shared/Ninja/Components/DashAbilityComponent.cs
index ba4060c7035..464f48f187e 100644
--- a/Content.Shared/Ninja/Components/DashAbilityComponent.cs
+++ b/Content.Shared/Ninja/Components/DashAbilityComponent.cs
@@ -8,6 +8,7 @@ namespace Content.Shared.Ninja.Components;
///
/// Adds an action to dash, teleport to clicked position, when this item is held.
+/// Cancel to prevent using it.
///
[RegisterComponent, NetworkedComponent, Access(typeof(DashAbilitySystem)), AutoGenerateComponentState]
public sealed partial class DashAbilityComponent : Component
@@ -16,19 +17,10 @@ public sealed partial class DashAbilityComponent : Component
/// The action id for dashing.
///
[DataField]
- public EntProtoId DashAction = "ActionEnergyKatanaDash";
+ public EntProtoId DashAction = "ActionEnergyKatanaDash";
[DataField, AutoNetworkedField]
public EntityUid? DashActionEntity;
-
- ///
- /// Sound played when using dash action.
- ///
- [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;
diff --git a/Content.Shared/Ninja/Components/EmagProviderComponent.cs b/Content.Shared/Ninja/Components/EmagProviderComponent.cs
index db7678f61d7..ae3e85cbe42 100644
--- a/Content.Shared/Ninja/Components/EmagProviderComponent.cs
+++ b/Content.Shared/Ninja/Components/EmagProviderComponent.cs
@@ -2,7 +2,7 @@
using Content.Shared.Tag;
using Content.Shared.Whitelist;
using Robust.Shared.GameStates;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+using Robust.Shared.Prototypes;
namespace Content.Shared.Ninja.Components;
@@ -10,19 +10,18 @@ namespace Content.Shared.Ninja.Components;
/// Component for emagging things on click.
/// No charges but checks against a whitelist.
///
-[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
-[Access(typeof(EmagProviderSystem))]
+[RegisterComponent, NetworkedComponent, Access(typeof(EmagProviderSystem))]
public sealed partial class EmagProviderComponent : Component
{
///
/// The tag that marks an entity as immune to emagging.
///
- [DataField("emagImmuneTag", customTypeSerializer: typeof(PrototypeIdSerializer))]
- public string EmagImmuneTag = "EmagImmune";
+ [DataField]
+ public ProtoId EmagImmuneTag = "EmagImmune";
///
/// Whitelist that entities must be on to work.
///
- [DataField("whitelist"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
- public EntityWhitelist? Whitelist = null;
+ [DataField]
+ public EntityWhitelist? Whitelist;
}
diff --git a/Content.Shared/Ninja/Components/EnergyKatanaComponent.cs b/Content.Shared/Ninja/Components/EnergyKatanaComponent.cs
index 33b8fc78933..84c58bb6480 100644
--- a/Content.Shared/Ninja/Components/EnergyKatanaComponent.cs
+++ b/Content.Shared/Ninja/Components/EnergyKatanaComponent.cs
@@ -7,6 +7,4 @@ namespace Content.Shared.Ninja.Components;
/// Requires a ninja with a suit for abilities to work.
///
[RegisterComponent, NetworkedComponent]
-public sealed partial class EnergyKatanaComponent : Component
-{
-}
+public sealed partial class EnergyKatanaComponent : Component;
diff --git a/Content.Shared/Ninja/Components/ItemCreatorComponent.cs b/Content.Shared/Ninja/Components/ItemCreatorComponent.cs
new file mode 100644
index 00000000000..d9f66d21a31
--- /dev/null
+++ b/Content.Shared/Ninja/Components/ItemCreatorComponent.cs
@@ -0,0 +1,52 @@
+using Content.Shared.Actions;
+using Content.Shared.Ninja.Systems;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Ninja.Components;
+
+///
+/// Uses battery charge to spawn an item and place it in the user's hands.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+[Access(typeof(SharedItemCreatorSystem))]
+public sealed partial class ItemCreatorComponent : Component
+{
+ ///
+ /// The battery entity to use charge from
+ ///
+ [DataField, AutoNetworkedField]
+ public EntityUid? Battery;
+
+ ///
+ /// The action id for creating an item.
+ ///
+ [DataField(required: true)]
+ public EntProtoId Action = string.Empty;
+
+ [DataField, AutoNetworkedField]
+ public EntityUid? ActionEntity;
+
+ ///
+ /// Battery charge used to create an item.
+ ///
+ [DataField(required: true)]
+ public float Charge = 14.4f;
+
+ ///
+ /// Item to create with the action
+ ///
+ [DataField(required: true)]
+ public EntProtoId SpawnedPrototype = string.Empty;
+
+ ///
+ /// Popup shown to the user when there isn't enough power to create an item.
+ ///
+ [DataField(required: true)]
+ public LocId NoPowerPopup = string.Empty;
+}
+
+///
+/// Action event to use an .
+///
+public sealed partial class CreateItemEvent : InstantActionEvent;
diff --git a/Content.Shared/Ninja/Components/NinjaGlovesComponent.cs b/Content.Shared/Ninja/Components/NinjaGlovesComponent.cs
index 7b57926330b..3b9e2a5e356 100644
--- a/Content.Shared/Ninja/Components/NinjaGlovesComponent.cs
+++ b/Content.Shared/Ninja/Components/NinjaGlovesComponent.cs
@@ -1,20 +1,17 @@
-using Content.Shared.DoAfter;
using Content.Shared.Ninja.Systems;
-using Content.Shared.Toggleable;
-using Content.Shared.Whitelist;
+using Content.Shared.Objectives.Components;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-using Robust.Shared.Utility;
namespace Content.Shared.Ninja.Components;
///
/// Component for toggling glove powers.
-/// Powers being enabled is controlled by User not being null.
///
+///
+/// Requires ItemToggleComponent.
+///
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
[Access(typeof(SharedNinjaGlovesSystem))]
public sealed partial class NinjaGlovesComponent : Component
@@ -22,24 +19,33 @@ public sealed partial class NinjaGlovesComponent : Component
///
/// Entity of the ninja using these gloves, usually means enabled
///
- [DataField("user"), AutoNetworkedField]
+ [DataField, AutoNetworkedField]
public EntityUid? User;
///
- /// The action id for toggling ninja gloves abilities
+ /// Abilities to give to the user when enabled.
///
- [DataField("toggleAction", customTypeSerializer: typeof(PrototypeIdSerializer))]
- public string ToggleAction = "ActionToggleNinjaGloves";
+ [DataField(required: true)]
+ public List Abilities = new();
+}
- [DataField, AutoNetworkedField]
- public EntityUid? ToggleActionEntity;
+///
+/// An ability that adds components to the user when the gloves are enabled.
+///
+[DataRecord]
+public record struct NinjaGloveAbility()
+{
+ ///
+ /// 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 CodeConditionComponent to be checked.
+ ///
+ [DataField]
+ public EntProtoId? Objective;
///
- /// The whitelist used for the emag provider to emag airlocks only (not regular doors).
+ /// Components to add and remove.
///
- [DataField("doorjackWhitelist")]
- public EntityWhitelist DoorjackWhitelist = new()
- {
- Components = new[] {"Airlock"}
- };
+ [DataField(required: true)]
+ public ComponentRegistry Components = new();
}
diff --git a/Content.Shared/Ninja/Components/NinjaSuitComponent.cs b/Content.Shared/Ninja/Components/NinjaSuitComponent.cs
index 7e7b1ffcd30..8b477b2aa5f 100644
--- a/Content.Shared/Ninja/Components/NinjaSuitComponent.cs
+++ b/Content.Shared/Ninja/Components/NinjaSuitComponent.cs
@@ -3,9 +3,6 @@
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Utility;
namespace Content.Shared.Ninja.Components;
@@ -14,68 +11,27 @@ namespace Content.Shared.Ninja.Components;
/// Component for ninja suit abilities and power consumption.
/// As an implementation detail, dashing with katana is a suit action which isn't ideal.
///
-[RegisterComponent, NetworkedComponent, Access(typeof(SharedNinjaSuitSystem)), AutoGenerateComponentState]
-[AutoGenerateComponentPause]
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+[Access(typeof(SharedNinjaSuitSystem))]
public sealed partial class NinjaSuitComponent : Component
{
- ///
- /// Battery charge used passively, in watts. Will last 1000 seconds on a small-capacity power cell.
- ///
- [DataField("passiveWattage")]
- public float PassiveWattage = 0.36f;
-
- ///
- /// Battery charge used while cloaked, stacks with passive. Will last 200 seconds while cloaked on a small-capacity power cell.
- ///
- [DataField("cloakWattage")]
- public float CloakWattage = 1.44f;
-
///
/// Sound played when a ninja is hit while cloaked.
///
- [DataField("revealSound")]
+ [DataField]
public SoundSpecifier RevealSound = new SoundPathSpecifier("/Audio/Effects/chime.ogg");
///
- /// How long to disable all abilities when revealed.
- /// Normally, ninjas are revealed when attacking or getting damaged.
- ///
- [DataField("disableTime")]
- public TimeSpan DisableTime = TimeSpan.FromSeconds(5);
-
- ///
- /// Time at which we will be able to use our abilities again
+ /// ID of the use delay to disable all ninja abilities.
///
- [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
- [AutoPausedField]
- public TimeSpan DisableCooldown;
-
- ///
- /// The action id for creating throwing stars.
- ///
- [DataField("createThrowingStarAction", customTypeSerializer: typeof(PrototypeIdSerializer))]
- public string CreateThrowingStarAction = "ActionCreateThrowingStar";
-
- [DataField, AutoNetworkedField]
- public EntityUid? CreateThrowingStarActionEntity;
-
- ///
- /// Battery charge used to create a throwing star. Can do it 25 times on a small-capacity power cell.
- ///
- [DataField("throwingStarCharge")]
- public float ThrowingStarCharge = 14.4f;
-
- ///
- /// Throwing star item to create with the action
- ///
- [DataField("throwingStarPrototype", customTypeSerializer: typeof(PrototypeIdSerializer))]
- public string ThrowingStarPrototype = "ThrowingStarNinja";
+ [DataField]
+ public string DisableDelayId = "suit_powers";
///
/// The action id for recalling a bound energy katana
///
- [DataField("recallKatanaAction", customTypeSerializer: typeof(PrototypeIdSerializer))]
- public string RecallKatanaAction = "ActionRecallKatana";
+ [DataField]
+ public EntProtoId RecallKatanaAction = "ActionRecallKatana";
[DataField, AutoNetworkedField]
public EntityUid? RecallKatanaActionEntity;
@@ -84,14 +40,14 @@ public sealed partial class NinjaSuitComponent : Component
/// Battery charge used per tile the katana teleported.
/// Uses 1% of a default battery per tile.
///
- [DataField("recallCharge")]
+ [DataField]
public float RecallCharge = 3.6f;
///
/// The action id for creating an EMP burst
///
- [DataField("empAction", customTypeSerializer: typeof(PrototypeIdSerializer))]
- public string EmpAction = "ActionNinjaEmp";
+ [DataField]
+ public EntProtoId EmpAction = "ActionNinjaEmp";
[DataField, AutoNetworkedField]
public EntityUid? EmpActionEntity;
@@ -99,36 +55,29 @@ public sealed partial class NinjaSuitComponent : Component
///
/// Battery charge used to create an EMP burst. Can do it 2 times on a small-capacity power cell.
///
- [DataField("empCharge")]
+ [DataField]
public float EmpCharge = 180f;
+ // TODO: EmpOnTrigger bruh
///
/// Range of the EMP in tiles.
///
- [DataField("empRange")]
+ [DataField]
public float EmpRange = 6f;
///
/// Power consumed from batteries by the EMP
///
- [DataField("empConsumption")]
+ [DataField]
public float EmpConsumption = 100000f;
///
/// How long the EMP effects last for, in seconds
///
- [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;
diff --git a/Content.Shared/Ninja/Components/SpaceNinjaComponent.cs b/Content.Shared/Ninja/Components/SpaceNinjaComponent.cs
index 91c816df5c9..a19537be1c8 100644
--- a/Content.Shared/Ninja/Components/SpaceNinjaComponent.cs
+++ b/Content.Shared/Ninja/Components/SpaceNinjaComponent.cs
@@ -7,34 +7,28 @@ namespace Content.Shared.Ninja.Components;
///
/// 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.
///
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
[Access(typeof(SharedSpaceNinjaSystem))]
public sealed partial class SpaceNinjaComponent : Component
{
- ///
- /// The ninja game rule that spawned this ninja.
- ///
- [DataField("rule")]
- public EntityUid? Rule;
-
///
/// Currently worn suit
///
- [DataField("suit"), AutoNetworkedField]
+ [DataField, AutoNetworkedField]
public EntityUid? Suit;
///
- /// Currently worn gloves
+ /// Currently worn gloves, if enabled.
///
- [DataField("gloves"), AutoNetworkedField]
+ [DataField, AutoNetworkedField]
public EntityUid? Gloves;
///
/// Bound katana, set once picked up and never removed
///
- [DataField("katana"), AutoNetworkedField]
+ [DataField, AutoNetworkedField]
public EntityUid? Katana;
///
@@ -55,6 +49,9 @@ public sealed partial class SpaceNinjaComponent : Component
[DataField]
public EntProtoId SpiderChargeObjective = "SpiderChargeObjective";
+ ///
+ /// Alert to show for suit power.
+ ///
[DataField]
public ProtoId SuitPowerAlert = "SuitPower";
}
diff --git a/Content.Shared/Ninja/Components/SpiderChargeComponent.cs b/Content.Shared/Ninja/Components/SpiderChargeComponent.cs
index dacf47bb235..3ba4494cca4 100644
--- a/Content.Shared/Ninja/Components/SpiderChargeComponent.cs
+++ b/Content.Shared/Ninja/Components/SpiderChargeComponent.cs
@@ -1,3 +1,4 @@
+using Content.Shared.Ninja.Systems;
using Robust.Shared.GameStates;
namespace Content.Shared.Ninja.Components;
@@ -6,14 +7,14 @@ namespace Content.Shared.Ninja.Components;
/// Component for the Space Ninja's unique Spider Charge.
/// Only this component detonating can trigger the ninja's objective.
///
-[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;
}
diff --git a/Content.Shared/Ninja/Components/StunProviderComponent.cs b/Content.Shared/Ninja/Components/StunProviderComponent.cs
index 37a27074a49..2da094291d7 100644
--- a/Content.Shared/Ninja/Components/StunProviderComponent.cs
+++ b/Content.Shared/Ninja/Components/StunProviderComponent.cs
@@ -3,7 +3,6 @@
using Content.Shared.Whitelist;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Shared.Ninja.Components;
@@ -11,32 +10,33 @@ namespace Content.Shared.Ninja.Components;
/// Component for stunning mobs on click outside of harm mode.
/// Knocks them down for a bit and deals shock damage.
///
-[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedStunProviderSystem))]
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+[Access(typeof(SharedStunProviderSystem))]
public sealed partial class StunProviderComponent : Component
{
///
/// The powercell entity to take power from.
/// Determines whether stunning is possible.
///
- [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
+ [DataField, AutoNetworkedField]
public EntityUid? BatteryUid;
///
/// Sound played when stunning someone.
///
- [DataField, ViewVariables(VVAccess.ReadWrite)]
+ [DataField]
public SoundSpecifier Sound = new SoundCollectionSpecifier("sparks");
///
/// Joules required in the battery to stun someone. Defaults to 10 uses on a small battery.
///
- [DataField, ViewVariables(VVAccess.ReadWrite)]
+ [DataField]
public float StunCharge = 36f;
///
/// Damage dealt when stunning someone
///
- [DataField, ViewVariables(VVAccess.ReadWrite)]
+ [DataField]
public DamageSpecifier StunDamage = new()
{
DamageDict = new()
@@ -48,34 +48,30 @@ public sealed partial class StunProviderComponent : Component
///
/// Time that someone is stunned for, stacks if done multiple times.
///
- [DataField, ViewVariables(VVAccess.ReadWrite)]
+ [DataField]
public TimeSpan StunTime = TimeSpan.FromSeconds(5);
///
/// How long stunning is disabled after stunning something.
///
- [DataField, ViewVariables(VVAccess.ReadWrite)]
+ [DataField]
public TimeSpan Cooldown = TimeSpan.FromSeconds(2);
///
- /// Locale string to popup when there is no power
+ /// ID of the cooldown use delay.
///
- [DataField(required: true), ViewVariables(VVAccess.ReadWrite)]
- public string NoPowerPopup = string.Empty;
+ [DataField]
+ public string DelayId = "stun_cooldown";
///
- /// Whitelist for what counts as a mob.
+ /// Locale string to popup when there is no power
///
- [DataField]
- public EntityWhitelist Whitelist = new()
- {
- Components = new[] {"Stamina"}
- };
+ [DataField(required: true)]
+ public LocId NoPowerPopup = string.Empty;
///
- /// When someone can next be stunned.
- /// Essentially a UseDelay unique to this component.
+ /// Whitelist for what counts as a mob.
///
- [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
- public TimeSpan NextStun = TimeSpan.Zero;
+ [DataField(required: true)]
+ public EntityWhitelist Whitelist = new();
}
diff --git a/Content.Shared/Ninja/Systems/DashAbilitySystem.cs b/Content.Shared/Ninja/Systems/DashAbilitySystem.cs
index 4853968b61f..1385219e473 100644
--- a/Content.Shared/Ninja/Systems/DashAbilitySystem.cs
+++ b/Content.Shared/Ninja/Systems/DashAbilitySystem.cs
@@ -16,6 +16,7 @@ namespace Content.Shared.Ninja.Systems;
///
public sealed class DashAbilitySystem : EntitySystem
{
+ [Dependency] private readonly ActionContainerSystem _actionContainer = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedChargesSystem _charges = default!;
@@ -23,48 +24,40 @@ public sealed class DashAbilitySystem : EntitySystem
[Dependency] private readonly ExamineSystemShared _examine = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
- [Dependency] private readonly ActionContainerSystem _actionContainer = default!;
public override void Initialize()
{
base.Initialize();
- SubscribeLocalEvent(OnGetItemActions);
+ SubscribeLocalEvent(OnGetActions);
SubscribeLocalEvent(OnDash);
SubscribeLocalEvent(OnMapInit);
}
- private void OnMapInit(EntityUid uid, DashAbilityComponent component, MapInitEvent args)
+ private void OnMapInit(Entity 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 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);
}
///
/// Handle charges and teleport to a visible location.
///
- private void OnDash(EntityUid uid, DashAbilityComponent comp, DashEvent args)
+ private void OnDash(Entity ent, ref DashEvent args)
{
if (!_timing.IsFirstTimePredicted)
return;
+ var (uid, comp) = ent;
var user = args.Performer;
- args.Handled = true;
-
- var ev = new DashAttemptEvent(user);
- RaiseLocalEvent(uid, ev);
- if (ev.Cancelled)
+ if (!CheckDash(uid, user))
return;
if (!_hands.IsHolding(user, uid, out var _))
@@ -73,15 +66,8 @@ private void OnDash(EntityUid uid, DashAbilityComponent comp, DashEvent args)
return;
}
- TryComp(uid, out var charges);
- if (_charges.IsEmpty(uid, charges))
- {
- _popup.PopupClient(Loc.GetString("dash-ability-no-charges", ("item", uid)), user, user);
- return;
- }
var origin = _transform.GetMapCoordinates(user);
var target = args.Target.ToMap(EntityManager, _transform);
- // prevent collision with the user duh
if (!_examine.InRangeUnOccluded(origin, target, SharedInteractionSystem.MaxRaycastRange, null))
{
// can only dash if the destination is visible on screen
@@ -89,36 +75,28 @@ private void OnDash(EntityUid uid, DashAbilityComponent comp, DashEvent args)
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;
+ }
-///
-/// Raised on the item before adding the dash action
-///
-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;
}
}
///
-/// Raised on the item before dashing is done.
+/// Raised on the item before adding the dash action and when using the action.
///
-public sealed class DashAttemptEvent : CancellableEntityEventArgs
-{
- public EntityUid User;
-
- public DashAttemptEvent(EntityUid user)
- {
- User = user;
- }
-}
+[ByRefEvent]
+public record struct CheckDashEvent(EntityUid User, bool Cancelled = false);
diff --git a/Content.Shared/Ninja/Systems/EmagProviderSystem.cs b/Content.Shared/Ninja/Systems/EmagProviderSystem.cs
index df9cf8ac825..ae0bacaf5f6 100644
--- a/Content.Shared/Ninja/Systems/EmagProviderSystem.cs
+++ b/Content.Shared/Ninja/Systems/EmagProviderSystem.cs
@@ -1,6 +1,6 @@
using Content.Shared.Administration.Logs;
-using Content.Shared.Emag.Systems;
using Content.Shared.Database;
+using Content.Shared.Emag.Systems;
using Content.Shared.Interaction;
using Content.Shared.Ninja.Components;
using Content.Shared.Tag;
@@ -14,9 +14,10 @@ namespace Content.Shared.Ninja.Systems;
public sealed class EmagProviderSystem : EntitySystem
{
[Dependency] private readonly EmagSystem _emag = default!;
+ [Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly SharedNinjaGlovesSystem _gloves = default!;
- [Dependency] private readonly TagSystem _tags = default!;
+ [Dependency] private readonly TagSystem _tag = default!;
public override void Initialize()
{
@@ -28,18 +29,20 @@ public override void Initialize()
///
/// Emag clicked entities that are on the whitelist.
///
- private void OnBeforeInteractHand(EntityUid uid, EmagProviderComponent comp, BeforeInteractHandEvent args)
+ private void OnBeforeInteractHand(Entity 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 (comp.Whitelist != null && !comp.Whitelist.IsValid(target, EntityManager))
+ 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);
@@ -51,18 +54,6 @@ private void OnBeforeInteractHand(EntityUid uid, EmagProviderComponent comp, Bef
RaiseLocalEvent(uid, ref ev);
args.Handled = true;
}
-
- ///
- /// Set the whitelist for emagging something outside of yaml.
- ///
- public void SetWhitelist(EntityUid uid, EntityWhitelist? whitelist, EmagProviderComponent? comp = null)
- {
- if (!Resolve(uid, ref comp))
- return;
-
- comp.Whitelist = whitelist;
- Dirty(uid, comp);
- }
}
///
diff --git a/Content.Shared/Ninja/Systems/EnergyKatanaSystem.cs b/Content.Shared/Ninja/Systems/EnergyKatanaSystem.cs
index d427ffa39b4..281b97a648a 100644
--- a/Content.Shared/Ninja/Systems/EnergyKatanaSystem.cs
+++ b/Content.Shared/Ninja/Systems/EnergyKatanaSystem.cs
@@ -15,33 +15,20 @@ public override void Initialize()
base.Initialize();
SubscribeLocalEvent(OnEquipped);
- SubscribeLocalEvent(OnAddDashAction);
- SubscribeLocalEvent(OnDashAttempt);
+ SubscribeLocalEvent(OnCheckDash);
}
///
/// When equipped by a ninja, try to bind it.
///
- private void OnEquipped(EntityUid uid, EnergyKatanaComponent comp, GotEquippedEvent args)
+ private void OnEquipped(Entity ent, ref GotEquippedEvent args)
{
- // check if user isnt a ninja or already has a katana bound
- var user = args.Equipee;
- if (!TryComp(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(args.User))
- args.Cancel();
+ _ninja.BindKatana(args.Equipee, ent);
}
- private void OnDashAttempt(EntityUid uid, EnergyKatanaComponent comp, DashAttemptEvent args)
+ private void OnCheckDash(Entity ent, ref CheckDashEvent args)
{
- if (!TryComp(args.User, out var ninja) || ninja.Katana != uid)
- args.Cancel();
+ if (!_ninja.IsNinja(args.User))
+ args.Cancelled = true;
}
}
diff --git a/Content.Shared/Ninja/Systems/ItemCreatorSystem.cs b/Content.Shared/Ninja/Systems/ItemCreatorSystem.cs
new file mode 100644
index 00000000000..56112e9a697
--- /dev/null
+++ b/Content.Shared/Ninja/Systems/ItemCreatorSystem.cs
@@ -0,0 +1,56 @@
+using Content.Shared.Actions;
+using Content.Shared.Ninja.Components;
+
+namespace Content.Shared.Ninja.Systems;
+
+///
+/// Handles predicting that the action exists, creating items is done serverside.
+///
+public abstract class SharedItemCreatorSystem : EntitySystem
+{
+ [Dependency] private readonly ActionContainerSystem _actionContainer = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnMapInit);
+ SubscribeLocalEvent(OnGetActions);
+ }
+
+ private void OnMapInit(Entity 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 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;
+ }
+}
+
+///
+/// Raised on the item creator before adding the action.
+///
+[ByRefEvent]
+public record struct CheckItemCreatorEvent(EntityUid User, bool Cancelled = false);
+
+///
+/// Raised on the item creator before creating an item.
+///
+[ByRefEvent]
+public record struct CreateItemAttemptEvent(EntityUid User, bool Cancelled = false);
diff --git a/Content.Shared/Ninja/Systems/SharedBatteryDrainerSystem.cs b/Content.Shared/Ninja/Systems/SharedBatteryDrainerSystem.cs
index ac11063eb71..0abcca7d1bd 100644
--- a/Content.Shared/Ninja/Systems/SharedBatteryDrainerSystem.cs
+++ b/Content.Shared/Ninja/Systems/SharedBatteryDrainerSystem.cs
@@ -18,34 +18,32 @@ public override void Initialize()
}
///
- /// 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.
///
- protected virtual void OnDoAfterAttempt(EntityUid uid, BatteryDrainerComponent comp, DoAfterAttemptEvent args)
+ protected virtual void OnDoAfterAttempt(Entity ent, ref DoAfterAttemptEvent args)
{
- if (comp.BatteryUid == null)
- {
+ if (ent.Comp.BatteryUid == null)
args.Cancel();
- }
}
///
/// Drain power from a power source (on server) and repeat if it succeeded.
/// Client will predict always succeeding since power is serverside.
///
- private void OnDoAfter(EntityUid uid, BatteryDrainerComponent comp, DrainDoAfterEvent args)
+ private void OnDoAfter(Entity 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);
}
///
/// 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.
///
- protected virtual bool TryDrainPower(EntityUid uid, BatteryDrainerComponent comp, EntityUid target)
+ protected virtual bool TryDrainPower(Entity ent, EntityUid target)
{
return true;
}
@@ -53,12 +51,13 @@ protected virtual bool TryDrainPower(EntityUid uid, BatteryDrainerComponent comp
///
/// Sets the battery field on the drainer.
///
- public void SetBattery(EntityUid uid, EntityUid? battery, BatteryDrainerComponent? comp = null)
+ public void SetBattery(Entity ent, EntityUid? battery)
{
- if (!Resolve(uid, ref comp))
+ if (!Resolve(ent, ref ent.Comp) || ent.Comp.BatteryUid == battery)
return;
- comp.BatteryUid = battery;
+ ent.Comp.BatteryUid = battery;
+ Dirty(ent, ent.Comp);
}
}
@@ -66,4 +65,4 @@ public void SetBattery(EntityUid uid, EntityUid? battery, BatteryDrainerComponen
/// DoAfter event for .
///
[Serializable, NetSerializable]
-public sealed partial class DrainDoAfterEvent : SimpleDoAfterEvent { }
+public sealed partial class DrainDoAfterEvent : SimpleDoAfterEvent;
diff --git a/Content.Shared/Ninja/Systems/SharedNinjaGlovesSystem.cs b/Content.Shared/Ninja/Systems/SharedNinjaGlovesSystem.cs
index f61d0c6a908..8b892190b7b 100644
--- a/Content.Shared/Ninja/Systems/SharedNinjaGlovesSystem.cs
+++ b/Content.Shared/Ninja/Systems/SharedNinjaGlovesSystem.cs
@@ -1,15 +1,13 @@
-using Content.Shared.Actions;
+using Content.Shared.Clothing.Components;
using Content.Shared.CombatMode;
-using Content.Shared.Communications;
-using Content.Shared.CriminalRecords.Components;
using Content.Shared.Examine;
using Content.Shared.Hands.Components;
using Content.Shared.Interaction;
using Content.Shared.Inventory.Events;
+using Content.Shared.Item.ItemToggle;
+using Content.Shared.Item.ItemToggle.Components;
using Content.Shared.Ninja.Components;
using Content.Shared.Popups;
-using Content.Shared.Research.Components;
-using Content.Shared.Toggleable;
using Robust.Shared.Timing;
namespace Content.Shared.Ninja.Systems;
@@ -20,85 +18,105 @@ namespace Content.Shared.Ninja.Systems;
public abstract class SharedNinjaGlovesSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
- [Dependency] protected readonly SharedAppearanceSystem Appearance = default!;
[Dependency] private readonly SharedCombatModeSystem _combatMode = default!;
- [Dependency] protected readonly SharedInteractionSystem Interaction = default!;
- [Dependency] protected readonly SharedPopupSystem Popup = default!;
- [Dependency] private readonly ActionContainerSystem _actionContainer = default!;
+ [Dependency] private readonly SharedInteractionSystem _interaction = default!;
+ [Dependency] private readonly ItemToggleSystem _toggle = default!;
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
+ [Dependency] private readonly SharedSpaceNinjaSystem _ninja = default!;
public override void Initialize()
{
base.Initialize();
- SubscribeLocalEvent(OnGetItemActions);
+ SubscribeLocalEvent(OnToggleCheck);
+ SubscribeLocalEvent(OnActivateAttempt);
+ SubscribeLocalEvent(OnToggled);
SubscribeLocalEvent(OnExamined);
- SubscribeLocalEvent(OnUnequipped);
- SubscribeLocalEvent(OnMapInit);
- }
-
- private void OnMapInit(EntityUid uid, NinjaGlovesComponent component, MapInitEvent args)
- {
- _actionContainer.EnsureAction(uid, ref component.ToggleActionEntity, component.ToggleAction);
- Dirty(uid, component);
}
///
/// Disable glove abilities and show the popup if they were enabled previously.
///
- public void DisableGloves(EntityUid uid, NinjaGlovesComponent? comp = null)
+ private void DisableGloves(Entity 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(user);
- RemComp(user);
- RemComp(user);
- RemComp(user);
- RemComp(user);
- RemComp(user);
+ foreach (var ability in comp.Abilities)
+ {
+ EntityManager.RemoveComponents(user, ability.Components);
+ }
}
///
- /// Adds the toggle action when equipped.
+ /// Adds the toggle action when equipped by a ninja only.
///
- private void OnGetItemActions(EntityUid uid, NinjaGlovesComponent comp, GetItemActionsEvent args)
+ private void OnToggleCheck(Entity ent, ref ToggleClothingCheckEvent args)
{
- if (HasComp(args.User))
- args.AddAction(ref comp.ToggleActionEntity, comp.ToggleAction);
+ if (!_ninja.IsNinja(args.User))
+ args.Cancelled = true;
}
///
/// Show if the gloves are enabled when examining.
///
- private void OnExamined(EntityUid uid, NinjaGlovesComponent comp, ExaminedEvent args)
+ private void OnExamined(Entity 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}"));
}
- ///
- /// Disable gloves when unequipped and clean up ninja's gloves reference
- ///
- private void OnUnequipped(EntityUid uid, NinjaGlovesComponent comp, GotUnequippedEvent args)
+ private void OnActivateAttempt(Entity 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(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 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 ent, Entity 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
///
@@ -112,6 +130,6 @@ public bool AbilityCheck(EntityUid uid, BeforeInteractHandEvent args, out Entity
&& !_combatMode.IsInCombatMode(uid)
&& TryComp(uid, out var hands)
&& hands.ActiveHandEntity == null
- && Interaction.InRangeUnobstructed(uid, target);
+ && _interaction.InRangeUnobstructed(uid, target);
}
}
diff --git a/Content.Shared/Ninja/Systems/SharedNinjaSuitSystem.cs b/Content.Shared/Ninja/Systems/SharedNinjaSuitSystem.cs
index fed41eaed8a..3800d15b267 100644
--- a/Content.Shared/Ninja/Systems/SharedNinjaSuitSystem.cs
+++ b/Content.Shared/Ninja/Systems/SharedNinjaSuitSystem.cs
@@ -1,11 +1,14 @@
using Content.Shared.Actions;
+using Content.Shared.Clothing;
using Content.Shared.Clothing.Components;
using Content.Shared.Clothing.EntitySystems;
using Content.Shared.Inventory.Events;
+using Content.Shared.Item.ItemToggle;
+using Content.Shared.Item.ItemToggle.Components;
using Content.Shared.Ninja.Components;
using Content.Shared.Popups;
+using Content.Shared.Timing;
using Robust.Shared.Audio.Systems;
-using Robust.Shared.Timing;
namespace Content.Shared.Ninja.Systems;
@@ -14,137 +17,158 @@ namespace Content.Shared.Ninja.Systems;
///
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(OnMapInit);
-
- SubscribeLocalEvent(OnEquipped);
+ SubscribeLocalEvent(OnEquipped);
SubscribeLocalEvent(OnGetItemActions);
- SubscribeLocalEvent(OnAddStealthAction);
+ SubscribeLocalEvent(OnCloakCheck);
+ SubscribeLocalEvent(OnStarCheck);
+ SubscribeLocalEvent(OnCreateStarAttempt);
+ SubscribeLocalEvent(OnActivateAttempt);
SubscribeLocalEvent(OnUnequipped);
}
- private void OnMapInit(EntityUid uid, NinjaSuitComponent component, MapInitEvent args)
+ private void OnEquipped(Entity 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));
}
- ///
- /// Call the shared and serverside code for when a ninja equips the suit.
- ///
- private void OnEquipped(EntityUid uid, NinjaSuitComponent comp, GotEquippedEvent args)
+ protected virtual void NinjaEquipped(Entity ent, Entity user)
{
- var user = args.Equipee;
- if (!TryComp(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 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);
}
///
/// Add all the actions when a suit is equipped by a ninja.
///
- private void OnGetItemActions(EntityUid uid, NinjaSuitComponent comp, GetItemActionsEvent args)
+ private void OnGetItemActions(Entity ent, ref GetItemActionsEvent args)
{
- if (!HasComp(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);
}
///
- /// Only add stealth clothing's toggle action when equipped by a ninja.
+ /// Only add toggle cloak action when equipped by a ninja.
///
- private void OnAddStealthAction(EntityUid uid, NinjaSuitComponent comp, AddStealthActionEvent args)
+ private void OnCloakCheck(Entity ent, ref ToggleClothingCheckEvent args)
{
- if (!HasComp(args.User))
- args.Cancel();
+ if (!_ninja.IsNinja(args.User))
+ args.Cancelled = true;
}
- ///
- /// Call the shared and serverside code for when anyone unequips a suit.
- ///
- private void OnUnequipped(EntityUid uid, NinjaSuitComponent comp, GotUnequippedEvent args)
+ private void OnStarCheck(Entity ent, ref CheckItemCreatorEvent args)
+ {
+ if (!_ninja.IsNinja(args.User))
+ args.Cancelled = true;
+ }
+
+ private void OnCreateStarAttempt(Entity ent, ref CreateItemAttemptEvent args)
{
- UserUnequippedSuit(uid, comp, args.Equipee);
+ if (CheckDisabled(ent, args.User))
+ args.Cancelled = true;
}
///
- /// 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.
///
- protected virtual void NinjaEquippedSuit(EntityUid uid, NinjaSuitComponent comp, EntityUid user, SpaceNinjaComponent ninja)
+ private void OnUnequipped(Entity 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));
}
///
/// Force uncloaks the user and disables suit abilities.
///
- public void RevealNinja(EntityUid uid, EntityUid user, bool disable = true, NinjaSuitComponent? comp = null, StealthClothingComponent? stealthClothing = null)
+ public void RevealNinja(Entity 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 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
///
- /// Returns the power used by a suit
+ /// Returns true if the suit is currently disabled
///
- public float SuitWattage(EntityUid uid, NinjaSuitComponent? suit = null)
+ public bool IsDisabled(Entity 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 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(uid, out var stealthClothing) && stealthClothing.Enabled)
- wattage += suit.CloakWattage;
- return wattage;
+ return false;
}
///
/// 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.
///
- protected virtual void UserUnequippedSuit(EntityUid uid, NinjaSuitComponent comp, EntityUid user)
+ protected virtual void UserUnequippedSuit(Entity ent, Entity user)
{
- if (!TryComp(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(ninja.Gloves.Value, out var gloves))
- _gloves.DisableGloves(ninja.Gloves.Value, gloves);
+ if (user.Comp.Gloves is {} uid)
+ _toggle.TryDeactivate(uid, user: user);
}
}
diff --git a/Content.Shared/Ninja/Systems/SharedSpaceNinjaSystem.cs b/Content.Shared/Ninja/Systems/SharedSpaceNinjaSystem.cs
index 522f29fe420..d738f2dd8a2 100644
--- a/Content.Shared/Ninja/Systems/SharedSpaceNinjaSystem.cs
+++ b/Content.Shared/Ninja/Systems/SharedSpaceNinjaSystem.cs
@@ -3,6 +3,7 @@
using Content.Shared.Weapons.Melee.Events;
using Content.Shared.Weapons.Ranged.Events;
using Content.Shared.Popups;
+using System.Diagnostics.CodeAnalysis;
namespace Content.Shared.Ninja.Systems;
@@ -14,49 +15,59 @@ public abstract class SharedSpaceNinjaSystem : EntitySystem
[Dependency] protected readonly SharedNinjaSuitSystem Suit = default!;
[Dependency] protected readonly SharedPopupSystem Popup = default!;
+ public EntityQuery NinjaQuery;
+
public override void Initialize()
{
base.Initialize();
+ NinjaQuery = GetEntityQuery();
+
SubscribeLocalEvent(OnNinjaAttacked);
SubscribeLocalEvent(OnNinjaAttack);
SubscribeLocalEvent(OnShotAttempted);
}
+ public bool IsNinja([NotNullWhen(true)] EntityUid? uid)
+ {
+ return NinjaQuery.HasComp(uid);
+ }
+
///
/// Set the ninja's worn suit entity
///
- public void AssignSuit(EntityUid uid, EntityUid? suit, SpaceNinjaComponent? comp = null)
+ public void AssignSuit(Entity 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);
}
///
/// Set the ninja's worn gloves entity
///
- public void AssignGloves(EntityUid uid, EntityUid? gloves, SpaceNinjaComponent? comp = null)
+ public void AssignGloves(Entity 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);
}
///
/// 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.
///
- public void BindKatana(EntityUid uid, EntityUid? katana, SpaceNinjaComponent? comp = null)
+ public void BindKatana(Entity 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);
}
///
@@ -71,32 +82,32 @@ public virtual bool TryUseCharge(EntityUid user, float charge)
///
/// Handle revealing ninja if cloaked when attacked.
///
- private void OnNinjaAttacked(EntityUid uid, SpaceNinjaComponent comp, AttackedEvent args)
+ private void OnNinjaAttacked(Entity ent, ref AttackedEvent args)
{
- if (comp.Suit != null && TryComp(comp.Suit, out var stealthClothing) && stealthClothing.Enabled)
- {
- Suit.RevealNinja(comp.Suit.Value, uid, true, null, stealthClothing);
- }
+ TryRevealNinja(ent, disable: true);
}
///
/// Handle revealing ninja if cloaked when attacking.
/// Only reveals, there is no cooldown.
///
- private void OnNinjaAttack(EntityUid uid, SpaceNinjaComponent comp, ref MeleeAttackEvent args)
+ private void OnNinjaAttack(Entity ent, ref MeleeAttackEvent args)
+ {
+ TryRevealNinja(ent, disable: false);
+ }
+
+ private void TryRevealNinja(Entity ent, bool disable)
{
- if (comp.Suit != null && TryComp(comp.Suit, out var stealthClothing) && stealthClothing.Enabled)
- {
- Suit.RevealNinja(comp.Suit.Value, uid, false, null, stealthClothing);
- }
+ if (ent.Comp.Suit is {} uid && TryComp(ent.Comp.Suit, out var suit))
+ Suit.RevealNinja((uid, suit), ent, disable: disable);
}
///
/// Require ninja to fight with HONOR, no guns!
///
- private void OnShotAttempted(EntityUid uid, SpaceNinjaComponent comp, ref ShotAttemptedEvent args)
+ private void OnShotAttempted(Entity ent, ref ShotAttemptedEvent args)
{
- Popup.PopupClient(Loc.GetString("gun-disabled"), uid, uid);
+ Popup.PopupClient(Loc.GetString("gun-disabled"), ent, ent);
args.Cancel();
}
}
diff --git a/Content.Shared/Ninja/Systems/SharedSpiderChargeSystem.cs b/Content.Shared/Ninja/Systems/SharedSpiderChargeSystem.cs
new file mode 100644
index 00000000000..f4b158aced4
--- /dev/null
+++ b/Content.Shared/Ninja/Systems/SharedSpiderChargeSystem.cs
@@ -0,0 +1,6 @@
+namespace Content.Shared.Ninja.Systems;
+
+///
+/// Sticking triggering and exploding are all in server so this is just for access.
+///
+public abstract class SharedSpiderChargeSystem : EntitySystem;
diff --git a/Content.Shared/Ninja/Systems/SharedStunProviderSystem.cs b/Content.Shared/Ninja/Systems/SharedStunProviderSystem.cs
index 61b6e4313ed..061c019c9b6 100644
--- a/Content.Shared/Ninja/Systems/SharedStunProviderSystem.cs
+++ b/Content.Shared/Ninja/Systems/SharedStunProviderSystem.cs
@@ -11,22 +11,12 @@ public abstract class SharedStunProviderSystem : EntitySystem
///
/// Set the battery field on the stun provider.
///
- public void SetBattery(EntityUid uid, EntityUid? battery, StunProviderComponent? comp = null)
+ public void SetBattery(Entity ent, EntityUid? battery)
{
- if (!Resolve(uid, ref comp))
+ if (!Resolve(ent, ref ent.Comp) || ent.Comp.BatteryUid == battery)
return;
- comp.BatteryUid = battery;
- }
-
- ///
- /// Set the no power popup field on the stun provider.
- ///
- 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);
}
}
diff --git a/Content.Shared/Objectives/Systems/SharedObjectivesSystem.cs b/Content.Shared/Objectives/Systems/SharedObjectivesSystem.cs
index 07032a00ce9..8d2c4dcfebe 100644
--- a/Content.Shared/Objectives/Systems/SharedObjectivesSystem.cs
+++ b/Content.Shared/Objectives/Systems/SharedObjectivesSystem.cs
@@ -92,7 +92,7 @@ public bool CanBeAssigned(EntityUid uid, EntityUid mindId, MindComponent mind, O
}
///
- /// Get the title, description, icon and progress of an objective using .
+ /// Get the title, description, icon and progress of an objective using .
/// If any of them are null it is logged and null is returned.
///
/// ID of the condition entity
@@ -103,20 +103,43 @@ public bool CanBeAssigned(EntityUid uid, EntityUid mindId, MindComponent mind, O
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(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);
+ }
+
+ ///
+ /// Gets the progress of an objective using .
+ /// Returning null is a programmer error.
+ ///
+ public float? GetProgress(EntityUid uid, Entity 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;
+ }
+
+ ///
+ /// Returns true if an objective is completed.
+ ///
+ public bool IsCompleted(EntityUid uid, Entity mind)
+ {
+ return (GetProgress(uid, mind) ?? 0f) >= 0.999f;
}
///
diff --git a/Content.Shared/Pinpointer/SharedProximityBeeper.cs b/Content.Shared/Pinpointer/SharedProximityBeeper.cs
deleted file mode 100644
index 51631126833..00000000000
--- a/Content.Shared/Pinpointer/SharedProximityBeeper.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using Robust.Shared.Serialization;
-
-namespace Content.Shared.Pinpointer;
-
-[Serializable, NetSerializable]
-public enum ProximityBeeperVisuals : byte
-{
- Enabled
-}
diff --git a/Content.Shared/PowerCell/PowerCellDrawComponent.cs b/Content.Shared/PowerCell/PowerCellDrawComponent.cs
index 708a86a8eaf..94de7c77878 100644
--- a/Content.Shared/PowerCell/PowerCellDrawComponent.cs
+++ b/Content.Shared/PowerCell/PowerCellDrawComponent.cs
@@ -6,6 +6,10 @@ namespace Content.Shared.PowerCell;
///
/// Indicates that the entity's ActivatableUI requires power or else it closes.
///
+///
+/// With ActivatableUI it will activate and deactivate when the ui is opened and closed, drawing power inbetween.
+/// Requires to work.
+///
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
public sealed partial class PowerCellDrawComponent : Component
{
@@ -26,10 +30,12 @@ public sealed partial class PowerCellDrawComponent : Component
#endregion
///
- /// 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.
///
- [ViewVariables(VVAccess.ReadWrite), DataField("enabled")]
- public bool Drawing;
+ [DataField, AutoNetworkedField]
+ public bool Enabled = true;
///
/// How much the entity draws while the UI is open.
@@ -51,4 +57,10 @@ public sealed partial class PowerCellDrawComponent : Component
[DataField("nextUpdate", customTypeSerializer: typeof(TimeOffsetSerializer))]
[AutoPausedField]
public TimeSpan NextUpdateTime;
+
+ ///
+ /// How long to wait between power drawing.
+ ///
+ [DataField]
+ public TimeSpan Delay = TimeSpan.FromSeconds(1);
}
diff --git a/Content.Shared/PowerCell/SharedPowerCellSystem.cs b/Content.Shared/PowerCell/SharedPowerCellSystem.cs
index 508bfc85f08..2b2a836633c 100644
--- a/Content.Shared/PowerCell/SharedPowerCellSystem.cs
+++ b/Content.Shared/PowerCell/SharedPowerCellSystem.cs
@@ -1,4 +1,6 @@
using Content.Shared.Containers.ItemSlots;
+using Content.Shared.Item.ItemToggle;
+using Content.Shared.Item.ItemToggle.Components;
using Content.Shared.PowerCell.Components;
using Content.Shared.Rejuvenate;
using Robust.Shared.Containers;
@@ -11,14 +13,19 @@ public abstract class SharedPowerCellSystem : EntitySystem
[Dependency] protected readonly IGameTiming Timing = default!;
[Dependency] private readonly ItemSlotsSystem _itemSlots = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+ [Dependency] protected readonly ItemToggleSystem Toggle = default!;
public override void Initialize()
{
base.Initialize();
+
SubscribeLocalEvent(OnRejuvenate);
SubscribeLocalEvent(OnCellInserted);
SubscribeLocalEvent(OnCellRemoved);
SubscribeLocalEvent(OnCellInsertAttempt);
+
+ SubscribeLocalEvent(OnActivateAttempt);
+ SubscribeLocalEvent(OnToggled);
}
private void OnRejuvenate(EntityUid uid, PowerCellSlotComponent component, RejuvenateEvent args)
@@ -63,13 +70,25 @@ protected virtual void OnCellRemoved(EntityUid uid, PowerCellSlotComponent compo
RaiseLocalEvent(uid, new PowerCellChangedEvent(true), false);
}
- public void SetPowerCellDrawEnabled(EntityUid uid, bool enabled, PowerCellDrawComponent? component = null)
+ private void OnActivateAttempt(Entity 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 ent, ref ItemToggledEvent args)
+ {
+ ent.Comp.NextUpdateTime = Timing.CurTime;
+ }
+
+ public void SetDrawEnabled(Entity 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);
}
///
diff --git a/Content.Shared/ProximityDetection/Components/ProximityDetectorComponent.cs b/Content.Shared/ProximityDetection/Components/ProximityDetectorComponent.cs
index 09cb7f06d5d..7e2bb4dfe62 100644
--- a/Content.Shared/ProximityDetection/Components/ProximityDetectorComponent.cs
+++ b/Content.Shared/ProximityDetection/Components/ProximityDetectorComponent.cs
@@ -10,12 +10,6 @@ namespace Content.Shared.ProximityDetection.Components;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState ,Access(typeof(ProximityDetectionSystem))]
public sealed partial class ProximityDetectorComponent : Component
{
- ///
- /// Whether or not it's on.
- ///
- [DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)]
- public bool Enabled = true;
-
///
/// The criteria used to filter entities
/// Note: RequireAll is only supported for tags, all components are required to count as a match!
@@ -35,13 +29,13 @@ public sealed partial class ProximityDetectorComponent : Component
[ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
public FixedPoint2 Distance = -1;
-
///
/// The farthest distance to search for targets
///
[DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
public FixedPoint2 Range = 10f;
+ // TODO: use timespans not this
public float AccumulatedFrameTime;
[DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
diff --git a/Content.Shared/ProximityDetection/Systems/ProximityDetectionSystem.cs b/Content.Shared/ProximityDetection/Systems/ProximityDetectionSystem.cs
index db25e8bc511..df302f94771 100644
--- a/Content.Shared/ProximityDetection/Systems/ProximityDetectionSystem.cs
+++ b/Content.Shared/ProximityDetection/Systems/ProximityDetectionSystem.cs
@@ -1,4 +1,6 @@
-using Content.Shared.ProximityDetection.Components;
+using Content.Shared.Item.ItemToggle;
+using Content.Shared.Item.ItemToggle.Components;
+using Content.Shared.ProximityDetection.Components;
using Content.Shared.Tag;
using Robust.Shared.Network;
@@ -9,6 +11,7 @@ namespace Content.Shared.ProximityDetection.Systems;
public sealed class ProximityDetectionSystem : EntitySystem
{
[Dependency] private readonly EntityLookupSystem _entityLookup = default!;
+ [Dependency] private readonly ItemToggleSystem _toggle = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly TagSystem _tagSystem = default!;
[Dependency] private readonly INetManager _net = default!;
@@ -17,10 +20,10 @@ public sealed class ProximityDetectionSystem : EntitySystem
public override void Initialize()
{
- SubscribeLocalEvent(OnPaused);
- SubscribeLocalEvent(OnUnpaused);
- SubscribeLocalEvent(OnCompInit);
+ base.Initialize();
+ SubscribeLocalEvent(OnCompInit);
+ SubscribeLocalEvent(OnToggled);
}
private void OnCompInit(EntityUid uid, ProximityDetectorComponent component, ComponentInit args)
@@ -30,57 +33,39 @@ private void OnCompInit(EntityUid uid, ProximityDetectorComponent component, Com
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();
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 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)
@@ -90,11 +75,31 @@ public void ForceUpdate(EntityUid owner, ProximityDetectorComponent? detector =
RunUpdate_Internal(owner, detector);
}
+ private void ClearTarget(Entity 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();
var xform = xformQuery.GetComponent(owner);
List<(EntityUid TargetEnt, float Distance)> detections = new();
@@ -173,15 +178,7 @@ private void UpdateTargetFromClosest(EntityUid owner, ProximityDetectorComponent
{
if (detections.Count == 0)
{
- if (detector.TargetEnt == null)
- return;
- detector.Distance = -1;
- detector.TargetEnt = null;
- var noDetectEvent = new ProximityTargetUpdatedEvent(detector, null, -1);
- RaiseLocalEvent(owner, ref noDetectEvent);
- var newTargetEvent = new NewProximityTargetEvent(detector, null);
- RaiseLocalEvent(owner, ref newTargetEvent);
- Dirty(owner, detector);
+ ClearTarget((owner, detector));
return;
}
var closestDistance = detections[0].Distance;
@@ -198,6 +195,7 @@ private void UpdateTargetFromClosest(EntityUid owner, ProximityDetectorComponent
var newData = newTarget || detector.Distance != closestDistance;
detector.TargetEnt = closestEnt;
detector.Distance = closestDistance;
+ Dirty(owner, detector);
if (newTarget)
{
var newTargetEvent = new NewProximityTargetEvent(detector, closestEnt);
diff --git a/Content.Shared/Silicons/Borgs/Components/BorgChassisComponent.cs b/Content.Shared/Silicons/Borgs/Components/BorgChassisComponent.cs
index e1776873da9..de0fe0bce38 100644
--- a/Content.Shared/Silicons/Borgs/Components/BorgChassisComponent.cs
+++ b/Content.Shared/Silicons/Borgs/Components/BorgChassisComponent.cs
@@ -15,12 +15,6 @@ namespace Content.Shared.Silicons.Borgs.Components;
[RegisterComponent, NetworkedComponent, Access(typeof(SharedBorgSystem)), AutoGenerateComponentState]
public sealed partial class BorgChassisComponent : Component
{
- ///
- /// Whether or not the borg is activated, meaning it has access to modules and a heightened movement speed
- ///
- [DataField("activated"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
- public bool Activated;
-
#region Brain
///
/// A whitelist for which entities count as valid brains
@@ -68,7 +62,7 @@ public sealed partial class BorgChassisComponent : Component
///
/// The currently selected module
///
- [DataField("selectedModule")]
+ [DataField("selectedModule"), AutoNetworkedField]
public EntityUid? SelectedModule;
#region Visuals
diff --git a/Content.Shared/Silicons/Borgs/SharedBorgSystem.cs b/Content.Shared/Silicons/Borgs/SharedBorgSystem.cs
index 0431d95a42f..896d97bf52c 100644
--- a/Content.Shared/Silicons/Borgs/SharedBorgSystem.cs
+++ b/Content.Shared/Silicons/Borgs/SharedBorgSystem.cs
@@ -1,5 +1,6 @@
using Content.Shared.Access.Components;
using Content.Shared.Containers.ItemSlots;
+using Content.Shared.Item.ItemToggle;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems;
using Content.Shared.Popups;
@@ -17,6 +18,7 @@ public abstract partial class SharedBorgSystem : EntitySystem
{
[Dependency] protected readonly SharedContainerSystem Container = default!;
[Dependency] protected readonly ItemSlotsSystem ItemSlots = default!;
+ [Dependency] protected readonly ItemToggleSystem Toggle = default!;
[Dependency] protected readonly SharedPopupSystem Popup = default!;
///
@@ -87,7 +89,7 @@ protected virtual void OnRemoved(EntityUid uid, BorgChassisComponent component,
private void OnRefreshMovementSpeedModifiers(EntityUid uid, BorgChassisComponent component, RefreshMovementSpeedModifiersEvent args)
{
- if (component.Activated)
+ if (Toggle.IsActivated(uid))
return;
if (!TryComp(uid, out var movement))
diff --git a/Content.Shared/Toggleable/ToggleActionEvent.cs b/Content.Shared/Toggleable/ToggleActionEvent.cs
index 1283b6699bf..f28e62e7dd1 100644
--- a/Content.Shared/Toggleable/ToggleActionEvent.cs
+++ b/Content.Shared/Toggleable/ToggleActionEvent.cs
@@ -4,9 +4,12 @@
namespace Content.Shared.Toggleable;
///
-/// Generic action-event for toggle-able components.
+/// Generic action-event for toggle-able components.
///
-public sealed partial class ToggleActionEvent : InstantActionEvent { }
+///
+/// If you are using ItemToggleComponent subscribe to ItemToggledEvent instead.
+///
+public sealed partial class ToggleActionEvent : InstantActionEvent;
///
/// Generic enum keys for toggle-visualizer appearance data & sprite layers.
diff --git a/Content.Shared/Tools/Systems/SharedToolSystem.cs b/Content.Shared/Tools/Systems/SharedToolSystem.cs
index 0d7972a8de6..aa69d3db78a 100644
--- a/Content.Shared/Tools/Systems/SharedToolSystem.cs
+++ b/Content.Shared/Tools/Systems/SharedToolSystem.cs
@@ -24,7 +24,7 @@ public abstract partial class SharedToolSystem : EntitySystem
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] protected readonly SharedInteractionSystem InteractionSystem = default!;
- [Dependency] protected readonly SharedItemToggleSystem ItemToggle = default!;
+ [Dependency] protected readonly ItemToggleSystem ItemToggle = default!;
[Dependency] private readonly SharedMapSystem _maps = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] protected readonly SharedSolutionContainerSystem SolutionContainerSystem = default!;
diff --git a/Content.Shared/UserInterface/ActivatableUISystem.Power.cs b/Content.Shared/UserInterface/ActivatableUISystem.Power.cs
index b8a815c7a81..e494253c832 100644
--- a/Content.Shared/UserInterface/ActivatableUISystem.Power.cs
+++ b/Content.Shared/UserInterface/ActivatableUISystem.Power.cs
@@ -1,3 +1,5 @@
+using Content.Shared.Item.ItemToggle;
+using Content.Shared.Item.ItemToggle.Components;
using Content.Shared.PowerCell;
using Robust.Shared.Containers;
@@ -5,6 +7,7 @@ namespace Content.Shared.UserInterface;
public sealed partial class ActivatableUISystem
{
+ [Dependency] private readonly ItemToggleSystem _toggle = default!;
[Dependency] private readonly SharedPowerCellSystem _cell = default!;
private void InitializePower()
@@ -12,27 +15,22 @@ private void InitializePower()
SubscribeLocalEvent(OnBatteryOpenAttempt);
SubscribeLocalEvent(OnBatteryOpened);
SubscribeLocalEvent(OnBatteryClosed);
-
- SubscribeLocalEvent(OnPowerCellRemoved);
+ SubscribeLocalEvent(OnToggled);
}
- private void OnPowerCellRemoved(EntityUid uid, PowerCellDrawComponent component, EntRemovedFromContainerMessage args)
+ private void OnToggled(Entity ent, ref ItemToggledEvent args)
{
- _cell.SetPowerCellDrawEnabled(uid, false);
-
- if (!HasComp(uid) ||
- !TryComp(uid, out ActivatableUIComponent? activatable))
- {
+ // only close ui when losing power
+ if (!TryComp(ent, out var activatable) || args.Activated)
return;
- }
if (activatable.Key == null)
{
- Log.Error($"Encountered null key in activatable ui on entity {ToPrettyString(uid)}");
+ Log.Error($"Encountered null key in activatable ui on entity {ToPrettyString(ent)}");
return;
}
- _uiSystem.CloseUi(uid, activatable.Key);
+ _uiSystem.CloseUi(ent.Owner, activatable.Key);
}
private void OnBatteryOpened(EntityUid uid, ActivatableUIRequiresPowerCellComponent component, BoundUIOpenedEvent args)
@@ -42,7 +40,7 @@ private void OnBatteryOpened(EntityUid uid, ActivatableUIRequiresPowerCellCompon
if (!args.UiKey.Equals(activatable.Key))
return;
- _cell.SetPowerCellDrawEnabled(uid, true);
+ _toggle.TryActivate(uid);
}
private void OnBatteryClosed(EntityUid uid, ActivatableUIRequiresPowerCellComponent component, BoundUIClosedEvent args)
@@ -54,7 +52,7 @@ private void OnBatteryClosed(EntityUid uid, ActivatableUIRequiresPowerCellCompon
// 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);
}
///
diff --git a/Content.Shared/Weapons/Reflect/ReflectComponent.cs b/Content.Shared/Weapons/Reflect/ReflectComponent.cs
index 5d8432ac776..5edd445afcb 100644
--- a/Content.Shared/Weapons/Reflect/ReflectComponent.cs
+++ b/Content.Shared/Weapons/Reflect/ReflectComponent.cs
@@ -5,16 +5,11 @@ namespace Content.Shared.Weapons.Reflect;
///
/// Entities with this component have a chance to reflect projectiles and hitscan shots
+/// Uses ItemToggleComponent to control reflection.
///
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class ReflectComponent : Component
{
- ///
- /// Can only reflect when enabled
- ///
- [DataField("enabled"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
- public bool Enabled = true;
-
///
/// What we reflect.
///
diff --git a/Content.Shared/Weapons/Reflect/ReflectSystem.cs b/Content.Shared/Weapons/Reflect/ReflectSystem.cs
index 76c98a427f5..7f20974a144 100644
--- a/Content.Shared/Weapons/Reflect/ReflectSystem.cs
+++ b/Content.Shared/Weapons/Reflect/ReflectSystem.cs
@@ -9,6 +9,7 @@
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;
@@ -31,10 +32,11 @@ namespace Content.Shared.Weapons.Reflect;
///
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!;
@@ -102,10 +104,9 @@ private void OnReflectCollide(EntityUid uid, ReflectComponent component, ref Pro
private bool TryReflectProjectile(EntityUid user, EntityUid reflector, EntityUid projectile, ProjectileComponent? projectileComp = null, ReflectComponent? reflect = null)
{
- // Do we have the components needed to try a reflect at all?
if (
!Resolve(reflector, ref reflect, false) ||
- !reflect.Enabled ||
+ !_toggle.IsActivated(reflector) ||
!TryComp(projectile, out var reflective) ||
(reflect.Reflects & reflective.Reflective) == 0x0 ||
!TryComp(projectile, out var physics) ||
@@ -210,16 +211,10 @@ private bool TryReflectHitscan(
{
newDirection = null;
if (!TryComp(reflector, out var reflect) ||
- !reflect.Enabled ||
- TryComp(reflector, out var staminaComponent) && staminaComponent.Critical ||
- _standing.IsDown(reflector))
- return false;
-
- // Non cultists can't use cult items to reflect anything.
- if (HasComp(reflector) && !HasComp(user))
- return false;
-
- if (!_random.Prob(CalcReflectChance(reflector, reflect)))
+ !_toggle.IsActivated(reflector) ||
+ !_random.Prob(reflect.ReflectProb))
+ {
+ newDirection = null;
return false;
if (_netManager.IsServer)
@@ -273,13 +268,8 @@ private void OnReflectHandUnequipped(EntityUid uid, ReflectComponent component,
private void OnToggleReflect(EntityUid uid, ReflectComponent comp, ref ItemToggledEvent args)
{
- comp.Enabled = args.Activated;
- Dirty(uid, comp);
-
- if (comp.Enabled)
- EnableAlert(uid);
- else
- DisableAlert(uid);
+ if (args.User is {} user)
+ RefreshReflectUser(user);
}
///
@@ -289,7 +279,7 @@ private void RefreshReflectUser(EntityUid user)
{
foreach (var ent in _inventorySystem.GetHandOrInventoryEntities(user, SlotFlags.WITHOUT_POCKET))
{
- if (!HasComp(ent))
+ if (!HasComp(ent) || !_toggle.IsActivated(ent))
continue;
EnsureComp(user);
diff --git a/Resources/Prototypes/Actions/ninja.yml b/Resources/Prototypes/Actions/ninja.yml
index 51bfc33c494..3ad67844d66 100644
--- a/Resources/Prototypes/Actions/ninja.yml
+++ b/Resources/Prototypes/Actions/ninja.yml
@@ -2,7 +2,7 @@
- type: entity
id: ActionToggleNinjaGloves
name: Toggle ninja gloves
- description: Toggles all glove actions on left click. Includes your doorjack, draining power, stunning enemies, downloading research and calling in a threat.
+ description: Toggles all glove actions on left click. Includes your doorjack, draining power, stunning enemies and hacking certain computers.
categories: [ HideSpawnMenu ]
components:
- type: InstantAction
@@ -23,7 +23,7 @@
state: icon
itemIconStyle: NoItem
priority: -10
- event: !type:CreateThrowingStarEvent {}
+ event: !type:CreateItemEvent {}
- type: entity
id: ActionRecallKatana
@@ -64,7 +64,7 @@
# have to plan (un)cloaking ahead of time
useDelay: 5
priority: -9
- event: !type:ToggleStealthEvent
+ event: !type:ToggleActionEvent
# katana
- type: entity
@@ -78,6 +78,10 @@
sprite: Objects/Magic/magicactions.rsi
state: blink
itemIconStyle: NoItem
+ sound:
+ path: /Audio/Magic/blink.ogg
+ params:
+ volume: 5
priority: -12
event: !type:DashEvent
checkCanAccess: false
diff --git a/Resources/Prototypes/Entities/Clothing/Hands/gloves.yml b/Resources/Prototypes/Entities/Clothing/Hands/gloves.yml
index 2abe1f0a80f..748e3482a8e 100644
--- a/Resources/Prototypes/Entities/Clothing/Hands/gloves.yml
+++ b/Resources/Prototypes/Entities/Clothing/Hands/gloves.yml
@@ -206,7 +206,7 @@
- type: FingerprintMask
- type: entity
- parent: ClothingHandsBase
+ parent: [ClothingHandsBase, BaseToggleClothing]
id: ClothingHandsGlovesSpaceNinja
name: space ninja gloves
description: These black nano-enhanced gloves insulate from electricity and provide fire resistance.
@@ -233,7 +233,32 @@
- type: FingerprintMask
- 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
diff --git a/Resources/Prototypes/Entities/Clothing/Head/misc.yml b/Resources/Prototypes/Entities/Clothing/Head/misc.yml
index c7ba6e0b32a..10eaf1cb44e 100644
--- a/Resources/Prototypes/Entities/Clothing/Head/misc.yml
+++ b/Resources/Prototypes/Entities/Clothing/Head/misc.yml
@@ -172,6 +172,36 @@
- type: AddAccentClothing
accent: OwOAccent
+- type: entity
+ parent: [ClothingHeadHatCatEars, BaseToggleClothing]
+ id: ClothingHeadHatCatEarsValid
+ suffix: Valid, DO NOT MAP
+ components:
+ - type: ToggleClothing
+ action: ActionBecomeValid
+ disableOnUnequip: true
+ - type: ComponentToggler
+ parent: true
+ components:
+ - type: KillSign
+ - type: Tag
+ tags: [] # ignore "WhitelistChameleon" tag
+ - type: Sprite
+ sprite: Clothing/Head/Hats/catears.rsi
+ - type: Clothing
+ sprite: Clothing/Head/Hats/catears.rsi
+ - type: AddAccentClothing
+ accent: OwOAccent
+
+- type: entity
+ noSpawn: true
+ id: ActionBecomeValid
+ name: Become Valid
+ description: "*notices your killsign* owo whats this"
+ components:
+ - type: InstantAction
+ event: !type:ToggleActionEvent
+
- type: entity
parent: ClothingHeadBase
id: ClothingHeadHatDogEars
diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml
index 6215fb3b45a..7ba33bd0da6 100644
--- a/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml
+++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml
@@ -154,7 +154,7 @@
- HidesHarpyWings
- 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.
@@ -163,9 +163,7 @@
sprite: Clothing/OuterClothing/Suits/spaceninja.rsi
- type: Clothing
sprite: Clothing/OuterClothing/Suits/spaceninja.rsi
- - type: StealthClothing
- visibility: 1.1
- toggleAction: ActionTogglePhaseCloak
+ # hardsuit stuff
- type: PressureProtection
highPressureMultiplier: 0.6
lowPressureMultiplier: 1000
@@ -178,7 +176,27 @@
Slash: 0.8
Piercing: 0.8
Heat: 0.8
+ # phase cloak
+ - type: ToggleClothing
+ action: ActionTogglePhaseCloak
+ - type: ComponentToggler
+ parent: true
+ components:
+ - type: Stealth
+ minVisibility: 0.1
+ lastVisibility: 0.1
+ - type: PowerCellDraw
+ drawRate: 1.8 # 200 seconds on the default cell
+ # throwing star ability
+ - type: ItemCreator
+ action: ActionCreateThrowingStar
+ charge: 14.4
+ spawnedPrototype: ThrowingStarNinja
+ noPowerPopup: ninja-no-power
+ # core ninja suit stuff
- type: NinjaSuit
+ - type: UseDelay
+ delay: 5 # disable time
- type: PowerCellSlot
cellSlotId: cell_slot
# throwing in a recharger would bypass glove charging mechanic
diff --git a/Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml b/Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml
index 13fbc087164..bb2b163f080 100644
--- a/Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml
+++ b/Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml
@@ -1,45 +1,39 @@
- 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: ReverseEngineering
- difficulty: 2
- recipes:
- - ClothingShoesBootsMag
- - type: DamageOtherOnHit
- damage:
- types:
- Blunt: 9
- staminaCost: 11.5
- soundHit:
- path: /Audio/Weapons/smash.ogg
-
+ - 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
@@ -52,13 +46,9 @@
state: icon
- type: Clothing
sprite: Clothing/Shoes/Boots/magboots-advanced.rsi
- - type: Magboots
- toggleAction: ActionToggleMagbootsAdvanced
- type: ClothingSpeedModifier
walkModifier: 1
sprintModifier: 1
- enabled: false
- - type: NoSlip
- type: Tag
tags:
- WhitelistChameleon
@@ -81,8 +71,6 @@
sprite: Clothing/Shoes/Boots/magboots-science.rsi
- type: Clothing
sprite: Clothing/Shoes/Boots/magboots-science.rsi
- - type: Magboots
- toggleAction: ActionToggleMagbootsSci
- type: entity
parent: ClothingShoesBootsMag
@@ -93,7 +81,6 @@
- type: ClothingSpeedModifier
walkModifier: 1.10 #PVS isn't too much of an issue when you are blind...
sprintModifier: 1.10
- enabled: false
- type: StaticPrice
price: 3000
@@ -108,12 +95,9 @@
state: icon
- type: Clothing
sprite: Clothing/Shoes/Boots/magboots-syndicate.rsi
- - type: Magboots
- toggleAction: ActionToggleMagbootsSyndie
- type: ClothingSpeedModifier
walkModifier: 0.95
sprintModifier: 0.9
- enabled: false
- type: GasTank
outputPressure: 42.6
air:
@@ -121,79 +105,18 @@
volume: 0.75
temperature: 293.15
moles:
- - 0.153853429 # oxygen
- - 0.153853429 # nitrogen
- - type: ActivatableUI
- key: enum.SharedGasTankUiKey.Key
- - type: UserInterface
- interfaces:
- enum.SharedGasTankUiKey.Key:
- type: GasTankBoundUserInterface
- - type: Explosive
- explosionType: Default
- maxIntensity: 20
- - type: Jetpack
- moleUsage: 0.00085
- - type: CanMoveInAir
- - type: InputMover
- toParent: true
- - type: MovementSpeedModifier
- weightlessAcceleration: 1
- weightlessFriction: 0.3
- weightlessModifier: 1.2
- - type: Tag
- tags:
- - WhitelistChameleon
- - type: DamageOtherOnHit
- damage:
- types:
- Blunt: 20
- staminaCost: 25
- soundHit:
- collection: MetalThud
+ - 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.
categories: [ HideSpawnMenu ]
components:
- type: InstantAction
- itemIconStyle: NoItem
- event: !type:ToggleMagbootsEvent
-
-- type: entity
- id: ActionToggleMagboots
- parent: ActionBaseToggleMagboots
- categories: [ HideSpawnMenu ]
- 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
- categories: [ HideSpawnMenu ]
- 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
- categories: [ HideSpawnMenu ]
- 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
- categories: [ HideSpawnMenu ]
- 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
diff --git a/Resources/Prototypes/Entities/Clothing/Shoes/misc.yml b/Resources/Prototypes/Entities/Clothing/Shoes/misc.yml
index 888bcd769cf..5d81a416199 100644
--- a/Resources/Prototypes/Entities/Clothing/Shoes/misc.yml
+++ b/Resources/Prototypes/Entities/Clothing/Shoes/misc.yml
@@ -88,7 +88,7 @@
- type: NoSlip
- type: entity
- parent: [ClothingShoesBase, PowerCellSlotSmallItem]
+ parent: [ClothingShoesBase, PowerCellSlotSmallItem, BaseToggleClothing]
id: ClothingShoesBootsSpeed
name: speed boots
description: High-tech boots woven with quantum fibers, able to convert electricity into pure speed!
@@ -100,12 +100,11 @@
map: [ "enum.ToggleVisuals.Layer" ]
- type: Clothing
sprite: Clothing/Shoes/Boots/speedboots.rsi
- - type: ToggleClothingSpeed
- toggleAction: ActionToggleSpeedBoots
+ - type: ToggleClothing
+ action: ActionToggleSpeedBoots
- type: ClothingSpeedModifier
walkModifier: 1.5
sprintModifier: 1.5
- enabled: false
- type: Appearance
- type: GenericVisualizer
visuals:
@@ -131,7 +130,23 @@
categories: [ HideSpawnMenu ]
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
+ id: ClothingShoesBootsMoon
+ name: moon boots
+ description: Special anti-gravity boots developed with a speciality blend of lunar rock gel. Shipped from the Netherlands.
+ components:
+ - type: Sprite
+ sprite: Clothing/Shoes/Boots/moonboots.rsi
+ layers:
+ - state: icon
+ - type: Clothing
+ sprite: Clothing/Shoes/Boots/moonboots.rsi
+ - type: AntiGravityClothing
+ - type: StaticPrice
+ price: 75
+ - type: Tag
+ tags: [ ]
diff --git a/Resources/Prototypes/Entities/Clothing/base_clothing.yml b/Resources/Prototypes/Entities/Clothing/base_clothing.yml
index bc592616be9..3d0732d1cab 100644
--- a/Resources/Prototypes/Entities/Clothing/base_clothing.yml
+++ b/Resources/Prototypes/Entities/Clothing/base_clothing.yml
@@ -51,3 +51,12 @@
- 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
diff --git a/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml b/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml
index 3911a686266..f0f37822f4c 100644
--- a/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml
+++ b/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml
@@ -130,10 +130,9 @@
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
@@ -141,8 +140,6 @@
rules: ghost-role-information-space-ninja-rules
raffle:
settings: default
- - type: GhostRoleMobSpawner
- prototype: MobHumanSpaceNinja
- type: Sprite
sprite: Markers/jobs.rsi
layers:
diff --git a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml
index 1d0a8d92280..2989c40ff77 100644
--- a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml
+++ b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml
@@ -114,6 +114,11 @@
- 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
diff --git a/Resources/Prototypes/Entities/Mobs/Player/human.yml b/Resources/Prototypes/Entities/Mobs/Player/human.yml
index 4284632d288..a7fb7de2f87 100644
--- a/Resources/Prototypes/Entities/Mobs/Player/human.yml
+++ b/Resources/Prototypes/Entities/Mobs/Player/human.yml
@@ -69,28 +69,3 @@
- Syndicate
- type: Psionic
powerRollMultiplier: 7
-
-# Space Ninja
-- type: entity
- categories: [ HideSpawnMenu ]
- 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
diff --git a/Resources/Prototypes/Entities/Objects/Devices/base_handheld.yml b/Resources/Prototypes/Entities/Objects/Devices/base_handheld.yml
new file mode 100644
index 00000000000..259323fede3
--- /dev/null
+++ b/Resources/Prototypes/Entities/Objects/Devices/base_handheld.yml
@@ -0,0 +1,11 @@
+- 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
diff --git a/Resources/Prototypes/Entities/Objects/Devices/pda.yml b/Resources/Prototypes/Entities/Objects/Devices/pda.yml
index 2dbcfc60ab3..00df4e02cbe 100644
--- a/Resources/Prototypes/Entities/Objects/Devices/pda.yml
+++ b/Resources/Prototypes/Entities/Objects/Devices/pda.yml
@@ -118,6 +118,9 @@
id: BaseMedicalPDA
abstract: true
components:
+ - type: ItemToggle
+ toggleLight: false
+ onUse: false
- type: HealthAnalyzer
scanDelay: 1
scanningEndSound:
diff --git a/Resources/Prototypes/Entities/Objects/Devices/station_map.yml b/Resources/Prototypes/Entities/Objects/Devices/station_map.yml
index 0d2f890a1d3..54fc4a70c5a 100644
--- a/Resources/Prototypes/Entities/Objects/Devices/station_map.yml
+++ b/Resources/Prototypes/Entities/Objects/Devices/station_map.yml
@@ -1,9 +1,9 @@
- 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
@@ -34,14 +34,9 @@
- 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
diff --git a/Resources/Prototypes/Entities/Objects/Shields/shields.yml b/Resources/Prototypes/Entities/Objects/Shields/shields.yml
index f0d9a7c0b1b..ac23bd3995d 100644
--- a/Resources/Prototypes/Entities/Objects/Shields/shields.yml
+++ b/Resources/Prototypes/Entities/Objects/Shields/shields.yml
@@ -325,8 +325,10 @@
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:
@@ -358,7 +360,6 @@
energy: 2
color: blue
- type: Reflect
- enabled: false
reflectProb: 0.95
innate: true
reflects:
@@ -436,8 +437,10 @@
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
diff --git a/Resources/Prototypes/Entities/Objects/Specific/Medical/defib.yml b/Resources/Prototypes/Entities/Objects/Specific/Medical/defib.yml
index 1a905685934..0a7041ea548 100644
--- a/Resources/Prototypes/Entities/Objects/Specific/Medical/defib.yml
+++ b/Resources/Prototypes/Entities/Objects/Specific/Medical/defib.yml
@@ -31,6 +31,11 @@
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:
diff --git a/Resources/Prototypes/Entities/Objects/Specific/Medical/handheld_crew_monitor.yml b/Resources/Prototypes/Entities/Objects/Specific/Medical/handheld_crew_monitor.yml
index a9d6bbb20f5..93208f80c00 100644
--- a/Resources/Prototypes/Entities/Objects/Specific/Medical/handheld_crew_monitor.yml
+++ b/Resources/Prototypes/Entities/Objects/Specific/Medical/handheld_crew_monitor.yml
@@ -1,19 +1,14 @@
- 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.
components:
- 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
diff --git a/Resources/Prototypes/Entities/Objects/Specific/Medical/healthanalyzer.yml b/Resources/Prototypes/Entities/Objects/Specific/Medical/healthanalyzer.yml
index 47e633276b8..df9c27149d3 100644
--- a/Resources/Prototypes/Entities/Objects/Specific/Medical/healthanalyzer.yml
+++ b/Resources/Prototypes/Entities/Objects/Specific/Medical/healthanalyzer.yml
@@ -21,6 +21,8 @@
interfaces:
enum.HealthAnalyzerUiKey.Key:
type: HealthAnalyzerBoundUserInterface
+ - type: ItemToggle
+ onUse: false
- type: HealthAnalyzer
scanningEndSound:
path: "/Audio/Items/Medical/healthscanner.ogg"
diff --git a/Resources/Prototypes/Entities/Objects/Specific/Research/anomaly.yml b/Resources/Prototypes/Entities/Objects/Specific/Research/anomaly.yml
index 3c3b2154226..a90f7099adc 100644
--- a/Resources/Prototypes/Entities/Objects/Specific/Research/anomaly.yml
+++ b/Resources/Prototypes/Entities/Objects/Specific/Research/anomaly.yml
@@ -40,23 +40,22 @@
- 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:
diff --git a/Resources/Prototypes/Entities/Objects/Tools/handheld_mass_scanner.yml b/Resources/Prototypes/Entities/Objects/Tools/handheld_mass_scanner.yml
index 38b1f7cda9f..6491f699f00 100644
--- a/Resources/Prototypes/Entities/Objects/Tools/handheld_mass_scanner.yml
+++ b/Resources/Prototypes/Entities/Objects/Tools/handheld_mass_scanner.yml
@@ -1,6 +1,6 @@
- type: entity
name: handheld mass scanner
- parent: [ BaseItem, PowerCellSlotSmallItem]
+ parent: BaseHandheldComputer
id: HandHeldMassScanner
description: A hand-held mass scanner.
components:
@@ -27,7 +27,6 @@
- type: PowerCellDraw
drawRate: 3
useRate: 100
- - type: ActivatableUIRequiresPowerCell
- type: ActivatableUI
key: enum.RadarConsoleUiKey.Key
inHandsOnly: true
diff --git a/Resources/Prototypes/Entities/Objects/Tools/welders.yml b/Resources/Prototypes/Entities/Objects/Tools/welders.yml
index eae5cdbf46f..3b4394bdc3c 100644
--- a/Resources/Prototypes/Entities/Objects/Tools/welders.yml
+++ b/Resources/Prototypes/Entities/Objects/Tools/welders.yml
@@ -64,8 +64,10 @@
- type: ItemToggleSize
activatedSize: Large
- type: ItemToggleHot
- - type: ItemToggleDisarmMalus
- activatedDisarmMalus: 0.6
+ - type: ComponentToggler
+ components:
+ - type: DisarmMalus
+ malus: 0.6
- type: ToggleableLightVisuals
spriteLayer: flame
inhandVisuals:
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Launchers/launchers.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Launchers/launchers.yml
index dc49dce0f3a..e95db1c6e9b 100644
--- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Launchers/launchers.yml
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Launchers/launchers.yml
@@ -180,6 +180,8 @@
- type: TetherGun
frequency: 5
dampingRatio: 4
+ - type: ItemToggle
+ onUse: false
- type: PowerCellDraw
- type: Sprite
sprite: Objects/Weapons/Guns/Launchers/tether_gun.rsi
@@ -225,6 +227,8 @@
path: /Audio/Weapons/soup.ogg
params:
volume: 2
+ - type: ItemToggle
+ onUse: false
- type: PowerCellDraw
- type: Sprite
sprite: Objects/Weapons/Guns/Launchers/force_gun.rsi
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml
index 59c6d63c6e1..e4dd7b5b418 100644
--- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml
@@ -13,10 +13,12 @@
- 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
@@ -86,15 +88,7 @@
right:
- state: inhand-right-blade
shader: unshaded
- - type: DisarmMalus
- malus: 0
- type: Reflect
- enabled: false
- reflectProb: 0.5
- minReflectProb: 0.25
- - type: Tag
- tags:
- - NoPaint
- type: IgnitionSource
temperature: 700
- type: Scalpel
@@ -129,7 +123,6 @@
suffix: E-Dagger
description: 'A dark ink pen.'
components:
- - type: EnergySword
- type: ItemToggle
soundActivate:
path: /Audio/Weapons/ebladeon.ogg
@@ -155,14 +148,11 @@
path: /Audio/Weapons/ebladehum.ogg
params:
volume: -6
- - type: ItemToggleDisarmMalus
- activatedDisarmMalus: 0.4
- - type: ItemToggleEmbeddableProjectile
- activatedOffset: 0.0,0.0
- activatedRemovalTime: 3
- - type: ItemToggleThrowingAngle
- activatedAngle: 135
- deleteOnDeactivate: false
+ - type: ComponentToggler
+ components:
+ - type: Sharp
+ - type: DisarmMalus
+ malus: 0.4
- type: Sprite
sprite: Objects/Weapons/Melee/e_dagger.rsi
layers:
@@ -246,15 +236,12 @@
id: EnergyCutlass
description: An exotic energy weapon, brutal blade crackling with crudely harnessed plasma. #DeltaV - nicer description.
components:
- - type: EnergySword
- type: ItemToggleMeleeWeapon
activatedDamage:
types:
Slash: 10
Heat: 12
deactivatedSecret: true
- - type: ItemToggleDisarmMalus
- activatedDisarmMalus: 0.6
- type: Sprite
sprite: DeltaV/Objects/Weapons/Melee/e_cutlass.rsi # DeltaV
layers:
@@ -290,8 +277,8 @@
id: EnergySwordDouble
description: Syndicate Command's intern thought that having only one blade on energy swords was not cool 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:
@@ -316,9 +303,13 @@
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: .6666
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml
index 2da3d3bb92e..d36f0704751 100644
--- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml
@@ -30,13 +30,7 @@
- type: ThrowingAngle
angle: 225
- type: Reflect
- enabled: true
- # Design intent: a robust captain or tot can sacrifice movement to make the most of this weapon, but they have to
- # really restrict themselves to walking speed or less.
- reflectProb: 0.5
- velocityBeforeNotMaxProb: 1.0
- velocityBeforeMinProb: 3.0
- minReflectProb: 0.1
+ reflectProb: .1
spread: 90
- type: Item
size: Normal
@@ -261,11 +255,7 @@
path: /Audio/Effects/explosion_small1.ogg
- type: DamageOtherOnHit
- type: Reflect
- enabled: true
- reflectProb: 0.5 # In robust hands, deflects as well as an e-sword
- velocityBeforeNotMaxProb: 1.0
- velocityBeforeMinProb: 3.0
- minReflectProb: 0.1
+ reflectProb: .25
spread: 90
- type: Item
size: Ginormous
diff --git a/Resources/Prototypes/Entities/Structures/Machines/Medical/cryo_pod.yml b/Resources/Prototypes/Entities/Structures/Machines/Medical/cryo_pod.yml
index 4cb76ea4b92..0485b5a5178 100644
--- a/Resources/Prototypes/Entities/Structures/Machines/Medical/cryo_pod.yml
+++ b/Resources/Prototypes/Entities/Structures/Machines/Medical/cryo_pod.yml
@@ -85,6 +85,7 @@
whitelist:
components:
- FitsInDispenser
+ - type: ItemToggle
- type: HealthAnalyzer
scanDelay: 0
- type: UserInterface
diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml
index a0649eb2d6b..8a9c4880884 100644
--- a/Resources/Prototypes/GameRules/events.yml
+++ b/Resources/Prototypes/GameRules/events.yml
@@ -104,7 +104,43 @@
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
diff --git a/Resources/Prototypes/GameRules/midround.yml b/Resources/Prototypes/GameRules/midround.yml
index ba2cd530480..50864930b0f 100644
--- a/Resources/Prototypes/GameRules/midround.yml
+++ b/Resources/Prototypes/GameRules/midround.yml
@@ -1,34 +1,3 @@
-# doesnt spawn a ninja or anything, just stores configuration for it
-# see NinjaSpawn event for spawning
-- type: entity
- id: Ninja
- parent: BaseGameRule
- categories: [ HideSpawnMenu ]
- components:
- - type: GenericAntagRule
- agentName: ninja-round-end-agent-name
- objectives:
- - StealResearchObjective
- - DoorjackObjective
- - SpiderChargeObjective
- - TerrorObjective
- - MassArrestObjective
- - NinjaSurviveObjective
- - type: NinjaRule
- threats: NinjaThreats
-
-# stores configuration for dragon
-- type: entity
- categories: [ HideSpawnMenu ]
- parent: BaseGameRule
- id: Dragon
- components:
- - type: GenericAntagRule
- agentName: dragon-round-end-agent-name
- objectives:
- - CarpRiftsObjective
- - DragonSurviveObjective
-
- type: entity
categories: [ HideSpawnMenu ]
parent: BaseGameRule
From 0102f7d21b079348115c0ef581e44faaaa105784 Mon Sep 17 00:00:00 2001
From: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
Date: Sun, 4 Aug 2024 07:38:53 +0200
Subject: [PATCH 008/366] replace all uses of TryGetContainingContainer with
non-obsolete overload (#30583)
* replace all uses of TryGetContainerContainer with non-obsolete overload
* rerun
---
Content.Client/Commands/HideMechanismsCommand.cs | 2 +-
Content.Client/Instruments/UI/InstrumentMenu.xaml.cs | 2 +-
Content.Client/Items/Systems/ItemSystem.cs | 2 +-
Content.Client/Movement/Systems/JetpackSystem.cs | 4 ++--
Content.Server/Atmos/Rotting/RottingSystem.cs | 6 +++---
Content.Server/Cargo/Systems/CargoSystem.Bounty.cs | 2 +-
.../Nutrition/EntitySystems/SmokingSystem.cs | 2 +-
Content.Server/PDA/PdaSystem.cs | 2 +-
Content.Server/Polymorph/Systems/PolymorphSystem.cs | 2 +-
Content.Server/PowerCell/PowerCellSystem.cs | 4 ++--
Content.Server/Resist/EscapeInventorySystem.cs | 2 +-
.../Singularity/EntitySystems/EventHorizonSystem.cs | 6 +++---
.../Body/Systems/SharedBodySystem.Organs.cs | 2 +-
.../Body/Systems/SharedBodySystem.Parts.cs | 6 +++---
.../Clothing/ClothingSpeedModifierSystem.cs | 4 ++--
.../Clothing/EntitySystems/ClothingSystem.cs | 2 +-
Content.Shared/Clothing/MagbootsSystem.cs | 2 +-
.../Damage/Systems/DamageOnHoldingSystem.cs | 2 +-
.../Interaction/SharedInteractionSystem.cs | 4 ++--
Content.Shared/Item/SharedItemSystem.cs | 4 ++--
.../Materials/SharedMaterialReclaimerSystem.cs | 2 +-
.../Movement/Systems/SharedJetpackSystem.cs | 12 ++++++------
.../Storage/EntitySystems/SharedStorageSystem.cs | 2 +-
.../Ranged/Systems/SharedGunSystem.Clothing.cs | 2 +-
24 files changed, 40 insertions(+), 40 deletions(-)
diff --git a/Content.Client/Commands/HideMechanismsCommand.cs b/Content.Client/Commands/HideMechanismsCommand.cs
index 5f9afc78b98..97d792628a6 100644
--- a/Content.Client/Commands/HideMechanismsCommand.cs
+++ b/Content.Client/Commands/HideMechanismsCommand.cs
@@ -30,7 +30,7 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args)
sprite.ContainerOccluded = false;
var tempParent = uid;
- while (containerSys.TryGetContainingContainer(tempParent, out var container))
+ while (containerSys.TryGetContainingContainer((tempParent, null, null), out var container))
{
if (!container.ShowContents)
{
diff --git a/Content.Client/Instruments/UI/InstrumentMenu.xaml.cs b/Content.Client/Instruments/UI/InstrumentMenu.xaml.cs
index da443e3fb5b..b54a91ee975 100644
--- a/Content.Client/Instruments/UI/InstrumentMenu.xaml.cs
+++ b/Content.Client/Instruments/UI/InstrumentMenu.xaml.cs
@@ -180,7 +180,7 @@ private bool PlayCheck()
var container = _owner.Entities.System();
// If we're a handheld instrument, we might be in a container. Get it just in case.
- container.TryGetContainingContainer(instrumentEnt, out var conMan);
+ container.TryGetContainingContainer((Entity, null, null), out var conMan);
// If the instrument is handheld and we're not holding it, we return.
if ((instrument.Handheld && (conMan == null || conMan.Owner != localEntity)))
diff --git a/Content.Client/Items/Systems/ItemSystem.cs b/Content.Client/Items/Systems/ItemSystem.cs
index 5e60d06d0ce..2b5a41c6ce7 100644
--- a/Content.Client/Items/Systems/ItemSystem.cs
+++ b/Content.Client/Items/Systems/ItemSystem.cs
@@ -43,7 +43,7 @@ private void OnEquipped(EntityUid uid, SpriteComponent component, GotEquippedEve
public override void VisualsChanged(EntityUid uid)
{
// if the item is in a container, it might be equipped to hands or inventory slots --> update visuals.
- if (Container.TryGetContainingContainer(uid, out var container))
+ if (Container.TryGetContainingContainer((uid, null, null), out var container))
RaiseLocalEvent(container.Owner, new VisualsChangedEvent(GetNetEntity(uid), container.ID));
}
diff --git a/Content.Client/Movement/Systems/JetpackSystem.cs b/Content.Client/Movement/Systems/JetpackSystem.cs
index b7f5e48821f..6f9ef830408 100644
--- a/Content.Client/Movement/Systems/JetpackSystem.cs
+++ b/Content.Client/Movement/Systems/JetpackSystem.cs
@@ -63,15 +63,15 @@ public override void Update(float frameTime)
private void CreateParticles(EntityUid uid)
{
+ var uidXform = Transform(uid);
// Don't show particles unless the user is moving.
- if (Container.TryGetContainingContainer(uid, out var container) &&
+ if (Container.TryGetContainingContainer((uid, uidXform, null), out var container) &&
TryComp(container.Owner, out var body) &&
body.LinearVelocity.LengthSquared() < 1f)
{
return;
}
- var uidXform = Transform(uid);
var coordinates = uidXform.Coordinates;
var gridUid = coordinates.GetGridUid(EntityManager);
diff --git a/Content.Server/Atmos/Rotting/RottingSystem.cs b/Content.Server/Atmos/Rotting/RottingSystem.cs
index 40bdf657439..bfff8a91d0d 100644
--- a/Content.Server/Atmos/Rotting/RottingSystem.cs
+++ b/Content.Server/Atmos/Rotting/RottingSystem.cs
@@ -34,7 +34,7 @@ private void OnGibbed(EntityUid uid, RottingComponent component, BeingGibbedEven
if (!TryComp(uid, out var perishable))
return;
- var molsToDump = perishable.MolsPerSecondPerUnitMass * physics.FixturesMass * (float) component.TotalRotTime.TotalSeconds;
+ var molsToDump = perishable.MolsPerSecondPerUnitMass * physics.FixturesMass * (float)component.TotalRotTime.TotalSeconds;
var tileMix = _atmosphere.GetTileMixture(uid, excite: true);
tileMix?.AdjustMoles(Gas.Ammonia, molsToDump);
}
@@ -77,7 +77,7 @@ public void ReduceAccumulator(EntityUid uid, TimeSpan time)
///
private float GetRotRate(EntityUid uid)
{
- if (_container.TryGetContainingContainer(uid, out var container) &&
+ if (_container.TryGetContainingContainer((uid, null, null), out var container) &&
TryComp(container.Owner, out var rotContainer))
{
return rotContainer.DecayModifier;
@@ -147,7 +147,7 @@ public override void Update(float frameTime)
continue;
// We need a way to get the mass of the mob alone without armor etc in the future
// or just remove the mass mechanics altogether because they aren't good.
- var molRate = perishable.MolsPerSecondPerUnitMass * (float) rotting.RotUpdateRate.TotalSeconds;
+ var molRate = perishable.MolsPerSecondPerUnitMass * (float)rotting.RotUpdateRate.TotalSeconds;
var tileMix = _atmosphere.GetTileMixture(uid, excite: true);
tileMix?.AdjustMoles(Gas.Ammonia, molRate * physics.FixturesMass);
}
diff --git a/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs b/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs
index e132e4f12a3..9922cc5a37e 100644
--- a/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs
+++ b/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs
@@ -135,7 +135,7 @@ private void OnGetBountyPrice(EntityUid uid, CargoBountyLabelComponent component
return;
// make sure this label was actually applied to a crate.
- if (!_container.TryGetContainingContainer(uid, out var container) || container.ID != LabelSystem.ContainerName)
+ if (!_container.TryGetContainingContainer((uid, null, null), out var container) || container.ID != LabelSystem.ContainerName)
return;
if (component.AssociatedStationId is not { } station || !TryComp(station, out var database))
diff --git a/Content.Server/Nutrition/EntitySystems/SmokingSystem.cs b/Content.Server/Nutrition/EntitySystems/SmokingSystem.cs
index a33c944c994..7f8253efedf 100644
--- a/Content.Server/Nutrition/EntitySystems/SmokingSystem.cs
+++ b/Content.Server/Nutrition/EntitySystems/SmokingSystem.cs
@@ -142,7 +142,7 @@ public override void Update(float frameTime)
// This is awful. I hate this so much.
// TODO: Please, someone refactor containers and free me from this bullshit.
- if (!_container.TryGetContainingContainer(uid, out var containerManager) ||
+ if (!_container.TryGetContainingContainer((uid, null, null), out var containerManager) ||
!(_inventorySystem.TryGetSlotEntity(containerManager.Owner, "mask", out var inMaskSlotUid) && inMaskSlotUid == uid) ||
!TryComp(containerManager.Owner, out BloodstreamComponent? bloodstream))
{
diff --git a/Content.Server/PDA/PdaSystem.cs b/Content.Server/PDA/PdaSystem.cs
index d4934ee24e5..f75fd9920f3 100644
--- a/Content.Server/PDA/PdaSystem.cs
+++ b/Content.Server/PDA/PdaSystem.cs
@@ -121,7 +121,7 @@ private void OnNotification(Entity ent, ref CartridgeLoaderNotific
{
_ringer.RingerPlayRingtone(ent.Owner);
- if (!_containerSystem.TryGetContainingContainer(ent, out var container)
+ if (!_containerSystem.TryGetContainingContainer((ent, null, null), out var container)
|| !TryComp(container.Owner, out var actor))
return;
diff --git a/Content.Server/Polymorph/Systems/PolymorphSystem.cs b/Content.Server/Polymorph/Systems/PolymorphSystem.cs
index 5c970b1a748..913299eea91 100644
--- a/Content.Server/Polymorph/Systems/PolymorphSystem.cs
+++ b/Content.Server/Polymorph/Systems/PolymorphSystem.cs
@@ -226,7 +226,7 @@ private void OnDestruction(Entity ent, ref Destructi
var childXform = Transform(child);
_transform.SetLocalRotation(child, targetTransformComp.LocalRotation, childXform);
- if (_container.TryGetContainingContainer(uid, out var cont))
+ if (_container.TryGetContainingContainer((uid, targetTransformComp, null), out var cont))
_container.Insert(child, cont);
//Transfers all damage from the original to the new one
diff --git a/Content.Server/PowerCell/PowerCellSystem.cs b/Content.Server/PowerCell/PowerCellSystem.cs
index 0d2d9012bc7..f7b4cf02491 100644
--- a/Content.Server/PowerCell/PowerCellSystem.cs
+++ b/Content.Server/PowerCell/PowerCellSystem.cs
@@ -64,11 +64,11 @@ private void OnChargeChanged(EntityUid uid, PowerCellComponent component, ref Ch
}
var frac = args.Charge / args.MaxCharge;
- var level = (byte) ContentHelpers.RoundToNearestLevels(frac, 1, PowerCellComponent.PowerCellVisualsLevels);
+ var level = (byte)ContentHelpers.RoundToNearestLevels(frac, 1, PowerCellComponent.PowerCellVisualsLevels);
_sharedAppearanceSystem.SetData(uid, PowerCellVisuals.ChargeLevel, level);
// If this power cell is inside a cell-slot, inform that entity that the power has changed (for updating visuals n such).
- if (_containerSystem.TryGetContainingContainer(uid, out var container)
+ if (_containerSystem.TryGetContainingContainer((uid, null, null), out var container)
&& TryComp(container.Owner, out PowerCellSlotComponent? slot)
&& _itemSlotsSystem.TryGetSlot(container.Owner, slot.CellSlotId, out var itemSlot))
{
diff --git a/Content.Server/Resist/EscapeInventorySystem.cs b/Content.Server/Resist/EscapeInventorySystem.cs
index 2e060b1b42d..a6eb703ab0f 100644
--- a/Content.Server/Resist/EscapeInventorySystem.cs
+++ b/Content.Server/Resist/EscapeInventorySystem.cs
@@ -56,7 +56,7 @@ private void OnRelayMovement(EntityUid uid, CanEscapeInventoryComponent componen
if (!args.HasDirectionalMovement)
return;
- if (!_containerSystem.TryGetContainingContainer(uid, out var container) || !_actionBlockerSystem.CanInteract(uid, container.Owner))
+ if (!_containerSystem.TryGetContainingContainer((uid, null, null), out var container) || !_actionBlockerSystem.CanInteract(uid, container.Owner))
return;
// Make sure there's nothing stopped the removal (like being glued)
diff --git a/Content.Server/Singularity/EntitySystems/EventHorizonSystem.cs b/Content.Server/Singularity/EntitySystems/EventHorizonSystem.cs
index c74a3c49d63..2aec9a45ad1 100644
--- a/Content.Server/Singularity/EntitySystems/EventHorizonSystem.cs
+++ b/Content.Server/Singularity/EntitySystems/EventHorizonSystem.cs
@@ -97,7 +97,7 @@ public void Update(EntityUid uid, EventHorizonComponent? eventHorizon = null, Tr
return;
// Handle singularities some admin smited into a locker.
- if (_containerSystem.TryGetContainingContainer(uid, out var container, transform: xform)
+ if (_containerSystem.TryGetContainingContainer((uid, xform, null), out var container)
&& !AttemptConsumeEntity(uid, container.Owner, eventHorizon))
{
// Locker is indestructible. Consume everything else in the locker instead of magically teleporting out.
@@ -214,7 +214,7 @@ public void ConsumeEntitiesInContainer(EntityUid hungry, BaseContainer container
if (_containerSystem.Insert(entity, target_container))
break;
- _containerSystem.TryGetContainingContainer(target_container.Owner, out target_container);
+ _containerSystem.TryGetContainingContainer((target_container.Owner, null, null), out target_container);
}
// If we couldn't or there was no container to insert into just dump them to the map/grid.
@@ -470,7 +470,7 @@ private void OnContainerConsumed(EntityUid uid, ContainerManagerComponent comp,
{
var drop_container = args.Container;
if (drop_container is null)
- _containerSystem.TryGetContainingContainer(uid, out drop_container);
+ _containerSystem.TryGetContainingContainer((uid, null, null), out drop_container);
foreach (var container in comp.GetAllContainers())
{
diff --git a/Content.Shared/Body/Systems/SharedBodySystem.Organs.cs b/Content.Shared/Body/Systems/SharedBodySystem.Organs.cs
index 9c9fdc57276..a67d4b1f7fa 100644
--- a/Content.Shared/Body/Systems/SharedBodySystem.Organs.cs
+++ b/Content.Shared/Body/Systems/SharedBodySystem.Organs.cs
@@ -148,7 +148,7 @@ public bool InsertOrgan(
///
public bool RemoveOrgan(EntityUid organId, OrganComponent? organ = null)
{
- if (!Containers.TryGetContainingContainer(organId, out var container))
+ if (!Containers.TryGetContainingContainer((organId, null, null), out var container))
return false;
var parent = container.Owner;
diff --git a/Content.Shared/Body/Systems/SharedBodySystem.Parts.cs b/Content.Shared/Body/Systems/SharedBodySystem.Parts.cs
index dc088542279..36a650863e4 100644
--- a/Content.Shared/Body/Systems/SharedBodySystem.Parts.cs
+++ b/Content.Shared/Body/Systems/SharedBodySystem.Parts.cs
@@ -379,7 +379,7 @@ private void DisablePart(Entity partEnt)
///
public EntityUid? GetParentPartOrNull(EntityUid uid)
{
- if (!Containers.TryGetContainingContainer(uid, out var container))
+ if (!Containers.TryGetContainingContainer((uid, null, null), out var container))
return null;
var parent = container.Owner;
@@ -395,7 +395,7 @@ private void DisablePart(Entity partEnt)
///
public (EntityUid Parent, string Slot)? GetParentPartAndSlotOrNull(EntityUid uid)
{
- if (!Containers.TryGetContainingContainer(uid, out var container))
+ if (!Containers.TryGetContainingContainer((uid, null, null), out var container))
return null;
var slotId = GetPartSlotContainerIdFromContainer(container.ID);
@@ -425,7 +425,7 @@ public bool TryGetParentBodyPart(
parentUid = null;
parentComponent = null;
- if (Containers.TryGetContainingContainer(partUid, out var container) &&
+ if (Containers.TryGetContainingContainer((partUid, null, null), out var container) &&
TryComp(container.Owner, out parentComponent))
{
parentUid = container.Owner;
diff --git a/Content.Shared/Clothing/ClothingSpeedModifierSystem.cs b/Content.Shared/Clothing/ClothingSpeedModifierSystem.cs
index c89ab827e67..670bf99ef90 100644
--- a/Content.Shared/Clothing/ClothingSpeedModifierSystem.cs
+++ b/Content.Shared/Clothing/ClothingSpeedModifierSystem.cs
@@ -51,7 +51,7 @@ private void OnHandleState(EntityUid uid, ClothingSpeedModifierComponent compone
// 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.
- if (diff && _container.TryGetContainingContainer(uid, out var container))
+ if (diff && _container.TryGetContainingContainer((uid, null, null), out var container))
{
_movementSpeed.RefreshMovementSpeedModifiers(container.Owner);
}
@@ -115,7 +115,7 @@ private void OnToggled(Entity ent, ref ItemToggl
// make sentient boots slow or fast too
_movementSpeed.RefreshMovementSpeedModifiers(ent);
- if (_container.TryGetContainingContainer(ent.Owner, out var container))
+ if (_container.TryGetContainingContainer((ent.Owner, null, null), out var container))
{
// inventory system will automatically hook into the event raised by this and update accordingly
_movementSpeed.RefreshMovementSpeedModifiers(container.Owner);
diff --git a/Content.Shared/Clothing/EntitySystems/ClothingSystem.cs b/Content.Shared/Clothing/EntitySystems/ClothingSystem.cs
index 9e3f917e96f..d03cd095bb7 100644
--- a/Content.Shared/Clothing/EntitySystems/ClothingSystem.cs
+++ b/Content.Shared/Clothing/EntitySystems/ClothingSystem.cs
@@ -165,7 +165,7 @@ private void OnHandleState(EntityUid uid, ClothingComponent component, ref Compo
if (args.Current is ClothingComponentState state)
{
SetEquippedPrefix(uid, state.EquippedPrefix, component);
- if (component.InSlot != null && _containerSys.TryGetContainingContainer(uid, out var container))
+ if (component.InSlot != null && _containerSys.TryGetContainingContainer((uid, null, null), out var container))
{
CheckEquipmentForLayerHide(uid, container.Owner);
}
diff --git a/Content.Shared/Clothing/MagbootsSystem.cs b/Content.Shared/Clothing/MagbootsSystem.cs
index 88d987aae18..d41f27fefb0 100644
--- a/Content.Shared/Clothing/MagbootsSystem.cs
+++ b/Content.Shared/Clothing/MagbootsSystem.cs
@@ -36,7 +36,7 @@ private void OnToggled(Entity 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) &&
+ if (_container.TryGetContainingContainer((uid, null, null), out var container) &&
_inventory.TryGetSlotEntity(container.Owner, comp.Slot, out var worn)
&& uid == worn)
{
diff --git a/Content.Shared/Damage/Systems/DamageOnHoldingSystem.cs b/Content.Shared/Damage/Systems/DamageOnHoldingSystem.cs
index c13ec0a1b9f..78e86f5ab42 100644
--- a/Content.Shared/Damage/Systems/DamageOnHoldingSystem.cs
+++ b/Content.Shared/Damage/Systems/DamageOnHoldingSystem.cs
@@ -37,7 +37,7 @@ public override void Update(float frameTime)
{
if (!component.Enabled || component.NextDamage > _timing.CurTime)
continue;
- if (_container.TryGetContainingContainer(uid, out var container))
+ if (_container.TryGetContainingContainer((uid, null, null), out var container))
{
_damageableSystem.TryChangeDamage(container.Owner, component.Damage, origin: uid);
}
diff --git a/Content.Shared/Interaction/SharedInteractionSystem.cs b/Content.Shared/Interaction/SharedInteractionSystem.cs
index 32d69dc8e73..53a66e5ada5 100644
--- a/Content.Shared/Interaction/SharedInteractionSystem.cs
+++ b/Content.Shared/Interaction/SharedInteractionSystem.cs
@@ -1226,7 +1226,7 @@ public bool IsAccessible(Entity user, Entity
public bool CanAccessViaStorage(EntityUid user, EntityUid target)
{
- if (!_containerSystem.TryGetContainingContainer(target, out var container))
+ if (!_containerSystem.TryGetContainingContainer((target, null, null), out var container))
return false;
return CanAccessViaStorage(user, target, container);
@@ -1252,7 +1252,7 @@ public bool CanAccessEquipment(EntityUid user, EntityUid target)
if (Deleted(target))
return false;
- if (!_containerSystem.TryGetContainingContainer(target, out var container))
+ if (!_containerSystem.TryGetContainingContainer((target, null, null), out var container))
return false;
var wearer = container.Owner;
diff --git a/Content.Shared/Item/SharedItemSystem.cs b/Content.Shared/Item/SharedItemSystem.cs
index 5eaa25f4847..a058cb86588 100644
--- a/Content.Shared/Item/SharedItemSystem.cs
+++ b/Content.Shared/Item/SharedItemSystem.cs
@@ -110,8 +110,8 @@ private void AddPickupVerb(EntityUid uid, ItemComponent component, GetVerbsEvent
// if the item already in a container (that is not the same as the user's), then change the text.
// this occurs when the item is in their inventory or in an open backpack
- Container.TryGetContainingContainer(args.User, out var userContainer);
- if (Container.TryGetContainingContainer(args.Target, out var container) && container != userContainer)
+ Container.TryGetContainingContainer((args.User, null, null), out var userContainer);
+ if (Container.TryGetContainingContainer((args.Target, null, null), out var container) && container != userContainer)
verb.Text = Loc.GetString("pick-up-verb-get-data-text-inventory");
else
verb.Text = Loc.GetString("pick-up-verb-get-data-text");
diff --git a/Content.Shared/Materials/SharedMaterialReclaimerSystem.cs b/Content.Shared/Materials/SharedMaterialReclaimerSystem.cs
index cd4ae2e7676..d57eea7e3ed 100644
--- a/Content.Shared/Materials/SharedMaterialReclaimerSystem.cs
+++ b/Content.Shared/Materials/SharedMaterialReclaimerSystem.cs
@@ -100,7 +100,7 @@ public bool TryStartProcessItem(EntityUid uid, EntityUid item, MaterialReclaimer
if (component.Blacklist is {} blacklist && blacklist.IsValid(item))
return false;
- if (Container.TryGetContainingContainer(item, out _) && !Container.TryRemoveFromContainer(item))
+ if (Container.TryGetContainingContainer((item, null, null), out _) && !Container.TryRemoveFromContainer(item))
return false;
if (user != null)
diff --git a/Content.Shared/Movement/Systems/SharedJetpackSystem.cs b/Content.Shared/Movement/Systems/SharedJetpackSystem.cs
index 8d358f8db39..8eb943d7153 100644
--- a/Content.Shared/Movement/Systems/SharedJetpackSystem.cs
+++ b/Content.Shared/Movement/Systems/SharedJetpackSystem.cs
@@ -15,12 +15,12 @@ namespace Content.Shared.Movement.Systems;
public abstract class SharedJetpackSystem : EntitySystem
{
- [Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!;
- [Dependency] protected readonly SharedAppearanceSystem Appearance = default!;
+ [Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!;
+ [Dependency] protected readonly SharedAppearanceSystem Appearance = default!;
[Dependency] protected readonly SharedContainerSystem Container = default!;
- [Dependency] private readonly SharedMoverController _mover = default!;
- [Dependency] private readonly SharedPopupSystem _popup = default!;
- [Dependency] private readonly SharedPhysicsSystem _physics = default!;
+ [Dependency] private readonly SharedMoverController _mover = default!;
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
+ [Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly ActionContainerSystem _actionContainer = default!;
[Dependency] private readonly IConfigurationManager _config = default!;
@@ -164,7 +164,7 @@ public void SetEnabled(EntityUid uid, JetpackComponent component, bool enabled,
if (user == null)
{
- Container.TryGetContainingContainer(uid, out var container);
+ Container.TryGetContainingContainer((uid, null, null), out var container);
user = container?.Owner;
}
diff --git a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs
index fad9d724388..d5449e88f8a 100644
--- a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs
+++ b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs
@@ -1408,7 +1408,7 @@ private void OnLockToggled(EntityUid uid, StorageComponent component, ref LockTo
private void OnStackCountChanged(EntityUid uid, MetaDataComponent component, StackCountChangedEvent args)
{
- if (_containerSystem.TryGetContainingContainer(uid, out var container, component) &&
+ if (_containerSystem.TryGetContainingContainer((uid, null, component), out var container) &&
container.ID == StorageComponent.ContainerId)
{
UpdateAppearance(container.Owner);
diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Clothing.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Clothing.cs
index 77ee419ac3b..78386e89519 100644
--- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Clothing.cs
+++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Clothing.cs
@@ -33,7 +33,7 @@ private bool TryGetClothingSlotEntity(EntityUid uid, ClothingSlotAmmoProviderCom
{
slotEntity = null;
- if (!Containers.TryGetContainingContainer(uid, out var container))
+ if (!Containers.TryGetContainingContainer((uid, null, null), out var container))
return false;
var user = container.Owner;
From 1e4cb00770c0b40e3857c847be8763e8a7826ddc Mon Sep 17 00:00:00 2001
From: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com>
Date: Sat, 10 Aug 2024 19:29:44 -0700
Subject: [PATCH 009/366] Add a component that inserts the held item when no
interaction happens on the stored item (#29823)
* Insert when held item has no interaction with stored item
* Decouple inserting on failure
* Add component that stores the used entity when no interaction happened
* Add prediction
---
.../Interaction/SharedInteractionSystem.cs | 67 +++++++++++++------
.../StoreAfterFailedInteractComponent.cs | 8 +++
.../EntitySystems/SharedStorageSystem.cs | 40 ++++++-----
.../StoreAfterFailedInteractSystem.cs | 21 ++++++
.../Events/StorageInsertFailedEvent.cs | 6 ++
5 files changed, 105 insertions(+), 37 deletions(-)
create mode 100644 Content.Shared/Storage/Components/StoreAfterFailedInteractComponent.cs
create mode 100644 Content.Shared/Storage/EntitySystems/StoreAfterFailedInteractSystem.cs
create mode 100644 Content.Shared/Storage/Events/StorageInsertFailedEvent.cs
diff --git a/Content.Shared/Interaction/SharedInteractionSystem.cs b/Content.Shared/Interaction/SharedInteractionSystem.cs
index 53a66e5ada5..d325de92c83 100644
--- a/Content.Shared/Interaction/SharedInteractionSystem.cs
+++ b/Content.Shared/Interaction/SharedInteractionSystem.cs
@@ -956,11 +956,21 @@ public bool RangedInteractDoBefore(
}
///
- /// Uses a item/object on an entity
+ /// Uses an item/object on an entity
/// Finds components with the InteractUsing interface and calls their function
/// NOTE: Does not have an InRangeUnobstructed check
///
- public void InteractUsing(
+ /// User doing the interaction.
+ /// Item being used on the .
+ /// Entity getting interacted with by the using the
+ /// entity.
+ /// The location that the clicked.
+ /// Whether to check that the can interact with the
+ /// .
+ /// Whether to check that the can use the
+ /// entity.
+ /// True if the interaction was handled. Otherwise, false.
+ public bool InteractUsing(
EntityUid user,
EntityUid used,
EntityUid target,
@@ -972,13 +982,21 @@ public void InteractUsing(
return;
if (checkCanInteract && !_actionBlockerSystem.CanInteract(user, target))
- return;
+ return false;
+
+ if (checkCanInteract && !_actionBlockerSystem.CanInteract(user, target))
+ return false;
if (checkCanUse && !_actionBlockerSystem.CanUseHeldEntity(user, used))
- return;
+ return false;
+
+ _adminLogger.Add(
+ LogType.InteractUsing,
+ LogImpact.Low,
+ $"{ToPrettyString(user):user} interacted with {ToPrettyString(target):target} using {ToPrettyString(used):used}");
if (RangedInteractDoBefore(user, used, target, clickLocation, true))
- return;
+ return true;
// all interactions should only happen when in range / unobstructed, so no range check is needed
var interactUsingEvent = new InteractUsingEvent(user, used, target, clickLocation);
@@ -987,42 +1005,44 @@ public void InteractUsing(
DoContactInteraction(user, target, interactUsingEvent);
// Contact interactions are currently only used for forensics, so we don't raise used -> target
if (interactUsingEvent.Handled)
- return;
+ return true;
- InteractDoAfter(user, used, target, clickLocation, canReach: true);
+ if (InteractDoAfter(user, used, target, clickLocation, canReach: true))
+ return true;
+ return false;
}
///
/// Used when clicking on an entity resulted in no other interaction. Used for low-priority interactions.
///
- public void InteractDoAfter(
- EntityUid user,
- EntityUid used,
- EntityUid? target,
- EntityCoordinates clickLocation,
- bool canReach,
- bool checkDeletion = false
- )
+ ///
+ ///
+ ///
+ ///
+ /// Whether the is in range of the .
+ ///
+ /// True if the interaction was handled. Otherwise, false.
+ public bool InteractDoAfter(EntityUid user, EntityUid used, EntityUid? target, EntityCoordinates clickLocation, bool canReach)
{
if (target is { Valid: false })
target = null;
if (checkDeletion && (IsDeleted(user) || IsDeleted(used) || IsDeleted(target)))
- return;
+ return false;
var afterInteractEvent = new AfterInteractEvent(user, used, target, clickLocation, canReach);
RaiseLocalEvent(used, afterInteractEvent);
DoContactInteraction(user, used, afterInteractEvent);
+
if (canReach)
DoContactInteraction(user, target, afterInteractEvent);
- // Contact interactions are currently only used for forensics, so we don't raise used -> target
- }
+ // Contact interactions are currently only used for forensics, so we don't raise used -> target
if (afterInteractEvent.Handled)
- return;
+ return true;
if (target == null)
- return;
+ return false;
var afterInteractUsingEvent = new AfterInteractUsingEvent(user, used, target, clickLocation, canReach);
RaiseLocalEvent(target.Value, afterInteractUsingEvent);
@@ -1030,8 +1050,11 @@ public void InteractDoAfter(
DoContactInteraction(user, used, afterInteractUsingEvent);
if (canReach)
DoContactInteraction(user, target, afterInteractUsingEvent);
- // Contact interactions are currently only used for forensics, so we don't raise used -> target
- }
+ // Contact interactions are currently only used for forensics, so we don't raise used -> target
+
+ if (afterInteractUsingEvent.Handled)
+ return true;
+ return false;
}
#region ActivateItemInWorld
diff --git a/Content.Shared/Storage/Components/StoreAfterFailedInteractComponent.cs b/Content.Shared/Storage/Components/StoreAfterFailedInteractComponent.cs
new file mode 100644
index 00000000000..578da52dfc1
--- /dev/null
+++ b/Content.Shared/Storage/Components/StoreAfterFailedInteractComponent.cs
@@ -0,0 +1,8 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Storage.Components;
+
+[RegisterComponent, NetworkedComponent]
+public sealed partial class StoreAfterFailedInteractComponent : Component
+{
+}
diff --git a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs
index d5449e88f8a..9fd8bdf5e7b 100644
--- a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs
+++ b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs
@@ -21,6 +21,7 @@
using Content.Shared.Stacks;
using Content.Shared.Storage.Components;
using Content.Shared.Timing;
+using Content.Shared.Storage.Events;
using Content.Shared.Verbs;
using Content.Shared.Whitelist;
using Robust.Shared.Audio.Systems;
@@ -350,7 +351,7 @@ private void OnInteractUsing(EntityUid uid, StorageComponent storageComp, Intera
if (HasComp(uid))
return;
- PlayerInsertHeldEntity(uid, args.User, storageComp);
+ PlayerInsertHeldEntity((uid, storageComp), args.User);
// Always handle it, even if insertion fails.
// We don't want to trigger any AfterInteract logic here.
// Example issue would be placing wires if item doesn't fit in backpack.
@@ -597,7 +598,15 @@ private void OnInteractWithItem(StorageInteractWithItemEvent msg, EntitySessionE
}
// Else, interact using the held item
- _interactionSystem.InteractUsing(player, hands.ActiveHandEntity.Value, entity, Transform(entity).Coordinates, checkCanInteract: false);
+ if (_interactionSystem.InteractUsing(player,
+ player.Comp.ActiveHandEntity.Value,
+ item,
+ Transform(item).Coordinates,
+ checkCanInteract: false))
+ return;
+
+ var failedEv = new StorageInsertFailedEvent((storage, storage.Comp), (player, player.Comp));
+ RaiseLocalEvent(storage, ref failedEv);
}
private void OnSetItemLocation(StorageSetItemLocationEvent msg, EntitySessionEventArgs args)
@@ -1039,35 +1048,36 @@ public bool Insert(
///
/// Inserts an entity into storage from the player's active hand
///
- ///
- /// The player to insert an entity from
- ///
- /// true if inserted, false otherwise
- public bool PlayerInsertHeldEntity(EntityUid uid, EntityUid player, StorageComponent? storageComp = null)
+ /// The storage entity and component to insert into.
+ /// The player and hands component to insert the held entity from.
+ /// True if inserted, otherwise false.
+ public bool PlayerInsertHeldEntity(Entity ent, Entity player)
{
- if (!Resolve(uid, ref storageComp) || !TryComp(player, out HandsComponent? hands) || hands.ActiveHandEntity == null)
+ if (!Resolve(ent.Owner, ref ent.Comp)
+ || !Resolve(player.Owner, ref player.Comp)
+ || player.Comp.ActiveHandEntity == null)
return false;
- var toInsert = hands.ActiveHandEntity;
+ var toInsert = player.Comp.ActiveHandEntity;
- if (!CanInsert(uid, toInsert.Value, out var reason, storageComp))
+ if (!CanInsert(ent, toInsert.Value, out var reason, ent.Comp))
{
- _popupSystem.PopupClient(Loc.GetString(reason ?? "comp-storage-cant-insert"), uid, player);
+ _popupSystem.PopupClient(Loc.GetString(reason ?? "comp-storage-cant-insert"), ent, player);
return false;
}
- if (!_sharedHandsSystem.CanDrop(player, toInsert.Value, hands))
+ if (!_sharedHandsSystem.CanDrop(player, toInsert.Value, player.Comp))
{
- _popupSystem.PopupClient(Loc.GetString("comp-storage-cant-drop", ("entity", toInsert.Value)), uid, player);
+ _popupSystem.PopupClient(Loc.GetString("comp-storage-cant-drop", ("entity", toInsert.Value)), ent, player);
return false;
}
- return PlayerInsertEntityInWorld((uid, storageComp), player, toInsert.Value);
+ return PlayerInsertEntityInWorld((ent, ent.Comp), player, toInsert.Value);
}
///
/// Inserts an Entity () in the world into storage, informing if it fails.
- /// is *NOT* held, see .
+ /// is *NOT* held, see .
///
///
/// The player to insert an entity with
diff --git a/Content.Shared/Storage/EntitySystems/StoreAfterFailedInteractSystem.cs b/Content.Shared/Storage/EntitySystems/StoreAfterFailedInteractSystem.cs
new file mode 100644
index 00000000000..77f72806f4b
--- /dev/null
+++ b/Content.Shared/Storage/EntitySystems/StoreAfterFailedInteractSystem.cs
@@ -0,0 +1,21 @@
+using Content.Shared.Storage.Components;
+using Content.Shared.Storage.Events;
+
+namespace Content.Shared.Storage.EntitySystems;
+
+public sealed class StoreAfterFailedInteractSystem : EntitySystem
+{
+ [Dependency] private readonly SharedStorageSystem _storage = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnStorageInsertFailed);
+ }
+
+ private void OnStorageInsertFailed(Entity ent, ref StorageInsertFailedEvent args)
+ {
+ _storage.PlayerInsertHeldEntity(args.Storage, args.Player);
+ }
+}
diff --git a/Content.Shared/Storage/Events/StorageInsertFailedEvent.cs b/Content.Shared/Storage/Events/StorageInsertFailedEvent.cs
new file mode 100644
index 00000000000..50f759e43fd
--- /dev/null
+++ b/Content.Shared/Storage/Events/StorageInsertFailedEvent.cs
@@ -0,0 +1,6 @@
+using Content.Shared.Hands.Components;
+
+namespace Content.Shared.Storage.Events;
+
+[ByRefEvent]
+public record struct StorageInsertFailedEvent(Entity Storage, Entity Player);
From 89c061be103501c25c3ce1d58c9db683d24854fd Mon Sep 17 00:00:00 2001
From: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com>
Date: Wed, 29 May 2024 23:46:22 -0700
Subject: [PATCH 010/366] meow
---
.../Components/ApcPowerReceiverComponent.cs | 8 +++
.../EntitySystems/PowerReceiverSystem.cs | 23 ++++++++
.../Components/ApcPowerReceiverComponent.cs | 18 +++----
.../Power/EntitySystems/PowerNetSystem.cs | 21 +++++---
.../EntitySystems/PowerReceiverSystem.cs | 52 ++++++-------------
.../ApcPowerReceiverComponentState.cs | 9 ++++
.../SharedApcPowerReceiverComponent.cs | 10 ++++
.../SharedPowerReceiverSystem.cs | 6 +++
8 files changed, 92 insertions(+), 55 deletions(-)
create mode 100644 Content.Client/Power/Components/ApcPowerReceiverComponent.cs
create mode 100644 Content.Client/Power/EntitySystems/PowerReceiverSystem.cs
create mode 100644 Content.Shared/Power/Components/ApcPowerReceiverComponentState.cs
create mode 100644 Content.Shared/Power/Components/SharedApcPowerReceiverComponent.cs
create mode 100644 Content.Shared/Power/EntitySystems/SharedPowerReceiverSystem.cs
diff --git a/Content.Client/Power/Components/ApcPowerReceiverComponent.cs b/Content.Client/Power/Components/ApcPowerReceiverComponent.cs
new file mode 100644
index 00000000000..fbebcb7cf83
--- /dev/null
+++ b/Content.Client/Power/Components/ApcPowerReceiverComponent.cs
@@ -0,0 +1,8 @@
+using Content.Shared.Power.Components;
+
+namespace Content.Client.Power.Components;
+
+[RegisterComponent]
+public sealed partial class ApcPowerReceiverComponent : SharedApcPowerReceiverComponent
+{
+}
diff --git a/Content.Client/Power/EntitySystems/PowerReceiverSystem.cs b/Content.Client/Power/EntitySystems/PowerReceiverSystem.cs
new file mode 100644
index 00000000000..4d56592c232
--- /dev/null
+++ b/Content.Client/Power/EntitySystems/PowerReceiverSystem.cs
@@ -0,0 +1,23 @@
+using Content.Client.Power.Components;
+using Content.Shared.Power.Components;
+using Content.Shared.Power.EntitySystems;
+using Robust.Shared.GameStates;
+
+namespace Content.Client.Power.EntitySystems;
+
+public sealed class PowerReceiverSystem : SharedPowerReceiverSystem
+{
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent(OnHandleState);
+ }
+
+ private void OnHandleState(EntityUid uid, ApcPowerReceiverComponent component, ref ComponentHandleState args)
+ {
+ if (args.Current is not ApcPowerReceiverComponentState state)
+ return;
+
+ component.Powered = state.Powered;
+ }
+}
diff --git a/Content.Server/Power/Components/ApcPowerReceiverComponent.cs b/Content.Server/Power/Components/ApcPowerReceiverComponent.cs
index b8340d0be4b..9a68e2aabb8 100644
--- a/Content.Server/Power/Components/ApcPowerReceiverComponent.cs
+++ b/Content.Server/Power/Components/ApcPowerReceiverComponent.cs
@@ -1,5 +1,6 @@
using Content.Server.Power.NodeGroups;
using Content.Server.Power.Pow3r;
+using Content.Shared.Power.Components;
namespace Content.Server.Power.Components
{
@@ -8,11 +9,8 @@ namespace Content.Server.Power.Components
/// so that it can receive power from a .
///
[RegisterComponent]
- public sealed partial class ApcPowerReceiverComponent : Component
+ public sealed partial class ApcPowerReceiverComponent : SharedApcPowerReceiverComponent
{
- [ViewVariables]
- public bool Powered => (MathHelper.CloseToPercent(NetworkLoad.ReceivingPower, Load) || !NeedsPower) && !PowerDisabled;
-
///
/// Amount of charge this needs from an APC per second to function.
///
@@ -33,7 +31,7 @@ public bool NeedsPower
{
_needsPower = value;
// Reset this so next tick will do a power update.
- PoweredLastUpdate = null;
+ Recalculate = true;
}
}
@@ -50,7 +48,8 @@ public bool PowerDisabled {
set => NetworkLoad.Enabled = !value;
}
- public bool? PoweredLastUpdate;
+ // TODO Is this needed? It forces a PowerChangedEvent when NeedsPower is toggled even if it changes to the same state.
+ public bool Recalculate;
[ViewVariables]
public PowerState.Load NetworkLoad { get; } = new PowerState.Load
@@ -66,10 +65,5 @@ public bool PowerDisabled {
/// Does nothing on the client.
///
[ByRefEvent]
- public readonly record struct PowerChangedEvent(bool Powered, float ReceivingPower)
- {
- public readonly bool Powered = Powered;
- public readonly float ReceivingPower = ReceivingPower;
- }
-
+ public readonly record struct PowerChangedEvent(bool Powered, float ReceivingPower);
}
diff --git a/Content.Server/Power/EntitySystems/PowerNetSystem.cs b/Content.Server/Power/EntitySystems/PowerNetSystem.cs
index 2d80c810e2e..6c35ba20083 100644
--- a/Content.Server/Power/EntitySystems/PowerNetSystem.cs
+++ b/Content.Server/Power/EntitySystems/PowerNetSystem.cs
@@ -22,6 +22,7 @@ public sealed class PowerNetSystem : EntitySystem
[Dependency] private readonly PowerNetConnectorSystem _powerNetConnector = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IParallelManager _parMan = default!;
+ [Dependency] private readonly PowerReceiverSystem _powerReceiver = default!;
private readonly PowerState _powerState = new();
private readonly HashSet _powerNetReconnectQueue = new();
@@ -313,19 +314,27 @@ private void UpdateApcPowerReceiver()
var enumerator = AllEntityQuery();
while (enumerator.MoveNext(out var uid, out var apcReceiver))
{
- var powered = apcReceiver.Powered;
- if (powered == apcReceiver.PoweredLastUpdate)
+ var powered = !apcReceiver.PowerDisabled
+ && (!apcReceiver.NeedsPower
+ || MathHelper.CloseToPercent(apcReceiver.NetworkLoad.ReceivingPower,
+ apcReceiver.Load));
+
+ // If new value is the same as the old, then exit
+ if (!apcReceiver.Recalculate && apcReceiver.Powered == powered)
continue;
- if (metaQuery.GetComponent(uid).EntityPaused)
+ var metadata = metaQuery.Comp(uid);
+ if (metadata.EntityPaused)
continue;
- apcReceiver.PoweredLastUpdate = powered;
- var ev = new PowerChangedEvent(apcReceiver.Powered, apcReceiver.NetworkLoad.ReceivingPower);
+ apcReceiver.Recalculate = false;
+ apcReceiver.Powered = powered;
+ Dirty(uid, apcReceiver, metadata);
+ var ev = new PowerChangedEvent(powered, apcReceiver.NetworkLoad.ReceivingPower);
RaiseLocalEvent(uid, ref ev);
- if (appearanceQuery.TryGetComponent(uid, out var appearance))
+ if (appearanceQuery.TryComp(uid, out var appearance))
_appearance.SetData(uid, PowerDeviceVisuals.Powered, powered, appearance);
}
}
diff --git a/Content.Server/Power/EntitySystems/PowerReceiverSystem.cs b/Content.Server/Power/EntitySystems/PowerReceiverSystem.cs
index 9ba30813dd5..0a5d475f27f 100644
--- a/Content.Server/Power/EntitySystems/PowerReceiverSystem.cs
+++ b/Content.Server/Power/EntitySystems/PowerReceiverSystem.cs
@@ -7,10 +7,13 @@
using Content.Shared.Examine;
using Content.Shared.Hands.Components;
using Content.Shared.Power;
+using Content.Shared.Power.Components;
+using Content.Shared.Power.EntitySystems;
using Content.Shared.Verbs;
using Robust.Server.Audio;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
using Robust.Shared.Utility;
using Content.Shared.Emp;
using Content.Shared.Interaction;
@@ -18,7 +21,7 @@
namespace Content.Server.Power.EntitySystems
{
- public sealed class PowerReceiverSystem : EntitySystem
+ public sealed class PowerReceiverSystem : SharedPowerReceiverSystem
{
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly IAdminManager _adminManager = default!;
@@ -43,8 +46,7 @@ public override void Initialize()
SubscribeLocalEvent>(OnGetVerbs);
SubscribeLocalEvent>(AddSwitchPowerVerb);
- SubscribeLocalEvent(OnEmpPulse);
- SubscribeLocalEvent(OnEmpEnd);
+ SubscribeLocalEvent(OnGetState);
_recQuery = GetEntityQuery();
_provQuery = GetEntityQuery();
@@ -151,14 +153,18 @@ private void AddSwitchPowerVerb(EntityUid uid, PowerSwitchComponent component, G
args.Verbs.Add(verb);
}
+ private void OnGetState(EntityUid uid, ApcPowerReceiverComponent component, ref ComponentGetState args)
+ {
+ args.State = new ApcPowerReceiverComponentState
+ {
+ Powered = component.Powered
+ };
+ }
+
private void ProviderChanged(Entity receiver)
{
var comp = receiver.Comp;
comp.NetworkLoad.LinkedNetwork = default;
- var ev = new PowerChangedEvent(comp.Powered, comp.NetworkLoad.ReceivingPower);
-
- RaiseLocalEvent(receiver, ref ev);
- _appearance.SetData(receiver, PowerDeviceVisuals.Powered, comp.Powered);
}
///
@@ -166,12 +172,10 @@ private void ProviderChanged(Entity receiver)
/// Otherwise, it returns 'true' because if something doesn't take power
/// it's effectively always powered.
///
+ /// True when entity has no ApcPowerReceiverComponent or is Powered. False when not.
public bool IsPowered(EntityUid uid, ApcPowerReceiverComponent? receiver = null)
{
- if (!_recQuery.Resolve(uid, ref receiver, false))
- return true;
-
- return receiver.Powered;
+ return !_recQuery.Resolve(uid, ref receiver, false) || receiver.Powered;
}
///
@@ -204,35 +208,9 @@ public bool TogglePower(EntityUid uid, bool playSwitchSound = true, ApcPowerRece
return !receiver.PowerDisabled; // i.e. PowerEnabled
}
- public bool TryTogglePower(EntityUid uid, bool playSwitchSound = true, ApcPowerReceiverComponent? receiver = null, EntityUid? user = null)
- {
- if (HasComp(uid))
- return false;
-
- return TogglePower(uid, playSwitchSound, receiver, user);
- }
-
public void SetLoad(ApcPowerReceiverComponent comp, float load)
{
comp.Load = load;
}
-
- private void OnEmpPulse(EntityUid uid, ApcPowerReceiverComponent component, ref EmpPulseEvent args)
- {
- if (!component.PowerDisabled)
- {
- args.Affected = true;
- args.Disabled = true;
- TogglePower(uid, false);
- }
- }
-
- private void OnEmpEnd(EntityUid uid, ApcPowerReceiverComponent component, ref EmpDisabledRemoved args)
- {
- if (component.PowerDisabled)
- {
- TogglePower(uid, false);
- }
- }
}
}
diff --git a/Content.Shared/Power/Components/ApcPowerReceiverComponentState.cs b/Content.Shared/Power/Components/ApcPowerReceiverComponentState.cs
new file mode 100644
index 00000000000..9b18d6ad938
--- /dev/null
+++ b/Content.Shared/Power/Components/ApcPowerReceiverComponentState.cs
@@ -0,0 +1,9 @@
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Power.Components;
+
+[Serializable, NetSerializable]
+public sealed class ApcPowerReceiverComponentState : ComponentState
+{
+ public bool Powered;
+}
diff --git a/Content.Shared/Power/Components/SharedApcPowerReceiverComponent.cs b/Content.Shared/Power/Components/SharedApcPowerReceiverComponent.cs
new file mode 100644
index 00000000000..d73993357a3
--- /dev/null
+++ b/Content.Shared/Power/Components/SharedApcPowerReceiverComponent.cs
@@ -0,0 +1,10 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Power.Components;
+
+[NetworkedComponent]
+public abstract partial class SharedApcPowerReceiverComponent : Component
+{
+ [ViewVariables]
+ public bool Powered;
+}
diff --git a/Content.Shared/Power/EntitySystems/SharedPowerReceiverSystem.cs b/Content.Shared/Power/EntitySystems/SharedPowerReceiverSystem.cs
new file mode 100644
index 00000000000..be2a9dc3abc
--- /dev/null
+++ b/Content.Shared/Power/EntitySystems/SharedPowerReceiverSystem.cs
@@ -0,0 +1,6 @@
+namespace Content.Shared.Power.EntitySystems;
+
+public abstract class SharedPowerReceiverSystem : EntitySystem
+{
+
+}
From ee6f398e50457cb596765eccf55fcedc83f17fad Mon Sep 17 00:00:00 2001
From: nikthechampiongr <32041239+nikthechampiongr@users.noreply.github.com>
Date: Sun, 12 May 2024 15:07:54 +0000
Subject: [PATCH 011/366] Record deletion (#27883)
* Allow for Station Records interface for aghosts to delete records
* Fix record consoles not working when there are more than 2 crew members.
HOW DID NOONE NOTICE THIS SOONER???
* Stop being unconventional
---
.../StationRecords/GeneralRecord.xaml | 13 +++++
.../StationRecords/GeneralRecord.xaml.cs | 33 ++++++++++++
...lStationRecordConsoleBoundUserInterface.cs | 1 +
.../GeneralStationRecordConsoleWindow.xaml | 2 +-
.../GeneralStationRecordConsoleWindow.xaml.cs | 51 +++----------------
.../GeneralStationRecordConsoleComponent.cs | 6 +++
.../GeneralStationRecordConsoleSystem.cs | 20 ++++++--
.../StationRecords/GeneralRecordsUi.cs | 23 +++++++--
.../general-station-records.ftl | 1 +
.../Entities/Mobs/Player/admin_ghost.yml | 1 +
10 files changed, 100 insertions(+), 51 deletions(-)
create mode 100644 Content.Client/StationRecords/GeneralRecord.xaml
create mode 100644 Content.Client/StationRecords/GeneralRecord.xaml.cs
diff --git a/Content.Client/StationRecords/GeneralRecord.xaml b/Content.Client/StationRecords/GeneralRecord.xaml
new file mode 100644
index 00000000000..add688c3f38
--- /dev/null
+++ b/Content.Client/StationRecords/GeneralRecord.xaml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/StationRecords/GeneralRecord.xaml.cs b/Content.Client/StationRecords/GeneralRecord.xaml.cs
new file mode 100644
index 00000000000..6a2622fba9e
--- /dev/null
+++ b/Content.Client/StationRecords/GeneralRecord.xaml.cs
@@ -0,0 +1,33 @@
+using Content.Shared.StationRecords;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.StationRecords;
+
+[GenerateTypedNameReferences]
+public sealed partial class GeneralRecord : Control
+{
+ public Action? OnDeletePressed;
+ public GeneralRecord(GeneralStationRecord record, bool canDelete, uint? id)
+ {
+ RobustXamlLoader.Load(this);
+ RecordName.Text = record.Name;
+ Age.Text = Loc.GetString("general-station-record-console-record-age", ("age", record.Age.ToString()));
+ Title.Text = Loc.GetString("general-station-record-console-record-title",
+ ("job", Loc.GetString(record.JobTitle)));
+ Species.Text = Loc.GetString("general-station-record-console-record-species", ("species", record.Species));
+ Gender.Text = Loc.GetString("general-station-record-console-record-gender",
+ ("gender", record.Gender.ToString()));
+ Fingerprint.Text = Loc.GetString("general-station-record-console-record-fingerprint",
+ ("fingerprint", record.Fingerprint ?? Loc.GetString("generic-not-available-shorthand")));
+ Dna.Text = Loc.GetString("general-station-record-console-record-dna",
+ ("dna", record.DNA ?? Loc.GetString("generic-not-available-shorthand")));
+
+ if (canDelete && id != null )
+ {
+ DeleteButton.Visible = true;
+ DeleteButton.OnPressed += _ => OnDeletePressed?.Invoke(id.Value);
+ }
+ }
+}
diff --git a/Content.Client/StationRecords/GeneralStationRecordConsoleBoundUserInterface.cs b/Content.Client/StationRecords/GeneralStationRecordConsoleBoundUserInterface.cs
index 3be3d98778d..720a2efb9dd 100644
--- a/Content.Client/StationRecords/GeneralStationRecordConsoleBoundUserInterface.cs
+++ b/Content.Client/StationRecords/GeneralStationRecordConsoleBoundUserInterface.cs
@@ -20,6 +20,7 @@ protected override void Open()
SendMessage(new SelectStationRecord(key));
_window.OnFiltersChanged += (type, filterValue) =>
SendMessage(new SetStationRecordFilter(type, filterValue));
+ _window.OnDeleted += id => SendMessage(new DeleteStationRecord(id));
_window.OnClose += Close;
_window.OpenCentered();
diff --git a/Content.Client/StationRecords/GeneralStationRecordConsoleWindow.xaml b/Content.Client/StationRecords/GeneralStationRecordConsoleWindow.xaml
index a915d329bc3..3615eb8e008 100644
--- a/Content.Client/StationRecords/GeneralStationRecordConsoleWindow.xaml
+++ b/Content.Client/StationRecords/GeneralStationRecordConsoleWindow.xaml
@@ -19,7 +19,7 @@
-
+
diff --git a/Content.Client/StationRecords/GeneralStationRecordConsoleWindow.xaml.cs b/Content.Client/StationRecords/GeneralStationRecordConsoleWindow.xaml.cs
index fbdd6c2f0b5..272e6c3b251 100644
--- a/Content.Client/StationRecords/GeneralStationRecordConsoleWindow.xaml.cs
+++ b/Content.Client/StationRecords/GeneralStationRecordConsoleWindow.xaml.cs
@@ -1,7 +1,5 @@
using Content.Shared.StationRecords;
using Robust.Client.AutoGenerated;
-using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
@@ -13,6 +11,7 @@ public sealed partial class GeneralStationRecordConsoleWindow : DefaultWindow
public Action? OnKeySelected;
public Action? OnFiltersChanged;
+ public Action? OnDeleted;
private bool _isPopulating;
@@ -112,11 +111,10 @@ public void UpdateState(GeneralStationRecordConsoleState state)
RecordContainerStatus.Text = state.SelectedKey == null
? Loc.GetString("general-station-record-console-no-record-found")
: Loc.GetString("general-station-record-console-select-record-info");
- PopulateRecordContainer(state.Record);
+ PopulateRecordContainer(state.Record, state.CanDeleteEntries, state.SelectedKey);
}
else
{
- RecordContainer.DisposeAllChildren();
RecordContainer.RemoveAllChildren();
}
}
@@ -138,49 +136,13 @@ private void PopulateRecordListing(Dictionary listing, uint? selec
RecordListing.SortItemsByText();
}
- private void PopulateRecordContainer(GeneralStationRecord record)
+ private void PopulateRecordContainer(GeneralStationRecord record, bool enableDelete, uint? id)
{
- RecordContainer.DisposeAllChildren();
RecordContainer.RemoveAllChildren();
- // sure
- var recordControls = new Control[]
- {
- new Label()
- {
- Text = record.Name,
- StyleClasses = { "LabelBig" }
- },
- new Label()
- {
- Text = Loc.GetString("general-station-record-console-record-age", ("age", record.Age.ToString()))
-
- },
- new Label()
- {
- Text = Loc.GetString("general-station-record-console-record-title", ("job", Loc.GetString(record.JobTitle)))
- },
- new Label()
- {
- Text = Loc.GetString("general-station-record-console-record-species", ("species", record.Species))
- },
- new Label()
- {
- Text = Loc.GetString("general-station-record-console-record-gender", ("gender", record.Gender.ToString()))
- },
- new Label()
- {
- Text = Loc.GetString("general-station-record-console-record-fingerprint", ("fingerprint", record.Fingerprint ?? Loc.GetString("generic-not-available-shorthand")))
- },
- new Label()
- {
- Text = Loc.GetString("general-station-record-console-record-dna", ("dna", record.DNA ?? Loc.GetString("generic-not-available-shorthand")))
- }
- };
+ var newRecord = new GeneralRecord(record, enableDelete, id);
+ newRecord.OnDeletePressed = OnDeleted;
- foreach (var control in recordControls)
- {
- RecordContainer.AddChild(control);
- }
+ RecordContainer.AddChild(newRecord);
}
private void FilterListingOfRecords(string text = "")
@@ -195,4 +157,5 @@ private string GetTypeFilterLocals(StationRecordFilterType type)
{
return Loc.GetString($"general-station-record-{type.ToString().ToLower()}-filter");
}
+
}
diff --git a/Content.Server/StationRecords/Components/GeneralStationRecordConsoleComponent.cs b/Content.Server/StationRecords/Components/GeneralStationRecordConsoleComponent.cs
index 9076bee436f..a6356f0baa3 100644
--- a/Content.Server/StationRecords/Components/GeneralStationRecordConsoleComponent.cs
+++ b/Content.Server/StationRecords/Components/GeneralStationRecordConsoleComponent.cs
@@ -18,4 +18,10 @@ public sealed partial class GeneralStationRecordConsoleComponent : Component
///
[DataField]
public StationRecordsFilter? Filter;
+
+ ///
+ /// Whether this Records Console is able to delete entries.
+ ///
+ [DataField]
+ public bool CanDeleteEntries;
}
diff --git a/Content.Server/StationRecords/Systems/GeneralStationRecordConsoleSystem.cs b/Content.Server/StationRecords/Systems/GeneralStationRecordConsoleSystem.cs
index a5202285d99..87246ab6757 100644
--- a/Content.Server/StationRecords/Systems/GeneralStationRecordConsoleSystem.cs
+++ b/Content.Server/StationRecords/Systems/GeneralStationRecordConsoleSystem.cs
@@ -23,9 +23,22 @@ public override void Initialize()
subs.Event(UpdateUserInterface);
subs.Event(OnKeySelected);
subs.Event(OnFiltersChanged);
+ subs.Event(OnRecordDelete);
});
}
+ private void OnRecordDelete(Entity ent, ref DeleteStationRecord args)
+ {
+ if (!ent.Comp.CanDeleteEntries)
+ return;
+
+ var owning = _station.GetOwningStation(ent.Owner);
+
+ if (owning != null)
+ _stationRecords.RemoveRecord(new StationRecordKey(args.Id, owning.Value));
+ UpdateUserInterface(ent); // Apparently an event does not get raised for this.
+ }
+
private void UpdateUserInterface(Entity ent, ref T args)
{
UpdateUserInterface(ent);
@@ -68,8 +81,9 @@ private void UpdateUserInterface(Entity en
case 0:
_ui.SetUiState(uid, GeneralStationRecordConsoleKey.Key, new GeneralStationRecordConsoleState());
return;
- case 1:
- console.ActiveKey = listing.Keys.First();
+ default:
+ if (console.ActiveKey == null)
+ console.ActiveKey = listing.Keys.First();
break;
}
@@ -79,7 +93,7 @@ private void UpdateUserInterface(Entity en
var key = new StationRecordKey(id, owningStation.Value);
_stationRecords.TryGetRecord(key, out var record, stationRecords);
- GeneralStationRecordConsoleState newState = new(id, record, listing, console.Filter);
+ GeneralStationRecordConsoleState newState = new(id, record, listing, console.Filter, ent.Comp.CanDeleteEntries);
_ui.SetUiState(uid, GeneralStationRecordConsoleKey.Key, newState);
}
}
diff --git a/Content.Shared/StationRecords/GeneralRecordsUi.cs b/Content.Shared/StationRecords/GeneralRecordsUi.cs
index 860454efde5..2105c53df25 100644
--- a/Content.Shared/StationRecords/GeneralRecordsUi.cs
+++ b/Content.Shared/StationRecords/GeneralRecordsUi.cs
@@ -37,17 +37,22 @@ public sealed class GeneralStationRecordConsoleState : BoundUserInterfaceState
public readonly GeneralStationRecord? Record;
public readonly Dictionary? RecordListing;
public readonly StationRecordsFilter? Filter;
+ public readonly bool CanDeleteEntries;
- public GeneralStationRecordConsoleState(uint? key, GeneralStationRecord? record,
- Dictionary? recordListing, StationRecordsFilter? newFilter)
+ public GeneralStationRecordConsoleState(uint? key,
+ GeneralStationRecord? record,
+ Dictionary? recordListing,
+ StationRecordsFilter? newFilter,
+ bool canDeleteEntries)
{
SelectedKey = key;
Record = record;
RecordListing = recordListing;
Filter = newFilter;
+ CanDeleteEntries = canDeleteEntries;
}
- public GeneralStationRecordConsoleState() : this(null, null, null, null)
+ public GeneralStationRecordConsoleState() : this(null, null, null, null, false)
{
}
@@ -69,3 +74,15 @@ public SelectStationRecord(uint? selectedKey)
SelectedKey = selectedKey;
}
}
+
+
+[Serializable, NetSerializable]
+public sealed class DeleteStationRecord : BoundUserInterfaceMessage
+{
+ public DeleteStationRecord(uint id)
+ {
+ Id = id;
+ }
+
+ public readonly uint Id;
+}
diff --git a/Resources/Locale/en-US/station-records/general-station-records.ftl b/Resources/Locale/en-US/station-records/general-station-records.ftl
index e36bfac7cb3..4516a547f4d 100644
--- a/Resources/Locale/en-US/station-records/general-station-records.ftl
+++ b/Resources/Locale/en-US/station-records/general-station-records.ftl
@@ -16,3 +16,4 @@ general-station-record-prints-filter = Fingerprints
general-station-record-dna-filter = DNA
general-station-record-console-search-records = Search
general-station-record-console-reset-filters = Reset
+general-station-record-console-delete = Delete
diff --git a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml
index c9aac35732b..1366dfb76ab 100644
--- a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml
+++ b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml
@@ -80,6 +80,7 @@
- type: CargoOrderConsole
- type: CrewMonitoringConsole
- type: GeneralStationRecordConsole
+ canDeleteEntries: true
- type: DeviceNetwork
deviceNetId: Wireless
receiveFrequencyId: CrewMonitor
From 47710b6e8e246da7732d7227f4c5313b8f0da3b9 Mon Sep 17 00:00:00 2001
From: Tayrtahn
Date: Tue, 18 Jun 2024 06:59:37 -0400
Subject: [PATCH 012/366] Implement vital chef's hat functionality (#25950)
* Implement crucial chef's hat functionality
* Unified stopping code and added events.
* Added documentation to events
* Rerun tests
* Made review changes, and fixed potential desync bug.
* Update whitelist
---
.../Systems/PilotedByClothingSystem.cs | 19 ++
.../Components/PilotedByClothingComponent.cs | 12 ++
.../Components/PilotedClothingComponent.cs | 38 ++++
.../EntitySystems/PilotedClothingSystem.cs | 169 ++++++++++++++++++
.../Entities/Clothing/Head/hats.yml | 4 +
.../Prototypes/Entities/Mobs/NPCs/animals.yml | 2 +
Resources/Prototypes/tags.yml | 4 +
7 files changed, 248 insertions(+)
create mode 100644 Content.Client/Clothing/Systems/PilotedByClothingSystem.cs
create mode 100644 Content.Shared/Clothing/Components/PilotedByClothingComponent.cs
create mode 100644 Content.Shared/Clothing/Components/PilotedClothingComponent.cs
create mode 100644 Content.Shared/Clothing/EntitySystems/PilotedClothingSystem.cs
diff --git a/Content.Client/Clothing/Systems/PilotedByClothingSystem.cs b/Content.Client/Clothing/Systems/PilotedByClothingSystem.cs
new file mode 100644
index 00000000000..c04cf0a60ba
--- /dev/null
+++ b/Content.Client/Clothing/Systems/PilotedByClothingSystem.cs
@@ -0,0 +1,19 @@
+using Content.Shared.Clothing.Components;
+using Robust.Client.Physics;
+
+namespace Content.Client.Clothing.Systems;
+
+public sealed partial class PilotedByClothingSystem : EntitySystem
+{
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnUpdatePredicted);
+ }
+
+ private void OnUpdatePredicted(Entity entity, ref UpdateIsPredictedEvent args)
+ {
+ args.BlockPrediction = true;
+ }
+}
diff --git a/Content.Shared/Clothing/Components/PilotedByClothingComponent.cs b/Content.Shared/Clothing/Components/PilotedByClothingComponent.cs
new file mode 100644
index 00000000000..cd4d0d62030
--- /dev/null
+++ b/Content.Shared/Clothing/Components/PilotedByClothingComponent.cs
@@ -0,0 +1,12 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Clothing.Components;
+
+///
+/// Disables client-side physics prediction for this entity.
+/// Without this, movement with is very rubberbandy.
+///
+[RegisterComponent, NetworkedComponent]
+public sealed partial class PilotedByClothingComponent : Component
+{
+}
diff --git a/Content.Shared/Clothing/Components/PilotedClothingComponent.cs b/Content.Shared/Clothing/Components/PilotedClothingComponent.cs
new file mode 100644
index 00000000000..a349e4e485e
--- /dev/null
+++ b/Content.Shared/Clothing/Components/PilotedClothingComponent.cs
@@ -0,0 +1,38 @@
+using Content.Shared.Whitelist;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Clothing.Components;
+
+///
+/// Allows an entity stored in this clothing item to pass inputs to the entity wearing it.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class PilotedClothingComponent : Component
+{
+ ///
+ /// Whitelist for entities that are allowed to act as pilots when inside this entity.
+ ///
+ [DataField]
+ public EntityWhitelist? PilotWhitelist;
+
+ ///
+ /// Should movement input be relayed from the pilot to the target?
+ ///
+ [DataField]
+ public bool RelayMovement = true;
+
+
+ ///
+ /// Reference to the entity contained in the clothing and acting as pilot.
+ ///
+ [DataField, AutoNetworkedField]
+ public EntityUid? Pilot;
+
+ ///
+ /// Reference to the entity wearing this clothing who will be controlled by the pilot.
+ ///
+ [DataField, AutoNetworkedField]
+ public EntityUid? Wearer;
+
+ public bool IsActive => Pilot != null && Wearer != null;
+}
diff --git a/Content.Shared/Clothing/EntitySystems/PilotedClothingSystem.cs b/Content.Shared/Clothing/EntitySystems/PilotedClothingSystem.cs
new file mode 100644
index 00000000000..49df7aee943
--- /dev/null
+++ b/Content.Shared/Clothing/EntitySystems/PilotedClothingSystem.cs
@@ -0,0 +1,169 @@
+using Content.Shared.Clothing.Components;
+using Content.Shared.Inventory.Events;
+using Content.Shared.Movement.Components;
+using Content.Shared.Movement.Systems;
+using Content.Shared.Storage;
+using Content.Shared.Whitelist;
+using Robust.Shared.Containers;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Clothing.EntitySystems;
+
+public sealed partial class PilotedClothingSystem : EntitySystem
+{
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly SharedMoverController _moverController = default!;
+ [Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnEntInserted);
+ SubscribeLocalEvent(OnEntRemoved);
+ SubscribeLocalEvent(OnEquipped);
+ SubscribeLocalEvent(OnUnequipped);
+ }
+
+ private void OnEntInserted(Entity entity, ref EntInsertedIntoContainerMessage args)
+ {
+ // Make sure the entity was actually inserted into storage and not a different container.
+ if (!TryComp(entity, out StorageComponent? storage) || args.Container != storage.Container)
+ return;
+
+ // Check potential pilot against whitelist, if one exists.
+ if (_whitelist.IsWhitelistFail(entity.Comp.PilotWhitelist, args.Entity))
+ return;
+
+ entity.Comp.Pilot = args.Entity;
+ Dirty(entity);
+
+ // Attempt to setup control link, if Pilot and Wearer are both present.
+ StartPiloting(entity);
+ }
+
+ private void OnEntRemoved(Entity entity, ref EntRemovedFromContainerMessage args)
+ {
+ // Make sure the removed entity is actually the pilot.
+ if (args.Entity != entity.Comp.Pilot)
+ return;
+
+ StopPiloting(entity);
+ entity.Comp.Pilot = null;
+ Dirty(entity);
+ }
+
+ private void OnEquipped(Entity entity, ref GotEquippedEvent args)
+ {
+ if (!TryComp(entity, out ClothingComponent? clothing))
+ return;
+
+ // Make sure the clothing item was equipped to the right slot, and not just held in a hand.
+ var isCorrectSlot = (clothing.Slots & args.SlotFlags) != Inventory.SlotFlags.NONE;
+ if (!isCorrectSlot)
+ return;
+
+ entity.Comp.Wearer = args.Equipee;
+ Dirty(entity);
+
+ // Attempt to setup control link, if Pilot and Wearer are both present.
+ StartPiloting(entity);
+ }
+
+ private void OnUnequipped(Entity entity, ref GotUnequippedEvent args)
+ {
+ StopPiloting(entity);
+
+ entity.Comp.Wearer = null;
+ Dirty(entity);
+ }
+
+ ///
+ /// Attempts to establish movement/interaction relay connection(s) from Pilot to Wearer.
+ /// If either is missing, fails and returns false.
+ ///
+ private bool StartPiloting(Entity entity)
+ {
+ // Make sure we have both a Pilot and a Wearer
+ if (entity.Comp.Pilot == null || entity.Comp.Wearer == null)
+ return false;
+
+ if (!_timing.IsFirstTimePredicted)
+ return false;
+
+ var pilotEnt = entity.Comp.Pilot.Value;
+ var wearerEnt = entity.Comp.Wearer.Value;
+
+ // Add component to block prediction of wearer
+ EnsureComp(wearerEnt);
+
+ if (entity.Comp.RelayMovement)
+ {
+ // Establish movement input relay.
+ _moverController.SetRelay(pilotEnt, wearerEnt);
+ }
+
+ var pilotEv = new StartedPilotingClothingEvent(entity, wearerEnt);
+ RaiseLocalEvent(pilotEnt, ref pilotEv);
+
+ var wearerEv = new StartingBeingPilotedByClothing(entity, pilotEnt);
+ RaiseLocalEvent(wearerEnt, ref wearerEv);
+
+ return true;
+ }
+
+ ///
+ /// Removes components from the Pilot and Wearer to stop the control relay.
+ /// Returns false if a connection does not already exist.
+ ///
+ private bool StopPiloting(Entity entity)
+ {
+ if (entity.Comp.Pilot == null || entity.Comp.Wearer == null)
+ return false;
+
+ // Clean up components on the Pilot
+ var pilotEnt = entity.Comp.Pilot.Value;
+ RemCompDeferred(pilotEnt);
+
+ // Clean up components on the Wearer
+ var wearerEnt = entity.Comp.Wearer.Value;
+ RemCompDeferred(wearerEnt);
+ RemCompDeferred(wearerEnt);
+
+ // Raise an event on the Pilot
+ var pilotEv = new StoppedPilotingClothingEvent(entity, wearerEnt);
+ RaiseLocalEvent(pilotEnt, ref pilotEv);
+
+ // Raise an event on the Wearer
+ var wearerEv = new StoppedBeingPilotedByClothing(entity, pilotEnt);
+ RaiseLocalEvent(wearerEnt, ref wearerEv);
+
+ return true;
+ }
+}
+
+///
+/// Raised on the Pilot when they gain control of the Wearer.
+///
+[ByRefEvent]
+public record struct StartedPilotingClothingEvent(EntityUid Clothing, EntityUid Wearer);
+
+///
+/// Raised on the Pilot when they lose control of the Wearer,
+/// due to the Pilot exiting the clothing or the clothing being unequipped by the Wearer.
+///
+[ByRefEvent]
+public record struct StoppedPilotingClothingEvent(EntityUid Clothing, EntityUid Wearer);
+
+///
+/// Raised on the Wearer when the Pilot gains control of them.
+///
+[ByRefEvent]
+public record struct StartingBeingPilotedByClothing(EntityUid Clothing, EntityUid Pilot);
+
+///
+/// Raised on the Wearer when the Pilot loses control of them
+/// due to the Pilot exiting the clothing or the clothing being unequipped by the Wearer.
+///
+[ByRefEvent]
+public record struct StoppedBeingPilotedByClothing(EntityUid Clothing, EntityUid Pilot);
diff --git a/Resources/Prototypes/Entities/Clothing/Head/hats.yml b/Resources/Prototypes/Entities/Clothing/Head/hats.yml
index 5b5d281362d..6bba9320e8b 100644
--- a/Resources/Prototypes/Entities/Clothing/Head/hats.yml
+++ b/Resources/Prototypes/Entities/Clothing/Head/hats.yml
@@ -252,6 +252,10 @@
- type: ContainerContainer
containers:
storagebase: !type:Container
+ - type: PilotedClothing
+ pilotWhitelist:
+ tags:
+ - ChefPilot
- type: Tag
tags:
- ClothMade
diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
index 6e369389a90..5aa41301d77 100644
--- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
+++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
@@ -1726,6 +1726,7 @@
tags:
- Trash
- VimPilot
+ - ChefPilot
- Mouse
- Meat
- type: Respirator
@@ -3374,6 +3375,7 @@
- type: Tag
tags:
- VimPilot
+ - ChefPilot
- Trash
- Hamster
- Meat
diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml
index e7f20f8f47b..415f9369dc7 100644
--- a/Resources/Prototypes/tags.yml
+++ b/Resources/Prototypes/tags.yml
@@ -371,6 +371,10 @@
- type: Tag
id: Chicken
+# Allowed to control someone wearing a Chef's hat if inside their hat.
+- type: Tag
+ id: ChefPilot
+
- type: Tag
id: ChemDispensable # container that can go into the chem dispenser
From c5f663bf5567865be921d501af25b74d3393b572 Mon Sep 17 00:00:00 2001
From: Mervill
Date: Mon, 29 Jul 2024 00:28:17 -0700
Subject: [PATCH 013/366] Make the powered examine text fully client predicted
(#30441)
* Make the powered examine text fully client predicted
* switch to using the Entity