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:
@@ -21,7 +21,10 @@ public sealed class SecretStartsTest
|
|||||||
|
|
||||||
await server.WaitAssertion(() =>
|
await server.WaitAssertion(() =>
|
||||||
{
|
{
|
||||||
gameTicker.StartGameRule("Secret");
|
// this mimics roundflow:
|
||||||
|
// rules added, then round starts
|
||||||
|
gameTicker.AddGameRule("Secret");
|
||||||
|
gameTicker.StartGamePresetRules();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Wait three ticks for any random update loops that might happen
|
// Wait three ticks for any random update loops that might happen
|
||||||
|
|||||||
@@ -30,10 +30,10 @@ public sealed partial class SolutionContainerSystem : SharedSolutionContainerSys
|
|||||||
public Solution EnsureSolution(Entity<MetaDataComponent?> entity, string name, out bool existed)
|
public Solution EnsureSolution(Entity<MetaDataComponent?> entity, string name, out bool existed)
|
||||||
=> EnsureSolution(entity, name, FixedPoint2.Zero, out existed);
|
=> EnsureSolution(entity, name, FixedPoint2.Zero, out existed);
|
||||||
|
|
||||||
public Solution EnsureSolution(Entity<MetaDataComponent?> entity, string name, FixedPoint2 minVol, out bool existed)
|
public Solution EnsureSolution(Entity<MetaDataComponent?> entity, string name, FixedPoint2 maxVol, out bool existed)
|
||||||
=> EnsureSolution(entity, name, minVol, null, out existed);
|
=> EnsureSolution(entity, name, maxVol, null, out existed);
|
||||||
|
|
||||||
public Solution EnsureSolution(Entity<MetaDataComponent?> entity, string name, FixedPoint2 minVol, Solution? prototype, out bool existed)
|
public Solution EnsureSolution(Entity<MetaDataComponent?> entity, string name, FixedPoint2 maxVol, Solution? prototype, out bool existed)
|
||||||
{
|
{
|
||||||
var (uid, meta) = entity;
|
var (uid, meta) = entity;
|
||||||
if (!Resolve(uid, ref meta))
|
if (!Resolve(uid, ref meta))
|
||||||
@@ -41,12 +41,26 @@ public sealed partial class SolutionContainerSystem : SharedSolutionContainerSys
|
|||||||
|
|
||||||
var manager = EnsureComp<SolutionContainerManagerComponent>(uid);
|
var manager = EnsureComp<SolutionContainerManagerComponent>(uid);
|
||||||
if (meta.EntityLifeStage >= EntityLifeStage.MapInitialized)
|
if (meta.EntityLifeStage >= EntityLifeStage.MapInitialized)
|
||||||
return EnsureSolutionEntity((uid, manager), name, minVol, prototype, out existed).Comp.Solution;
|
return EnsureSolutionEntity((uid, manager), name, maxVol, prototype, out existed).Comp.Solution;
|
||||||
else
|
else
|
||||||
return EnsureSolutionPrototype((uid, manager), name, minVol, prototype, out existed);
|
return EnsureSolutionPrototype((uid, manager), name, maxVol, prototype, out existed);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Entity<SolutionComponent> EnsureSolutionEntity(Entity<SolutionContainerManagerComponent?> entity, string name, FixedPoint2 minVol, Solution? prototype, out bool existed)
|
public void EnsureAllSolutions(Entity<SolutionContainerManagerComponent> entity)
|
||||||
|
{
|
||||||
|
if (entity.Comp.Solutions is not { } prototypes)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var (name, prototype) in prototypes)
|
||||||
|
{
|
||||||
|
EnsureSolutionEntity((entity.Owner, entity.Comp), name, prototype.MaxVolume, prototype, out _);
|
||||||
|
}
|
||||||
|
|
||||||
|
entity.Comp.Solutions = null;
|
||||||
|
Dirty(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Entity<SolutionComponent> EnsureSolutionEntity(Entity<SolutionContainerManagerComponent?> entity, string name, FixedPoint2 maxVol, Solution? prototype, out bool existed)
|
||||||
{
|
{
|
||||||
existed = true;
|
existed = true;
|
||||||
|
|
||||||
@@ -69,9 +83,9 @@ public sealed partial class SolutionContainerSystem : SharedSolutionContainerSys
|
|||||||
SolutionComponent solutionComp;
|
SolutionComponent solutionComp;
|
||||||
if (solutionSlot.ContainedEntity is not { } solutionId)
|
if (solutionSlot.ContainedEntity is not { } solutionId)
|
||||||
{
|
{
|
||||||
prototype ??= new() { MaxVolume = minVol };
|
prototype ??= new() { MaxVolume = maxVol };
|
||||||
prototype.Name = name;
|
prototype.Name = name;
|
||||||
(solutionId, solutionComp, _) = SpawnSolutionUninitialized(solutionSlot, name, minVol, prototype);
|
(solutionId, solutionComp, _) = SpawnSolutionUninitialized(solutionSlot, name, maxVol, prototype);
|
||||||
existed = false;
|
existed = false;
|
||||||
needsInit = true;
|
needsInit = true;
|
||||||
Dirty(uid, container);
|
Dirty(uid, container);
|
||||||
@@ -83,7 +97,7 @@ public sealed partial class SolutionContainerSystem : SharedSolutionContainerSys
|
|||||||
DebugTools.Assert(solutionComp.Solution.Name == name);
|
DebugTools.Assert(solutionComp.Solution.Name == name);
|
||||||
|
|
||||||
var solution = solutionComp.Solution;
|
var solution = solutionComp.Solution;
|
||||||
solution.MaxVolume = FixedPoint2.Max(solution.MaxVolume, minVol);
|
solution.MaxVolume = FixedPoint2.Max(solution.MaxVolume, maxVol);
|
||||||
|
|
||||||
// Depending on MapInitEvent order some systems can ensure solution empty solutions and conflict with the prototype solutions.
|
// Depending on MapInitEvent order some systems can ensure solution empty solutions and conflict with the prototype solutions.
|
||||||
// We want the reagents from the prototype to exist even if something else already created the solution.
|
// We want the reagents from the prototype to exist even if something else already created the solution.
|
||||||
@@ -99,7 +113,7 @@ public sealed partial class SolutionContainerSystem : SharedSolutionContainerSys
|
|||||||
return (solutionId, solutionComp);
|
return (solutionId, solutionComp);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Solution EnsureSolutionPrototype(Entity<SolutionContainerManagerComponent?> entity, string name, FixedPoint2 minVol, Solution? prototype, out bool existed)
|
private Solution EnsureSolutionPrototype(Entity<SolutionContainerManagerComponent?> entity, string name, FixedPoint2 maxVol, Solution? prototype, out bool existed)
|
||||||
{
|
{
|
||||||
existed = true;
|
existed = true;
|
||||||
|
|
||||||
@@ -115,19 +129,19 @@ public sealed partial class SolutionContainerSystem : SharedSolutionContainerSys
|
|||||||
|
|
||||||
if (!container.Solutions.TryGetValue(name, out var solution))
|
if (!container.Solutions.TryGetValue(name, out var solution))
|
||||||
{
|
{
|
||||||
solution = prototype ?? new() { Name = name, MaxVolume = minVol };
|
solution = prototype ?? new() { Name = name, MaxVolume = maxVol };
|
||||||
container.Solutions.Add(name, solution);
|
container.Solutions.Add(name, solution);
|
||||||
existed = false;
|
existed = false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
solution.MaxVolume = FixedPoint2.Max(solution.MaxVolume, minVol);
|
solution.MaxVolume = FixedPoint2.Max(solution.MaxVolume, maxVol);
|
||||||
|
|
||||||
Dirty(uid, container);
|
Dirty(uid, container);
|
||||||
return solution;
|
return solution;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private Entity<SolutionComponent, ContainedSolutionComponent> SpawnSolutionUninitialized(ContainerSlot container, string name, FixedPoint2 minVol, Solution prototype)
|
private Entity<SolutionComponent, ContainedSolutionComponent> SpawnSolutionUninitialized(ContainerSlot container, string name, FixedPoint2 maxVol, Solution prototype)
|
||||||
{
|
{
|
||||||
var coords = new EntityCoordinates(container.Owner, Vector2.Zero);
|
var coords = new EntityCoordinates(container.Owner, Vector2.Zero);
|
||||||
var uid = EntityManager.CreateEntityUninitialized(null, coords, null);
|
var uid = EntityManager.CreateEntityUninitialized(null, coords, null);
|
||||||
@@ -148,16 +162,7 @@ public sealed partial class SolutionContainerSystem : SharedSolutionContainerSys
|
|||||||
|
|
||||||
private void OnMapInit(Entity<SolutionContainerManagerComponent> entity, ref MapInitEvent args)
|
private void OnMapInit(Entity<SolutionContainerManagerComponent> entity, ref MapInitEvent args)
|
||||||
{
|
{
|
||||||
if (entity.Comp.Solutions is not { } prototypes)
|
EnsureAllSolutions(entity);
|
||||||
return;
|
|
||||||
|
|
||||||
foreach (var (name, prototype) in prototypes)
|
|
||||||
{
|
|
||||||
EnsureSolutionEntity((entity.Owner, entity.Comp), name, prototype.MaxVolume, prototype, out _);
|
|
||||||
}
|
|
||||||
|
|
||||||
entity.Comp.Solutions = null;
|
|
||||||
Dirty(entity);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnComponentShutdown(Entity<SolutionContainerManagerComponent> entity, ref ComponentShutdown args)
|
private void OnComponentShutdown(Entity<SolutionContainerManagerComponent> entity, ref ComponentShutdown args)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Content.Server.Fluids.Components;
|
|||||||
using Content.Server.Spreader;
|
using Content.Server.Spreader;
|
||||||
using Content.Shared.Chemistry;
|
using Content.Shared.Chemistry;
|
||||||
using Content.Shared.Chemistry.Components;
|
using Content.Shared.Chemistry.Components;
|
||||||
|
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||||
using Content.Shared.Chemistry.EntitySystems;
|
using Content.Shared.Chemistry.EntitySystems;
|
||||||
using Content.Shared.Chemistry.Reaction;
|
using Content.Shared.Chemistry.Reaction;
|
||||||
using Content.Shared.Chemistry.Reagent;
|
using Content.Shared.Chemistry.Reagent;
|
||||||
@@ -505,11 +506,14 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
|
|||||||
Solution addedSolution,
|
Solution addedSolution,
|
||||||
bool sound = true,
|
bool sound = true,
|
||||||
bool checkForOverflow = true,
|
bool checkForOverflow = true,
|
||||||
PuddleComponent? puddleComponent = null)
|
PuddleComponent? puddleComponent = null,
|
||||||
|
SolutionContainerManagerComponent? sol = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(puddleUid, ref puddleComponent))
|
if (!Resolve(puddleUid, ref puddleComponent, ref sol))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
_solutionContainerSystem.EnsureAllSolutions((puddleUid, sol));
|
||||||
|
|
||||||
if (addedSolution.Volume == 0 ||
|
if (addedSolution.Volume == 0 ||
|
||||||
!_solutionContainerSystem.ResolveSolution(puddleUid, puddleComponent.SolutionName,
|
!_solutionContainerSystem.ResolveSolution(puddleUid, puddleComponent.SolutionName,
|
||||||
ref puddleComponent.Solution))
|
ref puddleComponent.Solution))
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ namespace Content.Server.GameTicking
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void StartGamePresetRules()
|
public void StartGamePresetRules()
|
||||||
{
|
{
|
||||||
// May be touched by the preset during init.
|
// May be touched by the preset during init.
|
||||||
var rules = new List<EntityUid>(GetAddedGameRules());
|
var rules = new List<EntityUid>(GetAddedGameRules());
|
||||||
|
|||||||
@@ -141,6 +141,24 @@ public sealed partial class GameTicker
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if a game rule with the given component has been added.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsGameRuleAdded<T>()
|
||||||
|
where T : IComponent
|
||||||
|
{
|
||||||
|
var query = EntityQueryEnumerator<T, GameRuleComponent>();
|
||||||
|
while (query.MoveNext(out var uid, out _, out _))
|
||||||
|
{
|
||||||
|
if (HasComp<EndedGameRuleComponent>(uid))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsGameRuleAdded(EntityUid ruleEntity, GameRuleComponent? component = null)
|
public bool IsGameRuleAdded(EntityUid ruleEntity, GameRuleComponent? component = null)
|
||||||
{
|
{
|
||||||
return Resolve(ruleEntity, ref component) && !HasComp<EndedGameRuleComponent>(ruleEntity);
|
return Resolve(ruleEntity, ref component) && !HasComp<EndedGameRuleComponent>(ruleEntity);
|
||||||
@@ -157,6 +175,22 @@ public sealed partial class GameTicker
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if a game rule with the given component is active..
|
||||||
|
/// </summary>
|
||||||
|
public bool IsGameRuleActive<T>()
|
||||||
|
where T : IComponent
|
||||||
|
{
|
||||||
|
var query = EntityQueryEnumerator<T, ActiveGameRuleComponent, GameRuleComponent>();
|
||||||
|
// out, damned underscore!!!
|
||||||
|
while (query.MoveNext(out _, out _, out _, out _))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsGameRuleActive(EntityUid ruleEntity, GameRuleComponent? component = null)
|
public bool IsGameRuleActive(EntityUid ruleEntity, GameRuleComponent? component = null)
|
||||||
{
|
{
|
||||||
return Resolve(ruleEntity, ref component) && HasComp<ActiveGameRuleComponent>(ruleEntity);
|
return Resolve(ruleEntity, ref component) && HasComp<ActiveGameRuleComponent>(ruleEntity);
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using Content.Shared.Storage;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Server.GameTicking.Rules.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This handles starting various roundstart variation rules after a station has been loaded.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed partial class RoundstartStationVariationRuleComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The list of rules that will be started once the map is spawned.
|
||||||
|
/// Uses <see cref="EntitySpawnEntry"/> to support probabilities for various rules
|
||||||
|
/// without having to hardcode the probability directly in the rule's logic.
|
||||||
|
/// </summary>
|
||||||
|
[DataField(required: true)]
|
||||||
|
public List<EntitySpawnEntry> Rules = new();
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Content.Server.GameTicking.Rules.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is a marker component placed on rule entities which are a single "pass" of station variation.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed partial class StationVariationPassRuleComponent : Component
|
||||||
|
{
|
||||||
|
}
|
||||||
137
Content.Server/GameTicking/Rules/GameRuleSystem.Utility.cs
Normal file
137
Content.Server/GameTicking/Rules/GameRuleSystem.Utility.cs
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
|
using Content.Server.Station.Components;
|
||||||
|
using Robust.Shared.Collections;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Map.Components;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
|
namespace Content.Server.GameTicking.Rules;
|
||||||
|
|
||||||
|
public abstract partial class GameRuleSystem<T> where T: IComponent
|
||||||
|
{
|
||||||
|
protected EntityQueryEnumerator<ActiveGameRuleComponent, T, GameRuleComponent> QueryActiveRules()
|
||||||
|
{
|
||||||
|
return EntityQueryEnumerator<ActiveGameRuleComponent, T, GameRuleComponent>();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected bool TryRoundStartAttempt(RoundStartAttemptEvent ev, string localizedPresetName)
|
||||||
|
{
|
||||||
|
var query = EntityQueryEnumerator<ActiveGameRuleComponent, T, GameRuleComponent>();
|
||||||
|
while (query.MoveNext(out _, out _, out _, out var gameRule))
|
||||||
|
{
|
||||||
|
var minPlayers = gameRule.MinPlayers;
|
||||||
|
if (!ev.Forced && ev.Players.Length < minPlayers)
|
||||||
|
{
|
||||||
|
ChatManager.SendAdminAnnouncement(Loc.GetString("preset-not-enough-ready-players",
|
||||||
|
("readyPlayersCount", ev.Players.Length), ("minimumPlayers", minPlayers),
|
||||||
|
("presetName", localizedPresetName)));
|
||||||
|
ev.Cancel();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ev.Players.Length == 0)
|
||||||
|
{
|
||||||
|
ChatManager.DispatchServerAnnouncement(Loc.GetString("preset-no-one-ready"));
|
||||||
|
ev.Cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return !ev.Cancelled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Utility function for finding a random event-eligible station entity
|
||||||
|
/// </summary>
|
||||||
|
protected bool TryGetRandomStation([NotNullWhen(true)] out EntityUid? station, Func<EntityUid, bool>? filter = null)
|
||||||
|
{
|
||||||
|
var stations = new ValueList<EntityUid>(Count<StationEventEligibleComponent>());
|
||||||
|
|
||||||
|
filter ??= _ => true;
|
||||||
|
var query = AllEntityQuery<StationEventEligibleComponent>();
|
||||||
|
|
||||||
|
while (query.MoveNext(out var uid, out _))
|
||||||
|
{
|
||||||
|
if (!filter(uid))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
stations.Add(uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stations.Count == 0)
|
||||||
|
{
|
||||||
|
station = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Engine PR.
|
||||||
|
station = stations[RobustRandom.Next(stations.Count)];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected bool TryFindRandomTile(out Vector2i tile,
|
||||||
|
[NotNullWhen(true)] out EntityUid? targetStation,
|
||||||
|
out EntityUid targetGrid,
|
||||||
|
out EntityCoordinates targetCoords)
|
||||||
|
{
|
||||||
|
tile = default;
|
||||||
|
targetStation = EntityUid.Invalid;
|
||||||
|
targetGrid = EntityUid.Invalid;
|
||||||
|
targetCoords = EntityCoordinates.Invalid;
|
||||||
|
if (TryGetRandomStation(out targetStation))
|
||||||
|
{
|
||||||
|
return TryFindRandomTileOnStation((targetStation.Value, Comp<StationDataComponent>(targetStation.Value)),
|
||||||
|
out tile,
|
||||||
|
out targetGrid,
|
||||||
|
out targetCoords);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected bool TryFindRandomTileOnStation(Entity<StationDataComponent> station,
|
||||||
|
out Vector2i tile,
|
||||||
|
out EntityUid targetGrid,
|
||||||
|
out EntityCoordinates targetCoords)
|
||||||
|
{
|
||||||
|
tile = default;
|
||||||
|
targetCoords = EntityCoordinates.Invalid;
|
||||||
|
targetGrid = EntityUid.Invalid;
|
||||||
|
|
||||||
|
var possibleTargets = station.Comp.Grids;
|
||||||
|
if (possibleTargets.Count == 0)
|
||||||
|
{
|
||||||
|
targetGrid = EntityUid.Invalid;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
targetGrid = RobustRandom.Pick(possibleTargets);
|
||||||
|
|
||||||
|
if (!TryComp<MapGridComponent>(targetGrid, out var gridComp))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var found = false;
|
||||||
|
var aabb = gridComp.LocalAABB;
|
||||||
|
|
||||||
|
for (var i = 0; i < 10; i++)
|
||||||
|
{
|
||||||
|
var randomX = RobustRandom.Next((int) aabb.Left, (int) aabb.Right);
|
||||||
|
var randomY = RobustRandom.Next((int) aabb.Bottom, (int) aabb.Top);
|
||||||
|
|
||||||
|
tile = new Vector2i(randomX, randomY);
|
||||||
|
if (_atmosphere.IsTileSpace(targetGrid, Transform(targetGrid).MapUid, tile,
|
||||||
|
mapGridComp: gridComp)
|
||||||
|
|| _atmosphere.IsTileAirBlocked(targetGrid, tile, mapGridComp: gridComp))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
found = true;
|
||||||
|
targetCoords = _map.GridTileToLocal(targetGrid, gridComp, tile);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,13 +1,27 @@
|
|||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using Content.Server.Atmos.EntitySystems;
|
||||||
using Content.Server.Chat.Managers;
|
using Content.Server.Chat.Managers;
|
||||||
using Content.Server.GameTicking.Rules.Components;
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
|
using Content.Server.Station.Components;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Shared.Collections;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Map.Components;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
namespace Content.Server.GameTicking.Rules;
|
namespace Content.Server.GameTicking.Rules;
|
||||||
|
|
||||||
public abstract partial class GameRuleSystem<T> : EntitySystem where T : IComponent
|
public abstract partial class GameRuleSystem<T> : EntitySystem where T : IComponent
|
||||||
{
|
{
|
||||||
|
[Dependency] protected readonly IRobustRandom RobustRandom = default!;
|
||||||
[Dependency] protected readonly IChatManager ChatManager = default!;
|
[Dependency] protected readonly IChatManager ChatManager = default!;
|
||||||
[Dependency] protected readonly GameTicker GameTicker = default!;
|
[Dependency] protected readonly GameTicker GameTicker = default!;
|
||||||
|
|
||||||
|
// Not protected, just to be used in utility methods
|
||||||
|
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||||
|
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
|
||||||
|
[Dependency] private readonly MapSystem _map = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
@@ -71,36 +85,6 @@ public abstract partial class GameRuleSystem<T> : EntitySystem where T : ICompon
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected EntityQueryEnumerator<ActiveGameRuleComponent, T, GameRuleComponent> QueryActiveRules()
|
|
||||||
{
|
|
||||||
return EntityQueryEnumerator<ActiveGameRuleComponent, T, GameRuleComponent>();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected bool TryRoundStartAttempt(RoundStartAttemptEvent ev, string localizedPresetName)
|
|
||||||
{
|
|
||||||
var query = EntityQueryEnumerator<ActiveGameRuleComponent, T, GameRuleComponent>();
|
|
||||||
while (query.MoveNext(out _, out _, out _, out var gameRule))
|
|
||||||
{
|
|
||||||
var minPlayers = gameRule.MinPlayers;
|
|
||||||
if (!ev.Forced && ev.Players.Length < minPlayers)
|
|
||||||
{
|
|
||||||
ChatManager.SendAdminAnnouncement(Loc.GetString("preset-not-enough-ready-players",
|
|
||||||
("readyPlayersCount", ev.Players.Length), ("minimumPlayers", minPlayers),
|
|
||||||
("presetName", localizedPresetName)));
|
|
||||||
ev.Cancel();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ev.Players.Length == 0)
|
|
||||||
{
|
|
||||||
ChatManager.DispatchServerAnnouncement(Loc.GetString("preset-no-one-ready"));
|
|
||||||
ev.Cancel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return !ev.Cancelled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Update(float frameTime)
|
public override void Update(float frameTime)
|
||||||
{
|
{
|
||||||
base.Update(frameTime);
|
base.Update(frameTime);
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
|
using Content.Server.Shuttles.Systems;
|
||||||
|
using Content.Server.Station.Components;
|
||||||
|
using Content.Server.Station.Events;
|
||||||
|
using Content.Shared.Storage;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
|
namespace Content.Server.GameTicking.Rules;
|
||||||
|
|
||||||
|
/// <inheritdoc cref="RoundstartStationVariationRuleComponent"/>
|
||||||
|
public sealed class RoundstartStationVariationRuleSystem : GameRuleSystem<RoundstartStationVariationRuleComponent>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<StationPostInitEvent>(OnStationPostInit, after: new []{typeof(ShuttleSystem)});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Added(EntityUid uid, RoundstartStationVariationRuleComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args)
|
||||||
|
{
|
||||||
|
var spawns = EntitySpawnCollection.GetSpawns(component.Rules, _random);
|
||||||
|
foreach (var rule in spawns)
|
||||||
|
{
|
||||||
|
GameTicker.AddGameRule(rule);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnStationPostInit(ref StationPostInitEvent ev)
|
||||||
|
{
|
||||||
|
// as long as one is running
|
||||||
|
if (!GameTicker.IsGameRuleAdded<RoundstartStationVariationRuleComponent>())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// this is unlikely, but could theoretically happen if it was saved and reloaded, so check anyway
|
||||||
|
if (HasComp<StationVariationHasRunComponent>(ev.Station))
|
||||||
|
return;
|
||||||
|
|
||||||
|
Log.Info($"Running variation rules for station {ToPrettyString(ev.Station)}");
|
||||||
|
|
||||||
|
// raise the event on any passes that have been added
|
||||||
|
var passEv = new StationVariationPassEvent(ev.Station);
|
||||||
|
var passQuery = EntityQueryEnumerator<StationVariationPassRuleComponent, GameRuleComponent>();
|
||||||
|
while (passQuery.MoveNext(out var uid, out _, out _))
|
||||||
|
{
|
||||||
|
// TODO: for some reason, ending a game rule just gives it a marker comp,
|
||||||
|
// and doesnt delete it
|
||||||
|
// so we have to check here that it isnt an ended game rule (which could happen if a preset failed to start
|
||||||
|
// or it was ended before station maps spawned etc etc etc)
|
||||||
|
if (HasComp<EndedGameRuleComponent>(uid))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
RaiseLocalEvent(uid, ref passEv);
|
||||||
|
}
|
||||||
|
|
||||||
|
EnsureComp<StationVariationHasRunComponent>(ev.Station);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised directed on game rule entities which are added and marked as <see cref="StationVariationPassRuleComponent"/>
|
||||||
|
/// when a new station is initialized that should be varied.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Station">The new station that was added, and its config & grids.</param>
|
||||||
|
[ByRefEvent]
|
||||||
|
public readonly record struct StationVariationPassEvent(Entity<StationDataComponent> Station);
|
||||||
@@ -18,9 +18,9 @@ public sealed class SecretRuleSystem : GameRuleSystem<SecretRuleComponent>
|
|||||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||||
|
|
||||||
protected override void Started(EntityUid uid, SecretRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
protected override void Added(EntityUid uid, SecretRuleComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args)
|
||||||
{
|
{
|
||||||
base.Started(uid, component, gameRule, args);
|
base.Added(uid, component, gameRule, args);
|
||||||
PickRule(component);
|
PickRule(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,13 +40,24 @@ public sealed class SecretRuleSystem : GameRuleSystem<SecretRuleComponent>
|
|||||||
// but currently there's no way to know anyway as they use cvars.
|
// but currently there's no way to know anyway as they use cvars.
|
||||||
var presetString = _configurationManager.GetCVar(CCVars.SecretWeightPrototype);
|
var presetString = _configurationManager.GetCVar(CCVars.SecretWeightPrototype);
|
||||||
var preset = _prototypeManager.Index<WeightedRandomPrototype>(presetString).Pick(_random);
|
var preset = _prototypeManager.Index<WeightedRandomPrototype>(presetString).Pick(_random);
|
||||||
Logger.InfoS("gamepreset", $"Selected {preset} for secret.");
|
Log.Info($"Selected {preset} for secret.");
|
||||||
_adminLogger.Add(LogType.EventStarted, $"Selected {preset} for secret.");
|
_adminLogger.Add(LogType.EventStarted, $"Selected {preset} for secret.");
|
||||||
|
|
||||||
var rules = _prototypeManager.Index<GamePresetPrototype>(preset).Rules;
|
var rules = _prototypeManager.Index<GamePresetPrototype>(preset).Rules;
|
||||||
foreach (var rule in rules)
|
foreach (var rule in rules)
|
||||||
{
|
{
|
||||||
GameTicker.StartGameRule(rule, out var ruleEnt);
|
EntityUid ruleEnt;
|
||||||
|
|
||||||
|
// if we're pre-round (i.e. will only be added)
|
||||||
|
// then just add rules. if we're added in the middle of the round (or at any other point really)
|
||||||
|
// then we want to start them as well
|
||||||
|
if (GameTicker.RunLevel <= GameRunLevel.InRound)
|
||||||
|
ruleEnt = GameTicker.AddGameRule(rule);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GameTicker.StartGameRule(rule, out ruleEnt);
|
||||||
|
}
|
||||||
|
|
||||||
component.AdditionalGameRules.Add(ruleEnt);
|
component.AdditionalGameRules.Add(ruleEnt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -214,6 +214,21 @@ namespace Content.Server.Light.EntitySystems
|
|||||||
return bulb;
|
return bulb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Replaces the spawned prototype of a pre-mapinit powered light with a different variant.
|
||||||
|
/// </summary>
|
||||||
|
public bool ReplaceSpawnedPrototype(Entity<PoweredLightComponent> light, string bulb)
|
||||||
|
{
|
||||||
|
if (light.Comp.LightBulbContainer.ContainedEntity != null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (LifeStage(light.Owner) >= EntityLifeStage.MapInitialized)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
light.Comp.HasLampOnSpawn = bulb;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Try to replace current bulb with a new one
|
/// Try to replace current bulb with a new one
|
||||||
/// If succeed old bulb just drops on floor
|
/// If succeed old bulb just drops on floor
|
||||||
@@ -241,6 +256,17 @@ namespace Content.Server.Light.EntitySystems
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool TryDestroyBulb(EntityUid uid, PoweredLightComponent? light = null)
|
public bool TryDestroyBulb(EntityUid uid, PoweredLightComponent? light = null)
|
||||||
{
|
{
|
||||||
|
if (!Resolve(uid, ref light, false))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// if we aren't mapinited,
|
||||||
|
// just null the spawned bulb
|
||||||
|
if (LifeStage(uid) < EntityLifeStage.MapInitialized)
|
||||||
|
{
|
||||||
|
light.HasLampOnSpawn = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// check bulb state
|
// check bulb state
|
||||||
var bulbUid = GetBulb(uid, light);
|
var bulbUid = GetBulb(uid, light);
|
||||||
if (bulbUid == null || !EntityManager.TryGetComponent(bulbUid.Value, out LightBulbComponent? lightBulb))
|
if (bulbUid == null || !EntityManager.TryGetComponent(bulbUid.Value, out LightBulbComponent? lightBulb))
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using Content.Server.GameTicking.Rules;
|
||||||
|
|
||||||
|
namespace Content.Server.Station.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Marker component for stations where procedural variation using <see cref="RoundstartStationVariationRuleSystem"/>
|
||||||
|
/// has already run, so as to avoid running it again if another station is added.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed partial class StationVariationHasRunComponent : Component
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
|
using Content.Server.Station.Components;
|
||||||
|
|
||||||
namespace Content.Server.Station.Events;
|
namespace Content.Server.Station.Events;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Raised directed on a station after it has been initialized.
|
/// Raised directed on a station after it has been initialized, as well as broadcast.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ByRefEvent]
|
[ByRefEvent]
|
||||||
public readonly record struct StationPostInitEvent;
|
public readonly record struct StationPostInitEvent(Entity<StationDataComponent> Station);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using Content.Server.Station.Events;
|
|||||||
using Content.Shared.CCVar;
|
using Content.Shared.CCVar;
|
||||||
using Content.Shared.Station;
|
using Content.Shared.Station;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Server.Player;
|
using Robust.Server.Player;
|
||||||
using Robust.Shared.Collections;
|
using Robust.Shared.Collections;
|
||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
@@ -35,6 +36,7 @@ public sealed class StationSystem : EntitySystem
|
|||||||
[Dependency] private readonly GameTicker _gameTicker = default!;
|
[Dependency] private readonly GameTicker _gameTicker = default!;
|
||||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||||
|
[Dependency] private readonly MapSystem _map = default!;
|
||||||
|
|
||||||
private ISawmill _sawmill = default!;
|
private ISawmill _sawmill = default!;
|
||||||
|
|
||||||
@@ -208,6 +210,23 @@ public sealed class StationSystem : EntitySystem
|
|||||||
return largestGrid;
|
return largestGrid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the total number of tiles contained in the station's grids.
|
||||||
|
/// </summary>
|
||||||
|
public int GetTileCount(StationDataComponent component)
|
||||||
|
{
|
||||||
|
var count = 0;
|
||||||
|
foreach (var gridUid in component.Grids)
|
||||||
|
{
|
||||||
|
if (!TryComp<MapGridComponent>(gridUid, out var grid))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
count += _map.GetAllTiles(gridUid, grid).Count();
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tries to retrieve a filter for everything in the station the source is on.
|
/// Tries to retrieve a filter for everything in the station the source is on.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -306,8 +325,8 @@ public sealed class StationSystem : EntitySystem
|
|||||||
AddGridToStation(station, grid, null, data, name);
|
AddGridToStation(station, grid, null, data, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
var ev = new StationPostInitEvent();
|
var ev = new StationPostInitEvent((station, data));
|
||||||
RaiseLocalEvent(station, ref ev);
|
RaiseLocalEvent(station, ref ev, true);
|
||||||
|
|
||||||
return station;
|
return station;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ public abstract partial class StationEventSystem<T> : GameRuleSystem<T> where T
|
|||||||
[Dependency] private readonly IGameTiming _timing = default!;
|
[Dependency] private readonly IGameTiming _timing = default!;
|
||||||
[Dependency] protected readonly IMapManager MapManager = default!;
|
[Dependency] protected readonly IMapManager MapManager = default!;
|
||||||
[Dependency] protected readonly IPrototypeManager PrototypeManager = default!;
|
[Dependency] protected readonly IPrototypeManager PrototypeManager = default!;
|
||||||
[Dependency] protected readonly IRobustRandom RobustRandom = default!;
|
|
||||||
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
|
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
|
||||||
[Dependency] protected readonly ChatSystem ChatSystem = default!;
|
[Dependency] protected readonly ChatSystem ChatSystem = default!;
|
||||||
[Dependency] protected readonly SharedAudioSystem Audio = default!;
|
[Dependency] protected readonly SharedAudioSystem Audio = default!;
|
||||||
@@ -135,79 +134,6 @@ public abstract partial class StationEventSystem<T> : GameRuleSystem<T> where T
|
|||||||
GameTicker.EndGameRule(uid, component);
|
GameTicker.EndGameRule(uid, component);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected bool TryGetRandomStation([NotNullWhen(true)] out EntityUid? station, Func<EntityUid, bool>? filter = null)
|
|
||||||
{
|
|
||||||
var stations = new ValueList<EntityUid>(Count<StationEventEligibleComponent>());
|
|
||||||
|
|
||||||
filter ??= _ => true;
|
|
||||||
var query = AllEntityQuery<StationEventEligibleComponent>();
|
|
||||||
|
|
||||||
while (query.MoveNext(out var uid, out _))
|
|
||||||
{
|
|
||||||
if (!filter(uid))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
stations.Add(uid);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stations.Count == 0)
|
|
||||||
{
|
|
||||||
station = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Engine PR.
|
|
||||||
station = stations[RobustRandom.Next(stations.Count)];
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected bool TryFindRandomTile(out Vector2i tile, [NotNullWhen(true)] out EntityUid? targetStation, out EntityUid targetGrid, out EntityCoordinates targetCoords)
|
|
||||||
{
|
|
||||||
tile = default;
|
|
||||||
|
|
||||||
targetCoords = EntityCoordinates.Invalid;
|
|
||||||
if (!TryGetRandomStation(out targetStation))
|
|
||||||
{
|
|
||||||
targetStation = EntityUid.Invalid;
|
|
||||||
targetGrid = EntityUid.Invalid;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
var possibleTargets = Comp<StationDataComponent>(targetStation.Value).Grids;
|
|
||||||
if (possibleTargets.Count == 0)
|
|
||||||
{
|
|
||||||
targetGrid = EntityUid.Invalid;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
targetGrid = RobustRandom.Pick(possibleTargets);
|
|
||||||
|
|
||||||
if (!TryComp<MapGridComponent>(targetGrid, out var gridComp))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var found = false;
|
|
||||||
var (gridPos, _, gridMatrix) = _transform.GetWorldPositionRotationMatrix(targetGrid);
|
|
||||||
var gridBounds = gridMatrix.TransformBox(gridComp.LocalAABB);
|
|
||||||
|
|
||||||
for (var i = 0; i < 10; i++)
|
|
||||||
{
|
|
||||||
var randomX = RobustRandom.Next((int) gridBounds.Left, (int) gridBounds.Right);
|
|
||||||
var randomY = RobustRandom.Next((int) gridBounds.Bottom, (int) gridBounds.Top);
|
|
||||||
|
|
||||||
tile = new Vector2i(randomX - (int) gridPos.X, randomY - (int) gridPos.Y);
|
|
||||||
if (_atmosphere.IsTileSpace(targetGrid, Transform(targetGrid).MapUid, tile,
|
|
||||||
mapGridComp: gridComp)
|
|
||||||
|| _atmosphere.IsTileAirBlocked(targetGrid, tile, mapGridComp: gridComp))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
found = true;
|
|
||||||
targetCoords = gridComp.GridTileToLocal(tile);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
public float GetSeverityModifier()
|
public float GetSeverityModifier()
|
||||||
{
|
{
|
||||||
var ev = new GetSeverityModifierEvent();
|
var ev = new GetSeverityModifierEvent();
|
||||||
|
|||||||
@@ -12,14 +12,12 @@ namespace Content.Shared.Storage;
|
|||||||
[DataDefinition]
|
[DataDefinition]
|
||||||
public partial struct EntitySpawnEntry
|
public partial struct EntitySpawnEntry
|
||||||
{
|
{
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[DataField("id")]
|
||||||
[DataField("id", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
public EntProtoId? PrototypeId = null;
|
||||||
public string? PrototypeId = null;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The probability that an item will spawn. Takes decimal form so 0.05 is 5%, 0.50 is 50% etc.
|
/// The probability that an item will spawn. Takes decimal form so 0.05 is 5%, 0.50 is 50% etc.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
[DataField("prob")] public float SpawnProbability = 1;
|
[DataField("prob")] public float SpawnProbability = 1;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -43,19 +41,16 @@ public partial struct EntitySpawnEntry
|
|||||||
/// </code>
|
/// </code>
|
||||||
/// </example>
|
/// </example>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
[DataField("orGroup")] public string? GroupId = null;
|
[DataField("orGroup")] public string? GroupId = null;
|
||||||
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[DataField] public int Amount = 1;
|
||||||
[DataField("amount")] public int Amount = 1;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// How many of this can be spawned, in total.
|
/// How many of this can be spawned, in total.
|
||||||
/// If this is lesser or equal to <see cref="Amount"/>, it will spawn <see cref="Amount"/> exactly.
|
/// If this is lesser or equal to <see cref="Amount"/>, it will spawn <see cref="Amount"/> exactly.
|
||||||
/// Otherwise, it chooses a random value between <see cref="Amount"/> and <see cref="MaxAmount"/> on spawn.
|
/// Otherwise, it chooses a random value between <see cref="Amount"/> and <see cref="MaxAmount"/> on spawn.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[DataField] public int MaxAmount = 1;
|
||||||
[DataField("maxAmount")] public int MaxAmount = 1;
|
|
||||||
|
|
||||||
public EntitySpawnEntry() { }
|
public EntitySpawnEntry() { }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,6 +127,19 @@
|
|||||||
lightRadius: 6
|
lightRadius: 6
|
||||||
lightSoftness: 1.1
|
lightSoftness: 1.1
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: LightBulb
|
||||||
|
name: old incandescent light bulb
|
||||||
|
id: LightBulbOld
|
||||||
|
description: An aging light bulb.
|
||||||
|
components:
|
||||||
|
- type: LightBulb
|
||||||
|
bulb: Bulb
|
||||||
|
color: "#FFD1A3" # 4000K color temp
|
||||||
|
lightEnergy: 0.3 # old incandescents just arent as bright
|
||||||
|
lightRadius: 6
|
||||||
|
lightSoftness: 1.1
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
suffix: Broken
|
suffix: Broken
|
||||||
parent: BaseLightbulb
|
parent: BaseLightbulb
|
||||||
@@ -164,6 +177,19 @@
|
|||||||
lightSoftness: 1
|
lightSoftness: 1
|
||||||
PowerUse: 25
|
PowerUse: 25
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: LightTube
|
||||||
|
name: old fluorescent light tube
|
||||||
|
id: LightTubeOld
|
||||||
|
description: An aging light fixture.
|
||||||
|
components:
|
||||||
|
- type: LightBulb
|
||||||
|
color: "#FFDABB" # old fluorescents are yellower-4500K temp
|
||||||
|
lightEnergy: 0.5
|
||||||
|
lightRadius: 10
|
||||||
|
lightSoftness: 1
|
||||||
|
PowerUse: 10
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
suffix: Broken
|
suffix: Broken
|
||||||
parent: BaseLightTube
|
parent: BaseLightTube
|
||||||
|
|||||||
@@ -545,6 +545,7 @@
|
|||||||
3: { state: reinf_construct-3, visible: true}
|
3: { state: reinf_construct-3, visible: true}
|
||||||
4: { state: reinf_construct-4, visible: true}
|
4: { state: reinf_construct-4, visible: true}
|
||||||
5: { state: reinf_construct-5, visible: true}
|
5: { state: reinf_construct-5, visible: true}
|
||||||
|
- type: ReinforcedWallReplacementMarker
|
||||||
- type: StaticPrice
|
- type: StaticPrice
|
||||||
price: 150
|
price: 150
|
||||||
- type: RadiationBlocker
|
- type: RadiationBlocker
|
||||||
@@ -840,6 +841,7 @@
|
|||||||
- RCDDeconstructWhitelist
|
- RCDDeconstructWhitelist
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
sprite: Structures/Walls/solid.rsi
|
sprite: Structures/Walls/solid.rsi
|
||||||
|
- type: WallReplacementMarker
|
||||||
- type: Construction
|
- type: Construction
|
||||||
graph: Girder
|
graph: Girder
|
||||||
node: wall
|
node: wall
|
||||||
|
|||||||
@@ -123,3 +123,22 @@
|
|||||||
noSpawn: true
|
noSpawn: true
|
||||||
components:
|
components:
|
||||||
- type: RampingStationEventScheduler
|
- type: RampingStationEventScheduler
|
||||||
|
|
||||||
|
# variation passes
|
||||||
|
- type: entity
|
||||||
|
id: BasicRoundstartVariation
|
||||||
|
parent: BaseGameRule
|
||||||
|
noSpawn: true
|
||||||
|
components:
|
||||||
|
- type: RoundstartStationVariationRule
|
||||||
|
rules:
|
||||||
|
- id: BasicPoweredLightVariationPass
|
||||||
|
- id: BasicTrashVariationPass
|
||||||
|
- id: SolidWallRustingVariationPass
|
||||||
|
- id: ReinforcedWallRustingVariationPass
|
||||||
|
- id: BasicPuddleMessVariationPass
|
||||||
|
prob: 0.99
|
||||||
|
orGroup: puddleMess
|
||||||
|
- id: BloodbathPuddleMessVariationPass
|
||||||
|
prob: 0.01
|
||||||
|
orGroup: puddleMess
|
||||||
|
|||||||
120
Resources/Prototypes/GameRules/variation.yml
Normal file
120
Resources/Prototypes/GameRules/variation.yml
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
# Base
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: BaseVariationPass
|
||||||
|
parent: BaseGameRule
|
||||||
|
abstract: true
|
||||||
|
noSpawn: true
|
||||||
|
components:
|
||||||
|
- type: StationVariationPassRule
|
||||||
|
|
||||||
|
# Actual rules
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: BasicPoweredLightVariationPass
|
||||||
|
parent: BaseVariationPass
|
||||||
|
noSpawn: true
|
||||||
|
components:
|
||||||
|
- type: PoweredLightVariationPass
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: SolidWallRustingVariationPass
|
||||||
|
parent: BaseVariationPass
|
||||||
|
noSpawn: true
|
||||||
|
components:
|
||||||
|
- type: WallReplaceVariationPass
|
||||||
|
- type: EntityReplaceVariationPass
|
||||||
|
entitiesPerReplacementAverage: 10
|
||||||
|
entitiesPerReplacementStdDev: 2
|
||||||
|
replacements:
|
||||||
|
- id: WallSolidRust
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: ReinforcedWallRustingVariationPass
|
||||||
|
parent: BaseVariationPass
|
||||||
|
noSpawn: true
|
||||||
|
components:
|
||||||
|
- type: ReinforcedWallReplaceVariationPass
|
||||||
|
- type: EntityReplaceVariationPass
|
||||||
|
entitiesPerReplacementAverage: 12
|
||||||
|
entitiesPerReplacementStdDev: 2
|
||||||
|
replacements:
|
||||||
|
- id: WallReinforcedRust
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: BasicTrashVariationPass
|
||||||
|
parent: BaseVariationPass
|
||||||
|
noSpawn: true
|
||||||
|
components:
|
||||||
|
- type: EntitySpawnVariationPass
|
||||||
|
tilesPerEntityAverage: 35
|
||||||
|
tilesPerEntityStdDev: 4
|
||||||
|
entities:
|
||||||
|
- id: RandomSpawner
|
||||||
|
|
||||||
|
- type: weightedRandomFillSolution
|
||||||
|
id: RandomFillTrashPuddle
|
||||||
|
fills:
|
||||||
|
- quantity: 80
|
||||||
|
weight: 5
|
||||||
|
reagents:
|
||||||
|
- Vomit
|
||||||
|
- InsectBlood
|
||||||
|
- WeldingFuel
|
||||||
|
- Mold
|
||||||
|
- quantity: 55
|
||||||
|
weight: 4
|
||||||
|
reagents:
|
||||||
|
- PlantBGone
|
||||||
|
- Potassium # :trollface:
|
||||||
|
- VentCrud
|
||||||
|
- Carbon
|
||||||
|
- Hydrogen
|
||||||
|
- Fat
|
||||||
|
- SpaceLube
|
||||||
|
- SpaceGlue
|
||||||
|
- Sulfur
|
||||||
|
- Acetone
|
||||||
|
- Bleach
|
||||||
|
- quantity: 40
|
||||||
|
weight: 3
|
||||||
|
reagents:
|
||||||
|
- Blood
|
||||||
|
- CopperBlood
|
||||||
|
- Slime
|
||||||
|
- quantity: 25
|
||||||
|
weight: 1
|
||||||
|
reagents:
|
||||||
|
- Omnizine
|
||||||
|
- Desoxyephedrine
|
||||||
|
- Napalm
|
||||||
|
- Radium
|
||||||
|
- Gold
|
||||||
|
- Silver
|
||||||
|
- Sodium
|
||||||
|
|
||||||
|
- type: weightedRandomFillSolution
|
||||||
|
id: RandomFillTrashPuddleBloodbath
|
||||||
|
fills:
|
||||||
|
- quantity: 80
|
||||||
|
weight: 1
|
||||||
|
reagents:
|
||||||
|
- Blood
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: BasicPuddleMessVariationPass
|
||||||
|
parent: BaseVariationPass
|
||||||
|
noSpawn: true
|
||||||
|
components:
|
||||||
|
- type: PuddleMessVariationPass
|
||||||
|
randomPuddleSolutionFill: RandomFillTrashPuddle
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: BloodbathPuddleMessVariationPass
|
||||||
|
parent: BaseVariationPass
|
||||||
|
noSpawn: true
|
||||||
|
components:
|
||||||
|
- type: PuddleMessVariationPass
|
||||||
|
tilesPerSpillAverage: 150
|
||||||
|
tilesPerSpillStdDev: 10
|
||||||
|
randomPuddleSolutionFill: RandomFillTrashPuddleBloodbath
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
description: survival-description
|
description: survival-description
|
||||||
rules:
|
rules:
|
||||||
- RampingStationEventScheduler
|
- RampingStationEventScheduler
|
||||||
|
- BasicRoundstartVariation
|
||||||
|
|
||||||
- type: gamePreset
|
- type: gamePreset
|
||||||
id: AllAtOnce
|
id: AllAtOnce
|
||||||
@@ -30,6 +31,7 @@
|
|||||||
description: extended-description
|
description: extended-description
|
||||||
rules:
|
rules:
|
||||||
- BasicStationEventScheduler
|
- BasicStationEventScheduler
|
||||||
|
- BasicRoundstartVariation
|
||||||
|
|
||||||
- type: gamePreset
|
- type: gamePreset
|
||||||
id: Greenshift
|
id: Greenshift
|
||||||
@@ -39,6 +41,8 @@
|
|||||||
name: greenshift-title
|
name: greenshift-title
|
||||||
showInVote: false #4boring4vote
|
showInVote: false #4boring4vote
|
||||||
description: greenshift-description
|
description: greenshift-description
|
||||||
|
rules:
|
||||||
|
- BasicRoundstartVariation
|
||||||
|
|
||||||
- type: gamePreset
|
- type: gamePreset
|
||||||
id: Secret
|
id: Secret
|
||||||
@@ -72,6 +76,7 @@
|
|||||||
rules:
|
rules:
|
||||||
- Traitor
|
- Traitor
|
||||||
- BasicStationEventScheduler
|
- BasicStationEventScheduler
|
||||||
|
- BasicRoundstartVariation
|
||||||
|
|
||||||
- type: gamePreset
|
- type: gamePreset
|
||||||
id: Deathmatch
|
id: Deathmatch
|
||||||
@@ -96,6 +101,7 @@
|
|||||||
rules:
|
rules:
|
||||||
- Nukeops
|
- Nukeops
|
||||||
- BasicStationEventScheduler
|
- BasicStationEventScheduler
|
||||||
|
- BasicRoundstartVariation
|
||||||
|
|
||||||
- type: gamePreset
|
- type: gamePreset
|
||||||
id: Revolutionary
|
id: Revolutionary
|
||||||
@@ -109,6 +115,7 @@
|
|||||||
rules:
|
rules:
|
||||||
- Revolutionary
|
- Revolutionary
|
||||||
- BasicStationEventScheduler
|
- BasicStationEventScheduler
|
||||||
|
- BasicRoundstartVariation
|
||||||
|
|
||||||
- type: gamePreset
|
- type: gamePreset
|
||||||
id: Zombie
|
id: Zombie
|
||||||
@@ -124,6 +131,7 @@
|
|||||||
rules:
|
rules:
|
||||||
- Zombie
|
- Zombie
|
||||||
- BasicStationEventScheduler
|
- BasicStationEventScheduler
|
||||||
|
- BasicRoundstartVariation
|
||||||
|
|
||||||
- type: gamePreset
|
- type: gamePreset
|
||||||
id: Pirates
|
id: Pirates
|
||||||
@@ -135,3 +143,4 @@
|
|||||||
rules:
|
rules:
|
||||||
- Pirates
|
- Pirates
|
||||||
- BasicStationEventScheduler
|
- BasicStationEventScheduler
|
||||||
|
- BasicRoundstartVariation
|
||||||
|
|||||||
Reference in New Issue
Block a user