diff --git a/Content.Server/_CP14/Magic/CPMagicSpellComponent.cs b/Content.Server/_CP14/Magic/CPMagicSpellComponent.cs new file mode 100644 index 0000000000..3ce5a94efb --- /dev/null +++ b/Content.Server/_CP14/Magic/CPMagicSpellComponent.cs @@ -0,0 +1,8 @@ +namespace Content.Server._CP14.Magic; + +[RegisterComponent] +public sealed partial class CPMagicSpellComponent : Component +{ + [DataField, ViewVariables(VVAccess.ReadWrite)] + public List Effects = new(); +} diff --git a/Content.Server/_CP14/Magic/CPMagicSpellContainerComponent.cs b/Content.Server/_CP14/Magic/CPMagicSpellContainerComponent.cs index 24fb23482d..1d5b736eb0 100644 --- a/Content.Server/_CP14/Magic/CPMagicSpellContainerComponent.cs +++ b/Content.Server/_CP14/Magic/CPMagicSpellContainerComponent.cs @@ -5,11 +5,18 @@ namespace Content.Server._CP14.Magic; [RegisterComponent] public sealed partial class CPMagicSpellContainerComponent : Component { - public readonly EntProtoId BaseSpellEffectEntity = "CPBaseSpellEntity"; - [DataField, ViewVariables(VVAccess.ReadWrite)] public List> Effects = new(); + [DataField, ViewVariables] + public List EffectPrototypes = new (); + [DataField, ViewVariables(VVAccess.ReadWrite)] public float MaximumCompleteness = 1f; + + [DataField, ViewVariables] + public float TotalCompleteness; + + [DataField, ViewVariables] + public TimeSpan TotalCastTime; } diff --git a/Content.Server/_CP14/Magic/CPMagicSpellContainerSystem.Cast.cs b/Content.Server/_CP14/Magic/CPMagicSpellContainerSystem.Cast.cs new file mode 100644 index 0000000000..cef3379cda --- /dev/null +++ b/Content.Server/_CP14/Magic/CPMagicSpellContainerSystem.Cast.cs @@ -0,0 +1,89 @@ +using System.Diagnostics.CodeAnalysis; +using Content.Shared._CP14.Magic.Events; +using Content.Shared.DoAfter; +using Content.Shared.Hands.Components; +using Content.Shared.Verbs; + +namespace Content.Server._CP14.Magic; + +public sealed partial class CPMagicSpellContainerSystem +{ + private void InitializeCast() + { + SubscribeLocalEvent>(OnGetVerb); + SubscribeLocalEvent(OnCast); + } + + private void OnGetVerb(Entity container, ref GetVerbsEvent args) + { + var user = args.User; + + args.Verbs.Add(new AlternativeVerb + { + Text = "Get spell", + Disabled = container.Comp.Effects.Count == 0, + Priority = 10, + Act = () => + { + StartCast(container, user); + } + }); + } + + private void OnCast(Entity container, ref CPMagicCastDoAfterEvent args) + { + if (args.Handled || args.Cancelled || args.Target is null) + return; + + Cast(container, args.Target.Value); + args.Handled = true; + } + + public void StartCast(Entity container, EntityUid caster) + { + if (!CastValidate(container, caster, out _)) + return; + + var doAfterArgs = new DoAfterArgs(EntityManager, caster, container.Comp.TotalCastTime, new CPMagicCastDoAfterEvent(), target: caster, used: container, eventTarget: container) + { + BreakOnDamage = true, + BreakOnMove = true, + MovementThreshold = 0.5f, + CancelDuplicate = false, + }; + + _doAfter.TryStartDoAfter(doAfterArgs); + } + + public void Cast(Entity container, EntityUid caster) + { + if (!CastValidate(container, caster, out var hand)) + return; + + var entity = Spawn(BaseSpellItemEntity, _transform.GetMapCoordinates(container)); + var spell = EnsureComp(entity); + + spell.Effects = new List(container.Comp.EffectPrototypes); + + _hands.TryPickup(caster, entity, hand); + } + + private bool CastValidate(Entity container, EntityUid caster, [NotNullWhen(true)] out Hand? hand) + { + hand = default; + + if (container.Comp.TotalCompleteness > container.Comp.MaximumCompleteness) + { + _popup.PopupEntity("Too much complicated", container); + return false; + } + + if (!_hands.TryGetEmptyHand(caster, out hand)) + { + _popup.PopupEntity("The spell can't fit in your hand", container); + return false; + } + + return true; + } +} diff --git a/Content.Server/_CP14/Magic/CPMagicSpellContainerSystem.Hash.cs b/Content.Server/_CP14/Magic/CPMagicSpellContainerSystem.Hash.cs new file mode 100644 index 0000000000..9f8b3f1efd --- /dev/null +++ b/Content.Server/_CP14/Magic/CPMagicSpellContainerSystem.Hash.cs @@ -0,0 +1,43 @@ +using Content.Server._CP14.Magic.Events; + +namespace Content.Server._CP14.Magic; + +public sealed partial class CPMagicSpellContainerSystem +{ + private void InitializeHash() + { + SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnListUpdated); + } + + private void OnStartup(Entity container, ref ComponentStartup args) + { + RaiseListUpdated(container); + } + + private void OnListUpdated(Entity container, ref CPMagicSpellContainerListUpdatedEvent args) + { + container.Comp.EffectPrototypes = new List(); + container.Comp.TotalCompleteness = 0f; + container.Comp.TotalCastTime = TimeSpan.Zero; + + foreach (var effectId in container.Comp.Effects) + { + if (!_prototype.TryIndex(effectId, out var prototype)) + { + _sawmill.Error($"Impossible to find a prototype for {effectId}"); + return; + } + + container.Comp.EffectPrototypes.Add(prototype); + container.Comp.TotalCompleteness += prototype.Complexity; + container.Comp.TotalCastTime += prototype.CastTime; + } + } + + private void RaiseListUpdated(Entity container) + { + var ev = new CPMagicSpellContainerListUpdatedEvent(); + RaiseLocalEvent(container, ev); + } +} diff --git a/Content.Server/_CP14/Magic/CPMagicSpellContainerSystem.Spell.cs b/Content.Server/_CP14/Magic/CPMagicSpellContainerSystem.Spell.cs new file mode 100644 index 0000000000..04555f9d81 --- /dev/null +++ b/Content.Server/_CP14/Magic/CPMagicSpellContainerSystem.Spell.cs @@ -0,0 +1,25 @@ +using Content.Shared.Interaction; + +namespace Content.Server._CP14.Magic; + +public sealed partial class CPMagicSpellContainerSystem +{ + private void InitializeSpell() + { + SubscribeLocalEvent(OnInteract); + } + + private void OnInteract(Entity spell, ref AfterInteractEvent args) + { + var entity = Spawn(BaseSpellEffectEntity, args.ClickLocation.ToMap(EntityManager, _transform)); + foreach (var effect in spell.Comp.Effects) + { + EntityManager.AddComponents(entity, effect.Components); + } + + var ev = new CPMagicCastedEvent(args.User, args.Target, args.ClickLocation); + RaiseLocalEvent(entity, ev); + + Del(spell); + } +} diff --git a/Content.Server/_CP14/Magic/CPMagicSpellContainerSystem.cs b/Content.Server/_CP14/Magic/CPMagicSpellContainerSystem.cs index 9754ff6131..317fb5fa20 100644 --- a/Content.Server/_CP14/Magic/CPMagicSpellContainerSystem.cs +++ b/Content.Server/_CP14/Magic/CPMagicSpellContainerSystem.cs @@ -1,64 +1,33 @@ -using Content.Server.Popups; -using Content.Shared.Verbs; +using Content.Server.DoAfter; +using Content.Server.Hands.Systems; +using Content.Server.Popups; +using Robust.Server.GameObjects; using Robust.Shared.Prototypes; namespace Content.Server._CP14.Magic; -public sealed class CPMagicSpellContainerSystem : EntitySystem +public sealed partial class CPMagicSpellContainerSystem : EntitySystem { + [Dependency] private readonly DoAfterSystem _doAfter = default!; [Dependency] private readonly IPrototypeManager _prototype = default!; + [Dependency] private readonly ILogManager _logManager = default!; + [Dependency] private readonly HandsSystem _hands = default!; + [Dependency] private readonly TransformSystem _transform = default!; [Dependency] private readonly PopupSystem _popup = default!; + public readonly EntProtoId BaseSpellItemEntity = "CPBSpellItemEntity"; + public readonly EntProtoId BaseSpellEffectEntity = "CPBaseSpellEntity"; + + private ISawmill _sawmill = default!; + public override void Initialize() { base.Initialize(); - SubscribeLocalEvent>(OnGetVerb); - } + _sawmill = _logManager.GetSawmill("cp14_magic_spell_container"); - private void OnGetVerb(Entity container, ref GetVerbsEvent args) - { - var user = args.User; - - args.Verbs.Add(new AlternativeVerb - { - Text = "cast", - Disabled = container.Comp.Effects.Count == 0, - Priority = 10, - Act = () => - { - Cast(container, user); - } - }); - } - - public void Cast(Entity container, EntityUid caster) - { - var effectPrototypes = new List(); - var complexity = 0f; - - foreach (var effectId in container.Comp.Effects) - { - if (!_prototype.TryIndex(effectId, out var prototype)) - { - _popup.PopupEntity("Fuck!", container); - return; - } - - complexity += prototype.Complexity; - effectPrototypes.Add(prototype); - } - - if (complexity > container.Comp.MaximumCompleteness) - { - _popup.PopupEntity("Too much complicated", container); - return; - } - - var entity = Spawn(container.Comp.BaseSpellEffectEntity, Transform(container).Coordinates); - foreach (var effect in effectPrototypes) - { - EntityManager.AddComponents(entity, effect.Components); - } + InitializeHash(); + InitializeCast(); + InitializeSpell(); } } diff --git a/Content.Server/_CP14/Magic/Effects/Damage/CPCastEffectDamageComponent.cs b/Content.Server/_CP14/Magic/Effects/Damage/CPCastEffectDamageComponent.cs new file mode 100644 index 0000000000..5d6e599fce --- /dev/null +++ b/Content.Server/_CP14/Magic/Effects/Damage/CPCastEffectDamageComponent.cs @@ -0,0 +1,16 @@ +using Content.Shared.Damage; + +namespace Content.Server._CP14.Magic.Effects.Damage; + +[RegisterComponent] +public sealed partial class CPCastEffectDamageComponent : Component +{ + [DataField(required: true)] + public DamageSpecifier Damage = default!; + + [DataField] + public bool IgnoreResistances; + + [DataField] + public bool InterruptsDoAfters = true; +} diff --git a/Content.Server/_CP14/Magic/Effects/Damage/CPCastEffectDamageSystem.cs b/Content.Server/_CP14/Magic/Effects/Damage/CPCastEffectDamageSystem.cs new file mode 100644 index 0000000000..4800370f68 --- /dev/null +++ b/Content.Server/_CP14/Magic/Effects/Damage/CPCastEffectDamageSystem.cs @@ -0,0 +1,24 @@ +using Content.Shared.Damage; + +namespace Content.Server._CP14.Magic.Effects.Damage; + +public sealed class CPCastEffectDamageSystem : EntitySystem +{ + [Dependency] private readonly DamageableSystem _damageable = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnCasted); + } + + private void OnCasted(Entity effect, ref CPMagicCastedEvent args) + { + if (args.Target is null) + return; + + _damageable.TryChangeDamage(args.Target, effect.Comp.Damage, effect.Comp.IgnoreResistances, + effect.Comp.InterruptsDoAfters); + } +} diff --git a/Content.Server/_CP14/Magic/Events/CPMagicCastedEvent.cs b/Content.Server/_CP14/Magic/Events/CPMagicCastedEvent.cs new file mode 100644 index 0000000000..bc64807d99 --- /dev/null +++ b/Content.Server/_CP14/Magic/Events/CPMagicCastedEvent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.Map; + +namespace Content.Server._CP14.Magic; + +public sealed class CPMagicCastedEvent(EntityUid caster, EntityUid? target, EntityCoordinates coordinates) : EntityEventArgs +{ + public readonly EntityUid Caster = caster; + public readonly EntityUid? Target = target; + public readonly EntityCoordinates Coordinates = coordinates; +} diff --git a/Content.Server/_CP14/Magic/Events/CPMagicSpellContainerListUpdatedEvent.cs b/Content.Server/_CP14/Magic/Events/CPMagicSpellContainerListUpdatedEvent.cs new file mode 100644 index 0000000000..0d66523ec6 --- /dev/null +++ b/Content.Server/_CP14/Magic/Events/CPMagicSpellContainerListUpdatedEvent.cs @@ -0,0 +1,6 @@ +namespace Content.Server._CP14.Magic.Events; + +public sealed class CPMagicSpellContainerListUpdatedEvent : EntityEventArgs +{ + +} diff --git a/Content.Server/_CP14/Magic/CPMagicEffectPrototype.cs b/Content.Shared/_CP14/Magic/CPMagicEffectPrototype.cs similarity index 84% rename from Content.Server/_CP14/Magic/CPMagicEffectPrototype.cs rename to Content.Shared/_CP14/Magic/CPMagicEffectPrototype.cs index 443956842b..16c84cf5f4 100644 --- a/Content.Server/_CP14/Magic/CPMagicEffectPrototype.cs +++ b/Content.Shared/_CP14/Magic/CPMagicEffectPrototype.cs @@ -13,4 +13,7 @@ public sealed partial class CPMagicEffectPrototype : IPrototype [DataField] public float Complexity = 1f; + + [DataField] + public TimeSpan CastTime = TimeSpan.Zero; } diff --git a/Content.Shared/_CP14/Magic/Events/CPMagicCastDoAfterEvent.cs b/Content.Shared/_CP14/Magic/Events/CPMagicCastDoAfterEvent.cs new file mode 100644 index 0000000000..56f0c9876a --- /dev/null +++ b/Content.Shared/_CP14/Magic/Events/CPMagicCastDoAfterEvent.cs @@ -0,0 +1,7 @@ +using Content.Shared.DoAfter; +using Robust.Shared.Serialization; + +namespace Content.Shared._CP14.Magic.Events; + +[Serializable, NetSerializable] +public sealed partial class CPMagicCastDoAfterEvent : SimpleDoAfterEvent; diff --git a/Resources/Prototypes/_CP14/Magic/Effects/damage.yml b/Resources/Prototypes/_CP14/Magic/Effects/damage.yml new file mode 100644 index 0000000000..ac3df0a725 --- /dev/null +++ b/Resources/Prototypes/_CP14/Magic/Effects/damage.yml @@ -0,0 +1,9 @@ +- type: CPMagicEffect + id: CPDamageGibber + complexity: 100 + components: + - type: CPCastEffectDamage + damage: + types: + Blunt: 20000 + ignoreResistances: true \ No newline at end of file diff --git a/Resources/Prototypes/_CP14/Magic/Effects/light.yml b/Resources/Prototypes/_CP14/Magic/Effects/light.yml index 29710bbc6b..42df228ef3 100644 --- a/Resources/Prototypes/_CP14/Magic/Effects/light.yml +++ b/Resources/Prototypes/_CP14/Magic/Effects/light.yml @@ -1,13 +1,9 @@ - type: CPMagicEffect id: CPPointLight + complexity: 0.5 + castTime: 5 components: - type: PointLight radius: 10 energy: 10 color: "#ffffff" - -- type: CPMagicEffect - id: CPPaintPointLightRed - components: - - type: PointLight - color: "#ff0000" diff --git a/Resources/Prototypes/_CP14/Magic/base.yml b/Resources/Prototypes/_CP14/Magic/base.yml index e78fbd5791..0f7d24fcc0 100644 --- a/Resources/Prototypes/_CP14/Magic/base.yml +++ b/Resources/Prototypes/_CP14/Magic/base.yml @@ -1,5 +1,14 @@ +- type: entity + parent: BaseItem + id: CPBSpellItemEntity + name: spell + noSpawn: true + components: + - type: Unremoveable + - type: TimedDespawn + lifetime: 5 + - type: entity id: CPBaseSpellEntity name: magic entity noSpawn: true - diff --git a/Resources/Prototypes/_CP14/Magic/charms.yml b/Resources/Prototypes/_CP14/Magic/charms.yml index 1bbc767f49..1a541f4451 100644 --- a/Resources/Prototypes/_CP14/Magic/charms.yml +++ b/Resources/Prototypes/_CP14/Magic/charms.yml @@ -4,7 +4,9 @@ name: charm description: The simplest storage for your spell components: + - type: DoAfter - type: CPMagicSpellContainer + maximumCompleteness: 999 effects: - CPPointLight - - CPPaintPointLightRed \ No newline at end of file + - CPDamageGibber \ No newline at end of file