-using Content.Shared.Lightning.Components;
+using Content.Shared.Lightning.Components;
namespace Content.Server.Lightning.Components;
[RegisterComponent]
--- /dev/null
+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;
+}
-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);
}
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>
{
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);
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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";
+}
--- /dev/null
+
+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;
+}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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.
+ }
+}
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
-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;
--- /dev/null
+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";
+}
--- /dev/null
+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;
+}
--- /dev/null
+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;
+}
--- /dev/null
+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";
+}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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;
+ }
+ }
+ }
+}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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);
+ }
+ }
+}
-using Content.Shared.Physics;
+using Content.Shared.Physics;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
--- /dev/null
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Power;
+
+[Serializable, NetSerializable]
+public enum TeslaCoilVisuals : byte
+{
+ Enabled,
+ Lightning
+}
[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"/>.
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"
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
.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
--- /dev/null
+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
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
- 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
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
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
- type: AccessReader
- type: StaticPrice
price: 150
+ - type: LightningTarget
+ priority: 1
- type: Tag
tags:
- Airlock
placement:
mode: SnapgridCenter
+
containers:
board: !type:Container
ents: []
+ - type: LightningTarget
+ priority: 1
- type: ApcPowerReceiver
powerLoad: 1000
- type: ExtensionCableReceiver
+ - type: LightningTarget
+ priority: 1
- type: entity
abstract: true
containers:
- machine_parts
- machine_board
+ - type: LightningTarget
+ priority: 1
--- /dev/null
+- 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
--- /dev/null
+- 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
--- /dev/null
+- 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
+
requirePower: true
mediumVoltageNode: input
lowVoltageNode: output
+ - type: LightningTarget
+ priority: 1
- type: StaticPrice
price: 500
--- /dev/null
+{
+ "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
--- /dev/null
+{
+ "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
--- /dev/null
+{
+ "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
--- /dev/null
+{
+ "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