Singularity fixes (#4383)
* Singularity fixes * Fix the rest * Woops * ahh * Nerf singulo for now * Final touchups for now * Review
This commit is contained in:
@@ -207,8 +207,6 @@ namespace Content.Client.Entry
|
|||||||
"GasTank",
|
"GasTank",
|
||||||
"BreathMask",
|
"BreathMask",
|
||||||
"RadiationCollector",
|
"RadiationCollector",
|
||||||
"ContainmentFieldGenerator",
|
|
||||||
"ContainmentField",
|
|
||||||
"Emitter",
|
"Emitter",
|
||||||
"SingularityGenerator",
|
"SingularityGenerator",
|
||||||
"ParticleProjectile",
|
"ParticleProjectile",
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
|
using Content.Shared.Singularity.Components;
|
||||||
using Robust.Client.GameObjects;
|
using Robust.Client.GameObjects;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Log;
|
using Robust.Shared.Log;
|
||||||
|
|
||||||
namespace Content.Client.Singularity.Components
|
namespace Content.Client.Singularity.Components
|
||||||
{
|
{
|
||||||
public class ContainmentFieldComponent : Component
|
[RegisterComponent]
|
||||||
|
[ComponentReference(typeof(SharedContainmentFieldComponent))]
|
||||||
|
public class ContainmentFieldComponent : SharedContainmentFieldComponent
|
||||||
{
|
{
|
||||||
public override string Name => "ContainmentField";
|
// Jesus what is this code.
|
||||||
|
// Singulo cleanup WHEEENNN
|
||||||
private SpriteComponent? _spriteComponent;
|
private SpriteComponent? _spriteComponent;
|
||||||
|
|
||||||
protected override void Initialize()
|
protected override void Initialize()
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using Content.Shared.Singularity.Components;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Client.Singularity.Components
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
[ComponentReference(typeof(SharedContainmentFieldGeneratorComponent))]
|
||||||
|
public sealed class ContainmentFieldGeneratorComponent : SharedContainmentFieldGeneratorComponent
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ using JetBrains.Annotations;
|
|||||||
using Robust.Client.GameObjects;
|
using Robust.Client.GameObjects;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Serialization.Manager.Attributes;
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Client.Singularity.Visualizers
|
namespace Content.Client.Singularity.Visualizers
|
||||||
{
|
{
|
||||||
@@ -33,8 +34,7 @@ namespace Content.Client.Singularity.Visualizers
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
sprite.LayerSetRSI(Layer, "Structures/Power/Generation/Singularity/singularity_" + level + ".rsi");
|
sprite.LayerSetSprite(Layer, new SpriteSpecifier.Rsi(new ResourcePath("Structures/Power/Generation/Singularity/singularity_" + level + ".rsi"), "singularity_" + level));
|
||||||
sprite.LayerSetState(Layer, "singularity_" + level);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
using Content.Server.Ghost.Components;
|
|
||||||
using Content.Server.Singularity.Components;
|
using Content.Server.Singularity.Components;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Map;
|
|
||||||
using Robust.Shared.Maths;
|
using Robust.Shared.Maths;
|
||||||
using Robust.Shared.Physics;
|
|
||||||
using Robust.Shared.Physics.Broadphase;
|
|
||||||
using Robust.Shared.Physics.Controllers;
|
using Robust.Shared.Physics.Controllers;
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
@@ -14,93 +10,48 @@ namespace Content.Server.Physics.Controllers
|
|||||||
{
|
{
|
||||||
internal sealed class SingularityController : VirtualController
|
internal sealed class SingularityController : VirtualController
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
|
||||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||||
|
|
||||||
private float _pullAccumulator;
|
private const float MaxMoveCooldown = 10f;
|
||||||
private float _moveAccumulator;
|
private const float MinMoveCooldown = 2f;
|
||||||
|
|
||||||
public override void UpdateBeforeSolve(bool prediction, float frameTime)
|
public override void UpdateBeforeSolve(bool prediction, float frameTime)
|
||||||
{
|
{
|
||||||
base.UpdateBeforeSolve(prediction, frameTime);
|
base.UpdateBeforeSolve(prediction, frameTime);
|
||||||
|
|
||||||
_moveAccumulator += frameTime;
|
foreach (var (singularity, physics) in ComponentManager.EntityQuery<ServerSingularityComponent, PhysicsComponent>())
|
||||||
_pullAccumulator += frameTime;
|
|
||||||
|
|
||||||
while (_pullAccumulator > 0.5f)
|
|
||||||
{
|
{
|
||||||
_pullAccumulator -= 0.5f;
|
if (singularity.Owner.HasComponent<ActorComponent>() ||
|
||||||
|
singularity.BeingDeletedByAnotherSingularity) continue;
|
||||||
|
|
||||||
foreach (var singularity in ComponentManager.EntityQuery<ServerSingularityComponent>())
|
singularity.MoveAccumulator -= frameTime;
|
||||||
{
|
|
||||||
// TODO: Use colliders instead probably yada yada
|
|
||||||
PullEntities(singularity);
|
|
||||||
// Yeah look the collision with station wasn't working and I'm 15k lines in and not debugging this shit
|
|
||||||
DestroyTiles(singularity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (_moveAccumulator > 1.0f)
|
if (singularity.MoveAccumulator > 0f) continue;
|
||||||
{
|
|
||||||
_moveAccumulator -= 1.0f;
|
|
||||||
|
|
||||||
foreach (var (singularity, physics) in ComponentManager.EntityQuery<ServerSingularityComponent, PhysicsComponent>())
|
singularity.MoveAccumulator = MinMoveCooldown + (MaxMoveCooldown - MinMoveCooldown) * _robustRandom.NextFloat();
|
||||||
{
|
|
||||||
if (singularity.Owner.HasComponent<ActorComponent>()) continue;
|
|
||||||
|
|
||||||
// TODO: Need to essentially use a push vector in a random direction for us PLUS
|
MoveSingulo(singularity, physics);
|
||||||
// Any entity colliding with our larger circlebox needs to have an impulse applied to itself.
|
|
||||||
physics.BodyStatus = BodyStatus.InAir;
|
|
||||||
MoveSingulo(singularity, physics);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MoveSingulo(ServerSingularityComponent singularity, PhysicsComponent physics)
|
private void MoveSingulo(ServerSingularityComponent singularity, PhysicsComponent physics)
|
||||||
{
|
{
|
||||||
// To prevent getting stuck, ServerSingularityComponent will zero the velocity of a singularity when it goes to a level <= 1 (see here).
|
// TODO: Need to make this events instead.
|
||||||
if (singularity.Level <= 1) return;
|
if (singularity.Level <= 1)
|
||||||
// TODO: Could try gradual changes instead but for now just try to replicate
|
{
|
||||||
|
physics.BodyStatus = BodyStatus.OnGround;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Could try gradual changes instead
|
||||||
var pushVector = new Vector2(_robustRandom.Next(-10, 10), _robustRandom.Next(-10, 10));
|
var pushVector = new Vector2(_robustRandom.Next(-10, 10), _robustRandom.Next(-10, 10));
|
||||||
|
|
||||||
if (pushVector == Vector2.Zero) return;
|
if (pushVector == Vector2.Zero) return;
|
||||||
|
|
||||||
physics.LinearVelocity = Vector2.Zero;
|
physics.LinearVelocity = Vector2.Zero;
|
||||||
physics.LinearVelocity = pushVector.Normalized * 2;
|
physics.BodyStatus = BodyStatus.InAir;
|
||||||
}
|
physics.ApplyLinearImpulse(pushVector.Normalized + 1f / singularity.Level * physics.Mass);
|
||||||
|
// TODO: Speedcap it probably?
|
||||||
private void PullEntities(ServerSingularityComponent component)
|
|
||||||
{
|
|
||||||
var singularityCoords = component.Owner.Transform.Coordinates;
|
|
||||||
// TODO: Maybe if we have named fixtures needs to pull out the outer circle collider (inner will be for deleting).
|
|
||||||
var entitiesToPull = IoCManager.Resolve<IEntityLookup>().GetEntitiesInRange(singularityCoords, component.Level * 10);
|
|
||||||
foreach (var entity in entitiesToPull)
|
|
||||||
{
|
|
||||||
if (!entity.TryGetComponent<PhysicsComponent>(out var collidableComponent) || collidableComponent.BodyType == BodyType.Static) continue;
|
|
||||||
if (entity.HasComponent<GhostComponent>()) continue;
|
|
||||||
if (singularityCoords.EntityId != entity.Transform.Coordinates.EntityId) continue;
|
|
||||||
var vec = (singularityCoords - entity.Transform.Coordinates).Position;
|
|
||||||
if (vec == Vector2.Zero) continue;
|
|
||||||
|
|
||||||
var speed = 10 / vec.Length * component.Level;
|
|
||||||
|
|
||||||
collidableComponent.ApplyLinearImpulse(vec.Normalized * speed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DestroyTiles(ServerSingularityComponent component)
|
|
||||||
{
|
|
||||||
if (!component.Owner.TryGetComponent(out PhysicsComponent? physicsComponent)) return;
|
|
||||||
var worldBox = physicsComponent.GetWorldAABB();
|
|
||||||
|
|
||||||
foreach (var grid in _mapManager.FindGridsIntersecting(component.Owner.Transform.MapID, worldBox))
|
|
||||||
{
|
|
||||||
foreach (var tile in grid.GetTilesIntersecting(worldBox))
|
|
||||||
{
|
|
||||||
grid.SetTile(tile.GridIndices, Tile.Empty);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,53 +11,39 @@ namespace Content.Server.Radiation
|
|||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public sealed class RadiationPulseSystem : EntitySystem
|
public sealed class RadiationPulseSystem : EntitySystem
|
||||||
{
|
{
|
||||||
private const string RadiationPrototype = "RadiationPulse";
|
[Dependency] private readonly IEntityLookup _lookup = default!;
|
||||||
|
|
||||||
public IEntity RadiationPulse(
|
private const float RadiationCooldown = 0.5f;
|
||||||
EntityCoordinates coordinates,
|
private float _accumulator;
|
||||||
float range,
|
|
||||||
int dps,
|
|
||||||
bool decay = true,
|
|
||||||
float minPulseLifespan = 0.8f,
|
|
||||||
float maxPulseLifespan = 2.5f,
|
|
||||||
SoundSpecifier sound = default!)
|
|
||||||
{
|
|
||||||
var radiationEntity = EntityManager.SpawnEntity(RadiationPrototype, coordinates);
|
|
||||||
var radiation = radiationEntity.GetComponent<RadiationPulseComponent>();
|
|
||||||
|
|
||||||
radiation.Range = range;
|
|
||||||
radiation.RadsPerSecond = dps;
|
|
||||||
radiation.Draw = false;
|
|
||||||
radiation.Decay = decay;
|
|
||||||
radiation.MinPulseLifespan = minPulseLifespan;
|
|
||||||
radiation.MaxPulseLifespan = maxPulseLifespan;
|
|
||||||
radiation.Sound = sound;
|
|
||||||
|
|
||||||
radiation.DoPulse();
|
|
||||||
|
|
||||||
return radiationEntity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Update(float frameTime)
|
public override void Update(float frameTime)
|
||||||
{
|
{
|
||||||
base.Update(frameTime);
|
base.Update(frameTime);
|
||||||
|
|
||||||
var lookupSystem = IoCManager.Resolve<IEntityLookup>();
|
_accumulator += RadiationCooldown;
|
||||||
|
|
||||||
foreach (var comp in ComponentManager.EntityQuery<RadiationPulseComponent>(true))
|
while (_accumulator > RadiationCooldown)
|
||||||
{
|
{
|
||||||
comp.Update(frameTime);
|
_accumulator -= RadiationCooldown;
|
||||||
var ent = comp.Owner;
|
|
||||||
|
|
||||||
if (ent.Deleted) continue;
|
foreach (var comp in ComponentManager.EntityQuery<RadiationPulseComponent>(true))
|
||||||
|
|
||||||
foreach (var entity in lookupSystem.GetEntitiesInRange(ent.Transform.Coordinates, comp.Range, true))
|
|
||||||
{
|
{
|
||||||
if (entity.Deleted) continue;
|
comp.Update(frameTime);
|
||||||
|
var ent = comp.Owner;
|
||||||
|
|
||||||
foreach (var radiation in entity.GetAllComponents<IRadiationAct>().ToArray())
|
if (ent.Deleted) continue;
|
||||||
|
|
||||||
|
foreach (var entity in _lookup.GetEntitiesInRange(ent.Transform.Coordinates, comp.Range))
|
||||||
{
|
{
|
||||||
radiation.RadiationAct(frameTime, comp);
|
// For now at least still need this because it uses a list internally then returns and this may be deleted before we get to it.
|
||||||
|
if (entity.Deleted) continue;
|
||||||
|
|
||||||
|
// Note: Radiation is liable for a refactor (stinky Sloth coding a basic version when he did StationEvents)
|
||||||
|
// so this ToArray doesn't really matter.
|
||||||
|
foreach (var radiation in entity.GetAllComponents<IRadiationAct>().ToArray())
|
||||||
|
{
|
||||||
|
radiation.RadiationAct(RadiationCooldown, comp);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
|
using Content.Shared.Singularity.Components;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
|
|
||||||
namespace Content.Server.Singularity.Components
|
namespace Content.Server.Singularity.Components
|
||||||
{
|
{
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public class ContainmentFieldComponent : Component
|
[ComponentReference(typeof(SharedContainmentFieldComponent))]
|
||||||
|
public class ContainmentFieldComponent : SharedContainmentFieldComponent
|
||||||
{
|
{
|
||||||
public override string Name => "ContainmentField";
|
|
||||||
public ContainmentFieldConnection? Parent;
|
public ContainmentFieldConnection? Parent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Shared.Physics;
|
using Content.Shared.Physics;
|
||||||
|
using Content.Shared.Singularity.Components;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Log;
|
using Robust.Shared.Log;
|
||||||
@@ -13,10 +14,9 @@ using Robust.Shared.ViewVariables;
|
|||||||
namespace Content.Server.Singularity.Components
|
namespace Content.Server.Singularity.Components
|
||||||
{
|
{
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public class ContainmentFieldGeneratorComponent : Component
|
[ComponentReference(typeof(SharedContainmentFieldGeneratorComponent))]
|
||||||
|
public class ContainmentFieldGeneratorComponent : SharedContainmentFieldGeneratorComponent
|
||||||
{
|
{
|
||||||
public override string Name => "ContainmentFieldGenerator";
|
|
||||||
|
|
||||||
private int _powerBuffer;
|
private int _powerBuffer;
|
||||||
|
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
|
|||||||
@@ -62,6 +62,8 @@ namespace Content.Server.Singularity.Components
|
|||||||
_ => 0
|
_ => 0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public float MoveAccumulator;
|
||||||
|
|
||||||
// This is an interesting little workaround.
|
// This is an interesting little workaround.
|
||||||
// See, two singularities queuing deletion of each other at the same time will annihilate.
|
// See, two singularities queuing deletion of each other at the same time will annihilate.
|
||||||
// This is undesirable behaviour, so this flag allows the imperatively first one processed to take priority.
|
// This is undesirable behaviour, so this flag allows the imperatively first one processed to take priority.
|
||||||
@@ -95,11 +97,6 @@ namespace Content.Server.Singularity.Components
|
|||||||
_singularitySystem.ChangeSingularityLevel(this, 1);
|
_singularitySystem.ChangeSingularityLevel(this, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Update(int seconds)
|
|
||||||
{
|
|
||||||
Energy -= EnergyDrain * seconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnRemove()
|
protected override void OnRemove()
|
||||||
{
|
{
|
||||||
_playingSound?.Stop();
|
_playingSound?.Stop();
|
||||||
|
|||||||
@@ -168,6 +168,8 @@ namespace Content.Server.Singularity.EntitySystems
|
|||||||
|
|
||||||
private void ShotTimerCallback(EmitterComponent component)
|
private void ShotTimerCallback(EmitterComponent component)
|
||||||
{
|
{
|
||||||
|
if (component.Deleted) return;
|
||||||
|
|
||||||
// Any power-off condition should result in the timer for this method being cancelled
|
// Any power-off condition should result in the timer for this method being cancelled
|
||||||
// and thus not firing
|
// and thus not firing
|
||||||
DebugTools.Assert(component.IsPowered);
|
DebugTools.Assert(component.IsPowered);
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
|
using Content.Server.Ghost.Components;
|
||||||
using Content.Server.Singularity.Components;
|
using Content.Server.Singularity.Components;
|
||||||
using Content.Shared.Singularity;
|
using Content.Shared.Singularity;
|
||||||
|
using Content.Shared.Singularity.Components;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Shared.Containers;
|
using Robust.Shared.Containers;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using Robust.Shared.Physics;
|
||||||
using Robust.Shared.Physics.Dynamics;
|
using Robust.Shared.Physics.Dynamics;
|
||||||
|
|
||||||
namespace Content.Server.Singularity.EntitySystems
|
namespace Content.Server.Singularity.EntitySystems
|
||||||
@@ -10,7 +16,18 @@ namespace Content.Server.Singularity.EntitySystems
|
|||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public class SingularitySystem : SharedSingularitySystem
|
public class SingularitySystem : SharedSingularitySystem
|
||||||
{
|
{
|
||||||
private float _updateInterval = 1.0f;
|
[Dependency] private readonly IEntityLookup _lookup = default!;
|
||||||
|
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How much energy the singulo gains from destroying a tile.
|
||||||
|
/// </summary>
|
||||||
|
private const int TileEnergyGain = 1;
|
||||||
|
|
||||||
|
private const float GravityCooldown = 0.5f;
|
||||||
|
private float _gravityAccumulator;
|
||||||
|
|
||||||
|
private int _updateInterval = 1;
|
||||||
private float _accumulator;
|
private float _accumulator;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
@@ -21,47 +38,23 @@ namespace Content.Server.Singularity.EntitySystems
|
|||||||
|
|
||||||
private void HandleCollide(EntityUid uid, ServerSingularityComponent component, StartCollideEvent args)
|
private void HandleCollide(EntityUid uid, ServerSingularityComponent component, StartCollideEvent args)
|
||||||
{
|
{
|
||||||
|
// This handles bouncing off of containment walls.
|
||||||
|
// If you want the delete behavior we do it under DeleteEntities for reasons (not everything has physics).
|
||||||
|
|
||||||
// If we're being deleted by another singularity, this call is probably for that singularity.
|
// If we're being deleted by another singularity, this call is probably for that singularity.
|
||||||
// Even if not, just don't bother.
|
// Even if not, just don't bother.
|
||||||
if (component.BeingDeletedByAnotherSingularity)
|
if (component.BeingDeletedByAnotherSingularity)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var otherEntity = args.OtherFixture.Body.Owner;
|
// Using this to also get smooth deletions is hard because we need to be hard for good bounce
|
||||||
|
// off of containment but also we need to be non-hard so we can freely move through the station.
|
||||||
if (otherEntity.TryGetComponent<IMapGridComponent>(out var mapGridComponent))
|
// For now I've just made it so only the lookup does deletions and collision is just for fields.
|
||||||
{
|
|
||||||
foreach (var tile in mapGridComponent.Grid.GetTilesIntersecting(args.OurFixture.Body.GetWorldAABB()))
|
|
||||||
{
|
|
||||||
mapGridComponent.Grid.SetTile(tile.GridIndices, Robust.Shared.Map.Tile.Empty);
|
|
||||||
component.Energy++;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (otherEntity.HasComponent<ContainmentFieldComponent>() ||
|
|
||||||
(otherEntity.TryGetComponent<ContainmentFieldGeneratorComponent>(out var containmentField) && containmentField.CanRepell(component.Owner)))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (otherEntity.IsInContainer())
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Singularity priority management / etc.
|
|
||||||
if (otherEntity.TryGetComponent<ServerSingularityComponent>(out var otherSingulo))
|
|
||||||
otherSingulo.BeingDeletedByAnotherSingularity = true;
|
|
||||||
|
|
||||||
otherEntity.QueueDelete();
|
|
||||||
|
|
||||||
if (otherEntity.TryGetComponent<SinguloFoodComponent>(out var singuloFood))
|
|
||||||
component.Energy += singuloFood.Energy;
|
|
||||||
else
|
|
||||||
component.Energy++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Update(float frameTime)
|
public override void Update(float frameTime)
|
||||||
{
|
{
|
||||||
base.Update(frameTime);
|
base.Update(frameTime);
|
||||||
|
_gravityAccumulator += frameTime;
|
||||||
_accumulator += frameTime;
|
_accumulator += frameTime;
|
||||||
|
|
||||||
while (_accumulator > _updateInterval)
|
while (_accumulator > _updateInterval)
|
||||||
@@ -70,7 +63,142 @@ namespace Content.Server.Singularity.EntitySystems
|
|||||||
|
|
||||||
foreach (var singularity in ComponentManager.EntityQuery<ServerSingularityComponent>())
|
foreach (var singularity in ComponentManager.EntityQuery<ServerSingularityComponent>())
|
||||||
{
|
{
|
||||||
singularity.Update(1);
|
singularity.Energy -= singularity.EnergyDrain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (_gravityAccumulator > GravityCooldown)
|
||||||
|
{
|
||||||
|
_gravityAccumulator -= GravityCooldown;
|
||||||
|
|
||||||
|
foreach (var singularity in ComponentManager.EntityQuery<ServerSingularityComponent>())
|
||||||
|
{
|
||||||
|
Update(singularity, GravityCooldown);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update(ServerSingularityComponent component, float frameTime)
|
||||||
|
{
|
||||||
|
if (component.BeingDeletedByAnotherSingularity) return;
|
||||||
|
|
||||||
|
var worldPos = component.Owner.Transform.WorldPosition;
|
||||||
|
DestroyEntities(component, worldPos);
|
||||||
|
DestroyTiles(component, worldPos);
|
||||||
|
PullEntities(component, worldPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private float PullRange(ServerSingularityComponent component)
|
||||||
|
{
|
||||||
|
// Level 6 is normally 15 range but that's yuge.
|
||||||
|
return 2 + component.Level * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
private float DestroyTileRange(ServerSingularityComponent component)
|
||||||
|
{
|
||||||
|
return component.Level - 0.5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CanDestroy(SharedSingularityComponent component, IEntity entity)
|
||||||
|
{
|
||||||
|
return entity == component.Owner ||
|
||||||
|
entity.HasComponent<IMapGridComponent>() ||
|
||||||
|
entity.HasComponent<GhostComponent>() ||
|
||||||
|
entity.HasComponent<ContainmentFieldComponent>() ||
|
||||||
|
entity.HasComponent<ContainmentFieldGeneratorComponent>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleDestroy(ServerSingularityComponent component, IEntity entity)
|
||||||
|
{
|
||||||
|
// TODO: Need singuloimmune tag
|
||||||
|
if (CanDestroy(component, entity)) return;
|
||||||
|
|
||||||
|
// Singularity priority management / etc.
|
||||||
|
if (entity.TryGetComponent<ServerSingularityComponent>(out var otherSingulo))
|
||||||
|
{
|
||||||
|
// MERGE
|
||||||
|
if (!otherSingulo.BeingDeletedByAnotherSingularity)
|
||||||
|
{
|
||||||
|
component.Energy += otherSingulo.Energy;
|
||||||
|
}
|
||||||
|
|
||||||
|
otherSingulo.BeingDeletedByAnotherSingularity = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
entity.QueueDelete();
|
||||||
|
|
||||||
|
if (entity.TryGetComponent<SinguloFoodComponent>(out var singuloFood))
|
||||||
|
component.Energy += singuloFood.Energy;
|
||||||
|
else
|
||||||
|
component.Energy++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handle deleting entities and increasing energy
|
||||||
|
/// </summary>
|
||||||
|
private void DestroyEntities(ServerSingularityComponent component, Vector2 worldPos)
|
||||||
|
{
|
||||||
|
// The reason we don't /just/ use collision is because we'll be deleting stuff that may not necessarily have physics (e.g. carpets).
|
||||||
|
var destroyRange = DestroyTileRange(component);
|
||||||
|
|
||||||
|
foreach (var entity in _lookup.GetEntitiesInRange(component.Owner.Transform.MapID, worldPos, destroyRange))
|
||||||
|
{
|
||||||
|
HandleDestroy(component, entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CanPull(IEntity entity)
|
||||||
|
{
|
||||||
|
return !(entity.HasComponent<GhostComponent>() ||
|
||||||
|
entity.HasComponent<IMapGridComponent>() ||
|
||||||
|
entity.HasComponent<MapComponent>() ||
|
||||||
|
entity.IsInContainer());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PullEntities(ServerSingularityComponent component, Vector2 worldPos)
|
||||||
|
{
|
||||||
|
// TODO: When we split up dynamic and static trees we might be able to make items always on the broadphase
|
||||||
|
// in which case we can just query dynamictree directly for brrt
|
||||||
|
var pullRange = PullRange(component);
|
||||||
|
var destroyRange = DestroyTileRange(component);
|
||||||
|
|
||||||
|
foreach (var entity in _lookup.GetEntitiesInRange(component.Owner.Transform.MapID, worldPos, pullRange))
|
||||||
|
{
|
||||||
|
// I tried having it so level 6 can de-anchor. BAD IDEA, MASSIVE LAG.
|
||||||
|
if (entity == component.Owner ||
|
||||||
|
!entity.TryGetComponent<PhysicsComponent>(out var collidableComponent) ||
|
||||||
|
collidableComponent.BodyType == BodyType.Static) continue;
|
||||||
|
|
||||||
|
if (!CanPull(entity)) continue;
|
||||||
|
|
||||||
|
var vec = worldPos - entity.Transform.WorldPosition;
|
||||||
|
|
||||||
|
if (vec.Length < destroyRange - 0.01f) continue;
|
||||||
|
|
||||||
|
var speed = vec.Length * component.Level * collidableComponent.Mass;
|
||||||
|
|
||||||
|
// Because tile friction is so high we'll just multiply by mass so stuff like closets can even move.
|
||||||
|
collidableComponent.ApplyLinearImpulse(vec.Normalized * speed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Destroy any grid tiles within the relevant Level range.
|
||||||
|
/// </summary>
|
||||||
|
private void DestroyTiles(ServerSingularityComponent component, Vector2 worldPos)
|
||||||
|
{
|
||||||
|
var radius = DestroyTileRange(component);
|
||||||
|
|
||||||
|
var circle = new Circle(worldPos, radius);
|
||||||
|
var box = new Box2(worldPos - radius, worldPos + radius);
|
||||||
|
|
||||||
|
foreach (var grid in _mapManager.FindGridsIntersecting(component.Owner.Transform.MapID, box))
|
||||||
|
{
|
||||||
|
foreach (var tile in grid.GetTilesIntersecting(circle))
|
||||||
|
{
|
||||||
|
if (tile.Tile.IsEmpty) continue;
|
||||||
|
grid.SetTile(tile.GridIndices, Tile.Empty);
|
||||||
|
component.Energy += TileEnergyGain;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,8 @@ namespace Content.Shared.Friction
|
|||||||
foreach (var body in map.AwakeBodies)
|
foreach (var body in map.AwakeBodies)
|
||||||
{
|
{
|
||||||
// Only apply friction when it's not a mob (or the mob doesn't have control)
|
// Only apply friction when it's not a mob (or the mob doesn't have control)
|
||||||
if (prediction && !body.Predict ||
|
if (body.Deleted ||
|
||||||
|
prediction && !body.Predict ||
|
||||||
body.BodyStatus == BodyStatus.InAir ||
|
body.BodyStatus == BodyStatus.InAir ||
|
||||||
Mover.UseMobMovement(body.Owner.Uid)) continue;
|
Mover.UseMobMovement(body.Owner.Uid)) continue;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Shared.Singularity.Components
|
||||||
|
{
|
||||||
|
public abstract class SharedContainmentFieldComponent : Component
|
||||||
|
{
|
||||||
|
public override string Name => "ContainmentField";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Shared.Singularity.Components
|
||||||
|
{
|
||||||
|
public abstract class SharedContainmentFieldGeneratorComponent : Component
|
||||||
|
{
|
||||||
|
public override string Name => "ContainmentFieldGenerator";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,8 +12,6 @@ namespace Content.Shared.Singularity.Components
|
|||||||
{
|
{
|
||||||
public override string Name => "Singularity";
|
public override string Name => "Singularity";
|
||||||
|
|
||||||
[DataField("deleteFixture")] public string? DeleteFixtureId { get; } = default;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Changed by <see cref="SharedSingularitySystem.ChangeSingularityLevel"/>
|
/// Changed by <see cref="SharedSingularitySystem.ChangeSingularityLevel"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -4,11 +4,14 @@ using Content.Shared.Singularity.Components;
|
|||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Maths;
|
using Robust.Shared.Maths;
|
||||||
using Robust.Shared.Physics.Collision.Shapes;
|
using Robust.Shared.Physics.Collision.Shapes;
|
||||||
|
using Robust.Shared.Physics.Dynamics;
|
||||||
|
|
||||||
namespace Content.Shared.Singularity
|
namespace Content.Shared.Singularity
|
||||||
{
|
{
|
||||||
public abstract class SharedSingularitySystem : EntitySystem
|
public abstract class SharedSingularitySystem : EntitySystem
|
||||||
{
|
{
|
||||||
|
public const string DeleteFixture = "DeleteCircle";
|
||||||
|
|
||||||
private float GetFalloff(int level)
|
private float GetFalloff(int level)
|
||||||
{
|
{
|
||||||
return level switch
|
return level switch
|
||||||
@@ -72,8 +75,7 @@ namespace Content.Shared.Singularity
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (physics != null &&
|
if (physics != null &&
|
||||||
singularity.DeleteFixtureId != null &&
|
physics.GetFixture(DeleteFixture) is {Shape: PhysShapeCircle circle})
|
||||||
physics.GetFixture(singularity.DeleteFixtureId) is {Shape: PhysShapeCircle circle})
|
|
||||||
{
|
{
|
||||||
circle.Radius = value - 0.5f;
|
circle.Radius = value - 0.5f;
|
||||||
}
|
}
|
||||||
@@ -86,5 +88,24 @@ namespace Content.Shared.Singularity
|
|||||||
|
|
||||||
singularity.Dirty();
|
singularity.Dirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
SubscribeLocalEvent<SharedSingularityComponent, PreventCollideEvent>(HandleFieldCollision);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleFieldCollision(EntityUid uid, SharedSingularityComponent component, PreventCollideEvent args)
|
||||||
|
{
|
||||||
|
var other = args.BodyB.Owner;
|
||||||
|
|
||||||
|
if ((!other.HasComponent<SharedContainmentFieldComponent>() &&
|
||||||
|
!other.HasComponent<SharedContainmentFieldGeneratorComponent>()) ||
|
||||||
|
component.Level >= 4)
|
||||||
|
{
|
||||||
|
args.Cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,20 +11,21 @@
|
|||||||
shape:
|
shape:
|
||||||
!type:PhysShapeCircle
|
!type:PhysShapeCircle
|
||||||
radius: 0.5
|
radius: 0.5
|
||||||
mass: 5
|
restitution: 0.9
|
||||||
# Keep an eye on ParticlesProjectile when adjusting these
|
|
||||||
layer:
|
|
||||||
- Impassable
|
|
||||||
mask:
|
mask:
|
||||||
- AllMask
|
- AllMask
|
||||||
|
layer:
|
||||||
|
- AllMask
|
||||||
- type: Singularity
|
- type: Singularity
|
||||||
- type: SingularityDistortion
|
- type: SingularityDistortion
|
||||||
- type: RadiationPulse
|
- type: RadiationPulse
|
||||||
range: 15
|
range: 15
|
||||||
decay: false
|
decay: false
|
||||||
|
dps: 1
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
sprite: Structures/Power/Generation/Singularity/singularity_1.rsi
|
sprite: Structures/Power/Generation/Singularity/singularity_1.rsi
|
||||||
state: singularity_1
|
state: singularity_1
|
||||||
|
netsync: false
|
||||||
- type: Icon
|
- type: Icon
|
||||||
sprite: Structures/Power/Generation/Singularity/singularity_1.rsi
|
sprite: Structures/Power/Generation/Singularity/singularity_1.rsi
|
||||||
state: singularity_1
|
state: singularity_1
|
||||||
|
|||||||
Reference in New Issue
Block a user