]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Tesla (#21124)
authorEd <96445749+TheShuEd@users.noreply.github.com>
Thu, 28 Dec 2023 13:11:50 +0000 (16:11 +0300)
committerGitHub <noreply@github.com>
Thu, 28 Dec 2023 13:11:50 +0000 (13:11 +0000)
* upload all textures
finished easiest part - TeslaGenerator

* Added Tesla energy logic

* add lightning pulse priority

* work

* optimise lightning arc system

* now tesla moving to LightningTarget entity and consume it

* some audio work
add airlock and computers to the LightningTarget

* add nice visual and explosions
add crashing to game

* rsi meta fix

* disabling explosions (crashing server)
I'll get back to that problem later.

* adding important admin logging

* a little bit of cleaning and documentation

* Persistent attempts to fix the server crashing on explosions. Accidental cleaning of everything I see.

* now the tesla incinerates everything it touches except the containment field.

* colliders work

* fix falling tesla on ground after being exploded

* add consume sound, add spawn and collapses sound

* added TeslaGenerator to cargo trading console

* add all tesla part to cargo trading console

* Tesla coils: Turn on and off, get energy from lightning, give energy to the grid.

* tesla coil is ready

* tesla grounding rod is ready

* clean up

* clean up 2

* grounding rods now working without power

* add LightningResistance parameter for LightningTarget Component

* add chaotic teleport

* eletrocution remove?

* deltanedas fix pack

* more fixes

* FIXES

* FIIIXEEES

* The "Grounding Rod" component is removed, and replaced with "LightiningSparking", which is responsible for changing the visuals when hit by lightning. Duplicate code from the coil is removed.

* ops

* fix

* nah, is escaped anyway

* increase tesla collider size

* keron bb

* try fix test?

* fix

* bruh

* check turn off sus comp

* prototype cleaning

* FIIX

* return and fix sus component

* fix tesla eating lightnings, now mini tesla is electrocuted

* commented some issues

* remove linq sorting
fix jumping system
minor fixes

* fix second Linq

* fix tesla colliders! Yeah, it works

* fix componentregistration

* Just retests

* not fix

* FIX TESLA

* fixes

* store targets

* back

* make dictionary of hashsets

* some sloth fixes

* stump

* playtest balance energy generation, return to unpotimized (but working) lightning shoot

* parity

* work on

* some new fix, some new bug (chasingComponent not chasing)

* comment

* fix ChasingWalkSystem

* fix collider tesla problem

* revert old unoptimized shoot lightning

* new fix pack

---------

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
51 files changed:
Content.Server/Lightning/Components/LightningComponent.cs
Content.Server/Lightning/Components/LightningTargetComponent.cs [new file with mode: 0644]
Content.Server/Lightning/LightningSystem.cs
Content.Server/Lightning/LightningTargetSystem.cs [new file with mode: 0644]
Content.Server/Physics/Components/ChaoticJumpComponent.cs [new file with mode: 0644]
Content.Server/Physics/Components/ChasingWalkComponent.cs [new file with mode: 0644]
Content.Server/Physics/Controllers/ChaoticJumpSystem.cs [new file with mode: 0644]
Content.Server/Physics/Controllers/ChasingWalkSystem.cs [new file with mode: 0644]
Content.Server/Singularity/EntitySystems/EventHorizonSystem.cs
Content.Server/Singularity/EntitySystems/SingularityGeneratorSystem.cs
Content.Server/Tesla/Components/LightningArcShooterComponent.cs [new file with mode: 0644]
Content.Server/Tesla/Components/LightningSparkingComponent.cs [new file with mode: 0644]
Content.Server/Tesla/Components/TeslaCoilComponent.cs [new file with mode: 0644]
Content.Server/Tesla/Components/TeslaEnergyBallComponent.cs [new file with mode: 0644]
Content.Server/Tesla/EntitySystem/LightningArcShooterSystem.cs [new file with mode: 0644]
Content.Server/Tesla/EntitySystem/LightningSparkingSystem.cs [new file with mode: 0644]
Content.Server/Tesla/EntitySystem/TeslaCoilSystem.cs [new file with mode: 0644]
Content.Server/Tesla/EntitySystem/TeslaEnergyBallSystem.cs [new file with mode: 0644]
Content.Shared/Lightning/Components/SharedLightningComponent.cs
Content.Shared/Power/TeslaCoilVisuals.cs [new file with mode: 0644]
Content.Shared/Singularity/Components/EventHorizonComponent.cs
Resources/Audio/Effects/attributions.yml
Resources/Audio/Effects/tesla.ogg [new file with mode: 0644]
Resources/Audio/Effects/tesla_collapse.ogg [new file with mode: 0644]
Resources/Audio/Effects/tesla_consume.ogg [new file with mode: 0644]
Resources/Locale/en-US/prototypes/catalog/cargo/cargo-engines.ftl
Resources/Locale/en-US/prototypes/catalog/fills/crates/engines-crates.ftl
Resources/Locale/en-US/tesla/tesla-components.ftl [new file with mode: 0644]
Resources/Prototypes/Catalog/Cargo/cargo_engines.yml
Resources/Prototypes/Catalog/Fills/Crates/engines.yml
Resources/Prototypes/Entities/Effects/sparks.yml
Resources/Prototypes/Entities/Mobs/base.yml
Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml
Resources/Prototypes/Entities/Structures/Machines/Computers/base_structurecomputers.yml
Resources/Prototypes/Entities/Structures/Machines/base_structuremachines.yml
Resources/Prototypes/Entities/Structures/Power/Generation/Tesla/coil.yml [new file with mode: 0644]
Resources/Prototypes/Entities/Structures/Power/Generation/Tesla/energyball.yml [new file with mode: 0644]
Resources/Prototypes/Entities/Structures/Power/Generation/Tesla/generator.yml [new file with mode: 0644]
Resources/Prototypes/Entities/Structures/Power/apc.yml
Resources/Textures/Structures/Power/Generation/Tesla/coil.rsi/coil.png [new file with mode: 0644]
Resources/Textures/Structures/Power/Generation/Tesla/coil.rsi/coilhit.png [new file with mode: 0644]
Resources/Textures/Structures/Power/Generation/Tesla/coil.rsi/grounding_rod.png [new file with mode: 0644]
Resources/Textures/Structures/Power/Generation/Tesla/coil.rsi/grounding_rodhit.png [new file with mode: 0644]
Resources/Textures/Structures/Power/Generation/Tesla/coil.rsi/meta.json [new file with mode: 0644]
Resources/Textures/Structures/Power/Generation/Tesla/energy_ball.rsi/energy_ball.png [new file with mode: 0644]
Resources/Textures/Structures/Power/Generation/Tesla/energy_ball.rsi/energy_ball_fast.png [new file with mode: 0644]
Resources/Textures/Structures/Power/Generation/Tesla/energy_ball.rsi/meta.json [new file with mode: 0644]
Resources/Textures/Structures/Power/Generation/Tesla/energy_miniball.rsi/meta.json [new file with mode: 0644]
Resources/Textures/Structures/Power/Generation/Tesla/energy_miniball.rsi/tesla_projectile.png [new file with mode: 0644]
Resources/Textures/Structures/Power/Generation/Tesla/generator.rsi/icon.png [new file with mode: 0644]
Resources/Textures/Structures/Power/Generation/Tesla/generator.rsi/meta.json [new file with mode: 0644]

index 138d7010d4ee04c058f7af12676674f195b210fa..f1a4d3bf84baa6155fe511f220cd4c14a8349379 100644 (file)
@@ -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 (file)
index 0000000..e859935
--- /dev/null
@@ -0,0 +1,59 @@
+using Content.Server.Tesla.EntitySystems;
+using Content.Shared.Explosion;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.Lightning.Components;
+
+/// <summary>
+/// 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.
+/// </summary>
+[RegisterComponent, Access(typeof(LightningSystem), typeof(LightningTargetSystem))]
+public sealed partial class LightningTargetComponent : Component
+{
+    /// <summary>
+    /// Priority level for selecting a lightning target. 
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public int Priority;
+
+    /// <summary>
+    /// 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.
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public int LightningResistance = 1; //by default, reduces the number of bounces from this target by 1
+
+    // BOOM PART
+
+    /// <summary>
+    /// Will the entity explode after being struck by lightning?
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public bool LightningExplode = true;
+
+    /// <summary>
+    /// The explosion prototype to spawn
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public ProtoId<ExplosionPrototype> ExplosionPrototype = "Default";
+
+    /// <summary>
+    /// The total amount of intensity an explosion can achieve
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public float TotalIntensity = 25f;
+
+    /// <summary>
+    /// How quickly does the explosion's power slope? Higher = smaller area and more concentrated damage, lower = larger area and more spread out damage
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public float Dropoff = 2f;
+
+    /// <summary>
+    /// How much intensity can be applied per tile?
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public float MaxTileIntensity = 5f;
+}
index 8d82275b0a88a03cb0a976c5f5d59c9cb1ab2fce..00704df1e03b9d03dfe953e4249acd0af5eedfaf 100644 (file)
@@ -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<Entity<LightningTargetComponent>> _lookupTargetsList = new();
+    private HashSet<Entity<LightningTargetComponent>> _lookupTargets = new();
 
     public override void Initialize()
     {
         base.Initialize();
 
-        SubscribeLocalEvent<LightningComponent, StartCollideEvent>(OnCollide);
         SubscribeLocalEvent<LightningComponent, ComponentRemove>(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<BeamComponent>(uid, out var lightningBeam)
-            || !TryComp<BeamComponent>(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);
-    }
-
     /// <summary>
     /// Fires lightning from user to target
     /// </summary>
@@ -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);
     }
 
     /// <summary>
-    /// 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.
     /// </summary>
-    /// <param name="component"></param>
-    /// <param name="target"></param>
-    /// <param name="controllerEntity"></param>
-    private void Arc(LightningComponent component, EntityUid target, EntityUid controllerEntity)
+    /// <param name="user">Where the lightning fires from</param>
+    /// <param name="range">Targets selection radius</param>
+    /// <param name="boltCount">Number of lightning bolts</param>
+    /// <param name="lightningPrototype">The prototype for the lightning to be created</param>
+    /// <param name="arcDepth">how many times to recursively fire lightning bolts from the target points of the first shot.</param>
+    public void ShootRandomLightnings(EntityUid user, float range, int boltCount, string lightningPrototype = "Lightning", int arcDepth = 0)
     {
-        if (!TryComp<BeamComponent>(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<LightningTargetComponent>(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<Direction>().Length;
-
-        var lightningQuery = GetEntityQuery<LightningComponent>();
-        var beamQuery = GetEntityQuery<BeamComponent>();
-        var xformQuery = GetEntityQuery<TransformComponent>();
-
-        Dictionary<Direction, EntityUid> 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)
-            {
-                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;
-            }
-
-            if (closestResult == null)
+            ShootLightning(user, targets[i].Owner, lightningPrototype);
+            if (arcDepth > 0)
             {
-                continue;
+                ShootRandomLightnings(targets[i].Owner, range, 1, lightningPrototype, arcDepth - targets[i].LightningResistance);
             }
-
-            arcDirections.Add(direction, closestResult.Value.HitEntity);
-        }
-
-        var randomDirection = (Direction) _random.Next(0, 7);
-
-        if (arcDirections.ContainsKey(randomDirection))
-        {
-            component.ArcTarget = arcDirections.GetValueOrDefault(randomDirection);
-            arcDirections.Clear();
         }
     }
 }
+
+/// <summary>
+/// Raised directed on the target when an entity becomes the target of a lightning strike (not when touched)
+/// </summary>
+/// <param name="Source">The entity that created the lightning</param>
+/// <param name="Target">The entity that was struck by lightning.</param>
+[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 (file)
index 0000000..f9e564a
--- /dev/null
@@ -0,0 +1,34 @@
+using Content.Server.Explosion.EntitySystems;
+using Content.Server.Lightning;
+using Content.Server.Lightning.Components;
+
+namespace Content.Server.Tesla.EntitySystems;
+
+/// <summary>
+/// The component allows lightning to strike this target. And determining the behavior of the target when struck by lightning.
+/// </summary>
+public sealed class LightningTargetSystem : EntitySystem
+{
+    [Dependency] private readonly ExplosionSystem _explosionSystem = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<LightningTargetComponent, HitByLightningEvent>(OnHitByLightning);
+    }
+
+    private void OnHitByLightning(Entity<LightningTargetComponent> 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 (file)
index 0000000..be6fb46
--- /dev/null
@@ -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;
+
+/// <summary>
+/// A component which makes its entity periodically chaotic jumps arounds
+/// </summary>
+[RegisterComponent, Access(typeof(ChaoticJumpSystem))]
+public sealed partial class ChaoticJumpComponent : Component
+{
+    /// <summary>
+    /// The next moment in time when the entity is pushed toward its goal
+    /// </summary>
+    [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
+    public TimeSpan NextJumpTime;
+
+    /// <summary>
+    /// Minimum interval between jumps
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public float JumpMinInterval = 5f;
+    /// <summary>
+    /// Maximum interval between jumps
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public float JumpMaxInterval = 15f;
+
+    /// <summary>
+    /// collision limits for which it is impossible to make a jump
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public int CollisionMask = (int) CollisionGroup.Impassable;
+
+    /// <summary>
+    /// Minimum jump range
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public float RangeMin = 5f;
+
+    /// <summary>
+    /// Maximum jump range
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public float RangeMax = 10f;
+
+    /// <summary>
+    /// Spawn before jump
+    /// </summary>
+    [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 (file)
index 0000000..ca7a027
--- /dev/null
@@ -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;
+
+/// <summary>
+/// A component which makes its entity chasing entity with selected component.
+/// </summary>
+[RegisterComponent, Access(typeof(ChasingWalkSystem))]
+public sealed partial class ChasingWalkComponent : Component
+{
+    /// <summary>
+    /// The next moment in time when the entity is pushed toward its goal
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public TimeSpan NextImpulseTime;
+
+    /// <summary>
+    /// Push-to-target frequency.
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public float ImpulseInterval = 2f;
+
+    /// <summary>
+    /// The minimum speed at which this entity will move.
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public float MinSpeed = 1.5f;
+
+    /// <summary>
+    /// The maximum speed at which this entity will move.
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public float MaxSpeed = 3f;
+
+    /// <summary>
+    /// The current speed.
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public float Speed;
+
+    /// <summary>
+    /// The minimum time interval in which an object can change its motion target.
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public float ChangeVectorMinInterval = 5f;
+
+    /// <summary>
+    /// The maximum time interval in which an object can change its motion target.
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public float ChangeVectorMaxInterval = 25f;
+
+    /// <summary>
+    /// The next change of direction time.
+    /// </summary>
+    [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
+    public TimeSpan NextChangeVectorTime;
+
+    /// <summary>
+    /// The component that the entity is chasing
+    /// </summary>
+    [DataField(required: true)]
+    public ComponentRegistry ChasingComponent = default!;
+
+    /// <summary>
+    /// The maximum radius in which the entity chooses the target component to follow
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public float MaxChaseRadius = 25;
+
+    /// <summary>
+    /// The entity uid, chasing by the component owner
+    /// </summary>
+    [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 (file)
index 0000000..ee4b776
--- /dev/null
@@ -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;
+
+/// <summary>
+/// A component which makes its entity periodically chaotic jumps arounds
+/// </summary>
+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<ChaoticJumpComponent, MapInitEvent>(OnMapInit);
+    }
+
+    private void OnMapInit(Entity<ChaoticJumpComponent> 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<ChaoticJumpComponent>();
+        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 (file)
index 0000000..8835b44
--- /dev/null
@@ -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;
+
+/// <summary>
+/// A system which makes its entity chasing another entity with selected component.
+/// </summary>
+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<Entity<IComponent>> _potentialChaseTargets = new();
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<ChasingWalkComponent, MapInitEvent>(OnChasingMapInit);
+        SubscribeLocalEvent<ChasingWalkComponent, EntityUnpausedEvent>(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<ChasingWalkComponent>();
+        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<PhysicsComponent>(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.
+    }
+}
index 5fb0e0756f8f2bbb4416231b203e0cf6c2b29f71..bbe19614a3d185cffe4245445d01eff4b1a65124 100644 (file)
@@ -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
index 3ae648f2e4638cc30324041695eff0e25e77eac0..a0c0262794824b1abd26f55822657cb955dd8fba 100644 (file)
@@ -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 (file)
index 0000000..641dc59
--- /dev/null
@@ -0,0 +1,55 @@
+using Content.Server.Tesla.EntitySystems;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.Tesla.Components;
+
+/// <summary>
+/// Periodically fires electric arcs at surrounding objects.
+/// </summary>
+[RegisterComponent, Access(typeof(LightningArcShooterSystem))]
+public sealed partial class LightningArcShooterComponent : Component
+{
+    /// <summary>
+    /// 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.
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public int MaxLightningArc = 1;
+
+    /// <summary>
+    /// Minimum interval between shooting.
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public float ShootMinInterval = 0.5f;
+
+    /// <summary>
+    /// Maximum interval between shooting.
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public float ShootMaxInterval = 8.0f;
+
+    /// <summary>
+    /// the target selection radius for lightning bolts.
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public float ShootRange = 5f;
+
+    /// <summary>
+    /// How many times after a hit the lightning bolt will bounce into an adjacent target
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public int ArcDepth = 1;
+
+    /// <summary>
+    /// The time, upon reaching which the next batch of lightning bolts will be fired.
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public TimeSpan NextShootTime;
+
+    /// <summary>
+    /// The type of lightning bolts we shoot
+    /// </summary>
+    [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 (file)
index 0000000..bb954de
--- /dev/null
@@ -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;
+
+/// <summary>
+/// The component changes the visual of an object after it is struck by lightning
+/// </summary>
+[RegisterComponent, Access(typeof(LightningSparkingSystem))]
+public sealed partial class LightningSparkingComponent : Component
+{
+    /// <summary>
+    /// Spark duration.
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public float LightningTime = 4;
+
+    /// <summary>
+    /// When the spark visual should turn off.
+    /// </summary>
+    [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 (file)
index 0000000..6effe8c
--- /dev/null
@@ -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;
+
+/// <summary>
+/// Generates electricity from lightning bolts
+/// </summary>
+[RegisterComponent, Access(typeof(TeslaCoilSystem))]
+public sealed partial class TeslaCoilComponent : Component
+{
+    /// <summary>
+    /// How much power will the coil generate from a lightning strike
+    /// </summary>
+    // 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 (file)
index 0000000..0f2b38d
--- /dev/null
@@ -0,0 +1,42 @@
+using Content.Server.Tesla.EntitySystems;
+using Robust.Shared.Audio;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.Tesla.Components;
+
+/// <summary>
+/// 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.
+/// </summary>
+[RegisterComponent, Access(typeof(TeslaEnergyBallSystem))]
+public sealed partial class TeslaEnergyBallComponent : Component
+{
+    /// <summary>
+    /// how much energy will Tesla get by eating various things. Walls, people, anything.
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public float ConsumeStuffEnergy = 2f;
+
+    /// <summary>
+    /// The amount of energy this entity contains. Once the limit is reached, the energy will be spent to spawn mini-energy balls
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public float Energy;
+
+    /// <summary>
+    /// The amount of energy an entity must reach in order to zero the energy and create another entity
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public float NeedEnergyToSpawn = 100f;
+
+    /// <summary>
+    /// Entities that spawn when the energy limit is reached
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public EntProtoId SpawnProto = "TeslaMiniEnergyBall";
+
+    /// <summary>
+    /// Entity, spun when tesla gobbles with touch.
+    /// </summary>
+    [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 (file)
index 0000000..80cfab7
--- /dev/null
@@ -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;
+
+/// <summary>
+/// Fires electric arcs at surrounding objects.
+/// </summary>
+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<LightningArcShooterComponent, MapInitEvent>(OnShooterMapInit);
+        SubscribeLocalEvent<LightningArcShooterComponent, EntityUnpausedEvent>(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<LightningArcShooterComponent>();
+        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 (file)
index 0000000..7b569e6
--- /dev/null
@@ -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;
+
+/// <summary>
+/// The component changes the visual of an object after it is struck by lightning
+/// </summary>
+public sealed class LightningSparkingSystem : EntitySystem
+{
+    [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+    [Dependency] private readonly IGameTiming _gameTiming = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<LightningSparkingComponent, HitByLightningEvent>(OnHitByLightning);
+        SubscribeLocalEvent<LightningSparkingComponent, EntityUnpausedEvent>(OnLightningUnpaused);
+    }
+
+    private void OnLightningUnpaused(EntityUid uid, LightningSparkingComponent component, ref EntityUnpausedEvent args)
+    {
+        component.LightningEndTime += args.PausedTime;
+    }
+
+    private void OnHitByLightning(Entity<LightningSparkingComponent> 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<LightningSparkingComponent>();
+        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 (file)
index 0000000..bf98d77
--- /dev/null
@@ -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;
+
+/// <summary>
+/// Generates electricity from lightning bolts
+/// </summary>
+public sealed class TeslaCoilSystem : EntitySystem
+{
+    [Dependency] private readonly BatterySystem _battery = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<TeslaCoilComponent, HitByLightningEvent>(OnHitByLightning);
+    }
+
+    //When struck by lightning, charge the internal battery
+    private void OnHitByLightning(Entity<TeslaCoilComponent> coil, ref HitByLightningEvent args)
+    {
+        if (!TryComp<BatteryComponent>(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 (file)
index 0000000..ff7793d
--- /dev/null
@@ -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;
+
+/// <summary>
+/// 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.
+/// </summary>
+public sealed class TeslaEnergyBallSystem : EntitySystem
+{
+    [Dependency] private readonly AudioSystem _audio = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<TeslaEnergyBallComponent, EntityConsumedByEventHorizonEvent>(OnConsumed);
+    }
+
+    private void OnConsumed(Entity<TeslaEnergyBallComponent> tesla, ref EntityConsumedByEventHorizonEvent args)
+    {
+        Spawn(tesla.Comp.ConsumeEffectProto, Transform(args.Entity).Coordinates);
+        if (TryComp<SinguloFoodComponent>(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);
+        }
+    }
+}
index e01307bfd9ec761de4a60dd508eb7b36b3c24468..396f2710f3f89f181c17ec617ffd36a1c8321915 100644 (file)
@@ -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 (file)
index 0000000..2cc633f
--- /dev/null
@@ -0,0 +1,10 @@
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Power;
+
+[Serializable, NetSerializable]
+public enum TeslaCoilVisuals : byte
+{
+    Enabled,
+    Lightning
+}
index 890b2231173f682d10fbbd4ebba4e048fb5eab09..2aa081915beb760a935559848745070a3bf95034 100644 (file)
@@ -21,6 +21,18 @@ public sealed partial class EventHorizonComponent : Component
     [DataField("radius")]
     public float Radius;
 
+    /// <summary>
+    /// involves periodically destroying tiles within a specified radius
+    /// </summary>
+    [DataField]
+    public bool ConsumeTiles = true;
+
+    /// <summary>
+    /// involves periodically destroying entities within a specified radius. Does not affect collide destruction of entities.
+    /// </summary>
+    [DataField]
+    public bool ConsumeEntities = true;
+
     /// <summary>
     /// Whether the event horizon can consume/destroy the devices built to contain it.
     /// If you want to set this go through <see cref="SharedEventHorizonSystem.SetCanBreachContainment"/>.
index 22743ab1e9a318b77b8308fdcd7240e9bebf1c90..15293bf0f8d461e3d7bfa514a0e3ebf0a4832180 100644 (file)
   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 (file)
index 0000000..1edea86
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 (file)
index 0000000..65cc4bf
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 (file)
index 0000000..75c2aa8
Binary files /dev/null and b/Resources/Audio/Effects/tesla_consume.ogg differ
index 6b8d4e6451ce50a70c8ae11383e33fbde7aaebdd..a4a62d466b781084ab082f6fe5a389011c61c666 100644 (file)
@@ -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
index 753e963323e31e27169792f1ae37aaa708eb9200..a72d43fa6969c5cf381ad2071f9461f69f90bef7 100644 (file)
@@ -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 (file)
index 0000000..61f4970
--- /dev/null
@@ -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
index bdbfd53333178c2b9606b0da52219fed9efd0c0c..532f56a98eb34b3c1843d686aaf8198572612325 100644 (file)
   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
index 99f937b1e315e3d8579c6e587466e9cdcf927b63..73bedacaf3b971dd73a9f8f5f314c4d969230bb2 100644 (file)
       - 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
index 53542a9b7ba9c0d80cefd030ac902e9523bf8368..d86522a9711194e6f804e0c648fcad78229fa7d5 100644 (file)
     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
index 8719e03dda8bc5b92e663438f402b87673501865..8fb534e9fa072896742ecb3ee2595c1f06c2cdab 100644 (file)
@@ -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
index d6dc74efd0a470f728a7433b54314992a6c35aff..bbb85f335e38267d057a86eb79901bf3b349b10e 100644 (file)
   - type: AccessReader
   - type: StaticPrice
     price: 150
+  - type: LightningTarget
+    priority: 1
   - type: Tag
     tags:
       - Airlock
   placement:
     mode: SnapgridCenter
 
+
index d2c87deaa65441aefca35ace85b03dedb5e39a87..d1b870646d6997451af888f11b664305be168707 100644 (file)
@@ -57,3 +57,5 @@
     containers:
       board: !type:Container
         ents: []
+  - type: LightningTarget
+    priority: 1
index 31983102693cba3877c2b38b744770a08e0ef366..b4665a9b79af977b24c9665e6a67afc3c65f8f5a 100644 (file)
@@ -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 (file)
index 0000000..912fb6c
--- /dev/null
@@ -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 (file)
index 0000000..1e99a94
--- /dev/null
@@ -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 (file)
index 0000000..a8c6163
--- /dev/null
@@ -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
+
index fb477c810c8d1b1180da68ec4f5d6b1454039487..ff8b7752effb1342b8afdc5ab0e5a75ee730c250 100644 (file)
     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 (file)
index 0000000..7a1c148
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 (file)
index 0000000..892a652
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 (file)
index 0000000..8574e20
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 (file)
index 0000000..69651b0
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 (file)
index 0000000..d0a620f
--- /dev/null
@@ -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 (file)
index 0000000..e3bb49a
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 (file)
index 0000000..e3bb49a
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 (file)
index 0000000..8519efe
--- /dev/null
@@ -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 (file)
index 0000000..029bc54
--- /dev/null
@@ -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 (file)
index 0000000..e17359d
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 (file)
index 0000000..98b0d57
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 (file)
index 0000000..cd32288
--- /dev/null
@@ -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