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