diff --git a/Content.Client/_White/Cult/UI/TeleportSpell/TeleportSpellEui.cs b/Content.Client/_White/Cult/UI/TeleportSpell/CultTeleportSpellEui.cs similarity index 71% rename from Content.Client/_White/Cult/UI/TeleportSpell/TeleportSpellEui.cs rename to Content.Client/_White/Cult/UI/TeleportSpell/CultTeleportSpellEui.cs index a6e921263e..c46f264af5 100644 --- a/Content.Client/_White/Cult/UI/TeleportSpell/TeleportSpellEui.cs +++ b/Content.Client/_White/Cult/UI/TeleportSpell/CultTeleportSpellEui.cs @@ -3,23 +3,19 @@ using Content.Client._White.Cult.UI.TeleportRunesList; using Content.Client.Eui; using Content.Shared.Eui; using Content.Shared._White.Cult.UI; +using JetBrains.Annotations; namespace Content.Client._White.Cult.UI.TeleportSpell; -public sealed class TeleportSpellEui : BaseEui +[UsedImplicitly] +public sealed class CultTeleportSpellEui : BaseEui { - - private TeleportRunesListWindow _window; - - public TeleportSpellEui() - { - _window = new TeleportRunesListWindow(); - } + private readonly TeleportRunesListWindow _window = new(); public override void Opened() { _window.OpenCentered(); - _window.ItemSelected += (index, _) => SendMessage(new TeleportSpellTargetRuneSelected(){RuneUid = index}); + _window.ItemSelected += (index, _) => SendMessage(new TeleportSpellTargetRuneSelected {RuneUid = index}); _window.OnClose += () => SendMessage(new CloseEuiMessage()); base.Opened(); @@ -33,7 +29,8 @@ public sealed class TeleportSpellEui : BaseEui public override void HandleState(EuiStateBase state) { - if(state is not TeleportSpellEuiState cast) return; + if (state is not CultTeleportSpellEuiState cast) + return; _window.Clear(); _window.PopulateList(cast.Runes.Keys.ToList(), cast.Runes.Values.ToList()); diff --git a/Content.Client/_White/Wizard/SpellBlade/SpellBladeBUI.cs b/Content.Client/_White/Wizard/SpellBlade/SpellBladeBUI.cs new file mode 100644 index 0000000000..b9fa571196 --- /dev/null +++ b/Content.Client/_White/Wizard/SpellBlade/SpellBladeBUI.cs @@ -0,0 +1,57 @@ +using Content.Client._White.UserInterface.Radial; +using Content.Shared._White.Wizard.SpellBlade; +using JetBrains.Annotations; +using Robust.Client.GameObjects; +using Robust.Shared.Prototypes; + +namespace Content.Client._White.Wizard.SpellBlade; + +[UsedImplicitly] +// ReSharper disable once InconsistentNaming +public sealed class SpellBladeBUI(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey) +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; + + private RadialContainer? _aspectSelector; + + protected override void Open() + { + base.Open(); + + if (!_entityManager.TryGetComponent(Owner, out SpellBladeComponent? spellBlade) || + spellBlade.ChosenAspect != string.Empty) + return; + + var spriteSystem = _entityManager.System(); + _aspectSelector = new RadialContainer(); + + _aspectSelector.Closed += Close; + + foreach (var aspect in spellBlade.Aspects) + { + if (!_prototypeManager.TryIndex(aspect, out var proto)) + continue; + + var button = _aspectSelector.AddButton(proto.Name, + spriteSystem.GetPrototypeIcon(proto).Default); + button.Tooltip = proto.Description; + + button.Controller.OnPressed += _ => + { + SendMessage(new SpellBladeSystemMessage(aspect)); + _aspectSelector.Close(); + }; + + } + + _aspectSelector.OpenAttachedLocalPlayer(); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + _aspectSelector?.Close(); + } +} diff --git a/Content.Client/_White/Wizard/SpellBlade/SpellBladeSystem.cs b/Content.Client/_White/Wizard/SpellBlade/SpellBladeSystem.cs new file mode 100644 index 0000000000..53905772c3 --- /dev/null +++ b/Content.Client/_White/Wizard/SpellBlade/SpellBladeSystem.cs @@ -0,0 +1,7 @@ +using Content.Shared._White.Wizard.SpellBlade; + +namespace Content.Client._White.Wizard.SpellBlade; + +public sealed class SpellBladeSystem : SharedSpellBladeSystem +{ +} diff --git a/Content.Client/_White/Wizard/TeleportSpell/TeleportSpellEui.cs b/Content.Client/_White/Wizard/TeleportSpell/WizardTeleportSpellEui.cs similarity index 89% rename from Content.Client/_White/Wizard/TeleportSpell/TeleportSpellEui.cs rename to Content.Client/_White/Wizard/TeleportSpell/WizardTeleportSpellEui.cs index ddbf1f63f2..613983a238 100644 --- a/Content.Client/_White/Wizard/TeleportSpell/TeleportSpellEui.cs +++ b/Content.Client/_White/Wizard/TeleportSpell/WizardTeleportSpellEui.cs @@ -8,7 +8,7 @@ using JetBrains.Annotations; namespace Content.Client._White.Wizard.TeleportSpell; [UsedImplicitly] -public sealed class TeleportSpellEui : BaseEui +public sealed class WizardTeleportSpellEui : BaseEui { private readonly TeleportRunesListWindow _window = new(); @@ -30,7 +30,7 @@ public sealed class TeleportSpellEui : BaseEui public override void HandleState(EuiStateBase state) { - if (state is not TeleportSpellEuiState cast) + if (state is not WizardTeleportSpellEuiState cast) return; _window.Clear(); diff --git a/Content.Server/Atmos/EntitySystems/FlammableSystem.cs b/Content.Server/Atmos/EntitySystems/FlammableSystem.cs index 72835e7819..84f7616675 100644 --- a/Content.Server/Atmos/EntitySystems/FlammableSystem.cs +++ b/Content.Server/Atmos/EntitySystems/FlammableSystem.cs @@ -1,3 +1,4 @@ +using Content.Server._White.Wizard.SpellBlade; using Content.Server.Administration.Logs; using Content.Server.Atmos.Components; using Content.Server.IgnitionSource; @@ -49,6 +50,7 @@ namespace Content.Server.Atmos.EntitySystems [Dependency] private readonly UseDelaySystem _useDelay = default!; [Dependency] private readonly AudioSystem _audio = default!; [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly SpellBladeSystem _spellBlade = default!; // WD public const float MinimumFireStacks = -10f; public const float MaximumFireStacks = 20f; @@ -84,13 +86,19 @@ namespace Content.Server.Atmos.EntitySystems private void OnMeleeHit(EntityUid uid, IgniteOnMeleeHitComponent component, MeleeHitEvent args) { + // WD START + var fireStacks = component.FireStacks; + if (args.Direction != null) // Heavy attack + fireStacks *= 0.5f; + // WD END + foreach (var entity in args.HitEntities) { if (!TryComp(entity, out var flammable)) continue; - AdjustFireStacks(entity, component.FireStacks, flammable); - if (component.FireStacks >= 0) + AdjustFireStacks(entity, fireStacks, flammable); // WD EDIT + if (fireStacks >= 0) // WD EDIT Ignite(entity, args.Weapon, flammable, args.User); } } @@ -203,8 +211,15 @@ namespace Content.Server.Atmos.EntitySystems if (!flammable.OnFire && !otherFlammable.OnFire) return; // Neither are on fire + // WD START + var weHold = _spellBlade.IsHoldingItemWithComponent(uid); + var theyHold = _spellBlade.IsHoldingItemWithComponent(otherUid); + // WD END + if (flammable.OnFire && otherFlammable.OnFire) { + if (weHold && !theyHold || theyHold && !weHold) // WD + return; // Both are on fire -> equalize fire stacks. var avg = (flammable.FireStacks + otherFlammable.FireStacks) / 2; flammable.FireStacks = flammable.CanExtinguish ? avg : Math.Max(flammable.FireStacks, avg); @@ -217,6 +232,8 @@ namespace Content.Server.Atmos.EntitySystems // Only one is on fire -> attempt to spread the fire. if (flammable.OnFire) { + if (theyHold) // WD + return; otherFlammable.FireStacks += flammable.FireStacks / 2; Ignite(otherUid, uid, otherFlammable); if (flammable.CanExtinguish) @@ -227,6 +244,8 @@ namespace Content.Server.Atmos.EntitySystems } else { + if (weHold) // WD + return; flammable.FireStacks += otherFlammable.FireStacks / 2; Ignite(uid, otherUid, flammable); if (otherFlammable.CanExtinguish) @@ -436,7 +455,8 @@ namespace Content.Server.Atmos.EntitySystems if (TryComp(uid, out TemperatureComponent? temp)) _temperatureSystem.ChangeHeat(uid, 12500 * damageScale, false, temp); - _damageableSystem.TryChangeDamage(uid, flammable.Damage * damageScale, interruptsDoAfters: false); + if (!_spellBlade.IsHoldingItemWithComponent(uid)) // WD EDIT + _damageableSystem.TryChangeDamage(uid, flammable.Damage * damageScale, interruptsDoAfters: false); AdjustFireStacks(uid, flammable.FirestackFade * (flammable.Resisting ? 10f : 1f), flammable); } diff --git a/Content.Server/Temperature/Systems/TemperatureSystem.cs b/Content.Server/Temperature/Systems/TemperatureSystem.cs index 685874624e..bc44304679 100644 --- a/Content.Server/Temperature/Systems/TemperatureSystem.cs +++ b/Content.Server/Temperature/Systems/TemperatureSystem.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.Server._White.Wizard.SpellBlade; using Content.Server.Administration.Logs; using Content.Server.Atmos.Components; using Content.Server.Atmos.EntitySystems; @@ -22,6 +23,7 @@ public sealed class TemperatureSystem : EntitySystem [Dependency] private readonly AtmosphereSystem _atmosphere = default!; [Dependency] private readonly DamageableSystem _damageable = default!; [Dependency] private readonly IAdminLogManager _adminLogger = default!; + [Dependency] private readonly SpellBladeSystem _spellBlade = default!; // WD /// /// All the components that will have their damage updated at the end of the tick. @@ -266,6 +268,17 @@ public sealed class TemperatureSystem : EntitySystem if (temperature.CurrentTemperature >= heatDamageThreshold) { + // WD START + if (_spellBlade.IsHoldingItemWithComponent(uid)) + { + if (!temperature.TakingDamage) + return; + _adminLogger.Add(LogType.Temperature, + $"{ToPrettyString(uid):entity} stopped taking temperature damage"); + temperature.TakingDamage = false; + return; + } + // WD END if (!temperature.TakingDamage) { _adminLogger.Add(LogType.Temperature, $"{ToPrettyString(uid):entity} started taking high temperature damage"); @@ -278,7 +291,8 @@ public sealed class TemperatureSystem : EntitySystem } else if (temperature.CurrentTemperature <= coldDamageThreshold) { - if (TryComp(uid, out VoidAdaptationComponent? voidAdaptation)) // WD + // WD START + if (TryComp(uid, out VoidAdaptationComponent? voidAdaptation)) { if (temperature.TakingDamage) { @@ -291,6 +305,17 @@ public sealed class TemperatureSystem : EntitySystem return; } + if (_spellBlade.IsHoldingItemWithComponent(uid)) + { + if (!temperature.TakingDamage) + return; + _adminLogger.Add(LogType.Temperature, + $"{ToPrettyString(uid):entity} stopped taking temperature damage"); + temperature.TakingDamage = false; + return; + } + // WD END + if (!temperature.TakingDamage) { _adminLogger.Add(LogType.Temperature, $"{ToPrettyString(uid):entity} started taking low temperature damage"); diff --git a/Content.Server/_White/ChangeTemperatureOnCollide/LowTemperatureSlowdownSystem.cs b/Content.Server/_White/ChangeTemperatureOnCollide/LowTemperatureSlowdownSystem.cs index cb1c676496..ac43c5dcef 100644 --- a/Content.Server/_White/ChangeTemperatureOnCollide/LowTemperatureSlowdownSystem.cs +++ b/Content.Server/_White/ChangeTemperatureOnCollide/LowTemperatureSlowdownSystem.cs @@ -1,3 +1,4 @@ +using Content.Server._White.Wizard.SpellBlade; using Content.Server.Temperature.Components; using Content.Server.Temperature.Systems; using Content.Shared.Changeling; @@ -10,6 +11,7 @@ namespace Content.Server._White.ChangeTemperatureOnCollide; public sealed class LowTemperatureSlowdownSystem : EntitySystem { [Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifierSystem = default!; + [Dependency] private readonly SpellBladeSystem _spellBlade = default!; public override void Initialize() { @@ -22,7 +24,8 @@ public sealed class LowTemperatureSlowdownSystem : EntitySystem private void OnMoveSpeedRefresh(EntityUid uid, TemperatureComponent component, RefreshMovementSpeedModifiersEvent args) { - var modifier = HasComp(uid) || HasComp(uid) || !component.Slowdown + var modifier = _spellBlade.IsHoldingItemWithComponent(uid) || + HasComp(uid) || HasComp(uid) || !component.Slowdown ? 1f : GetSpeedModifier(component.CurrentTemperature); args.ModifySpeed(modifier, modifier); @@ -32,7 +35,7 @@ public sealed class LowTemperatureSlowdownSystem : EntitySystem OnTemperatureChangeEvent args) { // ReSharper disable once CompareOfFloatsByEqualityOperator - if(GetSpeedModifier(args.LastTemperature) == GetSpeedModifier(args.CurrentTemperature)) + if (GetSpeedModifier(args.LastTemperature) == GetSpeedModifier(args.CurrentTemperature)) return; _movementSpeedModifierSystem.RefreshMovementSpeedModifiers(uid, component); diff --git a/Content.Server/_White/Cult/Runes/Systems/CultSystem.Actions.cs b/Content.Server/_White/Cult/Runes/Systems/CultSystem.Actions.cs index e1fe8b31cc..6004d21a1b 100644 --- a/Content.Server/_White/Cult/Runes/Systems/CultSystem.Actions.cs +++ b/Content.Server/_White/Cult/Runes/Systems/CultSystem.Actions.cs @@ -148,7 +148,7 @@ public partial class CultSystem _bloodstreamSystem.TryModifyBloodLevel(uid, -5, bloodstream, createPuddle: false); - var eui = new TeleportSpellEui(args.Performer, args.Target); + var eui = new CultTeleportSpellEui(args.Performer, args.Target); _euiManager.OpenEui(eui, actor.PlayerSession); eui.StateDirty(); diff --git a/Content.Server/_White/Cult/Runes/Systems/CultSystem.Rune.cs b/Content.Server/_White/Cult/Runes/Systems/CultSystem.Rune.cs index 3cf9396574..0aecf9b99c 100644 --- a/Content.Server/_White/Cult/Runes/Systems/CultSystem.Rune.cs +++ b/Content.Server/_White/Cult/Runes/Systems/CultSystem.Rune.cs @@ -631,6 +631,18 @@ public sealed partial class CultSystem : EntitySystem } private bool Teleport(EntityUid rune, EntityUid user, List? victims = null) + { + if (!OpenTeleportUi(user, rune)) + return false; + + _entityManager.EnsureComponent(user, out var providerComponent); + providerComponent.Targets = victims; + providerComponent.BaseRune = rune; + + return true; + } + + private bool OpenTeleportUi(EntityUid user, EntityUid? exceptRune = null) { var runesQuery = EntityQueryEnumerator(); var list = new List(); @@ -641,7 +653,7 @@ public sealed partial class CultSystem : EntitySystem if (teleportComponent.Label == null) continue; - if (runeUid == rune) + if (runeUid == exceptRune) continue; if (!int.TryParse(runeUid.ToString(), out var intValue)) @@ -665,10 +677,6 @@ public sealed partial class CultSystem : EntitySystem return false; } - _entityManager.EnsureComponent(user, out var providerComponent); - providerComponent.Targets = victims; - providerComponent.BaseRune = rune; - _ui.SetUiState(ui, new TeleportRunesListWindowBUIState(list, labels)); if (_ui.IsUiOpen(user, ui.UiKey)) diff --git a/Content.Server/_White/Cult/UI/TeleportSpellEui.cs b/Content.Server/_White/Cult/UI/CultTeleportSpellEui.cs similarity index 95% rename from Content.Server/_White/Cult/UI/TeleportSpellEui.cs rename to Content.Server/_White/Cult/UI/CultTeleportSpellEui.cs index 0947aec86a..8d568dc7cc 100644 --- a/Content.Server/_White/Cult/UI/TeleportSpellEui.cs +++ b/Content.Server/_White/Cult/UI/CultTeleportSpellEui.cs @@ -10,7 +10,7 @@ using Robust.Shared.Timing; namespace Content.Server._White.Cult.UI; -public sealed class TeleportSpellEui : BaseEui +public sealed class CultTeleportSpellEui : BaseEui { [Dependency] private readonly EntityManager _entityManager = default!; private readonly SharedTransformSystem _transformSystem; @@ -22,7 +22,7 @@ public sealed class TeleportSpellEui : BaseEui private bool _used; - public TeleportSpellEui(EntityUid performer, EntityUid target) + public CultTeleportSpellEui(EntityUid performer, EntityUid target) { IoCManager.InjectDependencies(this); @@ -39,7 +39,7 @@ public sealed class TeleportSpellEui : BaseEui public override EuiStateBase GetNewState() { var runesQuery = _entityManager.EntityQueryEnumerator(); - var state = new TeleportSpellEuiState(); + var state = new CultTeleportSpellEuiState(); while (runesQuery.MoveNext(out var runeUid, out var rune)) { @@ -110,4 +110,4 @@ public sealed class TeleportSpellEui : BaseEui _transformSystem.SetCoordinates(_target, runeTransform.Coordinates); Close(); } -} \ No newline at end of file +} diff --git a/Content.Server/_White/Wizard/Magic/WizardSpellsSystem.cs b/Content.Server/_White/Wizard/Magic/WizardSpellsSystem.cs index 5a1c0ef886..6c0dec15af 100644 --- a/Content.Server/_White/Wizard/Magic/WizardSpellsSystem.cs +++ b/Content.Server/_White/Wizard/Magic/WizardSpellsSystem.cs @@ -105,7 +105,7 @@ public sealed class WizardSpellsSystem : EntitySystem if (!TryComp(msg.Performer, out ActorComponent? actor)) return; - var eui = new TeleportSpellEui(msg.Performer); + var eui = new WizardTeleportSpellEui(msg.Performer); _euiManager.OpenEui(eui, actor.PlayerSession); eui.StateDirty(); diff --git a/Content.Server/_White/Wizard/SpellBlade/FireAspectComponent.cs b/Content.Server/_White/Wizard/SpellBlade/FireAspectComponent.cs new file mode 100644 index 0000000000..9c787a3d86 --- /dev/null +++ b/Content.Server/_White/Wizard/SpellBlade/FireAspectComponent.cs @@ -0,0 +1,6 @@ +namespace Content.Server._White.Wizard.SpellBlade; + +[RegisterComponent] +public sealed partial class FireAspectComponent : Component +{ +} diff --git a/Content.Server/_White/Wizard/SpellBlade/FrostAspectComponent.cs b/Content.Server/_White/Wizard/SpellBlade/FrostAspectComponent.cs new file mode 100644 index 0000000000..8c3f6dd5fa --- /dev/null +++ b/Content.Server/_White/Wizard/SpellBlade/FrostAspectComponent.cs @@ -0,0 +1,13 @@ +using Content.Shared.Atmos; + +namespace Content.Server._White.Wizard.SpellBlade; + +[RegisterComponent] +public sealed partial class FrostAspectComponent : Component +{ + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float TemperatureOnHit = 100; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float MinTemperature = Atmospherics.TCMB; +} diff --git a/Content.Server/_White/Wizard/SpellBlade/LightningAspectComponent.cs b/Content.Server/_White/Wizard/SpellBlade/LightningAspectComponent.cs new file mode 100644 index 0000000000..597a1531b5 --- /dev/null +++ b/Content.Server/_White/Wizard/SpellBlade/LightningAspectComponent.cs @@ -0,0 +1,22 @@ +namespace Content.Server._White.Wizard.SpellBlade; + +[RegisterComponent] +public sealed partial class LightningAspectComponent : Component +{ + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float Range = 2f; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public int BoltCount = 3; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public string LightningPrototype = "WeakWizardLightning"; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public int ArcDepth = 2; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public TimeSpan ShockRate = TimeSpan.FromSeconds(10); + + public TimeSpan NextShock; +} diff --git a/Content.Server/_White/Wizard/SpellBlade/SpellBladeSystem.cs b/Content.Server/_White/Wizard/SpellBlade/SpellBladeSystem.cs new file mode 100644 index 0000000000..64df84d8fe --- /dev/null +++ b/Content.Server/_White/Wizard/SpellBlade/SpellBladeSystem.cs @@ -0,0 +1,79 @@ +using Content.Server.Atmos.Components; +using Content.Server.Lightning; +using Content.Server.Temperature.Components; +using Content.Server.Temperature.Systems; +using Content.Shared._White.Wizard.SpellBlade; +using Content.Shared.Weapons.Melee.Events; +using Robust.Shared.Timing; + +namespace Content.Server._White.Wizard.SpellBlade; + +public sealed class SpellBladeSystem : SharedSpellBladeSystem +{ + [Dependency] private readonly TemperatureSystem _temperature = default!; + [Dependency] private readonly LightningSystem _lightning = default!; + [Dependency] private readonly IGameTiming _timing = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnFrostMeleeHit); + SubscribeLocalEvent(OnLightningMeleeHit); + } + + private void OnLightningMeleeHit(Entity ent, ref MeleeHitEvent args) + { + if (args.Direction != null || args.HitEntities.Count != 1) + return; + + if (ent.Comp.NextShock > _timing.CurTime) + return; + + ent.Comp.NextShock = _timing.CurTime + ent.Comp.ShockRate; + + _lightning.ShootRandomLightnings(args.HitEntities[0], ent.Comp.Range, ent.Comp.BoltCount, + ent.Comp.LightningPrototype, ent.Comp.ArcDepth, false, args.User); + } + + private void OnFrostMeleeHit(Entity ent, ref MeleeHitEvent args) + { + var temp = ent.Comp.TemperatureOnHit; + if (args.Direction != null) // Heavy attack + temp *= 0.5f; + + foreach (var entity in args.HitEntities) + { + if (!TryComp(entity, out var temperature)) + continue; + + var curTemp = temperature.CurrentTemperature; + var newTemp = curTemp - temp; + + newTemp = curTemp < ent.Comp.MinTemperature + ? MathF.Min(curTemp, newTemp) + : Math.Max(newTemp, ent.Comp.MinTemperature); + + _temperature.ForceChangeTemperature(entity, newTemp, temperature); + } + } + + protected override void ApplyFireAspect(EntityUid uid) + { + var ignite = EnsureComp(uid); + ignite.FireStacks = 2f; + EnsureComp(uid); + } + + protected override void ApplyFrostAspect(EntityUid uid) + { + var ignite = EnsureComp(uid); + ignite.FireStacks = -5f; + EnsureComp(uid); + } + + protected override void ApplyLightningAspect(EntityUid uid) + { + EnsureComp(uid); + } +} diff --git a/Content.Server/_White/Wizard/Teleport/TeleportSpellEui.cs b/Content.Server/_White/Wizard/Teleport/WizardTeleportSpellEui.cs similarity index 88% rename from Content.Server/_White/Wizard/Teleport/TeleportSpellEui.cs rename to Content.Server/_White/Wizard/Teleport/WizardTeleportSpellEui.cs index f9bb440233..2aa84f1f22 100644 --- a/Content.Server/_White/Wizard/Teleport/TeleportSpellEui.cs +++ b/Content.Server/_White/Wizard/Teleport/WizardTeleportSpellEui.cs @@ -1,14 +1,12 @@ -using System.Linq; -using Content.Server.EUI; +using Content.Server.EUI; using Content.Server.Popups; using Content.Shared._White.Wizard.Teleport; using Content.Shared.Eui; using Robust.Shared.Timing; -using TeleportSpellEuiState = Content.Shared._White.Wizard.Teleport.TeleportSpellEuiState; namespace Content.Server._White.Wizard.Teleport; -public sealed class TeleportSpellEui : BaseEui +public sealed class WizardTeleportSpellEui : BaseEui { [Dependency] private readonly EntityManager _entityManager = default!; private readonly SharedTransformSystem _transformSystem; @@ -19,7 +17,7 @@ public sealed class TeleportSpellEui : BaseEui private bool _used; - public TeleportSpellEui(EntityUid performer) + public WizardTeleportSpellEui(EntityUid performer) { IoCManager.InjectDependencies(this); @@ -29,13 +27,13 @@ public sealed class TeleportSpellEui : BaseEui _performer = performer; - Timer.Spawn(TimeSpan.FromSeconds(10), Close); + Timer.Spawn(TimeSpan.FromSeconds(60), Close); } public override EuiStateBase GetNewState() { var locationQuery = _entityManager.EntityQueryEnumerator(); - var state = new TeleportSpellEuiState(); + var state = new WizardTeleportSpellEuiState(); while (locationQuery.MoveNext(out var locationUid, out var locationComponent, out var transformComponent)) { diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs index ca5db48c8d..873e3eeaa6 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs @@ -533,6 +533,11 @@ public abstract partial class SharedGunSystem : EntitySystem Dirty(gun); } // WD EDIT + public void SetUseKey(GunComponent gun, bool useKey) + { + gun.UseKey = useKey; + } + public void SetProjectileSpeed(EntityUid weapon, float projectileSpeed) { if(!TryComp(weapon, out var gunComponent)) diff --git a/Content.Shared/_White/BetrayalDagger/BlinkComponent.cs b/Content.Shared/_White/BetrayalDagger/BlinkComponent.cs index 64163d0284..b75684b977 100644 --- a/Content.Shared/_White/BetrayalDagger/BlinkComponent.cs +++ b/Content.Shared/_White/BetrayalDagger/BlinkComponent.cs @@ -1,10 +1,11 @@ using System.Numerics; using Robust.Shared.Audio; +using Robust.Shared.GameStates; using Robust.Shared.Serialization; namespace Content.Shared._White.BetrayalDagger; -[RegisterComponent] +[RegisterComponent, NetworkedComponent] public sealed partial class BlinkComponent : Component { [DataField, ViewVariables(VVAccess.ReadWrite)] diff --git a/Content.Shared/_White/Cult/UI/TeleportSpellEuiState.cs b/Content.Shared/_White/Cult/UI/CultTeleportSpellEuiState.cs similarity index 84% rename from Content.Shared/_White/Cult/UI/TeleportSpellEuiState.cs rename to Content.Shared/_White/Cult/UI/CultTeleportSpellEuiState.cs index 4d7a0831de..33dc445049 100644 --- a/Content.Shared/_White/Cult/UI/TeleportSpellEuiState.cs +++ b/Content.Shared/_White/Cult/UI/CultTeleportSpellEuiState.cs @@ -4,7 +4,7 @@ using Robust.Shared.Serialization; namespace Content.Shared._White.Cult.UI; [Serializable, NetSerializable] -public sealed class TeleportSpellEuiState : EuiStateBase +public sealed class CultTeleportSpellEuiState : EuiStateBase { public Dictionary Runes = new(); } diff --git a/Content.Shared/_White/Wizard/SpellBlade/SharedSpellBladeSystem.cs b/Content.Shared/_White/Wizard/SpellBlade/SharedSpellBladeSystem.cs new file mode 100644 index 0000000000..af0f27fa46 --- /dev/null +++ b/Content.Shared/_White/Wizard/SpellBlade/SharedSpellBladeSystem.cs @@ -0,0 +1,115 @@ +using System.Linq; +using Content.Shared._White.BetrayalDagger; +using Content.Shared.Examine; +using Content.Shared.Hands.EntitySystems; +using Content.Shared.Popups; +using Content.Shared.UserInterface; +using Content.Shared.Weapons.Ranged.Components; +using Content.Shared.Weapons.Ranged.Systems; +using Robust.Shared.Audio; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Prototypes; + +namespace Content.Shared._White.Wizard.SpellBlade; + +public abstract class SharedSpellBladeSystem : EntitySystem +{ + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SharedHandsSystem _hands = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedGunSystem _gun = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnMessage); + SubscribeLocalEvent(OnOpenAttempt); + SubscribeLocalEvent(OnExamined); + } + + private void OnExamined(Entity ent, ref ExaminedEvent args) + { + if (ent.Comp.ChosenAspect == string.Empty) + { + args.PushMarkup("Аспект не выбран."); + return; + } + + var proto = _prototypeManager.Index(ent.Comp.ChosenAspect); + + args.PushMarkup($"Выбранный аспект: {proto.Name}"); + } + + private void OnOpenAttempt(Entity ent, ref ActivatableUIOpenAttemptEvent args) + { + if (ent.Comp.ChosenAspect == string.Empty) + return; + + _popup.PopupEntity("Аспект уже выбран.", args.User, args.User); + args.Cancel(); + } + + private void OnMessage(Entity ent, ref SpellBladeSystemMessage args) + { + if (ent.Comp.ChosenAspect != string.Empty) + return; + + switch (args.ProtoId) + { + case "AspectFire": + ApplyFireAspect(ent); + break; + case "AspectFrost": + ApplyFrostAspect(ent); + break; + case "AspectLightning": + ApplyLightningAspect(ent); + break; + case "AspectBluespace": + ApplyBluespaceAspect(ent); + break; + case "AspectMagicMissile": + ApplyMagicMissileAspect(ent); + break; + default: + return; + } + + ent.Comp.ChosenAspect = args.ProtoId; + + _audio.PlayPvs(ent.Comp.AspectChosenSound, ent); + + + Dirty(ent); + } + + protected virtual void ApplyFireAspect(EntityUid uid) { } + + protected virtual void ApplyFrostAspect(EntityUid uid) { } + + protected virtual void ApplyLightningAspect(EntityUid uid) { } + + private void ApplyBluespaceAspect(EntityUid uid) + { + var blink = EnsureComp(uid); + blink.Distance = 15f; + blink.BlinkRate = 1f; + } + + private void ApplyMagicMissileAspect(EntityUid uid) + { + var gun = EnsureComp(uid); + _gun.SetUseKey(gun, false); + _gun.SetSound(uid, new SoundPathSpecifier("/Audio/Weapons/Guns/Gunshots/Magic/staff_healing.ogg")); + _gun.SetFireRate(uid, 1.2f); + var ammoProvider = EnsureComp(uid); + ammoProvider.Proto = "ProjectileMagicMissile"; + } + + public bool IsHoldingItemWithComponent(EntityUid uid) where T : Component + { + return _hands.EnumerateHeld(uid).Any(HasComp); + } +} diff --git a/Content.Shared/_White/Wizard/SpellBlade/SpellBladeComponent.cs b/Content.Shared/_White/Wizard/SpellBlade/SpellBladeComponent.cs new file mode 100644 index 0000000000..ff1fc59b31 --- /dev/null +++ b/Content.Shared/_White/Wizard/SpellBlade/SpellBladeComponent.cs @@ -0,0 +1,39 @@ +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + +namespace Content.Shared._White.Wizard.SpellBlade; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class SpellBladeComponent : Component +{ + [ViewVariables, AutoNetworkedField] + public string ChosenAspect = string.Empty; + + [DataField] + public List Aspects = new() + { + "AspectFire", + "AspectFrost", + "AspectLightning", + "AspectBluespace", + "AspectMagicMissile" + }; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public SoundSpecifier AspectChosenSound = new SoundPathSpecifier("/Audio/White/Magic/spellblade-aspect.ogg"); +} + +[Serializable, NetSerializable] +public sealed class SpellBladeSystemMessage(EntProtoId protoId) : BoundUserInterfaceMessage +{ + public EntProtoId ProtoId = protoId; +} + + +[Serializable, NetSerializable] +public enum SpellBladeUiKey : byte +{ + Key +} diff --git a/Content.Shared/_White/Wizard/Teleport/TeleportSpellEuiState.cs b/Content.Shared/_White/Wizard/Teleport/WizardTeleportSpellEuiState.cs similarity index 84% rename from Content.Shared/_White/Wizard/Teleport/TeleportSpellEuiState.cs rename to Content.Shared/_White/Wizard/Teleport/WizardTeleportSpellEuiState.cs index 7161f85018..26d6a3e236 100644 --- a/Content.Shared/_White/Wizard/Teleport/TeleportSpellEuiState.cs +++ b/Content.Shared/_White/Wizard/Teleport/WizardTeleportSpellEuiState.cs @@ -4,7 +4,7 @@ using Robust.Shared.Serialization; namespace Content.Shared._White.Wizard.Teleport; [Serializable, NetSerializable] -public sealed class TeleportSpellEuiState : EuiStateBase +public sealed class WizardTeleportSpellEuiState : EuiStateBase { public Dictionary Locations = new(); } diff --git a/Resources/Audio/White/Magic/spellblade-aspect.ogg b/Resources/Audio/White/Magic/spellblade-aspect.ogg new file mode 100644 index 0000000000..2df5431748 Binary files /dev/null and b/Resources/Audio/White/Magic/spellblade-aspect.ogg differ diff --git a/Resources/Prototypes/Entities/Effects/lightning.yml b/Resources/Prototypes/Entities/Effects/lightning.yml index 11e6c6c0f0..69c98f8bab 100644 --- a/Resources/Prototypes/Entities/Effects/lightning.yml +++ b/Resources/Prototypes/Entities/Effects/lightning.yml @@ -176,3 +176,16 @@ - type: Lightning canArc: true lightningPrototype: WizardLightning + +- type: entity + name: wizard lightning + id: WeakWizardLightning + parent: BaseLightning + noSpawn: true + components: + - type: Electrified + requirePower: false + ignoreInsulation: true + - type: Lightning + canArc: true + lightningPrototype: WeakWizardLightning diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/magic.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/magic.yml index e91a4d0ede..f5ebed108d 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/magic.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/magic.yml @@ -236,3 +236,44 @@ stunAmount: 2 knockdownAmount: 2 - type: TeslaProjectile + +- type: entity + id: ProjectileMagicMissile + name: magic missile + description: asdf + parent: BaseBullet + noSpawn: true + components: + - type: Sprite + sprite: Objects/Weapons/Guns/Projectiles/magic.rsi + layers: + - state: spell + color: pink + - type: Projectile + damage: + groups: + Burn: 0 + - type: StaminaDamageOnCollide + damage: 60 + - type: Ammo + muzzleFlash: null + - type: Trail + splineIteratorType: CatmullRom + splineRendererType: Continuous + creationMethod: OnMove + lengthStep: 0.1 + scale: 0.05, 0.0 + lifetime: 1 + randomWalk: 0.1, 0.001 + gravity: 0.0, 0.0 + texturePath: /Textures/White/Effects/Trails/Continuous/trail.png + gradientIteratorType: Linear + gradient: + - 1, 0, 0, 1 + - 1, 1, 0, 0.85 + - 0, 1, 0, 0.7 + - 0, 1, 1, 0.55 + - 0, 0, 1, 0.4 + - 1, 0, 1, 0.25 + - 1, 0, 0, 0.1 + optionsConcealable: true diff --git a/Resources/Prototypes/_White/Entities/Objects/Weapons/chaplain_weapons.yml b/Resources/Prototypes/_White/Entities/Objects/Weapons/chaplain_weapons.yml index 1340f789fd..c110b7a49d 100644 --- a/Resources/Prototypes/_White/Entities/Objects/Weapons/chaplain_weapons.yml +++ b/Resources/Prototypes/_White/Entities/Objects/Weapons/chaplain_weapons.yml @@ -36,7 +36,6 @@ - HolyKatana - MultiverseBlade - VorpalScythe - - SpellBlade - PossessedBlade - ChainsawHand - HolyWhip @@ -195,31 +194,6 @@ sprite: White/Objects/Weapons/Chaplain/scythe-inhands.rsi - type: HolyWeapon -- type: entity - name: клинок заклинаний - parent: HolyKatana - id: SpellBlade - description: Клинок, с шансом 20% наносящий критический удар. - components: - - type: Sprite - sprite: White/Objects/Weapons/Chaplain/spellblade.rsi - - type: MeleeWeapon - wideAnimationRotation: 135 - damage: - types: - Slash: 18 - - type: Clothing - sprite: White/Objects/Weapons/Chaplain/spellblade.rsi - slots: - - back - - suitStorage - - type: Crit - critChance: 20 - critMultiplier: 2.5 - - type: Item - sprite: White/Objects/Weapons/Chaplain/spellblade.rsi - - type: HolyWeapon - - type: entity name: одержимый клинок parent: HolyKatana diff --git a/Resources/Prototypes/_White/Wizard/magic_items.yml b/Resources/Prototypes/_White/Wizard/magic_items.yml index 446f860ea2..c1555ee2c7 100644 --- a/Resources/Prototypes/_White/Wizard/magic_items.yml +++ b/Resources/Prototypes/_White/Wizard/magic_items.yml @@ -21,3 +21,33 @@ reflectProb: 0.4 - type: Item sprite: White/Objects/Weapons/Chaplain/hfrequency.rsi + +- type: entity + name: клинок заклинаний + parent: Katana + id: SpellBlade + description: Магический клинок, наделяемый силой одного из пяти аспектов. + components: + - type: Sprite + sprite: White/Objects/Weapons/Chaplain/spellblade.rsi + - type: MeleeWeapon + wideAnimationRotation: 135 + damage: + types: + Slash: 30 + - type: Clothing + sprite: White/Objects/Weapons/Chaplain/spellblade.rsi + slots: + - back + - suitStorage + - type: Item + sprite: White/Objects/Weapons/Chaplain/spellblade.rsi + - type: UserInterface + interfaces: + - key: enum.SpellBladeUiKey.Key + type: SpellBladeBUI + - type: ActivatableUI + key: enum.SpellBladeUiKey.Key + inHandsOnly: true + closeOnHandDeselect: true + - type: SpellBlade diff --git a/Resources/Prototypes/_White/Wizard/spellblade.yml b/Resources/Prototypes/_White/Wizard/spellblade.yml new file mode 100644 index 0000000000..635f32a704 --- /dev/null +++ b/Resources/Prototypes/_White/Wizard/spellblade.yml @@ -0,0 +1,49 @@ +- type: entity + name: Огонь + description: Клинок заклинаний наделяется способностью поджигать врагов. И тот, кто удерживает клинок в руках, становится неуязвимым к огню и высокой температуре. + id: AspectFire + noSpawn: true + components: + - type: Sprite + sprite: Objects/Magic/magicactions.rsi + state: fireball + +- type: entity + name: Холод + description: Клинок заклинаний наделяется замораживать врагов. И тот, кто удерживает клинок в руках, становится неуязвимым к низкой температуре. + id: AspectFrost + noSpawn: true + components: + - type: Sprite + sprite: Objects/Magic/magicactions.rsi + state: icebeam_active + +- type: entity + name: Молния + description: Клинок заклинаний наделяется способностью каждые 10 секунд при ударе излучать шоковый заряд, поражающий ближайшие цели. + id: AspectLightning + noSpawn: true + components: + - type: Sprite + sprite: Objects/Magic/magicactions.rsi + state: thunder + +- type: entity + name: Блюспейс + description: Клинок заклинаний наделяется способностью телепортации на далёкое расстояние. + id: AspectBluespace + noSpawn: true + components: + - type: Sprite + sprite: Objects/Magic/magicactions.rsi + state: blink + +- type: entity + name: Магическая Стрела + description: Клинок заклинаний наделяется способностью стрелять оглушающей магической стрелой. + id: AspectMagicMissile + noSpawn: true + components: + - type: Sprite + sprite: Objects/Magic/magicactions.rsi + state: magicmissile diff --git a/Resources/Prototypes/_White/Wizard/spellbook_catalog.yml b/Resources/Prototypes/_White/Wizard/spellbook_catalog.yml index df0339e937..1d891bba02 100644 --- a/Resources/Prototypes/_White/Wizard/spellbook_catalog.yml +++ b/Resources/Prototypes/_White/Wizard/spellbook_catalog.yml @@ -247,3 +247,16 @@ conditions: - !type:ListingLimitedStockCondition stock: 1 + +- type: listing + id: SpellBookSpellBlade + name: spellbook-spellblade-name + description: spellbook-spellblade-desc + productEntity: SpellBlade + cost: + SpellPoint: 2 + categories: + - MagicItems + conditions: + - !type:ListingLimitedStockCondition + stock: 1 diff --git a/Resources/Textures/Objects/Magic/magicactions.rsi/icebeam_active.png b/Resources/Textures/Objects/Magic/magicactions.rsi/icebeam_active.png new file mode 100644 index 0000000000..c876e20bfc Binary files /dev/null and b/Resources/Textures/Objects/Magic/magicactions.rsi/icebeam_active.png differ diff --git a/Resources/Textures/Objects/Magic/magicactions.rsi/meta.json b/Resources/Textures/Objects/Magic/magicactions.rsi/meta.json index 193f4b82e9..b84afefb47 100644 --- a/Resources/Textures/Objects/Magic/magicactions.rsi/meta.json +++ b/Resources/Textures/Objects/Magic/magicactions.rsi/meta.json @@ -54,6 +54,9 @@ }, { "name": "teleport" + }, + { + "name": "icebeam_active" } ] }