Refactor magic speak system to be a component added to actions (#36328)

This commit is contained in:
J
2025-05-04 18:34:19 +01:00
committed by GitHub
parent 81cbb31425
commit 7b352643d5
28 changed files with 115 additions and 115 deletions

View File

@@ -21,13 +21,6 @@ public sealed class MagicSystem : SharedMagicSystem
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<SpeakSpellEvent>(OnSpellSpoken);
}
private void OnSpellSpoken(ref SpeakSpellEvent args)
{
_chat.TrySendInGameICMessage(args.Performer, Loc.GetString(args.Speech), InGameICChatType.Speak, false);
} }
public override void OnVoidApplause(VoidApplauseSpellEvent ev) public override void OnVoidApplause(VoidApplauseSpellEvent ev)

View File

@@ -0,0 +1,39 @@
using Content.Server.Chat.Systems;
using Content.Shared.Speech.Components;
using Content.Shared.Speech;
using Content.Shared.Speech.EntitySystems;
using Content.Shared.Speech.Muting;
using Content.Shared.Actions.Events;
namespace Content.Server.Speech.EntitySystems;
/// <summary>
/// As soon as the chat refactor moves to Shared
/// the logic here can move to the shared <see cref="SharedSpeakOnActionSystem"/>
/// </summary>
public sealed class SpeakOnActionSystem : SharedSpeakOnActionSystem
{
[Dependency] private readonly ChatSystem _chat = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SpeakOnActionComponent, ActionPerformedEvent>(OnActionPerformed);
}
private void OnActionPerformed(Entity<SpeakOnActionComponent> ent, ref ActionPerformedEvent args)
{
var user = args.Performer;
// If we can't speak, we can't speak
if (!HasComp<SpeechComponent>(user) || HasComp<MutedComponent>(user))
return;
if (string.IsNullOrWhiteSpace(ent.Comp.Sentence))
return;
_chat.TrySendInGameICMessage(user, Loc.GetString(ent.Comp.Sentence), InGameICChatType.Speak, false);
}
}

View File

@@ -6,7 +6,7 @@ namespace Content.Shared.Magic.Events;
/// <summary> /// <summary>
/// Spell that uses the magic of ECS to add & remove components. Components are first removed, then added. /// Spell that uses the magic of ECS to add & remove components. Components are first removed, then added.
/// </summary> /// </summary>
public sealed partial class ChangeComponentsSpellEvent : EntityTargetActionEvent, ISpeakSpell public sealed partial class ChangeComponentsSpellEvent : EntityTargetActionEvent
{ {
// TODO allow it to set component data-fields? // 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. // for now a Hackish way to do that is to remove & add, but that doesn't allow you to selectively set specific data fields.
@@ -19,9 +19,4 @@ public sealed partial class ChangeComponentsSpellEvent : EntityTargetActionEvent
[AlwaysPushInheritance] [AlwaysPushInheritance]
public HashSet<string> ToRemove = new(); public HashSet<string> ToRemove = new();
[DataField]
public string? Speech { get; private set; }
[DataField]
public bool DoSpeech { get; private set; }
} }

View File

@@ -1,18 +1,15 @@
using Content.Shared.Actions; using Content.Shared.Actions;
namespace Content.Shared.Magic.Events; namespace Content.Shared.Magic.Events;
/// <summary> /// <summary>
/// Adds provided Charge to the held wand /// Adds provided Charge to the held wand
/// </summary> /// </summary>
public sealed partial class ChargeSpellEvent : InstantActionEvent, ISpeakSpell public sealed partial class ChargeSpellEvent : InstantActionEvent
{ {
[DataField(required: true)] [DataField(required: true)]
public int Charge; public int Charge;
[DataField] [DataField]
public string WandTag = "WizardWand"; public string WandTag = "WizardWand";
[DataField]
public string? Speech { get; private set; }
} }

View File

@@ -1,9 +1,9 @@
using Content.Shared.Actions; using Content.Shared.Actions;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
namespace Content.Shared.Magic.Events; namespace Content.Shared.Magic.Events;
public sealed partial class InstantSpawnSpellEvent : InstantActionEvent, ISpeakSpell public sealed partial class InstantSpawnSpellEvent : InstantActionEvent
{ {
/// <summary> /// <summary>
/// What entity should be spawned. /// What entity should be spawned.
@@ -14,9 +14,6 @@ public sealed partial class InstantSpawnSpellEvent : InstantActionEvent, ISpeakS
[DataField] [DataField]
public bool PreventCollideWithCaster = true; public bool PreventCollideWithCaster = true;
[DataField]
public string? Speech { get; private set; }
/// <summary> /// <summary>
/// Gets the targeted spawn positons; may lead to multiple entities being spawned. /// Gets the targeted spawn positons; may lead to multiple entities being spawned.
/// </summary> /// </summary>

View File

@@ -1,8 +1,8 @@
using Content.Shared.Actions; using Content.Shared.Actions;
namespace Content.Shared.Magic.Events; namespace Content.Shared.Magic.Events;
public sealed partial class KnockSpellEvent : InstantActionEvent, ISpeakSpell public sealed partial class KnockSpellEvent : InstantActionEvent
{ {
/// <summary> /// <summary>
/// The range this spell opens doors in /// The range this spell opens doors in
@@ -11,7 +11,4 @@ public sealed partial class KnockSpellEvent : InstantActionEvent, ISpeakSpell
/// </summary> /// </summary>
[DataField] [DataField]
public float Range = 10f; public float Range = 10f;
[DataField]
public string? Speech { get; private set; }
} }

View File

@@ -2,14 +2,11 @@ using Content.Shared.Actions;
namespace Content.Shared.Magic.Events; namespace Content.Shared.Magic.Events;
public sealed partial class MindSwapSpellEvent : EntityTargetActionEvent, ISpeakSpell public sealed partial class MindSwapSpellEvent : EntityTargetActionEvent
{ {
[DataField] [DataField]
public TimeSpan PerformerStunDuration = TimeSpan.FromSeconds(10); public TimeSpan PerformerStunDuration = TimeSpan.FromSeconds(10);
[DataField] [DataField]
public TimeSpan TargetStunDuration = TimeSpan.FromSeconds(10); public TimeSpan TargetStunDuration = TimeSpan.FromSeconds(10);
[DataField]
public string? Speech { get; private set; }
} }

View File

@@ -1,16 +1,13 @@
using Content.Shared.Actions; using Content.Shared.Actions;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
namespace Content.Shared.Magic.Events; namespace Content.Shared.Magic.Events;
public sealed partial class ProjectileSpellEvent : WorldTargetActionEvent, ISpeakSpell public sealed partial class ProjectileSpellEvent : WorldTargetActionEvent
{ {
/// <summary> /// <summary>
/// What entity should be spawned. /// What entity should be spawned.
/// </summary> /// </summary>
[DataField(required: true)] [DataField(required: true)]
public EntProtoId Prototype; public EntProtoId Prototype;
[DataField]
public string? Speech { get; private set; }
} }

View File

@@ -4,7 +4,7 @@ using Robust.Shared.Audio;
namespace Content.Shared.Magic.Events; namespace Content.Shared.Magic.Events;
public sealed partial class RandomGlobalSpawnSpellEvent : InstantActionEvent, ISpeakSpell public sealed partial class RandomGlobalSpawnSpellEvent : InstantActionEvent
{ {
/// <summary> /// <summary>
/// The list of prototypes this spell can spawn, will select one randomly /// The list of prototypes this spell can spawn, will select one randomly
@@ -18,9 +18,6 @@ public sealed partial class RandomGlobalSpawnSpellEvent : InstantActionEvent, IS
[DataField] [DataField]
public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/Magic/staff_animation.ogg"); public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/Magic/staff_animation.ogg");
[DataField]
public string? Speech { get; private set; }
/// <summary> /// <summary>
/// Should this Global spawn spell turn its targets into a Survivor Antagonist? /// Should this Global spawn spell turn its targets into a Survivor Antagonist?
/// Ignores the caster for this. /// Ignores the caster for this.

View File

@@ -1,8 +1,8 @@
using Content.Shared.Actions; using Content.Shared.Actions;
namespace Content.Shared.Magic.Events; namespace Content.Shared.Magic.Events;
public sealed partial class SmiteSpellEvent : EntityTargetActionEvent, ISpeakSpell public sealed partial class SmiteSpellEvent : EntityTargetActionEvent
{ {
// TODO: Make part of gib method // TODO: Make part of gib method
/// <summary> /// <summary>
@@ -10,7 +10,4 @@ public sealed partial class SmiteSpellEvent : EntityTargetActionEvent, ISpeakSpe
/// </summary> /// </summary>
[DataField] [DataField]
public bool DeleteNonBrainParts = true; public bool DeleteNonBrainParts = true;
[DataField]
public string? Speech { get; private set; }
} }

View File

@@ -1,8 +0,0 @@
namespace Content.Shared.Magic.Events;
[ByRefEvent]
public readonly struct SpeakSpellEvent(EntityUid performer, string speech)
{
public readonly EntityUid Performer = performer;
public readonly string Speech = speech;
}

View File

@@ -1,12 +1,10 @@
using Content.Shared.Actions; using Content.Shared.Actions;
namespace Content.Shared.Magic.Events; namespace Content.Shared.Magic.Events;
// TODO: Can probably just be an entity or something // TODO: Can probably just be an entity or something
public sealed partial class TeleportSpellEvent : WorldTargetActionEvent, ISpeakSpell public sealed partial class TeleportSpellEvent : WorldTargetActionEvent
{ {
[DataField]
public string? Speech { get; private set; }
// TODO: Move to magic component // TODO: Move to magic component
// TODO: Maybe not since sound specifier is a thing // TODO: Maybe not since sound specifier is a thing

View File

@@ -1,15 +1,12 @@
using Content.Shared.Actions; using Content.Shared.Actions;
using Content.Shared.Chat.Prototypes; using Content.Shared.Chat.Prototypes;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
namespace Content.Shared.Magic.Events; namespace Content.Shared.Magic.Events;
public sealed partial class VoidApplauseSpellEvent : EntityTargetActionEvent, ISpeakSpell public sealed partial class VoidApplauseSpellEvent : EntityTargetActionEvent
{ {
[DataField]
public string? Speech { get; private set; }
/// <summary> /// <summary>
/// Emote to use. /// Emote to use.
/// </summary> /// </summary>

View File

@@ -1,4 +1,4 @@
using System.Numerics; using System.Numerics;
using Content.Shared.Actions; using Content.Shared.Actions;
using Content.Shared.Storage; using Content.Shared.Storage;
@@ -6,7 +6,7 @@ namespace Content.Shared.Magic.Events;
// TODO: This class needs combining with InstantSpawnSpellEvent // TODO: This class needs combining with InstantSpawnSpellEvent
public sealed partial class WorldSpawnSpellEvent : WorldTargetActionEvent, ISpeakSpell public sealed partial class WorldSpawnSpellEvent : WorldTargetActionEvent
{ {
/// <summary> /// <summary>
/// The list of prototypes this spell will spawn /// The list of prototypes this spell will spawn
@@ -28,7 +28,4 @@ public sealed partial class WorldSpawnSpellEvent : WorldTargetActionEvent, ISpea
/// </summary> /// </summary>
[DataField] [DataField]
public float? Lifetime; public float? Lifetime;
[DataField]
public string? Speech { get; private set; }
} }

View File

@@ -1,9 +0,0 @@
namespace Content.Shared.Magic;
public interface ISpeakSpell // The speak n spell interface
{
/// <summary>
/// Localized string spoken by the caster when casting this spell.
/// </summary>
public string? Speech { get; }
}

View File

@@ -1,5 +1,4 @@
using System.Numerics; using System.Numerics;
using Content.Shared.Actions;
using Content.Shared.Body.Components; using Content.Shared.Body.Components;
using Content.Shared.Body.Systems; using Content.Shared.Body.Systems;
using Content.Shared.Coordinates.Helpers; using Content.Shared.Coordinates.Helpers;
@@ -141,7 +140,6 @@ public abstract class SharedMagicSystem : EntitySystem
SpawnSpellHelper(args.Prototype, position, args.Performer, preventCollide: args.PreventCollideWithCaster); SpawnSpellHelper(args.Prototype, position, args.Performer, preventCollide: args.PreventCollideWithCaster);
} }
Speak(args);
args.Handled = true; args.Handled = true;
} }
@@ -235,7 +233,6 @@ public abstract class SharedMagicSystem : EntitySystem
var targetMapCoords = args.Target; var targetMapCoords = args.Target;
WorldSpawnSpellHelper(args.Prototypes, targetMapCoords, args.Performer, args.Lifetime, args.Offset); WorldSpawnSpellHelper(args.Prototypes, targetMapCoords, args.Performer, args.Lifetime, args.Offset);
Speak(args);
args.Handled = true; args.Handled = true;
} }
@@ -271,7 +268,6 @@ public abstract class SharedMagicSystem : EntitySystem
return; return;
ev.Handled = true; ev.Handled = true;
Speak(ev);
var xform = Transform(ev.Performer); var xform = Transform(ev.Performer);
var fromCoords = xform.Coordinates; var fromCoords = xform.Coordinates;
@@ -299,8 +295,6 @@ public abstract class SharedMagicSystem : EntitySystem
return; return;
ev.Handled = true; ev.Handled = true;
if (ev.DoSpeech)
Speak(ev);
RemoveComponents(ev.Target, ev.ToRemove); RemoveComponents(ev.Target, ev.ToRemove);
AddComponents(ev.Target, ev.ToAdd); AddComponents(ev.Target, ev.ToAdd);
@@ -324,7 +318,6 @@ public abstract class SharedMagicSystem : EntitySystem
_transform.SetCoordinates(args.Performer, args.Target); _transform.SetCoordinates(args.Performer, args.Target);
_transform.AttachToGridOrMap(args.Performer, transform); _transform.AttachToGridOrMap(args.Performer, transform);
Speak(args);
args.Handled = true; args.Handled = true;
} }
@@ -334,7 +327,6 @@ public abstract class SharedMagicSystem : EntitySystem
return; return;
ev.Handled = true; ev.Handled = true;
Speak(ev);
_transform.SwapPositions(ev.Performer, ev.Target); _transform.SwapPositions(ev.Performer, ev.Target);
} }
@@ -392,7 +384,6 @@ public abstract class SharedMagicSystem : EntitySystem
return; return;
ev.Handled = true; ev.Handled = true;
Speak(ev);
var direction = _transform.GetMapCoordinates(ev.Target, Transform(ev.Target)).Position - _transform.GetMapCoordinates(ev.Performer, Transform(ev.Performer)).Position; var direction = _transform.GetMapCoordinates(ev.Target, Transform(ev.Target)).Position - _transform.GetMapCoordinates(ev.Performer, Transform(ev.Performer)).Position;
var impulseVector = direction * 10000; var impulseVector = direction * 10000;
@@ -418,7 +409,6 @@ public abstract class SharedMagicSystem : EntitySystem
return; return;
args.Handled = true; args.Handled = true;
Speak(args);
var transform = Transform(args.Performer); var transform = Transform(args.Performer);
@@ -457,7 +447,6 @@ public abstract class SharedMagicSystem : EntitySystem
} }
ev.Handled = true; ev.Handled = true;
Speak(ev);
if (wand == null || !TryComp<BasicEntityAmmoProviderComponent>(wand, out var basicAmmoComp) || basicAmmoComp.Count == null) if (wand == null || !TryComp<BasicEntityAmmoProviderComponent>(wand, out var basicAmmoComp) || basicAmmoComp.Count == null)
return; return;
@@ -475,7 +464,6 @@ public abstract class SharedMagicSystem : EntitySystem
return; return;
ev.Handled = true; ev.Handled = true;
Speak(ev);
var allHumans = _mind.GetAliveHumans(); var allHumans = _mind.GetAliveHumans();
@@ -509,7 +497,6 @@ public abstract class SharedMagicSystem : EntitySystem
return; return;
ev.Handled = true; ev.Handled = true;
Speak(ev);
// Need performer mind, but target mind is unnecessary, such as taking over a NPC // Need performer mind, but target mind is unnecessary, such as taking over a NPC
// Need to get target mind before putting performer mind into their body if they have one // Need to get target mind before putting performer mind into their body if they have one
@@ -535,14 +522,4 @@ public abstract class SharedMagicSystem : EntitySystem
// End Spells // End Spells
#endregion #endregion
// When any spell is cast it will raise this as an event, so then it can be played in server or something. At least until chat gets moved to shared
// TODO: Temp until chat is in shared
private void Speak(BaseActionEvent args)
{
if (args is not ISpeakSpell speak || string.IsNullOrWhiteSpace(speak.Speech))
return;
var ev = new SpeakSpellEvent(args.Performer, speak.Speech);
RaiseLocalEvent(ref ev);
}
} }

View File

@@ -0,0 +1,17 @@
using Content.Shared.Speech.EntitySystems;
using Robust.Shared.GameStates;
namespace Content.Shared.Speech.Components;
/// <summary>
/// Action components which should write a message to ICChat on use
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedSpeakOnActionSystem))]
public sealed partial class SpeakOnActionComponent : Component
{
/// <summary>
/// The ftl id of the sentence that the user will speak.
/// </summary>
[DataField, AutoNetworkedField]
public LocId? Sentence;
}

View File

@@ -0,0 +1,13 @@
using Content.Shared.Chasm;
using Content.Shared.Speech.Components;
using Content.Shared.Speech.Muting;
using System;
namespace Content.Shared.Speech.EntitySystems;
/// <summary>
/// Once the chat refactor has happened, move the code from
/// <see cref="Content.Server.Speech.EntitySystems.SpeakOnUseSystem"/>
/// to here and set this class to sealed.
/// </summary>
public abstract class SharedSpeakOnActionSystem : EntitySystem;

View File

@@ -8,3 +8,4 @@ action-speech-spell-summon-magic = RYGOIN FEMA-VERECO
action-speech-spell-mind-swap = GIN'YU CAPAN! action-speech-spell-mind-swap = GIN'YU CAPAN!
action-speech-spell-cluwne = !KNOH action-speech-spell-cluwne = !KNOH
action-speech-spell-slip = SLEE PARRI! action-speech-spell-slip = SLEE PARRI!
action-speech-spell-charge = DI'RI CEL!

View File

@@ -74,5 +74,3 @@
- BlockMovement - BlockMovement
- Item - Item
- MeleeRequiresWield - MeleeRequiresWield
speech: action-speech-spell-animate
doSpeech: false

View File

@@ -158,7 +158,8 @@
orGroup: Guns orGroup: Guns
- id: RevolverCapGunFake - id: RevolverCapGunFake
orGroup: Guns orGroup: Guns
speech: action-speech-spell-summon-guns - type: SpeakOnAction
sentence: action-speech-spell-summon-guns
- type: entity - type: entity
id: ActionSummonMagic id: ActionSummonMagic
@@ -205,4 +206,5 @@
orGroup: Magics orGroup: Magics
- id: RGBStaff - id: RGBStaff
orGroup: Magics orGroup: Magics
speech: action-speech-spell-summon-magic - type: SpeakOnAction
sentence: action-speech-spell-summon-magic

View File

@@ -14,4 +14,5 @@
event: !type:InstantSpawnSpellEvent event: !type:InstantSpawnSpellEvent
prototype: WallForce prototype: WallForce
posData: !type:TargetInFront posData: !type:TargetInFront
speech: action-speech-spell-forcewall - type: SpeakOnAction
sentence: action-speech-spell-forcewall

View File

@@ -12,4 +12,5 @@
sprite: Objects/Magic/magicactions.rsi sprite: Objects/Magic/magicactions.rsi
state: knock state: knock
event: !type:KnockSpellEvent event: !type:KnockSpellEvent
speech: action-speech-spell-knock - type: SpeakOnAction
sentence: action-speech-spell-knock

View File

@@ -18,4 +18,5 @@
sprite: Mobs/Species/Human/organs.rsi sprite: Mobs/Species/Human/organs.rsi
state: brain state: brain
event: !type:MindSwapSpellEvent event: !type:MindSwapSpellEvent
speech: action-speech-spell-mind-swap - type: SpeakOnAction
sentence: action-speech-spell-mind-swap

View File

@@ -17,7 +17,8 @@
state: fireball state: fireball
event: !type:ProjectileSpellEvent event: !type:ProjectileSpellEvent
prototype: ProjectileFireball prototype: ProjectileFireball
speech: action-speech-spell-fireball - type: SpeakOnAction
sentence: action-speech-spell-fireball
- type: ActionUpgrade - type: ActionUpgrade
effectedLevels: effectedLevels:
2: ActionFireballII 2: ActionFireballII
@@ -41,7 +42,8 @@
state: fireball state: fireball
event: !type:ProjectileSpellEvent event: !type:ProjectileSpellEvent
prototype: ProjectileFireball prototype: ProjectileFireball
speech: action-speech-spell-fireball - type: SpeakOnAction
sentence: action-speech-spell-fireball
- type: entity - type: entity
id: ActionFireballIII id: ActionFireballIII
@@ -61,4 +63,5 @@
state: fireball state: fireball
event: !type:ProjectileSpellEvent event: !type:ProjectileSpellEvent
prototype: ProjectileFireball prototype: ProjectileFireball
speech: action-speech-spell-fireball - type: SpeakOnAction
sentence: action-speech-spell-fireball

View File

@@ -15,4 +15,5 @@
- id: MobCarpMagic - id: MobCarpMagic
amount: 3 amount: 3
offset: 0, 1 offset: 0, 1
speech: action-speech-spell-summon-magicarp - type: SpeakOnAction
sentence: action-speech-spell-summon-magicarp

View File

@@ -17,7 +17,8 @@
sprite: Objects/Magic/magicactions.rsi sprite: Objects/Magic/magicactions.rsi
state: gib state: gib
event: !type:SmiteSpellEvent event: !type:SmiteSpellEvent
speech: action-speech-spell-smite - type: SpeakOnAction
sentence: action-speech-spell-smite
- type: Magic - type: Magic
requiresClothes: true requiresClothes: true
@@ -47,9 +48,10 @@
sprite: Clothing/Mask/cluwne.rsi sprite: Clothing/Mask/cluwne.rsi
state: icon state: icon
event: !type:ChangeComponentsSpellEvent event: !type:ChangeComponentsSpellEvent
speech: action-speech-spell-cluwne
toAdd: toAdd:
- type: Cluwne - type: Cluwne
- type: SpeakOnAction
sentence: action-speech-spell-cluwne
- type: Magic - type: Magic
requiresClothes: true requiresClothes: true
@@ -72,10 +74,11 @@
sprite: Objects/Specific/Janitorial/soap.rsi sprite: Objects/Specific/Janitorial/soap.rsi
state: omega-4 state: omega-4
event: !type:ChangeComponentsSpellEvent event: !type:ChangeComponentsSpellEvent
speech: action-speech-spell-slip
toAdd: toAdd:
- type: Slippery - type: Slippery
- type: StepTrigger - type: StepTrigger
requiredTriggeredSpeed: -1 requiredTriggeredSpeed: -1
- type: SpeakOnAction
sentence: action-speech-spell-slip
- type: Magic - type: Magic
requiresClothes: true requiresClothes: true

View File

@@ -11,4 +11,5 @@
state: nothing state: nothing
event: !type:ChargeSpellEvent event: !type:ChargeSpellEvent
charge: 1 charge: 1
speech: DI'RI CEL! - type: SpeakOnAction
sentence: action-speech-spell-charge