Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Temporarily make singularity a bit harder to loose as non-antag #33358

Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,69 @@
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;

using Content.Server.Singularity.EntitySystems;
using Content.Shared.Physics;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;

namespace Content.Server.Singularity.Components;

[RegisterComponent]
[RegisterComponent, AutoGenerateComponentPause]
[Access(typeof(SingularityGeneratorSystem))]
public sealed partial class SingularityGeneratorComponent : Component
{
/// <summary>
/// The amount of power this generator has accumulated.
/// If you want to set this use <see cref="SingularityGeneratorSystem.SetPower"/>
/// </summary>
[DataField("power")]
[Access(friends:typeof(SingularityGeneratorSystem))]
[DataField]
public float Power = 0;

/// <summary>
/// The power threshold at which this generator will spawn a singularity.
/// If you want to set this use <see cref="SingularityGeneratorSystem.SetThreshold"/>
/// </summary>
[DataField("threshold")]
[Access(friends:typeof(SingularityGeneratorSystem))]
[DataField]
public float Threshold = 16;

/// <summary>
/// Allows the generator to ignore all the failsafe stuff, e.g. when emagged
/// </summary>
[DataField]
public bool FailsafeDisabled = false;

/// <summary>
/// Maximum distance at which the generator will check for a field at
/// </summary>
[DataField]
public float FailsafeDistance = 16;

/// <summary>
/// The prototype ID used to spawn a singularity.
/// </summary>
[DataField("spawnId", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
[ViewVariables(VVAccess.ReadWrite)]
public string? SpawnPrototype = "Singularity";

/// <summary>
/// The masks the raycast should not go through
/// </summary>
[DataField]
public int CollisionMask = (int)CollisionGroup.FullTileMask;

/// <summary>
/// Message to use when there's no containment field on cardinal directions
/// </summary>
[DataField]
public LocId ContainmentFailsafeMessage = "comp-generator-failsafe";

/// <summary>
/// For how long the failsafe will cause the generator to stop working and not issue a failsafe warning
/// </summary>
[DataField]
public TimeSpan FailsafeCooldown = TimeSpan.FromSeconds(30);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lower this to something like 10 seconds. It can feel kinda weird if you miss the text and then it takes half a minute to get any idea why it failed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! Set it to 10 seconds exactly


/// <summary>
/// How long until the generator can issue a failsafe warning again
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
[AutoPausedField]
public TimeSpan NextFailsafe = TimeSpan.Zero;
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,35 @@
using System.Diagnostics;
using Content.Server.ParticleAccelerator.Components;
using Content.Server.Popups;
using Content.Server.Singularity.Components;
using Content.Shared.Emag.Systems;
using Content.Shared.Popups;
using Content.Shared.Singularity.Components;
using Robust.Server.GameObjects;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Events;
using Robust.Shared.Timing;

namespace Content.Server.Singularity.EntitySystems;

public sealed class SingularityGeneratorSystem : EntitySystem
{
#region Dependencies
[Dependency] private readonly IViewVariablesManager _vvm = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
[Dependency] private readonly PhysicsSystem _physics = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly MetaDataSystem _metadata = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
#endregion Dependencies

public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<ParticleProjectileComponent, StartCollideEvent>(HandleParticleCollide);
SubscribeLocalEvent<SingularityGeneratorComponent, GotEmaggedEvent>(OnEmagged);

var vvHandle = _vvm.GetTypeHandler<SingularityGeneratorComponent>();
vvHandle.AddPath(nameof(SingularityGeneratorComponent.Power), (_, comp) => comp.Power, SetPower);
Expand Down Expand Up @@ -100,11 +114,33 @@ public void SetThreshold(EntityUid uid, float value, SingularityGeneratorCompone
/// <param name="args">The state of the beginning of the collision.</param>
private void HandleParticleCollide(EntityUid uid, ParticleProjectileComponent component, ref StartCollideEvent args)
{
if (EntityManager.TryGetComponent<SingularityGeneratorComponent>(args.OtherEntity, out var singularityGeneratorComponent))
if (!EntityManager.TryGetComponent<SingularityGeneratorComponent>(args.OtherEntity, out var generatorComp))
return;

if (_timing.CurTime < _metadata.GetPauseTime(uid) + generatorComp.NextFailsafe)
{
EntityManager.QueueDeleteEntity(uid);
return;
}

var contained = true;
var transform = Transform(args.OtherEntity);
var directions = Enum.GetValues<Direction>().Length;
for (var i = 0; i < directions - 1; i += 2) // Skip every other direction, checking only cardinals
{
if (!CheckContainmentField((Direction)i, new Entity<SingularityGeneratorComponent>(args.OtherEntity, generatorComp), transform))
contained = false;
}

if (!contained)
{
generatorComp.NextFailsafe = _timing.CurTime + generatorComp.FailsafeCooldown;
_popupSystem.PopupEntity(Loc.GetString("comp-generator-failsafe", ("target", args.OtherEntity)), args.OtherEntity, PopupType.LargeCaution);
}
else
SetPower(
args.OtherEntity,
singularityGeneratorComponent.Power + component.State switch
generatorComp.Power + component.State switch
{
ParticleAcceleratorPowerState.Standby => 0,
ParticleAcceleratorPowerState.Level0 => 1,
Expand All @@ -113,10 +149,51 @@ private void HandleParticleCollide(EntityUid uid, ParticleProjectileComponent co
ParticleAcceleratorPowerState.Level3 => 8,
_ => 0
},
singularityGeneratorComponent
generatorComp
);
EntityManager.QueueDeleteEntity(uid);
}
EntityManager.QueueDeleteEntity(uid);
}

private void OnEmagged(EntityUid uid, SingularityGeneratorComponent component, ref GotEmaggedEvent args)
{
_popupSystem.PopupEntity(Loc.GetString("comp-generator-failsafe-disabled", ("target", uid)), uid);
component.FailsafeDisabled = true;
args.Handled = true;
}
metalgearsloth marked this conversation as resolved.
Show resolved Hide resolved
#endregion Event Handlers

/// <summary>
/// Checks whether there's a containment field in a given direction away from the generator
/// </summary>
/// <param name="transform">The transform component of the singularity generator.</param>
/// <remarks>Mostly copied from <see cref="ContainmentFieldGeneratorSystem"/> </remarks>
private bool CheckContainmentField(Direction dir, Entity<SingularityGeneratorComponent> generator, TransformComponent transform)
{
var component = generator.Comp;

var (worldPosition, worldRotation) = _transformSystem.GetWorldPositionRotation(transform);
var dirRad = dir.ToAngle() + worldRotation;

var ray = new CollisionRay(worldPosition, dirRad.ToVec(), component.CollisionMask);
var rayCastResults = _physics.IntersectRay(transform.MapID, ray, component.FailsafeDistance, generator, false);
var genQuery = GetEntityQuery<ContainmentFieldComponent>();

RayCastResults? closestResult = null;

foreach (var result in rayCastResults)
{
if (genQuery.HasComponent(result.HitEntity))
closestResult = result;

break;
}

if (closestResult == null)
return false;

var ent = closestResult.Value.HitEntity;

// Check that the field can't be moved. The fields' transform parenting is weird, so skip that
return TryComp<PhysicsComponent>(ent, out var collidableComponent) && collidableComponent.BodyType == BodyType.Static;
}
metalgearsloth marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
comp-generator-failsafe = The {$target} shakes as the containment failsafe triggers!
comp-generator-failsafe-disabled = Something fizzles out inside of {$target}...
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
- type: entity
id: SingularityGenerator
name: gravitational singularity generator
description: An Odd Device which produces a Gravitational Singularity when set up.
description: An Odd Device which produces a Gravitational Singularity when set up. Comes with a temporary shutdown containment failsafe.
placement:
mode: SnapgridCenter
components:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
id: TeslaGenerator
name: tesla generator
parent: BaseStructureDynamic
description: An Odd Device which produces a powerful Tesla ball when set up.
description: An Odd Device which produces a powerful Tesla ball when set up. Comes with a temporary shutdown containment failsafe.
components:
- type: Sprite
noRot: true
sprite: Structures/Power/Generation/Tesla/generator.rsi
state: icon
state: icon
- type: SingularityGenerator # TODO: rename the generator
spawnId: TeslaEnergyBall
- type: InteractionOutline
Expand Down
Loading