Re-organize all projects (#4166)

This commit is contained in:
DrSmugleaf
2021-06-09 22:19:39 +02:00
committed by GitHub
parent 9f50e4061b
commit ff1a2d97ea
1773 changed files with 5258 additions and 5508 deletions

View File

@@ -0,0 +1,25 @@
#nullable enable
using Robust.Shared.GameObjects;
using Robust.Shared.Physics.Collision;
using Robust.Shared.Physics.Dynamics;
namespace Content.Server.Singularity.Components
{
[RegisterComponent]
public class ContainmentFieldComponent : Component, IStartCollide
{
public override string Name => "ContainmentField";
public ContainmentFieldConnection? Parent;
void IStartCollide.CollideWith(Fixture ourFixture, Fixture otherFixture, in Manifold manifold)
{
if (Parent == null)
{
Owner.QueueDelete();
return;
}
Parent.TryRepell(Owner, otherFixture.Body.Owner);
}
}
}

View File

@@ -0,0 +1,146 @@
using System;
using System.Collections.Generic;
using System.Threading;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Timer = Robust.Shared.Timing.Timer;
namespace Content.Server.Singularity.Components
{
public class ContainmentFieldConnection : IDisposable
{
public readonly ContainmentFieldGeneratorComponent Generator1;
public readonly ContainmentFieldGeneratorComponent Generator2;
private readonly List<IEntity> _fields = new();
private int _sharedEnergyPool;
private readonly CancellationTokenSource _powerDecreaseCancellationTokenSource = new();
public int SharedEnergyPool
{
get => _sharedEnergyPool;
set
{
_sharedEnergyPool = Math.Clamp(value, 0, 25);
if (_sharedEnergyPool == 0)
{
Dispose();
}
}
}
public ContainmentFieldConnection(ContainmentFieldGeneratorComponent generator1, ContainmentFieldGeneratorComponent generator2)
{
Generator1 = generator1;
Generator2 = generator2;
//generateFields
var pos1 = generator1.Owner.Transform.Coordinates;
var pos2 = generator2.Owner.Transform.Coordinates;
if (pos1 == pos2)
{
Dispose();
return;
}
var entityManager = IoCManager.Resolve<IEntityManager>();
var delta = (pos2 - pos1).Position;
var dirVec = delta.Normalized;
var stopDist = delta.Length;
var currentOffset = dirVec;
while (currentOffset.Length < stopDist)
{
var currentCoords = pos1.Offset(currentOffset);
var newEnt = entityManager.SpawnEntity("ContainmentField", currentCoords);
if (!newEnt.TryGetComponent<ContainmentFieldComponent>(out var containmentFieldComponent))
{
Logger.Error("While creating Fields in ContainmentFieldConnection, a ContainmentField without a ContainmentFieldComponent was created. Deleting newly spawned ContainmentField...");
newEnt.Delete();
continue;
}
containmentFieldComponent.Parent = this;
newEnt.Transform.WorldRotation = dirVec.ToWorldAngle();
_fields.Add(newEnt);
currentOffset += dirVec;
}
Timer.SpawnRepeating(1000, () => { SharedEnergyPool--;}, _powerDecreaseCancellationTokenSource.Token);
}
public bool CanRepell(IEntity toRepell)
{
var powerNeeded = 1;
if (toRepell.TryGetComponent<ServerSingularityComponent>(out var singularityComponent))
{
powerNeeded += 2*singularityComponent.Level;
}
return _sharedEnergyPool > powerNeeded;
}
/// <summary>
/// Tries to repell a Entity. This deletes the connection if the repelling fails!
/// </summary>
/// <param name="repellFrom">Entity to repell from. Should be a field, otherwise return will be false.</param>
/// <param name="toRepell">Entity to repell.</param>
public void TryRepell(IEntity repellFrom, IEntity toRepell)
{
// TODO: Fix this also it's fucking repel
if (!_fields.Contains(repellFrom) || !toRepell.TryGetComponent<IPhysBody>(out var collidableComponent)) return;
return;
var speed = 5;
//var containmentFieldRepellController = collidableComponent.EnsureController<ContainmentFieldRepellController>();
if (!CanRepell(toRepell))
{
Dispose();
return;
}
if (Math.Abs(repellFrom.Transform.WorldRotation.Degrees + 90f) < 0.1f ||
Math.Abs(repellFrom.Transform.WorldRotation.Degrees - 90f) < 0.1f)
{
if (repellFrom.Transform.WorldPosition.X.CompareTo(toRepell.Transform.WorldPosition.X) > 0)
{
//containmentFieldRepellController.Repell(Direction.West, speed);
}
else
{
//containmentFieldRepellController.Repell(Direction.East, speed);
}
}
else
{
if (repellFrom.Transform.WorldPosition.Y.CompareTo(toRepell.Transform.WorldPosition.Y) > 0)
{
//containmentFieldRepellController.Repell(Direction.South, speed);
}
else
{
//containmentFieldRepellController.Repell(Direction.North, speed);
}
}
return;
}
public void Dispose()
{
_powerDecreaseCancellationTokenSource.Cancel();
foreach (var field in _fields)
{
field.Delete();
}
_fields.Clear();
Generator1.RemoveConnection(this);
Generator2.RemoveConnection(this);
}
}
}

View File

@@ -0,0 +1,192 @@
#nullable enable
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Shared.Physics;
using Content.Shared.Tag;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Broadphase;
using Robust.Shared.Physics.Collision;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.ViewVariables;
namespace Content.Server.Singularity.Components
{
[RegisterComponent]
public class ContainmentFieldGeneratorComponent : Component, IStartCollide
{
public override string Name => "ContainmentFieldGenerator";
private int _powerBuffer;
[ViewVariables]
public int PowerBuffer
{
get => _powerBuffer;
set => _powerBuffer = Math.Clamp(value, 0, 6);
}
public void ReceivePower(int power)
{
var totalPower = power + PowerBuffer;
var powerPerConnection = totalPower / 2;
var newBuffer = totalPower % 2;
TryPowerConnection(ref _connection1, ref newBuffer, powerPerConnection);
TryPowerConnection(ref _connection2, ref newBuffer, powerPerConnection);
PowerBuffer = newBuffer;
}
private void TryPowerConnection(ref Tuple<Direction, ContainmentFieldConnection>? connectionProperty, ref int powerBuffer, int powerPerConnection)
{
if (connectionProperty != null)
{
connectionProperty.Item2.SharedEnergyPool += powerPerConnection;
}
else
{
if (TryGenerateFieldConnection(ref connectionProperty))
{
connectionProperty.Item2.SharedEnergyPool += powerPerConnection;
}
else
{
powerBuffer += powerPerConnection;
}
}
}
[ComponentDependency] private readonly PhysicsComponent? _collidableComponent = default;
[ComponentDependency] private readonly PointLightComponent? _pointLightComponent = default;
private Tuple<Direction, ContainmentFieldConnection>? _connection1;
private Tuple<Direction, ContainmentFieldConnection>? _connection2;
public bool CanRepell(IEntity toRepell) => _connection1?.Item2?.CanRepell(toRepell) == true ||
_connection2?.Item2?.CanRepell(toRepell) == true;
public void OnAnchoredChanged()
{
if(_collidableComponent?.BodyType != BodyType.Static)
{
_connection1?.Item2.Dispose();
_connection2?.Item2.Dispose();
}
}
private bool IsConnectedWith(ContainmentFieldGeneratorComponent comp)
{
return comp == this || _connection1?.Item2.Generator1 == comp || _connection1?.Item2.Generator2 == comp ||
_connection2?.Item2.Generator1 == comp || _connection2?.Item2.Generator2 == comp;
}
public bool HasFreeConnections()
{
return _connection1 == null || _connection2 == null;
}
private bool TryGenerateFieldConnection([NotNullWhen(true)] ref Tuple<Direction, ContainmentFieldConnection>? propertyFieldTuple)
{
if (propertyFieldTuple != null) return false;
if(_collidableComponent?.BodyType != BodyType.Static) return false;
foreach (var direction in new[] {Direction.North, Direction.East, Direction.South, Direction.West})
{
if (_connection1?.Item1 == direction || _connection2?.Item1 == direction) continue;
var dirVec = direction.ToVec();
var ray = new CollisionRay(Owner.Transform.WorldPosition, dirVec, (int) CollisionGroup.MobMask);
var rawRayCastResults = EntitySystem.Get<SharedBroadPhaseSystem>().IntersectRay(Owner.Transform.MapID, ray, 4.5f, Owner, false);
var rayCastResults = rawRayCastResults as RayCastResults[] ?? rawRayCastResults.ToArray();
if(!rayCastResults.Any()) continue;
RayCastResults? closestResult = null;
var smallestDist = 4.5f;
foreach (var res in rayCastResults)
{
if (res.Distance > smallestDist) continue;
smallestDist = res.Distance;
closestResult = res;
}
if(closestResult == null) continue;
var ent = closestResult.Value.HitEntity;
if (!ent.TryGetComponent<ContainmentFieldGeneratorComponent>(out var fieldGeneratorComponent) ||
fieldGeneratorComponent.Owner == Owner ||
!fieldGeneratorComponent.HasFreeConnections() ||
IsConnectedWith(fieldGeneratorComponent) ||
!ent.TryGetComponent<PhysicsComponent>(out var collidableComponent) ||
collidableComponent.BodyType != BodyType.Static)
{
continue;
}
var connection = new ContainmentFieldConnection(this, fieldGeneratorComponent);
propertyFieldTuple = new Tuple<Direction, ContainmentFieldConnection>(direction, connection);
if (fieldGeneratorComponent._connection1 == null)
{
fieldGeneratorComponent._connection1 = new Tuple<Direction, ContainmentFieldConnection>(direction.GetOpposite(), connection);
}
else if (fieldGeneratorComponent._connection2 == null)
{
fieldGeneratorComponent._connection2 = new Tuple<Direction, ContainmentFieldConnection>(direction.GetOpposite(), connection);
}
else
{
Logger.Error("When trying to connect two Containmentfieldgenerators, the second one already had two connection but the check didn't catch it");
}
UpdateConnectionLights();
return true;
}
return false;
}
public void RemoveConnection(ContainmentFieldConnection? connection)
{
if (_connection1?.Item2 == connection)
{
_connection1 = null;
UpdateConnectionLights();
}
else if (_connection2?.Item2 == connection)
{
_connection2 = null;
UpdateConnectionLights();
}
else if(connection != null)
{
Logger.Error("RemoveConnection called on Containmentfieldgenerator with a connection that can't be found in its connections.");
}
}
void IStartCollide.CollideWith(Fixture ourFixture, Fixture otherFixture, in Manifold manifold)
{
if (otherFixture.Body.Owner.HasTag("EmitterBolt")) {
ReceivePower(6);
}
}
public void UpdateConnectionLights()
{
if (_pointLightComponent != null)
{
bool hasAnyConnection = (_connection1 != null) || (_connection2 != null);
_pointLightComponent.Enabled = hasAnyConnection;
}
}
public override void OnRemove()
{
_connection1?.Item2.Dispose();
_connection2?.Item2.Dispose();
base.OnRemove();
}
}
}

View File

@@ -0,0 +1,284 @@
#nullable enable
using System;
using System.Threading;
using System.Threading.Tasks;
using Content.Server.Access.Components;
using Content.Server.Power.Components;
using Content.Server.Projectiles.Components;
using Content.Shared.Audio;
using Content.Shared.Interaction;
using Content.Shared.Notification;
using Content.Shared.Singularity.Components;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Physics;
using Robust.Shared.Player;
using Robust.Shared.Random;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using Timer = Robust.Shared.Timing.Timer;
#nullable enable
namespace Content.Server.Singularity.Components
{
[RegisterComponent]
[ComponentReference(typeof(IActivate))]
public class EmitterComponent : Component, IActivate, IInteractUsing
{
[Dependency] private readonly IRobustRandom _robustRandom = default!;
[ComponentDependency] private readonly AppearanceComponent? _appearance = default;
[ComponentDependency] private readonly AccessReader? _accessReader = default;
public override string Name => "Emitter";
private CancellationTokenSource? _timerCancel;
private PowerConsumerComponent _powerConsumer = default!;
// whether the power switch is in "on"
[ViewVariables] private bool _isOn;
// Whether the power switch is on AND the machine has enough power (so is actively firing)
[ViewVariables] private bool _isPowered;
[ViewVariables] private bool _isLocked;
// For the "emitter fired" sound
private const float Variation = 0.25f;
private const float Volume = 0.5f;
private const float Distance = 3f;
[ViewVariables(VVAccess.ReadWrite)] private int _fireShotCounter;
[ViewVariables(VVAccess.ReadWrite)] [DataField("fireSound")] private string _fireSound = "/Audio/Weapons/emitter.ogg";
[ViewVariables(VVAccess.ReadWrite)] [DataField("boltType")] private string _boltType = "EmitterBolt";
[ViewVariables(VVAccess.ReadWrite)] [DataField("powerUseActive")] private int _powerUseActive = 500;
[ViewVariables(VVAccess.ReadWrite)] [DataField("fireBurstSize")] private int _fireBurstSize = 3;
[ViewVariables(VVAccess.ReadWrite)] [DataField("fireInterval")] private TimeSpan _fireInterval = TimeSpan.FromSeconds(2);
[ViewVariables(VVAccess.ReadWrite)] [DataField("fireBurstDelayMin")] private TimeSpan _fireBurstDelayMin = TimeSpan.FromSeconds(2);
[ViewVariables(VVAccess.ReadWrite)] [DataField("fireBurstDelayMax")] private TimeSpan _fireBurstDelayMax = TimeSpan.FromSeconds(10);
public override void Initialize()
{
base.Initialize();
Owner.EnsureComponent<PowerConsumerComponent>(out _powerConsumer);
_powerConsumer.OnReceivedPowerChanged += OnReceivedPowerChanged;
}
private void OnReceivedPowerChanged(object? sender, ReceivedPowerChangedEventArgs e)
{
if (!_isOn)
{
return;
}
if (e.ReceivedPower < e.DrawRate)
{
PowerOff();
}
else
{
PowerOn();
}
}
void IActivate.Activate(ActivateEventArgs eventArgs)
{
if (_isLocked)
{
Owner.PopupMessage(eventArgs.User, Loc.GetString("comp-emitter-access-locked", ("target", Owner)));
return;
}
if (Owner.TryGetComponent(out PhysicsComponent? phys) && phys.BodyType == BodyType.Static)
{
if (!_isOn)
{
SwitchOn();
Owner.PopupMessage(eventArgs.User, Loc.GetString("comp-emitter-turned-on", ("target", Owner)));
}
else
{
SwitchOff();
Owner.PopupMessage(eventArgs.User, Loc.GetString("comp-emitter-turned-off", ("target", Owner)));
}
}
else
{
Owner.PopupMessage(eventArgs.User, Loc.GetString("comp-emitter-not-anchored", ("target", Owner)));
}
}
Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
{
if (_accessReader == null || !eventArgs.Using.TryGetComponent(out IAccess? access))
{
return Task.FromResult(false);
}
if (_accessReader.IsAllowed(access))
{
_isLocked ^= true;
if (_isLocked)
{
Owner.PopupMessage(eventArgs.User, Loc.GetString("comp-emitter-lock", ("target", Owner)));
}
else
{
Owner.PopupMessage(eventArgs.User, Loc.GetString("comp-emitter-unlock", ("target", Owner)));
}
UpdateAppearance();
}
else
{
Owner.PopupMessage(eventArgs.User, Loc.GetString("comp-emitter-access-denied"));
}
return Task.FromResult(true);
}
public void SwitchOff()
{
_isOn = false;
_powerConsumer.DrawRate = 0;
PowerOff();
UpdateAppearance();
}
public void SwitchOn()
{
_isOn = true;
_powerConsumer.DrawRate = _powerUseActive;
// Do not directly PowerOn().
// OnReceivedPowerChanged will get fired due to DrawRate change which will turn it on.
UpdateAppearance();
}
private void PowerOff()
{
if (!_isPowered)
{
return;
}
_isPowered = false;
// Must be set while emitter powered.
DebugTools.AssertNotNull(_timerCancel);
_timerCancel!.Cancel();
UpdateAppearance();
}
private void PowerOn()
{
if (_isPowered)
{
return;
}
_isPowered = true;
_fireShotCounter = 0;
_timerCancel = new CancellationTokenSource();
Timer.Spawn(_fireBurstDelayMax, ShotTimerCallback, _timerCancel.Token);
UpdateAppearance();
}
private void ShotTimerCallback()
{
// Any power-off condition should result in the timer for this method being cancelled
// and thus not firing
DebugTools.Assert(_isPowered);
DebugTools.Assert(_isOn);
DebugTools.Assert(_powerConsumer.DrawRate <= _powerConsumer.ReceivedPower);
Fire();
TimeSpan delay;
if (_fireShotCounter < _fireBurstSize)
{
_fireShotCounter += 1;
delay = _fireInterval;
}
else
{
_fireShotCounter = 0;
var diff = _fireBurstDelayMax - _fireBurstDelayMin;
// TIL you can do TimeSpan * double.
delay = _fireBurstDelayMin + _robustRandom.NextFloat() * diff;
}
// Must be set while emitter powered.
DebugTools.AssertNotNull(_timerCancel);
Timer.Spawn(delay, ShotTimerCallback, _timerCancel!.Token);
}
private void Fire()
{
var projectile = Owner.EntityManager.SpawnEntity(_boltType, Owner.Transform.Coordinates);
if (!projectile.TryGetComponent<PhysicsComponent>(out var physicsComponent))
{
Logger.Error("Emitter tried firing a bolt, but it was spawned without a PhysicsComponent");
return;
}
physicsComponent.BodyStatus = BodyStatus.InAir;
if (!projectile.TryGetComponent<ProjectileComponent>(out var projectileComponent))
{
Logger.Error("Emitter tried firing a bolt, but it was spawned without a ProjectileComponent");
return;
}
projectileComponent.IgnoreEntity(Owner);
physicsComponent
.LinearVelocity = Owner.Transform.WorldRotation.ToWorldVec() * 20f;
projectile.Transform.WorldRotation = Owner.Transform.WorldRotation;
// TODO: Move to projectile's code.
Timer.Spawn(3000, () => projectile.Delete());
SoundSystem.Play(Filter.Pvs(Owner), _fireSound, Owner,
AudioHelpers.WithVariation(Variation).WithVolume(Volume).WithMaxDistance(Distance));
}
private void UpdateAppearance()
{
if (_appearance == null)
{
return;
}
EmitterVisualState state;
if (_isPowered)
{
state = EmitterVisualState.On;
}
else if (_isOn)
{
state = EmitterVisualState.Underpowered;
}
else
{
state = EmitterVisualState.Off;
}
_appearance.SetData(EmitterVisuals.VisualState, state);
_appearance.SetData(EmitterVisuals.Locked, _isLocked);
}
}
}

View File

@@ -0,0 +1,93 @@
#nullable enable
using System;
using Content.Server.Battery.Components;
using Content.Server.Power.Components;
using Content.Shared.Interaction;
using Content.Shared.Notification;
using Content.Shared.Radiation;
using Content.Shared.Singularity.Components;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Timing;
using Robust.Shared.ViewVariables;
namespace Content.Server.Singularity.Components
{
[RegisterComponent]
public class RadiationCollectorComponent : Component, IInteractHand, IRadiationAct
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
public override string Name => "RadiationCollector";
private bool _enabled;
private TimeSpan _coolDownEnd;
[ViewVariables(VVAccess.ReadWrite)]
public bool Collecting {
get => _enabled;
set
{
if (_enabled == value) return;
_enabled = value;
SetAppearance(_enabled ? RadiationCollectorVisualState.Activating : RadiationCollectorVisualState.Deactivating);
}
}
[ComponentDependency] private readonly BatteryComponent? _batteryComponent = default!;
[ComponentDependency] private readonly BatteryDischargerComponent? _batteryDischargerComponent = default!;
bool IInteractHand.InteractHand(InteractHandEventArgs eventArgs)
{
var curTime = _gameTiming.CurTime;
if(curTime < _coolDownEnd)
return true;
if (!_enabled)
{
Owner.PopupMessage(eventArgs.User, Loc.GetString("radiation-collector-component-use-on"));
Collecting = true;
}
else
{
Owner.PopupMessage(eventArgs.User, Loc.GetString("radiation-collector-component-use-off"));
Collecting = false;
}
_coolDownEnd = curTime + TimeSpan.FromSeconds(0.81f);
return true;
}
void IRadiationAct.RadiationAct(float frameTime, SharedRadiationPulseComponent radiation)
{
if (!_enabled) return;
// No idea if this is even vaguely accurate to the previous logic.
// The maths is copied from that logic even though it works differently.
// But the previous logic would also make the radiation collectors never ever stop providing energy.
// And since frameTime was used there, I'm assuming that this is what the intent was.
// This still won't stop things being potentially hilarously unbalanced though.
if (_batteryComponent != null)
{
_batteryComponent!.CurrentCharge += frameTime * radiation.RadsPerSecond * 3000f;
if (_batteryDischargerComponent != null)
{
// The battery discharger is controlled like this to ensure it won't drain the entire battery in a single tick.
// If that occurs then the battery discharger ends up shutting down.
_batteryDischargerComponent!.ActiveSupplyRate = (int) Math.Max(1, _batteryComponent!.CurrentCharge);
}
}
}
protected void SetAppearance(RadiationCollectorVisualState state)
{
if (Owner.TryGetComponent<AppearanceComponent>(out var appearance))
{
appearance.SetData(RadiationCollectorVisuals.VisualState, state);
}
}
}
}

View File

@@ -0,0 +1,185 @@
#nullable enable
using System.Linq;
using Content.Server.Radiation;
using Content.Shared.Singularity;
using Content.Shared.Singularity.Components;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Collision;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Player;
using Robust.Shared.Players;
using Robust.Shared.Timing;
using Robust.Shared.ViewVariables;
namespace Content.Server.Singularity.Components
{
[RegisterComponent]
public class ServerSingularityComponent : SharedSingularityComponent, IStartCollide
{
[ViewVariables(VVAccess.ReadWrite)]
public int Energy
{
get => _energy;
set
{
if (value == _energy) return;
_energy = value;
if (_energy <= 0)
{
Owner.Delete();
return;
}
Level = _energy switch
{
var n when n >= 1500 => 6,
var n when n >= 1000 => 5,
var n when n >= 600 => 4,
var n when n >= 300 => 3,
var n when n >= 200 => 2,
var n when n < 200 => 1,
_ => 1
};
}
}
private int _energy = 180;
[ViewVariables]
public int Level
{
get => _level;
set
{
if (value == _level) return;
if (value < 0) value = 0;
if (value > 6) value = 6;
if ((_level > 1) && (value <= 1))
{
// Prevents it getting stuck (see SingularityController.MoveSingulo)
if (_collidableComponent != null) _collidableComponent.LinearVelocity = Vector2.Zero;
}
_level = value;
if(_radiationPulseComponent != null) _radiationPulseComponent.RadsPerSecond = 10 * value;
if (Owner.TryGetComponent(out AppearanceComponent? appearance))
{
appearance.SetData(SingularityVisuals.Level, _level);
}
if (_collidableComponent != null && _collidableComponent.Fixtures.Any() && _collidableComponent.Fixtures[0].Shape is PhysShapeCircle circle)
{
circle.Radius = _level - 0.5f;
}
Dirty();
}
}
private int _level;
[ViewVariables]
public int EnergyDrain =>
Level switch
{
6 => 20,
5 => 15,
4 => 10,
3 => 5,
2 => 2,
1 => 1,
_ => 0
};
// This is an interesting little workaround.
// 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.
[ViewVariables(VVAccess.ReadWrite)]
public bool BeingDeletedByAnotherSingularity { get; set; } = false;
private PhysicsComponent _collidableComponent = default!;
private RadiationPulseComponent _radiationPulseComponent = default!;
private SpriteComponent _spriteComponent = default!;
private IPlayingAudioStream? _playingSound;
public override ComponentState GetComponentState(ICommonSession player)
{
return new SingularityComponentState(Level);
}
public override void Initialize()
{
base.Initialize();
Owner.EnsureComponent(out _radiationPulseComponent);
Owner.EnsureComponent(out _collidableComponent);
Owner.EnsureComponent(out _spriteComponent);
var audioParams = AudioParams.Default;
audioParams.Loop = true;
audioParams.MaxDistance = 20f;
audioParams.Volume = 5;
SoundSystem.Play(Filter.Pvs(Owner), "/Audio/Effects/singularity_form.ogg", Owner);
Timer.Spawn(5200,() => _playingSound = SoundSystem.Play(Filter.Pvs(Owner), "/Audio/Effects/singularity.ogg", Owner, audioParams));
Level = 1;
}
public void Update(int seconds)
{
Energy -= EnergyDrain * seconds;
}
void IStartCollide.CollideWith(Fixture ourFixture, Fixture otherFixture, in Manifold manifold)
{
// If we're being deleted by another singularity, this call is probably for that singularity.
// Even if not, just don't bother.
if (BeingDeletedByAnotherSingularity)
return;
var otherEntity = otherFixture.Body.Owner;
if (otherEntity.TryGetComponent<IMapGridComponent>(out var mapGridComponent))
{
foreach (var tile in mapGridComponent.Grid.GetTilesIntersecting(ourFixture.Body.GetWorldAABB()))
{
mapGridComponent.Grid.SetTile(tile.GridIndices, Robust.Shared.Map.Tile.Empty);
Energy++;
}
return;
}
if (otherEntity.HasComponent<ContainmentFieldComponent>() || (otherEntity.TryGetComponent<ContainmentFieldGeneratorComponent>(out var component) && component.CanRepell(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))
Energy += singuloFood.Energy;
else
Energy++;
}
public override void OnRemove()
{
_playingSound?.Stop();
SoundSystem.Play(Filter.Pvs(Owner), "/Audio/Effects/singularity_collapse.ogg", Owner.Transform.Coordinates);
base.OnRemove();
}
}
}

View File

@@ -0,0 +1,31 @@
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.ViewVariables;
namespace Content.Server.Singularity.Components
{
[RegisterComponent]
public class SingularityGeneratorComponent : Component
{
public override string Name => "SingularityGenerator";
[ViewVariables] private int _power;
public int Power
{
get => _power;
set
{
if(_power == value) return;
_power = value;
if (_power > 15)
{
var entityManager = IoCManager.Resolve<IEntityManager>();
entityManager.SpawnEntity("Singularity", Owner.Transform.Coordinates);
//dont delete ourselves, just wait to get eaten
}
}
}
}
}

View File

@@ -0,0 +1,18 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.Singularity.Components
{
/// <summary>
/// Overrides exactly how much energy this object gives to a singularity.
/// </summary>
[RegisterComponent]
public class SinguloFoodComponent : Component
{
public override string Name => "SinguloFood";
[ViewVariables(VVAccess.ReadWrite)]
[DataField("energy")]
public int Energy { get; set; } = 1;
}
}

View File

@@ -0,0 +1,30 @@
using Content.Server.Singularity.Components;
using Robust.Shared.GameObjects;
namespace Content.Server.Singularity.EntitySystems
{
public sealed class ContainmentFieldGeneratorSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ContainmentFieldGeneratorComponent, PhysicsBodyTypeChangedEvent>(BodyTypeChanged);
}
public override void Shutdown()
{
base.Shutdown();
UnsubscribeLocalEvent<ContainmentFieldGeneratorComponent, PhysicsBodyTypeChangedEvent>();
}
private static void BodyTypeChanged(
EntityUid uid,
ContainmentFieldGeneratorComponent component,
PhysicsBodyTypeChangedEvent args)
{
component.OnAnchoredChanged();
}
}
}

View File

@@ -0,0 +1,30 @@
using Content.Server.Singularity.Components;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Content.Server.Singularity.EntitySystems
{
[UsedImplicitly]
public class SingularitySystem : EntitySystem
{
private float _updateInterval = 1.0f;
private float _accumulator;
public override void Update(float frameTime)
{
base.Update(frameTime);
_accumulator += frameTime;
while (_accumulator > _updateInterval)
{
_accumulator -= _updateInterval;
foreach (var singularity in ComponentManager.EntityQuery<ServerSingularityComponent>())
{
singularity.Update(1);
}
}
}
}
}

View File

@@ -0,0 +1,47 @@
#nullable enable
using Content.Server.Administration;
using Content.Server.ParticleAccelerator.Components;
using Content.Server.Singularity.Components;
using Content.Shared.Administration;
using Content.Shared.Singularity.Components;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Content.Server.Singularity
{
[AdminCommand(AdminFlags.Admin)]
public class StartSingularityEngineCommand : IConsoleCommand
{
public string Command => "startsingularityengine";
public string Description => "Automatically turns on the particle accelerator and containment field emitters.";
public string Help => $"{Command}";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 0)
{
shell.WriteLine($"Invalid amount of arguments: {args.Length}.\n{Help}");
return;
}
var entityManager = IoCManager.Resolve<IEntityManager>();
foreach (var ent in entityManager.GetEntities(new TypeEntityQuery(typeof(EmitterComponent))))
{
ent.GetComponent<EmitterComponent>().SwitchOn();
}
foreach (var ent in entityManager.GetEntities(new TypeEntityQuery(typeof(RadiationCollectorComponent))))
{
ent.GetComponent<RadiationCollectorComponent>().Collecting = true;
}
foreach (var ent in entityManager.GetEntities(new TypeEntityQuery(typeof(ParticleAcceleratorControlBoxComponent))))
{
var pacb = ent.GetComponent<ParticleAcceleratorControlBoxComponent>();
pacb.RescanParts();
pacb.SetStrength(ParticleAcceleratorPowerState.Level0);
pacb.SwitchOn();
}
shell.WriteLine("Done!");
}
}
}