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