From b33d1f003b14dbae5b741df57ea72846ae135eb9 Mon Sep 17 00:00:00 2001
From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com>
Date: Mon, 2 Jan 2023 13:01:40 +1300
Subject: [PATCH] Add RGB staff (#13125)
---
Content.Client/Actions/ActionsSystem.cs | 4 +-
.../Systems/Actions/ActionUIController.cs | 6 +-
.../Actions/ActionOnInteractComponent.cs | 30 ++++
.../Actions/ActionOnInteractSystem.cs | 131 ++++++++++++++++++
Content.Server/Actions/ActionsSystem.cs | 4 +-
.../Events/ChangeComponentsSpellEvent.cs | 21 +++
Content.Server/Magic/MagicSystem.cs | 26 ++++
.../Actions/ActionTypes/ActionType.cs | 2 +-
Content.Shared/Actions/SharedActionsSystem.cs | 14 +-
.../Entities/Objects/Decoration/present.yml | 2 +
Resources/Prototypes/Magic/staves.yml | 37 +++++
.../Weapons/Guns/Basic/staves.rsi/meta.json | 13 +-
.../Basic/staves.rsi/nothing-unshaded.png | Bin 0 -> 172 bytes
.../staves.rsi/staff-inhand-left-unshaded.png | Bin 0 -> 4938 bytes
.../staff-inhand-right-unshaded.png | Bin 0 -> 5129 bytes
15 files changed, 274 insertions(+), 16 deletions(-)
create mode 100644 Content.Server/Actions/ActionOnInteractComponent.cs
create mode 100644 Content.Server/Actions/ActionOnInteractSystem.cs
create mode 100644 Content.Server/Magic/Events/ChangeComponentsSpellEvent.cs
create mode 100644 Resources/Prototypes/Magic/staves.yml
create mode 100644 Resources/Textures/Objects/Weapons/Guns/Basic/staves.rsi/nothing-unshaded.png
create mode 100644 Resources/Textures/Objects/Weapons/Guns/Basic/staves.rsi/staff-inhand-left-unshaded.png
create mode 100644 Resources/Textures/Objects/Weapons/Guns/Basic/staves.rsi/staff-inhand-right-unshaded.png
diff --git a/Content.Client/Actions/ActionsSystem.cs b/Content.Client/Actions/ActionsSystem.cs
index de67ffa87c..f76205063b 100644
--- a/Content.Client/Actions/ActionsSystem.cs
+++ b/Content.Client/Actions/ActionsSystem.cs
@@ -149,7 +149,7 @@ namespace Content.Client.Actions
///
/// Execute convenience functionality for actions (pop-ups, sound, speech)
///
- protected override bool PerformBasicActions(EntityUid user, ActionType action)
+ protected override bool PerformBasicActions(EntityUid user, ActionType action, bool predicted)
{
var performedAction = action.Sound != null
|| !string.IsNullOrWhiteSpace(action.UserPopup)
@@ -233,7 +233,7 @@ namespace Content.Client.Actions
if (instantAction.Event != null)
instantAction.Event.Performer = user;
- PerformAction(PlayerActions, instantAction, instantAction.Event, GameTiming.CurTime);
+ PerformAction(user, PlayerActions, instantAction, instantAction.Event, GameTiming.CurTime);
}
else
{
diff --git a/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs b/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs
index ac6e0510d2..ad8172695f 100644
--- a/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs
+++ b/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs
@@ -1,4 +1,4 @@
-using System.Linq;
+using System.Linq;
using System.Runtime.InteropServices;
using Content.Client.Actions;
using Content.Client.Construction;
@@ -224,7 +224,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged
+/// This component enables an entity to perform actions when used to interact with the world, without actually
+/// granting that action to the entity that is using the item.
+///
+///
+/// If the entity is used in hand (), it will perform a random available instant
+/// action. If the entity is used to interact with another entity (), it will
+/// attempt to perform a random entity target action. Finally, if the entity is used to click somewhere in the world
+/// and no other interaction takes place (), then it will try to perform a random
+/// available entity or world target action. This component does not bypass standard interaction checks.
+///
+/// This component mainly exists as a lazy way to add utility entities that can do things like cast "spells".
+///
+[RegisterComponent]
+public sealed class ActionOnInteractComponent : Component
+{
+ [DataField("activateActions")]
+ public List? ActivateActions;
+
+ [DataField("entityActions")]
+ public List? EntityActions;
+
+ [DataField("worldActions")]
+ public List? WorldActions;
+}
diff --git a/Content.Server/Actions/ActionOnInteractSystem.cs b/Content.Server/Actions/ActionOnInteractSystem.cs
new file mode 100644
index 0000000000..baa01e9123
--- /dev/null
+++ b/Content.Server/Actions/ActionOnInteractSystem.cs
@@ -0,0 +1,131 @@
+using Content.Shared.Actions;
+using Content.Shared.Actions.ActionTypes;
+using Content.Shared.Interaction;
+using Robust.Shared.Random;
+using Robust.Shared.Timing;
+
+namespace Content.Server.Actions;
+
+///
+/// This System handled interactions for the .
+///
+public sealed class ActionOnInteractSystem : EntitySystem
+{
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly SharedActionsSystem _actions = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnActivate);
+ SubscribeLocalEvent(OnAfterInteract);
+ }
+
+ private void OnActivate(EntityUid uid, ActionOnInteractComponent component, ActivateInWorldEvent args)
+ {
+ if (args.Handled || component.ActivateActions == null)
+ return;
+
+ var options = new List();
+ foreach (var action in component.ActivateActions)
+ {
+ if (ValidAction(action))
+ options.Add(action);
+ }
+
+ if (options.Count == 0)
+ return;
+
+ var act = _random.Pick(options);
+ if (act.Event != null)
+ act.Event.Performer = args.User;
+
+ act.Provider = uid;
+ _actions.PerformAction(args.User, null, act, act.Event, _timing.CurTime, false);
+ args.Handled = true;
+ }
+
+ private void OnAfterInteract(EntityUid uid, ActionOnInteractComponent component, AfterInteractEvent args)
+ {
+ if (args.Handled)
+ return;
+
+ // First, try entity target actions
+ if (args.Target != null && component.EntityActions != null)
+ {
+ var entOptions = new List();
+ foreach (var action in component.EntityActions)
+ {
+ if (!ValidAction(action, args.CanReach))
+ continue;
+
+ if (!_actions.ValidateEntityTarget(args.User, args.Target.Value, action))
+ continue;
+
+ entOptions.Add(action);
+ }
+
+ if (entOptions.Count > 0)
+ {
+ var entAct = _random.Pick(entOptions);
+ if (entAct.Event != null)
+ {
+ entAct.Event.Performer = args.User;
+ entAct.Event.Target = args.Target.Value;
+ }
+
+ entAct.Provider = uid;
+ _actions.PerformAction(args.User, null, entAct, entAct.Event, _timing.CurTime, false);
+ args.Handled = true;
+ return;
+ }
+ }
+
+ // else: try world target actions
+ if (component.WorldActions == null)
+ return;
+
+ var options = new List();
+ foreach (var action in component.WorldActions)
+ {
+ if (!ValidAction(action, args.CanReach))
+ continue;
+
+ if (!_actions.ValidateWorldTarget(args.User, args.ClickLocation, action))
+ continue;
+
+ options.Add(action);
+ }
+
+ if (options.Count == 0)
+ return;
+
+ var act = _random.Pick(options);
+ if (act.Event != null)
+ {
+ act.Event.Performer = args.User;
+ act.Event.Target = args.ClickLocation;
+ }
+
+ act.Provider = uid;
+ _actions.PerformAction(args.User, null, act, act.Event, _timing.CurTime, false);
+ args.Handled = true;
+ }
+
+ private bool ValidAction(ActionType act, bool canReach = true)
+ {
+ if (!act.Enabled)
+ return false;
+
+ if (act.Charges.HasValue && act.Charges <= 0)
+ return false;
+
+ var curTime = _timing.CurTime;
+ if (act.Cooldown.HasValue && act.Cooldown.Value.End > curTime)
+ return false;
+
+ return canReach || act is TargetedAction { CheckCanAccess: false };
+ }
+}
diff --git a/Content.Server/Actions/ActionsSystem.cs b/Content.Server/Actions/ActionsSystem.cs
index 8d042be79a..3ccf02c206 100644
--- a/Content.Server/Actions/ActionsSystem.cs
+++ b/Content.Server/Actions/ActionsSystem.cs
@@ -18,9 +18,9 @@ namespace Content.Server.Actions
base.Initialize();
}
- protected override bool PerformBasicActions(EntityUid user, ActionType action)
+ protected override bool PerformBasicActions(EntityUid user, ActionType action, bool predicted)
{
- var result = base.PerformBasicActions(user, action);
+ var result = base.PerformBasicActions(user, action, predicted);
if (!string.IsNullOrWhiteSpace(action.Speech))
{
diff --git a/Content.Server/Magic/Events/ChangeComponentsSpellEvent.cs b/Content.Server/Magic/Events/ChangeComponentsSpellEvent.cs
new file mode 100644
index 0000000000..c9961605b9
--- /dev/null
+++ b/Content.Server/Magic/Events/ChangeComponentsSpellEvent.cs
@@ -0,0 +1,21 @@
+using Content.Shared.Actions;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.Magic.Events;
+
+///
+/// Spell that uses the magic of ECS to add & remove components. Components are first removed, then added.
+///
+public sealed class ChangeComponentsSpellEvent : EntityTargetActionEvent
+{
+ // TODO allow it to set component data-fields?
+ // for now a Hackish way to do that is to remove & add, but that doesn't allow you to selectively set specific data fields.
+
+ [DataField("toAdd")]
+ [AlwaysPushInheritance]
+ public EntityPrototype.ComponentRegistry ToAdd = new();
+
+ [DataField("toRemove")]
+ [AlwaysPushInheritance]
+ public HashSet ToRemove = new();
+}
diff --git a/Content.Server/Magic/MagicSystem.cs b/Content.Server/Magic/MagicSystem.cs
index 76e5336673..9628c2b6f6 100644
--- a/Content.Server/Magic/MagicSystem.cs
+++ b/Content.Server/Magic/MagicSystem.cs
@@ -18,11 +18,13 @@ using Content.Shared.Spawners.Components;
using Content.Shared.Storage;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
+using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Physics.Components;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
+using Robust.Shared.Serialization.Manager;
namespace Content.Server.Magic;
@@ -31,6 +33,8 @@ namespace Content.Server.Magic;
///
public sealed class MagicSystem : EntitySystem
{
+ [Dependency] private readonly ISerializationManager _seriMan = default!;
+ [Dependency] private readonly IComponentFactory _compFact = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
@@ -58,6 +62,7 @@ public sealed class MagicSystem : EntitySystem
SubscribeLocalEvent(OnSmiteSpell);
SubscribeLocalEvent(OnWorldSpawn);
SubscribeLocalEvent(OnProjectileSpell);
+ SubscribeLocalEvent(OnChangeComponentsSpell);
}
private void OnInit(EntityUid uid, SpellbookComponent component, ComponentInit args)
@@ -173,6 +178,27 @@ public sealed class MagicSystem : EntitySystem
}
}
+ private void OnChangeComponentsSpell(ChangeComponentsSpellEvent ev)
+ {
+ foreach (var toRemove in ev.ToRemove)
+ {
+ if (_compFact.TryGetRegistration(toRemove, out var registration))
+ RemComp(ev.Target, registration.Type);
+ }
+
+ foreach (var (name, data) in ev.ToAdd)
+ {
+ if (HasComp(ev.Target, data.Component.GetType()))
+ continue;
+
+ var component = (Component) _compFact.GetComponent(name);
+ component.Owner = ev.Target;
+ var temp = (object) component;
+ _seriMan.CopyTo(data.Component, ref temp);
+ EntityManager.AddComponent(ev.Target, (Component) temp!);
+ }
+ }
+
private List GetSpawnPositions(TransformComponent casterXform, MagicSpawnData data)
{
switch (data)
diff --git a/Content.Shared/Actions/ActionTypes/ActionType.cs b/Content.Shared/Actions/ActionTypes/ActionType.cs
index f8f334d400..5681be17aa 100644
--- a/Content.Shared/Actions/ActionTypes/ActionType.cs
+++ b/Content.Shared/Actions/ActionTypes/ActionType.cs
@@ -34,7 +34,7 @@ public abstract class ActionType : IEquatable, IComparable, ICloneab
///
/// Name to show in UI.
///
- [DataField("name", required: true)]
+ [DataField("name")]
public string DisplayName = string.Empty;
///
diff --git a/Content.Shared/Actions/SharedActionsSystem.cs b/Content.Shared/Actions/SharedActionsSystem.cs
index 0d0dbffda4..6bc8ef513a 100644
--- a/Content.Shared/Actions/SharedActionsSystem.cs
+++ b/Content.Shared/Actions/SharedActionsSystem.cs
@@ -202,7 +202,7 @@ public abstract class SharedActionsSystem : EntitySystem
performEvent.Performer = user;
// All checks passed. Perform the action!
- PerformAction(component, act, performEvent, curTime);
+ PerformAction(user, component, act, performEvent, curTime);
}
public bool ValidateEntityTarget(EntityUid user, EntityUid target, EntityTargetAction action)
@@ -265,7 +265,7 @@ public abstract class SharedActionsSystem : EntitySystem
return _interactionSystem.InRangeUnobstructed(user, coords, range: action.Range);
}
- public void PerformAction(ActionsComponent component, ActionType action, BaseActionEvent? actionEvent, TimeSpan curTime)
+ public void PerformAction(EntityUid performer, ActionsComponent? component, ActionType action, BaseActionEvent? actionEvent, TimeSpan curTime, bool predicted = true)
{
var handled = false;
@@ -277,7 +277,7 @@ public abstract class SharedActionsSystem : EntitySystem
actionEvent.Handled = false;
if (action.Provider == null)
- RaiseLocalEvent(component.Owner, (object) actionEvent, broadcast: true);
+ RaiseLocalEvent(performer, (object) actionEvent, broadcast: true);
else
RaiseLocalEvent(action.Provider.Value, (object) actionEvent, broadcast: true);
@@ -285,7 +285,7 @@ public abstract class SharedActionsSystem : EntitySystem
}
// Execute convenience functionality (pop-ups, sound, speech)
- handled |= PerformBasicActions(component.Owner, action);
+ handled |= PerformBasicActions(performer, action, predicted);
if (!handled)
return; // no interaction occurred.
@@ -309,19 +309,19 @@ public abstract class SharedActionsSystem : EntitySystem
action.Cooldown = (curTime, curTime + action.UseDelay.Value);
}
- if (dirty)
+ if (dirty && component != null)
Dirty(component);
}
///
/// Execute convenience functionality for actions (pop-ups, sound, speech)
///
- protected virtual bool PerformBasicActions(EntityUid performer, ActionType action)
+ protected virtual bool PerformBasicActions(EntityUid performer, ActionType action, bool predicted)
{
if (action.Sound == null && string.IsNullOrWhiteSpace(action.Popup))
return false;
- var filter = Filter.PvsExcept(performer);
+ var filter = predicted ? Filter.PvsExcept(performer) : Filter.Pvs(performer);
_audio.Play(action.Sound, filter, performer, true, action.AudioParams);
diff --git a/Resources/Prototypes/Entities/Objects/Decoration/present.yml b/Resources/Prototypes/Entities/Objects/Decoration/present.yml
index fb186f7b07..0740bb0a4d 100644
--- a/Resources/Prototypes/Entities/Objects/Decoration/present.yml
+++ b/Resources/Prototypes/Entities/Objects/Decoration/present.yml
@@ -352,6 +352,8 @@
- id: WeaponPulseCarbine
prob: .001
orGroup: GiftPool
+ - id: RGBStaff
+ orGroup: GiftPool
sound:
path: /Audio/Effects/unwrap.ogg
diff --git a/Resources/Prototypes/Magic/staves.yml b/Resources/Prototypes/Magic/staves.yml
new file mode 100644
index 0000000000..833c1215f0
--- /dev/null
+++ b/Resources/Prototypes/Magic/staves.yml
@@ -0,0 +1,37 @@
+# non-projectile / "gun" staves
+
+# wand that gives lights an RGB effect.
+- type: entity
+ id: RGBStaff
+ parent: BaseItem
+ name: RGB Staff
+ description: Helps fix the underabundance of RGB gear on the station.
+ components:
+ - type: Sprite
+ sprite: Objects/Weapons/Guns/Basic/staves.rsi
+ layers:
+ - state: nothing
+ - state: nothing-unshaded
+ shader: unshaded
+ - type: ActionOnInteract
+ entityActions:
+ - whitelist: { components: [ PointLight ] }
+ charges: 25
+ sound: /Audio/Magic/blink.ogg
+ event: !type:ChangeComponentsSpellEvent
+ toAdd:
+ - type: RgbLightController
+ - type: Item
+ inhandVisuals:
+ left:
+ - state: staff-inhand-left
+ - state: staff-inhand-left-unshaded
+ shader: unshaded
+ right:
+ - state: staff-inhand-right
+ - state: staff-inhand-right-unshaded
+ shader: unshaded
+ - type: RgbLightController
+ - type: PointLight
+ enabled: true
+ radius: 2
\ No newline at end of file
diff --git a/Resources/Textures/Objects/Weapons/Guns/Basic/staves.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Basic/staves.rsi/meta.json
index 336829591b..a8510801f8 100644
--- a/Resources/Textures/Objects/Weapons/Guns/Basic/staves.rsi/meta.json
+++ b/Resources/Textures/Objects/Weapons/Guns/Basic/staves.rsi/meta.json
@@ -1,7 +1,7 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
- "copyright": "/tg/station at https://github.com/tgstation/tgstation/commit/270acce4f551253d8ac75de19236b8b4be598f7f. edited by mirrorcult to allow layering",
+ "copyright": "/tg/station at https://github.com/tgstation/tgstation/commit/270acce4f551253d8ac75de19236b8b4be598f7f. edited by mirrorcult to allow layering. Generic unshaded layers added by electro.",
"size": {
"x": 32,
"y": 32
@@ -517,6 +517,17 @@
0.5
]
]
+ },
+ {
+ "name": "staff-inhand-right-unshaded",
+ "directions": 4
+ },
+ {
+ "name": "staff-inhand-left-unshaded",
+ "directions": 4
+ },
+ {
+ "name": "nothing-unshaded"
}
]
}
\ No newline at end of file
diff --git a/Resources/Textures/Objects/Weapons/Guns/Basic/staves.rsi/nothing-unshaded.png b/Resources/Textures/Objects/Weapons/Guns/Basic/staves.rsi/nothing-unshaded.png
new file mode 100644
index 0000000000000000000000000000000000000000..f1bacf6d5735419c31f0733f8f3bcdbd78bb3327
GIT binary patch
literal 172
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
IM6N<$f?F4Y-2eap
literal 0
HcmV?d00001
diff --git a/Resources/Textures/Objects/Weapons/Guns/Basic/staves.rsi/staff-inhand-right-unshaded.png b/Resources/Textures/Objects/Weapons/Guns/Basic/staves.rsi/staff-inhand-right-unshaded.png
new file mode 100644
index 0000000000000000000000000000000000000000..71b57625dbeb4e74ccd583be9ceaa7380b878611
GIT binary patch
literal 5129
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
literal 0
HcmV?d00001