Roundstart variation game rules (#24397)
* Raise `StationPostInitEvent` broadcast * Basic variation pass handling * standardize names + rule entities * why does it work like that? * add to defaults * light break variation pass * ent spawn entry * move some stationevent utility functions to gamerule + add one for finding random tile on specified station * forgot how statistics works * powered light variation pass is good now * station tile count function * public method to ensure all solutions (for procedural use before mapinit) * move gamerulesystem utility funcs to partial * ensure all solutions before spilling in puddlesystem. for use when spilling before mapinit * trash & puddle variation passes! * oh yeah * ehh lets live a little * std * utility for game rule check based on comp * entprotoid the trash spawner oops * generalize trash variation * use added instead of started for secret rule * random cleanup * generic replacement variation system * Wall rusting variation rule * account for modifying while enumerating * use localaabb * fix test * minor tweaks * reinforced wall replacer + puddletweaker
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
using Content.Server.GameTicking.Rules.VariationPass.Components;
|
||||
using Content.Shared.Storage;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules.VariationPass;
|
||||
|
||||
/// <inheritdoc cref="EntityReplaceVariationPassComponent"/>
|
||||
/// <summary>
|
||||
/// A base system for fast replacement of entities utilizing a query, rather than having to iterate every entity
|
||||
/// To use, you must have a marker component to use for <see cref="TEntComp"/>--each replaceable entity must have it
|
||||
/// Then you need an inheriting system as well as a unique game rule component for <see cref="TGameRuleComp"/>
|
||||
///
|
||||
/// This means a bit more boilerplate for each one, but significantly faster to actually execute.
|
||||
/// See <see cref="WallReplaceVariationPassSystem"/>
|
||||
/// </summary>
|
||||
public abstract class BaseEntityReplaceVariationPassSystem<TEntComp, TGameRuleComp> : VariationPassSystem<TGameRuleComp>
|
||||
where TEntComp: IComponent
|
||||
where TGameRuleComp: IComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// Used so we don't modify while enumerating
|
||||
/// if the replaced entity also has <see cref="TEntComp"/>.
|
||||
///
|
||||
/// Filled and cleared within the same tick so no persistence issues.
|
||||
/// </summary>
|
||||
private readonly Queue<(string, EntityCoordinates, Angle)> _queuedSpawns = new();
|
||||
|
||||
protected override void ApplyVariation(Entity<TGameRuleComp> ent, ref StationVariationPassEvent args)
|
||||
{
|
||||
if (!TryComp<EntityReplaceVariationPassComponent>(ent, out var pass))
|
||||
return;
|
||||
|
||||
var stopwatch = new Stopwatch();
|
||||
stopwatch.Start();
|
||||
|
||||
var replacementMod = Random.NextGaussian(pass.EntitiesPerReplacementAverage, pass.EntitiesPerReplacementStdDev);
|
||||
var prob = (float) Math.Clamp(1 / replacementMod, 0f, 1f);
|
||||
|
||||
if (prob == 0)
|
||||
return;
|
||||
|
||||
var enumerator = AllEntityQuery<TEntComp, TransformComponent>();
|
||||
while (enumerator.MoveNext(out var uid, out _, out var xform))
|
||||
{
|
||||
if (!IsMemberOfStation((uid, xform), ref args))
|
||||
continue;
|
||||
|
||||
if (RobustRandom.Prob(prob))
|
||||
QueueReplace((uid, xform), pass.Replacements);
|
||||
}
|
||||
|
||||
while (_queuedSpawns.TryDequeue(out var tup))
|
||||
{
|
||||
var (spawn, coords, rot) = tup;
|
||||
var newEnt = Spawn(spawn, coords);
|
||||
Transform(newEnt).LocalRotation = rot;
|
||||
}
|
||||
|
||||
Log.Debug($"Entity replacement took {stopwatch.Elapsed} with {Stations.GetTileCount(args.Station)} tiles");
|
||||
}
|
||||
|
||||
private void QueueReplace(Entity<TransformComponent> ent, List<EntitySpawnEntry> replacements)
|
||||
{
|
||||
var coords = ent.Comp.Coordinates;
|
||||
var rot = ent.Comp.LocalRotation;
|
||||
QueueDel(ent);
|
||||
|
||||
foreach (var spawn in EntitySpawnCollection.GetSpawns(replacements, RobustRandom))
|
||||
{
|
||||
_queuedSpawns.Enqueue((spawn, coords, rot));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using Content.Shared.Storage;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules.VariationPass.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for replacing a certain amount of entities with other entities in a variation pass.
|
||||
///
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// POTENTIALLY REPLACEABLE ENTITIES MUST BE MARKED WITH A REPLACEMENT MARKER
|
||||
/// AND HAVE A SYSTEM INHERITING FROM <see cref="BaseEntityReplaceVariationPassSystem{TEntComp,TGameRuleComp}"/>
|
||||
/// SEE <see cref="WallReplaceVariationPassSystem"/>
|
||||
/// </remarks>
|
||||
[RegisterComponent]
|
||||
public sealed partial class EntityReplaceVariationPassComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Number of matching entities before one will be replaced on average.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public float EntitiesPerReplacementAverage;
|
||||
|
||||
[DataField(required: true)]
|
||||
public float EntitiesPerReplacementStdDev;
|
||||
|
||||
/// <summary>
|
||||
/// Prototype(s) to replace matched entities with.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public List<EntitySpawnEntry> Replacements = default!;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using Content.Shared.Random;
|
||||
using Content.Shared.Storage;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules.VariationPass.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for spawning entities randomly dotted around the station in a variation pass.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class EntitySpawnVariationPassComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Number of tiles before we spawn one entity on average.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float TilesPerEntityAverage = 50f;
|
||||
|
||||
[DataField]
|
||||
public float TilesPerEntityStdDev = 7f;
|
||||
|
||||
/// <summary>
|
||||
/// Spawn entries for each chosen location.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public List<EntitySpawnEntry> Entities = default!;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using Content.Shared.Light.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules.VariationPass.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This handle randomly destroying lights, causing them to flicker endlessly, or replacing their tube/bulb with different variants.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class PoweredLightVariationPassComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Chance that a light will be replaced with a broken variant.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float LightBreakChance = 0.15f;
|
||||
|
||||
/// <summary>
|
||||
/// Chance that a light will be replaced with an aged variant.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float LightAgingChance = 0.05f;
|
||||
|
||||
[DataField]
|
||||
public float AgedLightTubeFlickerChance = 0.03f;
|
||||
|
||||
[DataField]
|
||||
public EntProtoId BrokenLightBulbPrototype = "LightBulbBroken";
|
||||
|
||||
[DataField]
|
||||
public EntProtoId BrokenLightTubePrototype = "LightTubeBroken";
|
||||
|
||||
[DataField]
|
||||
public EntProtoId AgedLightBulbPrototype = "LightBulbOld";
|
||||
|
||||
[DataField]
|
||||
public EntProtoId AgedLightTubePrototype = "LightTubeOld";
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using Content.Shared.Random;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules.VariationPass.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Handles spilling puddles with various reagents randomly around the station.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class PuddleMessVariationPassComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Tiles before one spill on average.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float TilesPerSpillAverage = 600f;
|
||||
|
||||
[DataField]
|
||||
public float TilesPerSpillStdDev = 50f;
|
||||
|
||||
/// <summary>
|
||||
/// Weighted random prototype to use for random messes.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public ProtoId<WeightedRandomFillSolutionPrototype> RandomPuddleSolutionFill = default!;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Content.Server.GameTicking.Rules.VariationPass.Components;
|
||||
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class ReinforcedWallReplaceVariationPassComponent : Component
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Content.Server.GameTicking.Rules.VariationPass.Components.ReplacementMarkers;
|
||||
|
||||
/// <summary>
|
||||
/// This component marks replaceable reinforced walls for use with fast queries in variation passes.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class ReinforcedWallReplacementMarkerComponent : Component
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Content.Server.GameTicking.Rules.VariationPass.Components.ReplacementMarkers;
|
||||
|
||||
/// <summary>
|
||||
/// This component marks replaceable walls for use with fast queries in variation passes.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class WallReplacementMarkerComponent : Component
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Content.Server.GameTicking.Rules.VariationPass.Components;
|
||||
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class WallReplaceVariationPassComponent : Component
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Content.Server.GameTicking.Rules.VariationPass.Components;
|
||||
using Content.Shared.Storage;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules.VariationPass;
|
||||
|
||||
/// <inheritdoc cref="EntitySpawnVariationPassComponent"/>
|
||||
public sealed class EntitySpawnVariationPassSystem : VariationPassSystem<EntitySpawnVariationPassComponent>
|
||||
{
|
||||
protected override void ApplyVariation(Entity<EntitySpawnVariationPassComponent> ent, ref StationVariationPassEvent args)
|
||||
{
|
||||
var totalTiles = Stations.GetTileCount(args.Station);
|
||||
|
||||
var dirtyMod = Random.NextGaussian(ent.Comp.TilesPerEntityAverage, ent.Comp.TilesPerEntityStdDev);
|
||||
var trashTiles = Math.Max((int) (totalTiles * (1 / dirtyMod)), 0);
|
||||
|
||||
for (var i = 0; i < trashTiles; i++)
|
||||
{
|
||||
if (!TryFindRandomTileOnStation(args.Station, out _, out _, out var coords))
|
||||
continue;
|
||||
|
||||
var ents = EntitySpawnCollection.GetSpawns(ent.Comp.Entities, Random);
|
||||
foreach (var spawn in ents)
|
||||
{
|
||||
SpawnAtPosition(spawn, coords);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using Content.Server.GameTicking.Rules.VariationPass.Components;
|
||||
using Content.Server.Light.Components;
|
||||
using Content.Server.Light.EntitySystems;
|
||||
using Content.Shared.Light.Components;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules.VariationPass;
|
||||
|
||||
/// <inheritdoc cref="PoweredLightVariationPassComponent"/>
|
||||
public sealed class PoweredLightVariationPassSystem : VariationPassSystem<PoweredLightVariationPassComponent>
|
||||
{
|
||||
[Dependency] private readonly PoweredLightSystem _poweredLight = default!;
|
||||
|
||||
protected override void ApplyVariation(Entity<PoweredLightVariationPassComponent> ent, ref StationVariationPassEvent args)
|
||||
{
|
||||
var query = AllEntityQuery<PoweredLightComponent, TransformComponent>();
|
||||
while (query.MoveNext(out var uid, out var comp, out var xform))
|
||||
{
|
||||
if (!IsMemberOfStation((uid, xform), ref args))
|
||||
continue;
|
||||
|
||||
if (Random.Prob(ent.Comp.LightBreakChance))
|
||||
{
|
||||
var proto = comp.BulbType switch
|
||||
{
|
||||
LightBulbType.Tube => ent.Comp.BrokenLightTubePrototype,
|
||||
_ => ent.Comp.BrokenLightBulbPrototype,
|
||||
};
|
||||
|
||||
_poweredLight.ReplaceSpawnedPrototype((uid, comp), proto);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!Random.Prob(ent.Comp.LightAgingChance))
|
||||
continue;
|
||||
|
||||
if (comp.BulbType == LightBulbType.Tube)
|
||||
{
|
||||
// some aging fluorescents (tubes) start to flicker
|
||||
// its also way too annoying right now so we wrap it in another prob lol
|
||||
if (Random.Prob(ent.Comp.AgedLightTubeFlickerChance))
|
||||
_poweredLight.ToggleBlinkingLight(uid, comp, true);
|
||||
_poweredLight.ReplaceSpawnedPrototype((uid, comp), ent.Comp.AgedLightTubePrototype);
|
||||
}
|
||||
else
|
||||
{
|
||||
_poweredLight.ReplaceSpawnedPrototype((uid, comp), ent.Comp.AgedLightBulbPrototype);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using Content.Server.Fluids.EntitySystems;
|
||||
using Content.Server.GameTicking.Rules.VariationPass.Components;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules.VariationPass;
|
||||
|
||||
/// <inheritdoc cref="PuddleMessVariationPassComponent"/>
|
||||
public sealed class PuddleMessVariationPassSystem : VariationPassSystem<PuddleMessVariationPassComponent>
|
||||
{
|
||||
[Dependency] private readonly PuddleSystem _puddle = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
|
||||
protected override void ApplyVariation(Entity<PuddleMessVariationPassComponent> ent, ref StationVariationPassEvent args)
|
||||
{
|
||||
var totalTiles = Stations.GetTileCount(args.Station);
|
||||
|
||||
if (!_proto.TryIndex(ent.Comp.RandomPuddleSolutionFill, out var proto))
|
||||
return;
|
||||
|
||||
var puddleMod = Random.NextGaussian(ent.Comp.TilesPerSpillAverage, ent.Comp.TilesPerSpillStdDev);
|
||||
var puddleTiles = Math.Max((int) (totalTiles * (1 / puddleMod)), 0);
|
||||
|
||||
for (var i = 0; i < puddleTiles; i++)
|
||||
{
|
||||
if (!TryFindRandomTileOnStation(args.Station, out _, out _, out var coords))
|
||||
continue;
|
||||
|
||||
var sol = proto.Pick(Random);
|
||||
_puddle.TrySpillAt(coords, new Solution(sol.reagent, sol.quantity), out _, sound: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using Content.Server.GameTicking.Rules.VariationPass.Components;
|
||||
using Content.Server.GameTicking.Rules.VariationPass.Components.ReplacementMarkers;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules.VariationPass;
|
||||
|
||||
/// <summary>
|
||||
/// This handles the ability to replace entities marked with <see cref="ReinforcedWallReplacementMarkerComponent"/> in a variation pass
|
||||
/// </summary>
|
||||
public sealed class ReinforcedWallReplaceVariationPassSystem : BaseEntityReplaceVariationPassSystem<ReinforcedWallReplacementMarkerComponent, ReinforcedWallReplaceVariationPassComponent>
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Content.Server.Station.Systems;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules.VariationPass;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for procedural variation rule passes, which apply some kind of variation to a station,
|
||||
/// so we simply reduce the boilerplate for the event handling a bit with this.
|
||||
/// </summary>
|
||||
public abstract class VariationPassSystem<T> : GameRuleSystem<T>
|
||||
where T: IComponent
|
||||
{
|
||||
[Dependency] protected readonly StationSystem Stations = default!;
|
||||
[Dependency] protected readonly IRobustRandom Random = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<T, StationVariationPassEvent>(ApplyVariation);
|
||||
}
|
||||
|
||||
protected bool IsMemberOfStation(Entity<TransformComponent> ent, ref StationVariationPassEvent args)
|
||||
{
|
||||
return Stations.GetOwningStation(ent, ent.Comp) == args.Station.Owner;
|
||||
}
|
||||
|
||||
protected abstract void ApplyVariation(Entity<T> ent, ref StationVariationPassEvent args);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using Content.Server.GameTicking.Rules.VariationPass.Components;
|
||||
using Content.Server.GameTicking.Rules.VariationPass.Components.ReplacementMarkers;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules.VariationPass;
|
||||
|
||||
/// <summary>
|
||||
/// This handles the ability to replace entities marked with <see cref="WallReplacementMarkerComponent"/> in a variation pass
|
||||
/// </summary>
|
||||
public sealed class WallReplaceVariationPassSystem : BaseEntityReplaceVariationPassSystem<WallReplacementMarkerComponent, WallReplaceVariationPassComponent>
|
||||
{
|
||||
}
|
||||
Reference in New Issue
Block a user