Залью спеллы потестить, мне похуй ПР НОМЕР 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:
rhailrake
2024-03-07 16:01:54 +00:00
committed by GitHub
parent d944178a7b
commit c08cdeb84d
99 changed files with 3461 additions and 406 deletions

View 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;
}

View 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";
}

View 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;
}
}

View File

@@ -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++;
}
}
}

View File

@@ -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;
}

View File

@@ -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
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}

View 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
}

View File

@@ -0,0 +1,6 @@
namespace Content.Server._White.Wizard.Magic.Amaterasu;
[RegisterComponent]
public sealed partial class AmaterasuComponent : Component
{
}

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,7 @@
namespace Content.Server._White.Wizard.Magic.Other;
[RegisterComponent]
public sealed partial class InstantRecallComponent : Component
{
public EntityUid? Item;
}

View File

@@ -0,0 +1,4 @@
namespace Content.Server._White.Wizard.Magic.TeslaProjectile;
[RegisterComponent]
public sealed partial class TeslaProjectileComponent : Component {}

View File

@@ -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);
}
}

View 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
}

View 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);
}