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)
--- /dev/null
+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);
+ }
+}
/// <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.
[AlwaysPushInheritance]
public HashSet<string> ToRemove = new();
- [DataField]
- public string? Speech { get; private set; }
-
- [DataField]
- public bool DoSpeech { get; private set; }
}
-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; }
}
-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.
[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>
-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
/// </summary>
[DataField]
public float Range = 10f;
-
- [DataField]
- public string? Speech { get; private set; }
}
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; }
}
-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; }
}
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
[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.
-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>
/// </summary>
[DataField]
public bool DeleteNonBrainParts = true;
-
- [DataField]
- public string? Speech { get; private set; }
}
+++ /dev/null
-namespace Content.Shared.Magic.Events;
-
-[ByRefEvent]
-public readonly struct SpeakSpellEvent(EntityUid performer, string speech)
-{
- public readonly EntityUid Performer = performer;
- public readonly string Speech = speech;
-}
-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
-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>
-using System.Numerics;
+using System.Numerics;
using Content.Shared.Actions;
using Content.Shared.Storage;
// 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
/// </summary>
[DataField]
public float? Lifetime;
-
- [DataField]
- public string? Speech { get; private set; }
}
+++ /dev/null
-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; }
-}
using System.Numerics;
-using Content.Shared.Actions;
using Content.Shared.Body.Components;
using Content.Shared.Body.Systems;
using Content.Shared.Coordinates.Helpers;
SpawnSpellHelper(args.Prototype, position, args.Performer, preventCollide: args.PreventCollideWithCaster);
}
- Speak(args);
args.Handled = true;
}
var targetMapCoords = args.Target;
WorldSpawnSpellHelper(args.Prototypes, targetMapCoords, args.Performer, args.Lifetime, args.Offset);
- Speak(args);
args.Handled = true;
}
return;
ev.Handled = true;
- Speak(ev);
var xform = Transform(ev.Performer);
var fromCoords = xform.Coordinates;
return;
ev.Handled = true;
- if (ev.DoSpeech)
- Speak(ev);
RemoveComponents(ev.Target, ev.ToRemove);
AddComponents(ev.Target, ev.ToAdd);
_transform.SetCoordinates(args.Performer, args.Target);
_transform.AttachToGridOrMap(args.Performer, transform);
- Speak(args);
args.Handled = true;
}
return;
ev.Handled = true;
- Speak(ev);
_transform.SwapPositions(ev.Performer, ev.Target);
}
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;
return;
args.Handled = true;
- Speak(args);
var transform = Transform(args.Performer);
}
ev.Handled = true;
- Speak(ev);
if (wand == null || !TryComp<BasicEntityAmmoProviderComponent>(wand, out var basicAmmoComp) || basicAmmoComp.Count == null)
return;
return;
ev.Handled = true;
- Speak(ev);
var allHumans = _mind.GetAliveHumans();
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
// 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);
- }
}
--- /dev/null
+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;
+}
--- /dev/null
+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;
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!
\ No newline at end of file
- BlockMovement
- Item
- MeleeRequiresWield
- speech: action-speech-spell-animate
- doSpeech: false
orGroup: Guns
- id: RevolverCapGunFake
orGroup: Guns
- speech: action-speech-spell-summon-guns
+ - type: SpeakOnAction
+ sentence: action-speech-spell-summon-guns
- type: entity
id: ActionSummonMagic
orGroup: Magics
- id: RGBStaff
orGroup: Magics
- speech: action-speech-spell-summon-magic
+ - type: SpeakOnAction
+ sentence: action-speech-spell-summon-magic
event: !type:InstantSpawnSpellEvent
prototype: WallForce
posData: !type:TargetInFront
- speech: action-speech-spell-forcewall
+ - type: SpeakOnAction
+ sentence: action-speech-spell-forcewall
sprite: Objects/Magic/magicactions.rsi
state: knock
event: !type:KnockSpellEvent
- speech: action-speech-spell-knock
+ - type: SpeakOnAction
+ sentence: action-speech-spell-knock
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
state: fireball
event: !type:ProjectileSpellEvent
prototype: ProjectileFireball
- speech: action-speech-spell-fireball
+ - type: SpeakOnAction
+ sentence: action-speech-spell-fireball
- type: ActionUpgrade
effectedLevels:
2: ActionFireballII
state: fireball
event: !type:ProjectileSpellEvent
prototype: ProjectileFireball
- speech: action-speech-spell-fireball
+ - type: SpeakOnAction
+ sentence: action-speech-spell-fireball
- type: entity
id: ActionFireballIII
state: fireball
event: !type:ProjectileSpellEvent
prototype: ProjectileFireball
- speech: action-speech-spell-fireball
+ - type: SpeakOnAction
+ sentence: action-speech-spell-fireball
- id: MobCarpMagic
amount: 3
offset: 0, 1
- speech: action-speech-spell-summon-magicarp
+ - type: SpeakOnAction
+ sentence: action-speech-spell-summon-magicarp
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
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
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
state: nothing
event: !type:ChargeSpellEvent
charge: 1
- speech: DI'RI CEL!
+ - type: SpeakOnAction
+ sentence: action-speech-spell-charge