From 655b32b7201dcfd6f2e3ebb6b5ca33216de753f6 Mon Sep 17 00:00:00 2001 From: Ed <96445749+TheShuEd@users.noreply.github.com> Date: Sun, 28 Jul 2024 17:26:47 +0300 Subject: [PATCH] Magic spells backend (#358) * add EntityEffect aability support * delayed actions * renaming * delayed projectile spells * spawn on self + entityEffect on self spells * spawn on point spell * rename * clean up base species components * magic alert * move magic energy to Shared, add manacost to spells * magic recoil * improve magic recoil * verbal aspect * somatic aspect * add simple vfx proto and sprites * add casting VFX * add TODO --- Content.Server/_CP14/Magic/CP14MagicSystem.cs | 37 +++ .../MagicEnergy/CP14MagicEnergySystem.cs | 61 +---- .../_CP14/Magic/CP14IDelayedMagicEffect.cs | 13 ++ .../Magic/CP14SharedMagicSystem.Spells.cs | 130 +++++++++++ .../_CP14/Magic/CP14SharedMagicSystem.cs | 219 ++++++++++++++++++ .../CP14MagicEffectCastingVisualComponent.cs | 16 ++ .../Components/CP14MagicEffectComponent.cs | 16 ++ .../CP14MagicEffectSomaticAspectComponent.cs | 9 + .../CP14MagicEffectVerbalAspectComponent.cs | 27 +++ ...DelayedApplyEntityEffectsSpellComponent.cs | 13 ++ .../CP14DelayedProjectileSpellComponent.cs | 16 ++ ...14DelayedSelfEntityEffectSpellComponent.cs | 13 ++ .../CP14DelayedSpawnEntitiesSpellComponent.cs | 16 ++ ...DelayedSpawnOnWorldTargetSpellComponent.cs | 16 ++ .../Magic/Events/CP14CastMagicEffectEvent.cs | 41 ++++ .../Magic/Events/CP14DelayedActionEvents.cs | 64 +++++ .../CP14MagicEnergyContainerComponent.cs | 5 + .../SharedCP14MagicEnergySystem.cs | 110 ++++++++- .../CP14MagicUnsafeDamageComponent.cs | 21 ++ .../CP14MagicUnsafeSleepComponent.cs | 20 ++ .../MagicWeakness/CP14MagicWeaknessSystem.cs | 43 ++++ .../Locale/en-US/_CP14/alerts/alerts.ftl | 2 + .../en-US/_CP14/magicEnergy/magic-energy.ftl | 2 +- .../en-US/_CP14/magicEnergy/magic-spells.ftl | 10 + .../Locale/ru-RU/_CP14/alerts/alerts.ftl | 2 + .../ru-RU/_CP14/magicEnergy/magic-energy.ftl | 2 +- .../ru-RU/_CP14/magicEnergy/magic-spells.ftl | 13 ++ Resources/Prototypes/Alerts/alerts.yml | 1 + Resources/Prototypes/Entities/Mobs/base.yml | 2 +- Resources/Prototypes/_CP14/Alerts/alerts.yml | 29 +++ .../_CP14/Entities/Actions/test_spell.yml | 153 ++++++++++++ .../_CP14/Entities/Effects/cast_vfx.yml | 31 +++ .../_CP14/Entities/Mobs/Species/base.yml | 25 +- .../_CP14/Entities/Mobs/Species/tiefling.yml | 1 - .../Magic/cast_rune.rsi/double_outer.png | Bin 0 -> 3167 bytes .../Magic/cast_rune.rsi/medium_circle.png | Bin 0 -> 7974 bytes .../Magic/cast_rune.rsi/medium_line.png | Bin 0 -> 2572 bytes .../Effects/Magic/cast_rune.rsi/meta.json | 71 ++++++ .../_CP14/Effects/Magic/cast_rune.rsi/sun.png | Bin 0 -> 4597 bytes .../Alerts/magic_energy.rsi/mana_0.png | Bin 0 -> 413 bytes .../Alerts/magic_energy.rsi/mana_1.png | Bin 0 -> 466 bytes .../Alerts/magic_energy.rsi/mana_10.png | Bin 0 -> 467 bytes .../Alerts/magic_energy.rsi/mana_2.png | Bin 0 -> 503 bytes .../Alerts/magic_energy.rsi/mana_3.png | Bin 0 -> 499 bytes .../Alerts/magic_energy.rsi/mana_4.png | Bin 0 -> 507 bytes .../Alerts/magic_energy.rsi/mana_5.png | Bin 0 -> 517 bytes .../Alerts/magic_energy.rsi/mana_6.png | Bin 0 -> 557 bytes .../Alerts/magic_energy.rsi/mana_7.png | Bin 0 -> 525 bytes .../Alerts/magic_energy.rsi/mana_8.png | Bin 0 -> 541 bytes .../Alerts/magic_energy.rsi/mana_9.png | Bin 0 -> 523 bytes .../Alerts/magic_energy.rsi/meta.json | 44 ++++ 51 files changed, 1213 insertions(+), 81 deletions(-) create mode 100644 Content.Server/_CP14/Magic/CP14MagicSystem.cs create mode 100644 Content.Shared/_CP14/Magic/CP14IDelayedMagicEffect.cs create mode 100644 Content.Shared/_CP14/Magic/CP14SharedMagicSystem.Spells.cs create mode 100644 Content.Shared/_CP14/Magic/CP14SharedMagicSystem.cs create mode 100644 Content.Shared/_CP14/Magic/Components/CP14MagicEffectCastingVisualComponent.cs create mode 100644 Content.Shared/_CP14/Magic/Components/CP14MagicEffectComponent.cs create mode 100644 Content.Shared/_CP14/Magic/Components/CP14MagicEffectSomaticAspectComponent.cs create mode 100644 Content.Shared/_CP14/Magic/Components/CP14MagicEffectVerbalAspectComponent.cs create mode 100644 Content.Shared/_CP14/Magic/Components/Spells/CP14DelayedApplyEntityEffectsSpellComponent.cs create mode 100644 Content.Shared/_CP14/Magic/Components/Spells/CP14DelayedProjectileSpellComponent.cs create mode 100644 Content.Shared/_CP14/Magic/Components/Spells/CP14DelayedSelfEntityEffectSpellComponent.cs create mode 100644 Content.Shared/_CP14/Magic/Components/Spells/CP14DelayedSpawnEntitiesSpellComponent.cs create mode 100644 Content.Shared/_CP14/Magic/Components/Spells/CP14DelayedSpawnOnWorldTargetSpellComponent.cs create mode 100644 Content.Shared/_CP14/Magic/Events/CP14CastMagicEffectEvent.cs create mode 100644 Content.Shared/_CP14/Magic/Events/CP14DelayedActionEvents.cs create mode 100644 Content.Shared/_CP14/MagicWeakness/CP14MagicUnsafeDamageComponent.cs create mode 100644 Content.Shared/_CP14/MagicWeakness/CP14MagicUnsafeSleepComponent.cs create mode 100644 Content.Shared/_CP14/MagicWeakness/CP14MagicWeaknessSystem.cs create mode 100644 Resources/Locale/en-US/_CP14/alerts/alerts.ftl create mode 100644 Resources/Locale/en-US/_CP14/magicEnergy/magic-spells.ftl create mode 100644 Resources/Locale/ru-RU/_CP14/alerts/alerts.ftl create mode 100644 Resources/Locale/ru-RU/_CP14/magicEnergy/magic-spells.ftl create mode 100644 Resources/Prototypes/_CP14/Alerts/alerts.yml create mode 100644 Resources/Prototypes/_CP14/Entities/Actions/test_spell.yml create mode 100644 Resources/Prototypes/_CP14/Entities/Effects/cast_vfx.yml create mode 100644 Resources/Textures/_CP14/Effects/Magic/cast_rune.rsi/double_outer.png create mode 100644 Resources/Textures/_CP14/Effects/Magic/cast_rune.rsi/medium_circle.png create mode 100644 Resources/Textures/_CP14/Effects/Magic/cast_rune.rsi/medium_line.png create mode 100644 Resources/Textures/_CP14/Effects/Magic/cast_rune.rsi/meta.json create mode 100644 Resources/Textures/_CP14/Effects/Magic/cast_rune.rsi/sun.png create mode 100644 Resources/Textures/_CP14/Interface/Alerts/magic_energy.rsi/mana_0.png create mode 100644 Resources/Textures/_CP14/Interface/Alerts/magic_energy.rsi/mana_1.png create mode 100644 Resources/Textures/_CP14/Interface/Alerts/magic_energy.rsi/mana_10.png create mode 100644 Resources/Textures/_CP14/Interface/Alerts/magic_energy.rsi/mana_2.png create mode 100644 Resources/Textures/_CP14/Interface/Alerts/magic_energy.rsi/mana_3.png create mode 100644 Resources/Textures/_CP14/Interface/Alerts/magic_energy.rsi/mana_4.png create mode 100644 Resources/Textures/_CP14/Interface/Alerts/magic_energy.rsi/mana_5.png create mode 100644 Resources/Textures/_CP14/Interface/Alerts/magic_energy.rsi/mana_6.png create mode 100644 Resources/Textures/_CP14/Interface/Alerts/magic_energy.rsi/mana_7.png create mode 100644 Resources/Textures/_CP14/Interface/Alerts/magic_energy.rsi/mana_8.png create mode 100644 Resources/Textures/_CP14/Interface/Alerts/magic_energy.rsi/mana_9.png create mode 100644 Resources/Textures/_CP14/Interface/Alerts/magic_energy.rsi/meta.json diff --git a/Content.Server/_CP14/Magic/CP14MagicSystem.cs b/Content.Server/_CP14/Magic/CP14MagicSystem.cs new file mode 100644 index 0000000000..f0ee72f2d7 --- /dev/null +++ b/Content.Server/_CP14/Magic/CP14MagicSystem.cs @@ -0,0 +1,37 @@ +using Content.Server.Chat.Systems; +using Content.Shared._CP14.Magic; +using Content.Shared._CP14.Magic.Components; +using Content.Shared._CP14.Magic.Events; + +namespace Content.Server._CP14.Magic; + +public sealed partial class CP14MagicSystem : CP14SharedMagicSystem +{ + [Dependency] private readonly ChatSystem _chat = default!; + + public override void Initialize() + { + SubscribeLocalEvent(OnSpellSpoken); + + SubscribeLocalEvent(OnSpawnMagicVisualEffect); + SubscribeLocalEvent(OnDespawnMagicVisualEffect); + } + + private void OnSpellSpoken(Entity ent, ref CP14VerbalAspectSpeechEvent args) + { + if (args.Performer is not null && args.Speech is not null) + _chat.TrySendInGameICMessage(args.Performer.Value, args.Speech, InGameICChatType.Speak, false); + } + + private void OnSpawnMagicVisualEffect(Entity ent, ref CP14StartCastMagicEffectEvent args) + { + var vfx = SpawnAttachedTo(ent.Comp.Proto, Transform(args.Performer).Coordinates); + ent.Comp.SpawnedEntity = vfx; + } + + private void OnDespawnMagicVisualEffect(Entity ent, ref CP14StopCastMagicEffectEvent args) + { + QueueDel(ent.Comp.SpawnedEntity); + ent.Comp.SpawnedEntity = null; + } +} diff --git a/Content.Server/_CP14/MagicEnergy/CP14MagicEnergySystem.cs b/Content.Server/_CP14/MagicEnergy/CP14MagicEnergySystem.cs index 0ec735fe69..df2aeb72fb 100644 --- a/Content.Server/_CP14/MagicEnergy/CP14MagicEnergySystem.cs +++ b/Content.Server/_CP14/MagicEnergy/CP14MagicEnergySystem.cs @@ -2,7 +2,6 @@ using Content.Server._CP14.MagicEnergy.Components; using Content.Shared._CP14.MagicEnergy; using Content.Shared._CP14.MagicEnergy.Components; using Content.Shared.Examine; -using Content.Shared.FixedPoint; using Content.Shared.Inventory; using Robust.Server.GameObjects; using Robust.Shared.Timing; @@ -18,6 +17,7 @@ public sealed partial class CP14MagicEnergySystem : SharedCP14MagicEnergySystem public override void Initialize() { SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnEnergyChange); SubscribeLocalEvent(OnExamined); @@ -58,8 +58,6 @@ public sealed partial class CP14MagicEnergySystem : SharedCP14MagicEnergySystem args.PushMarkup(GetEnergyExaminedText(ent, magicContainer)); } - - public override void Update(float frameTime) { base.Update(frameTime); @@ -92,61 +90,4 @@ public sealed partial class CP14MagicEnergySystem : SharedCP14MagicEnergySystem ChangeEnergy(energyEnt.Value, energyComp, draw.Energy, draw.Safe); } } - - public bool TryConsumeEnergy(EntityUid uid, FixedPoint2 energy, CP14MagicEnergyContainerComponent? component = null, bool safe = false) - { - if (!Resolve(uid, ref component)) - return false; - - if (energy <= 0) - return true; - - // Attempting to absorb more energy than is contained in the carrier will still waste all the energy - if (component.Energy < energy) - { - ChangeEnergy(uid, component, -component.Energy); - return false; - } - - ChangeEnergy(uid, component, -energy, safe); - return true; - } - - public void ChangeEnergy(EntityUid uid, CP14MagicEnergyContainerComponent component, FixedPoint2 energy, bool safe = false) - { - if (!safe) - { - //Overload - if (component.Energy + energy > component.MaxEnergy) - { - RaiseLocalEvent(uid, new CP14MagicEnergyOverloadEvent() - { - OverloadEnergy = (component.Energy + energy) - component.MaxEnergy, - }); - } - - //Burn out - if (component.Energy + energy < 0) - { - RaiseLocalEvent(uid, new CP14MagicEnergyBurnOutEvent() - { - BurnOutEnergy = -energy - component.Energy - }); - } - } - - var oldEnergy = component.Energy; - var newEnergy = Math.Clamp((float)component.Energy + (float)energy, 0, (float)component.MaxEnergy); - component.Energy = newEnergy; - - if (oldEnergy != newEnergy) - { - RaiseLocalEvent(uid, new CP14MagicEnergyLevelChangeEvent() - { - OldValue = component.Energy, - NewValue = newEnergy, - MaxValue = component.MaxEnergy, - }); - } - } } diff --git a/Content.Shared/_CP14/Magic/CP14IDelayedMagicEffect.cs b/Content.Shared/_CP14/Magic/CP14IDelayedMagicEffect.cs new file mode 100644 index 0000000000..3070769bfc --- /dev/null +++ b/Content.Shared/_CP14/Magic/CP14IDelayedMagicEffect.cs @@ -0,0 +1,13 @@ +namespace Content.Shared._CP14.Magic; + +public interface ICP14DelayedMagicEffect // The speak n spell interface +{ + /// + /// Localized string spoken by the caster when casting this spell. + /// + public float Delay { get; } + + public bool BreakOnMove { get; } + + public bool BreakOnDamage { get; } +} diff --git a/Content.Shared/_CP14/Magic/CP14SharedMagicSystem.Spells.cs b/Content.Shared/_CP14/Magic/CP14SharedMagicSystem.Spells.cs new file mode 100644 index 0000000000..f330e17de2 --- /dev/null +++ b/Content.Shared/_CP14/Magic/CP14SharedMagicSystem.Spells.cs @@ -0,0 +1,130 @@ +using Content.Shared._CP14.Magic.Components.Spells; +using Content.Shared._CP14.Magic.Events; +using Content.Shared.EntityEffects; + +namespace Content.Shared._CP14.Magic; + +public partial class CP14SharedMagicSystem +{ + private void InitializeSpells() + { + // Instants + SubscribeLocalEvent(OnCastEntitiesSpawn); + SubscribeLocalEvent(OnCastSelfEntityEffects); + //Entity Target + SubscribeLocalEvent(OnCastApplyEntityEffects); + //World Target + SubscribeLocalEvent(OnCastProjectileSpell); + SubscribeLocalEvent(OnCastSpawnOnPoint); + } + + //TODO: Fuck,there's a lot of code repetition here that needs to be squeezed together somehow. Event calls, checks, and all this stuff + private void OnCastEntitiesSpawn(Entity spell, ref CP14DelayedInstantActionDoAfterEvent args) + { + var stopEv = new CP14StopCastMagicEffectEvent(); + RaiseLocalEvent(spell, ref stopEv); + + if (args.Cancelled || args.Handled || !_net.IsServer) + return; + + args.Handled = true; + + foreach (var spawn in spell.Comp.Spawns) + { + SpawnAtPosition(spawn, Transform(args.User).Coordinates); + } + + var ev = new CP14AfterCastMagicEffectEvent {Performer = args.User}; + RaiseLocalEvent(spell, ref ev); + } + + private void OnCastSelfEntityEffects(Entity spell, ref CP14DelayedInstantActionDoAfterEvent args) + { + var stopEv = new CP14StopCastMagicEffectEvent(); + RaiseLocalEvent(spell, ref stopEv); + + if (args.Cancelled || args.Handled) + return; + + args.Handled = true; + + foreach (var effect in spell.Comp.Effects) + { + effect.Effect(new EntityEffectBaseArgs(args.User, EntityManager)); + } + + var ev = new CP14AfterCastMagicEffectEvent {Performer = args.User}; + RaiseLocalEvent(spell, ref ev); + } + + private void OnCastApplyEntityEffects(Entity spell, ref CP14DelayedEntityTargetActionDoAfterEvent args) + { + var stopEv = new CP14StopCastMagicEffectEvent(); + RaiseLocalEvent(spell, ref stopEv); + + if (args.Cancelled || args.Handled || args.Target == null) + return; + + args.Handled = true; + + foreach (var effect in spell.Comp.Effects) + { + effect.Effect(new EntityEffectBaseArgs(args.Target.Value, EntityManager)); + } + + var ev = new CP14AfterCastMagicEffectEvent {Performer = args.User}; + RaiseLocalEvent(spell, ref ev); + } + + private void OnCastProjectileSpell(Entity spell, ref CP14DelayedWorldTargetActionDoAfterEvent args) + { + var stopEv = new CP14StopCastMagicEffectEvent(); + RaiseLocalEvent(spell, ref stopEv); + + if (args.Cancelled || args.Handled || !_net.IsServer) + return; + + args.Handled = true; + + var xform = Transform(args.User); + var fromCoords = xform.Coordinates; + var toCoords = GetCoordinates(args.Target); + var userVelocity = _physics.GetMapLinearVelocity(args.User); + + // If applicable, this ensures the projectile is parented to grid on spawn, instead of the map. + var fromMap = fromCoords.ToMap(EntityManager, _transform); + var spawnCoords = _mapManager.TryFindGridAt(fromMap, out var gridUid, out _) + ? fromCoords.WithEntityId(gridUid, EntityManager) + : new(_mapManager.GetMapEntityId(fromMap.MapId), fromMap.Position); + + var ent = Spawn(spell.Comp.Prototype, spawnCoords); + var direction = toCoords.ToMapPos(EntityManager, _transform) - + spawnCoords.ToMapPos(EntityManager, _transform); + _gunSystem.ShootProjectile(ent, direction, userVelocity, args.User, args.User); + + var ev = new CP14AfterCastMagicEffectEvent {Performer = args.User}; + RaiseLocalEvent(spell, ref ev); + } + + private void OnCastSpawnOnPoint(Entity spell, ref CP14DelayedWorldTargetActionDoAfterEvent args) + { + var stopEv = new CP14StopCastMagicEffectEvent(); + RaiseLocalEvent(spell, ref stopEv); + + if (args.Cancelled || args.Handled || !_net.IsServer) + return; + + args.Handled = true; + + var xform = Transform(args.User); + var toCoords = GetCoordinates(args.Target); + + foreach (var spawn in spell.Comp.Spawns) + { + SpawnAtPosition(spawn, toCoords); + } + + var ev = new CP14AfterCastMagicEffectEvent {Performer = args.User}; + RaiseLocalEvent(spell, ref ev); + } +} diff --git a/Content.Shared/_CP14/Magic/CP14SharedMagicSystem.cs b/Content.Shared/_CP14/Magic/CP14SharedMagicSystem.cs new file mode 100644 index 0000000000..3a0b8d922b --- /dev/null +++ b/Content.Shared/_CP14/Magic/CP14SharedMagicSystem.cs @@ -0,0 +1,219 @@ +using Content.Shared._CP14.Magic.Components; +using Content.Shared._CP14.Magic.Events; +using Content.Shared._CP14.MagicEnergy; +using Content.Shared._CP14.MagicEnergy.Components; +using Content.Shared.DoAfter; +using Content.Shared.Hands.Components; +using Content.Shared.Hands.EntitySystems; +using Content.Shared.Popups; +using Content.Shared.Speech.Muting; +using Content.Shared.Weapons.Ranged.Systems; +using Robust.Shared.Map; +using Robust.Shared.Network; +using Robust.Shared.Physics.Systems; +using Robust.Shared.Random; + +namespace Content.Shared._CP14.Magic; + +public partial class CP14SharedMagicSystem : EntitySystem +{ + [Dependency] private readonly SharedPhysicsSystem _physics = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly INetManager _net = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly SharedGunSystem _gunSystem = default!; + [Dependency] private readonly SharedCP14MagicEnergySystem _magicEnergy = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly SharedHandsSystem _hands = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnBeforeCastMagicEffect); + + SubscribeLocalEvent(OnSomaticAspectBeforeCast); + + SubscribeLocalEvent(OnVerbalAspectBeforeCast); + SubscribeLocalEvent(OnVerbalAspectAfterCast); + + SubscribeLocalEvent(OnAfterCastMagicEffect); + + SubscribeLocalEvent(OnInstantAction); + SubscribeLocalEvent(OnEntityTargetAction); + SubscribeLocalEvent(OnWorldTargetAction); + + InitializeSpells(); + } + + private void OnSomaticAspectBeforeCast(Entity ent, ref CP14BeforeCastMagicEffectEvent args) + { + if (TryComp(args.Performer, out var hands) || hands is not null) + { + foreach (var hand in hands.Hands) + { + if (hand.Value.IsEmpty) + return; + } + } + args.PushReason(Loc.GetString("cp14-magic-spell-need-somatic-component")); + args.Cancel(); + } + + private void OnVerbalAspectBeforeCast(Entity ent, ref CP14BeforeCastMagicEffectEvent args) + { + if (HasComp(args.Performer)) + { + args.PushReason(Loc.GetString("cp14-magic-spell-need-verbal-component")); + args.Cancel(); + } + else + { + if (!args.Cancelled) + { + var ev = new CP14VerbalAspectSpeechEvent + { + Performer = args.Performer, + Speech = ent.Comp.StartSpeech, + }; + RaiseLocalEvent(ent, ref ev); + } + } + } + + private void OnVerbalAspectAfterCast(Entity ent, ref CP14AfterCastMagicEffectEvent args) + { + var ev = new CP14VerbalAspectSpeechEvent + { + Performer = args.Performer, + Speech = ent.Comp.EndSpeech, + }; + RaiseLocalEvent(ent, ref ev); + } + + private void OnInstantAction(CP14DelayedInstantActionEvent args) + { + if (args.Handled) + return; + + args.Handled = true; + + if (args is not ICP14DelayedMagicEffect delayedEffect) + return; + + if (!TryCastSpell(args.Action, args.Performer)) + return; + + var doAfterEventArgs = new DoAfterArgs(EntityManager, args.Performer, delayedEffect.Delay, new CP14DelayedInstantActionDoAfterEvent(), args.Action) + { + BreakOnMove = delayedEffect.BreakOnMove, + BreakOnDamage = delayedEffect.BreakOnDamage, + }; + + _doAfter.TryStartDoAfter(doAfterEventArgs); + } + + private void OnWorldTargetAction(CP14DelayedWorldTargetActionEvent args) + { + if (args.Handled) + return; + + args.Handled = true; + + if (args is not ICP14DelayedMagicEffect delayedEffect) + return; + + if (!TryCastSpell(args.Action, args.Performer)) + return; + + var doAfter = new CP14DelayedWorldTargetActionDoAfterEvent() + { + Target = EntityManager.GetNetCoordinates(args.Target) + }; + + var doAfterEventArgs = new DoAfterArgs(EntityManager, args.Performer, delayedEffect.Delay, doAfter, args.Action) + { + BreakOnMove = delayedEffect.BreakOnMove, + BreakOnDamage = delayedEffect.BreakOnDamage, + }; + + _doAfter.TryStartDoAfter(doAfterEventArgs); + } + + private void OnEntityTargetAction(CP14DelayedEntityTargetActionEvent args) + { + if (args.Handled) + return; + + args.Handled = true; + + if (args is not ICP14DelayedMagicEffect delayedEffect) + return; + + if (!TryCastSpell(args.Action, args.Performer)) + return; + + var doAfterEventArgs = new DoAfterArgs(EntityManager, args.Performer, delayedEffect.Delay, new CP14DelayedEntityTargetActionDoAfterEvent(), args.Action, args.Target) + { + BreakOnMove = delayedEffect.BreakOnMove, + BreakOnDamage = delayedEffect.BreakOnDamage, + }; + + _doAfter.TryStartDoAfter(doAfterEventArgs); + } + + private bool TryCastSpell(EntityUid spell, EntityUid performer) + { + var ev = new CP14BeforeCastMagicEffectEvent + { + Performer = performer, + }; + RaiseLocalEvent(spell, ref ev); + if (ev.Reason != string.Empty && _net.IsServer) + { + _popup.PopupEntity(ev.Reason, performer, performer); + } + + if (!ev.Cancelled) + { + var evStart = new CP14StartCastMagicEffectEvent() + { + Performer = performer, + }; + RaiseLocalEvent(spell, ref evStart); + } + return !ev.Cancelled; + } + + private void OnBeforeCastMagicEffect(Entity ent, ref CP14BeforeCastMagicEffectEvent args) + { + if (!TryComp(args.Performer, out var magicContainer)) + { + args.Cancel(); + return; + } + + if (!_magicEnergy.HasEnergy(args.Performer, ent.Comp.ManaCost, magicContainer, ent.Comp.Safe)) + { + args.PushReason(Loc.GetString("cp14-magic-spell-not-enough-mana")); + args.Cancel(); + } + else if(!_magicEnergy.HasEnergy(args.Performer, ent.Comp.ManaCost, magicContainer, true) && _net.IsServer) + { + _popup.PopupEntity(Loc.GetString("cp14-magic-spell-not-enough-mana-cast-warning-"+_random.Next(5)), args.Performer, args.Performer, PopupType.SmallCaution); + } + } + + private void OnAfterCastMagicEffect(Entity ent, ref CP14AfterCastMagicEffectEvent args) + { + if (_net.IsClient) + return; + + if (!HasComp(args.Performer)) + return; + + _magicEnergy.TryConsumeEnergy(args.Performer.Value, ent.Comp.ManaCost, safe: ent.Comp.Safe); + } +} diff --git a/Content.Shared/_CP14/Magic/Components/CP14MagicEffectCastingVisualComponent.cs b/Content.Shared/_CP14/Magic/Components/CP14MagicEffectCastingVisualComponent.cs new file mode 100644 index 0000000000..6475b0c220 --- /dev/null +++ b/Content.Shared/_CP14/Magic/Components/CP14MagicEffectCastingVisualComponent.cs @@ -0,0 +1,16 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared._CP14.Magic.Components; + +/// +/// Creates a temporary entity that exists while the spell is cast, and disappears at the end. For visual special effects. +/// +[RegisterComponent, Access(typeof(CP14SharedMagicSystem))] +public sealed partial class CP14MagicEffectCastingVisualComponent : Component +{ + [DataField] + public EntityUid? SpawnedEntity; + + [DataField(required: true)] + public EntProtoId Proto = default!; +} diff --git a/Content.Shared/_CP14/Magic/Components/CP14MagicEffectComponent.cs b/Content.Shared/_CP14/Magic/Components/CP14MagicEffectComponent.cs new file mode 100644 index 0000000000..bc3bc40b4e --- /dev/null +++ b/Content.Shared/_CP14/Magic/Components/CP14MagicEffectComponent.cs @@ -0,0 +1,16 @@ +using Content.Shared.FixedPoint; + +namespace Content.Shared._CP14.Magic.Components; + +/// +/// Restricts the use of this action, by spending mana or user requirements. +/// +[RegisterComponent, Access(typeof(CP14SharedMagicSystem))] +public sealed partial class CP14MagicEffectComponent : Component +{ + [DataField] + public FixedPoint2 ManaCost = 0f; + + [DataField] + public bool Safe = false; +} diff --git a/Content.Shared/_CP14/Magic/Components/CP14MagicEffectSomaticAspectComponent.cs b/Content.Shared/_CP14/Magic/Components/CP14MagicEffectSomaticAspectComponent.cs new file mode 100644 index 0000000000..2e4f816cc8 --- /dev/null +++ b/Content.Shared/_CP14/Magic/Components/CP14MagicEffectSomaticAspectComponent.cs @@ -0,0 +1,9 @@ +namespace Content.Shared._CP14.Magic.Components; + +/// +/// Requires the user to have at least one free hand to use this spell +/// +[RegisterComponent, Access(typeof(CP14SharedMagicSystem))] +public sealed partial class CP14MagicEffectSomaticAspectComponent : Component +{ +} diff --git a/Content.Shared/_CP14/Magic/Components/CP14MagicEffectVerbalAspectComponent.cs b/Content.Shared/_CP14/Magic/Components/CP14MagicEffectVerbalAspectComponent.cs new file mode 100644 index 0000000000..e319fca45a --- /dev/null +++ b/Content.Shared/_CP14/Magic/Components/CP14MagicEffectVerbalAspectComponent.cs @@ -0,0 +1,27 @@ +using Content.Shared.FixedPoint; + +namespace Content.Shared._CP14.Magic.Components; + +/// +/// Requires the user to be able to speak in order to use this spell. Also forces the user to use certain phrases at the beginning and end of a spell cast +/// +[RegisterComponent, Access(typeof(CP14SharedMagicSystem))] +public sealed partial class CP14MagicEffectVerbalAspectComponent : Component +{ + [DataField] + public string StartSpeech = string.Empty; + + [DataField] + public string EndSpeech = string.Empty; +} + +/// +/// patch to send an event to the server for saying a phrase out loud +/// +[ByRefEvent] +public sealed class CP14VerbalAspectSpeechEvent : EntityEventArgs +{ + public EntityUid? Performer { get; init; } + + public string? Speech { get; init; } +} diff --git a/Content.Shared/_CP14/Magic/Components/Spells/CP14DelayedApplyEntityEffectsSpellComponent.cs b/Content.Shared/_CP14/Magic/Components/Spells/CP14DelayedApplyEntityEffectsSpellComponent.cs new file mode 100644 index 0000000000..619efcb9aa --- /dev/null +++ b/Content.Shared/_CP14/Magic/Components/Spells/CP14DelayedApplyEntityEffectsSpellComponent.cs @@ -0,0 +1,13 @@ +using Content.Shared.EntityEffects; + +namespace Content.Shared._CP14.Magic.Components.Spells; + +/// +/// Stores a list of effects for delayed actions. +/// +[RegisterComponent, Access(typeof(CP14SharedMagicSystem))] +public sealed partial class CP14DelayedApplyEntityEffectsSpellComponent : Component +{ + [DataField(required: true, serverOnly: true)] + public List Effects = new(); +} diff --git a/Content.Shared/_CP14/Magic/Components/Spells/CP14DelayedProjectileSpellComponent.cs b/Content.Shared/_CP14/Magic/Components/Spells/CP14DelayedProjectileSpellComponent.cs new file mode 100644 index 0000000000..7a47ea0573 --- /dev/null +++ b/Content.Shared/_CP14/Magic/Components/Spells/CP14DelayedProjectileSpellComponent.cs @@ -0,0 +1,16 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared._CP14.Magic.Components.Spells; + +/// +/// Stores a list of effects for delayed actions. +/// +[RegisterComponent, Access(typeof(CP14SharedMagicSystem))] +public sealed partial class CP14DelayedProjectileSpellComponent : Component +{ + /// + /// What entity should be spawned. + /// + [DataField(required: true)] + public EntProtoId Prototype; +} diff --git a/Content.Shared/_CP14/Magic/Components/Spells/CP14DelayedSelfEntityEffectSpellComponent.cs b/Content.Shared/_CP14/Magic/Components/Spells/CP14DelayedSelfEntityEffectSpellComponent.cs new file mode 100644 index 0000000000..a8b1396913 --- /dev/null +++ b/Content.Shared/_CP14/Magic/Components/Spells/CP14DelayedSelfEntityEffectSpellComponent.cs @@ -0,0 +1,13 @@ +using Content.Shared.EntityEffects; + +namespace Content.Shared._CP14.Magic.Components.Spells; + +/// +/// Stores a list of effects for delayed actions. +/// +[RegisterComponent, Access(typeof(CP14SharedMagicSystem))] +public sealed partial class CP14DelayedSelfEntityEffectSpellComponent : Component +{ + [DataField(required: true, serverOnly: true)] + public List Effects = new(); +} diff --git a/Content.Shared/_CP14/Magic/Components/Spells/CP14DelayedSpawnEntitiesSpellComponent.cs b/Content.Shared/_CP14/Magic/Components/Spells/CP14DelayedSpawnEntitiesSpellComponent.cs new file mode 100644 index 0000000000..3172c995c8 --- /dev/null +++ b/Content.Shared/_CP14/Magic/Components/Spells/CP14DelayedSpawnEntitiesSpellComponent.cs @@ -0,0 +1,16 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared._CP14.Magic.Components.Spells; + +/// +/// Stores a list of effects for delayed actions. +/// +[RegisterComponent, Access(typeof(CP14SharedMagicSystem))] +public sealed partial class CP14DelayedSpawnEntitiesSpellComponent : Component +{ + /// + /// What entities should be spawned. + /// + [DataField(required: true)] + public HashSet Spawns = new(); +} diff --git a/Content.Shared/_CP14/Magic/Components/Spells/CP14DelayedSpawnOnWorldTargetSpellComponent.cs b/Content.Shared/_CP14/Magic/Components/Spells/CP14DelayedSpawnOnWorldTargetSpellComponent.cs new file mode 100644 index 0000000000..992a3a02ef --- /dev/null +++ b/Content.Shared/_CP14/Magic/Components/Spells/CP14DelayedSpawnOnWorldTargetSpellComponent.cs @@ -0,0 +1,16 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared._CP14.Magic.Components.Spells; + +/// +/// Stores a list of effects for delayed actions. +/// +[RegisterComponent, Access(typeof(CP14SharedMagicSystem))] +public sealed partial class CP14DelayedSpawnOnWorldTargetSpellComponent : Component +{ + /// + /// What entities should be spawned. + /// + [DataField(required: true)] + public HashSet Spawns = new(); +} diff --git a/Content.Shared/_CP14/Magic/Events/CP14CastMagicEffectEvent.cs b/Content.Shared/_CP14/Magic/Events/CP14CastMagicEffectEvent.cs new file mode 100644 index 0000000000..2f15277140 --- /dev/null +++ b/Content.Shared/_CP14/Magic/Events/CP14CastMagicEffectEvent.cs @@ -0,0 +1,41 @@ +namespace Content.Shared._CP14.Magic.Events; + +[ByRefEvent] +public sealed class CP14BeforeCastMagicEffectEvent : CancellableEntityEventArgs +{ + /// + /// The Performer of the event, to check if they meet the requirements. + /// + public EntityUid Performer { get; init; } + + public string Reason = string.Empty; + + public void PushReason(string reason) + { + Reason += $"{reason}\n"; + } +} + +[ByRefEvent] +public sealed class CP14AfterCastMagicEffectEvent : EntityEventArgs +{ + public EntityUid? Performer { get; init; } +} +/// +/// is invoked if all conditions are met and the spell has begun to be cast +/// +[ByRefEvent] +public sealed class CP14StartCastMagicEffectEvent : EntityEventArgs +{ + public EntityUid Performer { get; init; } +} + +/// +/// is invoked on the spell itself when the spell process has been completed or interrupted +/// +[ByRefEvent] +public sealed class CP14StopCastMagicEffectEvent : EntityEventArgs +{ +} + + diff --git a/Content.Shared/_CP14/Magic/Events/CP14DelayedActionEvents.cs b/Content.Shared/_CP14/Magic/Events/CP14DelayedActionEvents.cs new file mode 100644 index 0000000000..d8eb86482b --- /dev/null +++ b/Content.Shared/_CP14/Magic/Events/CP14DelayedActionEvents.cs @@ -0,0 +1,64 @@ +using Content.Shared.Actions; +using Content.Shared.DoAfter; +using Robust.Shared.Map; +using Robust.Shared.Serialization; + +namespace Content.Shared._CP14.Magic.Events; + +//World target +public sealed partial class CP14DelayedWorldTargetActionEvent : WorldTargetActionEvent, ICP14DelayedMagicEffect +{ + [DataField] + public float Delay { get; private set; } = 1f; + + [DataField] + public bool BreakOnMove { get; private set; } = true; + + [DataField] + public bool BreakOnDamage { get; private set; } = true; +} + +[Serializable, NetSerializable] +public sealed partial class CP14DelayedWorldTargetActionDoAfterEvent : DoAfterEvent +{ + [DataField] + public NetCoordinates Target; + public override DoAfterEvent Clone() => this; +} + + +//Entity Target +public sealed partial class CP14DelayedEntityTargetActionEvent : EntityTargetActionEvent, ICP14DelayedMagicEffect +{ + [DataField] + public float Delay { get; private set; } = 1f; + + [DataField] + public bool BreakOnMove { get; private set; } = true; + + [DataField] + public bool BreakOnDamage { get; private set; } = true; +} + +[Serializable, NetSerializable] +public sealed partial class CP14DelayedEntityTargetActionDoAfterEvent : SimpleDoAfterEvent +{ +} + +//Instant +public sealed partial class CP14DelayedInstantActionEvent : InstantActionEvent, ICP14DelayedMagicEffect +{ + [DataField] + public float Delay { get; private set; } = 1f; + + [DataField] + public bool BreakOnMove { get; private set; } = true; + + [DataField] + public bool BreakOnDamage { get; private set; } = true; +} + +[Serializable, NetSerializable] +public sealed partial class CP14DelayedInstantActionDoAfterEvent : SimpleDoAfterEvent +{ +} diff --git a/Content.Shared/_CP14/MagicEnergy/Components/CP14MagicEnergyContainerComponent.cs b/Content.Shared/_CP14/MagicEnergy/Components/CP14MagicEnergyContainerComponent.cs index 2bbbe22ddf..adcebc3eca 100644 --- a/Content.Shared/_CP14/MagicEnergy/Components/CP14MagicEnergyContainerComponent.cs +++ b/Content.Shared/_CP14/MagicEnergy/Components/CP14MagicEnergyContainerComponent.cs @@ -1,4 +1,6 @@ +using Content.Shared.Alert; using Content.Shared.FixedPoint; +using Robust.Shared.Prototypes; namespace Content.Shared._CP14.MagicEnergy.Components; @@ -13,4 +15,7 @@ public sealed partial class CP14MagicEnergyContainerComponent : Component [DataField] public FixedPoint2 MaxEnergy = 100f; + + [DataField] + public ProtoId? MagicAlert = null; } diff --git a/Content.Shared/_CP14/MagicEnergy/SharedCP14MagicEnergySystem.cs b/Content.Shared/_CP14/MagicEnergy/SharedCP14MagicEnergySystem.cs index 9f5013468e..08a75a2e06 100644 --- a/Content.Shared/_CP14/MagicEnergy/SharedCP14MagicEnergySystem.cs +++ b/Content.Shared/_CP14/MagicEnergy/SharedCP14MagicEnergySystem.cs @@ -1,12 +1,34 @@ using Content.Shared._CP14.MagicEnergy.Components; -using Content.Shared.Examine; +using Content.Shared.Alert; using Content.Shared.FixedPoint; using Content.Shared.Inventory; +using Content.Shared.Rounding; namespace Content.Shared._CP14.MagicEnergy; public partial class SharedCP14MagicEnergySystem : EntitySystem { + [Dependency] private readonly AlertsSystem _alerts = default!; + + public override void Initialize() + { + SubscribeLocalEvent(OnComponentStartup); + SubscribeLocalEvent(OnComponentShutdown); + } + + private void OnComponentStartup(Entity ent, ref ComponentStartup args) + { + UpdateMagicAlert(ent); + } + + private void OnComponentShutdown(Entity ent, ref ComponentShutdown args) + { + if (ent.Comp.MagicAlert == null) + return; + + _alerts.ClearAlert(ent, ent.Comp.MagicAlert.Value); + } + public string GetEnergyExaminedText(EntityUid uid, CP14MagicEnergyContainerComponent ent) { var power = (int)((ent.Energy / ent.MaxEnergy) * 100); @@ -22,6 +44,92 @@ public partial class SharedCP14MagicEnergySystem : EntitySystem ("power", power), ("color", color)); } + + public void ChangeEnergy(EntityUid uid, CP14MagicEnergyContainerComponent component, FixedPoint2 energy, bool safe = false) + { + if (!safe) + { + //Overload + if (component.Energy + energy > component.MaxEnergy) + { + RaiseLocalEvent(uid, new CP14MagicEnergyOverloadEvent() + { + OverloadEnergy = (component.Energy + energy) - component.MaxEnergy, + }); + } + + //Burn out + if (component.Energy + energy < 0) + { + RaiseLocalEvent(uid, new CP14MagicEnergyBurnOutEvent() + { + BurnOutEnergy = -energy - component.Energy + }); + } + } + + var oldEnergy = component.Energy; + var newEnergy = Math.Clamp((float)component.Energy + (float)energy, 0, (float)component.MaxEnergy); + component.Energy = newEnergy; + + if (oldEnergy != newEnergy) + { + RaiseLocalEvent(uid, new CP14MagicEnergyLevelChangeEvent() + { + OldValue = component.Energy, + NewValue = newEnergy, + MaxValue = component.MaxEnergy, + }); + } + + UpdateMagicAlert((uid, component)); + } + + public bool HasEnergy(EntityUid uid, FixedPoint2 energy, CP14MagicEnergyContainerComponent? component = null, bool safe = false) + { + if (!Resolve(uid, ref component)) + return false; + + if (safe == false) + return true; + + return component.Energy > energy; + } + + public bool TryConsumeEnergy(EntityUid uid, FixedPoint2 energy, CP14MagicEnergyContainerComponent? component = null, bool safe = false) + { + if (!Resolve(uid, ref component)) + return false; + + if (energy <= 0) + return true; + + // Attempting to absorb more energy than is contained in the container available only in non-safe methods (with container destruction) + if (component.Energy < energy) + { + if (safe) + { + return false; + } + else + { + ChangeEnergy(uid, component, -energy, safe); + return true; + } + } + + ChangeEnergy(uid, component, -energy, safe); + return true; + } + + private void UpdateMagicAlert(Entity ent) + { + if (ent.Comp.MagicAlert == null) + return; + + var level = ContentHelpers.RoundToLevels(MathF.Max(0f, (float) ent.Comp.Energy), (float) ent.Comp.MaxEnergy, 10); + _alerts.ShowAlert(ent, ent.Comp.MagicAlert.Value, (short)level); + } } /// diff --git a/Content.Shared/_CP14/MagicWeakness/CP14MagicUnsafeDamageComponent.cs b/Content.Shared/_CP14/MagicWeakness/CP14MagicUnsafeDamageComponent.cs new file mode 100644 index 0000000000..6871f89ad6 --- /dev/null +++ b/Content.Shared/_CP14/MagicWeakness/CP14MagicUnsafeDamageComponent.cs @@ -0,0 +1,21 @@ +using Content.Shared.Damage; +using Content.Shared.FixedPoint; + +namespace Content.Shared._CP14.MagicWeakness; + +/// +/// imposes damage on excessive use of magic +/// +[RegisterComponent, Access(typeof(CP14MagicWeaknessSystem))] +public sealed partial class CP14MagicUnsafeDamageComponent : Component +{ + [DataField] + public DamageSpecifier DamagePerEnergy = new() + { + DamageDict = new Dictionary + { + {"Blunt", 0.5}, + {"Heat", 0.5}, + }, + }; +} diff --git a/Content.Shared/_CP14/MagicWeakness/CP14MagicUnsafeSleepComponent.cs b/Content.Shared/_CP14/MagicWeakness/CP14MagicUnsafeSleepComponent.cs new file mode 100644 index 0000000000..6ec14dae43 --- /dev/null +++ b/Content.Shared/_CP14/MagicWeakness/CP14MagicUnsafeSleepComponent.cs @@ -0,0 +1,20 @@ +using Content.Shared.Damage; +using Content.Shared.FixedPoint; + +namespace Content.Shared._CP14.MagicWeakness; + +/// +/// imposes debuffs on excessive use of magic +/// +[RegisterComponent, Access(typeof(CP14MagicWeaknessSystem))] +public sealed partial class CP14MagicUnsafeSleepComponent : Component +{ + [DataField] + public float SleepPerEnergy = 0.5f; + + /// + /// At the specified amount of extra mana expenditure, the character falls asleep. + /// + [DataField] + public FixedPoint2 SleepThreshold = 20f; +} diff --git a/Content.Shared/_CP14/MagicWeakness/CP14MagicWeaknessSystem.cs b/Content.Shared/_CP14/MagicWeakness/CP14MagicWeaknessSystem.cs new file mode 100644 index 0000000000..860342fc08 --- /dev/null +++ b/Content.Shared/_CP14/MagicWeakness/CP14MagicWeaknessSystem.cs @@ -0,0 +1,43 @@ +using Content.Shared._CP14.MagicEnergy; +using Content.Shared.Bed.Sleep; +using Content.Shared.Damage; +using Content.Shared.Popups; +using Content.Shared.StatusEffect; + +namespace Content.Shared._CP14.MagicWeakness; + +public partial class CP14MagicWeaknessSystem : EntitySystem +{ + [ValidatePrototypeId] + private const string StatusEffectKey = "ForcedSleep"; + + [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; + [Dependency] private readonly DamageableSystem _damageable = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnMagicEnergyBurnOutDamage); + SubscribeLocalEvent(OnMagicEnergyBurnOutSleep); + } + + private void OnMagicEnergyBurnOutSleep(Entity ent, ref CP14MagicEnergyBurnOutEvent args) + { + if (args.BurnOutEnergy > ent.Comp.SleepThreshold) + { + _popup.PopupEntity(Loc.GetString("cp14-magic-energy-damage-burn-out-fall"), ent, ent, PopupType.LargeCaution); + _statusEffects.TryAddStatusEffect(ent, + StatusEffectKey, + TimeSpan.FromSeconds(ent.Comp.SleepPerEnergy * (float)args.BurnOutEnergy), + false); + } + } + + private void OnMagicEnergyBurnOutDamage(Entity ent, ref CP14MagicEnergyBurnOutEvent args) + { + _popup.PopupEntity(Loc.GetString("cp14-magic-energy-damage-burn-out"), ent, ent, PopupType.LargeCaution); + _damageable.TryChangeDamage(ent, ent.Comp.DamagePerEnergy * args.BurnOutEnergy); + } +} diff --git a/Resources/Locale/en-US/_CP14/alerts/alerts.ftl b/Resources/Locale/en-US/_CP14/alerts/alerts.ftl new file mode 100644 index 0000000000..15af3f2557 --- /dev/null +++ b/Resources/Locale/en-US/_CP14/alerts/alerts.ftl @@ -0,0 +1,2 @@ +cp14-alerts-magic-energy-name = Magic energy +cp14-alerts-magic-energy-desc = A reserve of your internal magic power that allows you to use spells. \ No newline at end of file diff --git a/Resources/Locale/en-US/_CP14/magicEnergy/magic-energy.ftl b/Resources/Locale/en-US/_CP14/magicEnergy/magic-energy.ftl index 2a3de7acf9..d00edb6038 100644 --- a/Resources/Locale/en-US/_CP14/magicEnergy/magic-energy.ftl +++ b/Resources/Locale/en-US/_CP14/magicEnergy/magic-energy.ftl @@ -4,4 +4,4 @@ cp14-magic-energy-crystal-slot-name = Energy crystal cp14-magic-energy-no-crystal = No energy crystal! cp14-magic-energy-insufficient = Not enough energy! -cp14-magic-energy-insufficient-unsafe = Crystal cracks from lack of energy! \ No newline at end of file +cp14-magic-energy-insufficient-unsafe = Crystal cracks from lack of energy! diff --git a/Resources/Locale/en-US/_CP14/magicEnergy/magic-spells.ftl b/Resources/Locale/en-US/_CP14/magicEnergy/magic-spells.ftl new file mode 100644 index 0000000000..78e11469c7 --- /dev/null +++ b/Resources/Locale/en-US/_CP14/magicEnergy/magic-spells.ftl @@ -0,0 +1,10 @@ +cp14-magic-spell-not-enough-mana = Not enough mana! + +cp14-magic-spell-not-enough-mana-cast-warning-0 = Everything inside you starts to whine unpleasantly... +cp14-magic-spell-not-enough-mana-cast-warning-1 = Your head starts to buzz... +cp14-magic-spell-not-enough-mana-cast-warning-2 = Your hands are shaking... +cp14-magic-spell-not-enough-mana-cast-warning-3 = Your throat starts to lump... +cp14-magic-spell-not-enough-mana-cast-warning-4 = Your head becomes heavy... + +cp14-magic-energy-damage-burn-out = The pain is piercing your body! +cp14-magic-energy-damage-burn-out-fall = You pass out from the intense pain! \ No newline at end of file diff --git a/Resources/Locale/ru-RU/_CP14/alerts/alerts.ftl b/Resources/Locale/ru-RU/_CP14/alerts/alerts.ftl new file mode 100644 index 0000000000..06d3f4009a --- /dev/null +++ b/Resources/Locale/ru-RU/_CP14/alerts/alerts.ftl @@ -0,0 +1,2 @@ +cp14-alerts-magic-energy-name = Магическая энергия +cp14-alerts-magic-energy-desc = Запас вашей внутренней магической силы, позволяющий вам использовать заклинания. \ No newline at end of file diff --git a/Resources/Locale/ru-RU/_CP14/magicEnergy/magic-energy.ftl b/Resources/Locale/ru-RU/_CP14/magicEnergy/magic-energy.ftl index 08146f578b..b9fb9344e8 100644 --- a/Resources/Locale/ru-RU/_CP14/magicEnergy/magic-energy.ftl +++ b/Resources/Locale/ru-RU/_CP14/magicEnergy/magic-energy.ftl @@ -4,4 +4,4 @@ cp14-magic-energy-crystal-slot-name = Энергокристалл cp14-magic-energy-no-crystal = Отсутствует энергокристалл! cp14-magic-energy-insufficient = Не достаточно энергии! -cp14-magic-energy-insufficient-unsafe = Кристалл трескается от нехватки энергии! \ No newline at end of file +cp14-magic-energy-insufficient-unsafe = Кристалл трескается от нехватки энергии! diff --git a/Resources/Locale/ru-RU/_CP14/magicEnergy/magic-spells.ftl b/Resources/Locale/ru-RU/_CP14/magicEnergy/magic-spells.ftl new file mode 100644 index 0000000000..5821960e74 --- /dev/null +++ b/Resources/Locale/ru-RU/_CP14/magicEnergy/magic-spells.ftl @@ -0,0 +1,13 @@ +cp14-magic-spell-not-enough-mana = Недостаточно магической энергии! + +cp14-magic-spell-not-enough-mana-cast-warning-0 = Внутри вас все начинает неприятно ныть... +cp14-magic-spell-not-enough-mana-cast-warning-1 = Голова начинает шуметь... +cp14-magic-spell-not-enough-mana-cast-warning-2 = Ваши руки дрожат... +cp14-magic-spell-not-enough-mana-cast-warning-3 = К горлу подступает ком... +cp14-magic-spell-not-enough-mana-cast-warning-4 = Голова наливается свинцом... + +cp14-magic-energy-damage-burn-out = Боль пронзает ваше тело! +cp14-magic-energy-damage-burn-out-fall = Вы теряете сознание от сильной боли! + +cp14-magic-spell-need-verbal-component = Вы не можете произнести заклинание вслух. +cp14-magic-spell-need-somatic-component = Вам нужна свободная рука. \ No newline at end of file diff --git a/Resources/Prototypes/Alerts/alerts.yml b/Resources/Prototypes/Alerts/alerts.yml index c08453460f..8f7bc7f154 100644 --- a/Resources/Prototypes/Alerts/alerts.yml +++ b/Resources/Prototypes/Alerts/alerts.yml @@ -5,6 +5,7 @@ id: BaseAlertOrder order: - category: Health + - alertType: CP14MagicEnergy - category: Stamina - alertType: SuitPower - category: Internals diff --git a/Resources/Prototypes/Entities/Mobs/base.yml b/Resources/Prototypes/Entities/Mobs/base.yml index 3a555beebf..258ff3acbc 100644 --- a/Resources/Prototypes/Entities/Mobs/base.yml +++ b/Resources/Prototypes/Entities/Mobs/base.yml @@ -207,7 +207,7 @@ id: MobRespirator abstract: true components: - - type: Internals + #- type: Internals #CP14 dont need this - type: Respirator damage: types: diff --git a/Resources/Prototypes/_CP14/Alerts/alerts.yml b/Resources/Prototypes/_CP14/Alerts/alerts.yml new file mode 100644 index 0000000000..8f2b52b8fa --- /dev/null +++ b/Resources/Prototypes/_CP14/Alerts/alerts.yml @@ -0,0 +1,29 @@ +- type: alert + id: CP14MagicEnergy + icons: + - sprite: /Textures/_CP14/Interface/Alerts/magic_energy.rsi + state: mana_0 + - sprite: /Textures/_CP14/Interface/Alerts/magic_energy.rsi + state: mana_1 + - sprite: /Textures/_CP14/Interface/Alerts/magic_energy.rsi + state: mana_2 + - sprite: /Textures/_CP14/Interface/Alerts/magic_energy.rsi + state: mana_3 + - sprite: /Textures/_CP14/Interface/Alerts/magic_energy.rsi + state: mana_4 + - sprite: /Textures/_CP14/Interface/Alerts/magic_energy.rsi + state: mana_5 + - sprite: /Textures/_CP14/Interface/Alerts/magic_energy.rsi + state: mana_6 + - sprite: /Textures/_CP14/Interface/Alerts/magic_energy.rsi + state: mana_7 + - sprite: /Textures/_CP14/Interface/Alerts/magic_energy.rsi + state: mana_8 + - sprite: /Textures/_CP14/Interface/Alerts/magic_energy.rsi + state: mana_9 + - sprite: /Textures/_CP14/Interface/Alerts/magic_energy.rsi + state: mana_10 + name: cp14-alerts-magic-energy-name + description: cp14-alerts-magic-energy-desc + minSeverity: 0 + maxSeverity: 10 \ No newline at end of file diff --git a/Resources/Prototypes/_CP14/Entities/Actions/test_spell.yml b/Resources/Prototypes/_CP14/Entities/Actions/test_spell.yml new file mode 100644 index 0000000000..c3024be673 --- /dev/null +++ b/Resources/Prototypes/_CP14/Entities/Actions/test_spell.yml @@ -0,0 +1,153 @@ +- type: entity + id: CP14ActionSpellHealingWord + name: Healing word + description: bruh + components: + - type: CP14MagicEffect + manaCost: 10 + - type: EntityTargetAction + useDelay: 1 + itemIconStyle: BigAction + interactOnMiss: true + sound: !type:SoundPathSpecifier + path: /Audio/Magic/rumble.ogg + icon: + sprite: Objects/Magic/magicactions.rsi + state: gib + event: !type:CP14DelayedEntityTargetActionEvent + - type: CP14DelayedApplyEntityEffectsSpell + effects: + - !type:HealthChange + damage: + types: + Slash: -5 + Blunt: -5 + Piercing: -5 + Heat: -10 + - !type:Jitter + +- type: entity + id: CP14ActionSpellFireball + name: Fireball + description: Fires an explosive fireball towards the clicked location. + components: + - type: CP14MagicEffect + manaCost: 70 + - type: CP14MagicEffectVerbalAspect + startSpeech: "O tenebrae, ubi lux non penetrat..." + endSpeech: "Quaeso, quemdam inter vos quaero!" + - type: CP14MagicEffectSomaticAspect + - type: CP14MagicEffectCastingVisual + proto: CP14FireballRune + - type: WorldTargetAction + useDelay: 3 + itemIconStyle: BigAction + checkCanAccess: false + raiseOnUser: true + range: 60 + sound: !type:SoundPathSpecifier + path: /Audio/Magic/fireball.ogg + icon: + sprite: Objects/Magic/magicactions.rsi + state: fireball + event: !type:CP14DelayedWorldTargetActionEvent + delay: 3 + - type: CP14DelayedProjectileSpell + prototype: ProjectileFireball + +- type: entity + id: CP14ActionSpellSelfHeal + name: Self Heal + description: Toggles your suit's phase cloak. Beware that if you are hit, all abilities are disabled for 5 seconds, including your cloak! + components: + - type: InstantAction + # have to plan (un)cloaking ahead of time + useDelay: 5 + priority: -9 + event: !type:CP14DelayedInstantActionEvent + - type: CP14DelayedSelfEntityEffectSpell + effects: + - !type:HealthChange + damage: + types: + Slash: -5 + Blunt: -5 + Piercing: -5 + Heat: -10 + - !type:Jitter + +- type: entity + id: CP14ActionSpelldaggerSpawn + name: Gravity well + description: Toggles your suit's phase cloak. Beware that if you are hit, all abilities are disabled for 5 seconds, including your cloak! + components: + - type: CP14MagicEffect + manaCost: 20 + - type: WorldTargetAction + useDelay: 1 + itemIconStyle: BigAction + checkCanAccess: false + raiseOnUser: true + range: 4 + sound: !type:SoundPathSpecifier + path: /Audio/Magic/fireball.ogg + icon: + sprite: Objects/Magic/magicactions.rsi + state: fireball + event: !type:CP14DelayedWorldTargetActionEvent + - type: CP14DelayedSpawnOnWorldTargetSpell + spawns: + - AdminInstantEffectGravityWell + +- type: listing + id: CP14SpellbookJutter + name: jit + description: jit + productAction: CP14ActionSpellHealingWord + cost: + WizCoin: 1 + categories: + - SpellbookOffensive + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + + +- type: listing + id: CP14SpellbookFireball + name: fifire + description: fifire + productAction: CP14ActionSpellFireball + cost: + WizCoin: 1 + categories: + - SpellbookOffensive + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +- type: listing + id: CP14SpellbookFireball3 + name: selfheal + description: selfheal + productAction: CP14ActionSpellSelfHeal + cost: + WizCoin: 1 + categories: + - SpellbookOffensive + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +- type: listing + id: CP14SpellbookFireball33 + name: Gravity Well + description: well... + productAction: CP14ActionSpelldaggerSpawn + cost: + WizCoin: 1 + categories: + - SpellbookOffensive + conditions: + - !type:ListingLimitedStockCondition + stock: 1 \ No newline at end of file diff --git a/Resources/Prototypes/_CP14/Entities/Effects/cast_vfx.yml b/Resources/Prototypes/_CP14/Entities/Effects/cast_vfx.yml new file mode 100644 index 0000000000..2c9a9205a2 --- /dev/null +++ b/Resources/Prototypes/_CP14/Entities/Effects/cast_vfx.yml @@ -0,0 +1,31 @@ +- type: entity + id: CP14BaseMagicRune + name: magic rune + description: manifestation of magical energy in the physical plane + abstract: true + components: + - type: Sprite + drawDepth: FloorTiles + sprite: _CP14/Effects/Magic/cast_rune.rsi + - type: Tag + tags: + - HideContextMenu + - type: PointLight + radius: 1.5 + energy: 2.5 + netsync: false + +- type: entity + id: CP14FireballRune + parent: CP14BaseMagicRune + components: + - type: PointLight + color: "#d47d26" + - type: Sprite + layers: + - state: medium_circle + color: "#d47d26" + shader: unshaded + - state: sun + color: "#d47d26" + shader: unshaded \ No newline at end of file diff --git a/Resources/Prototypes/_CP14/Entities/Mobs/Species/base.yml b/Resources/Prototypes/_CP14/Entities/Mobs/Species/base.yml index f6f987babc..89edfa5029 100644 --- a/Resources/Prototypes/_CP14/Entities/Mobs/Species/base.yml +++ b/Resources/Prototypes/_CP14/Entities/Mobs/Species/base.yml @@ -54,10 +54,6 @@ sprite: Objects/Misc/handcuffs.rsi state: body-overlay-2 visible: false - - map: [ "clownedon" ] # Dynamically generated - sprite: "Effects/creampie.rsi" - state: "creampie_human" - visible: false - type: DamageVisuals thresholds: [ 10, 20, 30, 50, 70, 100 ] targetLayers: @@ -144,9 +140,7 @@ prototype: CP14Human requiredLegs: 2 - type: Identity - - type: IdExaminable - type: Hands - - type: Internals - type: Inventory - type: InventorySlots - type: FloatingVisuals @@ -171,8 +165,6 @@ - type: SleepEmitSound - type: SSDIndicator - type: StandingState - - type: Fingerprint - - type: Dna - type: MindContainer showExamineInfo: true - type: InteractionPopup @@ -180,11 +172,9 @@ interactSuccessString: hugging-success-generic interactSuccessSound: /Audio/Effects/thudswoosh.ogg messagePerceivedByOthers: hugging-success-generic-others - - type: CanHostGuardian - type: NpcFactionMember factions: - NanoTrasen - - type: CreamPied - type: Stripping - type: Strippable - type: UserInterface @@ -215,9 +205,16 @@ deathPenalty: 0.01 # However they really ought to be living and intact, otherwise they're worth 100x less. - type: Tag tags: - - CanPilot - FootstepSound - - DoorBumpOpener + - type: CP14MagicEnergyContainer + magicAlert: CP14MagicEnergy + maxEnergy: 100 + energy: 100 + - type: CP14MagicEnergyDraw + energy: 5 + delay: 5 + - type: CP14MagicUnsafeDamage + - type: CP14MagicUnsafeSleep - type: entity @@ -342,10 +339,6 @@ sprite: Objects/Misc/handcuffs.rsi state: body-overlay-2 visible: false - - map: [ "clownedon" ] # Dynamically generated - sprite: "Effects/creampie.rsi" - state: "creampie_human" - visible: false - type: Appearance - type: HumanoidAppearance species: CP14Human diff --git a/Resources/Prototypes/_CP14/Entities/Mobs/Species/tiefling.yml b/Resources/Prototypes/_CP14/Entities/Mobs/Species/tiefling.yml index 0071227d63..0f48312d60 100644 --- a/Resources/Prototypes/_CP14/Entities/Mobs/Species/tiefling.yml +++ b/Resources/Prototypes/_CP14/Entities/Mobs/Species/tiefling.yml @@ -77,4 +77,3 @@ 32: sprite: _CP14/Mobs/Species/Human/displacement.rsi state: female_shirt - diff --git a/Resources/Textures/_CP14/Effects/Magic/cast_rune.rsi/double_outer.png b/Resources/Textures/_CP14/Effects/Magic/cast_rune.rsi/double_outer.png new file mode 100644 index 0000000000000000000000000000000000000000..46c9e344285b262bee41bb5dc797ce2c0af61ff9 GIT binary patch literal 3167 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 LIa-n|d{h1dP*)WL literal 0 HcmV?d00001 diff --git a/Resources/Textures/_CP14/Effects/Magic/cast_rune.rsi/medium_circle.png b/Resources/Textures/_CP14/Effects/Magic/cast_rune.rsi/medium_circle.png new file mode 100644 index 0000000000000000000000000000000000000000..308926d4e859e66d4744bd784c21c6ed33cb9657 GIT binary patch literal 7974 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 M#QJOz+V#%=0KmeQT>t<8 literal 0 HcmV?d00001 diff --git a/Resources/Textures/_CP14/Effects/Magic/cast_rune.rsi/medium_line.png b/Resources/Textures/_CP14/Effects/Magic/cast_rune.rsi/medium_line.png new file mode 100644 index 0000000000000000000000000000000000000000..f553ec7a559c26a1ab4cd8ef49c3a33a135946ff GIT binary patch literal 2572 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 NuFmeq9;5wJ{|7NI@VEc~ literal 0 HcmV?d00001 diff --git a/Resources/Textures/_CP14/Interface/Alerts/magic_energy.rsi/mana_0.png b/Resources/Textures/_CP14/Interface/Alerts/magic_energy.rsi/mana_0.png new file mode 100644 index 0000000000000000000000000000000000000000..31f6340b4b31bd41e00dfb05fbaf06aa109c7701 GIT binary patch literal 413 zcmV;O0b>4%P)Px$S4l)cR9J<@mrt$&Aq>U~{yC0`nMl;U1G&|M=+*<7Xf$r#d7k52b%IPwTSoC) zDHOh6fg!~~f%b>vlWN~>H_kejYCvjuzTU{VRalsTvH@tE8s0A_GQL^;tS zYee*Z&%jVaNnOg5$@HT}BEYv)6@a=|ycFPx$j7da6R9J<@mp@L!FcikWq?Cz`12C~xLQw)m3~&ZEPSCYQ;t)t}*PftT&j14w z(5ee769-^pq$$&j9Opg%OKZLY+S#{ zy$u-y2&@CwZ!c_oc5%fTG8qv&CezuLz&bEZQ}&y(ENd}m#bsqgGQlbq;Y*PDW-cmsZLNb-UOKktW%P;67eH~cWKwF?qRm$D&;a)*j1OKI{2Dp7C zwFT^3>1a4e`bCajRz!oUJ7KgbkgD4-5PKS;RKS!}<2>-OUIN(N2BgEC&jY)d3YRh1 zBNfOn}Nq!Ch z_?|xi2)LN2CV=ME?C12BjX(Na)ZSZN%{U{ZXqQu0Lq0%Vf8;jYBaialT>t<807*qo IM6N<$g12tS#{d8T literal 0 HcmV?d00001 diff --git a/Resources/Textures/_CP14/Interface/Alerts/magic_energy.rsi/mana_10.png b/Resources/Textures/_CP14/Interface/Alerts/magic_energy.rsi/mana_10.png new file mode 100644 index 0000000000000000000000000000000000000000..3f1dc63bca403ccb99326ce610575aa40f66be47 GIT binary patch literal 467 zcmV;^0WAKBP)Px$jY&j7R9J<@mpx9yFc^kkhln7N2_#k~H~}fDgoK!wn7BmOc0h0fws!3ax@F=D zNJzj9dIFHxDg*;6(-*n+_r*U=-FHfD$IoLswQPlmj3?72S3VjJRA^sbFPOlW*EhCZ zzgGndwKgEI3Ea7PWZVBeJ+MM;B4WjOGF=kb1b)6hv+o?8T{O?duEKYH--t*OY$3r3 zECF3Py}ASde9i7e39CSd$Aa|~(kQtl((CnE$k~=2;UE&YEzwKRRiXr9Mk>9hr1hux z`~^c|eh1EPU|paq%@XV%mKy~*1-6x>0^Gimxf*Ob*=RUW{a%G~UMGXH|$E&(7~gf_R}i-|4)*jF=2$lOiWx|(q!lw_AvU!f47nSTzR-fGdlRB`|S002ov JPDHLkV1loK!&3kN literal 0 HcmV?d00001 diff --git a/Resources/Textures/_CP14/Interface/Alerts/magic_energy.rsi/mana_2.png b/Resources/Textures/_CP14/Interface/Alerts/magic_energy.rsi/mana_2.png new file mode 100644 index 0000000000000000000000000000000000000000..182cf54b620d272694e490f26e3fc95261ccc571 GIT binary patch literal 503 zcmVPx$u}MThR9J<@mrqW@Kop06EuzG51h;NU451OCM$X_R*n%66;0D-o3C-9QPa}qQ{5lMnQNN@s6KvN2Z zJpe$Z8i*2!11j7VY$Fl*Bf;q+l-24-1aad!iSS2SI0xZs(n>hbu_R_253ui^B6Z2# zB6VDy1?M(6hy>Op`(vIlP695SO6Pf|_dd-lXp{JJV0;F$0!;~9E*AH93Ni}(S8^5L zzAL_|VAt_G?Uq*$YN%9$+@R{0Ft=ME&2B;<@7s{e4CtCltOs6a6EwRcd@Z-@yQiBA zCvfBT9*3pB9`CL%0VLN03AJW-g!kzS0N{JE3@@ZcC#2SXzHL|2&F(0aP{S_C7ROhS zA`+n-)z3wNkMRQz$|ZKJJRizSGLwWPLy@35I4h7w!Zs3I6i_7qIE$3$LjVpN6IB98 tZp|bi0#Y(<%{UQCZkyAzLN>s7egSV)@CN-(N#Fng002ovPDHLkV1hzC&_@6O literal 0 HcmV?d00001 diff --git a/Resources/Textures/_CP14/Interface/Alerts/magic_energy.rsi/mana_3.png b/Resources/Textures/_CP14/Interface/Alerts/magic_energy.rsi/mana_3.png new file mode 100644 index 0000000000000000000000000000000000000000..b45fb943ddbb44b0264b8047286518fc440b6f42 GIT binary patch literal 499 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 P00000NkvXXu0mjfR!8Wo literal 0 HcmV?d00001 diff --git a/Resources/Textures/_CP14/Interface/Alerts/magic_energy.rsi/mana_8.png b/Resources/Textures/_CP14/Interface/Alerts/magic_energy.rsi/mana_8.png new file mode 100644 index 0000000000000000000000000000000000000000..54c98fb07a94bf1aafcfe199a5d7a6c9a10ef304 GIT binary patch literal 541 zcmV+&0^Px$*GWV{R9J<@m%mN|K@i5jJ5UH-rLBpTt%)I=LeywtVrAt^(1ML`pp9V3OYjU5 zQ4xtxKxINq?1_Oyz(VJ^+nv3=KX884u{*clZ)TRwt$_%t)f+Qzyjm$6Vb==uf@s)z z?Xhu1yAT@4H-L;q!?%}5Ha@?9zykS!C?{(5#*Bin0Fbe=0D!H+9!_ps4&d|E5ex5@0nD>conK>h<+mt< zPTN^nB?2H({CGIRX7Nyp&t*-#_qwq9O{WT;3Vb}Zkj}70(VB~6gHBtT=Msv)&0{JO z0GEB#_YCU)=lU62ez9y(&BgU-A7~pM*H9;?1#27DQjN3#|D{9$w0}e0!LtmR)k@hY z+67qsW@4u9lQ6LssH!UgB;AHYD&Uut^gS@#z5uYe14uM?_aE@7z#>`^WDWL{jH*Bi zTMd=5n7stx2cd(}Wy_hf06`$aN@cDDa6Px$#Ysd#R9J<@mp@O!KpckO{z)L(Nxy)LjwXiC2r)rioSgg;tT4D3XPsc>6S(mi zjK)L}o!n${G{%h>OavUvulD}@dc6Ymo!aZ&Jm!5+RX?MCaU3sh7aBTZZ)j$T` zJq)zvgi=6o@8}$lXEkeR_2NdjUn%~|e_WnuC4?J-zpWi&W4|7i_fHRm zPA8LZ1xO$Oc)7Y3>TlI+tVDDXhx-LzX)Nb+{I}lk&+1q4H5v=|g`fxk2!=gD!dF^K zc^&lGX9S_nv%~@T(VIHvMk$xe&*QUToPo6PBydu)eP0VqCV?y?<=Q#i`cvfhAS+z% zkKP00J1{TMlxPW7*Mp^ki~|3aTm^LdifgK{?6|FF!>NR21jAl#(D+W6+Y)fIs|e)1 z47toe(x8<8fzjqMfW>VySVIIui=MIcGSBx4$_4z3kQ zBVij3&I-gO0Av=S&CU8?5|;qfqZub8@1}Dd&2%D^+zzK{g=~OwegH0{=f;TNI`#kn N002ovPDHLkV1j=0?a}}M literal 0 HcmV?d00001 diff --git a/Resources/Textures/_CP14/Interface/Alerts/magic_energy.rsi/meta.json b/Resources/Textures/_CP14/Interface/Alerts/magic_energy.rsi/meta.json new file mode 100644 index 0000000000..c99c223279 --- /dev/null +++ b/Resources/Textures/_CP14/Interface/Alerts/magic_energy.rsi/meta.json @@ -0,0 +1,44 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Created by TheShuEd (Github) for CrystallPunk", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "mana_0" + }, + { + "name": "mana_1" + }, + { + "name": "mana_2" + }, + { + "name": "mana_3" + }, + { + "name": "mana_4" + }, + { + "name": "mana_5" + }, + { + "name": "mana_6" + }, + { + "name": "mana_7" + }, + { + "name": "mana_8" + }, + { + "name": "mana_9" + }, + { + "name": "mana_10" + } + ] +} \ No newline at end of file