2024-03-07 16:01:54 +00:00
|
|
|
using System.Linq;
|
2023-07-08 14:08:32 +10:00
|
|
|
using System.Numerics;
|
2024-06-10 10:57:32 +00:00
|
|
|
using Content.Server._White.Wizard.Magic;
|
2022-06-23 05:19:32 -07:00
|
|
|
using Content.Server.Body.Components;
|
2022-10-23 00:46:28 +02:00
|
|
|
using Content.Server.Body.Systems;
|
2023-04-26 16:04:44 +12:00
|
|
|
using Content.Server.Chat.Systems;
|
2022-09-29 15:51:59 +10:00
|
|
|
using Content.Server.Weapons.Ranged.Systems;
|
2022-05-29 02:29:10 -04:00
|
|
|
using Content.Shared.Actions;
|
2022-06-23 05:19:32 -07:00
|
|
|
using Content.Shared.Body.Components;
|
2023-06-29 08:35:54 -04:00
|
|
|
using Content.Shared.Coordinates.Helpers;
|
2022-05-29 02:29:10 -04:00
|
|
|
using Content.Shared.Doors.Components;
|
|
|
|
|
using Content.Shared.Doors.Systems;
|
2024-06-22 12:55:50 +00:00
|
|
|
using Content.Shared.Lock;
|
2023-04-03 13:13:48 +12:00
|
|
|
using Content.Shared.Magic;
|
2023-09-08 18:16:05 -07:00
|
|
|
using Content.Shared.Magic.Events;
|
2022-05-29 02:29:10 -04:00
|
|
|
using Content.Shared.Maps;
|
|
|
|
|
using Content.Shared.Physics;
|
|
|
|
|
using Content.Shared.Storage;
|
2022-12-10 16:45:18 +13:00
|
|
|
using Robust.Server.GameObjects;
|
2022-05-29 02:29:10 -04:00
|
|
|
using Robust.Shared.Audio;
|
2023-11-27 22:12:34 +11:00
|
|
|
using Robust.Shared.Audio.Systems;
|
2022-05-29 02:29:10 -04:00
|
|
|
using Robust.Shared.Map;
|
2024-03-07 16:01:54 +00:00
|
|
|
using Robust.Shared.Map.Components;
|
2024-01-31 21:48:39 +09:00
|
|
|
using Robust.Shared.Physics.Components;
|
2022-05-29 02:29:10 -04:00
|
|
|
using Robust.Shared.Random;
|
2023-01-02 13:01:40 +13:00
|
|
|
using Robust.Shared.Serialization.Manager;
|
2023-10-19 12:34:31 -07:00
|
|
|
using Robust.Shared.Spawners;
|
2022-05-29 02:29:10 -04:00
|
|
|
|
|
|
|
|
namespace Content.Server.Magic;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Handles learning and using spells (actions)
|
|
|
|
|
/// </summary>
|
|
|
|
|
public sealed class MagicSystem : EntitySystem
|
|
|
|
|
{
|
2024-03-07 16:01:54 +00:00
|
|
|
[Dependency] private readonly ISerializationManager _serializationManager = default!;
|
2023-01-02 13:01:40 +13:00
|
|
|
[Dependency] private readonly IComponentFactory _compFact = default!;
|
2022-05-29 02:29:10 -04:00
|
|
|
[Dependency] private readonly IMapManager _mapManager = default!;
|
2024-03-07 16:01:54 +00:00
|
|
|
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
|
2022-05-29 02:29:10 -04:00
|
|
|
[Dependency] private readonly IRobustRandom _random = default!;
|
2022-10-23 00:46:28 +02:00
|
|
|
[Dependency] private readonly BodySystem _bodySystem = default!;
|
2022-05-29 02:29:10 -04:00
|
|
|
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
|
|
|
|
[Dependency] private readonly SharedDoorSystem _doorSystem = default!;
|
2022-06-23 03:24:50 -07:00
|
|
|
[Dependency] private readonly GunSystem _gunSystem = default!;
|
2022-12-10 16:45:18 +13:00
|
|
|
[Dependency] private readonly PhysicsSystem _physics = default!;
|
2022-12-06 18:03:20 -05:00
|
|
|
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
2023-01-18 05:44:32 +11:00
|
|
|
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
2023-04-26 16:04:44 +12:00
|
|
|
[Dependency] private readonly ChatSystem _chat = default!;
|
2024-06-10 10:57:32 +00:00
|
|
|
[Dependency] private readonly WizardSpellsSystem _wizardSpells = default!;
|
2024-06-22 12:55:50 +00:00
|
|
|
[Dependency] private readonly LockSystem _lock = default!;
|
2022-12-10 16:45:18 +13:00
|
|
|
|
2022-05-29 02:29:10 -04:00
|
|
|
public override void Initialize()
|
|
|
|
|
{
|
|
|
|
|
base.Initialize();
|
|
|
|
|
|
|
|
|
|
SubscribeLocalEvent<InstantSpawnSpellEvent>(OnInstantSpawn);
|
|
|
|
|
SubscribeLocalEvent<TeleportSpellEvent>(OnTeleportSpell);
|
|
|
|
|
SubscribeLocalEvent<KnockSpellEvent>(OnKnockSpell);
|
2022-06-23 05:19:32 -07:00
|
|
|
SubscribeLocalEvent<SmiteSpellEvent>(OnSmiteSpell);
|
2022-05-29 02:29:10 -04:00
|
|
|
SubscribeLocalEvent<WorldSpawnSpellEvent>(OnWorldSpawn);
|
2022-06-23 03:24:50 -07:00
|
|
|
SubscribeLocalEvent<ProjectileSpellEvent>(OnProjectileSpell);
|
2023-01-02 13:01:40 +13:00
|
|
|
SubscribeLocalEvent<ChangeComponentsSpellEvent>(OnChangeComponentsSpell);
|
2022-05-29 02:29:10 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#region Spells
|
|
|
|
|
|
|
|
|
|
private void OnInstantSpawn(InstantSpawnSpellEvent args)
|
|
|
|
|
{
|
|
|
|
|
if (args.Handled)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
var transform = Transform(args.Performer);
|
|
|
|
|
|
|
|
|
|
foreach (var position in GetSpawnPositions(transform, args.Pos))
|
|
|
|
|
{
|
|
|
|
|
var ent = Spawn(args.Prototype, position.SnapToGrid(EntityManager, _mapManager));
|
|
|
|
|
|
2024-03-07 16:01:54 +00:00
|
|
|
if (!args.PreventCollideWithCaster)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
var comp = EnsureComp<PreventCollideComponent>(ent);
|
|
|
|
|
comp.Uid = args.Performer;
|
2022-05-29 02:29:10 -04:00
|
|
|
}
|
|
|
|
|
|
2023-04-26 16:04:44 +12:00
|
|
|
Speak(args);
|
2022-05-29 02:29:10 -04:00
|
|
|
args.Handled = true;
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-23 03:24:50 -07:00
|
|
|
private void OnProjectileSpell(ProjectileSpellEvent ev)
|
|
|
|
|
{
|
|
|
|
|
if (ev.Handled)
|
|
|
|
|
return;
|
|
|
|
|
|
2023-04-26 16:04:44 +12:00
|
|
|
ev.Handled = true;
|
|
|
|
|
Speak(ev);
|
|
|
|
|
|
2022-06-23 03:24:50 -07:00
|
|
|
var xform = Transform(ev.Performer);
|
2024-01-31 21:48:39 +09:00
|
|
|
|
2022-06-23 03:24:50 -07:00
|
|
|
foreach (var pos in GetSpawnPositions(xform, ev.Pos))
|
|
|
|
|
{
|
2024-03-07 16:01:54 +00:00
|
|
|
var mapPos = _transformSystem.ToMapCoordinates(pos);
|
|
|
|
|
var spawnCoords = _mapManager.TryFindGridAt(mapPos, out var gridUid, out var grid)
|
2023-05-28 23:22:44 +10:00
|
|
|
? pos.WithEntityId(gridUid, EntityManager)
|
2024-03-07 16:01:54 +00:00
|
|
|
: new EntityCoordinates(_mapManager.GetMapEntityId(mapPos.MapId), mapPos.Position);
|
2022-12-10 16:45:18 +13:00
|
|
|
|
2024-01-31 21:48:39 +09:00
|
|
|
var userVelocity = Vector2.Zero;
|
|
|
|
|
|
|
|
|
|
if (grid != null && TryComp(gridUid, out PhysicsComponent? physics))
|
|
|
|
|
userVelocity = physics.LinearVelocity;
|
|
|
|
|
|
2022-12-10 16:45:18 +13:00
|
|
|
var ent = Spawn(ev.Prototype, spawnCoords);
|
2023-05-01 04:29:18 -04:00
|
|
|
var direction = ev.Target.ToMapPos(EntityManager, _transformSystem) -
|
|
|
|
|
spawnCoords.ToMapPos(EntityManager, _transformSystem);
|
2023-12-06 01:21:26 +03:00
|
|
|
_gunSystem.ShootProjectile(ent, direction, userVelocity, ev.Performer, ev.Performer);
|
2022-06-23 03:24:50 -07:00
|
|
|
}
|
|
|
|
|
}
|
2023-01-02 13:01:40 +13:00
|
|
|
|
|
|
|
|
private void OnChangeComponentsSpell(ChangeComponentsSpellEvent ev)
|
|
|
|
|
{
|
2023-04-26 16:04:44 +12:00
|
|
|
if (ev.Handled)
|
|
|
|
|
return;
|
2024-03-07 16:01:54 +00:00
|
|
|
|
2023-04-26 16:04:44 +12:00
|
|
|
ev.Handled = true;
|
2024-03-07 16:01:54 +00:00
|
|
|
|
2023-04-26 16:04:44 +12:00
|
|
|
Speak(ev);
|
|
|
|
|
|
2023-01-02 13:01:40 +13:00
|
|
|
foreach (var toRemove in ev.ToRemove)
|
|
|
|
|
{
|
|
|
|
|
if (_compFact.TryGetRegistration(toRemove, out var registration))
|
|
|
|
|
RemComp(ev.Target, registration.Type);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (var (name, data) in ev.ToAdd)
|
|
|
|
|
{
|
|
|
|
|
if (HasComp(ev.Target, data.Component.GetType()))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
var component = (Component) _compFact.GetComponent(name);
|
|
|
|
|
var temp = (object) component;
|
2024-03-07 16:01:54 +00:00
|
|
|
_serializationManager.CopyTo(data.Component, ref temp);
|
2023-01-02 13:01:40 +13:00
|
|
|
EntityManager.AddComponent(ev.Target, (Component) temp!);
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-06-23 03:24:50 -07:00
|
|
|
|
2022-05-29 02:29:10 -04:00
|
|
|
private void OnTeleportSpell(TeleportSpellEvent args)
|
|
|
|
|
{
|
|
|
|
|
if (args.Handled)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
var transform = Transform(args.Performer);
|
|
|
|
|
|
2024-03-07 16:01:54 +00:00
|
|
|
if (transform.MapID != args.Target.GetMapId(EntityManager))
|
|
|
|
|
return;
|
2022-05-29 02:29:10 -04:00
|
|
|
|
2022-12-06 18:03:20 -05:00
|
|
|
_transformSystem.SetCoordinates(args.Performer, args.Target);
|
2024-03-07 16:01:54 +00:00
|
|
|
_transformSystem.AttachToGridOrMap(args.Performer);
|
2023-01-18 05:44:32 +11:00
|
|
|
_audio.PlayPvs(args.BlinkSound, args.Performer, AudioParams.Default.WithVolume(args.BlinkVolume));
|
2023-04-26 16:04:44 +12:00
|
|
|
Speak(args);
|
2022-05-29 02:29:10 -04:00
|
|
|
args.Handled = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void OnKnockSpell(KnockSpellEvent args)
|
|
|
|
|
{
|
2024-06-10 10:57:32 +00:00
|
|
|
if (!_wizardSpells.CanCast(args)) // WD EDIT
|
2022-05-29 02:29:10 -04:00
|
|
|
return;
|
|
|
|
|
|
2024-07-01 11:23:51 +00:00
|
|
|
_wizardSpells.Cast(args); // WD EDIT
|
2023-04-26 16:04:44 +12:00
|
|
|
|
2022-05-29 02:29:10 -04:00
|
|
|
var transform = Transform(args.Performer);
|
|
|
|
|
var coords = transform.Coordinates;
|
|
|
|
|
|
2023-01-18 05:44:32 +11:00
|
|
|
_audio.PlayPvs(args.KnockSound, args.Performer, AudioParams.Default.WithVolume(args.KnockVolume));
|
2022-05-29 02:29:10 -04:00
|
|
|
|
|
|
|
|
foreach (var entity in _lookup.GetEntitiesInRange(coords, args.Range))
|
|
|
|
|
{
|
2023-06-01 02:23:35 +12:00
|
|
|
if (TryComp<DoorBoltComponent>(entity, out var bolts))
|
2024-02-22 18:01:31 -05:00
|
|
|
_doorSystem.SetBoltsDown((entity, bolts), false);
|
2022-05-29 02:29:10 -04:00
|
|
|
|
|
|
|
|
if (TryComp<DoorComponent>(entity, out var doorComp) && doorComp.State is not DoorState.Open)
|
2023-10-19 12:34:31 -07:00
|
|
|
_doorSystem.StartOpening(entity);
|
2024-06-22 12:55:50 +00:00
|
|
|
|
|
|
|
|
if (TryComp<LockComponent>(entity, out var lockComp) && lockComp.Locked)
|
|
|
|
|
_lock.Unlock(entity, null, lockComp);
|
2022-05-29 02:29:10 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-23 05:19:32 -07:00
|
|
|
private void OnSmiteSpell(SmiteSpellEvent ev)
|
|
|
|
|
{
|
2024-06-10 10:57:32 +00:00
|
|
|
if (!_wizardSpells.CanCast(ev)) // WD EDIT
|
2022-06-23 05:19:32 -07:00
|
|
|
return;
|
|
|
|
|
|
2024-07-01 11:23:51 +00:00
|
|
|
_wizardSpells.Cast(ev); // WD EDIT
|
2023-04-26 16:04:44 +12:00
|
|
|
|
2024-03-07 16:01:54 +00:00
|
|
|
var direction = _transformSystem.GetMapCoordinates(ev.Target).Position - _transformSystem.GetMapCoordinates(ev.Performer).Position;
|
2022-07-09 05:49:30 -04:00
|
|
|
var impulseVector = direction * 10000;
|
2023-01-15 15:38:59 +11:00
|
|
|
|
|
|
|
|
_physics.ApplyLinearImpulse(ev.Target, impulseVector);
|
2022-07-09 05:49:30 -04:00
|
|
|
|
2022-06-23 05:19:32 -07:00
|
|
|
if (!TryComp<BodyComponent>(ev.Target, out var body))
|
|
|
|
|
return;
|
|
|
|
|
|
2024-03-07 16:01:54 +00:00
|
|
|
var entities = _bodySystem.GibBody(ev.Target, true, body);
|
2022-06-23 05:19:32 -07:00
|
|
|
|
|
|
|
|
if (!ev.DeleteNonBrainParts)
|
|
|
|
|
return;
|
|
|
|
|
|
2024-03-07 16:01:54 +00:00
|
|
|
foreach (var part in entities.Where(part => HasComp<BodyComponent>(part) && !HasComp<BrainComponent>(part)))
|
2022-06-23 05:19:32 -07:00
|
|
|
{
|
2024-03-07 16:01:54 +00:00
|
|
|
QueueDel(part);
|
2022-06-23 05:19:32 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-29 02:29:10 -04:00
|
|
|
private void OnWorldSpawn(WorldSpawnSpellEvent args)
|
|
|
|
|
{
|
|
|
|
|
if (args.Handled)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
var targetMapCoords = args.Target;
|
|
|
|
|
|
|
|
|
|
SpawnSpellHelper(args.Contents, targetMapCoords, args.Lifetime, args.Offset);
|
2023-04-26 16:04:44 +12:00
|
|
|
Speak(args);
|
2022-05-29 02:29:10 -04:00
|
|
|
args.Handled = true;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-07 16:01:54 +00:00
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Helpers
|
|
|
|
|
|
|
|
|
|
public List<EntityCoordinates> GetSpawnPositions(TransformComponent casterXform, MagicSpawnData data)
|
|
|
|
|
{
|
|
|
|
|
return data switch
|
|
|
|
|
{
|
|
|
|
|
TargetCasterPos => GetCasterPosition(casterXform),
|
|
|
|
|
TargetInFront => GetPositionsInFront(casterXform),
|
|
|
|
|
_ => throw new ArgumentOutOfRangeException()
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public List<EntityCoordinates> GetCasterPosition(TransformComponent casterXform)
|
|
|
|
|
{
|
2024-03-26 15:52:23 +07:00
|
|
|
return [casterXform.Coordinates];
|
2024-03-07 16:01:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public List<EntityCoordinates> GetPositionsInFront(TransformComponent casterXform)
|
2022-05-29 02:29:10 -04:00
|
|
|
{
|
2024-03-07 16:01:54 +00:00
|
|
|
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);
|
2022-05-29 02:29:10 -04:00
|
|
|
|
2022-12-06 18:03:20 -05:00
|
|
|
var offsetCoords = entityCoords;
|
2024-03-07 16:01:54 +00:00
|
|
|
foreach (var proto in getPrototypes)
|
2022-05-29 02:29:10 -04:00
|
|
|
{
|
|
|
|
|
// TODO: Share this code with instant because they're both doing similar things for positioning.
|
|
|
|
|
var entity = Spawn(proto, offsetCoords);
|
|
|
|
|
offsetCoords = offsetCoords.Offset(offsetVector2);
|
|
|
|
|
|
|
|
|
|
if (lifetime != null)
|
|
|
|
|
{
|
|
|
|
|
var comp = EnsureComp<TimedDespawnComponent>(entity);
|
|
|
|
|
comp.Lifetime = lifetime.Value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-26 16:04:44 +12:00
|
|
|
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);
|
|
|
|
|
}
|
2024-03-07 16:01:54 +00:00
|
|
|
|
|
|
|
|
#endregion
|
2022-05-29 02:29:10 -04:00
|
|
|
}
|