diff --git a/Content.Server/Lightning/Components/LightningComponent.cs b/Content.Server/Lightning/Components/LightningComponent.cs
index 138d7010d4..f1a4d3bf84 100644
--- a/Content.Server/Lightning/Components/LightningComponent.cs
+++ b/Content.Server/Lightning/Components/LightningComponent.cs
@@ -1,4 +1,4 @@
-using Content.Shared.Lightning.Components;
+using Content.Shared.Lightning.Components;
namespace Content.Server.Lightning.Components;
[RegisterComponent]
diff --git a/Content.Server/Lightning/Components/LightningTargetComponent.cs b/Content.Server/Lightning/Components/LightningTargetComponent.cs
new file mode 100644
index 0000000000..e85993562c
--- /dev/null
+++ b/Content.Server/Lightning/Components/LightningTargetComponent.cs
@@ -0,0 +1,59 @@
+using Content.Server.Tesla.EntitySystems;
+using Content.Shared.Explosion;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.Lightning.Components;
+
+///
+/// This component allows the lightning system to select a given entity as the target of a lightning strike.
+/// It also determines the priority of selecting this target, and the behavior of the explosion. Used for tesla.
+///
+[RegisterComponent, Access(typeof(LightningSystem), typeof(LightningTargetSystem))]
+public sealed partial class LightningTargetComponent : Component
+{
+ ///
+ /// Priority level for selecting a lightning target.
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public int Priority;
+
+ ///
+ /// Lightning has a number of bounces into neighboring targets.
+ /// This number controls how many bounces the lightning bolt has left after hitting that target.
+ /// At high values, the lightning will not travel farther than that entity.
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public int LightningResistance = 1; //by default, reduces the number of bounces from this target by 1
+
+ // BOOM PART
+
+ ///
+ /// Will the entity explode after being struck by lightning?
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public bool LightningExplode = true;
+
+ ///
+ /// The explosion prototype to spawn
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public ProtoId ExplosionPrototype = "Default";
+
+ ///
+ /// The total amount of intensity an explosion can achieve
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public float TotalIntensity = 25f;
+
+ ///
+ /// How quickly does the explosion's power slope? Higher = smaller area and more concentrated damage, lower = larger area and more spread out damage
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public float Dropoff = 2f;
+
+ ///
+ /// How much intensity can be applied per tile?
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public float MaxTileIntensity = 5f;
+}
diff --git a/Content.Server/Lightning/LightningSystem.cs b/Content.Server/Lightning/LightningSystem.cs
index 8d82275b0a..00704df1e0 100644
--- a/Content.Server/Lightning/LightningSystem.cs
+++ b/Content.Server/Lightning/LightningSystem.cs
@@ -1,27 +1,34 @@
-using System.Linq;
+using System.Linq;
using Content.Server.Beam;
using Content.Server.Beam.Components;
using Content.Server.Lightning.Components;
using Content.Shared.Lightning;
using Robust.Server.GameObjects;
-using Robust.Shared.Physics;
-using Robust.Shared.Physics.Dynamics;
-using Robust.Shared.Physics.Events;
using Robust.Shared.Random;
namespace Content.Server.Lightning;
+// TheShuEd:
+//I've redesigned the lightning system to be more optimized.
+//Previously, each lightning element, when it touched something, would try to branch into nearby entities.
+//So if a lightning bolt was 20 entities long, each one would check its surroundings and have a chance to create additional lightning...
+//which could lead to recursive creation of more and more lightning bolts and checks.
+
+//I redesigned so that lightning branches can only be created from the point where the lightning struck, no more collide checks
+//and the number of these branches is explicitly controlled in the new function.
public sealed class LightningSystem : SharedLightningSystem
{
- [Dependency] private readonly PhysicsSystem _physics = default!;
[Dependency] private readonly BeamSystem _beam = default!;
[Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly EntityLookupSystem _lookup = default!;
+
+ private List> _lookupTargetsList = new();
+ private HashSet> _lookupTargets = new();
public override void Initialize()
{
base.Initialize();
- SubscribeLocalEvent(OnCollide);
SubscribeLocalEvent(OnRemove);
}
@@ -35,32 +42,6 @@ public sealed class LightningSystem : SharedLightningSystem
beamController.CreatedBeams.Remove(uid);
}
- private void OnCollide(EntityUid uid, LightningComponent component, ref StartCollideEvent args)
- {
- if (!TryComp(uid, out var lightningBeam)
- || !TryComp(lightningBeam.VirtualBeamController, out var beamController))
- return;
-
- if (!component.CanArc || beamController.CreatedBeams.Count >= component.MaxTotalArcs)
- return;
-
- Arc(component, args.OtherEntity, lightningBeam.VirtualBeamController.Value);
-
- if (component.ArcTarget == null)
- return;
-
- var spriteState = LightningRandomizer();
- component.ArcTargets.Add(args.OtherEntity);
- component.ArcTargets.Add(component.ArcTarget.Value);
-
- _beam.TryCreateBeam(
- args.OtherEntity,
- component.ArcTarget.Value,
- component.LightningPrototype,
- spriteState,
- controller: lightningBeam.VirtualBeamController.Value);
- }
-
///
/// Fires lightning from user to target
///
@@ -71,68 +52,47 @@ public sealed class LightningSystem : SharedLightningSystem
{
var spriteState = LightningRandomizer();
_beam.TryCreateBeam(user, target, lightningPrototype, spriteState);
+
+ var ev = new HitByLightningEvent(user, target);
+ RaiseLocalEvent(target, ref ev);
}
///
- /// Looks for a target to arc to in all 8 directions, adds the closest to a local dictionary and picks at random
+ /// Looks for objects with a LightningTarget component in the radius, prioritizes them, and hits the highest priority targets with lightning.
///
- ///
- ///
- ///
- private void Arc(LightningComponent component, EntityUid target, EntityUid controllerEntity)
+ /// Where the lightning fires from
+ /// Targets selection radius
+ /// Number of lightning bolts
+ /// The prototype for the lightning to be created
+ /// how many times to recursively fire lightning bolts from the target points of the first shot.
+ public void ShootRandomLightnings(EntityUid user, float range, int boltCount, string lightningPrototype = "Lightning", int arcDepth = 0)
{
- if (!TryComp(controllerEntity, out var controller))
+ //To Do: add support to different priority target tablem for different lightning types
+ //To Do: Remove Hardcode LightningTargetComponent (this should be a parameter of the SharedLightningComponent)
+ //To Do: This is still pretty bad for perf but better than before and at least it doesn't re-allocate
+ // several hashsets every time
+
+ var targets = _lookup.GetComponentsInRange(Transform(user).MapPosition, range).ToList();
+ _random.Shuffle(targets);
+ targets.Sort((x, y) => y.Priority.CompareTo(x.Priority));
+ var realCount = Math.Min(targets.Count, boltCount);
+ if (realCount <= 0)
return;
-
- var targetXForm = Transform(target);
- var directions = Enum.GetValues().Length;
-
- var lightningQuery = GetEntityQuery();
- var beamQuery = GetEntityQuery();
- var xformQuery = GetEntityQuery();
-
- Dictionary arcDirections = new();
-
- //TODO: Add scoring system for the Tesla PR which will have grounding rods
- for (int i = 0; i < directions; i++)
+ for (int i = 0; i < realCount; i++)
{
- var direction = (Direction) i;
- var (targetPos, targetRot) = targetXForm.GetWorldPositionRotation(xformQuery);
- var dirRad = direction.ToAngle() + targetRot;
- var ray = new CollisionRay(targetPos, dirRad.ToVec(), component.CollisionMask);
- var rayCastResults = _physics.IntersectRay(targetXForm.MapID, ray, component.MaxLength, target, false).ToList();
-
- RayCastResults? closestResult = null;
-
- foreach (var result in rayCastResults)
+ ShootLightning(user, targets[i].Owner, lightningPrototype);
+ if (arcDepth > 0)
{
- if (lightningQuery.HasComponent(result.HitEntity)
- || beamQuery.HasComponent(result.HitEntity)
- || component.ArcTargets.Contains(result.HitEntity)
- || controller.HitTargets.Contains(result.HitEntity)
- || controller.BeamShooter == result.HitEntity)
- {
- continue;
- }
-
- closestResult = result;
- break;
+ ShootRandomLightnings(targets[i].Owner, range, 1, lightningPrototype, arcDepth - targets[i].LightningResistance);
}
-
- if (closestResult == null)
- {
- continue;
- }
-
- arcDirections.Add(direction, closestResult.Value.HitEntity);
- }
-
- var randomDirection = (Direction) _random.Next(0, 7);
-
- if (arcDirections.ContainsKey(randomDirection))
- {
- component.ArcTarget = arcDirections.GetValueOrDefault(randomDirection);
- arcDirections.Clear();
}
}
}
+
+///
+/// Raised directed on the target when an entity becomes the target of a lightning strike (not when touched)
+///
+/// The entity that created the lightning
+/// The entity that was struck by lightning.
+[ByRefEvent]
+public readonly record struct HitByLightningEvent(EntityUid Source, EntityUid Target);
diff --git a/Content.Server/Lightning/LightningTargetSystem.cs b/Content.Server/Lightning/LightningTargetSystem.cs
new file mode 100644
index 0000000000..f9e564a76a
--- /dev/null
+++ b/Content.Server/Lightning/LightningTargetSystem.cs
@@ -0,0 +1,34 @@
+using Content.Server.Explosion.EntitySystems;
+using Content.Server.Lightning;
+using Content.Server.Lightning.Components;
+
+namespace Content.Server.Tesla.EntitySystems;
+
+///
+/// The component allows lightning to strike this target. And determining the behavior of the target when struck by lightning.
+///
+public sealed class LightningTargetSystem : EntitySystem
+{
+ [Dependency] private readonly ExplosionSystem _explosionSystem = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnHitByLightning);
+ }
+
+ private void OnHitByLightning(Entity uid, ref HitByLightningEvent args)
+ {
+
+ if (!uid.Comp.LightningExplode)
+ return;
+
+ _explosionSystem.QueueExplosion(
+ Transform(uid).MapPosition,
+ uid.Comp.ExplosionPrototype,
+ uid.Comp.TotalIntensity, uid.Comp.Dropoff,
+ uid.Comp.MaxTileIntensity,
+ canCreateVacuum: false);
+ }
+}
diff --git a/Content.Server/Physics/Components/ChaoticJumpComponent.cs b/Content.Server/Physics/Components/ChaoticJumpComponent.cs
new file mode 100644
index 0000000000..be6fb46aae
--- /dev/null
+++ b/Content.Server/Physics/Components/ChaoticJumpComponent.cs
@@ -0,0 +1,54 @@
+using Content.Server.Physics.Controllers;
+using Content.Shared.Physics;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Server.Physics.Components;
+
+///
+/// A component which makes its entity periodically chaotic jumps arounds
+///
+[RegisterComponent, Access(typeof(ChaoticJumpSystem))]
+public sealed partial class ChaoticJumpComponent : Component
+{
+ ///
+ /// The next moment in time when the entity is pushed toward its goal
+ ///
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
+ public TimeSpan NextJumpTime;
+
+ ///
+ /// Minimum interval between jumps
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public float JumpMinInterval = 5f;
+ ///
+ /// Maximum interval between jumps
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public float JumpMaxInterval = 15f;
+
+ ///
+ /// collision limits for which it is impossible to make a jump
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public int CollisionMask = (int) CollisionGroup.Impassable;
+
+ ///
+ /// Minimum jump range
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public float RangeMin = 5f;
+
+ ///
+ /// Maximum jump range
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public float RangeMax = 10f;
+
+ ///
+ /// Spawn before jump
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public EntProtoId Effect = "EffectEmpPulse";
+}
diff --git a/Content.Server/Physics/Components/ChasingWalkComponent.cs b/Content.Server/Physics/Components/ChasingWalkComponent.cs
new file mode 100644
index 0000000000..ca7a027544
--- /dev/null
+++ b/Content.Server/Physics/Components/ChasingWalkComponent.cs
@@ -0,0 +1,79 @@
+
+using Content.Server.Physics.Controllers;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Server.Physics.Components;
+
+///
+/// A component which makes its entity chasing entity with selected component.
+///
+[RegisterComponent, Access(typeof(ChasingWalkSystem))]
+public sealed partial class ChasingWalkComponent : Component
+{
+ ///
+ /// The next moment in time when the entity is pushed toward its goal
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public TimeSpan NextImpulseTime;
+
+ ///
+ /// Push-to-target frequency.
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public float ImpulseInterval = 2f;
+
+ ///
+ /// The minimum speed at which this entity will move.
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public float MinSpeed = 1.5f;
+
+ ///
+ /// The maximum speed at which this entity will move.
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public float MaxSpeed = 3f;
+
+ ///
+ /// The current speed.
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public float Speed;
+
+ ///
+ /// The minimum time interval in which an object can change its motion target.
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public float ChangeVectorMinInterval = 5f;
+
+ ///
+ /// The maximum time interval in which an object can change its motion target.
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public float ChangeVectorMaxInterval = 25f;
+
+ ///
+ /// The next change of direction time.
+ ///
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
+ public TimeSpan NextChangeVectorTime;
+
+ ///
+ /// The component that the entity is chasing
+ ///
+ [DataField(required: true)]
+ public ComponentRegistry ChasingComponent = default!;
+
+ ///
+ /// The maximum radius in which the entity chooses the target component to follow
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public float MaxChaseRadius = 25;
+
+ ///
+ /// The entity uid, chasing by the component owner
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public EntityUid? ChasingEntity;
+}
diff --git a/Content.Server/Physics/Controllers/ChaoticJumpSystem.cs b/Content.Server/Physics/Controllers/ChaoticJumpSystem.cs
new file mode 100644
index 0000000000..ee4b776cf0
--- /dev/null
+++ b/Content.Server/Physics/Controllers/ChaoticJumpSystem.cs
@@ -0,0 +1,78 @@
+using Content.Server.Physics.Components;
+using Robust.Shared.Random;
+using Robust.Shared.Timing;
+using Robust.Shared.Physics.Systems;
+using Robust.Shared.Physics;
+using System.Numerics;
+using Robust.Shared.Physics.Controllers;
+using Robust.Shared.Utility;
+
+namespace Content.Server.Physics.Controllers;
+
+///
+/// A component which makes its entity periodically chaotic jumps arounds
+///
+public sealed class ChaoticJumpSystem : VirtualController
+{
+ [Dependency] private readonly IGameTiming _gameTiming = default!;
+ [Dependency] private readonly SharedTransformSystem _transform = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly SharedPhysicsSystem _physics = default!;
+ [Dependency] private readonly SharedTransformSystem _xform = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnMapInit);
+ }
+
+ private void OnMapInit(Entity chaotic, ref MapInitEvent args)
+ {
+ //So the entity doesn't teleport instantly. For tesla, for example, it's important for it to eat tesla's generator.
+ chaotic.Comp.NextJumpTime = _gameTiming.CurTime + TimeSpan.FromSeconds(_random.NextFloat(chaotic.Comp.JumpMinInterval, chaotic.Comp.JumpMaxInterval));
+ }
+
+ public override void UpdateBeforeSolve(bool prediction, float frameTime)
+ {
+ base.UpdateBeforeSolve(prediction, frameTime);
+
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out var chaotic))
+ {
+ //Jump
+ if (chaotic.NextJumpTime <= _gameTiming.CurTime)
+ {
+ Jump(uid, chaotic);
+ chaotic.NextJumpTime += TimeSpan.FromSeconds(_random.NextFloat(chaotic.JumpMinInterval, chaotic.JumpMaxInterval));
+ }
+ }
+ }
+
+ private void Jump(EntityUid uid, ChaoticJumpComponent component)
+ {
+ var transform = Transform(uid);
+
+ var startPos = _transform.GetWorldPosition(uid);
+ Vector2 targetPos;
+
+ var direction = _random.NextAngle();
+ var range = _random.NextFloat(component.RangeMin, component.RangeMax);
+ var ray = new CollisionRay(startPos, direction.ToVec(), component.CollisionMask);
+ var rayCastResults = _physics.IntersectRay(transform.MapID, ray, range, uid, returnOnFirstHit: false).FirstOrNull();
+
+ if (rayCastResults != null)
+ {
+ targetPos = rayCastResults.Value.HitPos;
+ targetPos = new Vector2(targetPos.X - (float) Math.Cos(direction), targetPos.Y - (float) Math.Sin(direction)); //offset so that the teleport does not take place directly inside the target
+ }
+ else
+ {
+ targetPos = new Vector2(startPos.X + range * (float) Math.Cos(direction), startPos.Y + range * (float) Math.Sin(direction));
+ }
+
+ Spawn(component.Effect, transform.Coordinates);
+
+ _xform.SetWorldPosition(uid, targetPos);
+ }
+}
diff --git a/Content.Server/Physics/Controllers/ChasingWalkSystem.cs b/Content.Server/Physics/Controllers/ChasingWalkSystem.cs
new file mode 100644
index 0000000000..8835b44ab7
--- /dev/null
+++ b/Content.Server/Physics/Controllers/ChasingWalkSystem.cs
@@ -0,0 +1,109 @@
+using System.Linq;
+using System.Numerics;
+using Content.Server.Physics.Components;
+using Robust.Shared.Random;
+using Robust.Shared.Timing;
+using Robust.Shared.Physics.Systems;
+using Robust.Shared.Physics.Components;
+using Robust.Shared.Physics.Controllers;
+
+namespace Content.Server.Physics.Controllers;
+
+///
+/// A system which makes its entity chasing another entity with selected component.
+///
+public sealed class ChasingWalkSystem : VirtualController
+{
+ [Dependency] private readonly IGameTiming _gameTiming = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly SharedTransformSystem _transform = default!;
+ [Dependency] private readonly EntityLookupSystem _lookup = default!;
+ [Dependency] private readonly SharedPhysicsSystem _physics = default!;
+
+ private readonly HashSet> _potentialChaseTargets = new();
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnChasingMapInit);
+ SubscribeLocalEvent(OnChasingUnpaused);
+ }
+
+ private void OnChasingMapInit(EntityUid uid, ChasingWalkComponent component, MapInitEvent args)
+ {
+ component.NextImpulseTime = _gameTiming.CurTime;
+ component.NextChangeVectorTime = _gameTiming.CurTime;
+ }
+
+ private void OnChasingUnpaused(EntityUid uid, ChasingWalkComponent component, ref EntityUnpausedEvent args)
+ {
+ component.NextImpulseTime += args.PausedTime;
+ component.NextChangeVectorTime += args.PausedTime;
+ }
+
+ public override void UpdateBeforeSolve(bool prediction, float frameTime)
+ {
+ base.UpdateBeforeSolve(prediction, frameTime);
+
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out var chasing))
+ {
+ //Set Velocity to Target
+ if (chasing.NextImpulseTime <= _gameTiming.CurTime)
+ {
+ ForceImpulse(uid, chasing);
+ chasing.NextImpulseTime += TimeSpan.FromSeconds(chasing.ImpulseInterval);
+ }
+ //Change Target
+ if (chasing.NextChangeVectorTime <= _gameTiming.CurTime)
+ {
+ ChangeTarget(uid, chasing);
+
+ var delay = TimeSpan.FromSeconds(_random.NextFloat(chasing.ChangeVectorMinInterval, chasing.ChangeVectorMaxInterval));
+ chasing.NextChangeVectorTime += delay;
+ }
+ }
+ }
+
+ private void ChangeTarget(EntityUid uid, ChasingWalkComponent component)
+ {
+ //We find our coordinates and calculate the radius of the target search.
+ var xform = Transform(uid);
+ var range = component.MaxChaseRadius;
+ var compType = _random.Pick(component.ChasingComponent.Values).Component.GetType();
+ _potentialChaseTargets.Clear();
+ _lookup.GetEntitiesInRange(compType, _transform.GetMapCoordinates(xform), range, _potentialChaseTargets, LookupFlags.Uncontained);
+
+ //If there are no required components in the radius, don't moving.
+ if (_potentialChaseTargets.Count <= 0)
+ return;
+
+ //In the case of finding required components, we choose a random one of them and remember its uid.
+ component.ChasingEntity = _random.Pick(_potentialChaseTargets).Owner;
+ component.Speed = _random.NextFloat(component.MinSpeed, component.MaxSpeed);
+ }
+
+ //pushing the entity toward its target
+ private void ForceImpulse(EntityUid uid, ChasingWalkComponent component)
+ {
+ if (Deleted(component.ChasingEntity) || component.ChasingEntity == null)
+ {
+ ChangeTarget(uid, component);
+ return;
+ }
+
+ if (!TryComp(uid, out var physics))
+ return;
+
+ //Calculating direction to the target.
+ var pos1 = _transform.GetWorldPosition(uid);
+ var pos2 = _transform.GetWorldPosition(component.ChasingEntity.Value);
+
+ var delta = pos2 - pos1;
+ var speed = delta.Length() > 0 ? delta.Normalized() * component.Speed : Vector2.Zero;
+
+ _physics.SetLinearVelocity(uid, speed);
+ _physics.SetBodyStatus(physics, BodyStatus.InAir); //If this is not done, from the explosion up close, the tesla will "Fall" to the ground, and almost stop moving.
+ }
+}
diff --git a/Content.Server/Singularity/EntitySystems/EventHorizonSystem.cs b/Content.Server/Singularity/EntitySystems/EventHorizonSystem.cs
index 5fb0e0756f..bbe19614a3 100644
--- a/Content.Server/Singularity/EntitySystems/EventHorizonSystem.cs
+++ b/Content.Server/Singularity/EntitySystems/EventHorizonSystem.cs
@@ -325,8 +325,10 @@ public sealed class EventHorizonSystem : SharedEventHorizonSystem
if (!Resolve(uid, ref xform, ref eventHorizon))
return;
- ConsumeEntitiesInRange(uid, range, xform, eventHorizon);
- ConsumeTilesInRange(uid, range, xform, eventHorizon);
+ if (eventHorizon.ConsumeEntities)
+ ConsumeEntitiesInRange(uid, range, xform, eventHorizon);
+ if (eventHorizon.ConsumeTiles)
+ ConsumeTilesInRange(uid, range, xform, eventHorizon);
}
#endregion Consume
diff --git a/Content.Server/Singularity/EntitySystems/SingularityGeneratorSystem.cs b/Content.Server/Singularity/EntitySystems/SingularityGeneratorSystem.cs
index 3ae648f2e4..a0c0262794 100644
--- a/Content.Server/Singularity/EntitySystems/SingularityGeneratorSystem.cs
+++ b/Content.Server/Singularity/EntitySystems/SingularityGeneratorSystem.cs
@@ -1,4 +1,4 @@
-using Content.Server.ParticleAccelerator.Components;
+using Content.Server.ParticleAccelerator.Components;
using Content.Server.Singularity.Components;
using Content.Shared.Singularity.Components;
using Robust.Shared.Physics.Events;
diff --git a/Content.Server/Tesla/Components/LightningArcShooterComponent.cs b/Content.Server/Tesla/Components/LightningArcShooterComponent.cs
new file mode 100644
index 0000000000..641dc59e8c
--- /dev/null
+++ b/Content.Server/Tesla/Components/LightningArcShooterComponent.cs
@@ -0,0 +1,55 @@
+using Content.Server.Tesla.EntitySystems;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.Tesla.Components;
+
+///
+/// Periodically fires electric arcs at surrounding objects.
+///
+[RegisterComponent, Access(typeof(LightningArcShooterSystem))]
+public sealed partial class LightningArcShooterComponent : Component
+{
+ ///
+ /// The number of lightning bolts that are fired at the same time. From 0 to N
+ /// Important balance value: if there aren't a N number of coils or grounders around the tesla,
+ /// the tesla will have a chance to shoot into something important and break.
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public int MaxLightningArc = 1;
+
+ ///
+ /// Minimum interval between shooting.
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public float ShootMinInterval = 0.5f;
+
+ ///
+ /// Maximum interval between shooting.
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public float ShootMaxInterval = 8.0f;
+
+ ///
+ /// the target selection radius for lightning bolts.
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public float ShootRange = 5f;
+
+ ///
+ /// How many times after a hit the lightning bolt will bounce into an adjacent target
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public int ArcDepth = 1;
+
+ ///
+ /// The time, upon reaching which the next batch of lightning bolts will be fired.
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public TimeSpan NextShootTime;
+
+ ///
+ /// The type of lightning bolts we shoot
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public EntProtoId LightningPrototype = "Lightning";
+}
diff --git a/Content.Server/Tesla/Components/LightningSparkingComponent.cs b/Content.Server/Tesla/Components/LightningSparkingComponent.cs
new file mode 100644
index 0000000000..bb954de89a
--- /dev/null
+++ b/Content.Server/Tesla/Components/LightningSparkingComponent.cs
@@ -0,0 +1,27 @@
+using Content.Server.Tesla.EntitySystems;
+using Robust.Shared.Audio;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Server.Tesla.Components;
+
+///
+/// The component changes the visual of an object after it is struck by lightning
+///
+[RegisterComponent, Access(typeof(LightningSparkingSystem))]
+public sealed partial class LightningSparkingComponent : Component
+{
+ ///
+ /// Spark duration.
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public float LightningTime = 4;
+
+ ///
+ /// When the spark visual should turn off.
+ ///
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+ public TimeSpan LightningEndTime;
+
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public bool IsSparking;
+}
diff --git a/Content.Server/Tesla/Components/TeslaCoilComponent.cs b/Content.Server/Tesla/Components/TeslaCoilComponent.cs
new file mode 100644
index 0000000000..6effe8ca51
--- /dev/null
+++ b/Content.Server/Tesla/Components/TeslaCoilComponent.cs
@@ -0,0 +1,19 @@
+using Content.Server.Tesla.EntitySystems;
+using Robust.Shared.Audio;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Server.Tesla.Components;
+
+///
+/// Generates electricity from lightning bolts
+///
+[RegisterComponent, Access(typeof(TeslaCoilSystem))]
+public sealed partial class TeslaCoilComponent : Component
+{
+ ///
+ /// How much power will the coil generate from a lightning strike
+ ///
+ // To Do: Different lightning bolts have different powers and generate different amounts of energy
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public float ChargeFromLightning = 50000f;
+}
diff --git a/Content.Server/Tesla/Components/TeslaEnergyBallComponent.cs b/Content.Server/Tesla/Components/TeslaEnergyBallComponent.cs
new file mode 100644
index 0000000000..0f2b38da84
--- /dev/null
+++ b/Content.Server/Tesla/Components/TeslaEnergyBallComponent.cs
@@ -0,0 +1,42 @@
+using Content.Server.Tesla.EntitySystems;
+using Robust.Shared.Audio;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.Tesla.Components;
+
+///
+/// A component that tracks an entity's saturation level from absorbing other creatures by touch, and spawns new entities when the saturation limit is reached.
+///
+[RegisterComponent, Access(typeof(TeslaEnergyBallSystem))]
+public sealed partial class TeslaEnergyBallComponent : Component
+{
+ ///
+ /// how much energy will Tesla get by eating various things. Walls, people, anything.
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public float ConsumeStuffEnergy = 2f;
+
+ ///
+ /// The amount of energy this entity contains. Once the limit is reached, the energy will be spent to spawn mini-energy balls
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public float Energy;
+
+ ///
+ /// The amount of energy an entity must reach in order to zero the energy and create another entity
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public float NeedEnergyToSpawn = 100f;
+
+ ///
+ /// Entities that spawn when the energy limit is reached
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public EntProtoId SpawnProto = "TeslaMiniEnergyBall";
+
+ ///
+ /// Entity, spun when tesla gobbles with touch.
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public EntProtoId ConsumeEffectProto = "EffectTeslaSparks";
+}
diff --git a/Content.Server/Tesla/EntitySystem/LightningArcShooterSystem.cs b/Content.Server/Tesla/EntitySystem/LightningArcShooterSystem.cs
new file mode 100644
index 0000000000..80cfab7035
--- /dev/null
+++ b/Content.Server/Tesla/EntitySystem/LightningArcShooterSystem.cs
@@ -0,0 +1,55 @@
+using Content.Server.Lightning;
+using Content.Server.Tesla.Components;
+using Robust.Shared.Random;
+using Robust.Shared.Timing;
+
+namespace Content.Server.Tesla.EntitySystems;
+
+///
+/// Fires electric arcs at surrounding objects.
+///
+public sealed class LightningArcShooterSystem : EntitySystem
+{
+ [Dependency] private readonly IGameTiming _gameTiming = default!;
+ [Dependency] private readonly LightningSystem _lightning = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent(OnShooterMapInit);
+ SubscribeLocalEvent(OnShooterUnpaused);
+ }
+
+ private void OnShooterMapInit(EntityUid uid, LightningArcShooterComponent component, ref MapInitEvent args)
+ {
+ component.NextShootTime = _gameTiming.CurTime;
+ }
+
+ private void OnShooterUnpaused(EntityUid uid, LightningArcShooterComponent component, ref EntityUnpausedEvent args)
+ {
+ component.NextShootTime += args.PausedTime;
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out var arcShooter))
+ {
+ if (arcShooter.NextShootTime > _gameTiming.CurTime)
+ continue;
+
+ ArcShoot(uid, arcShooter);
+ var delay = TimeSpan.FromSeconds(_random.NextFloat(arcShooter.ShootMinInterval, arcShooter.ShootMaxInterval));
+ arcShooter.NextShootTime += delay;
+ }
+ }
+
+ private void ArcShoot(EntityUid uid, LightningArcShooterComponent component)
+ {
+ var arcs = _random.Next(1, component.MaxLightningArc);
+ _lightning.ShootRandomLightnings(uid, component.ShootRange, arcs, component.LightningPrototype, component.ArcDepth);
+ }
+}
diff --git a/Content.Server/Tesla/EntitySystem/LightningSparkingSystem.cs b/Content.Server/Tesla/EntitySystem/LightningSparkingSystem.cs
new file mode 100644
index 0000000000..7b569e66b3
--- /dev/null
+++ b/Content.Server/Tesla/EntitySystem/LightningSparkingSystem.cs
@@ -0,0 +1,53 @@
+using Content.Server.Tesla.Components;
+using Content.Server.Lightning;
+using Content.Shared.Power;
+using Robust.Shared.Timing;
+
+namespace Content.Server.Tesla.EntitySystems;
+
+///
+/// The component changes the visual of an object after it is struck by lightning
+///
+public sealed class LightningSparkingSystem : EntitySystem
+{
+ [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+ [Dependency] private readonly IGameTiming _gameTiming = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnHitByLightning);
+ SubscribeLocalEvent(OnLightningUnpaused);
+ }
+
+ private void OnLightningUnpaused(EntityUid uid, LightningSparkingComponent component, ref EntityUnpausedEvent args)
+ {
+ component.LightningEndTime += args.PausedTime;
+ }
+
+ private void OnHitByLightning(Entity uid, ref HitByLightningEvent args)
+ {
+ _appearance.SetData(uid.Owner, TeslaCoilVisuals.Lightning, true);
+ uid.Comp.LightningEndTime = _gameTiming.CurTime + TimeSpan.FromSeconds(uid.Comp.LightningTime);
+ uid.Comp.IsSparking = true;
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out var component))
+ {
+ if (!component.IsSparking)
+ continue;
+
+ if (component.LightningEndTime < _gameTiming.CurTime)
+ {
+ _appearance.SetData(uid, TeslaCoilVisuals.Lightning, false);
+ component.IsSparking = false;
+ }
+ }
+ }
+}
diff --git a/Content.Server/Tesla/EntitySystem/TeslaCoilSystem.cs b/Content.Server/Tesla/EntitySystem/TeslaCoilSystem.cs
new file mode 100644
index 0000000000..bf98d77406
--- /dev/null
+++ b/Content.Server/Tesla/EntitySystem/TeslaCoilSystem.cs
@@ -0,0 +1,32 @@
+using Content.Server.Popups;
+using Content.Server.Power.Components;
+using Content.Server.Power.EntitySystems;
+using Content.Server.Tesla.Components;
+using Content.Server.Lightning;
+using Robust.Shared.Timing;
+
+namespace Content.Server.Tesla.EntitySystems;
+
+///
+/// Generates electricity from lightning bolts
+///
+public sealed class TeslaCoilSystem : EntitySystem
+{
+ [Dependency] private readonly BatterySystem _battery = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnHitByLightning);
+ }
+
+ //When struck by lightning, charge the internal battery
+ private void OnHitByLightning(Entity coil, ref HitByLightningEvent args)
+ {
+ if (!TryComp(coil, out var batteryComponent))
+ return;
+
+ _battery.SetCharge(coil, batteryComponent.CurrentCharge + coil.Comp.ChargeFromLightning);
+ }
+}
diff --git a/Content.Server/Tesla/EntitySystem/TeslaEnergyBallSystem.cs b/Content.Server/Tesla/EntitySystem/TeslaEnergyBallSystem.cs
new file mode 100644
index 0000000000..ff7793d3b2
--- /dev/null
+++ b/Content.Server/Tesla/EntitySystem/TeslaEnergyBallSystem.cs
@@ -0,0 +1,51 @@
+using Content.Server.Administration.Logs;
+using Content.Server.Singularity.Components;
+using Content.Server.Tesla.Components;
+using Content.Shared.Database;
+using Content.Shared.Singularity.Components;
+using Content.Shared.Mind.Components;
+using Content.Shared.Tag;
+using Robust.Shared.Physics.Events;
+using Content.Server.Lightning.Components;
+using Robust.Server.Audio;
+using Content.Server.Singularity.Events;
+
+namespace Content.Server.Tesla.EntitySystems;
+
+///
+/// A component that tracks an entity's saturation level from absorbing other creatures by touch, and spawns new entities when the saturation limit is reached.
+///
+public sealed class TeslaEnergyBallSystem : EntitySystem
+{
+ [Dependency] private readonly AudioSystem _audio = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnConsumed);
+ }
+
+ private void OnConsumed(Entity tesla, ref EntityConsumedByEventHorizonEvent args)
+ {
+ Spawn(tesla.Comp.ConsumeEffectProto, Transform(args.Entity).Coordinates);
+ if (TryComp(args.Entity, out var singuloFood))
+ {
+ AdjustEnergy(tesla, tesla.Comp, singuloFood.Energy);
+ } else
+ {
+ AdjustEnergy(tesla, tesla.Comp, tesla.Comp.ConsumeStuffEnergy);
+ }
+ }
+
+ public void AdjustEnergy(EntityUid uid, TeslaEnergyBallComponent component, float delta)
+ {
+ component.Energy += delta;
+
+ if (component.Energy > component.NeedEnergyToSpawn)
+ {
+ component.Energy -= component.NeedEnergyToSpawn;
+ Spawn(component.SpawnProto, Transform(uid).Coordinates);
+ }
+ }
+}
diff --git a/Content.Shared/Lightning/Components/SharedLightningComponent.cs b/Content.Shared/Lightning/Components/SharedLightningComponent.cs
index e01307bfd9..396f2710f3 100644
--- a/Content.Shared/Lightning/Components/SharedLightningComponent.cs
+++ b/Content.Shared/Lightning/Components/SharedLightningComponent.cs
@@ -1,4 +1,4 @@
-using Content.Shared.Physics;
+using Content.Shared.Physics;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
diff --git a/Content.Shared/Power/TeslaCoilVisuals.cs b/Content.Shared/Power/TeslaCoilVisuals.cs
new file mode 100644
index 0000000000..2cc633f860
--- /dev/null
+++ b/Content.Shared/Power/TeslaCoilVisuals.cs
@@ -0,0 +1,10 @@
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Power;
+
+[Serializable, NetSerializable]
+public enum TeslaCoilVisuals : byte
+{
+ Enabled,
+ Lightning
+}
diff --git a/Content.Shared/Singularity/Components/EventHorizonComponent.cs b/Content.Shared/Singularity/Components/EventHorizonComponent.cs
index 890b223117..2aa081915b 100644
--- a/Content.Shared/Singularity/Components/EventHorizonComponent.cs
+++ b/Content.Shared/Singularity/Components/EventHorizonComponent.cs
@@ -21,6 +21,18 @@ public sealed partial class EventHorizonComponent : Component
[DataField("radius")]
public float Radius;
+ ///
+ /// involves periodically destroying tiles within a specified radius
+ ///
+ [DataField]
+ public bool ConsumeTiles = true;
+
+ ///
+ /// involves periodically destroying entities within a specified radius. Does not affect collide destruction of entities.
+ ///
+ [DataField]
+ public bool ConsumeEntities = true;
+
///
/// Whether the event horizon can consume/destroy the devices built to contain it.
/// If you want to set this go through .
diff --git a/Resources/Audio/Effects/attributions.yml b/Resources/Audio/Effects/attributions.yml
index 22743ab1e9..15293bf0f8 100644
--- a/Resources/Audio/Effects/attributions.yml
+++ b/Resources/Audio/Effects/attributions.yml
@@ -68,6 +68,21 @@
copyright: '"Pop, High, A (H1).wav" by InspectorJ (www.jshaw.co.uk) of Freesound.org. Mixed from stereo to mono.'
source: "https://freesound.org/people/InspectorJ/sounds/411642/"
+- files: ["tesla.ogg"]
+ license: "CC-BY-NC-4.0"
+ copyright: "Taken from bzakirasantjago via freesound.org and mixed from stereo to mono."
+ source: "https://freesound.org/people/bzakirasantjago/sounds/404462/"
+
+- files: ["tesla_collapse.ogg"]
+ license: "CC-BY-NC-SA-3.0"
+ copyright: "Taken from DragishaRambo21 via freesound.org and mixed from stereo to mono."
+ source: "https://freesound.org/people/DragishaRambo21/sounds/345920/"
+
+- files: ["tesla_consume.ogg"]
+ license: "CC0-1.0"
+ copyright: "Taken from egomassive via freesound.org and mixed from stereo to mono."
+ source: "https://freesound.org/people/egomassive/sounds/536741/"
+
- files: ["sizzle.ogg"]
license: "CC-BY-SA-3.0"
copyright: "Recorded by deltanedas for SS14"
diff --git a/Resources/Audio/Effects/tesla.ogg b/Resources/Audio/Effects/tesla.ogg
new file mode 100644
index 0000000000..1edea869b3
Binary files /dev/null and b/Resources/Audio/Effects/tesla.ogg differ
diff --git a/Resources/Audio/Effects/tesla_collapse.ogg b/Resources/Audio/Effects/tesla_collapse.ogg
new file mode 100644
index 0000000000..65cc4bfc46
Binary files /dev/null and b/Resources/Audio/Effects/tesla_collapse.ogg differ
diff --git a/Resources/Audio/Effects/tesla_consume.ogg b/Resources/Audio/Effects/tesla_consume.ogg
new file mode 100644
index 0000000000..75c2aa8f76
Binary files /dev/null and b/Resources/Audio/Effects/tesla_consume.ogg differ
diff --git a/Resources/Locale/en-US/prototypes/catalog/cargo/cargo-engines.ftl b/Resources/Locale/en-US/prototypes/catalog/cargo/cargo-engines.ftl
index 6b8d4e6451..a4a62d466b 100644
--- a/Resources/Locale/en-US/prototypes/catalog/cargo/cargo-engines.ftl
+++ b/Resources/Locale/en-US/prototypes/catalog/cargo/cargo-engines.ftl
@@ -21,3 +21,12 @@ ent-EngineParticleAccelerator = { ent-CrateEngineeringParticleAccelerator }
ent-EngineSolar = { ent-CrateEngineeringSolar }
.desc = { ent-CrateEngineeringSolar.desc }
+
+ent-EngineTeslaGenerator = { ent-CrateEngineeringTeslaGenerator }
+ .desc = { ent-CrateEngineeringTeslaGenerator.desc }
+
+ent-EngineTeslaCoil = { ent-CrateEngineeringTeslaCoil }
+ .desc = { ent-CrateEngineeringTeslaCoil.desc }
+
+ent-EngineTeslaGroundingRod = { ent-CrateEngineeringTeslaGroundingRod }
+ .desc = { ent-CrateEngineeringTeslaGroundingRod.desc }
\ No newline at end of file
diff --git a/Resources/Locale/en-US/prototypes/catalog/fills/crates/engines-crates.ftl b/Resources/Locale/en-US/prototypes/catalog/fills/crates/engines-crates.ftl
index 753e963323..a72d43fa69 100644
--- a/Resources/Locale/en-US/prototypes/catalog/fills/crates/engines-crates.ftl
+++ b/Resources/Locale/en-US/prototypes/catalog/fills/crates/engines-crates.ftl
@@ -28,3 +28,12 @@ ent-CrateEngineeringSolar = Solar assembly crate
.desc = Parts for constructing solar panels and trackers.
ent-CrateEngineeringShuttle = Shuttle powering crate
+
+ent-CrateEngineeringTeslaGenerator = Tesla generator crate
+ .desc = A tesla generator. God save you.
+
+ent-CrateEngineeringTeslaCoil = Tesla coil crate
+ .desc = Tesla coil. Attracts lightning and generates energy from it.
+
+ent-CrateEngineeringTeslaGroundingRod = Tesla grounding rod crate
+ .desc = Grounding rod, best for lightning protection.
\ No newline at end of file
diff --git a/Resources/Locale/en-US/tesla/tesla-components.ftl b/Resources/Locale/en-US/tesla/tesla-components.ftl
new file mode 100644
index 0000000000..61f4970457
--- /dev/null
+++ b/Resources/Locale/en-US/tesla/tesla-components.ftl
@@ -0,0 +1,5 @@
+tesla-coil-on = The tesla coil turns on.
+tesla-coil-off = The tesla coil turns off.
+
+tesla-grounding-on = The grounding rod turns on.
+tesla-grounding-off = The grounding rod turns off.
\ No newline at end of file
diff --git a/Resources/Prototypes/Catalog/Cargo/cargo_engines.yml b/Resources/Prototypes/Catalog/Cargo/cargo_engines.yml
index bdbfd53333..532f56a98e 100644
--- a/Resources/Prototypes/Catalog/Cargo/cargo_engines.yml
+++ b/Resources/Prototypes/Catalog/Cargo/cargo_engines.yml
@@ -89,3 +89,33 @@
cost: 500
category: Engineering
group: market
+
+- type: cargoProduct
+ id: EngineTeslaGenerator
+ icon:
+ sprite: Structures/Power/Generation/Tesla/generator.rsi
+ state: icon
+ product: CrateEngineeringTeslaGenerator
+ cost: 4000
+ category: Engineering
+ group: market
+
+- type: cargoProduct
+ id: EngineTeslaCoil
+ icon:
+ sprite: Structures/Power/Generation/Tesla/coil.rsi
+ state: coil
+ product: CrateEngineeringTeslaCoil
+ cost: 1200
+ category: Engineering
+ group: market
+
+- type: cargoProduct
+ id: EngineTeslaGroundingRod
+ icon:
+ sprite: Structures/Power/Generation/Tesla/coil.rsi
+ state: grounding_rod
+ product: CrateEngineeringTeslaGroundingRod
+ cost: 400
+ category: Engineering
+ group: market
\ No newline at end of file
diff --git a/Resources/Prototypes/Catalog/Fills/Crates/engines.yml b/Resources/Prototypes/Catalog/Fills/Crates/engines.yml
index 99f937b1e3..73bedacaf3 100644
--- a/Resources/Prototypes/Catalog/Fills/Crates/engines.yml
+++ b/Resources/Prototypes/Catalog/Fills/Crates/engines.yml
@@ -116,3 +116,27 @@
- id: WallmountGeneratorAPUElectronics
- id: HandheldGPSBasic
- id: InflatableDoorStack1
+
+- type: entity
+ id: CrateEngineeringTeslaGenerator
+ parent: CrateEngineeringSecure
+ components:
+ - type: StorageFill
+ contents:
+ - id: TeslaGenerator
+
+- type: entity
+ id: CrateEngineeringTeslaCoil
+ parent: CrateEngineeringSecure
+ components:
+ - type: StorageFill
+ contents:
+ - id: TeslaCoil
+
+- type: entity
+ id: CrateEngineeringTeslaGroundingRod
+ parent: CrateEngineeringSecure
+ components:
+ - type: StorageFill
+ contents:
+ - id: TeslaGroundingRod
\ No newline at end of file
diff --git a/Resources/Prototypes/Entities/Effects/sparks.yml b/Resources/Prototypes/Entities/Effects/sparks.yml
index 53542a9b7b..d86522a971 100644
--- a/Resources/Prototypes/Entities/Effects/sparks.yml
+++ b/Resources/Prototypes/Entities/Effects/sparks.yml
@@ -17,3 +17,28 @@
tags:
- HideContextMenu
- type: AnimationPlayer
+
+- type: entity
+ id: EffectTeslaSparks
+ noSpawn: true
+ components:
+ - type: TimedDespawn
+ lifetime: 0.5
+ - type: Sprite
+ drawdepth: Effects
+ noRot: true
+ layers:
+ - shader: unshaded
+ map: ["enum.EffectLayers.Unshaded"]
+ sprite: Effects/atmospherics.rsi
+ state: frezon_old
+ - type: EffectVisuals
+ - type: Tag
+ tags:
+ - HideContextMenu
+ - type: AnimationPlayer
+ - type: EmitSoundOnSpawn
+ sound:
+ path: /Audio/Effects/tesla_consume.ogg
+ params:
+ variation: 0.3
\ No newline at end of file
diff --git a/Resources/Prototypes/Entities/Mobs/base.yml b/Resources/Prototypes/Entities/Mobs/base.yml
index 8719e03dda..8fb534e9fa 100644
--- a/Resources/Prototypes/Entities/Mobs/base.yml
+++ b/Resources/Prototypes/Entities/Mobs/base.yml
@@ -89,6 +89,9 @@
soundHit:
path: /Audio/Effects/hit_kick.ogg
- type: Pullable
+ - type: LightningTarget
+ priority: 2
+ lightningExplode: false
# Used for mobs that can enter combat mode and can attack.
- type: entity
diff --git a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml
index d6dc74efd0..bbb85f335e 100644
--- a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml
+++ b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml
@@ -132,6 +132,8 @@
- type: AccessReader
- type: StaticPrice
price: 150
+ - type: LightningTarget
+ priority: 1
- type: Tag
tags:
- Airlock
@@ -140,3 +142,4 @@
placement:
mode: SnapgridCenter
+
diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/base_structurecomputers.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/base_structurecomputers.yml
index d2c87deaa6..d1b870646d 100644
--- a/Resources/Prototypes/Entities/Structures/Machines/Computers/base_structurecomputers.yml
+++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/base_structurecomputers.yml
@@ -57,3 +57,5 @@
containers:
board: !type:Container
ents: []
+ - type: LightningTarget
+ priority: 1
diff --git a/Resources/Prototypes/Entities/Structures/Machines/base_structuremachines.yml b/Resources/Prototypes/Entities/Structures/Machines/base_structuremachines.yml
index 3198310269..b4665a9b79 100644
--- a/Resources/Prototypes/Entities/Structures/Machines/base_structuremachines.yml
+++ b/Resources/Prototypes/Entities/Structures/Machines/base_structuremachines.yml
@@ -50,6 +50,8 @@
- type: ApcPowerReceiver
powerLoad: 1000
- type: ExtensionCableReceiver
+ - type: LightningTarget
+ priority: 1
- type: entity
abstract: true
@@ -66,3 +68,5 @@
containers:
- machine_parts
- machine_board
+ - type: LightningTarget
+ priority: 1
diff --git a/Resources/Prototypes/Entities/Structures/Power/Generation/Tesla/coil.yml b/Resources/Prototypes/Entities/Structures/Power/Generation/Tesla/coil.yml
new file mode 100644
index 0000000000..912fb6ce5a
--- /dev/null
+++ b/Resources/Prototypes/Entities/Structures/Power/Generation/Tesla/coil.yml
@@ -0,0 +1,125 @@
+- type: entity
+ id: TeslaCoil
+ name: tesla coil
+ description: A machine that converts lightning strikes into an electric current.
+ placement:
+ mode: SnapgridCenter
+ components:
+ - type: Transform
+ anchored: true
+ - type: Physics
+ bodyType: Static
+ - type: Fixtures
+ fixtures:
+ fix1:
+ shape:
+ !type:PhysShapeAabb
+ bounds: "-0.45,-0.45,0.45,0.45"
+ density: 190
+ mask:
+ - MachineMask
+ layer:
+ - MachineLayer
+ - type: Sprite
+ sprite: Structures/Power/Generation/Tesla/coil.rsi
+ snapCardinals: true
+ noRot: true
+ layers:
+ - state: coil
+ map: ["enabled"]
+ - state: coilhit
+ visible: false
+ map: ["hit"]
+ - type: Appearance
+ - type: GenericVisualizer
+ visuals:
+ enum.TeslaCoilVisuals.Enabled:
+ enabled:
+ True: { state: coil_open }
+ False: { state: coil }
+ enum.TeslaCoilVisuals.Lightning:
+ hit:
+ True: { visible: true }
+ False: { visible: false }
+ - type: LightningSparking
+ - type: AnimationPlayer
+ - type: NodeContainer
+ examinable: true
+ nodes:
+ input:
+ !type:CableDeviceNode
+ nodeGroupID: HVPower
+ - type: Battery
+ maxCharge: 1000000
+ startingCharge: 0
+ - type: BatteryDischarger
+ activeSupplyRate: 15000
+ - type: TeslaCoil
+ chargeFromLightning: 1000000
+ - type: LightningTarget
+ priority: 4
+ lightningExplode: false
+ - type: PowerNetworkBattery
+ maxSupply: 1000000
+ supplyRampTolerance: 1000000
+ - type: Anchorable
+ - type: Rotatable
+ - type: Pullable
+ - type: Clickable
+ - type: InteractionOutline
+ #- type: GuideHelp # To Do - add Tesla Guide
+
+- type: entity
+ id: TeslaGroundingRod
+ name: grounding rod
+ description: A machine that keeps lightning from striking too far away.
+ placement:
+ mode: SnapgridCenter
+ components:
+ - type: Transform
+ anchored: true
+ - type: Physics
+ bodyType: Static
+ - type: Fixtures
+ fixtures:
+ fix1:
+ shape:
+ !type:PhysShapeAabb
+ bounds: "-0.45,-0.45,0.45,0.45"
+ density: 110
+ mask:
+ - MachineMask
+ layer:
+ - MachineLayer
+ - type: Sprite
+ sprite: Structures/Power/Generation/Tesla/coil.rsi
+ noRot: true
+ snapCardinals: true
+ layers:
+ - state: grounding_rod
+ map: ["power"]
+ - state: grounding_rodhit
+ visible: false
+ map: ["hit"]
+ - type: Appearance
+ - type: GenericVisualizer
+ visuals:
+ enum.TeslaCoilVisuals.Lightning:
+ hit:
+ True: { visible: true }
+ False: { visible: false }
+ enum.TeslaCoilVisuals.Enabled:
+ power:
+ True: { state: grounding_rod }
+ False: { state: grounding_rod_open }
+ - type: LightningSparking
+ - type: LightningTarget
+ priority: 3
+ lightningResistance: 10
+ lightningExplode: false
+ - type: Anchorable
+ - type: Rotatable
+ - type: Pullable
+ - type: Clickable
+ - type: InteractionOutline
+ #- type: GuideHelp # To Do - add Tesla Guide
\ No newline at end of file
diff --git a/Resources/Prototypes/Entities/Structures/Power/Generation/Tesla/energyball.yml b/Resources/Prototypes/Entities/Structures/Power/Generation/Tesla/energyball.yml
new file mode 100644
index 0000000000..1e99a947b9
--- /dev/null
+++ b/Resources/Prototypes/Entities/Structures/Power/Generation/Tesla/energyball.yml
@@ -0,0 +1,133 @@
+- type: entity
+ id: BaseEnergyBall
+ abstract: true
+ components:
+ - type: Clickable
+ - type: Physics
+ bodyType: Dynamic
+ bodyStatus: InAir
+ sleepingAllowed: false
+ - type: CanMoveInAir
+ - type: ChasingWalk
+ minSpeed: 1
+ maxSpeed: 3
+ chasingComponent:
+ - type: LightningTarget
+ - type: AmbientSound
+ volume: 3
+ range: 15
+ sound:
+ path: /Audio/Effects/tesla.ogg
+ - type: PointLight
+ enabled: true
+ radius: 5
+ color: "#4080FF"
+ - type: Appearance
+ - type: LightningArcShooter
+ arcDepth: 2
+ maxLightningArc: 1
+ shootMinInterval: 4
+ shootMaxInterval: 10
+ shootRange: 3
+ lightningPrototype: Lightning
+
+- type: entity
+ id: TeslaEnergyBall
+ parent: BaseEnergyBall
+ name: ball lightning
+ description: A giant ball of pure energy. The space around it is humming and melting.
+ components:
+ - type: Fixtures
+ fixtures:
+ EventHorizonCollider:
+ shape:
+ !type:PhysShapeCircle
+ radius: 0.55
+ hard: true
+ restitution: 0.8
+ density: 99999 # to do: remove the interaction with the explosions. Now the explosions can push the Tesla very far away from the station.
+ mask:
+ - Opaque
+ layer:
+ - GlassLayer
+ EventHorizonConsumer:
+ shape:
+ !type:PhysShapeCircle
+ radius: 0.65
+ hard: false
+ mask:
+ - Opaque
+ layer:
+ - GlassLayer
+ - type: EventHorizon
+ radius: 0.5
+ canBreachContainment: false
+ colliderFixtureId: EventHorizonCollider
+ consumerFixtureId: EventHorizonConsumer
+ consumeTiles: false
+ consumeEntities: false
+ - type: TeslaEnergyBall
+ spawnProto: TeslaMiniEnergyBall
+ - type: LightningArcShooter
+ arcDepth: 3
+ maxLightningArc: 4
+ shootMinInterval: 3
+ shootMaxInterval: 5
+ shootRange: 7
+ lightningPrototype: Lightning #To do: change to HyperchargedLightning, after fix beam system
+ - type: ChasingWalk
+ minSpeed: 1
+ maxSpeed: 3
+ - type: ChaoticJump
+ jumpMinInterval: 8
+ jumpMaxInterval: 15
+ offset: 1
+ - type: WarpPoint
+ follow: true
+ location: tesla ball
+ - type: Sprite
+ drawdepth: Effects
+ sprite: Structures/Power/Generation/Tesla/energy_ball.rsi
+ shader: unshaded
+ layers:
+ - state: energy_ball
+ - type: EmitSoundOnSpawn
+ sound:
+ path: /Audio/Effects/tesla_collapse.ogg
+ params:
+ variation: 0.3
+
+- type: entity
+ id: TeslaMiniEnergyBall
+ parent: BaseEnergyBall
+ name: mini ball lightning
+ description: The cub of a destructive energy cage. Not as dangerous, but still not worth touching with bare hands.
+ components:
+ - type: ChasingWalk
+ minSpeed: 2
+ maxSpeed: 3
+ chasingComponent:
+ - type: TeslaEnergyBall
+ - type: Fixtures
+ fixtures:
+ TeslaCollider:
+ shape:
+ !type:PhysShapeCircle
+ radius: 0.35
+ hard: true
+ restitution: 0.8
+ density: 10
+ mask:
+ - None
+ layer:
+ - None
+ - type: TimedDespawn
+ lifetime: 120
+ - type: Sprite
+ drawdepth: Effects
+ sprite: Structures/Power/Generation/Tesla/energy_miniball.rsi
+ shader: unshaded
+ layers:
+ - state: tesla_projectile
+ - type: Electrified
+ requirePower: false
diff --git a/Resources/Prototypes/Entities/Structures/Power/Generation/Tesla/generator.yml b/Resources/Prototypes/Entities/Structures/Power/Generation/Tesla/generator.yml
new file mode 100644
index 0000000000..a8c61634df
--- /dev/null
+++ b/Resources/Prototypes/Entities/Structures/Power/Generation/Tesla/generator.yml
@@ -0,0 +1,29 @@
+- type: entity
+ id: TeslaGenerator
+ name: tesla generator
+ parent: BaseStructureDynamic
+ description: An Odd Device which produces a powerful Tesla ball when set up.
+ components:
+ - type: Sprite
+ noRot: true
+ sprite: Structures/Power/Generation/Tesla/generator.rsi
+ state: icon
+ - type: SingularityGenerator #To do: rename the generator
+ spawnId: TeslaEnergyBall
+ - type: InteractionOutline
+ - type: Fixtures
+ fixtures:
+ fix1:
+ shape:
+ # Using a circle here makes it a lot easier to pull it all the way from Cargo
+ !type:PhysShapeCircle
+ radius: 0.45
+ density: 190
+ # Keep an eye on ParticlesProjectile when adjusting these
+ mask:
+ - MachineMask
+ layer:
+ - Opaque
+ - type: Anchorable
+ #- type: GuideHelp # To Do - add Tesla Guide
+
diff --git a/Resources/Prototypes/Entities/Structures/Power/apc.yml b/Resources/Prototypes/Entities/Structures/Power/apc.yml
index fb477c810c..ff8b7752ef 100644
--- a/Resources/Prototypes/Entities/Structures/Power/apc.yml
+++ b/Resources/Prototypes/Entities/Structures/Power/apc.yml
@@ -131,6 +131,8 @@
requirePower: true
mediumVoltageNode: input
lowVoltageNode: output
+ - type: LightningTarget
+ priority: 1
- type: StaticPrice
price: 500
diff --git a/Resources/Textures/Structures/Power/Generation/Tesla/coil.rsi/coil.png b/Resources/Textures/Structures/Power/Generation/Tesla/coil.rsi/coil.png
new file mode 100644
index 0000000000..7a1c14860b
Binary files /dev/null and b/Resources/Textures/Structures/Power/Generation/Tesla/coil.rsi/coil.png differ
diff --git a/Resources/Textures/Structures/Power/Generation/Tesla/coil.rsi/coilhit.png b/Resources/Textures/Structures/Power/Generation/Tesla/coil.rsi/coilhit.png
new file mode 100644
index 0000000000..892a6523a5
Binary files /dev/null and b/Resources/Textures/Structures/Power/Generation/Tesla/coil.rsi/coilhit.png differ
diff --git a/Resources/Textures/Structures/Power/Generation/Tesla/coil.rsi/grounding_rod.png b/Resources/Textures/Structures/Power/Generation/Tesla/coil.rsi/grounding_rod.png
new file mode 100644
index 0000000000..8574e20d29
Binary files /dev/null and b/Resources/Textures/Structures/Power/Generation/Tesla/coil.rsi/grounding_rod.png differ
diff --git a/Resources/Textures/Structures/Power/Generation/Tesla/coil.rsi/grounding_rodhit.png b/Resources/Textures/Structures/Power/Generation/Tesla/coil.rsi/grounding_rodhit.png
new file mode 100644
index 0000000000..69651b0df8
Binary files /dev/null and b/Resources/Textures/Structures/Power/Generation/Tesla/coil.rsi/grounding_rodhit.png differ
diff --git a/Resources/Textures/Structures/Power/Generation/Tesla/coil.rsi/meta.json b/Resources/Textures/Structures/Power/Generation/Tesla/coil.rsi/meta.json
new file mode 100644
index 0000000000..d0a620f16b
--- /dev/null
+++ b/Resources/Textures/Structures/Power/Generation/Tesla/coil.rsi/meta.json
@@ -0,0 +1,42 @@
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "taken from tgstation https://github.com/tgstation/tgstation/blob/master/icons/obj/machines/engine/tesla_coil.dmi",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "coil"
+ },
+ {
+ "name": "coilhit",
+ "delays": [
+ [
+ 0.1,
+ 0.08,
+ 0.08,
+ 0.08,
+ 0.120000005,
+ 0.1
+ ]
+ ]
+ },
+ {
+ "name": "grounding_rod"
+ },
+ {
+ "name": "grounding_rodhit",
+ "delays": [
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ]
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Resources/Textures/Structures/Power/Generation/Tesla/energy_ball.rsi/energy_ball.png b/Resources/Textures/Structures/Power/Generation/Tesla/energy_ball.rsi/energy_ball.png
new file mode 100644
index 0000000000..e3bb49a881
Binary files /dev/null and b/Resources/Textures/Structures/Power/Generation/Tesla/energy_ball.rsi/energy_ball.png differ
diff --git a/Resources/Textures/Structures/Power/Generation/Tesla/energy_ball.rsi/energy_ball_fast.png b/Resources/Textures/Structures/Power/Generation/Tesla/energy_ball.rsi/energy_ball_fast.png
new file mode 100644
index 0000000000..e3bb49a881
Binary files /dev/null and b/Resources/Textures/Structures/Power/Generation/Tesla/energy_ball.rsi/energy_ball_fast.png differ
diff --git a/Resources/Textures/Structures/Power/Generation/Tesla/energy_ball.rsi/meta.json b/Resources/Textures/Structures/Power/Generation/Tesla/energy_ball.rsi/meta.json
new file mode 100644
index 0000000000..8519efeec0
--- /dev/null
+++ b/Resources/Textures/Structures/Power/Generation/Tesla/energy_ball.rsi/meta.json
@@ -0,0 +1,49 @@
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "taken from tgstation https://github.com/tgstation/tgstation/blob/master/icons/obj/machines/engine/energy_ball.dmi",
+ "size": {
+ "x": 96,
+ "y": 96
+ },
+ "states": [
+ {
+ "name": "energy_ball",
+ "delays": [
+ [
+ 0.120000005,
+ 0.04,
+ 0.120000005,
+ 0.08,
+ 0.04,
+ 0.120000005,
+ 0.04,
+ 0.120000005,
+ 0.08,
+ 0.04,
+ 0.120000005,
+ 0.04
+ ]
+ ]
+ },
+ {
+ "name": "energy_ball_fast",
+ "delays": [
+ [
+ 0.05,
+ 0.08,
+ 0.08,
+ 0.08,
+ 0.08,
+ 0.08,
+ 0.08,
+ 0.08,
+ 0.08,
+ 0.08,
+ 0.08,
+ 0.08
+ ]
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Resources/Textures/Structures/Power/Generation/Tesla/energy_miniball.rsi/meta.json b/Resources/Textures/Structures/Power/Generation/Tesla/energy_miniball.rsi/meta.json
new file mode 100644
index 0000000000..029bc54b5a
--- /dev/null
+++ b/Resources/Textures/Structures/Power/Generation/Tesla/energy_miniball.rsi/meta.json
@@ -0,0 +1,30 @@
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "taken from tgstation https://github.com/tgstation/tgstation/blob/HEAD/icons/obj/weapons/guns/projectiles.dmi",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "tesla_projectile",
+ "delays": [
+ [
+ 0.05,
+ 0.08,
+ 0.08,
+ 0.08,
+ 0.08,
+ 0.08,
+ 0.08,
+ 0.08,
+ 0.08,
+ 0.08,
+ 0.08,
+ 0.08
+ ]
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Resources/Textures/Structures/Power/Generation/Tesla/energy_miniball.rsi/tesla_projectile.png b/Resources/Textures/Structures/Power/Generation/Tesla/energy_miniball.rsi/tesla_projectile.png
new file mode 100644
index 0000000000..e17359dd18
Binary files /dev/null and b/Resources/Textures/Structures/Power/Generation/Tesla/energy_miniball.rsi/tesla_projectile.png differ
diff --git a/Resources/Textures/Structures/Power/Generation/Tesla/generator.rsi/icon.png b/Resources/Textures/Structures/Power/Generation/Tesla/generator.rsi/icon.png
new file mode 100644
index 0000000000..98b0d57987
Binary files /dev/null and b/Resources/Textures/Structures/Power/Generation/Tesla/generator.rsi/icon.png differ
diff --git a/Resources/Textures/Structures/Power/Generation/Tesla/generator.rsi/meta.json b/Resources/Textures/Structures/Power/Generation/Tesla/generator.rsi/meta.json
new file mode 100644
index 0000000000..cd32288585
--- /dev/null
+++ b/Resources/Textures/Structures/Power/Generation/Tesla/generator.rsi/meta.json
@@ -0,0 +1,14 @@
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "Taken from tgstation https://github.com/tgstation/tgstation/blob/master/icons/obj/machines/engine/tesla_generator.dmi",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "icon"
+ }
+ ]
+}
\ No newline at end of file