Stabilise singularity a lot more (#5725)
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace Content.Client.Singularity
|
namespace Content.Client.Singularity
|
||||||
{
|
{
|
||||||
public class SingularitySystem : SharedSingularitySystem
|
public sealed class SingularitySystem : SharedSingularitySystem
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
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;
|
||||||
@@ -12,7 +13,8 @@ namespace Content.Server.Physics.Controllers
|
|||||||
{
|
{
|
||||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||||
|
|
||||||
private const float MaxMoveCooldown = 10f;
|
// SS13 has 10s but that's quite a while
|
||||||
|
private const float MaxMoveCooldown = 5f;
|
||||||
private const float MinMoveCooldown = 2f;
|
private const float MinMoveCooldown = 2f;
|
||||||
|
|
||||||
public override void UpdateBeforeSolve(bool prediction, float frameTime)
|
public override void UpdateBeforeSolve(bool prediction, float frameTime)
|
||||||
@@ -28,7 +30,7 @@ namespace Content.Server.Physics.Controllers
|
|||||||
|
|
||||||
if (singularity.MoveAccumulator > 0f) continue;
|
if (singularity.MoveAccumulator > 0f) continue;
|
||||||
|
|
||||||
singularity.MoveAccumulator = MinMoveCooldown + (MaxMoveCooldown - MinMoveCooldown) * _robustRandom.NextFloat();
|
singularity.MoveAccumulator = _robustRandom.NextFloat(MinMoveCooldown, MaxMoveCooldown);
|
||||||
|
|
||||||
MoveSingulo(singularity, physics);
|
MoveSingulo(singularity, physics);
|
||||||
}
|
}
|
||||||
@@ -44,13 +46,12 @@ namespace Content.Server.Physics.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Could try gradual changes instead
|
// TODO: Could try gradual changes instead
|
||||||
var pushVector = new Vector2(_robustRandom.Next(-10, 10), _robustRandom.Next(-10, 10));
|
var pushAngle = _robustRandom.NextAngle();
|
||||||
|
var pushStrength = _robustRandom.NextFloat(0.75f, 1.0f);
|
||||||
if (pushVector == Vector2.Zero) return;
|
|
||||||
|
|
||||||
physics.LinearVelocity = Vector2.Zero;
|
physics.LinearVelocity = Vector2.Zero;
|
||||||
physics.BodyStatus = BodyStatus.InAir;
|
physics.BodyStatus = BodyStatus.InAir;
|
||||||
physics.ApplyLinearImpulse(pushVector.Normalized + 1f / singularity.Level * physics.Mass);
|
physics.ApplyLinearImpulse(pushAngle.ToVec() * (pushStrength + 10f / Math.Min(singularity.Level, 4) * physics.Mass));
|
||||||
// TODO: Speedcap it probably?
|
// TODO: Speedcap it probably?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
using Content.Server.Ghost.Components;
|
using Content.Server.Ghost.Components;
|
||||||
using Content.Server.Singularity.Components;
|
using Content.Server.Singularity.Components;
|
||||||
using Content.Shared.Singularity;
|
using Content.Shared.Singularity;
|
||||||
@@ -14,10 +15,11 @@ using Robust.Shared.Physics.Dynamics;
|
|||||||
namespace Content.Server.Singularity.EntitySystems
|
namespace Content.Server.Singularity.EntitySystems
|
||||||
{
|
{
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public class SingularitySystem : SharedSingularitySystem
|
public sealed class SingularitySystem : SharedSingularitySystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IEntityLookup _lookup = default!;
|
[Dependency] private readonly IEntityLookup _lookup = default!;
|
||||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||||
|
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// How much energy the singulo gains from destroying a tile.
|
/// How much energy the singulo gains from destroying a tile.
|
||||||
@@ -33,11 +35,28 @@ namespace Content.Server.Singularity.EntitySystems
|
|||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
SubscribeLocalEvent<ServerSingularityComponent, StartCollideEvent>(HandleCollide);
|
SubscribeLocalEvent<ServerSingularityComponent, StartCollideEvent>(OnCollide);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleCollide(EntityUid uid, ServerSingularityComponent component, StartCollideEvent args)
|
protected override bool PreventCollide(EntityUid uid, SharedSingularityComponent component, PreventCollideEvent args)
|
||||||
{
|
{
|
||||||
|
if (base.PreventCollide(uid, component, args)) return true;
|
||||||
|
|
||||||
|
var otherUid = args.BodyB.Owner;
|
||||||
|
|
||||||
|
if (args.Cancelled) return true;
|
||||||
|
|
||||||
|
// If it's not cancelled then we'll cancel if we can't immediately destroy it on collision
|
||||||
|
if (!CanDestroy(component, otherUid))
|
||||||
|
args.Cancel();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCollide(EntityUid uid, ServerSingularityComponent component, StartCollideEvent args)
|
||||||
|
{
|
||||||
|
if (args.OurFixture.ID != "DeleteCircle") return;
|
||||||
|
|
||||||
// This handles bouncing off of containment walls.
|
// 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 you want the delete behavior we do it under DeleteEntities for reasons (not everything has physics).
|
||||||
|
|
||||||
@@ -46,9 +65,10 @@ namespace Content.Server.Singularity.EntitySystems
|
|||||||
if (component.BeingDeletedByAnotherSingularity)
|
if (component.BeingDeletedByAnotherSingularity)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Using this to also get smooth deletions is hard because we need to be hard for good bounce
|
var otherUid = args.OtherFixture.Body.Owner;
|
||||||
// off of containment but also we need to be non-hard so we can freely move through the station.
|
|
||||||
// For now I've just made it so only the lookup does deletions and collision is just for fields.
|
// HandleDestroy will also check CanDestroy for us
|
||||||
|
HandleDestroy(component, otherUid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Update(float frameTime)
|
public override void Update(float frameTime)
|
||||||
@@ -71,21 +91,21 @@ namespace Content.Server.Singularity.EntitySystems
|
|||||||
{
|
{
|
||||||
_gravityAccumulator -= GravityCooldown;
|
_gravityAccumulator -= GravityCooldown;
|
||||||
|
|
||||||
foreach (var singularity in EntityManager.EntityQuery<ServerSingularityComponent>())
|
foreach (var (singularity, xform) in EntityManager.EntityQuery<ServerSingularityComponent, TransformComponent>())
|
||||||
{
|
{
|
||||||
Update(singularity, GravityCooldown);
|
Update(singularity, xform, GravityCooldown);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Update(ServerSingularityComponent component, float frameTime)
|
private void Update(ServerSingularityComponent component, TransformComponent xform, float frameTime)
|
||||||
{
|
{
|
||||||
if (component.BeingDeletedByAnotherSingularity) return;
|
if (component.BeingDeletedByAnotherSingularity) return;
|
||||||
|
|
||||||
var worldPos = EntityManager.GetComponent<TransformComponent>(component.Owner).WorldPosition;
|
var worldPos = xform.WorldPosition;
|
||||||
DestroyEntities(component, worldPos);
|
DestroyEntities(component, xform, worldPos);
|
||||||
DestroyTiles(component, worldPos);
|
DestroyTiles(component, xform, worldPos);
|
||||||
PullEntities(component, worldPos);
|
PullEntities(component, xform, worldPos, frameTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
private float PullRange(ServerSingularityComponent component)
|
private float PullRange(ServerSingularityComponent component)
|
||||||
@@ -101,17 +121,18 @@ namespace Content.Server.Singularity.EntitySystems
|
|||||||
|
|
||||||
private bool CanDestroy(SharedSingularityComponent component, EntityUid entity)
|
private bool CanDestroy(SharedSingularityComponent component, EntityUid entity)
|
||||||
{
|
{
|
||||||
return entity == component.Owner ||
|
return entity != component.Owner &&
|
||||||
EntityManager.HasComponent<IMapGridComponent>(entity) ||
|
!EntityManager.HasComponent<IMapGridComponent>(entity) &&
|
||||||
EntityManager.HasComponent<GhostComponent>(entity) ||
|
!EntityManager.HasComponent<GhostComponent>(entity) &&
|
||||||
EntityManager.HasComponent<ContainmentFieldComponent>(entity) ||
|
(component.Level > 4 ||
|
||||||
EntityManager.HasComponent<ContainmentFieldGeneratorComponent>(entity);
|
!EntityManager.HasComponent<ContainmentFieldComponent>(entity) &&
|
||||||
|
!EntityManager.HasComponent<ContainmentFieldGeneratorComponent>(entity));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleDestroy(ServerSingularityComponent component, EntityUid entity)
|
private void HandleDestroy(ServerSingularityComponent component, EntityUid entity)
|
||||||
{
|
{
|
||||||
// TODO: Need singuloimmune tag
|
// TODO: Need singuloimmune tag
|
||||||
if (CanDestroy(component, entity)) return;
|
if (!CanDestroy(component, entity)) return;
|
||||||
|
|
||||||
// Singularity priority management / etc.
|
// Singularity priority management / etc.
|
||||||
if (EntityManager.TryGetComponent<ServerSingularityComponent?>(entity, out var otherSingulo))
|
if (EntityManager.TryGetComponent<ServerSingularityComponent?>(entity, out var otherSingulo))
|
||||||
@@ -125,23 +146,23 @@ namespace Content.Server.Singularity.EntitySystems
|
|||||||
otherSingulo.BeingDeletedByAnotherSingularity = true;
|
otherSingulo.BeingDeletedByAnotherSingularity = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
EntityManager.QueueDeleteEntity(entity);
|
|
||||||
|
|
||||||
if (EntityManager.TryGetComponent<SinguloFoodComponent?>(entity, out var singuloFood))
|
if (EntityManager.TryGetComponent<SinguloFoodComponent?>(entity, out var singuloFood))
|
||||||
component.Energy += singuloFood.Energy;
|
component.Energy += singuloFood.Energy;
|
||||||
else
|
else
|
||||||
component.Energy++;
|
component.Energy++;
|
||||||
|
|
||||||
|
EntityManager.QueueDeleteEntity(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handle deleting entities and increasing energy
|
/// Handle deleting entities and increasing energy
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void DestroyEntities(ServerSingularityComponent component, Vector2 worldPos)
|
private void DestroyEntities(ServerSingularityComponent component, TransformComponent xform, 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).
|
// 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);
|
var destroyRange = DestroyTileRange(component);
|
||||||
|
|
||||||
foreach (var entity in _lookup.GetEntitiesInRange(EntityManager.GetComponent<TransformComponent>(component.Owner).MapID, worldPos, destroyRange))
|
foreach (var entity in _lookup.GetEntitiesInRange(xform.MapID, worldPos, destroyRange))
|
||||||
{
|
{
|
||||||
HandleDestroy(component, entity);
|
HandleDestroy(component, entity);
|
||||||
}
|
}
|
||||||
@@ -152,17 +173,21 @@ namespace Content.Server.Singularity.EntitySystems
|
|||||||
return !(EntityManager.HasComponent<GhostComponent>(entity) ||
|
return !(EntityManager.HasComponent<GhostComponent>(entity) ||
|
||||||
EntityManager.HasComponent<IMapGridComponent>(entity) ||
|
EntityManager.HasComponent<IMapGridComponent>(entity) ||
|
||||||
EntityManager.HasComponent<MapComponent>(entity) ||
|
EntityManager.HasComponent<MapComponent>(entity) ||
|
||||||
entity.IsInContainer());
|
EntityManager.HasComponent<ServerSingularityComponent>(entity) ||
|
||||||
|
_container.IsEntityInContainer(entity));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PullEntities(ServerSingularityComponent component, Vector2 worldPos)
|
/// <summary>
|
||||||
|
/// Pull dynamic bodies in range to the singulo.
|
||||||
|
/// </summary>
|
||||||
|
private void PullEntities(ServerSingularityComponent component, TransformComponent xform, Vector2 worldPos, float frameTime)
|
||||||
{
|
{
|
||||||
// TODO: When we split up dynamic and static trees we might be able to make items always on the broadphase
|
// 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
|
// in which case we can just query dynamictree directly for brrt
|
||||||
var pullRange = PullRange(component);
|
var pullRange = PullRange(component);
|
||||||
var destroyRange = DestroyTileRange(component);
|
var destroyRange = DestroyTileRange(component);
|
||||||
|
|
||||||
foreach (var entity in _lookup.GetEntitiesInRange(EntityManager.GetComponent<TransformComponent>(component.Owner).MapID, worldPos, pullRange))
|
foreach (var entity in _lookup.GetEntitiesInRange(xform.MapID, worldPos, pullRange))
|
||||||
{
|
{
|
||||||
// I tried having it so level 6 can de-anchor. BAD IDEA, MASSIVE LAG.
|
// I tried having it so level 6 can de-anchor. BAD IDEA, MASSIVE LAG.
|
||||||
if (entity == component.Owner ||
|
if (entity == component.Owner ||
|
||||||
@@ -175,31 +200,55 @@ namespace Content.Server.Singularity.EntitySystems
|
|||||||
|
|
||||||
if (vec.Length < destroyRange - 0.01f) continue;
|
if (vec.Length < destroyRange - 0.01f) continue;
|
||||||
|
|
||||||
var speed = vec.Length * component.Level * collidableComponent.Mass;
|
var speed = vec.Length * component.Level * collidableComponent.Mass * 100f;
|
||||||
|
|
||||||
// Because tile friction is so high we'll just multiply by mass so stuff like closets can even move.
|
// Because tile friction is so high we'll just multiply by mass so stuff like closets can even move.
|
||||||
collidableComponent.ApplyLinearImpulse(vec.Normalized * speed);
|
collidableComponent.ApplyLinearImpulse(vec.Normalized * speed * frameTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Destroy any grid tiles within the relevant Level range.
|
/// Destroy any grid tiles within the relevant Level range.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void DestroyTiles(ServerSingularityComponent component, Vector2 worldPos)
|
private void DestroyTiles(ServerSingularityComponent component, TransformComponent xform, Vector2 worldPos)
|
||||||
{
|
{
|
||||||
var radius = DestroyTileRange(component);
|
var radius = DestroyTileRange(component);
|
||||||
|
|
||||||
var circle = new Circle(worldPos, radius);
|
var circle = new Circle(worldPos, radius);
|
||||||
var box = new Box2(worldPos - radius, worldPos + radius);
|
var box = new Box2(worldPos - radius, worldPos + radius);
|
||||||
|
|
||||||
foreach (var grid in _mapManager.FindGridsIntersecting(EntityManager.GetComponent<TransformComponent>(component.Owner).MapID, box))
|
foreach (var grid in _mapManager.FindGridsIntersecting(xform.MapID, box))
|
||||||
{
|
{
|
||||||
|
// Bundle these together so we can use the faster helper to set tiles.
|
||||||
|
var toDestroy = new List<(Vector2i, Tile)>();
|
||||||
|
|
||||||
foreach (var tile in grid.GetTilesIntersecting(circle))
|
foreach (var tile in grid.GetTilesIntersecting(circle))
|
||||||
{
|
{
|
||||||
if (tile.Tile.IsEmpty) continue;
|
if (tile.Tile.IsEmpty) continue;
|
||||||
grid.SetTile(tile.GridIndices, Tile.Empty);
|
|
||||||
component.Energy += TileEnergyGain;
|
// Avoid ripping up tiles that may be essential to containment
|
||||||
|
if (component.Level < 5)
|
||||||
|
{
|
||||||
|
var canDelete = true;
|
||||||
|
|
||||||
|
foreach (var ent in grid.GetAnchoredEntities(tile.GridIndices))
|
||||||
|
{
|
||||||
|
if (EntityManager.HasComponent<ContainmentFieldComponent>(ent) ||
|
||||||
|
EntityManager.HasComponent<ContainmentFieldGeneratorComponent>(ent))
|
||||||
|
{
|
||||||
|
canDelete = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!canDelete) continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
toDestroy.Add((tile.GridIndices, Tile.Empty));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
component.Energy += TileEnergyGain * toDestroy.Count;
|
||||||
|
grid.SetTiles(toDestroy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using Content.Shared.Ghost;
|
||||||
using Content.Shared.Radiation;
|
using Content.Shared.Radiation;
|
||||||
using Content.Shared.Singularity.Components;
|
using Content.Shared.Singularity.Components;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
@@ -46,6 +47,46 @@ namespace Content.Shared.Singularity
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
SubscribeLocalEvent<SharedSingularityComponent, PreventCollideEvent>(OnPreventCollide);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void OnPreventCollide(EntityUid uid, SharedSingularityComponent component, PreventCollideEvent args)
|
||||||
|
{
|
||||||
|
PreventCollide(uid, component, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual bool PreventCollide(EntityUid uid, SharedSingularityComponent component,
|
||||||
|
PreventCollideEvent args)
|
||||||
|
{
|
||||||
|
var otherUid = args.BodyB.Owner;
|
||||||
|
|
||||||
|
// For prediction reasons always want the client to ignore these.
|
||||||
|
if (EntityManager.HasComponent<IMapGridComponent>(otherUid) ||
|
||||||
|
EntityManager.HasComponent<SharedGhostComponent>(otherUid))
|
||||||
|
{
|
||||||
|
args.Cancel();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're above 4 then breach containment
|
||||||
|
// otherwise, check if it's containment and just keep the collision
|
||||||
|
if (EntityManager.HasComponent<SharedContainmentFieldComponent>(otherUid) ||
|
||||||
|
EntityManager.HasComponent<SharedContainmentFieldGeneratorComponent>(otherUid))
|
||||||
|
{
|
||||||
|
if (component.Level > 4)
|
||||||
|
{
|
||||||
|
args.Cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public void ChangeSingularityLevel(SharedSingularityComponent singularity, int value)
|
public void ChangeSingularityLevel(SharedSingularityComponent singularity, int value)
|
||||||
{
|
{
|
||||||
if (value == singularity.Level)
|
if (value == singularity.Level)
|
||||||
@@ -91,24 +132,5 @@ 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 ((!EntityManager.HasComponent<SharedContainmentFieldComponent>(other) &&
|
|
||||||
!EntityManager.HasComponent<SharedContainmentFieldGeneratorComponent>(other)) ||
|
|
||||||
component.Level >= 4)
|
|
||||||
{
|
|
||||||
args.Cancel();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user