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()
{
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)

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>
/// Spell that uses the magic of ECS to add & remove components. Components are first removed, then added.
/// </summary>
public sealed partial class ChangeComponentsSpellEvent : EntityTargetActionEvent, ISpeakSpell
public sealed partial 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.
@@ -19,9 +19,4 @@ public sealed partial class ChangeComponentsSpellEvent : EntityTargetActionEvent
[AlwaysPushInheritance]
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;
/// <summary>
/// Adds provided Charge to the held wand
/// </summary>
public sealed partial class ChargeSpellEvent : InstantActionEvent, ISpeakSpell
public sealed partial class ChargeSpellEvent : InstantActionEvent
{
[DataField(required: true)]
public int Charge;
[DataField]
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;
namespace Content.Shared.Magic.Events;
public sealed partial class InstantSpawnSpellEvent : InstantActionEvent, ISpeakSpell
public sealed partial class InstantSpawnSpellEvent : InstantActionEvent
{
/// <summary>
/// What entity should be spawned.
@@ -14,9 +14,6 @@ public sealed partial class InstantSpawnSpellEvent : InstantActionEvent, ISpeakS
[DataField]
public bool PreventCollideWithCaster = true;
[DataField]
public string? Speech { get; private set; }
/// <summary>
/// Gets the targeted spawn positons; may lead to multiple entities being spawned.
/// </summary>

View File

@@ -1,8 +1,8 @@
using Content.Shared.Actions;
using Content.Shared.Actions;
namespace Content.Shared.Magic.Events;
public sealed partial class KnockSpellEvent : InstantActionEvent, ISpeakSpell
public sealed partial class KnockSpellEvent : InstantActionEvent
{
/// <summary>
/// The range this spell opens doors in
@@ -11,7 +11,4 @@ public sealed partial class KnockSpellEvent : InstantActionEvent, ISpeakSpell
/// </summary>
[DataField]
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;
public sealed partial class MindSwapSpellEvent : EntityTargetActionEvent, ISpeakSpell
public sealed partial class MindSwapSpellEvent : EntityTargetActionEvent
{
[DataField]
public TimeSpan PerformerStunDuration = TimeSpan.FromSeconds(10);
[DataField]
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;
namespace Content.Shared.Magic.Events;
public sealed partial class ProjectileSpellEvent : WorldTargetActionEvent, ISpeakSpell
public sealed partial class ProjectileSpellEvent : WorldTargetActionEvent
{
/// <summary>
/// What entity should be spawned.
/// </summary>
[DataField(required: true)]
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;
public sealed partial class RandomGlobalSpawnSpellEvent : InstantActionEvent, ISpeakSpell
public sealed partial class RandomGlobalSpawnSpellEvent : InstantActionEvent
{
/// <summary>
/// The list of prototypes this spell can spawn, will select one randomly
@@ -18,9 +18,6 @@ public sealed partial class RandomGlobalSpawnSpellEvent : InstantActionEvent, IS
[DataField]
public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/Magic/staff_animation.ogg");
[DataField]
public string? Speech { get; private set; }
/// <summary>
/// Should this Global spawn spell turn its targets into a Survivor Antagonist?
/// Ignores the caster for this.

View File

@@ -1,8 +1,8 @@
using Content.Shared.Actions;
using Content.Shared.Actions;
namespace Content.Shared.Magic.Events;
public sealed partial class SmiteSpellEvent : EntityTargetActionEvent, ISpeakSpell
public sealed partial class SmiteSpellEvent : EntityTargetActionEvent
{
// TODO: Make part of gib method
/// <summary>
@@ -10,7 +10,4 @@ public sealed partial class SmiteSpellEvent : EntityTargetActionEvent, ISpeakSpe
/// </summary>
[DataField]
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;
// 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: 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 Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
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>
/// Emote to use.
/// </summary>

View File

@@ -1,4 +1,4 @@
using System.Numerics;
using System.Numerics;
using Content.Shared.Actions;
using Content.Shared.Storage;
@@ -6,7 +6,7 @@ namespace Content.Shared.Magic.Events;
// TODO: This class needs combining with InstantSpawnSpellEvent
public sealed partial class WorldSpawnSpellEvent : WorldTargetActionEvent, ISpeakSpell
public sealed partial class WorldSpawnSpellEvent : WorldTargetActionEvent
{
/// <summary>
/// The list of prototypes this spell will spawn
@@ -28,7 +28,4 @@ public sealed partial class WorldSpawnSpellEvent : WorldTargetActionEvent, ISpea
/// </summary>
[DataField]
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 Content.Shared.Actions;
using Content.Shared.Body.Components;
using Content.Shared.Body.Systems;
using Content.Shared.Coordinates.Helpers;
@@ -141,7 +140,6 @@ public abstract class SharedMagicSystem : EntitySystem
SpawnSpellHelper(args.Prototype, position, args.Performer, preventCollide: args.PreventCollideWithCaster);
}
Speak(args);
args.Handled = true;
}
@@ -235,7 +233,6 @@ public abstract class SharedMagicSystem : EntitySystem
var targetMapCoords = args.Target;
WorldSpawnSpellHelper(args.Prototypes, targetMapCoords, args.Performer, args.Lifetime, args.Offset);
Speak(args);
args.Handled = true;
}
@@ -271,7 +268,6 @@ public abstract class SharedMagicSystem : EntitySystem
return;
ev.Handled = true;
Speak(ev);
var xform = Transform(ev.Performer);
var fromCoords = xform.Coordinates;
@@ -299,8 +295,6 @@ public abstract class SharedMagicSystem : EntitySystem
return;
ev.Handled = true;
if (ev.DoSpeech)
Speak(ev);
RemoveComponents(ev.Target, ev.ToRemove);
AddComponents(ev.Target, ev.ToAdd);
@@ -324,7 +318,6 @@ public abstract class SharedMagicSystem : EntitySystem
_transform.SetCoordinates(args.Performer, args.Target);
_transform.AttachToGridOrMap(args.Performer, transform);
Speak(args);
args.Handled = true;
}
@@ -334,7 +327,6 @@ public abstract class SharedMagicSystem : EntitySystem
return;
ev.Handled = true;
Speak(ev);
_transform.SwapPositions(ev.Performer, ev.Target);
}
@@ -392,7 +384,6 @@ public abstract class SharedMagicSystem : EntitySystem
return;
ev.Handled = true;
Speak(ev);
var direction = _transform.GetMapCoordinates(ev.Target, Transform(ev.Target)).Position - _transform.GetMapCoordinates(ev.Performer, Transform(ev.Performer)).Position;
var impulseVector = direction * 10000;
@@ -418,7 +409,6 @@ public abstract class SharedMagicSystem : EntitySystem
return;
args.Handled = true;
Speak(args);
var transform = Transform(args.Performer);
@@ -457,7 +447,6 @@ public abstract class SharedMagicSystem : EntitySystem
}
ev.Handled = true;
Speak(ev);
if (wand == null || !TryComp<BasicEntityAmmoProviderComponent>(wand, out var basicAmmoComp) || basicAmmoComp.Count == null)
return;
@@ -475,7 +464,6 @@ public abstract class SharedMagicSystem : EntitySystem
return;
ev.Handled = true;
Speak(ev);
var allHumans = _mind.GetAliveHumans();
@@ -509,7 +497,6 @@ public abstract class SharedMagicSystem : EntitySystem
return;
ev.Handled = true;
Speak(ev);
// 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
@@ -535,14 +522,4 @@ public abstract class SharedMagicSystem : EntitySystem
// End Spells
#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-cluwne = !KNOH
action-speech-spell-slip = SLEE PARRI!
action-speech-spell-charge = DI'RI CEL!

View File

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

View File

@@ -158,7 +158,8 @@
orGroup: Guns
- id: RevolverCapGunFake
orGroup: Guns
speech: action-speech-spell-summon-guns
- type: SpeakOnAction
sentence: action-speech-spell-summon-guns
- type: entity
id: ActionSummonMagic
@@ -205,4 +206,5 @@
orGroup: Magics
- id: RGBStaff
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
prototype: WallForce
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
state: knock
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
state: brain
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
event: !type:ProjectileSpellEvent
prototype: ProjectileFireball
speech: action-speech-spell-fireball
- type: SpeakOnAction
sentence: action-speech-spell-fireball
- type: ActionUpgrade
effectedLevels:
2: ActionFireballII
@@ -41,7 +42,8 @@
state: fireball
event: !type:ProjectileSpellEvent
prototype: ProjectileFireball
speech: action-speech-spell-fireball
- type: SpeakOnAction
sentence: action-speech-spell-fireball
- type: entity
id: ActionFireballIII
@@ -61,4 +63,5 @@
state: fireball
event: !type:ProjectileSpellEvent
prototype: ProjectileFireball
speech: action-speech-spell-fireball
- type: SpeakOnAction
sentence: action-speech-spell-fireball

View File

@@ -15,4 +15,5 @@
- id: MobCarpMagic
amount: 3
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
state: gib
event: !type:SmiteSpellEvent
speech: action-speech-spell-smite
- type: SpeakOnAction
sentence: action-speech-spell-smite
- type: Magic
requiresClothes: true
@@ -47,9 +48,10 @@
sprite: Clothing/Mask/cluwne.rsi
state: icon
event: !type:ChangeComponentsSpellEvent
speech: action-speech-spell-cluwne
toAdd:
- type: Cluwne
- type: SpeakOnAction
sentence: action-speech-spell-cluwne
- type: Magic
requiresClothes: true
@@ -72,10 +74,11 @@
sprite: Objects/Specific/Janitorial/soap.rsi
state: omega-4
event: !type:ChangeComponentsSpellEvent
speech: action-speech-spell-slip
toAdd:
- type: Slippery
- type: StepTrigger
requiredTriggeredSpeed: -1
- type: SpeakOnAction
sentence: action-speech-spell-slip
- type: Magic
requiresClothes: true

View File

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