Multiantag Gamemode (#37783)
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> Co-authored-by: Southbridge <7013162+southbridge-fur@users.noreply.github.com> Co-authored-by: ScarKy0 <106310278+ScarKy0@users.noreply.github.com>
This commit is contained in:
@@ -34,6 +34,8 @@ namespace Content.Client.GameTicking.Managers
|
|||||||
[ViewVariables] public TimeSpan StartTime { get; private set; }
|
[ViewVariables] public TimeSpan StartTime { get; private set; }
|
||||||
[ViewVariables] public new bool Paused { get; private set; }
|
[ViewVariables] public new bool Paused { get; private set; }
|
||||||
|
|
||||||
|
public override IReadOnlyList<(TimeSpan, string)> AllPreviousGameRules => new List<(TimeSpan, string)>();
|
||||||
|
|
||||||
[ViewVariables] public IReadOnlyDictionary<NetEntity, Dictionary<ProtoId<JobPrototype>, int?>> JobsAvailable => _jobsAvailable;
|
[ViewVariables] public IReadOnlyDictionary<NetEntity, Dictionary<ProtoId<JobPrototype>, int?>> JobsAvailable => _jobsAvailable;
|
||||||
[ViewVariables] public IReadOnlyDictionary<NetEntity, string> StationNames => _stationNames;
|
[ViewVariables] public IReadOnlyDictionary<NetEntity, string> StationNames => _stationNames;
|
||||||
|
|
||||||
|
|||||||
103
Content.Server/GameTicking/Commands/DynamicRuleCommand.cs
Normal file
103
Content.Server/GameTicking/Commands/DynamicRuleCommand.cs
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Content.Server.Administration;
|
||||||
|
using Content.Server.GameTicking.Rules;
|
||||||
|
using Content.Shared.Administration;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Toolshed;
|
||||||
|
|
||||||
|
namespace Content.Server.GameTicking.Commands;
|
||||||
|
|
||||||
|
[ToolshedCommand, AdminCommand(AdminFlags.Round)]
|
||||||
|
public sealed class DynamicRuleCommand : ToolshedCommand
|
||||||
|
{
|
||||||
|
private DynamicRuleSystem? _dynamicRuleSystem;
|
||||||
|
|
||||||
|
[CommandImplementation("list")]
|
||||||
|
public IEnumerable<EntityUid> List()
|
||||||
|
{
|
||||||
|
_dynamicRuleSystem ??= GetSys<DynamicRuleSystem>();
|
||||||
|
|
||||||
|
return _dynamicRuleSystem.GetDynamicRules();
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandImplementation("get")]
|
||||||
|
public EntityUid Get()
|
||||||
|
{
|
||||||
|
_dynamicRuleSystem ??= GetSys<DynamicRuleSystem>();
|
||||||
|
|
||||||
|
return _dynamicRuleSystem.GetDynamicRules().FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandImplementation("budget")]
|
||||||
|
public IEnumerable<float?> Budget([PipedArgument] IEnumerable<EntityUid> input)
|
||||||
|
=> input.Select(Budget);
|
||||||
|
|
||||||
|
[CommandImplementation("budget")]
|
||||||
|
public float? Budget([PipedArgument] EntityUid input)
|
||||||
|
{
|
||||||
|
_dynamicRuleSystem ??= GetSys<DynamicRuleSystem>();
|
||||||
|
|
||||||
|
return _dynamicRuleSystem.GetRuleBudget(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandImplementation("adjust")]
|
||||||
|
public IEnumerable<float?> Adjust([PipedArgument] IEnumerable<EntityUid> input, float value)
|
||||||
|
=> input.Select(i => Adjust(i,value));
|
||||||
|
|
||||||
|
[CommandImplementation("adjust")]
|
||||||
|
public float? Adjust([PipedArgument] EntityUid input, float value)
|
||||||
|
{
|
||||||
|
_dynamicRuleSystem ??= GetSys<DynamicRuleSystem>();
|
||||||
|
|
||||||
|
return _dynamicRuleSystem.AdjustBudget(input, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandImplementation("set")]
|
||||||
|
public IEnumerable<float?> Set([PipedArgument] IEnumerable<EntityUid> input, float value)
|
||||||
|
=> input.Select(i => Set(i,value));
|
||||||
|
|
||||||
|
[CommandImplementation("set")]
|
||||||
|
public float? Set([PipedArgument] EntityUid input, float value)
|
||||||
|
{
|
||||||
|
_dynamicRuleSystem ??= GetSys<DynamicRuleSystem>();
|
||||||
|
|
||||||
|
return _dynamicRuleSystem.SetBudget(input, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandImplementation("dryrun")]
|
||||||
|
public IEnumerable<IEnumerable<EntProtoId>> DryRun([PipedArgument] IEnumerable<EntityUid> input)
|
||||||
|
=> input.Select(DryRun);
|
||||||
|
|
||||||
|
[CommandImplementation("dryrun")]
|
||||||
|
public IEnumerable<EntProtoId> DryRun([PipedArgument] EntityUid input)
|
||||||
|
{
|
||||||
|
_dynamicRuleSystem ??= GetSys<DynamicRuleSystem>();
|
||||||
|
|
||||||
|
return _dynamicRuleSystem.DryRun(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandImplementation("executenow")]
|
||||||
|
public IEnumerable<IEnumerable<EntityUid>> ExecuteNow([PipedArgument] IEnumerable<EntityUid> input)
|
||||||
|
=> input.Select(ExecuteNow);
|
||||||
|
|
||||||
|
[CommandImplementation("executenow")]
|
||||||
|
public IEnumerable<EntityUid> ExecuteNow([PipedArgument] EntityUid input)
|
||||||
|
{
|
||||||
|
_dynamicRuleSystem ??= GetSys<DynamicRuleSystem>();
|
||||||
|
|
||||||
|
return _dynamicRuleSystem.ExecuteNow(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandImplementation("rules")]
|
||||||
|
public IEnumerable<IEnumerable<EntityUid>> Rules([PipedArgument] IEnumerable<EntityUid> input)
|
||||||
|
=> input.Select(Rules);
|
||||||
|
|
||||||
|
[CommandImplementation("rules")]
|
||||||
|
public IEnumerable<EntityUid> Rules([PipedArgument] EntityUid input)
|
||||||
|
{
|
||||||
|
_dynamicRuleSystem ??= GetSys<DynamicRuleSystem>();
|
||||||
|
|
||||||
|
return _dynamicRuleSystem.Rules(input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ public sealed partial class GameTicker
|
|||||||
/// A list storing the start times of all game rules that have been started this round.
|
/// A list storing the start times of all game rules that have been started this round.
|
||||||
/// Game rules can be started and stopped at any time, including midround.
|
/// Game rules can be started and stopped at any time, including midround.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IReadOnlyList<(TimeSpan, string)> AllPreviousGameRules => _allPreviousGameRules;
|
public override IReadOnlyList<(TimeSpan, string)> AllPreviousGameRules => _allPreviousGameRules;
|
||||||
|
|
||||||
private void InitializeGameRules()
|
private void InitializeGameRules()
|
||||||
{
|
{
|
||||||
|
|||||||
195
Content.Server/GameTicking/Rules/DynamicRuleSystem.cs
Normal file
195
Content.Server/GameTicking/Rules/DynamicRuleSystem.cs
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using Content.Server.Administration.Logs;
|
||||||
|
using Content.Server.RoundEnd;
|
||||||
|
using Content.Shared.Database;
|
||||||
|
using Content.Shared.EntityTable;
|
||||||
|
using Content.Shared.EntityTable.Conditions;
|
||||||
|
using Content.Shared.GameTicking.Components;
|
||||||
|
using Content.Shared.GameTicking.Rules;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
|
namespace Content.Server.GameTicking.Rules;
|
||||||
|
|
||||||
|
public sealed class DynamicRuleSystem : GameRuleSystem<DynamicRuleComponent>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IAdminLogManager _adminLog = default!;
|
||||||
|
[Dependency] private readonly EntityTableSystem _entityTable = default!;
|
||||||
|
[Dependency] private readonly RoundEndSystem _roundEnd = default!;
|
||||||
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
|
|
||||||
|
protected override void Added(EntityUid uid, DynamicRuleComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args)
|
||||||
|
{
|
||||||
|
base.Added(uid, component, gameRule, args);
|
||||||
|
|
||||||
|
component.Budget = _random.Next(component.StartingBudgetMin, component.StartingBudgetMax);;
|
||||||
|
component.NextRuleTime = Timing.CurTime + _random.Next(component.MinRuleInterval, component.MaxRuleInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Started(EntityUid uid, DynamicRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||||
|
{
|
||||||
|
base.Started(uid, component, gameRule, args);
|
||||||
|
|
||||||
|
// Since we don't know how long until this rule is activated, we need to
|
||||||
|
// set the last budget update to now so it doesn't immediately give the component a bunch of points.
|
||||||
|
component.LastBudgetUpdate = Timing.CurTime;
|
||||||
|
Execute((uid, component));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Ended(EntityUid uid, DynamicRuleComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args)
|
||||||
|
{
|
||||||
|
base.Ended(uid, component, gameRule, args);
|
||||||
|
|
||||||
|
foreach (var rule in component.Rules)
|
||||||
|
{
|
||||||
|
GameTicker.EndGameRule(rule);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ActiveTick(EntityUid uid, DynamicRuleComponent component, GameRuleComponent gameRule, float frameTime)
|
||||||
|
{
|
||||||
|
base.ActiveTick(uid, component, gameRule, frameTime);
|
||||||
|
|
||||||
|
if (Timing.CurTime < component.NextRuleTime)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// don't spawn antags during evac
|
||||||
|
if (_roundEnd.IsRoundEndRequested())
|
||||||
|
return;
|
||||||
|
|
||||||
|
Execute((uid, component));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates and returns a list of randomly selected,
|
||||||
|
/// valid rules to spawn based on <see cref="DynamicRuleComponent.Table"/>.
|
||||||
|
/// </summary>
|
||||||
|
private IEnumerable<EntProtoId> GetRuleSpawns(Entity<DynamicRuleComponent> entity)
|
||||||
|
{
|
||||||
|
UpdateBudget((entity.Owner, entity.Comp));
|
||||||
|
var ctx = new EntityTableContext(new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ HasBudgetCondition.BudgetContextKey, entity.Comp.Budget },
|
||||||
|
});
|
||||||
|
|
||||||
|
return _entityTable.GetSpawns(entity.Comp.Table, ctx: ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the budget of the provided dynamic rule component based on the amount of time since the last update
|
||||||
|
/// multiplied by the <see cref="DynamicRuleComponent.BudgetPerSecond"/> value.
|
||||||
|
/// </summary>
|
||||||
|
private void UpdateBudget(Entity<DynamicRuleComponent> entity)
|
||||||
|
{
|
||||||
|
var duration = (float) (Timing.CurTime - entity.Comp.LastBudgetUpdate).TotalSeconds;
|
||||||
|
|
||||||
|
entity.Comp.Budget += duration * entity.Comp.BudgetPerSecond;
|
||||||
|
entity.Comp.LastBudgetUpdate = Timing.CurTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Executes this rule, generating new dynamic rules and starting them.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// Returns a list of the rules that were executed.
|
||||||
|
/// </returns>
|
||||||
|
private List<EntityUid> Execute(Entity<DynamicRuleComponent> entity)
|
||||||
|
{
|
||||||
|
entity.Comp.NextRuleTime =
|
||||||
|
Timing.CurTime + _random.Next(entity.Comp.MinRuleInterval, entity.Comp.MaxRuleInterval);
|
||||||
|
|
||||||
|
var executedRules = new List<EntityUid>();
|
||||||
|
|
||||||
|
foreach (var rule in GetRuleSpawns(entity))
|
||||||
|
{
|
||||||
|
var res = GameTicker.StartGameRule(rule, out var ruleUid);
|
||||||
|
Debug.Assert(res);
|
||||||
|
|
||||||
|
executedRules.Add(ruleUid);
|
||||||
|
|
||||||
|
if (TryComp<DynamicRuleCostComponent>(ruleUid, out var cost))
|
||||||
|
{
|
||||||
|
entity.Comp.Budget -= cost.Cost;
|
||||||
|
_adminLog.Add(LogType.EventRan, LogImpact.High, $"{ToPrettyString(entity)} ran rule {ToPrettyString(ruleUid)} with cost {cost.Cost} on budget {entity.Comp.Budget}.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_adminLog.Add(LogType.EventRan, LogImpact.High, $"{ToPrettyString(entity)} ran rule {ToPrettyString(ruleUid)} which had no cost.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
entity.Comp.Rules.AddRange(executedRules);
|
||||||
|
return executedRules;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Command Methods
|
||||||
|
|
||||||
|
public List<EntityUid> GetDynamicRules()
|
||||||
|
{
|
||||||
|
var rules = new List<EntityUid>();
|
||||||
|
var query = EntityQueryEnumerator<DynamicRuleComponent, GameRuleComponent>();
|
||||||
|
while (query.MoveNext(out var uid, out _, out var comp))
|
||||||
|
{
|
||||||
|
if (!GameTicker.IsGameRuleActive(uid, comp))
|
||||||
|
continue;
|
||||||
|
rules.Add(uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rules;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float? GetRuleBudget(Entity<DynamicRuleComponent?> entity)
|
||||||
|
{
|
||||||
|
if (!Resolve(entity, ref entity.Comp))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
UpdateBudget((entity.Owner, entity.Comp));
|
||||||
|
return entity.Comp.Budget;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float? AdjustBudget(Entity<DynamicRuleComponent?> entity, float amount)
|
||||||
|
{
|
||||||
|
if (!Resolve(entity, ref entity.Comp))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
UpdateBudget((entity.Owner, entity.Comp));
|
||||||
|
entity.Comp.Budget += amount;
|
||||||
|
return entity.Comp.Budget;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float? SetBudget(Entity<DynamicRuleComponent?> entity, float amount)
|
||||||
|
{
|
||||||
|
if (!Resolve(entity, ref entity.Comp))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
entity.Comp.LastBudgetUpdate = Timing.CurTime;
|
||||||
|
entity.Comp.Budget = amount;
|
||||||
|
return entity.Comp.Budget;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<EntProtoId> DryRun(Entity<DynamicRuleComponent?> entity)
|
||||||
|
{
|
||||||
|
if (!Resolve(entity, ref entity.Comp))
|
||||||
|
return new List<EntProtoId>();
|
||||||
|
|
||||||
|
return GetRuleSpawns((entity.Owner, entity.Comp));
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<EntityUid> ExecuteNow(Entity<DynamicRuleComponent?> entity)
|
||||||
|
{
|
||||||
|
if (!Resolve(entity, ref entity.Comp))
|
||||||
|
return new List<EntityUid>();
|
||||||
|
|
||||||
|
return Execute((entity.Owner, entity.Comp));
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<EntityUid> Rules(Entity<DynamicRuleComponent?> entity)
|
||||||
|
{
|
||||||
|
if (!Resolve(entity, ref entity.Comp))
|
||||||
|
return new List<EntityUid>();
|
||||||
|
|
||||||
|
return entity.Comp.Rules;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
51
Content.Shared/EntityTable/Conditions/HasBudgetCondition.cs
Normal file
51
Content.Shared/EntityTable/Conditions/HasBudgetCondition.cs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
using Content.Shared.EntityTable.EntitySelectors;
|
||||||
|
using Content.Shared.GameTicking.Rules;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Shared.EntityTable.Conditions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Condition that only succeeds if a table supplies a sufficient "cost" to a given
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class HasBudgetCondition : EntityTableCondition
|
||||||
|
{
|
||||||
|
public const string BudgetContextKey = "Budget";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used for determining the cost for the budget.
|
||||||
|
/// If null, attempts to fetch the cost from the attached selector.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public int? CostOverride;
|
||||||
|
|
||||||
|
protected override bool EvaluateImplementation(EntityTableSelector root,
|
||||||
|
IEntityManager entMan,
|
||||||
|
IPrototypeManager proto,
|
||||||
|
EntityTableContext ctx)
|
||||||
|
{
|
||||||
|
if (!ctx.TryGetData<float>(BudgetContextKey, out var budget))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int cost;
|
||||||
|
if (CostOverride != null)
|
||||||
|
{
|
||||||
|
cost = CostOverride.Value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (root is not EntSelector entSelector)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!proto.Index(entSelector.Id).TryGetComponent(out DynamicRuleCostComponent? costComponent, entMan.ComponentFactory))
|
||||||
|
{
|
||||||
|
var log = Logger.GetSawmill("HasBudgetCondition");
|
||||||
|
log.Error($"Rule {entSelector.Id} does not have a DynamicRuleCostComponent.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cost = costComponent.Cost;
|
||||||
|
}
|
||||||
|
|
||||||
|
return budget >= cost;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Content.Shared.EntityTable.EntitySelectors;
|
||||||
|
using Content.Shared.GameTicking;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Shared.EntityTable.Conditions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Condition that succeeds only when the specified gamerule has been run under a certain amount of times
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is meant to be attached directly to EntSelector. If it is not, then you'll need to specify what rule
|
||||||
|
/// is being used inside RuleOverride.
|
||||||
|
/// </remarks>
|
||||||
|
public sealed partial class MaxRuleOccurenceCondition : EntityTableCondition
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum amount of times this rule can have already be run.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public int Max = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The rule that is being checked for occurrences.
|
||||||
|
/// If null, it will use the value on the attached selector.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public EntProtoId? RuleOverride;
|
||||||
|
|
||||||
|
protected override bool EvaluateImplementation(EntityTableSelector root,
|
||||||
|
IEntityManager entMan,
|
||||||
|
IPrototypeManager proto,
|
||||||
|
EntityTableContext ctx)
|
||||||
|
{
|
||||||
|
string rule;
|
||||||
|
if (RuleOverride is { } ruleOverride)
|
||||||
|
{
|
||||||
|
rule = ruleOverride;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rule = root is EntSelector entSelector
|
||||||
|
? entSelector.Id
|
||||||
|
: string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rule == string.Empty)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var gameTicker = entMan.System<SharedGameTicker>();
|
||||||
|
|
||||||
|
return gameTicker.AllPreviousGameRules.Count(p => p.Item2 == rule) < Max;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Linq;
|
||||||
|
using Content.Shared.EntityTable.EntitySelectors;
|
||||||
|
using Content.Shared.GameTicking;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Shared.EntityTable.Conditions;
|
||||||
|
|
||||||
|
public sealed partial class ReoccurrenceDelayCondition : EntityTableCondition
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum amount of times this rule can have already be run.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public TimeSpan Delay = TimeSpan.Zero;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The rule that is being checked for occurrences.
|
||||||
|
/// If null, it will use the value on the attached selector.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public EntProtoId? RuleOverride;
|
||||||
|
|
||||||
|
protected override bool EvaluateImplementation(EntityTableSelector root,
|
||||||
|
IEntityManager entMan,
|
||||||
|
IPrototypeManager proto,
|
||||||
|
EntityTableContext ctx)
|
||||||
|
{
|
||||||
|
string rule;
|
||||||
|
if (RuleOverride is { } ruleOverride)
|
||||||
|
{
|
||||||
|
rule = ruleOverride;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rule = root is EntSelector entSelector
|
||||||
|
? entSelector.Id
|
||||||
|
: string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rule == string.Empty)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var gameTicker = entMan.System<SharedGameTicker>();
|
||||||
|
|
||||||
|
return gameTicker.AllPreviousGameRules.Any(
|
||||||
|
p => p.Item2 == rule && p.Item1 + Delay <= gameTicker.RoundDuration());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using Content.Shared.EntityTable.EntitySelectors;
|
||||||
|
using Content.Shared.GameTicking;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Shared.EntityTable.Conditions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Condition that passes only if the current round time falls between the minimum and maximum time values.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class RoundDurationCondition : EntityTableCondition
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Minimum time the round must have gone on for this condition to pass.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public TimeSpan Min = TimeSpan.Zero;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum amount of time the round could go on for this condition to pass.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public TimeSpan Max = TimeSpan.MaxValue;
|
||||||
|
|
||||||
|
protected override bool EvaluateImplementation(EntityTableSelector root,
|
||||||
|
IEntityManager entMan,
|
||||||
|
IPrototypeManager proto,
|
||||||
|
EntityTableContext ctx)
|
||||||
|
{
|
||||||
|
var gameTicker = entMan.System<SharedGameTicker>();
|
||||||
|
var duration = gameTicker.RoundDuration();
|
||||||
|
|
||||||
|
return duration >= Min && duration <= Max;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,6 +26,9 @@ public sealed partial class GroupSelector : EntityTableSelector
|
|||||||
children.Add(child, child.Weight);
|
children.Add(child, child.Weight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (children.Count == 0)
|
||||||
|
return Array.Empty<EntProtoId>();
|
||||||
|
|
||||||
var pick = SharedRandomExtensions.Pick(children, rand);
|
var pick = SharedRandomExtensions.Pick(children, rand);
|
||||||
|
|
||||||
return pick.GetSpawns(rand, entMan, proto, ctx);
|
return pick.GetSpawns(rand, entMan, proto, ctx);
|
||||||
|
|||||||
71
Content.Shared/GameTicking/Rules/DynamicRuleComponent.cs
Normal file
71
Content.Shared/GameTicking/Rules/DynamicRuleComponent.cs
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
using Content.Shared.EntityTable.EntitySelectors;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||||
|
|
||||||
|
namespace Content.Shared.GameTicking.Rules;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gamerule the spawns multiple antags at intervals based on a budget
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent, AutoGenerateComponentPause]
|
||||||
|
public sealed partial class DynamicRuleComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The total budget for antags.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public float Budget;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The last time budget was updated.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public TimeSpan LastBudgetUpdate;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The amount of budget accumulated every second.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public float BudgetPerSecond = 0.1f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The minimum or lower bound for budgets to start at.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public int StartingBudgetMin = 200;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum or upper bound for budgets to start at.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public int StartingBudgetMax = 350;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The time at which the next rule will start
|
||||||
|
/// </summary>
|
||||||
|
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
|
||||||
|
public TimeSpan NextRuleTime;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Minimum delay between rules
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public TimeSpan MinRuleInterval = TimeSpan.FromMinutes(10);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum delay between rules
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public TimeSpan MaxRuleInterval = TimeSpan.FromMinutes(30);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A table of rules that are picked from.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public EntityTableSelector Table = new NoneSelector();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The rules that have been spawned
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public List<EntityUid> Rules = new();
|
||||||
|
}
|
||||||
14
Content.Shared/GameTicking/Rules/DynamicRuleCostComponent.cs
Normal file
14
Content.Shared/GameTicking/Rules/DynamicRuleCostComponent.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
namespace Content.Shared.GameTicking.Rules;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Component that tracks how much a rule "costs" for Dynamic
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed partial class DynamicRuleCostComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The amount of budget a rule takes up
|
||||||
|
/// </summary>
|
||||||
|
[DataField(required: true)]
|
||||||
|
public int Cost;
|
||||||
|
}
|
||||||
@@ -15,6 +15,12 @@ namespace Content.Shared.GameTicking
|
|||||||
[Dependency] private readonly IReplayRecordingManager _replay = default!;
|
[Dependency] private readonly IReplayRecordingManager _replay = default!;
|
||||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A list storing the start times of all game rules that have been started this round.
|
||||||
|
/// Game rules can be started and stopped at any time, including midround.
|
||||||
|
/// </summary>
|
||||||
|
public abstract IReadOnlyList<(TimeSpan, string)> AllPreviousGameRules { get; }
|
||||||
|
|
||||||
// See ideally these would be pulled from the job definition or something.
|
// See ideally these would be pulled from the job definition or something.
|
||||||
// But this is easier, and at least it isn't hardcoded.
|
// But this is easier, and at least it isn't hardcoded.
|
||||||
//TODO: Move these, they really belong in StationJobsSystem or a cvar.
|
//TODO: Move these, they really belong in StationJobsSystem or a cvar.
|
||||||
|
|||||||
@@ -106,3 +106,19 @@ command-description-scale-multiplyvector =
|
|||||||
Multiply an entity's sprite size with a certain 2d vector (without changing its fixture).
|
Multiply an entity's sprite size with a certain 2d vector (without changing its fixture).
|
||||||
command-description-scale-multiplywithfixture =
|
command-description-scale-multiplywithfixture =
|
||||||
Multiply an entity's sprite size with a certain factor (including its fixture).
|
Multiply an entity's sprite size with a certain factor (including its fixture).
|
||||||
|
command-description-dynamicrule-list =
|
||||||
|
Lists all currently active dynamic rules, usually this is just one.
|
||||||
|
command-description-dynamicrule-get =
|
||||||
|
Gets the currently active dynamic rule.
|
||||||
|
command-description-dynamicrule-budget =
|
||||||
|
Gets the current budget of the piped dynamic rule(s).
|
||||||
|
command-description-dynamicrule-adjust =
|
||||||
|
Adjusts the budget of the piped dynamic rule(s) by the specified amount.
|
||||||
|
command-description-dynamicrule-set =
|
||||||
|
Sets the budget of the piped dynamic rule(s) to the specified amount.
|
||||||
|
command-description-dynamicrule-dryrun =
|
||||||
|
Returns a list of rules that could be activated if the rule ran at this moment with all current context. This is not a complete list of every single rule that could be run, just a sample of the current valid ones.
|
||||||
|
command-description-dynamicrule-executenow =
|
||||||
|
Executes the piped dynamic rule as if it had reached its regular update time.
|
||||||
|
command-description-dynamicrule-rules =
|
||||||
|
Gets a list of all the rules spawned by the piped dynamic rule.
|
||||||
|
|||||||
@@ -1,2 +1,5 @@
|
|||||||
secret-title = Secret
|
secret-title = Secret
|
||||||
secret-description = It's a secret to everyone. The threats you encounter are randomized.
|
secret-description = It's a secret to everyone. The threats you encounter are randomized.
|
||||||
|
|
||||||
|
dynamic-title = Dynamic
|
||||||
|
dynamic-description = No one knows what's coming. You can encounter any number of threats.
|
||||||
|
|||||||
112
Resources/Prototypes/GameRules/dynamic_rules.yml
Normal file
112
Resources/Prototypes/GameRules/dynamic_rules.yml
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
- type: entity
|
||||||
|
parent: BaseGameRule
|
||||||
|
id: DynamicRule
|
||||||
|
components:
|
||||||
|
- type: GameRule
|
||||||
|
minPlayers: 5 # <5 is greenshift hours, buddy.
|
||||||
|
- type: DynamicRule
|
||||||
|
startingBudgetMin: 200
|
||||||
|
startingBudgetMax: 350
|
||||||
|
table: !type:AllSelector
|
||||||
|
children:
|
||||||
|
# Roundstart Major Rules
|
||||||
|
- !type:GroupSelector
|
||||||
|
conditions:
|
||||||
|
- !type:RoundDurationCondition
|
||||||
|
max: 1
|
||||||
|
children:
|
||||||
|
- id: Traitor
|
||||||
|
weight: 60
|
||||||
|
conditions:
|
||||||
|
- !type:HasBudgetCondition
|
||||||
|
- !type:MaxRuleOccurenceCondition
|
||||||
|
- id: Nukeops
|
||||||
|
weight: 25
|
||||||
|
conditions:
|
||||||
|
- !type:HasBudgetCondition
|
||||||
|
- !type:MaxRuleOccurenceCondition
|
||||||
|
- !type:PlayerCountCondition
|
||||||
|
min: 20
|
||||||
|
- id: Revolutionary
|
||||||
|
weight: 5
|
||||||
|
conditions:
|
||||||
|
- !type:HasBudgetCondition
|
||||||
|
- !type:MaxRuleOccurenceCondition
|
||||||
|
- id: Zombie
|
||||||
|
weight: 5
|
||||||
|
conditions:
|
||||||
|
- !type:HasBudgetCondition
|
||||||
|
- !type:MaxRuleOccurenceCondition
|
||||||
|
- !type:PlayerCountCondition
|
||||||
|
min: 20
|
||||||
|
- id: Wizard
|
||||||
|
weight: 5
|
||||||
|
conditions:
|
||||||
|
- !type:HasBudgetCondition
|
||||||
|
- !type:MaxRuleOccurenceCondition
|
||||||
|
- !type:PlayerCountCondition
|
||||||
|
min: 10
|
||||||
|
# Roundstart Minor Rules
|
||||||
|
- !type:GroupSelector
|
||||||
|
conditions:
|
||||||
|
- !type:RoundDurationCondition
|
||||||
|
max: 1
|
||||||
|
children:
|
||||||
|
- id: Thief
|
||||||
|
prob: 0.5
|
||||||
|
conditions:
|
||||||
|
- !type:HasBudgetCondition
|
||||||
|
- !type:MaxRuleOccurenceCondition
|
||||||
|
# Midround rules
|
||||||
|
- !type:GroupSelector
|
||||||
|
conditions:
|
||||||
|
- !type:RoundDurationCondition
|
||||||
|
min: 300 # minimum 5 minutes
|
||||||
|
children:
|
||||||
|
- id: SleeperAgents
|
||||||
|
weight: 15
|
||||||
|
conditions:
|
||||||
|
- !type:HasBudgetCondition
|
||||||
|
- !type:MaxRuleOccurenceCondition
|
||||||
|
- !type:RoundDurationCondition
|
||||||
|
min: 900 # 15 minutes
|
||||||
|
- id: DragonSpawn
|
||||||
|
weight: 15
|
||||||
|
conditions:
|
||||||
|
- !type:HasBudgetCondition
|
||||||
|
- !type:MaxRuleOccurenceCondition
|
||||||
|
- !type:RoundDurationCondition
|
||||||
|
min: 900 # 15 minutes
|
||||||
|
- id: NinjaSpawn
|
||||||
|
weight: 20
|
||||||
|
conditions:
|
||||||
|
- !type:HasBudgetCondition
|
||||||
|
- !type:MaxRuleOccurenceCondition
|
||||||
|
- !type:RoundDurationCondition
|
||||||
|
min: 900 # 15 minutes
|
||||||
|
- id: ParadoxCloneSpawn
|
||||||
|
weight: 25
|
||||||
|
conditions:
|
||||||
|
- !type:HasBudgetCondition
|
||||||
|
- !type:MaxRuleOccurenceCondition
|
||||||
|
max: 2
|
||||||
|
- !type:RoundDurationCondition
|
||||||
|
min: 600 # 10 minutes
|
||||||
|
- id: ZombieOutbreak
|
||||||
|
weight: 2.5
|
||||||
|
conditions:
|
||||||
|
- !type:HasBudgetCondition
|
||||||
|
- !type:MaxRuleOccurenceCondition
|
||||||
|
- !type:PlayerCountCondition
|
||||||
|
min: 20
|
||||||
|
- !type:RoundDurationCondition
|
||||||
|
min: 2700 # 45 minutes
|
||||||
|
- id: LoneOpsSpawn
|
||||||
|
weight: 5
|
||||||
|
conditions:
|
||||||
|
- !type:HasBudgetCondition
|
||||||
|
- !type:MaxRuleOccurenceCondition
|
||||||
|
- !type:PlayerCountCondition
|
||||||
|
min: 20
|
||||||
|
- !type:RoundDurationCondition
|
||||||
|
min: 2100 # 35 minutes
|
||||||
@@ -30,15 +30,20 @@
|
|||||||
table: !type:AllSelector # we need to pass a list of rules, since rules have further restrictions to consider via StationEventComp
|
table: !type:AllSelector # we need to pass a list of rules, since rules have further restrictions to consider via StationEventComp
|
||||||
children:
|
children:
|
||||||
- id: ClosetSkeleton
|
- id: ClosetSkeleton
|
||||||
- id: DragonSpawn
|
|
||||||
- id: KingRatMigration
|
- id: KingRatMigration
|
||||||
|
- id: RevenantSpawn
|
||||||
|
- id: DerelictCyborgSpawn
|
||||||
|
|
||||||
|
- type: entityTable
|
||||||
|
id: ModerateAntagEventsTable
|
||||||
|
table: !type:AllSelector # we need to pass a list of rules, since rules have further restrictions to consider via StationEventComp
|
||||||
|
children:
|
||||||
|
- id: DragonSpawn
|
||||||
- id: NinjaSpawn
|
- id: NinjaSpawn
|
||||||
- id: ParadoxCloneSpawn
|
- id: ParadoxCloneSpawn
|
||||||
- id: RevenantSpawn
|
|
||||||
- id: SleeperAgents
|
- id: SleeperAgents
|
||||||
- id: ZombieOutbreak
|
- id: ZombieOutbreak
|
||||||
- id: LoneOpsSpawn
|
- id: LoneOpsSpawn
|
||||||
- id: DerelictCyborgSpawn
|
|
||||||
- id: WizardSpawn
|
- id: WizardSpawn
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
@@ -183,6 +188,8 @@
|
|||||||
pickPlayer: false
|
pickPlayer: false
|
||||||
mindRoles:
|
mindRoles:
|
||||||
- MindRoleDragon
|
- MindRoleDragon
|
||||||
|
- type: DynamicRuleCost
|
||||||
|
cost: 75
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: BaseGameRule
|
parent: BaseGameRule
|
||||||
@@ -233,6 +240,8 @@
|
|||||||
nameFormat: name-format-ninja
|
nameFormat: name-format-ninja
|
||||||
mindRoles:
|
mindRoles:
|
||||||
- MindRoleNinja
|
- MindRoleNinja
|
||||||
|
- type: DynamicRuleCost
|
||||||
|
cost: 75
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: BaseGameRule
|
parent: BaseGameRule
|
||||||
@@ -269,6 +278,8 @@
|
|||||||
sound: /Audio/Misc/paradox_clone_greeting.ogg
|
sound: /Audio/Misc/paradox_clone_greeting.ogg
|
||||||
mindRoles:
|
mindRoles:
|
||||||
- MindRoleParadoxClone
|
- MindRoleParadoxClone
|
||||||
|
- type: DynamicRuleCost
|
||||||
|
cost: 50
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: BaseGameRule
|
parent: BaseGameRule
|
||||||
@@ -520,6 +531,8 @@
|
|||||||
- type: InitialInfected
|
- type: InitialInfected
|
||||||
mindRoles:
|
mindRoles:
|
||||||
- MindRoleInitialInfected
|
- MindRoleInitialInfected
|
||||||
|
- type: DynamicRuleCost
|
||||||
|
cost: 200
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: BaseNukeopsRule
|
parent: BaseNukeopsRule
|
||||||
@@ -556,6 +569,8 @@
|
|||||||
- Syndicate
|
- Syndicate
|
||||||
mindRoles:
|
mindRoles:
|
||||||
- MindRoleNukeops
|
- MindRoleNukeops
|
||||||
|
- type: DynamicRuleCost
|
||||||
|
cost: 75
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: BaseTraitorRule
|
parent: BaseTraitorRule
|
||||||
|
|||||||
@@ -173,6 +173,8 @@
|
|||||||
- Syndicate
|
- Syndicate
|
||||||
mindRoles:
|
mindRoles:
|
||||||
- MindRoleNukeops
|
- MindRoleNukeops
|
||||||
|
- type: DynamicRuleCost
|
||||||
|
cost: 200
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
abstract: true
|
abstract: true
|
||||||
@@ -188,6 +190,8 @@
|
|||||||
maxDifficulty: 5
|
maxDifficulty: 5
|
||||||
- type: AntagSelection
|
- type: AntagSelection
|
||||||
agentName: traitor-round-end-agent-name
|
agentName: traitor-round-end-agent-name
|
||||||
|
- type: DynamicRuleCost
|
||||||
|
cost: 100
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: BaseTraitorRule
|
parent: BaseTraitorRule
|
||||||
@@ -207,7 +211,7 @@
|
|||||||
blacklist:
|
blacklist:
|
||||||
components:
|
components:
|
||||||
- AntagImmune
|
- AntagImmune
|
||||||
lateJoinAdditional: true
|
lateJoinAdditional: false
|
||||||
mindRoles:
|
mindRoles:
|
||||||
- MindRoleTraitor
|
- MindRoleTraitor
|
||||||
|
|
||||||
@@ -278,6 +282,8 @@
|
|||||||
- type: HeadRevolutionary
|
- type: HeadRevolutionary
|
||||||
mindRoles:
|
mindRoles:
|
||||||
- MindRoleHeadRevolutionary
|
- MindRoleHeadRevolutionary
|
||||||
|
- type: DynamicRuleCost
|
||||||
|
cost: 200
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
id: Sandbox
|
id: Sandbox
|
||||||
@@ -341,6 +347,8 @@
|
|||||||
nameFormat: name-format-wizard
|
nameFormat: name-format-wizard
|
||||||
mindRoles:
|
mindRoles:
|
||||||
- MindRoleWizard
|
- MindRoleWizard
|
||||||
|
- type: DynamicRuleCost
|
||||||
|
cost: 150
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
id: Zombie
|
id: Zombie
|
||||||
@@ -373,6 +381,8 @@
|
|||||||
- type: InitialInfected
|
- type: InitialInfected
|
||||||
mindRoles:
|
mindRoles:
|
||||||
- MindRoleInitialInfected
|
- MindRoleInitialInfected
|
||||||
|
- type: DynamicRuleCost
|
||||||
|
cost: 200
|
||||||
|
|
||||||
# This rule makes the chosen players unable to get other antag rules, as a way to prevent metagaming job rolls.
|
# This rule makes the chosen players unable to get other antag rules, as a way to prevent metagaming job rolls.
|
||||||
# Put this before antags assigned to station jobs, but after non-job antags (NukeOps/Wiz).
|
# Put this before antags assigned to station jobs, but after non-job antags (NukeOps/Wiz).
|
||||||
@@ -400,6 +410,8 @@
|
|||||||
tableId: BasicCalmEventsTable
|
tableId: BasicCalmEventsTable
|
||||||
- !type:NestedSelector
|
- !type:NestedSelector
|
||||||
tableId: BasicAntagEventsTable
|
tableId: BasicAntagEventsTable
|
||||||
|
- !type:NestedSelector
|
||||||
|
tableId: ModerateAntagEventsTable
|
||||||
- !type:NestedSelector
|
- !type:NestedSelector
|
||||||
tableId: CargoGiftsTable
|
tableId: CargoGiftsTable
|
||||||
- !type:NestedSelector
|
- !type:NestedSelector
|
||||||
@@ -407,6 +419,21 @@
|
|||||||
- !type:NestedSelector
|
- !type:NestedSelector
|
||||||
tableId: SpicyPestEventsTable
|
tableId: SpicyPestEventsTable
|
||||||
|
|
||||||
|
- type: entityTable
|
||||||
|
id: DynamicGameRulesTable
|
||||||
|
table: !type:AllSelector # we need to pass a list of rules, since rules have further restrictions to consider via StationEventComp
|
||||||
|
children:
|
||||||
|
- !type:NestedSelector
|
||||||
|
tableId: BasicCalmEventsTable
|
||||||
|
- !type:NestedSelector
|
||||||
|
tableId: BasicAntagEventsTable
|
||||||
|
- !type:NestedSelector
|
||||||
|
tableId: CargoGiftsTable
|
||||||
|
- !type:NestedSelector
|
||||||
|
tableId: CalmPestEventsTable
|
||||||
|
- !type:NestedSelector
|
||||||
|
tableId: SpicyPestEventsTable
|
||||||
|
|
||||||
- type: entityTable
|
- type: entityTable
|
||||||
id: SpaceTrafficControlTable
|
id: SpaceTrafficControlTable
|
||||||
table: !type:AllSelector # we need to pass a list of rules, since rules have further restrictions to consider via StationEventComp
|
table: !type:AllSelector # we need to pass a list of rules, since rules have further restrictions to consider via StationEventComp
|
||||||
@@ -426,6 +453,14 @@
|
|||||||
scheduledGameRules: !type:NestedSelector
|
scheduledGameRules: !type:NestedSelector
|
||||||
tableId: BasicGameRulesTable
|
tableId: BasicGameRulesTable
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: DynamicStationEventScheduler # this isn't the dynamic mode, but rather the station event scheduler used for dynamic
|
||||||
|
parent: BaseGameRule
|
||||||
|
components:
|
||||||
|
- type: BasicStationEventScheduler
|
||||||
|
scheduledGameRules: !type:NestedSelector
|
||||||
|
tableId: DynamicGameRulesTable
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
id: RampingStationEventScheduler
|
id: RampingStationEventScheduler
|
||||||
parent: BaseGameRule
|
parent: BaseGameRule
|
||||||
|
|||||||
@@ -34,6 +34,8 @@
|
|||||||
- MindRoleThief
|
- MindRoleThief
|
||||||
briefing:
|
briefing:
|
||||||
sound: "/Audio/Misc/thief_greeting.ogg"
|
sound: "/Audio/Misc/thief_greeting.ogg"
|
||||||
|
- type: DynamicRuleCost
|
||||||
|
cost: 75
|
||||||
|
|
||||||
# Needs testing
|
# Needs testing
|
||||||
- type: entity
|
- type: entity
|
||||||
|
|||||||
@@ -96,6 +96,23 @@
|
|||||||
- SpaceTrafficControlFriendlyEventScheduler
|
- SpaceTrafficControlFriendlyEventScheduler
|
||||||
- BasicRoundstartVariation
|
- BasicRoundstartVariation
|
||||||
|
|
||||||
|
- type: gamePreset
|
||||||
|
id: Dynamic
|
||||||
|
alias:
|
||||||
|
- dynamic
|
||||||
|
- multiantag
|
||||||
|
- director
|
||||||
|
name: dynamic-title
|
||||||
|
showInVote: true
|
||||||
|
description: dynamic-description
|
||||||
|
rules:
|
||||||
|
- DynamicRule
|
||||||
|
- DummyNonAntag
|
||||||
|
- DynamicStationEventScheduler
|
||||||
|
- MeteorSwarmScheduler
|
||||||
|
- SpaceTrafficControlEventScheduler
|
||||||
|
- BasicRoundstartVariation
|
||||||
|
|
||||||
- type: gamePreset
|
- type: gamePreset
|
||||||
id: Secret
|
id: Secret
|
||||||
alias:
|
alias:
|
||||||
|
|||||||
Reference in New Issue
Block a user