Залью спеллы потестить, мне похуй ПР НОМЕР 156
* 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 * та ваще поебать * одежда
This commit is contained in:
15
Content.Server/EnergyDome/EnergyDomeComponent.cs
Normal file
15
Content.Server/EnergyDome/EnergyDomeComponent.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace Content.Server.EnergyDome;
|
||||
|
||||
/// <summary>
|
||||
/// marker component that allows linking the dome generator with the dome itself
|
||||
/// </summary>
|
||||
|
||||
[RegisterComponent, Access(typeof(EnergyDomeSystem))]
|
||||
public sealed partial class EnergyDomeComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// A linked generator that uses energy
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityUid? Generator;
|
||||
}
|
||||
85
Content.Server/EnergyDome/EnergyDomeGeneratorComponent.cs
Normal file
85
Content.Server/EnergyDome/EnergyDomeGeneratorComponent.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
using Content.Shared.DeviceLinking;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.EnergyDome;
|
||||
|
||||
/// <summary>
|
||||
/// component, allows an entity to generate a battery-powered energy dome of a specific type.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(EnergyDomeSystem))] //Access add
|
||||
public sealed partial class EnergyDomeGeneratorComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public bool Enabled = false;
|
||||
|
||||
/// <summary>
|
||||
/// How much energy will be spent from the battery per unit of damage taken by the shield.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float DamageEnergyDraw = 10f;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the dome can be toggled via standard interactions
|
||||
/// (alt verbs, using in hand, etc)
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool CanInteractUse = true;
|
||||
|
||||
/// <summary>
|
||||
/// Can the NetworkDevice system activate and deactivate the barrier?
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool CanDeviceNetworkUse = false;
|
||||
|
||||
//Dome
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public EntProtoId DomePrototype = "EnergyDomeSmallRed";
|
||||
|
||||
[DataField]
|
||||
public EntityUid? SpawnedDome;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[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<SinkPortPrototype> TogglePort = "Toggle";
|
||||
|
||||
[DataField]
|
||||
public ProtoId<SinkPortPrototype> OnPort = "On";
|
||||
|
||||
[DataField]
|
||||
public ProtoId<SinkPortPrototype> OffPort = "Off";
|
||||
}
|
||||
329
Content.Server/EnergyDome/EnergyDomeSystem.cs
Normal file
329
Content.Server/EnergyDome/EnergyDomeSystem.cs
Normal file
@@ -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<EnergyDomeGeneratorComponent, MapInitEvent>(OnInit);
|
||||
|
||||
SubscribeLocalEvent<EnergyDomeGeneratorComponent, ActivateInWorldEvent>(OnActivatedInWorld);
|
||||
SubscribeLocalEvent<EnergyDomeGeneratorComponent, AfterInteractEvent>(OnAfterInteract);
|
||||
SubscribeLocalEvent<EnergyDomeGeneratorComponent, SignalReceivedEvent>(OnSignalReceived);
|
||||
SubscribeLocalEvent<EnergyDomeGeneratorComponent, GetItemActionsEvent>(OnGetActions);
|
||||
SubscribeLocalEvent<EnergyDomeGeneratorComponent, ToggleActionEvent>(OnToggleAction);
|
||||
|
||||
SubscribeLocalEvent<EnergyDomeGeneratorComponent, PowerCellChangedEvent>(OnPowerCellChanged);
|
||||
SubscribeLocalEvent<EnergyDomeGeneratorComponent, PowerCellSlotEmptyEvent>(OnPowerCellSlotEmpty);
|
||||
SubscribeLocalEvent<EnergyDomeGeneratorComponent, ChargeChangedEvent>(OnChargeChanged);
|
||||
|
||||
SubscribeLocalEvent<EnergyDomeGeneratorComponent, EntParentChangedMessage>(OnParentChanged);
|
||||
|
||||
SubscribeLocalEvent<EnergyDomeGeneratorComponent, GetVerbsEvent<ActivationVerb>>(AddToggleDomeVerb);
|
||||
SubscribeLocalEvent<EnergyDomeGeneratorComponent, ExaminedEvent>(OnExamine);
|
||||
|
||||
|
||||
SubscribeLocalEvent<EnergyDomeGeneratorComponent, ComponentRemove>(OnComponentRemove);
|
||||
|
||||
//Dome events
|
||||
SubscribeLocalEvent<EnergyDomeComponent, DamageChangedEvent>(OnDomeDamaged);
|
||||
}
|
||||
|
||||
|
||||
private void OnInit(Entity<EnergyDomeGeneratorComponent> 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<EnergyDomeGeneratorComponent> 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<EnergyDomeGeneratorComponent> generator, ref AfterInteractEvent args)
|
||||
{
|
||||
if (generator.Comp.CanInteractUse)
|
||||
AttemptToggle(generator, !generator.Comp.Enabled);
|
||||
}
|
||||
|
||||
private void OnActivatedInWorld(Entity<EnergyDomeGeneratorComponent> generator, ref ActivateInWorldEvent args)
|
||||
{
|
||||
if (generator.Comp.CanInteractUse)
|
||||
AttemptToggle(generator, !generator.Comp.Enabled);
|
||||
}
|
||||
|
||||
private void OnExamine(Entity<EnergyDomeGeneratorComponent> 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<EnergyDomeGeneratorComponent> generator, ref GetVerbsEvent<ActivationVerb> 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<EnergyDomeGeneratorComponent> generator, ref GetItemActionsEvent args)
|
||||
{
|
||||
if (generator.Comp.CanInteractUse)
|
||||
args.AddAction(ref generator.Comp.ToggleActionEntity, generator.Comp.ToggleAction);
|
||||
}
|
||||
|
||||
private void OnToggleAction(Entity<EnergyDomeGeneratorComponent> generator, ref ToggleActionEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
AttemptToggle(generator, !generator.Comp.Enabled);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
// System interactions
|
||||
|
||||
private void OnPowerCellSlotEmpty(Entity<EnergyDomeGeneratorComponent> generator, ref PowerCellSlotEmptyEvent args)
|
||||
{
|
||||
TurnOff(generator, true);
|
||||
}
|
||||
|
||||
private void OnPowerCellChanged(Entity<EnergyDomeGeneratorComponent> generator, ref PowerCellChangedEvent args)
|
||||
{
|
||||
if (args.Ejected || !_powerCell.HasDrawCharge(generator))
|
||||
TurnOff(generator, true);
|
||||
}
|
||||
|
||||
private void OnChargeChanged(Entity<EnergyDomeGeneratorComponent> generator, ref ChargeChangedEvent args)
|
||||
{
|
||||
if (args.Charge == 0)
|
||||
TurnOff(generator, true);
|
||||
}
|
||||
private void OnDomeDamaged(Entity<EnergyDomeComponent> dome, ref DamageChangedEvent args)
|
||||
{
|
||||
if (dome.Comp.Generator == null)
|
||||
return;
|
||||
|
||||
var generatorUid = dome.Comp.Generator.Value;
|
||||
|
||||
if (!TryComp<EnergyDomeGeneratorComponent>(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<PowerCellDrawComponent>(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<BatteryComponent>(generatorUid, out var battery)) {
|
||||
_battery.UseCharge(generatorUid, energyLeak);
|
||||
|
||||
if (battery.Charge == 0)
|
||||
TurnOff((generatorUid, generatorComp), true);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnParentChanged(Entity<EnergyDomeGeneratorComponent> 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<EnergyDomeGeneratorComponent> generator, ref ComponentRemove args)
|
||||
{
|
||||
TurnOff(generator, false);
|
||||
}
|
||||
|
||||
// Functional
|
||||
|
||||
public bool AttemptToggle(Entity<EnergyDomeGeneratorComponent> generator, bool status)
|
||||
{
|
||||
if (TryComp<UseDelayComponent>(generator, out var useDelay) && _useDelay.IsDelayed(new Entity<UseDelayComponent>(generator, useDelay)))
|
||||
{
|
||||
_audio.PlayPvs(generator.Comp.TurnOffSound, generator);
|
||||
_popup.PopupEntity(
|
||||
Loc.GetString("energy-dome-recharging"),
|
||||
generator);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (TryComp<PowerCellSlotComponent>(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<BatteryComponent>(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<EnergyDomeGeneratorComponent> generator, bool status)
|
||||
{
|
||||
if (status)
|
||||
TurnOn(generator);
|
||||
else
|
||||
TurnOff(generator, false);
|
||||
}
|
||||
|
||||
private void TurnOn(Entity<EnergyDomeGeneratorComponent> 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<EnergyDomeComponent>(newDome, out var domeComp))
|
||||
{
|
||||
domeComp.Generator = generator;
|
||||
}
|
||||
|
||||
_powerCell.SetPowerCellDrawEnabled(generator, true);
|
||||
if (TryComp<BatterySelfRechargerComponent>(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<EnergyDomeGeneratorComponent> generator, bool startReloading)
|
||||
{
|
||||
if (!generator.Comp.Enabled)
|
||||
return;
|
||||
|
||||
generator.Comp.Enabled = false;
|
||||
QueueDel(generator.Comp.SpawnedDome);
|
||||
|
||||
_powerCell.SetPowerCellDrawEnabled(generator, false);
|
||||
if (TryComp<BatterySelfRechargerComponent>(generator, out var recharger))
|
||||
{
|
||||
recharger.AutoRecharge = false;
|
||||
}
|
||||
|
||||
_audio.PlayPvs(generator.Comp.TurnOffSound, generator);
|
||||
if (startReloading)
|
||||
{
|
||||
_audio.PlayPvs(generator.Comp.EnergyOutSound, generator);
|
||||
if (TryComp<UseDelayComponent>(generator, out var useDelay))
|
||||
{
|
||||
_useDelay.TryResetDelay(new Entity<UseDelayComponent>(generator, useDelay));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Util
|
||||
|
||||
private EntityUid GetProtectedEntity(EntityUid entity)
|
||||
{
|
||||
return (_container.TryGetOuterContainer(entity, Transform(entity), out var container))
|
||||
? container.Owner
|
||||
: entity;
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,6 @@ public sealed class LightningSystem : SharedLightningSystem
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Looks for objects with a LightningTarget component in the radius, prioritizes them, and hits the highest priority targets with lightning.
|
||||
/// </summary>
|
||||
@@ -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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
|
||||
|
||||
namespace Content.Server.Magic.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Spellbooks for having an entity learn spells as long as they've read the book and it's in their hand.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class SpellbookComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// List of spells that this book has. This is a combination of the WorldSpells, EntitySpells, and InstantSpells.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public readonly List<EntityUid> Spells = new();
|
||||
|
||||
/// <summary>
|
||||
/// The three fields below is just used for initialization.
|
||||
/// </summary>
|
||||
[DataField("spells", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<int, EntityPrototype>))]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Dictionary<string, int> SpellActions = new();
|
||||
|
||||
[DataField("learnTime")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float LearnTime = .75f;
|
||||
|
||||
/// <summary>
|
||||
/// If true, the spell action stays even after the book is removed
|
||||
/// </summary>
|
||||
[DataField("learnPermanently")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool LearnPermanently;
|
||||
}
|
||||
@@ -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;
|
||||
/// </summary>
|
||||
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<SpellbookComponent, MapInitEvent>(OnInit);
|
||||
SubscribeLocalEvent<SpellbookComponent, UseInHandEvent>(OnUse);
|
||||
SubscribeLocalEvent<SpellbookComponent, SpellbookDoAfterEvent>(OnDoAfter);
|
||||
|
||||
SubscribeLocalEvent<InstantSpawnSpellEvent>(OnInstantSpawn);
|
||||
SubscribeLocalEvent<TeleportSpellEvent>(OnTeleportSpell);
|
||||
SubscribeLocalEvent<KnockSpellEvent>(OnKnockSpell);
|
||||
@@ -67,73 +60,8 @@ public sealed class MagicSystem : EntitySystem
|
||||
SubscribeLocalEvent<ChangeComponentsSpellEvent>(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
|
||||
|
||||
/// <summary>
|
||||
/// Handles the instant action (i.e. on the caster) attempting to spawn an entity.
|
||||
/// </summary>
|
||||
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<PreventCollideComponent>(ent);
|
||||
comp.Uid = args.Performer;
|
||||
}
|
||||
if (!args.PreventCollideWithCaster)
|
||||
continue;
|
||||
|
||||
var comp = EnsureComp<PreventCollideComponent>(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<EntityCoordinates> GetSpawnPositions(TransformComponent casterXform, MagicSpawnData data)
|
||||
{
|
||||
switch (data)
|
||||
{
|
||||
case TargetCasterPos:
|
||||
return new List<EntityCoordinates>(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<EntityCoordinates>();
|
||||
|
||||
if (!directionPos.TryGetTileRef(out var tileReference, EntityManager, _mapManager))
|
||||
return new List<EntityCoordinates>();
|
||||
|
||||
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<EntityCoordinates>(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<EntityCoordinates>(3)
|
||||
{
|
||||
coords,
|
||||
coordsPlus,
|
||||
coordsMinus,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return new List<EntityCoordinates>();
|
||||
}
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Teleports the user to the clicked location
|
||||
/// </summary>
|
||||
/// <param name="args"></param>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens all doors within range
|
||||
/// </summary>
|
||||
/// <param name="args"></param>
|
||||
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<DoorBoltComponent>(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<BodyComponent>(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<BodyComponent>(part) && !HasComp<BrainComponent>(part)))
|
||||
{
|
||||
// just leaves a brain and clothes
|
||||
if (HasComp<BodyComponent>(part) && !HasComp<BrainComponent>(part))
|
||||
{
|
||||
QueueDel(part);
|
||||
}
|
||||
QueueDel(part);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawns entity prototypes from a list within range of click.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// It will offset mobs after the first mob based on the OffsetVector2 property supplied.
|
||||
/// </remarks>
|
||||
/// <param name="args"> The Spawn Spell Event args.</param>
|
||||
private void OnWorldSpawn(WorldSpawnSpellEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
@@ -373,24 +220,85 @@ public sealed class MagicSystem : EntitySystem
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loops through a supplied list of entity prototypes and spawns them
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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
|
||||
/// </remarks>
|
||||
/// <param name="entityEntries"> The list of Entities to spawn in</param>
|
||||
/// <param name="entityCoords"> Map Coordinates where the entities will spawn</param>
|
||||
/// <param name="lifetime"> Check to see if the entities should self delete</param>
|
||||
/// <param name="offsetVector2"> A Vector2 offset that the entities will spawn in</param>
|
||||
private void SpawnSpellHelper(List<EntitySpawnEntry> entityEntries, EntityCoordinates entityCoords, float? lifetime, Vector2 offsetVector2)
|
||||
#endregion
|
||||
|
||||
#region Helpers
|
||||
|
||||
public List<EntityCoordinates> 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<EntityCoordinates> GetCasterPosition(TransformComponent casterXform)
|
||||
{
|
||||
return new List<EntityCoordinates>(1) { casterXform.Coordinates };
|
||||
}
|
||||
|
||||
public List<EntityCoordinates> GetPositionsInFront(TransformComponent casterXform)
|
||||
{
|
||||
var directionPos = casterXform.Coordinates.Offset(casterXform.LocalRotation.ToWorldVec().Normalized());
|
||||
|
||||
if (!TryComp<MapGridComponent>(casterXform.GridUid, out var mapGrid) ||
|
||||
!directionPos.TryGetTileRef(out var tileReference, EntityManager, _mapManager))
|
||||
{
|
||||
return new List<EntityCoordinates>();
|
||||
}
|
||||
|
||||
var tileIndex = tileReference.Value.GridIndices;
|
||||
var coords = _mapSystem.GridTileToLocal(casterXform.GridUid.Value, mapGrid, tileIndex);
|
||||
|
||||
var directions = GetCardinalDirections(casterXform.LocalRotation.GetCardinalDir());
|
||||
var spawnPositions = new List<EntityCoordinates>(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<Direction> 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<Direction>();
|
||||
}
|
||||
}
|
||||
|
||||
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<EntitySpawnEntry> 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
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<StealthComponent>(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<VisibilityComponent>(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<StealthComponent>(uid);
|
||||
_movement.RefreshMovementSpeedModifiers(uid);
|
||||
}
|
||||
|
||||
|
||||
183
Content.Server/_White/Wizard/Charging/ChargingSystem.cs
Normal file
183
Content.Server/_White/Wizard/Charging/ChargingSystem.cs
Normal file
@@ -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<EntityUid, List<EntityUid>> _charges = new();
|
||||
|
||||
private readonly Dictionary<EntityUid, EntityUid> _chargingLoops = new();
|
||||
private readonly Dictionary<EntityUid, EntityUid> _chargedLoop = new();
|
||||
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeNetworkEvent<RequestSpellChargingAudio>(OnCharging);
|
||||
SubscribeNetworkEvent<RequestSpellChargedAudio>(OnCharged);
|
||||
SubscribeNetworkEvent<RequestAudioSpellStop>(OnStop);
|
||||
SubscribeLocalEvent<PlayerDetachedEvent>(OnDetach);
|
||||
|
||||
SubscribeNetworkEvent<AddWizardChargeEvent>(Add);
|
||||
SubscribeNetworkEvent<RemoveWizardChargeEvent>(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<EntityUid>();
|
||||
}
|
||||
|
||||
_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
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Content.Server._White.Wizard.Magic.Amaterasu;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class AmaterasuComponent : Component
|
||||
{
|
||||
}
|
||||
@@ -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<AmaterasuComponent, MobStateChangedEvent>(OnMobState);
|
||||
}
|
||||
|
||||
private void OnMobState(EntityUid uid, AmaterasuComponent component, MobStateChangedEvent args)
|
||||
{
|
||||
if (args.NewMobState is MobState.Critical or MobState.Dead)
|
||||
{
|
||||
if(!TryComp<FlammableComponent>(uid, out var flammable))
|
||||
return;
|
||||
|
||||
if (flammable.OnFire)
|
||||
{
|
||||
_bodySystem.GibBody(uid);
|
||||
return;
|
||||
}
|
||||
|
||||
RemComp<AmaterasuComponent>(uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Content.Server._White.Wizard.Magic.Other;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class InstantRecallComponent : Component
|
||||
{
|
||||
public EntityUid? Item;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace Content.Server._White.Wizard.Magic.TeslaProjectile;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class TeslaProjectileComponent : Component {}
|
||||
@@ -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<TeslaProjectileComponent, ProjectileHitEvent>(OnStartCollide);
|
||||
}
|
||||
|
||||
private void OnStartCollide(Entity<TeslaProjectileComponent> ent, ref ProjectileHitEvent args)
|
||||
{
|
||||
_lightning.ShootRandomLightnings(ent, 2, 4, arcDepth:2);
|
||||
}
|
||||
}
|
||||
701
Content.Server/_White/Wizard/Magic/WizardSpellsSystem.cs
Normal file
701
Content.Server/_White/Wizard/Magic/WizardSpellsSystem.cs
Normal file
@@ -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<InstantRecallSpellEvent>(OnInstantRecallSpell);
|
||||
SubscribeLocalEvent<MimeTouchSpellEvent>(OnMimeTouchSpell);
|
||||
SubscribeLocalEvent<BananaTouchSpellEvent>(OnBananaTouchSpell);
|
||||
SubscribeLocalEvent<CluwneCurseSpellEvent>(OnCluwneCurseSpell);
|
||||
SubscribeLocalEvent<EmpSpellEvent>(OnEmpSpell);
|
||||
SubscribeLocalEvent<EtherealJauntSpellEvent>(OnJauntSpell);
|
||||
SubscribeLocalEvent<BlinkSpellEvent>(OnBlinkSpell);
|
||||
SubscribeLocalEvent<ForceWallSpellEvent>(OnForcewallSpell);
|
||||
SubscribeLocalEvent<CardsSpellEvent>(OnCardsSpell);
|
||||
SubscribeLocalEvent<FireballSpellEvent>(OnFireballSpell);
|
||||
SubscribeLocalEvent<ForceSpellEvent>(OnForceSpell);
|
||||
SubscribeLocalEvent<ArcSpellEvent>(OnArcSpell);
|
||||
|
||||
SubscribeLocalEvent<MagicComponent, BeforeCastSpellEvent>(OnBeforeCastSpell);
|
||||
}
|
||||
|
||||
#region Instant Recall
|
||||
|
||||
private void OnInstantRecallSpell(InstantRecallSpellEvent msg)
|
||||
{
|
||||
if (msg.Handled || !CheckRequirements(msg.Action, msg.Performer))
|
||||
return;
|
||||
|
||||
if (!TryComp<HandsComponent>(msg.Performer, out var handsComponent))
|
||||
return;
|
||||
|
||||
if (!TryComp<InstantRecallComponent>(msg.Action, out var recallComponent))
|
||||
{
|
||||
_popupSystem.PopupEntity("Что-то поломалось!", msg.Performer, msg.Performer);
|
||||
return;
|
||||
}
|
||||
|
||||
if (handsComponent.ActiveHandEntity != null)
|
||||
{
|
||||
if (HasComp<VirtualItemComponent>(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<HumanoidAppearanceComponent>(msg.Target))
|
||||
{
|
||||
_popupSystem.PopupEntity("Работает только на людях!", msg.Performer, msg.Performer);
|
||||
return;
|
||||
}
|
||||
|
||||
SetOutfitCommand.SetOutfit(msg.Target, "MimeGear", EntityManager);
|
||||
EnsureComp<MimePowersComponent>(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<HumanoidAppearanceComponent>(msg.Target))
|
||||
{
|
||||
_popupSystem.PopupEntity("Работает только на людях!", msg.Performer, msg.Performer);
|
||||
return;
|
||||
}
|
||||
|
||||
SetOutfitCommand.SetOutfit(msg.Target, "ClownGear", EntityManager);
|
||||
EnsureComp<ClumsyComponent>(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<HumanoidAppearanceComponent>(msg.Target))
|
||||
{
|
||||
_popupSystem.PopupEntity("Работает только на людях!", msg.Performer, msg.Performer);
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureComp<CluwneComponent>(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<IncorporealComponent>(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<PreventCollideComponent>(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<PreventCollideComponent>(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<PreventCollideComponent>(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<ItemComponent>(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<FlammableComponent>(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<FlammableComponent>(msg.TargetUid, out var flammableComponent))
|
||||
return;
|
||||
|
||||
flammableComponent.FireStacks += 4;
|
||||
|
||||
_flammableSystem.Ignite(msg.TargetUid, msg.Performer);
|
||||
|
||||
EnsureComp<AmaterasuComponent>(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<MobStateComponent>).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<EntityCoordinates> GetArenaPositions(TransformComponent casterXform, int arenaSize)
|
||||
{
|
||||
var positions = new List<EntityCoordinates>();
|
||||
|
||||
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<MagicComponent> 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<WizardClothesComponent>(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
|
||||
}
|
||||
8
Content.Server/_White/Wizard/Scrolls/ScrollSystem.cs
Normal file
8
Content.Server/_White/Wizard/Scrolls/ScrollSystem.cs
Normal file
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user