From c08cdeb84d7b58d779b85b0e6cdd8e0b96fa223f Mon Sep 17 00:00:00 2001
From: rhailrake <49613070+rhailrake@users.noreply.github.com>
Date: Thu, 7 Mar 2024 16:01:54 +0000
Subject: [PATCH] =?UTF-8?q?=D0=97=D0=B0=D0=BB=D1=8C=D1=8E=20=D1=81=D0=BF?=
=?UTF-8?q?=D0=B5=D0=BB=D0=BB=D1=8B=20=D0=BF=D0=BE=D1=82=D0=B5=D1=81=D1=82?=
=?UTF-8?q?=D0=B8=D1=82=D1=8C,=20=D0=BC=D0=BD=D0=B5=20=D0=BF=D0=BE=D1=85?=
=?UTF-8?q?=D1=83=D0=B9=20=D0=9F=D0=A0=20=D0=9D=D0=9E=D0=9C=D0=95=D0=A0=20?=
=?UTF-8?q?156?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* refactor CheZaHuetaMagicSystem
* эщкере
* alt-spells system. lmb, rmb, alt-click
* fix
* ChargeSpellsIndicator + Visual(CheZaHueta)
* Custom charge effect for spell
* Custom MaxChargeLevel
* Finally. Alt spells seems to work!! Need to start do spells and gamerule
* fuckkk
* fix crash, actually burn scroll..
* some fixes blyat
* ArcSpell
* очередная CheZaHuetaSystem, ForceSpell
* ONI'SOMA!
* mraow
* prepare this LMAO
* Yebanyy rot etogo kazino blyat! - CardsSpell
* forcewall
* nig
* blink
* Ethereal Jaunt
* игра говно
* Блядина
* ну на еще спеллов
* blyadina
* да иди ты нахуй БЛЯДЬ
* кто прочитал, тот сдохнет. сделай 5 репостов чтобы выжить....
* icons
* та ваще поебать
* одежда
---
.../ContextMenu/UI/ContextMenuUIController.cs | 19 +-
.../Systems/Actions/ActionUIController.cs | 110 +--
.../Systems/Actions/ChargeActionSystem.cs | 173 +++++
.../Weapons/Melee/MeleeWeaponSystem.cs | 2 -
.../_White/Wizard/Scrolls/ScrollSystem.cs | 7 +
.../EnergyDome/EnergyDomeComponent.cs | 15 +
.../EnergyDomeGeneratorComponent.cs | 85 +++
Content.Server/EnergyDome/EnergyDomeSystem.cs | 329 ++++++++
Content.Server/Lightning/LightningSystem.cs | 9 +-
.../Magic/Components/SpellbookComponent.cs | 35 -
Content.Server/Magic/MagicSystem.cs | 294 +++-----
.../Power/EntitySystems/BatterySystem.cs | 13 +-
.../IncorporealSystem/IncorporealSystem.cs | 16 +-
.../_White/Wizard/Charging/ChargingSystem.cs | 183 +++++
.../Magic/Amaterasu/AmaterasuComponent.cs | 6 +
.../Wizard/Magic/Amaterasu/AmaterasuSystem.cs | 34 +
.../Magic/Other/InstantRecallComponent.cs | 7 +
.../TeslaProjectileComponent.cs | 4 +
.../TeslaProjectile/TeslaProjectileSystem.cs | 21 +
.../_White/Wizard/Magic/WizardSpellsSystem.cs | 701 ++++++++++++++++++
.../_White/Wizard/Scrolls/ScrollSystem.cs | 8 +
Content.Shared/Actions/ActionEvents.cs | 20 +-
.../Actions/BaseTargetActionComponent.cs | 25 +
Content.Shared/Actions/SharedActionsSystem.cs | 9 +
.../Wizard/Charging/SharedChargingSystem.cs | 5 +
.../_White/Wizard/Magic/MagicComponent.cs | 13 +
.../Wizard/Magic/WizardClothesComponent.cs | 9 +
.../Wizard/ScrollSystem/ScrollComponent.cs | 43 ++
.../Wizard/ScrollSystem/SharedScrollSystem.cs | 87 +++
Content.Shared/_White/Wizard/WizardEvents.cs | 175 +++++
Resources/Audio/Machines/attributions.yml | 20 +
.../Audio/Machines/energyshield_ambient.ogg | Bin 0 -> 43605 bytes
.../Audio/Machines/energyshield_down.ogg | Bin 0 -> 26999 bytes
.../Audio/Machines/energyshield_parry.ogg | Bin 0 -> 9209 bytes
Resources/Audio/Machines/energyshield_up.ogg | Bin 0 -> 15379 bytes
.../Audio/White/Items/scroll/after_use.ogg | Bin 0 -> 15612 bytes
Resources/Audio/White/Items/scroll/use.ogg | Bin 0 -> 19075 bytes
Resources/Audio/White/Magic/Arc/cast.ogg | Bin 0 -> 7675 bytes
Resources/Audio/White/Magic/Arc/charge.ogg | Bin 0 -> 38441 bytes
Resources/Audio/White/Magic/Arc/max.ogg | Bin 0 -> 32658 bytes
Resources/Audio/White/Magic/Cards/cast.ogg | Bin 0 -> 22115 bytes
Resources/Audio/White/Magic/Cards/charge.ogg | Bin 0 -> 39277 bytes
Resources/Audio/White/Magic/Cards/max.ogg | Bin 0 -> 32658 bytes
Resources/Audio/White/Magic/Force/cast.ogg | Bin 0 -> 19829 bytes
Resources/Audio/White/Magic/Force/charge.ogg | Bin 0 -> 38041 bytes
Resources/Audio/White/Magic/Force/max.ogg | Bin 0 -> 32658 bytes
.../Audio/White/Magic/chargingfallback.ogg | Bin 0 -> 23825 bytes
.../Audio/White/Magic/maxchargefallback.ogg | Bin 0 -> 16310 bytes
.../Locale/en-US/EnergyDome/energydome.ftl | 9 +
.../Locale/en-US/research/technologies.ftl | 1 +
.../Locale/en-US/store/uplink-catalog.ftl | 3 +
Resources/Prototypes/Actions/types.yml | 10 +
Resources/Prototypes/Damage/modifier_sets.yml | 10 +
.../Clothing/OuterClothing/hardsuits.yml | 32 +-
.../Entities/Effects/admin_triggers.yml | 40 +-
.../Prototypes/Entities/Effects/dome.yml | 129 ++++
.../Prototypes/Entities/Mobs/Species/base.yml | 1 +
.../Entities/Objects/Magic/books.yml | 106 ---
.../Entities/Objects/Tools/energydome.yml | 179 +++++
.../Weapons/Guns/Projectiles/magic.yml | 31 +
.../Weapons/Throwable/throwing_stars.yml | 34 +
Resources/Prototypes/Magic/white.yml | 253 +++++++
.../White/Entities/Clothing/nigger.yml | 35 +
.../White/Objects/Scrolls/magic.yml | 119 +++
.../White/Objects/Scrolls/scrolls.yml | 133 ++++
.../EnergyDome/energydome_big.rsi/big.png | Bin 0 -> 49904 bytes
.../EnergyDome/energydome_big.rsi/meta.json | 26 +
.../energydome_directed.rsi/meta.json | 26 +
.../energydome_directed.rsi/small.png | Bin 0 -> 8373 bytes
.../energydome_medium.rsi/medium.png | Bin 0 -> 25305 bytes
.../energydome_medium.rsi/meta.json | 26 +
.../energydome_slowdown_big.rsi/big.png | Bin 0 -> 44698 bytes
.../energydome_slowdown_big.rsi/meta.json | 26 +
.../EnergyDome/energydome_small.rsi/meta.json | 27 +
.../EnergyDome/energydome_small.rsi/small.png | Bin 0 -> 9931 bytes
.../Textures/Objects/Magic/card.rsi/icon.png | Bin 0 -> 262 bytes
.../Textures/Objects/Magic/card.rsi/meta.json | 14 +
.../Objects/Magic/magicactions.rsi/clown.png | Bin 0 -> 526 bytes
.../Objects/Magic/magicactions.rsi/cluwne.png | Bin 0 -> 547 bytes
.../Magic/magicactions.rsi/emp_new.png | Bin 0 -> 371 bytes
.../Objects/Magic/magicactions.rsi/jaunt.png | Bin 0 -> 736 bytes
.../Objects/Magic/magicactions.rsi/meta.json | 28 +-
.../Magic/magicactions.rsi/mime_curse.png | Bin 0 -> 479 bytes
.../Objects/Magic/magicactions.rsi/push.png | Bin 0 -> 719 bytes
.../Magic/magicactions.rsi/summons.png | Bin 0 -> 746 bytes
.../Magic/magicactions.rsi/thunder.png | Bin 0 -> 962 bytes
.../Tools/EnergyDome/reinhardt.rsi/icon.png | Bin 0 -> 816 bytes
.../EnergyDome/reinhardt.rsi/inhand-left.png | Bin 0 -> 1066 bytes
.../EnergyDome/reinhardt.rsi/inhand-right.png | Bin 0 -> 1066 bytes
.../Tools/EnergyDome/reinhardt.rsi/meta.json | 22 +
.../EnergyDome/syndie.rsi/equipped-BELT.png | Bin 0 -> 812 bytes
.../Tools/EnergyDome/syndie.rsi/icon.png | Bin 0 -> 535 bytes
.../EnergyDome/syndie.rsi/inhand-left.png | Bin 0 -> 702 bytes
.../EnergyDome/syndie.rsi/inhand-right.png | Bin 0 -> 718 bytes
.../Tools/EnergyDome/syndie.rsi/meta.json | 26 +
.../Textures/White/Charge/charge.rsi/flux.png | Bin 0 -> 4167 bytes
.../White/Charge/charge.rsi/meta.json | 30 +
.../Textures/White/Misc/scrolls.rsi/meta.json | 14 +
.../White/Misc/scrolls.rsi/scroll.png | Bin 0 -> 673 bytes
99 files changed, 3461 insertions(+), 406 deletions(-)
create mode 100644 Content.Client/UserInterface/Systems/Actions/ChargeActionSystem.cs
create mode 100644 Content.Client/_White/Wizard/Scrolls/ScrollSystem.cs
create mode 100644 Content.Server/EnergyDome/EnergyDomeComponent.cs
create mode 100644 Content.Server/EnergyDome/EnergyDomeGeneratorComponent.cs
create mode 100644 Content.Server/EnergyDome/EnergyDomeSystem.cs
delete mode 100644 Content.Server/Magic/Components/SpellbookComponent.cs
create mode 100644 Content.Server/_White/Wizard/Charging/ChargingSystem.cs
create mode 100644 Content.Server/_White/Wizard/Magic/Amaterasu/AmaterasuComponent.cs
create mode 100644 Content.Server/_White/Wizard/Magic/Amaterasu/AmaterasuSystem.cs
create mode 100644 Content.Server/_White/Wizard/Magic/Other/InstantRecallComponent.cs
create mode 100644 Content.Server/_White/Wizard/Magic/TeslaProjectile/TeslaProjectileComponent.cs
create mode 100644 Content.Server/_White/Wizard/Magic/TeslaProjectile/TeslaProjectileSystem.cs
create mode 100644 Content.Server/_White/Wizard/Magic/WizardSpellsSystem.cs
create mode 100644 Content.Server/_White/Wizard/Scrolls/ScrollSystem.cs
create mode 100644 Content.Shared/_White/Wizard/Charging/SharedChargingSystem.cs
create mode 100644 Content.Shared/_White/Wizard/Magic/MagicComponent.cs
create mode 100644 Content.Shared/_White/Wizard/Magic/WizardClothesComponent.cs
create mode 100644 Content.Shared/_White/Wizard/ScrollSystem/ScrollComponent.cs
create mode 100644 Content.Shared/_White/Wizard/ScrollSystem/SharedScrollSystem.cs
create mode 100644 Content.Shared/_White/Wizard/WizardEvents.cs
create mode 100644 Resources/Audio/Machines/energyshield_ambient.ogg
create mode 100644 Resources/Audio/Machines/energyshield_down.ogg
create mode 100644 Resources/Audio/Machines/energyshield_parry.ogg
create mode 100644 Resources/Audio/Machines/energyshield_up.ogg
create mode 100644 Resources/Audio/White/Items/scroll/after_use.ogg
create mode 100644 Resources/Audio/White/Items/scroll/use.ogg
create mode 100644 Resources/Audio/White/Magic/Arc/cast.ogg
create mode 100644 Resources/Audio/White/Magic/Arc/charge.ogg
create mode 100644 Resources/Audio/White/Magic/Arc/max.ogg
create mode 100644 Resources/Audio/White/Magic/Cards/cast.ogg
create mode 100644 Resources/Audio/White/Magic/Cards/charge.ogg
create mode 100644 Resources/Audio/White/Magic/Cards/max.ogg
create mode 100644 Resources/Audio/White/Magic/Force/cast.ogg
create mode 100644 Resources/Audio/White/Magic/Force/charge.ogg
create mode 100644 Resources/Audio/White/Magic/Force/max.ogg
create mode 100644 Resources/Audio/White/Magic/chargingfallback.ogg
create mode 100644 Resources/Audio/White/Magic/maxchargefallback.ogg
create mode 100644 Resources/Locale/en-US/EnergyDome/energydome.ftl
create mode 100644 Resources/Prototypes/Entities/Effects/dome.yml
delete mode 100644 Resources/Prototypes/Entities/Objects/Magic/books.yml
create mode 100644 Resources/Prototypes/Entities/Objects/Tools/energydome.yml
create mode 100644 Resources/Prototypes/Magic/white.yml
create mode 100644 Resources/Prototypes/White/Entities/Clothing/nigger.yml
create mode 100644 Resources/Prototypes/White/Objects/Scrolls/magic.yml
create mode 100644 Resources/Prototypes/White/Objects/Scrolls/scrolls.yml
create mode 100644 Resources/Textures/Effects/EnergyDome/energydome_big.rsi/big.png
create mode 100644 Resources/Textures/Effects/EnergyDome/energydome_big.rsi/meta.json
create mode 100644 Resources/Textures/Effects/EnergyDome/energydome_directed.rsi/meta.json
create mode 100644 Resources/Textures/Effects/EnergyDome/energydome_directed.rsi/small.png
create mode 100644 Resources/Textures/Effects/EnergyDome/energydome_medium.rsi/medium.png
create mode 100644 Resources/Textures/Effects/EnergyDome/energydome_medium.rsi/meta.json
create mode 100644 Resources/Textures/Effects/EnergyDome/energydome_slowdown_big.rsi/big.png
create mode 100644 Resources/Textures/Effects/EnergyDome/energydome_slowdown_big.rsi/meta.json
create mode 100644 Resources/Textures/Effects/EnergyDome/energydome_small.rsi/meta.json
create mode 100644 Resources/Textures/Effects/EnergyDome/energydome_small.rsi/small.png
create mode 100644 Resources/Textures/Objects/Magic/card.rsi/icon.png
create mode 100644 Resources/Textures/Objects/Magic/card.rsi/meta.json
create mode 100644 Resources/Textures/Objects/Magic/magicactions.rsi/clown.png
create mode 100644 Resources/Textures/Objects/Magic/magicactions.rsi/cluwne.png
create mode 100644 Resources/Textures/Objects/Magic/magicactions.rsi/emp_new.png
create mode 100644 Resources/Textures/Objects/Magic/magicactions.rsi/jaunt.png
create mode 100644 Resources/Textures/Objects/Magic/magicactions.rsi/mime_curse.png
create mode 100644 Resources/Textures/Objects/Magic/magicactions.rsi/push.png
create mode 100644 Resources/Textures/Objects/Magic/magicactions.rsi/summons.png
create mode 100644 Resources/Textures/Objects/Magic/magicactions.rsi/thunder.png
create mode 100644 Resources/Textures/Objects/Tools/EnergyDome/reinhardt.rsi/icon.png
create mode 100644 Resources/Textures/Objects/Tools/EnergyDome/reinhardt.rsi/inhand-left.png
create mode 100644 Resources/Textures/Objects/Tools/EnergyDome/reinhardt.rsi/inhand-right.png
create mode 100644 Resources/Textures/Objects/Tools/EnergyDome/reinhardt.rsi/meta.json
create mode 100644 Resources/Textures/Objects/Tools/EnergyDome/syndie.rsi/equipped-BELT.png
create mode 100644 Resources/Textures/Objects/Tools/EnergyDome/syndie.rsi/icon.png
create mode 100644 Resources/Textures/Objects/Tools/EnergyDome/syndie.rsi/inhand-left.png
create mode 100644 Resources/Textures/Objects/Tools/EnergyDome/syndie.rsi/inhand-right.png
create mode 100644 Resources/Textures/Objects/Tools/EnergyDome/syndie.rsi/meta.json
create mode 100644 Resources/Textures/White/Charge/charge.rsi/flux.png
create mode 100644 Resources/Textures/White/Charge/charge.rsi/meta.json
create mode 100644 Resources/Textures/White/Misc/scrolls.rsi/meta.json
create mode 100644 Resources/Textures/White/Misc/scrolls.rsi/scroll.png
diff --git a/Content.Client/ContextMenu/UI/ContextMenuUIController.cs b/Content.Client/ContextMenu/UI/ContextMenuUIController.cs
index 5b156644a7..9d5005e9e8 100644
--- a/Content.Client/ContextMenu/UI/ContextMenuUIController.cs
+++ b/Content.Client/ContextMenu/UI/ContextMenuUIController.cs
@@ -2,6 +2,7 @@ using System.Numerics;
using System.Threading;
using Content.Client.CombatMode;
using Content.Client.Gameplay;
+using Content.Client.UserInterface.Systems.Actions;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
using Timer = Robust.Shared.Timing.Timer;
@@ -16,7 +17,7 @@ namespace Content.Client.ContextMenu.UI
///
/// This largely involves setting up timers to open and close sub-menus when hovering over other menu elements.
///
- public sealed class ContextMenuUIController : UIController, IOnStateEntered, IOnStateExited, IOnSystemChanged
+ public sealed class ContextMenuUIController : UIController, IOnStateEntered, IOnStateExited, IOnSystemChanged, IOnSystemChanged
{
public static readonly TimeSpan HoverDelay = TimeSpan.FromSeconds(0.2);
@@ -216,6 +217,12 @@ namespace Content.Client.ContextMenu.UI
Close();
}
+ private void OnChargingUpdated(bool charging)
+ {
+ if (charging)
+ Close();
+ }
+
public void OnSystemLoaded(CombatModeSystem system)
{
system.LocalPlayerCombatModeUpdated += OnCombatModeUpdated;
@@ -225,5 +232,15 @@ namespace Content.Client.ContextMenu.UI
{
system.LocalPlayerCombatModeUpdated -= OnCombatModeUpdated;
}
+
+ public void OnSystemLoaded(ChargeActionSystem system)
+ {
+ system.ChargingUpdated += OnChargingUpdated;
+ }
+
+ public void OnSystemUnloaded(ChargeActionSystem system)
+ {
+ system.ChargingUpdated -= OnChargingUpdated;
+ }
}
}
diff --git a/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs b/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs
index 09663ba82c..e6df68d933 100644
--- a/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs
+++ b/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs
@@ -22,6 +22,7 @@ using Robust.Client.UserInterface.Controls;
using Robust.Shared.Graphics.RSI;
using Robust.Shared.Input;
using Robust.Shared.Input.Binding;
+using Robust.Shared.Map;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using static Content.Client.Actions.ActionsSystem;
@@ -29,8 +30,7 @@ using static Content.Client.UserInterface.Systems.Actions.Windows.ActionsWindow;
using static Robust.Client.UserInterface.Control;
using static Robust.Client.UserInterface.Controls.BaseButton;
using static Robust.Client.UserInterface.Controls.LineEdit;
-using static Robust.Client.UserInterface.Controls.MultiselectOptionButton<
- Content.Client.UserInterface.Systems.Actions.Windows.ActionsWindow.Filters>;
+using static Robust.Client.UserInterface.Controls.MultiselectOptionButton;
using static Robust.Client.UserInterface.Controls.TextureRect;
using static Robust.Shared.Input.Binding.PointerInputCmdHandler;
@@ -128,25 +128,45 @@ public sealed class ActionUIController : UIController, IOnStateChanged ToggleWindow()))
- .BindBefore(EngineKeyFunctions.Use, new PointerInputCmdHandler(TargetingOnUse, outsidePrediction: true),
- typeof(ConstructionSystem), typeof(DragDropSystem))
- .BindBefore(EngineKeyFunctions.UIRightClick, new PointerInputCmdHandler(TargetingCancel, outsidePrediction: true))
+ .Bind(ContentKeyFunctions.OpenActionsMenu, InputCmdHandler.FromDelegate(_ => ToggleWindow()))
+ .BindBefore(EngineKeyFunctions.Use, new PointerInputCmdHandler(TargetingOnUse, outsidePrediction: true), typeof(ConstructionSystem), typeof(DragDropSystem))
+ .BindBefore(ContentKeyFunctions.AltActivateItemInWorld, new PointerInputCmdHandler(AltUse, outsidePrediction: true))
.Register();
}
- private bool TargetingCancel(in PointerInputCmdArgs args)
+ private bool AltUse(in PointerInputCmdArgs args)
{
- if (!_timing.IsFirstTimePredicted)
+ if (!_timing.IsFirstTimePredicted || _actionsSystem == null || SelectingTargetFor is not { } actionId)
return false;
- // only do something for actual target-based actions
- if (SelectingTargetFor == null)
+ if (_playerManager.LocalEntity is not { } user)
return false;
- StopTargeting();
- return true;
+ if (!EntityManager.TryGetComponent(user, out ActionsComponent? comp))
+ return false;
+
+ if (!_actionsSystem.TryGetActionData(actionId, out var baseAction) ||
+ baseAction is not BaseTargetActionComponent action || !action.IsAltEnabled)
+ {
+ return false;
+ }
+
+ // Is the action currently valid?
+ if (!action.Enabled
+ || action is { Charges: 0, RenewCharges: false }
+ || action.Cooldown.HasValue && action.Cooldown.Value.End > _timing.CurTime)
+ {
+ // The user is targeting with this action, but it is not valid. Maybe mark this click as
+ // handled and prevent further interactions.
+ return !action.InteractOnMiss;
+ }
+
+ return action switch
+ {
+ WorldTargetActionComponent mapTarget => TryTargetWorld(args.Coordinates, actionId, mapTarget, user, comp,
+ ActionUseType.AltUse, target: args.EntityUid) || !mapTarget.InteractOnMiss,
+ _ => false
+ };
}
///
@@ -179,28 +199,23 @@ public sealed class ActionUIController : UIController, IOnStateChanged TryTargetWorld(args.Coordinates, actionId, mapTarget, user, comp) ||
+ !mapTarget.InteractOnMiss,
+ EntityTargetActionComponent entTarget => TryTargetEntity(args.EntityUid, actionId, entTarget, user, comp) ||
+ !entTarget.InteractOnMiss,
+ _ => false
+ };
}
- private bool TryTargetWorld(in PointerInputCmdArgs args, EntityUid actionId, WorldTargetActionComponent action, EntityUid user, ActionsComponent actionComp)
+ public bool TryTargetWorld(EntityCoordinates coordinates, EntityUid actionId, WorldTargetActionComponent action,
+ EntityUid user, ActionsComponent actionComp, ActionUseType type = ActionUseType.Default, int chargeLevel = 0, EntityUid? target = default)
{
if (_actionsSystem == null)
return false;
- var coords = args.Coordinates;
-
- if (!_actionsSystem.ValidateWorldTarget(user, coords, action))
+ if (!_actionsSystem.ValidateWorldTarget(user, coordinates, action))
{
// Invalid target.
if (action.DeselectOnMiss)
@@ -213,14 +228,24 @@ public sealed class ActionUIController : UIController, IOnStateChanged actions)
{
if (_window is not { Disposed: false, IsOpen: true })
@@ -432,7 +456,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged? ChargingUpdated;
+
+ private bool _charging;
+ private bool _prevCharging;
+
+ private float _chargeTime;
+ private int _chargeLevel;
+ private int _prevChargeLevel;
+
+ private bool _isChargingPlaying;
+ private bool _isChargedPlaying;
+
+ private const float LevelChargeTime = 1.5f;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ _controller = _uiManager.GetUIController();
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ if (_playerManager.LocalEntity is not { } user)
+ return;
+
+ if (!_timing.IsFirstTimePredicted || _controller == null || _controller.SelectingTargetFor is not { } actionId)
+ return;
+
+ if (!_actionsSystem.TryGetActionData(actionId, out var baseAction) ||
+ baseAction is not BaseTargetActionComponent action || !action.IsChargeEnabled)
+ return;
+
+ if (!action.Enabled
+ || action is { Charges: 0, RenewCharges: false }
+ || action.Cooldown.HasValue && action.Cooldown.Value.End > _timing.CurTime)
+ {
+ return;
+ }
+
+ var altDown = _inputSystem.CmdStates.GetState(EngineKeyFunctions.UseSecondary);
+ switch (altDown)
+ {
+ case BoundKeyState.Down:
+ _prevCharging = _charging;
+ _charging = true;
+ _chargeTime += frameTime;
+ _chargeLevel = (int) (_chargeTime / LevelChargeTime) + 1;
+ _chargeLevel = Math.Clamp(_chargeLevel, 1, action.MaxChargeLevel);
+ break;
+ case BoundKeyState.Up when _charging:
+ _prevCharging = _charging;
+ _charging = false;
+ _chargeTime = 0f;
+ _isChargingPlaying = false;
+ _isChargedPlaying = false;
+
+ HandleAction(actionId, action, user, _chargeLevel);
+ _chargeLevel = 0;
+
+ RaiseNetworkEvent(new RequestAudioSpellStop());
+ RaiseNetworkEvent(new RemoveWizardChargeEvent());
+ break;
+ case BoundKeyState.Up:
+ _prevCharging = _charging;
+ _chargeLevel = 0;
+ _charging = false;
+ _chargeTime = 0f;
+ _isChargingPlaying = false;
+ _isChargedPlaying = false;
+
+ RaiseNetworkEvent(new RequestAudioSpellStop());
+ RaiseNetworkEvent(new RemoveWizardChargeEvent());
+ break;
+ }
+
+ if (_chargeLevel != _prevChargeLevel)
+ {
+ if (_chargeLevel > 0 && _charging)
+ {
+ RaiseNetworkEvent(new AddWizardChargeEvent(action.ChargeProto));
+ }
+ _prevChargeLevel = _chargeLevel;
+ }
+
+ if (_prevCharging != _charging)
+ {
+ ChargingUpdated?.Invoke(_charging);
+ }
+
+ if (_charging && !_isChargingPlaying)
+ {
+ _isChargingPlaying = true;
+ RaiseNetworkEvent(new RequestSpellChargingAudio(action.ChargingSound, action.LoopCharging));
+ }
+
+ if (_chargeLevel >= action.MaxChargeLevel && !_isChargedPlaying && _charging)
+ {
+ _isChargedPlaying = true;
+ RaiseNetworkEvent(new RequestSpellChargedAudio(action.MaxChargedSound, action.LoopMaxCharged));
+ }
+ }
+
+ private void HandleAction(EntityUid actionId, BaseTargetActionComponent action, EntityUid user, int chargeLevel)
+ {
+ var mousePos = _eyeManager.PixelToMap(_inputManager.MouseScreenPosition);
+ if (mousePos.MapId == MapId.Nullspace)
+ return;
+
+ var coordinates = EntityCoordinates.FromMap(_mapManager.TryFindGridAt(mousePos, out var gridUid, out _)
+ ? gridUid
+ : _mapManager.GetMapEntityId(mousePos.MapId), mousePos, _transformSystem, EntityManager);
+
+ if (!EntityManager.TryGetComponent(user, out ActionsComponent? comp))
+ return;
+
+ switch (action)
+ {
+ case WorldTargetActionComponent mapTarget:
+ _controller?.TryTargetWorld(coordinates, actionId, mapTarget, user, comp, ActionUseType.Charge, chargeLevel);
+ break;
+ }
+
+ RaiseNetworkEvent(new RequestAudioSpellStop());
+ RaiseNetworkEvent(new RemoveWizardChargeEvent());
+ }
+
+ public override void Shutdown()
+ {
+ base.Shutdown();
+
+ _controller = null;
+
+ _charging = false;
+ _prevCharging = false;
+ _chargeTime = 0f;
+ _chargeLevel = 0;
+ _prevChargeLevel = 0;
+ _isChargingPlaying = false;
+ _isChargedPlaying = false;
+ }
+}
diff --git a/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs b/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs
index c1064f62f7..0298b5de46 100644
--- a/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs
+++ b/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs
@@ -17,8 +17,6 @@ using Robust.Client.State;
using Robust.Shared.Input;
using Robust.Shared.Map;
using Robust.Shared.Player;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Timing;
namespace Content.Client.Weapons.Melee;
diff --git a/Content.Client/_White/Wizard/Scrolls/ScrollSystem.cs b/Content.Client/_White/Wizard/Scrolls/ScrollSystem.cs
new file mode 100644
index 0000000000..79aa123a89
--- /dev/null
+++ b/Content.Client/_White/Wizard/Scrolls/ScrollSystem.cs
@@ -0,0 +1,7 @@
+using Content.Shared._White.Wizard.ScrollSystem;
+
+namespace Content.Client._White.Wizard.Scrolls;
+
+public sealed class ScrollSystem : SharedScrollSystem
+{
+}
diff --git a/Content.Server/EnergyDome/EnergyDomeComponent.cs b/Content.Server/EnergyDome/EnergyDomeComponent.cs
new file mode 100644
index 0000000000..b1efc631ec
--- /dev/null
+++ b/Content.Server/EnergyDome/EnergyDomeComponent.cs
@@ -0,0 +1,15 @@
+namespace Content.Server.EnergyDome;
+
+///
+/// marker component that allows linking the dome generator with the dome itself
+///
+
+[RegisterComponent, Access(typeof(EnergyDomeSystem))]
+public sealed partial class EnergyDomeComponent : Component
+{
+ ///
+ /// A linked generator that uses energy
+ ///
+ [DataField]
+ public EntityUid? Generator;
+}
diff --git a/Content.Server/EnergyDome/EnergyDomeGeneratorComponent.cs b/Content.Server/EnergyDome/EnergyDomeGeneratorComponent.cs
new file mode 100644
index 0000000000..24189f518f
--- /dev/null
+++ b/Content.Server/EnergyDome/EnergyDomeGeneratorComponent.cs
@@ -0,0 +1,85 @@
+using Content.Shared.DeviceLinking;
+using Robust.Shared.Audio;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.EnergyDome;
+
+///
+/// component, allows an entity to generate a battery-powered energy dome of a specific type.
+///
+[RegisterComponent, Access(typeof(EnergyDomeSystem))] //Access add
+public sealed partial class EnergyDomeGeneratorComponent : Component
+{
+ [DataField]
+ public bool Enabled = false;
+
+ ///
+ /// How much energy will be spent from the battery per unit of damage taken by the shield.
+ ///
+ [DataField]
+ public float DamageEnergyDraw = 10f;
+
+ ///
+ /// Whether or not the dome can be toggled via standard interactions
+ /// (alt verbs, using in hand, etc)
+ ///
+ [DataField]
+ public bool CanInteractUse = true;
+
+ ///
+ /// Can the NetworkDevice system activate and deactivate the barrier?
+ ///
+ [DataField]
+ public bool CanDeviceNetworkUse = false;
+
+ //Dome
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public EntProtoId DomePrototype = "EnergyDomeSmallRed";
+
+ [DataField]
+ public EntityUid? SpawnedDome;
+
+ ///
+ /// the entity on which the shield will be hung. This is either the container containing
+ /// the item or the item itself. Determined when the shield is activated,
+ /// it is stored in the component for changing the protected entity.
+ ///
+ [DataField]
+ public EntityUid? DomeParentEntity;
+
+ //Action
+ [DataField]
+ public EntProtoId ToggleAction = "ActionToggleDome";
+
+ [DataField]
+ public EntityUid? ToggleActionEntity;
+
+ //Sounds
+ [DataField]
+ public SoundSpecifier AccessDeniedSound = new SoundPathSpecifier("/Audio/Machines/custom_deny.ogg");
+
+ [DataField]
+ public SoundSpecifier TurnOnSound = new SoundPathSpecifier("/Audio/Machines/anomaly_sync_connect.ogg");
+
+ [DataField]
+ public SoundSpecifier EnergyOutSound = new SoundPathSpecifier("/Audio/Machines/energyshield_down.ogg");
+
+ [DataField]
+ public SoundSpecifier TurnOffSound = new SoundPathSpecifier("/Audio/Machines/button.ogg");
+
+ [DataField]
+ public SoundSpecifier ParrySound = new SoundPathSpecifier("/Audio/Machines/energyshield_parry.ogg")
+ {
+ Params = AudioParams.Default.WithVariation(0.05f)
+ };
+
+ //Ports
+ [DataField]
+ public ProtoId TogglePort = "Toggle";
+
+ [DataField]
+ public ProtoId OnPort = "On";
+
+ [DataField]
+ public ProtoId OffPort = "Off";
+}
diff --git a/Content.Server/EnergyDome/EnergyDomeSystem.cs b/Content.Server/EnergyDome/EnergyDomeSystem.cs
new file mode 100644
index 0000000000..1439c38c2a
--- /dev/null
+++ b/Content.Server/EnergyDome/EnergyDomeSystem.cs
@@ -0,0 +1,329 @@
+using Content.Server.DeviceLinking.Events;
+using Content.Server.DeviceLinking.Systems;
+using Content.Server.Power.Components;
+using Content.Server.Power.EntitySystems;
+using Content.Server.PowerCell;
+using Content.Shared.Actions;
+using Content.Shared.Damage;
+using Content.Shared.Examine;
+using Content.Shared.Interaction;
+using Content.Shared.Popups;
+using Content.Shared.PowerCell;
+using Content.Shared.PowerCell.Components;
+using Content.Shared.Timing;
+using Content.Shared.Toggleable;
+using Content.Shared.Verbs;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Containers;
+
+namespace Content.Server.EnergyDome;
+
+public sealed partial class EnergyDomeSystem : EntitySystem
+{
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
+ [Dependency] private readonly BatterySystem _battery = default!;
+ [Dependency] private readonly SharedContainerSystem _container = default!;
+ [Dependency] private readonly UseDelaySystem _useDelay = default!;
+ [Dependency] private readonly SharedTransformSystem _transform = default!;
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
+ [Dependency] private readonly PowerCellSystem _powerCell = default!;
+ [Dependency] private readonly DeviceLinkSystem _signalSystem = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ //Generator events
+ SubscribeLocalEvent(OnInit);
+
+ SubscribeLocalEvent(OnActivatedInWorld);
+ SubscribeLocalEvent(OnAfterInteract);
+ SubscribeLocalEvent(OnSignalReceived);
+ SubscribeLocalEvent(OnGetActions);
+ SubscribeLocalEvent(OnToggleAction);
+
+ SubscribeLocalEvent(OnPowerCellChanged);
+ SubscribeLocalEvent(OnPowerCellSlotEmpty);
+ SubscribeLocalEvent(OnChargeChanged);
+
+ SubscribeLocalEvent(OnParentChanged);
+
+ SubscribeLocalEvent>(AddToggleDomeVerb);
+ SubscribeLocalEvent(OnExamine);
+
+
+ SubscribeLocalEvent(OnComponentRemove);
+
+ //Dome events
+ SubscribeLocalEvent(OnDomeDamaged);
+ }
+
+
+ private void OnInit(Entity generator, ref MapInitEvent args)
+ {
+ if (generator.Comp.CanDeviceNetworkUse)
+ _signalSystem.EnsureSinkPorts(generator, generator.Comp.TogglePort, generator.Comp.OnPort, generator.Comp.OffPort);
+ }
+
+ //different ways of use
+
+ private void OnSignalReceived(Entity generator, ref SignalReceivedEvent args)
+ {
+ if (!generator.Comp.CanDeviceNetworkUse)
+ return;
+
+ if (args.Port == generator.Comp.OnPort)
+ {
+ AttemptToggle(generator, true);
+ }
+ if (args.Port == generator.Comp.OffPort)
+ {
+ AttemptToggle(generator, false);
+ }
+ if (args.Port == generator.Comp.TogglePort)
+ {
+ AttemptToggle(generator, !generator.Comp.Enabled);
+ }
+ }
+
+ private void OnAfterInteract(Entity generator, ref AfterInteractEvent args)
+ {
+ if (generator.Comp.CanInteractUse)
+ AttemptToggle(generator, !generator.Comp.Enabled);
+ }
+
+ private void OnActivatedInWorld(Entity generator, ref ActivateInWorldEvent args)
+ {
+ if (generator.Comp.CanInteractUse)
+ AttemptToggle(generator, !generator.Comp.Enabled);
+ }
+
+ private void OnExamine(Entity generator, ref ExaminedEvent args)
+ {
+ args.PushMarkup(Loc.GetString(
+ (generator.Comp.Enabled)
+ ? "energy-dome-on-examine-is-on-message"
+ : "energy-dome-on-examine-is-off-message"
+ ));
+ }
+
+ private void AddToggleDomeVerb(Entity generator, ref GetVerbsEvent args)
+ {
+ if (!args.CanAccess || !args.CanInteract || !generator.Comp.CanInteractUse)
+ return;
+
+ var @event = args;
+ ActivationVerb verb = new()
+ {
+ Text = Loc.GetString("energy-dome-verb-toggle"),
+ Act = () => AttemptToggle(generator, !generator.Comp.Enabled)
+ };
+
+ args.Verbs.Add(verb);
+ }
+ private void OnGetActions(Entity generator, ref GetItemActionsEvent args)
+ {
+ if (generator.Comp.CanInteractUse)
+ args.AddAction(ref generator.Comp.ToggleActionEntity, generator.Comp.ToggleAction);
+ }
+
+ private void OnToggleAction(Entity generator, ref ToggleActionEvent args)
+ {
+ if (args.Handled)
+ return;
+
+ AttemptToggle(generator, !generator.Comp.Enabled);
+
+ args.Handled = true;
+ }
+
+ // System interactions
+
+ private void OnPowerCellSlotEmpty(Entity generator, ref PowerCellSlotEmptyEvent args)
+ {
+ TurnOff(generator, true);
+ }
+
+ private void OnPowerCellChanged(Entity generator, ref PowerCellChangedEvent args)
+ {
+ if (args.Ejected || !_powerCell.HasDrawCharge(generator))
+ TurnOff(generator, true);
+ }
+
+ private void OnChargeChanged(Entity generator, ref ChargeChangedEvent args)
+ {
+ if (args.Charge == 0)
+ TurnOff(generator, true);
+ }
+ private void OnDomeDamaged(Entity dome, ref DamageChangedEvent args)
+ {
+ if (dome.Comp.Generator == null)
+ return;
+
+ var generatorUid = dome.Comp.Generator.Value;
+
+ if (!TryComp(generatorUid, out var generatorComp))
+ return;
+
+ if (args.DamageDelta == null)
+ return;
+
+ float totalDamage = args.DamageDelta.GetTotal().Float();
+ var energyLeak = totalDamage * generatorComp.DamageEnergyDraw;
+
+ _audio.PlayPvs(generatorComp.ParrySound, dome);
+
+ if (HasComp(generatorUid))
+ {
+ _powerCell.TryGetBatteryFromSlot(generatorUid, out var cell);
+ if (cell != null)
+ {
+ _battery.UseCharge(cell.Owner, energyLeak);
+
+ if (cell.Charge == 0)
+ TurnOff((generatorUid, generatorComp), true);
+ }
+ }
+
+ //it seems to me it would not work well to hang both a powercell and an internal battery with wire charging on the object....
+ if (TryComp(generatorUid, out var battery)) {
+ _battery.UseCharge(generatorUid, energyLeak);
+
+ if (battery.Charge == 0)
+ TurnOff((generatorUid, generatorComp), true);
+ }
+ }
+
+ private void OnParentChanged(Entity generator, ref EntParentChangedMessage args)
+ {
+ //To do: taking the active barrier in hand for some reason does not manage to change the parent in this case,
+ //and the barrier is not turned off.
+ //
+ //Laying down works well (-_-)
+ if (GetProtectedEntity(generator) != generator.Comp.DomeParentEntity)
+ TurnOff(generator, false);
+ }
+
+ private void OnComponentRemove(Entity generator, ref ComponentRemove args)
+ {
+ TurnOff(generator, false);
+ }
+
+ // Functional
+
+ public bool AttemptToggle(Entity generator, bool status)
+ {
+ if (TryComp(generator, out var useDelay) && _useDelay.IsDelayed(new Entity(generator, useDelay)))
+ {
+ _audio.PlayPvs(generator.Comp.TurnOffSound, generator);
+ _popup.PopupEntity(
+ Loc.GetString("energy-dome-recharging"),
+ generator);
+ return false;
+ }
+
+ if (TryComp(generator, out var powerCellSlot))
+ {
+ if (!_powerCell.TryGetBatteryFromSlot(generator, out var cell) && !TryComp(generator, out cell))
+ {
+ _audio.PlayPvs(generator.Comp.TurnOffSound, generator);
+ _popup.PopupEntity(
+ Loc.GetString("energy-dome-no-cell"),
+ generator);
+ return false;
+ }
+
+ if (!_powerCell.HasDrawCharge(generator))
+ {
+ _audio.PlayPvs(generator.Comp.TurnOffSound, generator);
+ _popup.PopupEntity(
+ Loc.GetString("energy-dome-no-power"),
+ generator);
+ return false;
+ }
+ }
+
+ if (TryComp(generator, out var battery))
+ {
+ if (battery.Charge == 0)
+ {
+ _audio.PlayPvs(generator.Comp.TurnOffSound, generator);
+ _popup.PopupEntity(
+ Loc.GetString("energy-dome-no-power"),
+ generator);
+ return false;
+ }
+ }
+
+ Toggle(generator, status);
+ return true;
+ }
+
+ private void Toggle(Entity generator, bool status)
+ {
+ if (status)
+ TurnOn(generator);
+ else
+ TurnOff(generator, false);
+ }
+
+ private void TurnOn(Entity generator)
+ {
+ if (generator.Comp.Enabled)
+ return;
+
+ var protectedEntity = GetProtectedEntity(generator);
+
+ var newDome = Spawn(generator.Comp.DomePrototype, Transform(protectedEntity).Coordinates);
+ generator.Comp.DomeParentEntity = protectedEntity;
+ _transform.SetParent(newDome, protectedEntity);
+
+ if (TryComp(newDome, out var domeComp))
+ {
+ domeComp.Generator = generator;
+ }
+
+ _powerCell.SetPowerCellDrawEnabled(generator, true);
+ if (TryComp(generator, out var recharger)) {
+ recharger.AutoRecharge = true;
+ }
+
+ generator.Comp.SpawnedDome = newDome;
+ _audio.PlayPvs(generator.Comp.TurnOnSound, generator);
+ generator.Comp.Enabled = true;
+ }
+
+ private void TurnOff(Entity generator, bool startReloading)
+ {
+ if (!generator.Comp.Enabled)
+ return;
+
+ generator.Comp.Enabled = false;
+ QueueDel(generator.Comp.SpawnedDome);
+
+ _powerCell.SetPowerCellDrawEnabled(generator, false);
+ if (TryComp(generator, out var recharger))
+ {
+ recharger.AutoRecharge = false;
+ }
+
+ _audio.PlayPvs(generator.Comp.TurnOffSound, generator);
+ if (startReloading)
+ {
+ _audio.PlayPvs(generator.Comp.EnergyOutSound, generator);
+ if (TryComp(generator, out var useDelay))
+ {
+ _useDelay.TryResetDelay(new Entity(generator, useDelay));
+ }
+ }
+ }
+
+ // Util
+
+ private EntityUid GetProtectedEntity(EntityUid entity)
+ {
+ return (_container.TryGetOuterContainer(entity, Transform(entity), out var container))
+ ? container.Owner
+ : entity;
+ }
+}
diff --git a/Content.Server/Lightning/LightningSystem.cs b/Content.Server/Lightning/LightningSystem.cs
index 4f975a60fd..6f5a86b0bb 100644
--- a/Content.Server/Lightning/LightningSystem.cs
+++ b/Content.Server/Lightning/LightningSystem.cs
@@ -57,7 +57,6 @@ public sealed class LightningSystem : SharedLightningSystem
}
}
-
///
/// Looks for objects with a LightningTarget component in the radius, prioritizes them, and hits the highest priority targets with lightning.
///
@@ -78,9 +77,9 @@ public sealed class LightningSystem : SharedLightningSystem
_random.Shuffle(targets);
targets.Sort((x, y) => y.Priority.CompareTo(x.Priority));
- int shootedCount = 0;
- int count = -1;
- while(shootedCount < boltCount)
+ var shootCount = 0;
+ var count = -1;
+ while(shootCount < boltCount)
{
count++;
@@ -95,7 +94,7 @@ public sealed class LightningSystem : SharedLightningSystem
{
ShootRandomLightnings(targets[count].Owner, range, 1, lightningPrototype, arcDepth - targets[count].LightningResistance, triggerLightningEvents);
}
- shootedCount++;
+ shootCount++;
}
}
}
diff --git a/Content.Server/Magic/Components/SpellbookComponent.cs b/Content.Server/Magic/Components/SpellbookComponent.cs
deleted file mode 100644
index ebc3c88043..0000000000
--- a/Content.Server/Magic/Components/SpellbookComponent.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
-
-namespace Content.Server.Magic.Components;
-
-///
-/// Spellbooks for having an entity learn spells as long as they've read the book and it's in their hand.
-///
-[RegisterComponent]
-public sealed partial class SpellbookComponent : Component
-{
- ///
- /// List of spells that this book has. This is a combination of the WorldSpells, EntitySpells, and InstantSpells.
- ///
- [ViewVariables]
- public readonly List Spells = new();
-
- ///
- /// The three fields below is just used for initialization.
- ///
- [DataField("spells", customTypeSerializer: typeof(PrototypeIdDictionarySerializer))]
- [ViewVariables(VVAccess.ReadWrite)]
- public Dictionary SpellActions = new();
-
- [DataField("learnTime")]
- [ViewVariables(VVAccess.ReadWrite)]
- public float LearnTime = .75f;
-
- ///
- /// If true, the spell action stays even after the book is removed
- ///
- [DataField("learnPermanently")]
- [ViewVariables(VVAccess.ReadWrite)]
- public bool LearnPermanently;
-}
diff --git a/Content.Server/Magic/MagicSystem.cs b/Content.Server/Magic/MagicSystem.cs
index bb11c1f014..53963879fe 100644
--- a/Content.Server/Magic/MagicSystem.cs
+++ b/Content.Server/Magic/MagicSystem.cs
@@ -1,17 +1,15 @@
+using System.Linq;
using System.Numerics;
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
using Content.Server.Chat.Systems;
using Content.Server.Doors.Systems;
-using Content.Server.Magic.Components;
using Content.Server.Weapons.Ranged.Systems;
using Content.Shared.Actions;
using Content.Shared.Body.Components;
using Content.Shared.Coordinates.Helpers;
-using Content.Shared.DoAfter;
using Content.Shared.Doors.Components;
using Content.Shared.Doors.Systems;
-using Content.Shared.Interaction.Events;
using Content.Shared.Magic;
using Content.Shared.Magic.Events;
using Content.Shared.Maps;
@@ -21,6 +19,7 @@ using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Map;
+using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Components;
using Robust.Shared.Random;
using Robust.Shared.Serialization.Manager;
@@ -33,31 +32,25 @@ namespace Content.Server.Magic;
///
public sealed class MagicSystem : EntitySystem
{
- [Dependency] private readonly ISerializationManager _seriMan = default!;
+ [Dependency] private readonly ISerializationManager _serializationManager = default!;
[Dependency] private readonly IComponentFactory _compFact = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
+ [Dependency] private readonly SharedMapSystem _mapSystem = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly DoorBoltSystem _boltsSystem = default!;
[Dependency] private readonly BodySystem _bodySystem = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly SharedDoorSystem _doorSystem = default!;
- [Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
- [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly GunSystem _gunSystem = default!;
[Dependency] private readonly PhysicsSystem _physics = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly ChatSystem _chat = default!;
- [Dependency] private readonly ActionContainerSystem _actionContainer = default!;
public override void Initialize()
{
base.Initialize();
- SubscribeLocalEvent(OnInit);
- SubscribeLocalEvent(OnUse);
- SubscribeLocalEvent(OnDoAfter);
-
SubscribeLocalEvent(OnInstantSpawn);
SubscribeLocalEvent(OnTeleportSpell);
SubscribeLocalEvent(OnKnockSpell);
@@ -67,73 +60,8 @@ public sealed class MagicSystem : EntitySystem
SubscribeLocalEvent(OnChangeComponentsSpell);
}
- private void OnDoAfter(EntityUid uid, SpellbookComponent component, DoAfterEvent args)
- {
- if (args.Handled || args.Cancelled)
- return;
-
- args.Handled = true;
- if (!component.LearnPermanently)
- {
- _actionsSystem.GrantActions(args.Args.User, component.Spells, uid);
- return;
- }
-
- foreach (var (id, charges) in component.SpellActions)
- {
- // TOOD store spells entity ids on some sort of innate magic user component or something like that.
- EntityUid? actionId = null;
- if (_actionsSystem.AddAction(args.Args.User, ref actionId, id))
- _actionsSystem.SetCharges(actionId, charges < 0 ? null : charges);
- }
-
- component.SpellActions.Clear();
- }
-
- private void OnInit(EntityUid uid, SpellbookComponent component, MapInitEvent args)
- {
- if (component.LearnPermanently)
- return;
-
- foreach (var (id, charges) in component.SpellActions)
- {
- var spell = _actionContainer.AddAction(uid, id);
- if (spell == null)
- continue;
-
- _actionsSystem.SetCharges(spell, charges < 0 ? null : charges);
- component.Spells.Add(spell.Value);
- }
- }
-
- private void OnUse(EntityUid uid, SpellbookComponent component, UseInHandEvent args)
- {
- if (args.Handled)
- return;
-
- AttemptLearn(uid, component, args);
-
- args.Handled = true;
- }
-
- private void AttemptLearn(EntityUid uid, SpellbookComponent component, UseInHandEvent args)
- {
- var doAfterEventArgs = new DoAfterArgs(EntityManager, args.User, component.LearnTime, new SpellbookDoAfterEvent(), uid, target: uid)
- {
- BreakOnTargetMove = true,
- BreakOnUserMove = true,
- BreakOnDamage = true,
- NeedHand = true //What, are you going to read with your eyes only??
- };
-
- _doAfter.TryStartDoAfter(doAfterEventArgs);
- }
-
#region Spells
- ///
- /// Handles the instant action (i.e. on the caster) attempting to spawn an entity.
- ///
private void OnInstantSpawn(InstantSpawnSpellEvent args)
{
if (args.Handled)
@@ -145,11 +73,11 @@ public sealed class MagicSystem : EntitySystem
{
var ent = Spawn(args.Prototype, position.SnapToGrid(EntityManager, _mapManager));
- if (args.PreventCollideWithCaster)
- {
- var comp = EnsureComp(ent);
- comp.Uid = args.Performer;
- }
+ if (!args.PreventCollideWithCaster)
+ continue;
+
+ var comp = EnsureComp(ent);
+ comp.Uid = args.Performer;
}
Speak(args);
@@ -166,22 +94,17 @@ public sealed class MagicSystem : EntitySystem
var xform = Transform(ev.Performer);
- // var userVelocity = _physics.GetMapLinearVelocity(ev.Performer); WD EDIT
-
foreach (var pos in GetSpawnPositions(xform, ev.Pos))
{
- // If applicable, this ensures the projectile is parented to grid on spawn, instead of the map.
- var mapPos = pos.ToMap(EntityManager);
- var spawnCoords = _mapManager.TryFindGridAt(mapPos, out var gridUid, out var grid) // WD EDIT
+ var mapPos = _transformSystem.ToMapCoordinates(pos);
+ var spawnCoords = _mapManager.TryFindGridAt(mapPos, out var gridUid, out var grid)
? pos.WithEntityId(gridUid, EntityManager)
- : new(_mapManager.GetMapEntityId(mapPos.MapId), mapPos.Position);
+ : new EntityCoordinates(_mapManager.GetMapEntityId(mapPos.MapId), mapPos.Position);
- // WD EDIT
var userVelocity = Vector2.Zero;
if (grid != null && TryComp(gridUid, out PhysicsComponent? physics))
userVelocity = physics.LinearVelocity;
- // WD EDIT
var ent = Spawn(ev.Prototype, spawnCoords);
var direction = ev.Target.ToMapPos(EntityManager, _transformSystem) -
@@ -194,7 +117,9 @@ public sealed class MagicSystem : EntitySystem
{
if (ev.Handled)
return;
+
ev.Handled = true;
+
Speak(ev);
foreach (var toRemove in ev.ToRemove)
@@ -209,75 +134,12 @@ public sealed class MagicSystem : EntitySystem
continue;
var component = (Component) _compFact.GetComponent(name);
- component.Owner = ev.Target;
var temp = (object) component;
- _seriMan.CopyTo(data.Component, ref temp);
+ _serializationManager.CopyTo(data.Component, ref temp);
EntityManager.AddComponent(ev.Target, (Component) temp!);
}
}
- private List GetSpawnPositions(TransformComponent casterXform, MagicSpawnData data)
- {
- switch (data)
- {
- case TargetCasterPos:
- return new List(1) {casterXform.Coordinates};
- case TargetInFront:
- {
- // This is shit but you get the idea.
- var directionPos = casterXform.Coordinates.Offset(casterXform.LocalRotation.ToWorldVec().Normalized());
-
- if (!_mapManager.TryGetGrid(casterXform.GridUid, out var mapGrid))
- return new List();
-
- if (!directionPos.TryGetTileRef(out var tileReference, EntityManager, _mapManager))
- return new List();
-
- var tileIndex = tileReference.Value.GridIndices;
- var coords = mapGrid.GridTileToLocal(tileIndex);
- EntityCoordinates coordsPlus;
- EntityCoordinates coordsMinus;
-
- var dir = casterXform.LocalRotation.GetCardinalDir();
- switch (dir)
- {
- case Direction.North:
- case Direction.South:
- {
- coordsPlus = mapGrid.GridTileToLocal(tileIndex + (1, 0));
- coordsMinus = mapGrid.GridTileToLocal(tileIndex + (-1, 0));
- return new List(3)
- {
- coords,
- coordsPlus,
- coordsMinus,
- };
- }
- case Direction.East:
- case Direction.West:
- {
- coordsPlus = mapGrid.GridTileToLocal(tileIndex + (0, 1));
- coordsMinus = mapGrid.GridTileToLocal(tileIndex + (0, -1));
- return new List(3)
- {
- coords,
- coordsPlus,
- coordsMinus,
- };
- }
- }
-
- return new List();
- }
- default:
- throw new ArgumentOutOfRangeException();
- }
- }
-
- ///
- /// Teleports the user to the clicked location
- ///
- ///
private void OnTeleportSpell(TeleportSpellEvent args)
{
if (args.Handled)
@@ -285,19 +147,16 @@ public sealed class MagicSystem : EntitySystem
var transform = Transform(args.Performer);
- if (transform.MapID != args.Target.GetMapId(EntityManager)) return;
+ if (transform.MapID != args.Target.GetMapId(EntityManager))
+ return;
_transformSystem.SetCoordinates(args.Performer, args.Target);
- transform.AttachToGridOrMap();
+ _transformSystem.AttachToGridOrMap(args.Performer);
_audio.PlayPvs(args.BlinkSound, args.Performer, AudioParams.Default.WithVolume(args.BlinkVolume));
Speak(args);
args.Handled = true;
}
- ///
- /// Opens all doors within range
- ///
- ///
private void OnKnockSpell(KnockSpellEvent args)
{
if (args.Handled)
@@ -306,13 +165,11 @@ public sealed class MagicSystem : EntitySystem
args.Handled = true;
Speak(args);
- //Get the position of the player
var transform = Transform(args.Performer);
var coords = transform.Coordinates;
_audio.PlayPvs(args.KnockSound, args.Performer, AudioParams.Default.WithVolume(args.KnockVolume));
- //Look for doors and don't open them if they're already open.
foreach (var entity in _lookup.GetEntitiesInRange(coords, args.Range))
{
if (TryComp(entity, out var bolts))
@@ -329,9 +186,10 @@ public sealed class MagicSystem : EntitySystem
return;
ev.Handled = true;
+
Speak(ev);
- var direction = Transform(ev.Target).MapPosition.Position - Transform(ev.Performer).MapPosition.Position;
+ var direction = _transformSystem.GetMapCoordinates(ev.Target).Position - _transformSystem.GetMapCoordinates(ev.Performer).Position;
var impulseVector = direction * 10000;
_physics.ApplyLinearImpulse(ev.Target, impulseVector);
@@ -339,28 +197,17 @@ public sealed class MagicSystem : EntitySystem
if (!TryComp(ev.Target, out var body))
return;
- var ents = _bodySystem.GibBody(ev.Target, true, body);
+ var entities = _bodySystem.GibBody(ev.Target, true, body);
if (!ev.DeleteNonBrainParts)
return;
- foreach (var part in ents)
+ foreach (var part in entities.Where(part => HasComp(part) && !HasComp(part)))
{
- // just leaves a brain and clothes
- if (HasComp(part) && !HasComp(part))
- {
- QueueDel(part);
- }
+ QueueDel(part);
}
}
- ///
- /// Spawns entity prototypes from a list within range of click.
- ///
- ///
- /// It will offset mobs after the first mob based on the OffsetVector2 property supplied.
- ///
- /// The Spawn Spell Event args.
private void OnWorldSpawn(WorldSpawnSpellEvent args)
{
if (args.Handled)
@@ -373,24 +220,85 @@ public sealed class MagicSystem : EntitySystem
args.Handled = true;
}
- ///
- /// Loops through a supplied list of entity prototypes and spawns them
- ///
- ///
- /// If an offset of 0, 0 is supplied then the entities will all spawn on the same tile.
- /// Any other offset will spawn entities starting from the source Map Coordinates and will increment the supplied
- /// offset
- ///
- /// The list of Entities to spawn in
- /// Map Coordinates where the entities will spawn
- /// Check to see if the entities should self delete
- /// A Vector2 offset that the entities will spawn in
- private void SpawnSpellHelper(List entityEntries, EntityCoordinates entityCoords, float? lifetime, Vector2 offsetVector2)
+ #endregion
+
+ #region Helpers
+
+ public List GetSpawnPositions(TransformComponent casterXform, MagicSpawnData data)
{
- var getProtos = EntitySpawnCollection.GetSpawns(entityEntries, _random);
+ return data switch
+ {
+ TargetCasterPos => GetCasterPosition(casterXform),
+ TargetInFront => GetPositionsInFront(casterXform),
+ _ => throw new ArgumentOutOfRangeException()
+ };
+ }
+
+ public List GetCasterPosition(TransformComponent casterXform)
+ {
+ return new List(1) { casterXform.Coordinates };
+ }
+
+ public List GetPositionsInFront(TransformComponent casterXform)
+ {
+ var directionPos = casterXform.Coordinates.Offset(casterXform.LocalRotation.ToWorldVec().Normalized());
+
+ if (!TryComp(casterXform.GridUid, out var mapGrid) ||
+ !directionPos.TryGetTileRef(out var tileReference, EntityManager, _mapManager))
+ {
+ return new List();
+ }
+
+ var tileIndex = tileReference.Value.GridIndices;
+ var coords = _mapSystem.GridTileToLocal(casterXform.GridUid.Value, mapGrid, tileIndex);
+
+ var directions = GetCardinalDirections(casterXform.LocalRotation.GetCardinalDir());
+ var spawnPositions = new List(3);
+
+ foreach (var direction in directions)
+ {
+ var offset = GetOffsetForDirection(direction);
+ var coordinates = _mapSystem.GridTileToLocal(casterXform.GridUid.Value, mapGrid, tileIndex + offset);
+ spawnPositions.Add(coordinates);
+ }
+
+ spawnPositions.Add(coords);
+ return spawnPositions;
+ }
+
+ public IEnumerable GetCardinalDirections(Direction dir)
+ {
+ switch (dir)
+ {
+ case Direction.North:
+ case Direction.South:
+ return new[] { Direction.North, Direction.South };
+ case Direction.East:
+ case Direction.West:
+ return new[] { Direction.East, Direction.West };
+ default:
+ return Array.Empty();
+ }
+ }
+
+ public (int, int) GetOffsetForDirection(Direction direction)
+ {
+ return direction switch
+ {
+ Direction.North => (1, 0),
+ Direction.South => (-1, 0),
+ Direction.East => (0, 1),
+ Direction.West => (0, -1),
+ _ => (0, 0)
+ };
+ }
+
+ public void SpawnSpellHelper(List entityEntries, EntityCoordinates entityCoords, float? lifetime, Vector2 offsetVector2)
+ {
+ var getPrototypes = EntitySpawnCollection.GetSpawns(entityEntries, _random);
var offsetCoords = entityCoords;
- foreach (var proto in getProtos)
+ foreach (var proto in getPrototypes)
{
// TODO: Share this code with instant because they're both doing similar things for positioning.
var entity = Spawn(proto, offsetCoords);
@@ -404,8 +312,6 @@ public sealed class MagicSystem : EntitySystem
}
}
- #endregion
-
private void Speak(BaseActionEvent args)
{
if (args is not ISpeakSpell speak || string.IsNullOrWhiteSpace(speak.Speech))
@@ -414,4 +320,6 @@ public sealed class MagicSystem : EntitySystem
_chat.TrySendInGameICMessage(args.Performer, Loc.GetString(speak.Speech),
InGameICChatType.Speak, false);
}
+
+ #endregion
}
diff --git a/Content.Server/Power/EntitySystems/BatterySystem.cs b/Content.Server/Power/EntitySystems/BatterySystem.cs
index c844988b06..7971c8195d 100644
--- a/Content.Server/Power/EntitySystems/BatterySystem.cs
+++ b/Content.Server/Power/EntitySystems/BatterySystem.cs
@@ -84,8 +84,17 @@ namespace Content.Server.Power.EntitySystems
while (query.MoveNext(out var uid, out var comp, out var batt))
{
if (!comp.AutoRecharge) continue;
- if (batt.IsFullyCharged) continue;
- SetCharge(uid, batt.CurrentCharge + comp.AutoRechargeRate * frameTime, batt);
+
+ if (comp.AutoRechargeRate > 0)
+ {
+ if (batt.IsFullyCharged) continue;
+ SetCharge(uid, batt.CurrentCharge + comp.AutoRechargeRate * frameTime, batt);
+ }
+ if (comp.AutoRechargeRate < 0) //self discharging
+ {
+ if (batt.Charge == 0) continue;
+ UseCharge(uid, -comp.AutoRechargeRate * frameTime, batt);
+ }
}
}
diff --git a/Content.Server/_White/IncorporealSystem/IncorporealSystem.cs b/Content.Server/_White/IncorporealSystem/IncorporealSystem.cs
index 969f8af6ea..3231e4420b 100644
--- a/Content.Server/_White/IncorporealSystem/IncorporealSystem.cs
+++ b/Content.Server/_White/IncorporealSystem/IncorporealSystem.cs
@@ -2,6 +2,8 @@
using Content.Shared.Eye;
using Content.Shared.Movement.Systems;
using Content.Shared.Physics;
+using Content.Shared.Stealth;
+using Content.Shared.Stealth.Components;
using Robust.Server.GameObjects;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Systems;
@@ -10,11 +12,10 @@ namespace Content.Server._White.IncorporealSystem;
public sealed class IncorporealSystem : EntitySystem
{
- [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly MovementSpeedModifierSystem _movement = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly VisibilitySystem _visibilitySystem = default!;
-
+ [Dependency] private readonly SharedStealthSystem _stealth = default!;
public override void Initialize()
{
@@ -41,6 +42,9 @@ public sealed class IncorporealSystem : EntitySystem
_visibilitySystem.RefreshVisibility(uid);
}
+ Spawn("EffectEmpPulse", Transform(uid).Coordinates);
+ EnsureComp(uid);
+ _stealth.SetVisibility(uid, -1);
_movement.RefreshMovementSpeedModifiers(uid);
}
@@ -50,8 +54,8 @@ public sealed class IncorporealSystem : EntitySystem
{
var fixture = fixtures.Fixtures.First();
- _physics.SetCollisionMask(uid, fixture.Key, fixture.Value, (int) (CollisionGroup.FlyingMobMask | CollisionGroup.GhostImpassable), fixtures);
- _physics.SetCollisionLayer(uid, fixture.Key, fixture.Value, (int) CollisionGroup.FlyingMobLayer, fixtures);
+ _physics.SetCollisionMask(uid, fixture.Key, fixture.Value, (int) (CollisionGroup.MobMask | CollisionGroup.GhostImpassable), fixtures);
+ _physics.SetCollisionLayer(uid, fixture.Key, fixture.Value, (int) CollisionGroup.MobLayer, fixtures);
}
if (TryComp(uid, out var visibility))
@@ -62,6 +66,10 @@ public sealed class IncorporealSystem : EntitySystem
}
component.MovementSpeedBuff = 1;
+
+ Spawn("EffectEmpPulse", Transform(uid).Coordinates);
+ _stealth.SetVisibility(uid, 1);
+ RemComp(uid);
_movement.RefreshMovementSpeedModifiers(uid);
}
diff --git a/Content.Server/_White/Wizard/Charging/ChargingSystem.cs b/Content.Server/_White/Wizard/Charging/ChargingSystem.cs
new file mode 100644
index 0000000000..f5e8f840fe
--- /dev/null
+++ b/Content.Server/_White/Wizard/Charging/ChargingSystem.cs
@@ -0,0 +1,183 @@
+using Content.Shared._White.Wizard;
+using Content.Shared._White.Wizard.Charging;
+using Content.Shared.Follower;
+using Robust.Shared.Audio;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Player;
+
+namespace Content.Server._White.Wizard.Charging;
+
+public sealed class ChargingSystem : SharedChargingSystem
+{
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
+ [Dependency] private readonly FollowerSystem _followerSystem = default!;
+
+ private readonly Dictionary> _charges = new();
+
+ private readonly Dictionary _chargingLoops = new();
+ private readonly Dictionary _chargedLoop = new();
+
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeNetworkEvent(OnCharging);
+ SubscribeNetworkEvent(OnCharged);
+ SubscribeNetworkEvent(OnStop);
+ SubscribeLocalEvent(OnDetach);
+
+ SubscribeNetworkEvent(Add);
+ SubscribeNetworkEvent(Remove);
+ }
+
+ #region Audio
+
+ private void OnCharging(RequestSpellChargingAudio msg, EntitySessionEventArgs args)
+ {
+ var user = args.SenderSession?.AttachedEntity;
+ if (user == null)
+ return;
+
+ var shouldLoop = msg.Loop;
+ var sound = msg.Sound;
+
+ if (!shouldLoop)
+ {
+ _audio.PlayPvs(sound, user.Value);
+ return;
+ }
+
+ if (_chargingLoops.TryGetValue(user.Value, out var currentStream))
+ {
+ _audio.Stop(currentStream);
+ _chargingLoops.Remove(user.Value);
+ }
+
+ var newStream = _audio.PlayPvs(sound, user.Value, AudioParams.Default.WithLoop(true));
+
+ if (newStream.HasValue)
+ {
+ _chargingLoops[user.Value] = newStream.Value.Entity;
+ }
+ }
+
+ private void OnCharged(RequestSpellChargedAudio msg, EntitySessionEventArgs args)
+ {
+ var user = args.SenderSession?.AttachedEntity;
+ if (user == null)
+ return;
+
+ if (_chargingLoops.TryGetValue(user.Value, out var currentStream))
+ {
+ _audio.Stop(currentStream);
+ _chargingLoops.Remove(user.Value);
+ }
+
+ var shouldLoop = msg.Loop;
+ var sound = msg.Sound;
+
+ if (!shouldLoop)
+ {
+ _audio.PlayPvs(sound, user.Value);
+ return;
+ }
+
+ if (_chargedLoop.TryGetValue(user.Value, out var chargedLoop))
+ {
+ _audio.Stop(chargedLoop);
+ _chargedLoop.Remove(user.Value);
+ }
+
+ var newStream = _audio.PlayPvs(sound, user.Value, AudioParams.Default.WithLoop(true));
+
+ if (newStream.HasValue)
+ {
+ _chargedLoop[user.Value] = newStream.Value.Entity;
+ }
+ }
+
+ private void OnStop(RequestAudioSpellStop msg, EntitySessionEventArgs args)
+ {
+ var user = args.SenderSession?.AttachedEntity;
+ if (user == null)
+ return;
+
+ if (_chargingLoops.TryGetValue(user.Value, out var currentStream))
+ {
+ _audio.Stop(currentStream);
+ _chargingLoops.Remove(user.Value);
+ }
+
+ if (_chargedLoop.TryGetValue(user.Value, out var chargedLoop))
+ {
+ _audio.Stop(chargedLoop);
+ _chargedLoop.Remove(user.Value);
+ }
+ }
+
+ private void OnDetach(PlayerDetachedEvent msg, EntitySessionEventArgs args)
+ {
+ var user = msg.Entity;
+
+ if (_chargingLoops.TryGetValue(user, out var currentStream))
+ {
+ _audio.Stop(currentStream);
+ _chargingLoops.Remove(user);
+ }
+
+ if (_chargedLoop.TryGetValue(user, out var chargedLoop))
+ {
+ _audio.Stop(chargedLoop);
+ _chargedLoop.Remove(user);
+ }
+ }
+
+ #endregion
+
+ #region Charges
+
+ private void Add(AddWizardChargeEvent msg, EntitySessionEventArgs args)
+ {
+ if (args.SenderSession.AttachedEntity != null)
+ AddCharge(args.SenderSession.AttachedEntity.Value, msg.ChargeProto);
+ }
+
+ private void Remove(RemoveWizardChargeEvent msg, EntitySessionEventArgs args)
+ {
+ if (args.SenderSession.AttachedEntity != null)
+ RemoveAllCharges(args.SenderSession.AttachedEntity.Value);
+ }
+
+ #endregion
+
+ #region Helpers
+
+ public void AddCharge(EntityUid uid, string msgChargeProto)
+ {
+ var itemEnt = Spawn(msgChargeProto, Transform(uid).Coordinates);
+ _followerSystem.StartFollowingEntity(itemEnt, uid);
+
+ if (!_charges.ContainsKey(uid))
+ {
+ _charges[uid] = new List();
+ }
+
+ _charges[uid].Add(itemEnt);
+ }
+
+ public void RemoveAllCharges(EntityUid uid)
+ {
+ if (!_charges.ContainsKey(uid))
+ return;
+
+ foreach (var followerEnt in _charges[uid])
+ {
+ Del(followerEnt);
+ }
+
+ _charges.Remove(uid);
+ }
+
+ #endregion
+}
diff --git a/Content.Server/_White/Wizard/Magic/Amaterasu/AmaterasuComponent.cs b/Content.Server/_White/Wizard/Magic/Amaterasu/AmaterasuComponent.cs
new file mode 100644
index 0000000000..6cf0d7a5c6
--- /dev/null
+++ b/Content.Server/_White/Wizard/Magic/Amaterasu/AmaterasuComponent.cs
@@ -0,0 +1,6 @@
+namespace Content.Server._White.Wizard.Magic.Amaterasu;
+
+[RegisterComponent]
+public sealed partial class AmaterasuComponent : Component
+{
+}
diff --git a/Content.Server/_White/Wizard/Magic/Amaterasu/AmaterasuSystem.cs b/Content.Server/_White/Wizard/Magic/Amaterasu/AmaterasuSystem.cs
new file mode 100644
index 0000000000..6c16a7f52f
--- /dev/null
+++ b/Content.Server/_White/Wizard/Magic/Amaterasu/AmaterasuSystem.cs
@@ -0,0 +1,34 @@
+using Content.Server.Atmos.Components;
+using Content.Server.Body.Systems;
+using Content.Shared.Mobs;
+
+namespace Content.Server._White.Wizard.Magic.Amaterasu;
+
+public sealed class AmaterasuSystem : EntitySystem
+{
+ [Dependency] private readonly BodySystem _bodySystem = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnMobState);
+ }
+
+ private void OnMobState(EntityUid uid, AmaterasuComponent component, MobStateChangedEvent args)
+ {
+ if (args.NewMobState is MobState.Critical or MobState.Dead)
+ {
+ if(!TryComp(uid, out var flammable))
+ return;
+
+ if (flammable.OnFire)
+ {
+ _bodySystem.GibBody(uid);
+ return;
+ }
+
+ RemComp(uid);
+ }
+ }
+}
diff --git a/Content.Server/_White/Wizard/Magic/Other/InstantRecallComponent.cs b/Content.Server/_White/Wizard/Magic/Other/InstantRecallComponent.cs
new file mode 100644
index 0000000000..e20a7ac19e
--- /dev/null
+++ b/Content.Server/_White/Wizard/Magic/Other/InstantRecallComponent.cs
@@ -0,0 +1,7 @@
+namespace Content.Server._White.Wizard.Magic.Other;
+
+[RegisterComponent]
+public sealed partial class InstantRecallComponent : Component
+{
+ public EntityUid? Item;
+}
diff --git a/Content.Server/_White/Wizard/Magic/TeslaProjectile/TeslaProjectileComponent.cs b/Content.Server/_White/Wizard/Magic/TeslaProjectile/TeslaProjectileComponent.cs
new file mode 100644
index 0000000000..9ffce14fd6
--- /dev/null
+++ b/Content.Server/_White/Wizard/Magic/TeslaProjectile/TeslaProjectileComponent.cs
@@ -0,0 +1,4 @@
+namespace Content.Server._White.Wizard.Magic.TeslaProjectile;
+
+[RegisterComponent]
+public sealed partial class TeslaProjectileComponent : Component {}
diff --git a/Content.Server/_White/Wizard/Magic/TeslaProjectile/TeslaProjectileSystem.cs b/Content.Server/_White/Wizard/Magic/TeslaProjectile/TeslaProjectileSystem.cs
new file mode 100644
index 0000000000..44740f704d
--- /dev/null
+++ b/Content.Server/_White/Wizard/Magic/TeslaProjectile/TeslaProjectileSystem.cs
@@ -0,0 +1,21 @@
+using Content.Server.Lightning;
+using Content.Shared.Projectiles;
+
+namespace Content.Server._White.Wizard.Magic.TeslaProjectile;
+
+public sealed class TeslaProjectileSystem : EntitySystem
+{
+ [Dependency] private readonly LightningSystem _lightning = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnStartCollide);
+ }
+
+ private void OnStartCollide(Entity ent, ref ProjectileHitEvent args)
+ {
+ _lightning.ShootRandomLightnings(ent, 2, 4, arcDepth:2);
+ }
+}
diff --git a/Content.Server/_White/Wizard/Magic/WizardSpellsSystem.cs b/Content.Server/_White/Wizard/Magic/WizardSpellsSystem.cs
new file mode 100644
index 0000000000..9a4cd57b25
--- /dev/null
+++ b/Content.Server/_White/Wizard/Magic/WizardSpellsSystem.cs
@@ -0,0 +1,701 @@
+using System.Linq;
+using System.Numerics;
+using Content.Server._White.IncorporealSystem;
+using Content.Server._White.Wizard.Magic.Amaterasu;
+using Content.Server._White.Wizard.Magic.Other;
+using Content.Server.Abilities.Mime;
+using Content.Server.Administration.Commands;
+using Content.Server.Atmos.Components;
+using Content.Server.Atmos.EntitySystems;
+using Content.Server.Chat.Systems;
+using Content.Server.Emp;
+using Content.Server.Lightning;
+using Content.Server.Magic;
+using Content.Server.Singularity.EntitySystems;
+using Content.Server.Weapons.Ranged.Systems;
+using Content.Shared._White.Wizard;
+using Content.Shared._White.Wizard.Magic;
+using Content.Shared.Actions;
+using Content.Shared.Cluwne;
+using Content.Shared.Coordinates.Helpers;
+using Content.Shared.Hands.Components;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Humanoid;
+using Content.Shared.Interaction.Components;
+using Content.Shared.Inventory;
+using Content.Shared.Inventory.VirtualItem;
+using Content.Shared.Item;
+using Content.Shared.Magic;
+using Content.Shared.Maps;
+using Content.Shared.Mobs.Components;
+using Content.Shared.Physics;
+using Content.Shared.Popups;
+using Content.Shared.StatusEffect;
+using Content.Shared.Throwing;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Map;
+using Robust.Shared.Physics.Components;
+using Robust.Shared.Random;
+
+namespace Content.Server._White.Wizard.Magic;
+
+public sealed class WizardSpellsSystem : EntitySystem
+{
+ #region Dependencies
+
+ [Dependency] private readonly IMapManager _mapManager = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly EntityLookupSystem _lookup = default!;
+ [Dependency] private readonly GunSystem _gunSystem = default!;
+ [Dependency] private readonly SharedTransformSystem _transformSystem = default!;
+ [Dependency] private readonly ChatSystem _chat = default!;
+ [Dependency] private readonly LightningSystem _lightning = default!;
+ [Dependency] private readonly MagicSystem _magicSystem = default!;
+ [Dependency] private readonly GravityWellSystem _gravityWell = default!;
+ [Dependency] private readonly FlammableSystem _flammableSystem = default!;
+ [Dependency] private readonly SharedHandsSystem _handsSystem = default!;
+ [Dependency] private readonly ThrowingSystem _throwingSystem = default!;
+ [Dependency] private readonly TurfSystem _turf = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
+ [Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!;
+ [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
+ [Dependency] private readonly InventorySystem _inventory = default!;
+ [Dependency] private readonly EmpSystem _empSystem = default!;
+
+ #endregion
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnInstantRecallSpell);
+ SubscribeLocalEvent(OnMimeTouchSpell);
+ SubscribeLocalEvent(OnBananaTouchSpell);
+ SubscribeLocalEvent(OnCluwneCurseSpell);
+ SubscribeLocalEvent(OnEmpSpell);
+ SubscribeLocalEvent(OnJauntSpell);
+ SubscribeLocalEvent(OnBlinkSpell);
+ SubscribeLocalEvent(OnForcewallSpell);
+ SubscribeLocalEvent(OnCardsSpell);
+ SubscribeLocalEvent(OnFireballSpell);
+ SubscribeLocalEvent(OnForceSpell);
+ SubscribeLocalEvent(OnArcSpell);
+
+ SubscribeLocalEvent(OnBeforeCastSpell);
+ }
+
+ #region Instant Recall
+
+ private void OnInstantRecallSpell(InstantRecallSpellEvent msg)
+ {
+ if (msg.Handled || !CheckRequirements(msg.Action, msg.Performer))
+ return;
+
+ if (!TryComp(msg.Performer, out var handsComponent))
+ return;
+
+ if (!TryComp(msg.Action, out var recallComponent))
+ {
+ _popupSystem.PopupEntity("Что-то поломалось!", msg.Performer, msg.Performer);
+ return;
+ }
+
+ if (handsComponent.ActiveHandEntity != null)
+ {
+ if (HasComp(handsComponent.ActiveHandEntity.Value))
+ {
+ _popupSystem.PopupEntity("Не могу работать с этим!", msg.Performer, msg.Performer);
+ return;
+ }
+
+ recallComponent.Item = handsComponent.ActiveHandEntity.Value;
+ _popupSystem.PopupEntity($"Сопряжено с {MetaData(handsComponent.ActiveHandEntity.Value).EntityName}", msg.Performer, msg.Performer);
+ return;
+ }
+
+ if (handsComponent.ActiveHandEntity == null && recallComponent.Item != null)
+ {
+ var coordsItem = Transform(recallComponent.Item.Value).Coordinates;
+ var coordsPerformer = Transform(msg.Performer).Coordinates;
+
+ Spawn("EffectEmpPulse", coordsItem);
+
+ _transformSystem.SetCoordinates(recallComponent.Item.Value, coordsPerformer);
+ _transformSystem.AttachToGridOrMap(recallComponent.Item.Value);
+
+ _handsSystem.TryForcePickupAnyHand(msg.Performer, recallComponent.Item.Value);
+
+ msg.Handled = true;
+ return;
+ }
+
+ _popupSystem.PopupEntity("Нет привязки.", msg.Performer, msg.Performer);
+ }
+
+ #endregion
+
+ #region Mime Touch
+
+ private void OnMimeTouchSpell(MimeTouchSpellEvent msg)
+ {
+ if (msg.Handled || !CheckRequirements(msg.Action, msg.Performer))
+ return;
+
+ if (!HasComp(msg.Target))
+ {
+ _popupSystem.PopupEntity("Работает только на людях!", msg.Performer, msg.Performer);
+ return;
+ }
+
+ SetOutfitCommand.SetOutfit(msg.Target, "MimeGear", EntityManager);
+ EnsureComp(msg.Target);
+
+ Spawn("AdminInstantEffectSmoke3", Transform(msg.Target).Coordinates);
+
+ msg.Handled = true;
+ Speak(msg);
+ }
+
+ #endregion
+
+ #region Banana Touch
+
+ private void OnBananaTouchSpell(BananaTouchSpellEvent msg)
+ {
+ if (msg.Handled || !CheckRequirements(msg.Action, msg.Performer))
+ return;
+
+ if (!HasComp(msg.Target))
+ {
+ _popupSystem.PopupEntity("Работает только на людях!", msg.Performer, msg.Performer);
+ return;
+ }
+
+ SetOutfitCommand.SetOutfit(msg.Target, "ClownGear", EntityManager);
+ EnsureComp(msg.Target);
+
+ Spawn("AdminInstantEffectSmoke3", Transform(msg.Target).Coordinates);
+
+ msg.Handled = true;
+ Speak(msg);
+ }
+
+ #endregion
+
+ #region Cluwne Curse
+
+ private void OnCluwneCurseSpell(CluwneCurseSpellEvent msg)
+ {
+ if (msg.Handled || !CheckRequirements(msg.Action, msg.Performer))
+ return;
+
+ if (!HasComp(msg.Target))
+ {
+ _popupSystem.PopupEntity("Работает только на людях!", msg.Performer, msg.Performer);
+ return;
+ }
+
+ EnsureComp(msg.Target);
+
+ Spawn("AdminInstantEffectSmoke3", Transform(msg.Target).Coordinates);
+
+ msg.Handled = true;
+ Speak(msg);
+ }
+
+ #endregion
+
+ #region EMP
+
+ private void OnEmpSpell(EmpSpellEvent msg)
+ {
+ if (msg.Handled || !CheckRequirements(msg.Action, msg.Performer))
+ return;
+
+ var coords = _transformSystem.ToMapCoordinates(Transform(msg.Performer).Coordinates);
+
+ _empSystem.EmpPulse(coords, 15, 1000000, 60f);
+
+ msg.Handled = true;
+ Speak(msg);
+ }
+
+ #endregion
+
+ #region Ethereal Jaunt
+
+ private void OnJauntSpell(EtherealJauntSpellEvent msg)
+ {
+ if (msg.Handled || !CheckRequirements(msg.Action, msg.Performer))
+ return;
+
+ if (_statusEffectsSystem.HasStatusEffect(msg.Performer, "Incorporeal"))
+ {
+ _popupSystem.PopupEntity("Вы уже в потустороннем мире", msg.Performer, msg.Performer);
+ return;
+ }
+
+ Spawn("AdminInstantEffectSmoke10", Transform(msg.Performer).Coordinates);
+
+ _statusEffectsSystem.TryAddStatusEffect(msg.Performer, "Incorporeal", TimeSpan.FromSeconds(10), false);
+
+ msg.Handled = true;
+ Speak(msg);
+ }
+
+ #endregion
+
+ #region Blink
+
+ private void OnBlinkSpell(BlinkSpellEvent msg)
+ {
+ if (msg.Handled || !CheckRequirements(msg.Action, msg.Performer))
+ return;
+
+ var transform = Transform(msg.Performer);
+
+ var oldCoords = transform.Coordinates;
+
+ EntityCoordinates coords = default;
+ var foundTeleportPos = false;
+ var attempts = 10;
+
+ while (attempts > 0)
+ {
+ attempts--;
+
+ var random = new Random().Next(10, 20);
+ var offset = transform.LocalRotation.ToWorldVec().Normalized();
+ var direction = transform.LocalRotation.GetDir().ToVec();
+ var newOffset = offset + direction * random;
+ coords = transform.Coordinates.Offset(newOffset).SnapToGrid(EntityManager);
+
+ var tile = coords.GetTileRef();
+
+ if (tile != null && _turf.IsTileBlocked(tile.Value, CollisionGroup.AllMask))
+ continue;
+
+ foundTeleportPos = true;
+ break;
+ }
+
+ if (!foundTeleportPos)
+ return;
+
+ _transformSystem.SetCoordinates(msg.Performer, coords);
+ _transformSystem.AttachToGridOrMap(msg.Performer);
+
+ _audio.PlayPvs("/Audio/White/Cult/veilin.ogg", coords);
+ _audio.PlayPvs("/Audio/White/Cult/veilout.ogg", oldCoords);
+
+ Spawn("AdminInstantEffectSmoke10", oldCoords);
+ Spawn("AdminInstantEffectSmoke10", coords);
+
+ msg.Handled = true;
+ Speak(msg);
+ }
+
+ #endregion
+
+ #region Forcewall
+
+ private void OnForcewallSpell(ForceWallSpellEvent msg)
+ {
+ if (msg.Handled || !CheckRequirements(msg.Action, msg.Performer))
+ return;
+
+ switch (msg.ActionUseType)
+ {
+ case ActionUseType.Default:
+ ForcewallSpellDefault(msg);
+ break;
+ case ActionUseType.Charge:
+ ForcewallSpellCharge(msg);
+ break;
+ case ActionUseType.AltUse:
+ ForcewallSpellAlt(msg);
+ break;
+ }
+
+ msg.Handled = true;
+ Speak(msg);
+ }
+
+ private void ForcewallSpellDefault(ForceWallSpellEvent msg)
+ {
+ var transform = Transform(msg.Performer);
+
+ foreach (var position in _magicSystem.GetPositionsInFront(transform))
+ {
+ var ent = Spawn(msg.Prototype, position.SnapToGrid(EntityManager, _mapManager));
+
+ var comp = EnsureComp(ent);
+ comp.Uid = msg.Performer;
+ }
+ }
+
+ private void ForcewallSpellCharge(ForceWallSpellEvent msg)
+ {
+ var xform = Transform(msg.Performer);
+
+ var positions = GetArenaPositions(xform, msg.ChargeLevel);
+
+ foreach (var position in positions)
+ {
+ var ent = Spawn(msg.Prototype, position);
+
+ var comp = EnsureComp(ent);
+ comp.Uid = msg.Performer;
+ }
+ }
+
+ private void ForcewallSpellAlt(ForceWallSpellEvent msg)
+ {
+ var xform = Transform(msg.TargetUid);
+
+ var positions = GetArenaPositions(xform, 2);
+
+ foreach (var direction in positions)
+ {
+ var ent = Spawn(msg.Prototype, direction);
+
+ var comp = EnsureComp(ent);
+ comp.Uid = msg.Performer;
+ }
+ }
+
+ #endregion
+
+ #region Cards
+
+ private void OnCardsSpell(CardsSpellEvent msg)
+ {
+ if (msg.Handled || !CheckRequirements(msg.Action, msg.Performer))
+ return;
+
+ switch (msg.ActionUseType)
+ {
+ case ActionUseType.Default:
+ CardsSpellDefault(msg);
+ break;
+ case ActionUseType.Charge:
+ CardsSpellCharge(msg);
+ break;
+ case ActionUseType.AltUse:
+ CardsSpellAlt(msg);
+ break;
+ }
+
+ msg.Handled = true;
+ Speak(msg);
+ }
+
+ private void CardsSpellDefault(CardsSpellEvent msg)
+ {
+ var xform = Transform(msg.Performer);
+
+ for (var i = 0; i < 10; i++)
+ {
+ foreach (var pos in _magicSystem.GetSpawnPositions(xform, msg.Pos))
+ {
+ var mapPos = _transformSystem.ToMapCoordinates(pos);
+ var spawnCoords = _mapManager.TryFindGridAt(mapPos, out var gridUid, out _)
+ ? pos.WithEntityId(gridUid, EntityManager)
+ : new EntityCoordinates(_mapManager.GetMapEntityId(mapPos.MapId), mapPos.Position);
+
+ var ent = Spawn(msg.Prototype, spawnCoords);
+
+ var direction = msg.Target.ToMapPos(EntityManager, _transformSystem) - spawnCoords.ToMapPos(EntityManager, _transformSystem);
+ var randomizedDirection = direction + new Vector2(_random.Next(-2, 2), _random.Next(-2, 2));
+
+ _throwingSystem.TryThrow(ent, randomizedDirection, 60, msg.Performer);
+ }
+ }
+ }
+
+ private void CardsSpellCharge(CardsSpellEvent msg)
+ {
+ var xform = Transform(msg.Performer);
+
+ var count = 5 * msg.ChargeLevel;
+ var angleStep = 360f / count;
+
+ for (var i = 0; i < count; i++)
+ {
+ var angle = i * angleStep;
+
+ var direction = new Vector2(MathF.Cos(MathHelper.DegreesToRadians(angle)), MathF.Sin(MathHelper.DegreesToRadians(angle)));
+
+ foreach (var pos in _magicSystem.GetSpawnPositions(xform, msg.Pos))
+ {
+ var mapPos = _transformSystem.ToMapCoordinates(pos);
+
+ var spawnCoords = _mapManager.TryFindGridAt(mapPos, out var gridUid, out _)
+ ? pos.WithEntityId(gridUid, EntityManager)
+ : new EntityCoordinates(_mapManager.GetMapEntityId(mapPos.MapId), mapPos.Position);
+
+ var ent = Spawn(msg.Prototype, spawnCoords);
+
+ _throwingSystem.TryThrow(ent, direction, 60, msg.Performer);
+ }
+ }
+ }
+
+ private void CardsSpellAlt(CardsSpellEvent msg)
+ {
+ if (!HasComp(msg.TargetUid))
+ return;
+
+ Del(msg.TargetUid);
+ var item = Spawn(msg.Prototype);
+ _handsSystem.TryPickupAnyHand(msg.Performer, item);
+ }
+
+ #endregion
+
+ #region Fireball
+
+ private void OnFireballSpell(FireballSpellEvent msg)
+ {
+ if (msg.Handled || !CheckRequirements(msg.Action, msg.Performer))
+ return;
+
+ switch (msg.ActionUseType)
+ {
+ case ActionUseType.Default:
+ FireballSpellDefault(msg);
+ break;
+ case ActionUseType.Charge:
+ FireballSpellCharge(msg);
+ break;
+ case ActionUseType.AltUse:
+ FireballSpellAlt(msg);
+ break;
+ }
+
+ msg.Handled = true;
+ Speak(msg);
+ }
+
+ private void FireballSpellDefault(FireballSpellEvent msg)
+ {
+ var xform = Transform(msg.Performer);
+
+ foreach (var pos in _magicSystem.GetSpawnPositions(xform, msg.Pos))
+ {
+ var mapPos = _transformSystem.ToMapCoordinates(pos);
+ var spawnCoords = _mapManager.TryFindGridAt(mapPos, out var gridUid, out var grid)
+ ? pos.WithEntityId(gridUid, EntityManager)
+ : new EntityCoordinates(_mapManager.GetMapEntityId(mapPos.MapId), mapPos.Position);
+
+ var userVelocity = Vector2.Zero;
+
+ if (grid != null && TryComp(gridUid, out PhysicsComponent? physics))
+ userVelocity = physics.LinearVelocity;
+
+ var ent = Spawn(msg.Prototype, spawnCoords);
+ var direction = msg.Target.ToMapPos(EntityManager, _transformSystem) - spawnCoords.ToMapPos(EntityManager, _transformSystem);
+ _gunSystem.ShootProjectile(ent, direction, userVelocity, msg.Performer, msg.Performer);
+ }
+ }
+
+ private void FireballSpellCharge(FireballSpellEvent msg)
+ {
+ var coords = Transform(msg.Performer).Coordinates;
+
+ var targets = _lookup.GetEntitiesInRange(coords, 2 * msg.ChargeLevel);
+
+ foreach (var target in targets.Where(target => target.Owner != msg.Performer))
+ {
+ target.Comp.FireStacks += 3;
+ _flammableSystem.Ignite(target, msg.Performer);
+ }
+ }
+
+ private void FireballSpellAlt(FireballSpellEvent msg)
+ {
+ if (!TryComp(msg.TargetUid, out var flammableComponent))
+ return;
+
+ flammableComponent.FireStacks += 4;
+
+ _flammableSystem.Ignite(msg.TargetUid, msg.Performer);
+
+ EnsureComp(msg.TargetUid);
+ }
+
+ #endregion
+
+ #region Force
+
+ private void OnForceSpell(ForceSpellEvent msg)
+ {
+ if (msg.Handled || !CheckRequirements(msg.Action, msg.Performer))
+ return;
+
+ switch (msg.ActionUseType)
+ {
+ case ActionUseType.Default:
+ ForceSpellDefault(msg);
+ break;
+ case ActionUseType.Charge:
+ ForceSpellCharge(msg);
+ break;
+ case ActionUseType.AltUse:
+ ForceSpellAlt(msg);
+ break;
+ }
+
+ msg.Handled = true;
+ Speak(msg);
+ }
+
+ private void ForceSpellDefault(ForceSpellEvent msg)
+ {
+ Spawn("AdminInstantEffectMinusGravityWell", msg.Target);
+ }
+
+ private void ForceSpellCharge(ForceSpellEvent msg)
+ {
+ _gravityWell.GravPulse(msg.Performer, 15, 0, -80 * msg.ChargeLevel, -2 * msg.ChargeLevel);
+ }
+
+ private void ForceSpellAlt(ForceSpellEvent msg)
+ {
+ _gravityWell.GravPulse(msg.Target, 10, 0, 200, 10);
+ }
+
+ #endregion
+
+ #region Arc
+
+ private void OnArcSpell(ArcSpellEvent msg)
+ {
+ if (msg.Handled || !CheckRequirements(msg.Action, msg.Performer))
+ return;
+
+ switch (msg.ActionUseType)
+ {
+ case ActionUseType.Default:
+ ArcSpellDefault(msg);
+ break;
+ case ActionUseType.Charge:
+ ArcSpellCharge(msg);
+ break;
+ case ActionUseType.AltUse:
+ ArcSpellAlt(msg);
+ break;
+ }
+
+ msg.Handled = true;
+ Speak(msg);
+ }
+
+ private void ArcSpellDefault(ArcSpellEvent msg)
+ {
+ const int possibleEntitiesCount = 2;
+
+ var entitiesInRange = _lookup.GetEntitiesInRange(msg.Target, 1);
+ var entitiesToHit = entitiesInRange.Where(HasComp).Take(possibleEntitiesCount);
+
+ foreach (var entity in entitiesToHit)
+ {
+ _lightning.ShootLightning(msg.Performer, entity);
+ }
+ }
+
+ private void ArcSpellCharge(ArcSpellEvent msg)
+ {
+ _lightning.ShootRandomLightnings(msg.Performer, 2 * msg.ChargeLevel, msg.ChargeLevel * 2, arcDepth: 2);
+ }
+
+ private void ArcSpellAlt(ArcSpellEvent msg)
+ {
+ var xform = Transform(msg.Performer);
+
+ foreach (var pos in _magicSystem.GetSpawnPositions(xform, msg.Pos))
+ {
+ var mapPos = _transformSystem.ToMapCoordinates(pos);
+ var spawnCoords = _mapManager.TryFindGridAt(mapPos, out var gridUid, out var grid)
+ ? pos.WithEntityId(gridUid, EntityManager)
+ : new EntityCoordinates(_mapManager.GetMapEntityId(mapPos.MapId), mapPos.Position);
+
+ var userVelocity = Vector2.Zero;
+
+ if (grid != null && TryComp(gridUid, out PhysicsComponent? physics))
+ userVelocity = physics.LinearVelocity;
+
+ var ent = Spawn(msg.Prototype, spawnCoords);
+ var direction = msg.Target.ToMapPos(EntityManager, _transformSystem) - spawnCoords.ToMapPos(EntityManager, _transformSystem);
+ _gunSystem.ShootProjectile(ent, direction, userVelocity, msg.Performer, msg.Performer);
+ }
+ }
+
+ #endregion
+
+ #region Helpers
+
+ private void Speak(BaseActionEvent args)
+ {
+ if (args is not ISpeakSpell speak || string.IsNullOrWhiteSpace(speak.Speech))
+ return;
+
+ _chat.TrySendInGameICMessage(args.Performer, Loc.GetString(speak.Speech),
+ InGameICChatType.Speak, false);
+ }
+
+ private List GetArenaPositions(TransformComponent casterXform, int arenaSize)
+ {
+ var positions = new List();
+
+ arenaSize--;
+
+ for (var i = -arenaSize; i <= arenaSize; i++)
+ {
+ for (var j = -arenaSize; j <= arenaSize; j++)
+ {
+ var position = new Vector2(i, j);
+ var coordinates = casterXform.Coordinates.Offset(position);
+ positions.Add(coordinates);
+ }
+ }
+
+ return positions;
+ }
+
+ private bool CheckRequirements(EntityUid spell, EntityUid performer)
+ {
+ var ev = new BeforeCastSpellEvent(performer);
+ RaiseLocalEvent(spell, ref ev);
+ return !ev.Cancelled;
+ }
+
+ private void OnBeforeCastSpell(Entity ent, ref BeforeCastSpellEvent args)
+ {
+ var comp = ent.Comp;
+ var hasReqs = false;
+
+ if (comp.RequiresClothes)
+ {
+ var enumerator = _inventory.GetSlotEnumerator(args.Performer, SlotFlags.OUTERCLOTHING | SlotFlags.HEAD);
+ while (enumerator.MoveNext(out var containerSlot))
+ {
+ if (containerSlot.ContainedEntity is { } item)
+ hasReqs = HasComp(item);
+ else
+ hasReqs = false;
+
+ if (!hasReqs)
+ break;
+ }
+ }
+
+ if (!hasReqs)
+ {
+ args.Cancelled = true;
+ _popupSystem.PopupEntity("Missing Requirements! You need to wear your robe and hat!", args.Performer, args.Performer);
+ }
+ }
+
+ #endregion
+}
diff --git a/Content.Server/_White/Wizard/Scrolls/ScrollSystem.cs b/Content.Server/_White/Wizard/Scrolls/ScrollSystem.cs
new file mode 100644
index 0000000000..a1e1a33d27
--- /dev/null
+++ b/Content.Server/_White/Wizard/Scrolls/ScrollSystem.cs
@@ -0,0 +1,8 @@
+using Content.Shared._White.Wizard.ScrollSystem;
+
+namespace Content.Server._White.Wizard.Scrolls;
+
+public sealed class ScrollSystem : SharedScrollSystem
+{
+ protected override void BurnScroll(EntityUid uid) => Del(uid);
+}
diff --git a/Content.Shared/Actions/ActionEvents.cs b/Content.Shared/Actions/ActionEvents.cs
index 4715f03c70..a256167410 100644
--- a/Content.Shared/Actions/ActionEvents.cs
+++ b/Content.Shared/Actions/ActionEvents.cs
@@ -81,8 +81,10 @@ public sealed class GetItemActionsEvent : EntityEventArgs
public sealed class RequestPerformActionEvent : EntityEventArgs
{
public readonly NetEntity Action;
- public readonly NetEntity? EntityTarget;
+ public NetEntity? EntityTarget;
public readonly NetCoordinates? EntityCoordinatesTarget;
+ public ActionUseType ActionUseType = ActionUseType.Default;
+ public int ChargeLevel;
public RequestPerformActionEvent(NetEntity action)
{
@@ -148,6 +150,8 @@ public abstract partial class WorldTargetActionEvent : BaseActionEvent
/// The coordinates of the location that the user targeted.
///
public EntityCoordinates Target;
+
+ public EntityUid TargetUid;
}
///
@@ -161,4 +165,18 @@ public abstract partial class BaseActionEvent : HandledEntityEventArgs
/// The user performing the action.
///
public EntityUid Performer;
+
+ public EntityUid Action;
+
+ public ActionUseType ActionUseType = ActionUseType.Default;
+
+ public int ChargeLevel;
+}
+
+[Serializable, NetSerializable]
+public enum ActionUseType
+{
+ Default, // left mouse click.
+ Charge, // Holding right mouse click(has 4 charges).
+ AltUse // Alt + left mouse click.
}
diff --git a/Content.Shared/Actions/BaseTargetActionComponent.cs b/Content.Shared/Actions/BaseTargetActionComponent.cs
index 7e40b10c32..b96ac9ea38 100644
--- a/Content.Shared/Actions/BaseTargetActionComponent.cs
+++ b/Content.Shared/Actions/BaseTargetActionComponent.cs
@@ -1,4 +1,5 @@
using Content.Shared.Interaction;
+using Robust.Shared.Audio;
namespace Content.Shared.Actions;
@@ -40,4 +41,28 @@ public abstract partial class BaseTargetActionComponent : BaseActionComponent
/// over lay in place of the currently held item "held item".
///
[DataField("targetingIndicator")] public bool TargetingIndicator = true;
+
+ [DataField]
+ public bool IsAltEnabled;
+
+ [DataField]
+ public bool IsChargeEnabled;
+
+ [DataField]
+ public string ChargeProto = "MagicFollowerEntity";
+
+ [DataField]
+ public int MaxChargeLevel = 4;
+
+ [DataField]
+ public SoundSpecifier ChargingSound = new SoundPathSpecifier("/Audio/White/Magic/chargingfallback.ogg");
+
+ [DataField]
+ public bool LoopCharging = true;
+
+ [DataField]
+ public SoundSpecifier MaxChargedSound = new SoundPathSpecifier("/Audio/White/Magic/maxchargefallback.ogg");
+
+ [DataField]
+ public bool LoopMaxCharged;
}
diff --git a/Content.Shared/Actions/SharedActionsSystem.cs b/Content.Shared/Actions/SharedActionsSystem.cs
index df469f4ac1..dbe3fc8673 100644
--- a/Content.Shared/Actions/SharedActionsSystem.cs
+++ b/Content.Shared/Actions/SharedActionsSystem.cs
@@ -413,6 +413,8 @@ public abstract class SharedActionsSystem : EntitySystem
if (worldAction.Event != null)
{
worldAction.Event.Target = entityCoordinatesTarget;
+ if (ev.EntityTarget != null)
+ worldAction.Event.TargetUid = GetEntity(ev.EntityTarget.Value);
Dirty(actionEnt, worldAction);
performEvent = worldAction.Event;
}
@@ -430,7 +432,12 @@ public abstract class SharedActionsSystem : EntitySystem
}
if (performEvent != null)
+ {
performEvent.Performer = user;
+ performEvent.Action = actionEnt;
+ performEvent.ActionUseType = ev.ActionUseType;
+ performEvent.ChargeLevel = ev.ChargeLevel;
+ }
// All checks passed. Perform the action!
PerformAction(user, component, actionEnt, action, performEvent, curTime);
@@ -677,6 +684,8 @@ public abstract class SharedActionsSystem : EntitySystem
/// Entity to receive the actions
/// The actions to add
/// The entity that enables these actions (e.g., flashlight). May be null (innate actions).
+ /// ActionsComponent.
+ /// ActionContainerComponent.
public void GrantActions(EntityUid performer, IEnumerable actions, EntityUid container, ActionsComponent? comp = null, ActionsContainerComponent? containerComp = null)
{
if (!Resolve(container, ref containerComp))
diff --git a/Content.Shared/_White/Wizard/Charging/SharedChargingSystem.cs b/Content.Shared/_White/Wizard/Charging/SharedChargingSystem.cs
new file mode 100644
index 0000000000..2f5984bd4c
--- /dev/null
+++ b/Content.Shared/_White/Wizard/Charging/SharedChargingSystem.cs
@@ -0,0 +1,5 @@
+namespace Content.Shared._White.Wizard.Charging;
+
+public abstract class SharedChargingSystem : EntitySystem
+{
+}
diff --git a/Content.Shared/_White/Wizard/Magic/MagicComponent.cs b/Content.Shared/_White/Wizard/Magic/MagicComponent.cs
new file mode 100644
index 0000000000..a43001ca53
--- /dev/null
+++ b/Content.Shared/_White/Wizard/Magic/MagicComponent.cs
@@ -0,0 +1,13 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared._White.Wizard.Magic;
+
+[RegisterComponent, NetworkedComponent]
+public sealed partial class MagicComponent : Component
+{
+ ///
+ /// Does this spell require Wizard Robes & Hat?
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public bool RequiresClothes;
+}
diff --git a/Content.Shared/_White/Wizard/Magic/WizardClothesComponent.cs b/Content.Shared/_White/Wizard/Magic/WizardClothesComponent.cs
new file mode 100644
index 0000000000..13283f849d
--- /dev/null
+++ b/Content.Shared/_White/Wizard/Magic/WizardClothesComponent.cs
@@ -0,0 +1,9 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared._White.Wizard.Magic;
+
+[RegisterComponent, NetworkedComponent]
+public sealed partial class WizardClothesComponent : Component
+{
+
+}
diff --git a/Content.Shared/_White/Wizard/ScrollSystem/ScrollComponent.cs b/Content.Shared/_White/Wizard/ScrollSystem/ScrollComponent.cs
new file mode 100644
index 0000000000..f14576519b
--- /dev/null
+++ b/Content.Shared/_White/Wizard/ScrollSystem/ScrollComponent.cs
@@ -0,0 +1,43 @@
+using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared._White.Wizard.ScrollSystem;
+
+[RegisterComponent, NetworkedComponent, Access(typeof(SharedScrollSystem))]
+public sealed partial class ScrollComponent : Component
+{
+ ///
+ /// ActionId to give on use.
+ ///
+ [DataField]
+ [ViewVariables]
+ public string ActionId;
+
+ ///
+ /// How time it takes to learn.
+ ///
+ [DataField]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public float LearnTime = 5f;
+
+ ///
+ /// Popup on learn.
+ ///
+ [DataField]
+ [ViewVariables]
+ public string LearnPopup;
+
+ ///
+ /// Sound to play on use.
+ ///
+ [DataField]
+ [ViewVariables]
+ public SoundSpecifier UseSound;
+
+ ///
+ /// Sound to play after use.
+ ///
+ [DataField]
+ [ViewVariables]
+ public SoundSpecifier AfterUseSound;
+}
diff --git a/Content.Shared/_White/Wizard/ScrollSystem/SharedScrollSystem.cs b/Content.Shared/_White/Wizard/ScrollSystem/SharedScrollSystem.cs
new file mode 100644
index 0000000000..6d267eaf17
--- /dev/null
+++ b/Content.Shared/_White/Wizard/ScrollSystem/SharedScrollSystem.cs
@@ -0,0 +1,87 @@
+using Content.Shared.Actions;
+using Content.Shared.DoAfter;
+using Content.Shared.Interaction.Events;
+using Content.Shared.Popups;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Network;
+
+namespace Content.Shared._White.Wizard.ScrollSystem;
+
+public abstract class SharedScrollSystem : EntitySystem
+{
+ #region Dependencies
+
+ [Dependency] private readonly INetManager _net = default!;
+ [Dependency] private readonly SharedAudioSystem _audioSystem = default!;
+ [Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
+
+ #endregion
+
+ #region Init
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnScrollUse);
+ SubscribeLocalEvent(OnScrollDoAfter);
+ }
+
+ #endregion
+
+ #region Handlers
+
+ private void OnScrollUse(EntityUid uid, ScrollComponent component, UseInHandEvent args)
+ {
+ if (args.Handled)
+ return;
+
+ var doAfterEventArgs = new DoAfterArgs(EntityManager, args.User, component.LearnTime, new ScrollDoAfterEvent(), uid, target: uid)
+ {
+ BreakOnTargetMove = true,
+ BreakOnUserMove = true,
+ BreakOnDamage = true,
+ NeedHand = true
+ };
+
+ if (_net.IsServer)
+ {
+ _audioSystem.PlayPvs(component.UseSound, args.User);
+ }
+
+ _popupSystem.PopupClient($"You start learning about {component.LearnPopup}.", args.User, args.User, PopupType.Medium);
+
+ _doAfterSystem.TryStartDoAfter(doAfterEventArgs);
+
+ args.Handled = true;
+ }
+
+ private void OnScrollDoAfter(EntityUid uid, ScrollComponent component, ScrollDoAfterEvent args)
+ {
+ if (args.Handled || args.Cancelled)
+ return;
+
+ _actionsSystem.AddAction(args.User, component.ActionId);
+
+ if (_net.IsServer)
+ {
+ _audioSystem.PlayEntity(component.AfterUseSound, args.User, args.User);
+ }
+
+ _popupSystem.PopupClient($"You learned much about {component.LearnPopup}. The scroll is slowly burning in your hands.", args.User, args.User, PopupType.Medium);
+
+ BurnScroll(uid);
+
+ args.Handled = true;
+ }
+
+ #endregion
+
+ #region Helpers
+
+ protected virtual void BurnScroll(EntityUid uid) {}
+
+ #endregion
+}
diff --git a/Content.Shared/_White/Wizard/WizardEvents.cs b/Content.Shared/_White/Wizard/WizardEvents.cs
new file mode 100644
index 0000000000..c5d6d7e055
--- /dev/null
+++ b/Content.Shared/_White/Wizard/WizardEvents.cs
@@ -0,0 +1,175 @@
+using Content.Shared.Actions;
+using Content.Shared.DoAfter;
+using Content.Shared.Magic;
+using Robust.Shared.Audio;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+
+namespace Content.Shared._White.Wizard;
+
+#region HelperEvents
+
+[Serializable, NetSerializable]
+public sealed partial class ScrollDoAfterEvent : SimpleDoAfterEvent
+{
+}
+
+[ByRefEvent]
+public struct BeforeCastSpellEvent
+{
+ public EntityUid Performer;
+
+ public bool Cancelled;
+
+ public BeforeCastSpellEvent(EntityUid performer)
+ {
+ Performer = performer;
+ }
+}
+
+[Serializable, NetSerializable]
+public sealed partial class AddWizardChargeEvent : EntityEventArgs
+{
+ public string ChargeProto;
+
+ public AddWizardChargeEvent(string chargeProto)
+ {
+ ChargeProto = chargeProto;
+ }
+}
+
+[Serializable, NetSerializable]
+public sealed partial class RemoveWizardChargeEvent : EntityEventArgs
+{
+}
+
+[Serializable, NetSerializable]
+public sealed partial class RequestSpellChargingAudio : EntityEventArgs
+{
+ public SoundSpecifier Sound;
+ public bool Loop;
+
+ public RequestSpellChargingAudio(SoundSpecifier sound, bool loop)
+ {
+ Sound = sound;
+ Loop = loop;
+ }
+}
+
+[Serializable, NetSerializable]
+public sealed partial class RequestSpellChargedAudio : EntityEventArgs
+{
+ public SoundSpecifier Sound;
+ public bool Loop;
+
+ public RequestSpellChargedAudio(SoundSpecifier sound, bool loop)
+ {
+ Sound = sound;
+ Loop = loop;
+ }
+}
+
+[Serializable, NetSerializable]
+public sealed partial class RequestAudioSpellStop : EntityEventArgs
+{
+}
+
+#endregion
+
+#region Spells
+
+public sealed partial class ArcSpellEvent : WorldTargetActionEvent, ISpeakSpell
+{
+ [DataField("prototype", required: true, customTypeSerializer: typeof(PrototypeIdSerializer))]
+ public string Prototype = default!;
+
+ [DataField("posData")]
+ public MagicSpawnData Pos = new TargetCasterPos();
+
+ [DataField("speech")]
+ public string? Speech { get; private set; }
+}
+
+public sealed partial class ForceSpellEvent : WorldTargetActionEvent, ISpeakSpell
+{
+ [DataField("speech")]
+ public string? Speech { get; private set; }
+}
+
+public sealed partial class FireballSpellEvent : WorldTargetActionEvent, ISpeakSpell
+{
+ [DataField("prototype", required: true, customTypeSerializer: typeof(PrototypeIdSerializer))]
+ public string Prototype = default!;
+
+ [DataField("posData")]
+ public MagicSpawnData Pos = new TargetCasterPos();
+
+ [DataField("speech")]
+ public string? Speech { get; private set; }
+}
+
+public sealed partial class CardsSpellEvent : WorldTargetActionEvent, ISpeakSpell
+{
+ [DataField("prototype", required: true, customTypeSerializer: typeof(PrototypeIdSerializer))]
+ public string Prototype = default!;
+
+ [DataField("posData")]
+ public MagicSpawnData Pos = new TargetCasterPos();
+
+ [DataField("speech")]
+ public string? Speech { get; private set; }
+}
+
+public sealed partial class ForceWallSpellEvent : WorldTargetActionEvent, ISpeakSpell
+{
+ [DataField("prototype", required: true, customTypeSerializer: typeof(PrototypeIdSerializer))]
+ public string Prototype = default!;
+
+ [DataField("speech")]
+ public string? Speech { get; private set; }
+}
+
+public sealed partial class BlinkSpellEvent : InstantActionEvent, ISpeakSpell
+{
+ [DataField("speech")]
+ public string? Speech { get; private set; }
+}
+
+public sealed partial class EtherealJauntSpellEvent : InstantActionEvent, ISpeakSpell
+{
+ [DataField("speech")]
+ public string? Speech { get; private set; }
+}
+
+public sealed partial class EmpSpellEvent : InstantActionEvent, ISpeakSpell
+{
+ [DataField("speech")]
+ public string? Speech { get; private set; }
+}
+
+public sealed partial class CluwneCurseSpellEvent : EntityTargetActionEvent, ISpeakSpell
+{
+ [DataField("speech")]
+ public string? Speech { get; private set; }
+}
+
+public sealed partial class BananaTouchSpellEvent : EntityTargetActionEvent, ISpeakSpell
+{
+ [DataField("speech")]
+ public string? Speech { get; private set; }
+}
+
+public sealed partial class MimeTouchSpellEvent : EntityTargetActionEvent, ISpeakSpell
+{
+ [DataField("speech")]
+ public string? Speech { get; private set; }
+}
+
+public sealed partial class InstantRecallSpellEvent : InstantActionEvent, ISpeakSpell
+{
+ [DataField("speech")]
+ public string? Speech { get; private set; }
+}
+
+#endregion
diff --git a/Resources/Audio/Machines/attributions.yml b/Resources/Audio/Machines/attributions.yml
index a0f1c9f7e7..5ef62f6d5d 100644
--- a/Resources/Audio/Machines/attributions.yml
+++ b/Resources/Audio/Machines/attributions.yml
@@ -152,3 +152,23 @@
license: "CC0-1.0"
copyright: "dakamakat on freesound.org"
source: "https://freesound.org/people/Dakamakat/sounds/717370/"
+
+- files: ["energyshield_up.ogg"]
+ license: "CC0-1.0"
+ copyright: "unfa on freesound.org"
+ source: "https://freesound.org/people/unfa/sounds/584173/"
+
+- files: ["energyshield_down.ogg"]
+ license: "CC-BY-4.0"
+ copyright: "SilverIllusionist on freesound.org"
+ source: "https://freesound.org/people/SilverIllusionist/sounds/673556/"
+
+- files: ["energyshield_ambient.ogg"]
+ license: "CC0-1.0"
+ copyright: "julianmateo_ on freesound.org"
+ source: "https://freesound.org/people/julianmateo_/sounds/524165/"
+
+- files: ["energyshield_parry.ogg"]
+ license: "CC-BY-4.0"
+ copyright: "Robinhood76 on freesound.org"
+ source: "https://freesound.org/people/Robinhood76/sounds/107613/"
\ No newline at end of file
diff --git a/Resources/Audio/Machines/energyshield_ambient.ogg b/Resources/Audio/Machines/energyshield_ambient.ogg
new file mode 100644
index 0000000000000000000000000000000000000000..ea560a076ba8c8186d3a7101ebe893ed70c46da1
GIT binary patch
literal 43605
zcmagFbwE^K&^LZ|S!xOC29c7~MOiwQ?q*5pkOn0TL@te>h=@oE(jW*ZW#OWP0@96y
zMTvxoNc`?De4qDu|9OAc%X;_RbLXBjGiT;Ab7oD?p0xx>aCqPlQe4DK?sc8l2tGu3
z(0MnX5Ml!YdPe*Y07xD|`o9HXN__Hv4&sxV5CX^u%u5N_{(Vw>Cn>D(AJidIjt=#uO$z+NMqzQ
z<+U{AG~_j;wX~!$95AU+p8(JQ^>S%>Y3b843RoEg`N8l7G&HRd00)3j2#2^%X$V^!
z1DA#w=O-SGKEG8?4I{=Fo>N8+J;EVr$4Uw{k{m>%2h#9>_&}oiMV7c^b*f`=!Ya1u
zvBy>5t0V=u&4<{=pz+^_wt_c&SDlJC?h{6yHuj7B7EheEf-M8ww!|NIM584qwps!1
zEQHlFi=|Ll;)#1DDViM9j%i_wLt#a)$9%@T8HuIQNXm)T$8K@OomIcccJK}%b-lD$
z3iUS}aXRYo2q+X)^01P;DmacO=
zZ*TFut>>YkqO&81)dx_){q&Mc^in_Tr3=}m3zN=;YgLjxk4Wb!(&Ksnn3>aRpJSmU
zJ?8-cRiS)xscmwpck*IVDpg!UH4;Dopy1cYXj&@kW@zQWp#!KnB$xjp(
z^FsRGF62q~!B<6jcCUE_sZd@Rdl-rL>$IjqnwD
zjZbQhVLg1M_vDgAt|754gh-0?vC^^3lgCW_TJhpZX6`@-d}Z3XJe$YxKV3-uNKA(3
zi(^Q*;rd>TYG~1$Ux(3+>(sLPpCt@6S8Sff%HWcBNrobo@SSrO2FDK=js!TWWAQ8&
zdjEa6B^^uPINvQ+UKnE_URhCe{Zx%vb_Pv}nsy7z0PL8p-0XCTd4ozXxcRi>7M9%%M(~*)iMJ50g(*?!S1;o-n$)^hG?2~@>Qf5VV&T{rW@hVamz(DLmJd(>UCRbieuDF=Gl$7qBomo+t
zBhg*9`t;$~|G$%lq~JoZRT0uXFQjWBq-_CTkhlxc61mIsT3Sf^ykyUi%)uN7nN?th
z+Jqh$jY9zdK>N0irDu@Gm`QllLJDswHEN+fYKi~KfPF}B&D~V>
zrRuwHL`w?CO%xVN#(PC$@)jAS-LtwZq%~5Ibp
zK|sKN8lo~D+JNCWm=ESLzAaNCE)4=YB4<9_L{`cs_zo*~6X6O$a5eGD?WsWztX$-Io3vgi89%sdm
zW@=l8!Birh!#pR)wFbkq@pn9lue{^wc)`=A8Ga-HA09au>gaek%+c1%;ZOW9+@%Rsy0%zFond8G|
zG8~=Z$Jhg%<)&H#m1_gsr&S--3exJf)=4@}%RX&2&I+4Z;@12immo9T5CGGg33YV7
z8#d$p^{JT^-qFT$@(#5K%2l(0g-qwf8&(4J=sKRTWf(HPkktkVwLNyimhyomOd74J*Ni4CjDG-ZG
zvuNjt>s1ir46)DNa2W0QxFjjF)Pv@zM;^ja~)u{8-q8!iLUXQmn&P;ka;YssZJ((sTnO
z;ZhDw@%lR%Zt#r>d;cdLSs|jO9q9)C!lkwjMhZ!$e&Y#j4o&L66q{fc=9VJWT3Lq
z-qPp^aa2#p?LyXG#16nS?=UmCUejpra9tts@a4&kFe$(K-|!8Xl%WB(iNh}y`#Z<}
z0er*Hw|*ffWLa0%w=`L1j(gP#nSeEUOdPzGLEyrdR*^#$}Cw1kDtkow@m93B5?nPXuEh80`|
zehO}}`yDxKMQUk^cb381z%7}cbGDAeK22c`9M+@6mH-F21c&jknLqKa3X08vU!Si3
zd$p<51;7T15)>l2t##$axM(Cvqk@4OEL(NGW3dddD=lTPFo10%M=%UaF!yO$xE?Wq
zBv@CeZow|{Mh4MO60b@#nEnTw{U4O}e_fG-EmL{0r8ZG0Rxgjce@p@a!9)ohrCH}`
z6{*Fr*A*!4XJo*fa=k81Ra!D)Z+It!!9c&Val)-SFV@V$pr(V%9oDHa`|A3!93@z9
z+5D;%CU^{dq&jUw`d<7=v+$E1vkzH+ad*tE8oT&Dmf=+ci~GYTzvm;?#hUE>-l=Ty
z$C=fS+J|fq!DNc`pZBpFFu*)>U15xsqA(#JaOaGrX3yc5SU{M|R%dpUgAt;YL
ztg0O9$2swAuEo^sd=N>0aR9KiTU%bsSMQZRTX~F1x}s9UKrBI8vzxz6%o2Sp{#Z!z
zG35OYYr_uMu`{3m70dz*&J05aXz3YLDb3)PvD5e6#=pB_JF_YH7(N-nw7kHuzx^!32Yp!vlpvk*;7p1Sr%&P=;$g
zxJeAki0$|IvKOTh|L;S{z|_(dZ);_Zw>e{H?Or;(5f=UF
zRYh6SM2Ig=Q)yM3ekOEZ5wte%L^U%Ot+#B(=X~6A@|iqE<@m$@V8FJ<@)m)Q`Y&$u
zZJ(P#)!?pF6r
z-tP|J$T5B#Zw%Elk|xW4(s~$k_fJ)Q7aTPg
zATSgWyr-T|D!BvUrD42z
zPUr1fNg+EmJM>ME7$pJ_)l_gPG3qOQ&yP|$X%~|()V-@#BEm`Pokrj1IrgizE+DTk
zMVqw%x^e;e*C;E%r@!l1`+2|B^71?O4w!jQm|7oc6n>UtBMKB?p59p#ar~XxDy%x-
zpsOOU-H-en;P((~D6W!5c0D^wEQt(!W`}AndZ%$)so!@YocP26_&}@oF2<(w?3#b)
zR!+WevIM~SdgIDHV@aahpci2_+e4;R$wI>^veDIiyg@U&I@~5H1chaFx5g$6|KRUL
zEA!tPe@(@|c~}^?a4z#lR^rHinjlp#?>hfO)U0P+XitAkL_Ug
zd!5d-Owu}D%D)1kNg0q;Nw=^(QnF^jYeli5zlDZeQ6Pc_cSRJug^sFAv*fvK0GMVJ
zeWhExeA(z1he*iMatF(Z&6PSAhS1~RrD!8^0Sr7Sr>>H%4L-W>7ypRMIC?wW!?k^w
zF265DqC^_A!HB72g>n}*J<(DG@CE#lp!!H5^(jZ6y_
zM@C}RGD3J%ez>Ja2l^$L4xeD7i{>QwBXOD(O_N6ZCG+M3lVG-T_uI)B&f5GsgD=L=
z7>f;nm|WCH=(I^B`7q$oFRBTT8|b}fBP@)@eJ9R6Nw;ALpFqcG>*;^)H#=Q#`c8UB
zz{LnL(Ic+Tdpu$^e)#;~2CSBuraIWG-2GA;L;Z&=xtS5$v41Z{Ros`i*8vGQw+}@o
zmHksmrY3RAXjgf3e8c_*$LL+^n6lgm&l{%xvArohJ!d&(gN{j#n1EI*(C2ySg@g-B
zF%`Jly<6}|(^)C2Rdj?U${Bx}65}S0Lq6}+A&vSsU-RW{ZR(>c`??o?xs+6jTA@n@
z?CwD1(^YeK8LC!7ndVJ_lc2ol5t07Y@ys3Bd4wngz~1EU{+5P;S96%#->KWxVh;g0
zjyulK8Wc@Z-2rhaX6P^ZuWQmW(2f!BtY6QspWF3S*zsMTn${0vT9gaQGE02o|
zS>I5BRiOcHcCeRx$%J+j%&zQ4jihR=a-DD<$@v&{;0yqC|)4
zWIr-O{EC?B1PFNxF@GL*4hwNR#W+fGY8T|ysol|sw0{)eH~j8!u-q{njomBeLESgP
zAic*Yh`}%*9F>ZD>k!*8)_jLUbk);6l!yN*pBi>rAHTLR|9iR+wPMR;GKA+0VzZiE#2!*FJy
z2skNa7-jZenccl~M@QY{xsaaA1V8yxluvVxcewt$cD+
z8E8fCb#=uHjio}(zP`y(m2R3M>68BVP3EZp?v)OP#N54=o^w6T`PY;hQOT8K638z=
zQx2@nKY4k4LjMmpg~jWn$PcW+-ji(uua(c9k53TiquGn-%iV_rmMcTrD>IXQ6I82;)%qj75v5Ed})D2Ih}}7Pl_oYyvgWfAIYBIj^M))F~5r(7Ya_P>}zd;NUjG
zvVRG^P_T06CpC945ZI<7=2QXXCGk11r~2`WcaPE@jOK)o)9ccDpAOQE53FvH8)`8#
z(5!E*qrM#sYu1>f02WiP8H(aS%dIR|hkCg37o~0##)#He=
zR9w$U5)?5%{42&7v+1+<4a(bQ!8n4nLsoQPVNUR
zwt0mjr0cyLysA;DaVB8bR^;mLWZ{rKBfClvKL4koY{m5g&4m&ZX%Jta
z$>bO8XTB~D961f(Xc&v|SI<^Wep7Byw?K})
zrJy7Vk9p@ED%BF}A1Pdis7s6|@ghkGf9*Q|i}}%SMU9_dv9=sHviC3N>JPA7Zxmm)
zqZs&oD>_w97ff4IhI7#j_lmxiK9L6V7rpnXiMD@AVIv{uqq7b3YUE#}$r3^Qe(7H5e@1JzO^K+;!nxk0VhUnzuXa1htb-{K1$%>_x7-!c5P^KguWn#Lt=>{nN);QR-h)&bL^HRP=z((GOmWVd6|ajcnq%_
znfZqmB8=1z6t3~p_KwF@{p^i@_e}AtIR;u*{nui-v*@AUC%&o_Nv4nsUw)pn{KSGK
zReaJ)zq!iEca?E=Rqlo4D%mX8-b+7Wx>X29r2Ff){BPmDgdt^c6O1Hf!x`@cJQRs3
zd;4!}-pRa&k~!7Sw_FD3Gf~M${SiY;=;>`z;V$30jJS(Q3Jt&Yo9`?^0}0E_d4+0?
z65;F#8=NvKk5^;sly!)>8Cw?;bE{R#et&6xGrIPeI@SH=8e8dAWz(BSGM^irSLR@H
zrLPAYg5Z6`TsB#AZ0DBR;EN^H(rYD?P@Ve)VYiBsXCAq~`fu%fup
z$fo^Imw?vp?{KIgqvKRA--KuMDF6b>z#cVd8G;?#vxnLv&wiTc3uPJC;6o>A2dqth6Ti4N;oIuaSlOUct4sRTt)AV^L~y}
zgXCb$PY#jetXImfnuX5EoH@vc7$P%={*
zj`A@w*}vnrtodVNC}1&0anTU^T7I_b`6VA66`Zh5zqaPhfTK7o0}e$g^kU?_lG`?|
z+kU@nzD)p5_?6Zf76usssI7&Apsuzi{=`($7&m3~v*xFBG+!u-&@6hd;2cGe5?O09
zY$zO9WBv;K8lt9Q0FYJ+fW!}g@3ZOC0XG@q64RIf>2`So{Z9Fr;Og$miR{&YSumtsbqg0`6my)Rp`
z)|-`X*D#1%MDmu+*Z)jT3I%D7z`znP$wJ-<o4r8snD;&ZLs8Vmn^WcH
zkgjR^xJ5IcD*s9FS-GZYrG}NjFQ@d&_bO4}(G(a?#C(lL!6ju8G`bbNr@PF5^U04r
zaTxjRjI*@76%?YR<09Ov2LljE!!e;3HiHt;-YFt`+PeyzD3=qWZitO)oQC|BiPXHG
zLM^8Ew}RqwbD+C3FrIm6E()vO>^2-;QN;44MKX@E9355cyOhp5f^0#)5rI(P09`vs{@BdhWBfKC>o3O-r>Um0XRNRTYP3ul`f?mIHK4$MIuFYo
zg;4rZzGZ)#^Rd52kv`q{%;{=DAuV!{>n(WzDEfYyvgenSSg6kao)p*nVIaEH!oY8~
z8p<|q`xR+B;u4LbcvlRD&Q`4a-FqXcLK`7IfMFzJ0NroUlW^KA_sd%LZ=R7lrT6{R
z>%-{scJ=<1st*$M3NIH@mw$w>zt9X6Ji8TsR&N@UV=?J$8S9x0EL#!tTpa{O7nhT8
zfSKxcH-@5hdpGeZt5NW`2E*@IE#pl!V}fbeiqJEZY;*(nA;6NLRM~rZ`AC*=lC;2u7%(RE5{5E8etuEA
zos9irxNwX>KNRsn*Rk&BNcvsGG~3@Y{a0y5?9i4mQVGjehA5VPeDp+C3z83B$1CKw
zLoX1B0(;5I{f#%Js2uwm`3MQ9rw_PWsPcFGvPC0HPkMy{?p&b2ymlMXYFMc}T~y{$
z8~ri8gjZ4I5p;Sn5&oAaqO>+dAtbC?U0<*NylO-~XMlx$QbZzWWA?0{EIj*s$sOx&Gg>+ik
zbuf8!L9K8r2R05j#eyx#Zp-;mPaW%qk?&Sgq@OFuM16W`$yL1emK5J{{f;pQv
zd0$Y-w~_o<^t!`=1Kqjs;ra6UWKq^WCCQk2>ZA0RVtPhw=M^zl9N}}b%JUtnOhy-=
zT2*L539L!|%fw-B5g~At6nVt4@Vae*8nL|~ZLKM=ONF4JdZvD`WS{TJ+<5V6^RseX
zu*~?m)P;{*a}E(QhHqc%=W$1JyDS{lOSYikz-lsRs`*~wjndTBB+b4{z06Mz`)-`<
z0-S6x{uM3gdqW=r4RVKuKxcbd_1TMW2@$_J8oDOCYQ|6tKBIe9_pe~-)^x0%3!kF%
z=)pZQA?Gmg027V~9&XT4d*fFx^DEub^zx4DFncnxnDnBa!=TM=sh|Ymhcy=Tpp1vQZXNgPL%Pf
zqfTI;VAfpjf83Q7yC;6N3Wx;FzU6{!Bxx!}^T|FCkXI-yn6sOg;eVcGx`Y`#bfZ
z>7~=1YYHoucQ4%dWg0P+aH}l!Z*I!y7tsZoO9dB9f5f^xH2<#FVcmGI{mU$fhC76{bKcP(`4+V(i9DTo7^83{JdkJu_x~|<
zf5g81O{-V<`_NiLAwp7ms$;)%hur79@DDqAr)_&VK+l*lodJ3-o7^zC~gF@xz2Cla^1t
zr~~|4MNnR`^eA_RxeB4yaHzXqG_6j|CE*65JsY`W^Qg^T8bJ(tIw`DIsj6y|8_Ko{
z#4i(!Pb@6XykZ15{SVF632aG<&s(O(#lKf#lFuz?SUb`0yJb3p_ee6;34tYpQ$~U=
zNxb0RBj}5y3QgpoIm3Tx1zA$g5)ji0M3DcxN0{}bi4`$#U}|&51x_2-x}I^c#@pLi
z*cjS4*;rcO?d>h_W;RwJ6~q8|wm-
z1Yo45_~z4D2LYzo5>r3x=d8HMWwMNGdKhXe35Dy?II7S{uthhrVtx}9N{fJ%vg;r1
zPqHdFQi!s;7T5DA()ebA9)GUu;ar>Q8u9Is?W&qj{ALHlYu-KF(()#~D)JCmW6T
zW8a2W8J*-wjbsyb5Ia3WR~Iw|P72lGsa%R+qO;1pB@uJ#Kj1KAU&V9zgT(xmazHac
ziy|rfeChD{A4x-!Kj!3?Urjyw23ZLjdj`45pUfXG20TY)7P%$O;vuL&KZ^piUyG(dX%M)b|+1f+*IH2s3s;g3edT^C`hAtNZ5WjCwQ%F-E+!i+H~FT(Pnqn>*O#73A;}DQTeo*F
zsOq<_KhItAGlpR$D`V)a2c@gr2e6TN5E1~dNj|D+Q&xqUKjT(kYre$1pQGNVF{@&l
zcLZ<5AUY9XGJAb0jpB
zBd^edSUTX$hlxT!7_ilOQ>xb~OV0|luIwr~y_s%(oM<--r>yraA~j8a9%E1;6GRl(
zGZ2*minwg>clYwWnc>P0=y$QYlwKdDV~imo`jqb#Nm)+>K%qv=q8#9&F{2D;MX~g{
z@WfJDAz`Wc*mV9=<%wy=l7Jdo3wa3#?=ZPo9W32@0I~6lLj0k))}|zt8Bl$TtSAEN
znuT{SFmHB`&8ofSVombq+R~-9eMb#c(9i`b=n@jp62gt<9{`a;GI+5~n|_a+Q~%jS
zN`i@Fg>aAS0pTDEOvZ-$l^N{Yvj&{01;9nhB}@7*?RRK@-REGVE{>$fnUH~9x)U-L
z#lyUG5g~5vJ4N*VH2*@Ia9x0liI}8u?qoFNAscMgcvQAESN=k)I_`yLLId=c+HSxh@j3-YHa7$fw|k5LdcXj0xh%
zd5^e*BF0=%M&!Be($!T#Sa@5o;QZ+bvS4HG)8MDKBq;~vazjBRB7woOl-JGu;RAQP
zqzSfMy}u8xfb!FJ-O2+fRu|=3#6@Hds)Yj(Lz@0+W;_-pi8Xhe|SHO4qpT@2EboWWa0>%vuM8YoE+TA;)AP!IIA
zUr>}+Z-YS**WPq<4Cng`KdWjp8(Ye@oUOF?RRb8*R2!2(oZ7)?39ls-{BD%Sv`C*K
zlWHuM=o6}bSozUCP5=808MTS@f-r!%A+uqy!Gv6GN(Go0U4$y)s`#FzNgK~YUUoP}
zhLhxdLf$yGxx?^~R|}aX1WH~YXCis-tY}Q7ggV)E>IJm3J{@9)j^}G`SRe>gO}D#;
z8;}ilUSrI!hkV;2>0F8gfvUvpmG`a>V~hXh#QP=hS0>GOi)gLW)q$(A{3NMm?Z#bQ
zGf?{~TUg|s#$zX>H(8`utKO}`Yo`ACn-xlkqB9RYaMr9kRV>5c2s1M^aFWC54I1;!S94R_
zoHmR8H*m*Q09ohm0zIZ&qL=jl!p$XuhBLS??c0P@WQoYAcuGt6g8t)AXcrR~#~UQ7
zU~u+s9xlxLHyb1L%J;*X&=_MMxI3A32CPdXvQnaX&sMbf3!9`z$N*KPz1oul3w@$Q
zQgh;5xs{VEcPZqky=A!=4kIlW-QAmCcD9)Pr~W-I%t|8D!bdS0jPeqgKEY)l7fd*4
zk1WzfUn7T-8Dys0ZH#&oV1zxZI9u&$xcxhy%=sWAp36@{F&hwKpzf)~R4-$_dt#@Y
zxN^~_cl{I|Iyp51Gf{%Ky8BHimDJTgkY5S+(z0IS;^HKqcljh-S9N;5YBYvVAS{ZI
zU$F`H#P_C6h9Zy3wz1!bH+L^&zqxkD)$lc`q8NZUc8{ihwR7LQpWbAfmlqLvSKuZy
zRh;(bC`0d?ZXc{t+E@&h4Yzz;*mk(CMdshm6Z`nvCGNp4h&{!uwII)2w
zGQv9#rS59YHZ2676088^S11R^p`4G4zLmrAoh&?|T1cDW!*$ggxI27Spg&zC^VXZ4
zD$4wf*#)<%386L)QTO)|Ngc-lMw+&6MB{t3JIxuE6bT`4RcUX|kdj$x=>@(*Z0$$)
zW#*Ap)a`>@17RdeelY8Wsq@31U6u`xS8NPudL13@Vy=l&)%Xl&+n|)hvL8lrEr0B6
zM?F1Pntqx2$-*SlHcAK<*K@v-vvb)jK|;RCB}kC=_Hh4(9TYyZk-NdXu+oq>Yuya^bEVB)j
zy-t<`bK2*VNS9VzmcG_2L5{3<)xb`YK$n69_r&)SpWaschk&8{)=?YgtmDUD8~Ind
z!hVPIKww@&Lsjd!9>O+|={u~Vv&EhT#=jHp}J_ym#NIf5)G+mN}LnURl@LYRm=
zgd=0cy}44d`q%~1NL@5uiwxYm=|Wmjq5#;L-m-PdVcYomG{R4+5PG@tgG(%>>YYm-
z*|tBD^Q2@_@lxsS5F0-9=V6X{h&IZu?RaoZ0)8ERW#~m22aM>n^N2c=&2=x0E@g
zfoWnXJ?tL$Yuh*eemODVEPEZvEN-9pG8=6iM#FjQRP6N0uKds!-;dHP$AbA!sMZnx
z#eSmv7nP&4lw@1QVRsv`_Ibnm^S|m31~|?`iS4h|;N5O!r1o68&P~(zne(LBSzAa|
zr1J?q|C+9TpFfw~m3YkjI+!;GUK)Uo%CW7eI2q|AzUOH2eScQ{JM03`pbiI~`bZuO
z@c6NYXob(z7f)D*p3rUi;1XCjoBK*Q>8YdKoz(#$%?ml-pxtTO9DbCG6QXX_PH#O;
zC78C>g@W9PYcH4n(LWyg=l8vA5nz^TVq9KTSrPS$J13bcc0MX|8^M5)J3S?Y~6Qj+(>*LXf-NheWu&r
zz4Uju=um4t=+UlE2!8D*6iQp@ZZj&p!M9-s-Fo3erYZCP|*rlIo#tMY4lx0#UIiOy?9
zTJ)bKFgaql9dzqc%xy+cfgo6OAD34r^3&}doEg6y1{xm4Ye~32M-V8UD_?j
zxNnd-DZp;MJ+p}6Xw`sIcm4SJSsNjf0YI1rwsj;Cf!2(Niv{lN6ZiHlt*M9Fn~{k+
zOHdKIF8}^)ADvu^y
zPOHMbMLD-gEqN11`*Oj6>*E9*brPAdN^y``;j4pOUWgRcGg+eVYE9Tl2vF|-YQGuw
zsCx%<*MpaXf9gva~Y%i)H#~upi27+IB{K(*%Z=8
zcXrF;FvHIAt6}=YKkP5j`~ZK!&7gd?JCpeBSJ7gZO1vZ(6y8ylJu`bI#F4}bj`2?D
zOhDXd^nW>k@Bb$U09WIIZSS`iPnz3U+8A5dIa-@qTiW6+&8^L>t&NQEHWuavcoRI{
zRNv0V*51a%(#G)&9&c%P6;>k2&;Q=5Z^{}OZ#Gv+{miTE#J%Ey25k@I5>V1v@LtmK
zYpax&WL4NK$
zG4R>{XRta)jTMiJXT<&NJOpFRf5J-=es-xj?>Dh*4Dy!K|K1!qdp5mPjDlA;FTR*+
z>l7xK)`i_BVH;hMT}!T#?G2n@U-ahH>?+oP^XSq-X8(-GpXnFCmX)LyuG@>sUp
zkRrgsXcHH@$-^O{0YCgZ9{mTPdsZ!LUytwyr3@JuGpA!6TD_8v1_ZT5#)_G)Tr2vVcxC*P(MtyJg
z8Z(+K?H%4Aetb)BanXW9C3_ibKlsj5vRRb`+#~R
zx?zSD!N$*XsOk3|m^C)`S(Xh`Gh~{v6NM~erch^
zu=5x93lJ%VVqdOV>zSTml4=$tcsGIIFW{{uS)Hs#Lex)sRe0B&zKwBB=~FbEPJ-#O
zyHJGwks?JO^9XLB!wzZ2+19KL`r!S)eS7rKGQ1;+-75n1!->PIIwSOHQR!)sc~cYH
zOfaV`G*YDaw)NaKj#Hp75sjiDh9b4Km_>EwD^jq3TQ};}`1IO@c?n!UG{_8%wb3SA
z{gwe$&IQ*}nwJ1}n@U;AWurXyywQ4o)X~ReC5WtM1SAixyr)wlk5o9$M+TwbwW(I>
zIxs^47ijGL8G&8x#d}Irp+LRqAesaUpIoO^ibh5T(jPm}6t0Et+#Kp=;Ri$DXClY<
zAi=%Agc~X{-MvR;H*%F>*1~qf;B{e_)-PG3cUxIrzo&qbHY<%F#B-JP0DGJZdip*4
ztMOZ}Bl-Wt6n;5xuW19`U9IE_~ngdi#xl0x++8-9M@b%y50`pr8rN5IN;t3DmaaHcU8}+)$TuUG`1AJV2tfrldtNzK&=ZF=H1nM+I=;_1M`K-EHbloMS
z&;zQXIzlUzMuC(J*D;#?x4pc9+mnbPiMCtWLEK15GR!+f(lu(Ze%)o?1My*OaKIb^
z8i*$5j6rceALo+Ag!lcKMxP5c3&DE>PzWT-$IT>r;GRf2Ipt3-Y
zDcrBnU~c~@GatfP`)*!5KVfpAq#wn7y%@0v+j&)F815&QSO$s3bUVu5s_R2^b|OIx
z$u0+9Jna~9YTWwGC6hn=J+Al3?pVZwdHD6Kl+aHaKnJoC0e=o?_aQ?^yj4D`C<0(e
z|MHJk6YP##%`C5&6rX-~hmb*M%g!(3j-Wo+O2chCrCc@kzaNL$4?oE_WL6c=9nT7u
zEkXO$0IMo=jA#nF6BiISE7DcR$_)4<_MFF3jl+O5zEW&W&G=;6msexmhtb^9zvG|1
zQbV5guRnQW|Ks(zBYSoPGp^K5M9A^x-J>3cn1Dy1R0JdgT%Zs@ftmVJ=jaz?icHk4
zFqZW^{&JmI)gVv_K3$BV0;DP0gC4rVuLNGg
zfVHIB9({SFZygnZPOHe$n$TX^IfsqbCdSlr5nZYE?#Y|qIJ6FM+kavK&^LUz
zg|vG~84ust;(^**o8iy}MQa)DMbhw=l6B!`E!@ZeMyE3_aBj
z?v@`AdspY<>;2U8BilWS>#F<=xfFU}L!JztxAWy#YbE3?F`Nwfa)L3|bB$~QWXc{?
zLlW#xa~uCV{Yc=GmUBX+KL4$g429
zyXbaOO#ZSba9X`s2B{291(GtsU_PU%=~?4u&ZuAw?NF!xi%;E^1605^XfEGKr~n5n
z5nDxPSj=4`PMxFWbJ)`jNN5e8s*A)+5R10~fi`se#$k;OD_nYS+2#_Uou|ap+-)m*
zdBrEUCjW`>GwZmS51%>O#;Yd!L0Krs(v)5t!q+
z4VsI3?Z*NX$hXL$HNK^oYq#IR8;dY%<&EKPSzk>gZ3E0bV0yp*Fb|atMc}3mD_2}-
zpv8^TDChV}PS{{@EdiPT_`r;L!XJu9INOL7s>WXS7y9?>b6tZ{lV1xm(q3JAKM!1zd_{c|I3{(yG=0ZBkMr4L>!P|U2r@-N3oFMBa!o?jd
zxl$55?Xw`%Rl_|sKY{6B7Gf?r>Mm!VP-YsJEY!eFf``C+F5{y8Q1|c)XJfVQFY8Xx
z-#5#qSo~XTt~cG~r2vBc574QN4RiKKBz`}*C_~J4TyVlIw%KgLnBe`7k$PoMsd~Sg
z;-oPW_B*7Q=EPo4zYjYf&(*kBTULHrR{TZ>YK`eG$j=4|FLg?MsOxw*qbDx+VvGS0
zn?IdR%<`NLDcw)
zI|+orGpO5ZoICW$d2w2lIl+S)baO#F=du1>k7%M(Q{i;ffdX6%_h@rS(IWbb7Qq}g
zi1$B0_#=)QD5!2Kg~-yeNEuQWz4Y-GJFho5JOa*X|HE1T3rCcV+>)uN<<8*_q%17sT^~13|SwVBq9K{l?dbyW8A!L$V?5
zv!6mVB+&Jj%L5nQT3(A)t({ww5Tu0wp@I}S|6
z?~h0Xq)0Q4m#BGSbsX%L+7*+QIT>?43|c)V!*ymMDgbr48^o6jSnR=c$2dyyVcLIj
z`$N)hFlq0eSPf=hLiOY185>J0YX=J}Q&U?DGczl5D@zk|OLIFbGZR~DOA}KI2Rmzc
zFaM0Io1>G1yQ>ZCOPVD;`rjkq&;^FN65i&>u5PAT)KhhGrn;4#-p1Q$coqh#qc$qO~CQ&
z{t>fhZuHPksVtJ>7H9G8o@6@HUnkJ=)}2X8wRE^y&iPfwzIQiN(DWF@FJdt3d|B7`
zTT=qjKR5aEa%#$hkW8?KDWyOq@o%h
znu<_o#+^^$f@%=kDVnadA%id&n|;1o0`D#Ikq4&_1y%g`N0Fz`<}G#78j_`B;1R9^X2<L_U#{%~~Xi@n{EiywD{%oEqo+
ziHo5`iZAUu=1JiZT(*QeT$>OQY)oBTbrYc+&ULl789R}&*g4*Nb2>`raeD6qAM$;<
zWKW|%A`AKaU+#hM$3Xc3(gJ~G*uHczYl$c-INMS=gCfS-{lf-NmGxd&z(2^kcepv#
zphZw?j<3A#|NQl$*>u#sTlOS~-&R
zraSW{o$7U}1VfPrWmiv~yfu~1v-Dx2Cl2FA4j5<%A#Ay9kv7z|gbNMyPH@Hlkw;N;
z_49CL1S=>U0|Qv?1M}I08brqVc9JRm@ui-SzQQ$7gGtwEsU&lUmJEpIYbZfy-
zb5Av<0rgVmBiYQSM@WQ`jL;qz{6iSp`f&eghiDJu&9Fw95D(l>>S*$f0HQ4cz_xWu
zY`M9@;ag%-Ram9WHooIFeLv2K#$^J-|4^z=%=g30h&x@*f!}y0nf1gYgDwjqKKf`gLel=BD&pleX$MeRaen
zV+&p)+ygSdYhsIoc1O>os$7&T6-AYFblKoEpxI|8dv;`y}M2+C=07~|}pp$onA
z{}FZFfl&Yd|MkAb**m+lN5+Yil@nRndpjdhgtE#yBP2;8Bzt79LPeZp6-81+C_)+O
zQz3=l`_T7y|M>6i{eJD|W4-doS|lOJ#cgk4#+$Lm%2o+2KRM{bk1Cce6yF#U*eJi>
z`ql63sU6dMF6_#9XRwwl5UxyuR-&&8u~GVF$_?pCMG@&lN_+RV>Sg{pj)O-3&Bx+P
z{;4Irqb+Y|PGp@4%`ao)z7X%BE35!o+^t;|j<~kdphcU$$ES?UZtsL){`=DNsCfR5>QQMQM`L6ug|~I!rSW
zsoC{3B^#_VkVTI_qM{(3&ZP`P45HsD`o7$T-YSM)3qLPrK
z9S^0?yceY7XqCn7Om8C$3TYlVlRgU}PDxw1eK{l)TIkBl$_kudbTYW%%#;RfZl*vU
zfib&d8dS4%NBae&(xhXLo@X>A2Rp1-FkW(q(e1!aW%ZwT&B$_WkgXahl8$0aSq%qY
z1)=jsP2CyO^#GoLN2{Zi9O8pM9!l7K0k=)y)%EgufeV3f)k!
zX0-RF`Tm^q8uj6;fkPMcMtPwPE@)MioAWpzO9S!~>|?0MlM}Wg!bk97M19Z%3{c#(
zfc;_n2R{9gwTdE(
z9SsPed~we_wsko(q;eO7*To{q2luyc1b8}sHDjax6zTDf+G{4;;l+|0;=;Dp?JSaw
zj4ETrQm;d$`%NBUrr)E8LCELxI1(Mrbb}uuK3bnr%-9Ea2Tx@Q?|rt23GyqElQ;xm)iozO
zbY2`L47P`Z;3P9d6Lyj51MT~DQZcd{E<_kI_Gp=TPr&>a~;E~H-
zYjRZDh5a^Q)i~=V!2WI1UzuF18eY&XYEwu%nvGr<6M(i6CerzGVh^Pyd0}LCTYp>K
zP4NC>9?I6RW9oC$NI$>!9c;jD+84;<-$~8*j%1hqToB+&d2DMbE^uVronCXgSmmY4
z3Su1OS!8nOv-Jj1dE@Iz2)M7O+^AeB64s5YXR+C*V;0UrSl$1U(5QV)-?doh7%yoH
zcfDI0e&=mU1g8(B(3t$B!&z{cI1UrMX|!DWR0mKR6TK?k)kl;1*u*s~;SQfY%Bn
zFYwi>Fk}-%Za@Ap(EHgnOZJ>)d{C#)UUam!S&@ghmBKaZeQP1oNBcbqS8F>*Q7B8{BQ^MI~i{H{@tFv)tB>XH1e1%u&;9J3mJa1=(|ipEDM&fE|oquV%_+
zE*%Bf)uY^?g19>Xisio?TE?@ee~;92#VxL2R4w!q8pPDn`m3yC9HFP=->=&?yLF
z?id)9g0_x{mqD;hF!D`YqiZvB_;r;Ei3kJsr#b|G%efQCI6eY`bY=I!QuksrLlWwp
z&puHO8VWbXnLY7iBHh=4iZ{>1vLzwOQOo28t>1trWUG-iSVsdOz~p<_CxZD&vS_n{
z6@3TUy@L3mCu%1(5gupv+hIAzjq6-R^v3Th#gh98$O)uNQ7m_=s#!!ZmEX{SIbjl2
zvEbffFl+s}%B3(7wVL~UtJzs^{k_JTB1|?<)#f+o;=NcBfa`{$Amt^Ovb5gew|RuZ
zLVt?k-lIu|?Ebmh&&A}4mj}51zP_h2b={9`dYBtDVMcrW4oBoYeMP_~0WMfH%MJJ|
zwrUu=&n)vMHJ{wwn2=0_+qco+_TKhR%MWI>7t*%{0rP4pxLN6U+RCjk>aDbNfpd2Y
zBePb(1{RS;L(n_?kqM1d69vM6yvV|GS9zK1%`b?64p9aCm$!y}XGa(=a?URXz{k&V
z_DnJ9H0C;P+{6NkWJrJmcmVGAI$fq8%An^b$3@SJ5xJ=NKc;C&90Q5ujgT8LCuQqe
z=l1|oa8M`azb
z1po;GtC7DiUf7CB5g)_({doBYEUV;P)v#kme7F4%y7!&G@aai9EMExoH&!
z!STecGvleUA8rQBoZ^Ii4@w-7GDyYPHp(i*2|$P4-y{!uqr8hdBi5TYB(GPoZ(ib^
z)?mVqTn#1Vidq$AuA=f=Rm*1HTPW
zkD4{`a;zjQ@Iz&+XMB?UEBzUB3o;tvCNa)%b7>E}d95JnPZ%36$tUlEL
zkbtv$_!mXRPX#~Ezh~iytfh^agN3z)iG!_$DMi3
zZDDC@VQTJZV{Sna1skxR`$eqZ6s+LdZfj8xE_!8SLl}RvM`aWHMDQ
zQTU$FIaT6d_{Ps8Pj1%R@Au8;e`USTeq2yoMw%0MX5}Wiez+R|r$1ZH^H|j&ttYj1
zFi?{y=V{1d+(`O^@@U`J?`HkiI9q!>qH|c}QQ?u%7)(k>n7xwrC7dxHza)*B76VLF
zGhtTzFC`hr(VwqBl;`Rak4A%B*cSHsxnJ|%QN9wT?Wn<`f_0Bh9&DntCji}lKRV2|
zx+;`9-=I=>%gOi9pA>vW_a4HbNPDv~HIEe!U6Y`?F_RoYC%>+jXbyYrxF=hAXW_*o
zt`-j6Xs^Ni@l6*{%EfpPY!TFi=)DzmLg{AK+L7x|8_r&qB{V4%q^gbirX-R1}#Ufb0Sk57#{C!t=?w_f~+4clUxq@Ny9Nhuqr}XWIR5O9;f>1#P0VW57oE5q2#5<_2OuR#JNx9A`CsuA*ee+
zZ?((PWl;}ytOnfb6ptqLhM-H8jvoAR!EaGR6I~%iDcPOf#6GrQ_OTUG2pHekqcX|2
z0D;@L#nD>xP4Yp$&COdZp}vMmy@|s0E~?0MFNBGpZtnf|j}QE-OpqzrUZfk!WO0`~
zwn2&vs^=@+5yrSr=LiFkB)-+bxgyz0hn>Au-|p
z5a9_A|CWd+%?9#(GQ?jTkvx*>HfQwie4Xxc&fzfdiH6hjH%X;6lsCA5?jRBm;fnwJ
z?!LUX7Hpho@_Qj4f=eY>)}W*skA?AtmAn`3zKQF%lLk8WcKz?iM^caRYdU5)PaYGP
z9zAcODmNKDgYce(=c?v*uD_9?ir_6LDzJgL+7V^m>AU``@b)QV8p&QFMP{VqFQPUN
z_8Jy3irU!2gMkbnG@Iy&KOpGEkDQrHxUCR;J$cwL&fZeUUB5iWkAYrPK$}}K@+e7I
zh?*!!O^^o4@lCi))?i8{rrwzPQ6~#EMFZPIBM(Uhq8{FdvI(K6Jxv&?%VX-)dmfp`
zrPlHC@99iQ`pE^XsrEtyrDg4zuoDH0(?7}WBpky=eQ~&yD2{^IsbB2MZ+R=(mQI$S2YW|~VRs<#0bSnkS34ICh+uyfW?d2ict
zIv=67@E-_r*pO6#k3Dez2Rrti<+JxaZDOH;20Ius~7$DwXrC7=J2w9=L9wrl|71CvYN-NV*9
zS1f()&&I+06ce`WpLa`a`X(tYN;yYDFy#F?B&*p=KHC=c+UsNY?&3n&g}9HmIG;JTAS;VGoLqP~
z-~r%#S*))~o@ZBE`K{tMKQbwIWEG_B8#xHN9Q3I{AL(n4a%f=ZsLQ}Ld`SG*T#DgR
z?(Ic1QJHi%l7TpxgDDdL$4U=$AmgA27;|}f;{DfMLhR9hoI}Ql`{r0$o0j_BH2}O8
z*Rykd-qKSy-+J}?pq)&QfCcYt`6odS-rtiAcRs0r(mrsF>UAhS^9zrZ@^N^YX54gt
zKg1ShSpo-wJeL{uPON;13gJ)sTNS%J`dWGw%{K^9z>M$2ke5*3`0YC`XaFNJr1SquQ5=}K-ZRYGN^9ny
zJXv=3+ncmNe4dDf@@zpGxSaGb*E0nS!__wB_oT$_ZRXVB?=4)427=c*rr%7CbffAVvc}D5
zlf|u4;yFn!w0<@=oaJa`?PE0OGeIuTb|mjr??E>$il#xfi+2;Vuv~%VW*W2DTTVGY
ztjq)j9{-fde`_cG;t@N-ORB<5OOVqDg-rKHle4Cm*EQPtY*AuSjKMF{@6*9mZ_1!>2aKG1CAxit(eF
zzxdA*?*>f*MfKkCjd<%fCp}9_!M}R+Gqlc#y3$74iE&Ado-<38++OJ4
z?+hNb%ezj+>Pw@aMWgb`FA`58h531438>%`zAlUK%6Rx(-&%2C&XX!`untR2wNKiG
zN!@&N+tw8-^7qw9xF3M-WF*i;LA3s`@`=3+Dk?cdxb_I71_sM;N(BML=>D{Kl
zegkU!oB{~Ww_BaB7F;R>@VIO*jS~)H?Kp511_f`J)O8d}l0y-EfI#&RCTnhQAHX|U
znfEk@vYuX)Kb>jUC3v2#;}U=VnHIWtv8?og-
z$#L*_UstctWpKG9V;DoEj7zYfd_8Z6#4PTco682vt^jk-&bH(I-0my4{$
z`ogG46Ho{PCu*$esm@^r5~at@89ShFIe(sx*Y8?0m>BlGU$mpBr*g^3*Cl_H@W{5(
zxb!{@>-apCe3=)g9V>oEQolHmOs(_UjB}EH^Z^lNkX;UP2G|3f_3+tC6s68Hh
zvk|6j$~!d(Yo&QLqp_m>*pYLY3qA)$Wv?_hA%1AzKpn4aIC^^MwC95=tmg7F{wn5h;{TM?eymy~`V%(oDWmgjR2Xfoa6E#Q}OGerF16-XhX8?=1Z+!|2$VG84}TBv-n
zP>O^BH*f!)6f9W-)M1H?{9g%-u@U?Ml-IL2{%?Iu;|Gi=i&damx1M7jzhQ#3W^DJm
z`q^FWr`tEqSN~`E=0U=pv*jdvp*Syo4n2F0Z^Y4o%gc+!4JeH-VpRF`lKCG1=a*;!
zI8KRjRG8Jn5EAP)E?Cskk0hV!w76?ni{0&mPot8U-N`l#?gy
zJ+M>H|9xU$waT?P89RMP)C5yExsNmV3bm%oeW+xP4^uR0u+E{GK-+kCTNlOVDE3pI
zuDFQg9L0k?KF}lp7z&DP4B%K!?rk;4-n@0vZkUtT<>~Bqw3qb}(3=6m?kRa%TH*n&
zJo0JNlPt`e3KsEdXR11Rj*bQ9pcdi2C%P{ap#ScX3ZCgg#k5481DT$>1aO>t+p@_W
zcGB+KZ4_~&OGrMzLu<4Dk{dKB(!@?n@t0|suMz~>b;zLsVgkG*K2Zk3g*%a#UImn~
zwNjZVf&f7%_XH5Y*d)je$JbqRd&5L|-`ZN(EM`F2f8f5afVlI$rGO1?cPNDbANWK5
zzNO}yjz5fGb4Pnzj(s_LUgmex)2o7^oUx$iBtV5O@pv
z+xb`+3^T_#L$p6$im_s8B?=&pE+F4sTSQ8x_t(@(wY={mHR#s-VRt{!n7Uov=mZqjQ
zmX@XlW+s-fKHu8rI1R;bB_FK)%ou@4
z`s6+HdDU=^odbBVE`Sdcx#Ds~U%?WlwiT{ilUM>QH#fG$BDT$@MJR43w`>-_9j#{*%OkaYufgbz39UTetHH?VbmJjQ@^qmuJQ3rwJn_C}@8p74R;d2eFaM3k(J&@V6Kd>IcPxgY3
zeL$q}a_Tw1&vpIfLyAfcLNi3-hH&&xH#Xuen!0xS;te|2>0aQ6z$qPy$_ZQ0?kaQv
zSlFA~^O9YqKPH-TtUDU`CNPEht2TWO`Mos4(`1iF)lEKc%!7;@`YY1sDj!$t;1{Nn
z`xt=(tRIi{TVqe3ZdVTYC@c&`u*1qH5(@ZX;JTikg6Kw8v9}EI<&3<8Pa~1aS3NKn
zN}zApS&T4w|J5^lcJfpNSTJAe8QKS~M}C@qlZgC8V*;k%ISB*W=|d=7I~K2iB)kQH
z*bH&-rF$bI;1|xwlKQ42PYLIZbf6D(ATdOeunJmEOBSnr_foxo-$%FI%BJJ`m>ikbsUN>e1N48K
z0`-^$FLX@Ay1Kun1{_$h{h@hwJ$#fR%WAi#Uu(6A4#%WG{cgD3jexB9rs--K)m8qI
zUktiEW*e^v`Qf)v(Rg6vMcU$9a81J++#<%^`;z}RUC#4J^D{N0s|-?js14o>_}t-r
z>%Sv~lt2v&s|TV
z1OQWQCWz9^)4V|g=MvZCS28X5%nCxgzntI)=h;y4+(chQgK{5@-!&lq%>{1+ejj39
z2)swg8x{(7UG2j&Vky@3$OlFZk9m}@)joa0b}u}~P7UFW0G@GsLWKu2_IKWU2PlP2
z+8{uHLJYP1Q2uEVf$1#f>0w~H1PJ#LsXuPwKBm+8@wKtCAD!T1q6F8l%SK=QqjB$gEgWDPCKouJV3QflqKmj-
zvWL_V#~%bJ+0NZ8TPpby_ep*W=NW@iB`!o9CFvAzIdpMfHz}p--x=$ED;QesnioXe
z!Cw-2Nf2>-uJx6KaN685I{G^Fph+vNBHp>!IDH@Ws1Z%_qg5alBoZmjjdx=!&q}Vh
zu7AAbU)IU@I>Dk89AH9}9S~{VFf4ep_C!y<+R47;@>4#LU1Opi7|;y%3S0^`#TIL
zCPisXPJ9LvVuwXoEg(0xFGY#gg)mVIFHq+8&*;8q{uW{Eg&rGIUl?Q|i#0BK7;o=J
zuJCA0jGz6*bMwpI*UJ{$xW}8!u^tF!i
z2Zz43GfJ|LHoC@z4l}4qGJZpqWeYxtOuKaE+L++Vns>aK8s{`m^dgFKsCR5CZu{0>
zx(Vl7wcXYJbh_oE7UCk{&Su`-_%|9nXZe~ISL8m8^`@7Kpf~Q!SoGO&luzh?Kj0*s
zk&~0evdOyY@W$J~DEj{0SNtCx>+lm(4;ugnWxX4&g@bc{l