Gamerule Entities (#15513)
This commit is contained in:
@@ -1,12 +1,7 @@
|
||||
using System.Linq;
|
||||
using Content.Server.GameTicking.Rules;
|
||||
using Content.Server.GameTicking.Rules.Configurations;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.StationEvents.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.StationEvents
|
||||
@@ -16,53 +11,49 @@ namespace Content.Server.StationEvents
|
||||
/// game presets use.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed class BasicStationEventSchedulerSystem : GameRuleSystem
|
||||
public sealed class BasicStationEventSchedulerSystem : GameRuleSystem<BasicStationEventSchedulerComponent>
|
||||
{
|
||||
public override string Prototype => "BasicStationEventScheduler";
|
||||
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly EventManagerSystem _event = default!;
|
||||
|
||||
private const float MinimumTimeUntilFirstEvent = 300;
|
||||
|
||||
/// <summary>
|
||||
/// How long until the next check for an event runs
|
||||
/// </summary>
|
||||
/// Default value is how long until first event is allowed
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
private float _timeUntilNextEvent = MinimumTimeUntilFirstEvent;
|
||||
|
||||
public override void Started() { }
|
||||
|
||||
public override void Ended()
|
||||
protected override void Ended(EntityUid uid, BasicStationEventSchedulerComponent component, GameRuleComponent gameRule,
|
||||
GameRuleEndedEvent args)
|
||||
{
|
||||
_timeUntilNextEvent = MinimumTimeUntilFirstEvent;
|
||||
component.TimeUntilNextEvent = BasicStationEventSchedulerComponent.MinimumTimeUntilFirstEvent;
|
||||
}
|
||||
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
if (!RuleStarted || !_event.EventsEnabled)
|
||||
if (!_event.EventsEnabled)
|
||||
return;
|
||||
|
||||
if (_timeUntilNextEvent > 0)
|
||||
var query = EntityQueryEnumerator<BasicStationEventSchedulerComponent, GameRuleComponent>();
|
||||
while (query.MoveNext(out var uid, out var eventScheduler, out var gameRule))
|
||||
{
|
||||
_timeUntilNextEvent -= frameTime;
|
||||
return;
|
||||
}
|
||||
if (!GameTicker.IsGameRuleActive(uid, gameRule))
|
||||
continue;
|
||||
|
||||
_event.RunRandomEvent();
|
||||
ResetTimer();
|
||||
if (eventScheduler.TimeUntilNextEvent > 0)
|
||||
{
|
||||
eventScheduler.TimeUntilNextEvent -= frameTime;
|
||||
return;
|
||||
}
|
||||
|
||||
_event.RunRandomEvent();
|
||||
ResetTimer(eventScheduler);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset the event timer once the event is done.
|
||||
/// </summary>
|
||||
private void ResetTimer()
|
||||
private void ResetTimer(BasicStationEventSchedulerComponent component)
|
||||
{
|
||||
// 5 - 25 minutes. TG does 3-10 but that's pretty frequent
|
||||
_timeUntilNextEvent = _random.Next(300, 1500);
|
||||
component.TimeUntilNextEvent = _random.Next(300, 1500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
using Content.Server.StationEvents.Events;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Used an event that spawns an anomaly somewhere random on the map.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(AnomalySpawnRule))]
|
||||
public sealed class AnomalySpawnRuleComponent : Component
|
||||
{
|
||||
[DataField("anomalySpawnerPrototype", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string AnomalySpawnerPrototype = "RandomAnomalySpawner";
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(BasicStationEventSchedulerSystem))]
|
||||
public sealed class BasicStationEventSchedulerComponent : Component
|
||||
{
|
||||
public const float MinimumTimeUntilFirstEvent = 300;
|
||||
|
||||
/// <summary>
|
||||
/// How long until the next check for an event runs
|
||||
/// </summary>
|
||||
/// Default value is how long until first event is allowed
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float TimeUntilNextEvent = MinimumTimeUntilFirstEvent;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using Content.Server.StationEvents.Events;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for an event that spawns an artifact
|
||||
/// somewhere random on the station.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(BluespaceArtifactRule))]
|
||||
public sealed class BluespaceArtifactRuleComponent : Component
|
||||
{
|
||||
[DataField("artifactSpawnerPrototype", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string ArtifactSpawnerPrototype = "RandomArtifactSpawner";
|
||||
|
||||
[DataField("artifactFlashPrototype", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string ArtifactFlashPrototype = "EffectFlashBluespace";
|
||||
|
||||
[DataField("possibleSightings")]
|
||||
public List<string> PossibleSighting = new()
|
||||
{
|
||||
"bluespace-artifact-sighting-1",
|
||||
"bluespace-artifact-sighting-2",
|
||||
"bluespace-artifact-sighting-3",
|
||||
"bluespace-artifact-sighting-4",
|
||||
"bluespace-artifact-sighting-5",
|
||||
"bluespace-artifact-sighting-6",
|
||||
"bluespace-artifact-sighting-7"
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Content.Server.StationEvents.Events;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(BluespaceLockerRule))]
|
||||
public sealed class BluespaceLockerRuleComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Content.Server.StationEvents.Events;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(BreakerFlipRule))]
|
||||
public sealed class BreakerFlipRuleComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Content.Server.StationEvents.Events;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(BureaucraticErrorRule))]
|
||||
public sealed class BureaucraticErrorRuleComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using Content.Server.StationEvents.Events;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(DiseaseOutbreakRule))]
|
||||
public sealed class DiseaseOutbreakRuleComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Disease prototypes I decided were not too deadly for a random event
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Fire name
|
||||
/// </remarks>
|
||||
[DataField("notTooSeriousDiseases")]
|
||||
public readonly IReadOnlyList<string> NotTooSeriousDiseases = new[]
|
||||
{
|
||||
"SpaceCold",
|
||||
"VanAusdallsRobovirus",
|
||||
"VentCough",
|
||||
"AMIV",
|
||||
"SpaceFlu",
|
||||
"BirdFlew",
|
||||
"TongueTwister"
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Content.Server.StationEvents.Events;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(FalseAlarmRule))]
|
||||
public sealed class FalseAlarmRuleComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using Content.Server.StationEvents.Events;
|
||||
using Content.Shared.Atmos;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(GasLeakRule))]
|
||||
public sealed class GasLeakRuleComponent : Component
|
||||
{
|
||||
public readonly Gas[] LeakableGases =
|
||||
{
|
||||
Gas.Miasma,
|
||||
Gas.Plasma,
|
||||
Gas.Tritium,
|
||||
Gas.Frezon,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Running cooldown of how much time until another leak.
|
||||
/// </summary>
|
||||
public float TimeUntilLeak;
|
||||
|
||||
/// <summary>
|
||||
/// How long between more gas being added to the tile.
|
||||
/// </summary>
|
||||
public float LeakCooldown = 1.0f;
|
||||
|
||||
// Event variables
|
||||
public EntityUid TargetStation;
|
||||
public EntityUid TargetGrid;
|
||||
public Vector2i TargetTile;
|
||||
public EntityCoordinates TargetCoords;
|
||||
public bool FoundTile;
|
||||
public Gas LeakGas;
|
||||
public float MolesPerSecond;
|
||||
public readonly int MinimumMolesPerSecond = 20;
|
||||
|
||||
/// <summary>
|
||||
/// Don't want to make it too fast to give people time to flee.
|
||||
/// </summary>
|
||||
public int MaximumMolesPerSecond = 50;
|
||||
|
||||
public int MinimumGas = 250;
|
||||
public int MaximumGas = 1000;
|
||||
public float SparkChance = 0.05f;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Content.Server.StationEvents.Events;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(KudzuGrowthRule))]
|
||||
public sealed class KudzuGrowthRuleComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Content.Server.StationEvents.Events;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(LoneOpsSpawnRule))]
|
||||
public sealed class LoneOpsSpawnRuleComponent : Component
|
||||
{
|
||||
[DataField("loneOpsShuttlePath")]
|
||||
public string LoneOpsShuttlePath = "Maps/Shuttles/striker.yml";
|
||||
|
||||
[DataField("gameRuleProto", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string GameRuleProto = "Nukeops";
|
||||
|
||||
[DataField("additionalRule")]
|
||||
public EntityUid? AdditionalRule;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using Content.Server.StationEvents.Events;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(MeteorSwarmRule))]
|
||||
public sealed class MeteorSwarmRuleComponent : Component
|
||||
{
|
||||
public float _cooldown;
|
||||
|
||||
/// <summary>
|
||||
/// We'll send a specific amount of waves of meteors towards the station per ending rather than using a timer.
|
||||
/// </summary>
|
||||
public int _waveCounter;
|
||||
|
||||
public int MinimumWaves = 3;
|
||||
public int MaximumWaves = 8;
|
||||
|
||||
public float MinimumCooldown = 10f;
|
||||
public float MaximumCooldown = 60f;
|
||||
|
||||
public int MeteorsPerWave = 5;
|
||||
public float MeteorVelocity = 10f;
|
||||
public float MaxAngularVelocity = 0.25f;
|
||||
public float MinAngularVelocity = -0.25f;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Content.Server.StationEvents.Events;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(MouseMigrationRule))]
|
||||
public sealed class MouseMigrationRuleComponent : Component
|
||||
{
|
||||
[DataField("spawnedPrototypeChoices")]
|
||||
public List<string> SpawnedPrototypeChoices = new() //we double up for that ez fake probability
|
||||
{
|
||||
"MobMouse",
|
||||
"MobMouse1",
|
||||
"MobMouse2",
|
||||
"MobRatServant"
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.Threading;
|
||||
using Content.Server.StationEvents.Events;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(PowerGridCheckRule))]
|
||||
public sealed class PowerGridCheckRuleComponent : Component
|
||||
{
|
||||
public CancellationTokenSource? AnnounceCancelToken;
|
||||
|
||||
public readonly List<EntityUid> Powered = new();
|
||||
public readonly List<EntityUid> Unpowered = new();
|
||||
|
||||
public float SecondsUntilOff = 30.0f;
|
||||
|
||||
public int NumberPerSecond = 0;
|
||||
public float UpdateRate => 1.0f / NumberPerSecond;
|
||||
public float FrameTimeAccumulator = 0.0f;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(RampingStationEventSchedulerSystem))]
|
||||
public sealed class RampingStationEventSchedulerComponent : Component
|
||||
{
|
||||
[DataField("endTime"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public float EndTime;
|
||||
|
||||
[DataField("maxChaos"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public float MaxChaos;
|
||||
|
||||
[DataField("startingChaos"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public float StartingChaos;
|
||||
|
||||
[DataField("timeUntilNextEvent"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public float TimeUntilNextEvent;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Content.Server.StationEvents.Events;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(RandomSentienceRule))]
|
||||
public sealed class RandomSentienceRuleComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using Content.Server.StationEvents.Events;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(RevenantSpawnRule))]
|
||||
public sealed class RevenantSpawnRuleComponent : Component
|
||||
{
|
||||
[DataField("revenantPrototype")]
|
||||
public string RevenantPrototype = "MobRevenant";
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
using Content.Server.StationEvents.Events;
|
||||
|
||||
[RegisterComponent]
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(RandomSentienceRule))]
|
||||
public sealed class SentienceTargetComponent : Component
|
||||
{
|
||||
[DataField("flavorKind", required: true)]
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
using Content.Server.StationEvents.Events;
|
||||
using Content.Shared.Radio;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Solar Flare event specific configuration
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(SolarFlareRule))]
|
||||
public sealed class SolarFlareRuleComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// If true, only headsets affected, but e.g. handheld radio will still work
|
||||
/// </summary>
|
||||
[DataField("onlyJamHeadsets")]
|
||||
public bool OnlyJamHeadsets;
|
||||
|
||||
/// <summary>
|
||||
/// Channels that will be disabled for a duration of event
|
||||
/// </summary>
|
||||
[DataField("affectedChannels", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<RadioChannelPrototype>))]
|
||||
public readonly HashSet<string> AffectedChannels = new();
|
||||
|
||||
/// <summary>
|
||||
/// Chance light bulb breaks per second during event
|
||||
/// </summary>
|
||||
[DataField("lightBreakChancePerSecond")]
|
||||
public float LightBreakChancePerSecond;
|
||||
|
||||
/// <summary>
|
||||
/// Chance door toggles per second during event
|
||||
/// </summary>
|
||||
[DataField("doorToggleChancePerSecond")]
|
||||
public float DoorToggleChancePerSecond;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Content.Server.StationEvents.Events;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(SpiderSpawnRule))]
|
||||
public sealed class SpiderSpawnRuleComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Defines basic data for a station event
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class StationEventComponent : Component
|
||||
{
|
||||
public const float WeightVeryLow = 0.0f;
|
||||
public const float WeightLow = 5.0f;
|
||||
public const float WeightNormal = 10.0f;
|
||||
public const float WeightHigh = 15.0f;
|
||||
public const float WeightVeryHigh = 20.0f;
|
||||
|
||||
[DataField("weight")]
|
||||
public float Weight = WeightNormal;
|
||||
|
||||
[DataField("startAnnouncement")]
|
||||
public string? StartAnnouncement;
|
||||
|
||||
[DataField("endAnnouncement")]
|
||||
public string? EndAnnouncement;
|
||||
|
||||
[DataField("startAudio")]
|
||||
public SoundSpecifier? StartAudio;
|
||||
|
||||
[DataField("endAudio")]
|
||||
public SoundSpecifier? EndAudio;
|
||||
|
||||
/// <summary>
|
||||
/// In minutes, when is the first round time this event can start
|
||||
/// </summary>
|
||||
[DataField("earliestStart")]
|
||||
public int EarliestStart = 5;
|
||||
|
||||
/// <summary>
|
||||
/// In minutes, the amount of time before the same event can occur again
|
||||
/// </summary>
|
||||
[DataField("reoccurrenceDelay")]
|
||||
public int ReoccurrenceDelay = 30;
|
||||
|
||||
/// <summary>
|
||||
/// How long after being added does the event start
|
||||
/// </summary>
|
||||
[DataField("startDelay")]
|
||||
public TimeSpan StartDelay = TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// How long the event lasts.
|
||||
/// </summary>
|
||||
[DataField("duration")]
|
||||
public TimeSpan Duration = TimeSpan.FromSeconds(1);
|
||||
|
||||
/// <summary>
|
||||
/// The max amount of time the event lasts.
|
||||
/// </summary>
|
||||
[DataField("maxDuration")]
|
||||
public TimeSpan? MaxDuration;
|
||||
|
||||
/// <summary>
|
||||
/// How many players need to be present on station for the event to run
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// To avoid running deadly events with low-pop
|
||||
/// </remarks>
|
||||
[DataField("minimumPlayers")]
|
||||
public int MinimumPlayers;
|
||||
|
||||
/// <summary>
|
||||
/// How many times this even can occur in a single round
|
||||
/// </summary>
|
||||
[DataField("maxOccurrences")]
|
||||
public int? MaxOccurrences;
|
||||
|
||||
/// <summary>
|
||||
/// When the station event starts.
|
||||
/// </summary>
|
||||
[DataField("startTime", customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
public TimeSpan StartTime;
|
||||
|
||||
/// <summary>
|
||||
/// When the station event starts.
|
||||
/// </summary>
|
||||
[DataField("endTime", customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
public TimeSpan EndTime;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using Content.Server.StationEvents.Events;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(VentClogRule))]
|
||||
public sealed class VentClogRuleComponent : Component
|
||||
{
|
||||
[DataField("safeishVentChemicals")]
|
||||
public readonly IReadOnlyList<string> SafeishVentChemicals = new[]
|
||||
{
|
||||
"Water", "Blood", "Slime", "SpaceDrugs", "SpaceCleaner", "Nutriment", "Sugar", "SpaceLube", "Ephedrine", "Ale", "Beer"
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
using Content.Server.StationEvents.Events;
|
||||
|
||||
[RegisterComponent]
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(VentClogRule))]
|
||||
public sealed class VentCritterSpawnLocationComponent : Component
|
||||
{
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
using Content.Server.StationEvents.Events;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(VentCrittersRule))]
|
||||
public sealed class VentCrittersRuleComponent : Component
|
||||
{
|
||||
[DataField("spawnedPrototypeChoices")]
|
||||
public List<string> SpawnedPrototypeChoices = new()
|
||||
{
|
||||
"MobMouse",
|
||||
"MobMouse1",
|
||||
"MobMouse2"
|
||||
};
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
using System.Linq;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.GameTicking.Rules;
|
||||
using Content.Server.GameTicking.Rules.Configurations;
|
||||
using Content.Server.StationEvents.Components;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.GameTicking;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -31,6 +29,14 @@ public sealed class EventManagerSystem : EntitySystem
|
||||
_sawmill = Logger.GetSawmill("events");
|
||||
|
||||
_configurationManager.OnValueChanged(CCVars.EventsEnabled, SetEnabled, true);
|
||||
|
||||
SubscribeLocalEvent<StationEventComponent, EntityUnpausedEvent>(OnUnpaused);
|
||||
}
|
||||
|
||||
private void OnUnpaused(EntityUid uid, StationEventComponent component, ref EntityUnpausedEvent args)
|
||||
{
|
||||
component.StartTime += args.PausedTime;
|
||||
component.EndTime += args.PausedTime;
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
@@ -46,16 +52,15 @@ public sealed class EventManagerSystem : EntitySystem
|
||||
{
|
||||
var randomEvent = PickRandomEvent();
|
||||
|
||||
if (randomEvent == null
|
||||
|| !_prototype.TryIndex<GameRulePrototype>(randomEvent.Id, out var proto))
|
||||
if (randomEvent == null)
|
||||
{
|
||||
var errStr = Loc.GetString("station-event-system-run-random-event-no-valid-events");
|
||||
_sawmill.Error(errStr);
|
||||
return errStr;
|
||||
}
|
||||
|
||||
GameTicker.AddGameRule(proto);
|
||||
var str = Loc.GetString("station-event-system-run-event",("eventName", randomEvent.Id));
|
||||
var ent = GameTicker.AddGameRule(randomEvent);
|
||||
var str = Loc.GetString("station-event-system-run-event",("eventName", ToPrettyString(ent)));
|
||||
_sawmill.Info(str);
|
||||
return str;
|
||||
}
|
||||
@@ -63,7 +68,7 @@ public sealed class EventManagerSystem : EntitySystem
|
||||
/// <summary>
|
||||
/// Randomly picks a valid event.
|
||||
/// </summary>
|
||||
public StationEventRuleConfiguration? PickRandomEvent()
|
||||
public string? PickRandomEvent()
|
||||
{
|
||||
var availableEvents = AvailableEvents();
|
||||
_sawmill.Info($"Picking from {availableEvents.Count} total available events");
|
||||
@@ -74,7 +79,7 @@ public sealed class EventManagerSystem : EntitySystem
|
||||
/// Pick a random event from the available events at this time, also considering their weightings.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private StationEventRuleConfiguration? FindEvent(List<StationEventRuleConfiguration> availableEvents)
|
||||
private string? FindEvent(Dictionary<EntityPrototype, StationEventComponent> availableEvents)
|
||||
{
|
||||
if (availableEvents.Count == 0)
|
||||
{
|
||||
@@ -84,20 +89,20 @@ public sealed class EventManagerSystem : EntitySystem
|
||||
|
||||
var sumOfWeights = 0;
|
||||
|
||||
foreach (var stationEvent in availableEvents)
|
||||
foreach (var stationEvent in availableEvents.Values)
|
||||
{
|
||||
sumOfWeights += (int) stationEvent.Weight;
|
||||
}
|
||||
|
||||
sumOfWeights = _random.Next(sumOfWeights);
|
||||
|
||||
foreach (var stationEvent in availableEvents)
|
||||
foreach (var (proto, stationEvent) in availableEvents)
|
||||
{
|
||||
sumOfWeights -= (int) stationEvent.Weight;
|
||||
|
||||
if (sumOfWeights <= 0)
|
||||
{
|
||||
return stationEvent;
|
||||
return proto.ID;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,67 +115,73 @@ public sealed class EventManagerSystem : EntitySystem
|
||||
/// </summary>
|
||||
/// <param name="ignoreEarliestStart"></param>
|
||||
/// <returns></returns>
|
||||
private List<StationEventRuleConfiguration> AvailableEvents(bool ignoreEarliestStart = false)
|
||||
private Dictionary<EntityPrototype, StationEventComponent> AvailableEvents(bool ignoreEarliestStart = false)
|
||||
{
|
||||
TimeSpan currentTime;
|
||||
var playerCount = _playerManager.PlayerCount;
|
||||
|
||||
// playerCount does a lock so we'll just keep the variable here
|
||||
if (!ignoreEarliestStart)
|
||||
{
|
||||
currentTime = GameTicker.RoundDuration();
|
||||
}
|
||||
else
|
||||
{
|
||||
currentTime = TimeSpan.Zero;
|
||||
}
|
||||
var currentTime = !ignoreEarliestStart
|
||||
? GameTicker.RoundDuration()
|
||||
: TimeSpan.Zero;
|
||||
|
||||
var result = new List<StationEventRuleConfiguration>();
|
||||
var result = new Dictionary<EntityPrototype, StationEventComponent>();
|
||||
|
||||
foreach (var stationEvent in AllEvents())
|
||||
foreach (var (proto, stationEvent) in AllEvents())
|
||||
{
|
||||
if (CanRun(stationEvent, playerCount, currentTime))
|
||||
if (CanRun(proto, stationEvent, playerCount, currentTime))
|
||||
{
|
||||
_sawmill.Debug($"Adding event {stationEvent.Id} to possibilities");
|
||||
result.Add(stationEvent);
|
||||
_sawmill.Debug($"Adding event {proto.ID} to possibilities");
|
||||
result.Add(proto, stationEvent);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private IEnumerable<StationEventRuleConfiguration> AllEvents()
|
||||
public Dictionary<EntityPrototype, StationEventComponent> AllEvents()
|
||||
{
|
||||
return _prototype.EnumeratePrototypes<GameRulePrototype>()
|
||||
.Where(p => p.Configuration is StationEventRuleConfiguration)
|
||||
.Select(p => (StationEventRuleConfiguration) p.Configuration);
|
||||
var allEvents = new Dictionary<EntityPrototype, StationEventComponent>();
|
||||
foreach (var prototype in _prototype.EnumeratePrototypes<EntityPrototype>())
|
||||
{
|
||||
if (prototype.Abstract)
|
||||
continue;
|
||||
|
||||
if (!prototype.TryGetComponent<StationEventComponent>(out var stationEvent))
|
||||
continue;
|
||||
|
||||
allEvents.Add(prototype, stationEvent);
|
||||
}
|
||||
|
||||
return allEvents;
|
||||
}
|
||||
|
||||
private int GetOccurrences(StationEventRuleConfiguration stationEvent)
|
||||
private int GetOccurrences(EntityPrototype stationEvent)
|
||||
{
|
||||
return GameTicker.AllPreviousGameRules.Count(p => p.Item2.ID == stationEvent.Id);
|
||||
return GetOccurrences(stationEvent.ID);
|
||||
}
|
||||
|
||||
public TimeSpan TimeSinceLastEvent(StationEventRuleConfiguration? stationEvent)
|
||||
private int GetOccurrences(string stationEvent)
|
||||
{
|
||||
return GameTicker.AllPreviousGameRules.Count(p => p.Item2 == stationEvent);
|
||||
}
|
||||
|
||||
public TimeSpan TimeSinceLastEvent(EntityPrototype stationEvent)
|
||||
{
|
||||
foreach (var (time, rule) in GameTicker.AllPreviousGameRules.Reverse())
|
||||
{
|
||||
if (rule.Configuration is not StationEventRuleConfiguration)
|
||||
continue;
|
||||
|
||||
if (stationEvent == null || rule.ID == stationEvent.Id)
|
||||
if (rule == stationEvent.ID)
|
||||
return time;
|
||||
}
|
||||
|
||||
return TimeSpan.Zero;
|
||||
}
|
||||
|
||||
private bool CanRun(StationEventRuleConfiguration stationEvent, int playerCount, TimeSpan currentTime)
|
||||
private bool CanRun(EntityPrototype prototype, StationEventComponent stationEvent, int playerCount, TimeSpan currentTime)
|
||||
{
|
||||
if (GameTicker.IsGameRuleStarted(stationEvent.Id))
|
||||
if (GameTicker.IsGameRuleActive(prototype.ID))
|
||||
return false;
|
||||
|
||||
if (stationEvent.MaxOccurrences.HasValue && GetOccurrences(stationEvent) >= stationEvent.MaxOccurrences.Value)
|
||||
if (stationEvent.MaxOccurrences.HasValue && GetOccurrences(prototype) >= stationEvent.MaxOccurrences.Value)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -185,7 +196,7 @@ public sealed class EventManagerSystem : EntitySystem
|
||||
return false;
|
||||
}
|
||||
|
||||
var lastRun = TimeSinceLastEvent(stationEvent);
|
||||
var lastRun = TimeSinceLastEvent(prototype);
|
||||
if (lastRun != TimeSpan.Zero && currentTime.TotalMinutes <
|
||||
stationEvent.ReoccurrenceDelay + lastRun.TotalMinutes)
|
||||
{
|
||||
|
||||
@@ -1,31 +1,28 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Anomaly;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Server.StationEvents.Components;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
public sealed class AnomalySpawn : StationEventSystem
|
||||
public sealed class AnomalySpawnRule : StationEventSystem<AnomalySpawnRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly AnomalySystem _anomaly = default!;
|
||||
|
||||
public override string Prototype => "AnomalySpawn";
|
||||
|
||||
public readonly string AnomalySpawnerPrototype = "RandomAnomalySpawner";
|
||||
|
||||
public override void Added()
|
||||
protected override void Added(EntityUid uid, AnomalySpawnRuleComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args)
|
||||
{
|
||||
base.Added();
|
||||
base.Added(uid, component, gameRule, args);
|
||||
|
||||
var str = Loc.GetString("anomaly-spawn-event-announcement",
|
||||
("sighting", Loc.GetString($"anomaly-spawn-sighting-{_random.Next(1, 6)}")));
|
||||
("sighting", Loc.GetString($"anomaly-spawn-sighting-{RobustRandom.Next(1, 6)}")));
|
||||
ChatSystem.DispatchGlobalAnnouncement(str, colorOverride: Color.FromHex("#18abf5"));
|
||||
}
|
||||
|
||||
public override void Started()
|
||||
protected override void Started(EntityUid uid, AnomalySpawnRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started();
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
if (StationSystem.Stations.Count == 0)
|
||||
return; // No stations
|
||||
@@ -45,7 +42,7 @@ public sealed class AnomalySpawn : StationEventSystem
|
||||
var amountToSpawn = Math.Max(1, (int) MathF.Round(GetSeverityModifier() / 2));
|
||||
for (var i = 0; i < amountToSpawn; i++)
|
||||
{
|
||||
_anomaly.SpawnOnRandomGridLocation(grid.Value, AnomalySpawnerPrototype);
|
||||
_anomaly.SpawnOnRandomGridLocation(grid.Value, component.AnomalySpawnerPrototype);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
public sealed class BluespaceArtifact : StationEventSystem
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
public override string Prototype => "BluespaceArtifact";
|
||||
|
||||
public readonly string ArtifactSpawnerPrototype = "RandomArtifactSpawner";
|
||||
public readonly string ArtifactFlashPrototype = "EffectFlashBluespace";
|
||||
|
||||
public readonly List<string> PossibleSighting = new()
|
||||
{
|
||||
"bluespace-artifact-sighting-1",
|
||||
"bluespace-artifact-sighting-2",
|
||||
"bluespace-artifact-sighting-3",
|
||||
"bluespace-artifact-sighting-4",
|
||||
"bluespace-artifact-sighting-5",
|
||||
"bluespace-artifact-sighting-6",
|
||||
"bluespace-artifact-sighting-7"
|
||||
};
|
||||
|
||||
public override void Added()
|
||||
{
|
||||
base.Added();
|
||||
|
||||
var str = Loc.GetString("bluespace-artifact-event-announcement",
|
||||
("sighting", Loc.GetString(_random.Pick(PossibleSighting))));
|
||||
ChatSystem.DispatchGlobalAnnouncement(str, colorOverride: Color.FromHex("#18abf5"));
|
||||
}
|
||||
|
||||
public override void Started()
|
||||
{
|
||||
base.Started();
|
||||
var amountToSpawn = Math.Max(1, (int) MathF.Round(GetSeverityModifier() / 1.5f));
|
||||
for (var i = 0; i < amountToSpawn; i++)
|
||||
{
|
||||
if (!TryFindRandomTile(out _, out _, out _, out var coords))
|
||||
return;
|
||||
|
||||
EntityManager.SpawnEntity(ArtifactSpawnerPrototype, coords);
|
||||
EntityManager.SpawnEntity(ArtifactFlashPrototype, coords);
|
||||
|
||||
Sawmill.Info($"Spawning random artifact at {coords}");
|
||||
}
|
||||
}
|
||||
}
|
||||
34
Content.Server/StationEvents/Events/BluespaceArtifactRule.cs
Normal file
34
Content.Server/StationEvents/Events/BluespaceArtifactRule.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.StationEvents.Components;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
public sealed class BluespaceArtifactRule : StationEventSystem<BluespaceArtifactRuleComponent>
|
||||
{
|
||||
protected override void Added(EntityUid uid, BluespaceArtifactRuleComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args)
|
||||
{
|
||||
base.Added(uid, component, gameRule, args);
|
||||
|
||||
var str = Loc.GetString("bluespace-artifact-event-announcement",
|
||||
("sighting", Loc.GetString(RobustRandom.Pick(component.PossibleSighting))));
|
||||
ChatSystem.DispatchGlobalAnnouncement(str, colorOverride: Color.FromHex("#18abf5"));
|
||||
}
|
||||
|
||||
protected override void Started(EntityUid uid, BluespaceArtifactRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
var amountToSpawn = Math.Max(1, (int) MathF.Round(GetSeverityModifier() / 1.5f));
|
||||
for (var i = 0; i < amountToSpawn; i++)
|
||||
{
|
||||
if (!TryFindRandomTile(out _, out _, out _, out var coords))
|
||||
return;
|
||||
|
||||
Spawn(component.ArtifactSpawnerPrototype, coords);
|
||||
Spawn(component.ArtifactFlashPrototype, coords);
|
||||
|
||||
Sawmill.Info($"Spawning random artifact at {coords}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,25 @@
|
||||
using System.Linq;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.Resist;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Server.StationEvents.Components;
|
||||
using Content.Server.Storage.Components;
|
||||
using Content.Server.Storage.EntitySystems;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Coordinates;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
public sealed class BluespaceLockerLink : StationEventSystem
|
||||
public sealed class BluespaceLockerRule : StationEventSystem<BluespaceLockerRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||
[Dependency] private readonly BluespaceLockerSystem _bluespaceLocker = default!;
|
||||
|
||||
public override string Prototype => "BluespaceLockerLink";
|
||||
|
||||
public override void Started()
|
||||
protected override void Started(EntityUid uid, BluespaceLockerRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started();
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
var targets = EntityQuery<EntityStorageComponent, ResistLockerComponent>().ToList();
|
||||
_robustRandom.Shuffle(targets);
|
||||
RobustRandom.Shuffle(targets);
|
||||
|
||||
foreach (var target in targets)
|
||||
{
|
||||
@@ -1,44 +1,44 @@
|
||||
using System.Linq;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Server.StationEvents.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class BreakerFlip : StationEventSystem
|
||||
public sealed class BreakerFlipRule : StationEventSystem<BreakerFlipRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly ApcSystem _apcSystem = default!;
|
||||
|
||||
public override string Prototype => "BreakerFlip";
|
||||
|
||||
public override void Added()
|
||||
protected override void Added(EntityUid uid, BreakerFlipRuleComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args)
|
||||
{
|
||||
base.Added();
|
||||
base.Added(uid, component, gameRule, args);
|
||||
|
||||
var str = Loc.GetString("station-event-breaker-flip-announcement", ("data", Loc.GetString(Loc.GetString($"random-sentience-event-data-{RobustRandom.Next(1, 6)}"))));
|
||||
ChatSystem.DispatchGlobalAnnouncement(str, playSound: false, colorOverride: Color.Gold);
|
||||
}
|
||||
|
||||
public override void Started()
|
||||
protected override void Started(EntityUid uid, BreakerFlipRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started();
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
if (StationSystem.Stations.Count == 0)
|
||||
return;
|
||||
var chosenStation = RobustRandom.Pick(StationSystem.Stations.ToList());
|
||||
|
||||
var stationApcs = new List<ApcComponent>();
|
||||
foreach (var (apc, transform) in EntityQuery<ApcComponent, TransformComponent>())
|
||||
foreach (var (apc, transform) in EntityQuery<ApcComponent, TransformComponent>())
|
||||
{
|
||||
if (apc.MainBreakerEnabled && CompOrNull<StationMemberComponent>(transform.GridUid)?.Station == chosenStation)
|
||||
{
|
||||
stationApcs.Add(apc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var toDisable = Math.Min(RobustRandom.Next(3, 7), stationApcs.Count);
|
||||
if (toDisable == 0)
|
||||
return;
|
||||
@@ -1,20 +1,20 @@
|
||||
using System.Linq;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Server.StationEvents.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class BureaucraticError : StationEventSystem
|
||||
public sealed class BureaucraticErrorRule : StationEventSystem<BureaucraticErrorRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly StationJobsSystem _stationJobs = default!;
|
||||
|
||||
public override string Prototype => "BureaucraticError";
|
||||
|
||||
public override void Started()
|
||||
protected override void Started(EntityUid uid, BureaucraticErrorRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started();
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
if (StationSystem.Stations.Count == 0)
|
||||
return; // No stations
|
||||
@@ -1,5 +1,7 @@
|
||||
using Content.Server.Disease;
|
||||
using Content.Server.Disease.Components;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.StationEvents.Components;
|
||||
using Content.Shared.Disease;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
@@ -10,38 +12,23 @@ namespace Content.Server.StationEvents.Events;
|
||||
/// Infects a couple people
|
||||
/// with a random disease that isn't super deadly
|
||||
/// </summary>
|
||||
public sealed class DiseaseOutbreak : StationEventSystem
|
||||
public sealed class DiseaseOutbreakRule : StationEventSystem<DiseaseOutbreakRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly DiseaseSystem _diseaseSystem = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||
|
||||
public override string Prototype => "DiseaseOutbreak";
|
||||
|
||||
/// <summary>
|
||||
/// Disease prototypes I decided were not too deadly for a random event
|
||||
/// </summary>
|
||||
public readonly IReadOnlyList<string> NotTooSeriousDiseases = new[]
|
||||
{
|
||||
"SpaceCold",
|
||||
"VanAusdallsRobovirus",
|
||||
"VentCough",
|
||||
"AMIV",
|
||||
"SpaceFlu",
|
||||
"BirdFlew",
|
||||
"TongueTwister"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Finds 2-5 random, alive entities that can host diseases
|
||||
/// and gives them a randomly selected disease.
|
||||
/// They all get the same disease.
|
||||
/// </summary>
|
||||
public override void Started()
|
||||
protected override void Started(EntityUid uid, DiseaseOutbreakRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started();
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
HashSet<EntityUid> stationsToNotify = new();
|
||||
List<DiseaseCarrierComponent> aliveList = new();
|
||||
foreach (var (carrier, mobState) in EntityManager.EntityQuery<DiseaseCarrierComponent, MobStateComponent>())
|
||||
foreach (var (carrier, mobState) in EntityQuery<DiseaseCarrierComponent, MobStateComponent>())
|
||||
{
|
||||
if (!_mobStateSystem.IsDead(mobState.Owner, mobState))
|
||||
aliveList.Add(carrier);
|
||||
@@ -51,7 +38,7 @@ public sealed class DiseaseOutbreak : StationEventSystem
|
||||
// We're going to filter the above out to only alive mobs. Might change after future mobstate rework
|
||||
var toInfect = RobustRandom.Next(2, 5);
|
||||
|
||||
var diseaseName = RobustRandom.Pick(NotTooSeriousDiseases);
|
||||
var diseaseName = RobustRandom.Pick(component.NotTooSeriousDiseases);
|
||||
|
||||
if (!PrototypeManager.TryIndex(diseaseName, out DiseasePrototype? disease))
|
||||
return;
|
||||
@@ -1,33 +0,0 @@
|
||||
using Content.Server.GameTicking.Rules.Configurations;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server.StationEvents.Events
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class FalseAlarm : StationEventSystem
|
||||
{
|
||||
public override string Prototype => "FalseAlarm";
|
||||
|
||||
public override void Started()
|
||||
{
|
||||
base.Started();
|
||||
|
||||
var ev = GetRandomEventUnweighted(PrototypeManager, RobustRandom);
|
||||
|
||||
if (ev.Configuration is not StationEventRuleConfiguration cfg)
|
||||
return;
|
||||
|
||||
if (cfg.StartAnnouncement != null)
|
||||
{
|
||||
ChatSystem.DispatchGlobalAnnouncement(Loc.GetString(cfg.StartAnnouncement), playSound: false, colorOverride: Color.Gold);
|
||||
}
|
||||
|
||||
if (cfg.StartAudio != null)
|
||||
{
|
||||
SoundSystem.Play(cfg.StartAudio.GetSound(), Filter.Broadcast(), cfg.StartAudio.Params);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
28
Content.Server/StationEvents/Events/FalseAlarmRule.cs
Normal file
28
Content.Server/StationEvents/Events/FalseAlarmRule.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System.Linq;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.StationEvents.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class FalseAlarmRule : StationEventSystem<FalseAlarmRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly EventManagerSystem _event = default!;
|
||||
|
||||
protected override void Started(EntityUid uid, FalseAlarmRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
var allEv = _event.AllEvents().Select(p => p.Value).ToList();
|
||||
var picked = RobustRandom.Pick(allEv);
|
||||
|
||||
if (picked.StartAnnouncement != null)
|
||||
{
|
||||
ChatSystem.DispatchGlobalAnnouncement(Loc.GetString(picked.StartAnnouncement), playSound: false, colorOverride: Color.Gold);
|
||||
}
|
||||
Audio.PlayGlobal(picked.StartAudio, Filter.Broadcast(), true);
|
||||
}
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.GameTicking.Rules;
|
||||
using Content.Server.GameTicking.Rules.Configurations;
|
||||
using Content.Shared.Atmos;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.StationEvents.Events
|
||||
{
|
||||
internal sealed class GasLeak : StationEventSystem
|
||||
{
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
|
||||
|
||||
public override string Prototype => "GasLeak";
|
||||
|
||||
private static readonly Gas[] LeakableGases =
|
||||
{
|
||||
Gas.Miasma,
|
||||
Gas.Plasma,
|
||||
Gas.Tritium,
|
||||
Gas.Frezon,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Running cooldown of how much time until another leak.
|
||||
/// </summary>
|
||||
private float _timeUntilLeak;
|
||||
|
||||
/// <summary>
|
||||
/// How long between more gas being added to the tile.
|
||||
/// </summary>
|
||||
private const float LeakCooldown = 1.0f;
|
||||
|
||||
|
||||
// Event variables
|
||||
|
||||
private EntityUid _targetStation;
|
||||
private EntityUid _targetGrid;
|
||||
private Vector2i _targetTile;
|
||||
private EntityCoordinates _targetCoords;
|
||||
private bool _foundTile;
|
||||
private Gas _leakGas;
|
||||
private float _molesPerSecond;
|
||||
private const int MinimumMolesPerSecond = 20;
|
||||
private float _endAfter = float.MaxValue;
|
||||
|
||||
/// <summary>
|
||||
/// Don't want to make it too fast to give people time to flee.
|
||||
/// </summary>
|
||||
private const int MaximumMolesPerSecond = 50;
|
||||
|
||||
private const int MinimumGas = 250;
|
||||
private const int MaximumGas = 1000;
|
||||
private const float SparkChance = 0.05f;
|
||||
|
||||
public override void Started()
|
||||
{
|
||||
base.Started();
|
||||
|
||||
var mod = MathF.Sqrt(GetSeverityModifier());
|
||||
|
||||
// Essentially we'll pick out a target amount of gas to leak, then a rate to leak it at, then work out the duration from there.
|
||||
if (TryFindRandomTile(out _targetTile, out _targetStation, out _targetGrid, out _targetCoords))
|
||||
{
|
||||
_foundTile = true;
|
||||
|
||||
_leakGas = RobustRandom.Pick(LeakableGases);
|
||||
// Was 50-50 on using normal distribution.
|
||||
var totalGas = RobustRandom.Next(MinimumGas, MaximumGas) * mod;
|
||||
var startAfter = ((StationEventRuleConfiguration) Configuration).StartAfter;
|
||||
_molesPerSecond = RobustRandom.Next(MinimumMolesPerSecond, MaximumMolesPerSecond);
|
||||
_endAfter = totalGas / _molesPerSecond + startAfter;
|
||||
Sawmill.Info($"Leaking {totalGas} of {_leakGas} over {_endAfter - startAfter} seconds at {_targetTile}");
|
||||
}
|
||||
|
||||
// Look technically if you wanted to guarantee a leak you'd do this in announcement but having the announcement
|
||||
// there just to fuck with people even if there is no valid tile is funny.
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
if (!RuleStarted)
|
||||
return;
|
||||
|
||||
if (Elapsed > _endAfter)
|
||||
{
|
||||
ForceEndSelf();
|
||||
return;
|
||||
}
|
||||
|
||||
_timeUntilLeak -= frameTime;
|
||||
|
||||
if (_timeUntilLeak > 0f) return;
|
||||
_timeUntilLeak += LeakCooldown;
|
||||
|
||||
if (!_foundTile ||
|
||||
_targetGrid == default ||
|
||||
EntityManager.Deleted(_targetGrid) ||
|
||||
!_atmosphere.IsSimulatedGrid(_targetGrid))
|
||||
{
|
||||
ForceEndSelf();
|
||||
return;
|
||||
}
|
||||
|
||||
var environment = _atmosphere.GetTileMixture(_targetGrid, null, _targetTile, true);
|
||||
|
||||
environment?.AdjustMoles(_leakGas, LeakCooldown * _molesPerSecond);
|
||||
}
|
||||
|
||||
public override void Ended()
|
||||
{
|
||||
base.Ended();
|
||||
|
||||
Spark();
|
||||
|
||||
_foundTile = false;
|
||||
_targetGrid = default;
|
||||
_targetTile = default;
|
||||
_targetCoords = default;
|
||||
_leakGas = Gas.Oxygen;
|
||||
_endAfter = float.MaxValue;
|
||||
}
|
||||
|
||||
private void Spark()
|
||||
{
|
||||
if (RobustRandom.NextFloat() <= SparkChance)
|
||||
{
|
||||
if (!_foundTile ||
|
||||
_targetGrid == default ||
|
||||
(!EntityManager.EntityExists(_targetGrid) ? EntityLifeStage.Deleted : EntityManager.GetComponent<MetaDataComponent>(_targetGrid).EntityLifeStage) >= EntityLifeStage.Deleted ||
|
||||
!_atmosphere.IsSimulatedGrid(_targetGrid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't want it to be so obnoxious as to instantly murder anyone in the area but enough that
|
||||
// it COULD start potentially start a bigger fire.
|
||||
_atmosphere.HotspotExpose(_targetGrid, _targetTile, 700f, 50f, null, true);
|
||||
SoundSystem.Play("/Audio/Effects/sparks4.ogg", Filter.Pvs(_targetCoords), _targetCoords);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
90
Content.Server/StationEvents/Events/GasLeakRule.cs
Normal file
90
Content.Server/StationEvents/Events/GasLeakRule.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.StationEvents.Components;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.StationEvents.Events
|
||||
{
|
||||
internal sealed class GasLeakRule : StationEventSystem<GasLeakRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
|
||||
|
||||
protected override void Started(EntityUid uid, GasLeakRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
if (!TryComp<StationEventComponent>(uid, out var stationEvent))
|
||||
return;
|
||||
|
||||
var mod = MathF.Sqrt(GetSeverityModifier());
|
||||
|
||||
// Essentially we'll pick out a target amount of gas to leak, then a rate to leak it at, then work out the duration from there.
|
||||
if (TryFindRandomTile(out component.TargetTile, out component.TargetStation, out component.TargetGrid, out component.TargetCoords))
|
||||
{
|
||||
component.FoundTile = true;
|
||||
|
||||
component.LeakGas = RobustRandom.Pick(component.LeakableGases);
|
||||
// Was 50-50 on using normal distribution.
|
||||
var totalGas = RobustRandom.Next(component.MinimumGas, component.MaximumGas) * mod;
|
||||
var startAfter = stationEvent.StartDelay;
|
||||
component.MolesPerSecond = RobustRandom.Next(component.MinimumMolesPerSecond, component.MaximumMolesPerSecond);
|
||||
|
||||
stationEvent.EndTime = _timing.CurTime + TimeSpan.FromSeconds(totalGas / component.MolesPerSecond + startAfter.TotalSeconds);
|
||||
}
|
||||
|
||||
// Look technically if you wanted to guarantee a leak you'd do this in announcement but having the announcement
|
||||
// there just to fuck with people even if there is no valid tile is funny.
|
||||
}
|
||||
|
||||
protected override void ActiveTick(EntityUid uid, GasLeakRuleComponent component, GameRuleComponent gameRule, float frameTime)
|
||||
{
|
||||
base.ActiveTick(uid, component, gameRule, frameTime);
|
||||
component.TimeUntilLeak -= frameTime;
|
||||
|
||||
if (component.TimeUntilLeak > 0f)
|
||||
return;
|
||||
component.TimeUntilLeak += component.LeakCooldown;
|
||||
|
||||
if (!component.FoundTile ||
|
||||
component.TargetGrid == default ||
|
||||
Deleted(component.TargetGrid) ||
|
||||
!_atmosphere.IsSimulatedGrid(component.TargetGrid))
|
||||
{
|
||||
ForceEndSelf(uid, gameRule);
|
||||
return;
|
||||
}
|
||||
|
||||
var environment = _atmosphere.GetTileMixture(component.TargetGrid, null, component.TargetTile, true);
|
||||
|
||||
environment?.AdjustMoles(component.LeakGas, component.LeakCooldown * component.MolesPerSecond);
|
||||
}
|
||||
|
||||
protected override void Ended(EntityUid uid, GasLeakRuleComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args)
|
||||
{
|
||||
base.Ended(uid, component, gameRule, args);
|
||||
Spark(uid, component);
|
||||
}
|
||||
|
||||
private void Spark(EntityUid uid, GasLeakRuleComponent component)
|
||||
{
|
||||
if (RobustRandom.NextFloat() <= component.SparkChance)
|
||||
{
|
||||
if (!component.FoundTile ||
|
||||
component.TargetGrid == default ||
|
||||
(!Exists(component.TargetGrid) ? EntityLifeStage.Deleted : MetaData(component.TargetGrid).EntityLifeStage) >= EntityLifeStage.Deleted ||
|
||||
!_atmosphere.IsSimulatedGrid(component.TargetGrid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't want it to be so obnoxious as to instantly murder anyone in the area but enough that
|
||||
// it COULD start potentially start a bigger fire.
|
||||
_atmosphere.HotspotExpose(component.TargetGrid, component.TargetTile, 700f, 50f, null, true);
|
||||
Audio.PlayPvs(new SoundPathSpecifier("/Audio/Effects/sparks4.ogg"), component.TargetCoords);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
public sealed class KudzuGrowth : StationEventSystem
|
||||
{
|
||||
public override string Prototype => "KudzuGrowth";
|
||||
|
||||
private EntityUid _targetGrid;
|
||||
private Vector2i _targetTile;
|
||||
private EntityCoordinates _targetCoords;
|
||||
|
||||
public override void Started()
|
||||
{
|
||||
base.Started();
|
||||
|
||||
// Pick a place to plant the kudzu.
|
||||
if (TryFindRandomTile(out _targetTile, out _, out _targetGrid, out _targetCoords))
|
||||
{
|
||||
EntityManager.SpawnEntity("Kudzu", _targetCoords);
|
||||
Sawmill.Info($"Spawning a Kudzu at {_targetTile} on {_targetGrid}");
|
||||
}
|
||||
|
||||
// If the kudzu tile selection fails we just let the announcement happen anyways because it's funny and people
|
||||
// will be hunting the non-existent, dangerous plant.
|
||||
}
|
||||
}
|
||||
19
Content.Server/StationEvents/Events/KudzuGrowthRule.cs
Normal file
19
Content.Server/StationEvents/Events/KudzuGrowthRule.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.StationEvents.Components;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
public sealed class KudzuGrowthRule : StationEventSystem<KudzuGrowthRuleComponent>
|
||||
{
|
||||
protected override void Started(EntityUid uid, KudzuGrowthRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
// Pick a place to plant the kudzu.
|
||||
if (!TryFindRandomTile(out var targetTile, out _, out var targetGrid, out var targetCoords))
|
||||
return;
|
||||
Spawn("Kudzu", targetCoords);
|
||||
Sawmill.Info($"Spawning a Kudzu at {targetTile} on {targetGrid}");
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Maps;
|
||||
using Robust.Shared.Map;
|
||||
using Content.Server.GameTicking;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Content.Server.GameTicking.Rules;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
public sealed class LoneOpsSpawn : StationEventSystem
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly MapLoaderSystem _map = default!;
|
||||
[Dependency] private readonly GameTicker _gameTicker = default!;
|
||||
[Dependency] private readonly NukeopsRuleSystem _nukeopsRuleSystem = default!;
|
||||
|
||||
public override string Prototype => "LoneOpsSpawn";
|
||||
public const string LoneOpsShuttlePath = "Maps/Shuttles/striker.yml";
|
||||
public const string GameRuleProto = "Nukeops";
|
||||
|
||||
public override void Started()
|
||||
{
|
||||
base.Started();
|
||||
|
||||
if (!_nukeopsRuleSystem.CheckLoneOpsSpawn())
|
||||
return;
|
||||
|
||||
var shuttleMap = _mapManager.CreateMap();
|
||||
var options = new MapLoadOptions()
|
||||
{
|
||||
LoadMap = true,
|
||||
};
|
||||
|
||||
_map.TryLoad(shuttleMap, LoneOpsShuttlePath, out var grids, options);
|
||||
|
||||
if (!_prototypeManager.TryIndex<GameRulePrototype>(GameRuleProto, out var ruleProto))
|
||||
return;
|
||||
|
||||
_nukeopsRuleSystem.LoadLoneOpsConfig();
|
||||
_gameTicker.StartGameRule(ruleProto);
|
||||
}
|
||||
}
|
||||
|
||||
49
Content.Server/StationEvents/Events/LoneOpsSpawnRule.cs
Normal file
49
Content.Server/StationEvents/Events/LoneOpsSpawnRule.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Maps;
|
||||
using Robust.Shared.Map;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.GameTicking.Rules;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.StationEvents.Components;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
public sealed class LoneOpsSpawnRule : StationEventSystem<LoneOpsSpawnRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly MapLoaderSystem _map = default!;
|
||||
[Dependency] private readonly GameTicker _gameTicker = default!;
|
||||
[Dependency] private readonly NukeopsRuleSystem _nukeopsRuleSystem = default!;
|
||||
|
||||
protected override void Started(EntityUid uid, LoneOpsSpawnRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
if (!_nukeopsRuleSystem.CheckLoneOpsSpawn())
|
||||
return;
|
||||
|
||||
var shuttleMap = _mapManager.CreateMap();
|
||||
var options = new MapLoadOptions
|
||||
{
|
||||
LoadMap = true,
|
||||
};
|
||||
|
||||
_map.TryLoad(shuttleMap, component.LoneOpsShuttlePath, out _, options);
|
||||
|
||||
var nukeopsEntity = _gameTicker.AddGameRule(component.GameRuleProto);
|
||||
component.AdditionalRule = nukeopsEntity;
|
||||
var nukeopsComp = EntityManager.GetComponent<NukeopsRuleComponent>(nukeopsEntity);
|
||||
nukeopsComp.SpawnOutpost = false;
|
||||
nukeopsComp.EndsRound = false;
|
||||
_gameTicker.StartGameRule(nukeopsEntity);
|
||||
}
|
||||
|
||||
protected override void Ended(EntityUid uid, LoneOpsSpawnRuleComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args)
|
||||
{
|
||||
base.Ended(uid, component, gameRule, args);
|
||||
|
||||
if (component.AdditionalRule != null)
|
||||
GameTicker.EndGameRule(component.AdditionalRule.Value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.StationEvents.Components;
|
||||
using Content.Shared.Spawners.Components;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics.Components;
|
||||
@@ -6,67 +7,37 @@ using Robust.Shared.Physics.Systems;
|
||||
|
||||
namespace Content.Server.StationEvents.Events
|
||||
{
|
||||
public sealed class MeteorSwarm : StationEventSystem
|
||||
public sealed class MeteorSwarmRule : StationEventSystem<MeteorSwarmRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
|
||||
public override string Prototype => "MeteorSwarm";
|
||||
|
||||
private float _cooldown;
|
||||
|
||||
/// <summary>
|
||||
/// We'll send a specific amount of waves of meteors towards the station per ending rather than using a timer.
|
||||
/// </summary>
|
||||
private int _waveCounter;
|
||||
|
||||
private const int MinimumWaves = 3;
|
||||
private const int MaximumWaves = 8;
|
||||
|
||||
private const float MinimumCooldown = 10f;
|
||||
private const float MaximumCooldown = 60f;
|
||||
|
||||
private const int MeteorsPerWave = 5;
|
||||
private const float MeteorVelocity = 10f;
|
||||
private const float MaxAngularVelocity = 0.25f;
|
||||
private const float MinAngularVelocity = -0.25f;
|
||||
|
||||
public override void Started()
|
||||
protected override void Started(EntityUid uid, MeteorSwarmRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started();
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
var mod = Math.Sqrt(GetSeverityModifier());
|
||||
_waveCounter = (int) (RobustRandom.Next(MinimumWaves, MaximumWaves) * mod);
|
||||
component._waveCounter = (int) (RobustRandom.Next(component.MinimumWaves, component.MaximumWaves) * mod);
|
||||
}
|
||||
|
||||
public override void Ended()
|
||||
protected override void ActiveTick(EntityUid uid, MeteorSwarmRuleComponent component, GameRuleComponent gameRule, float frameTime)
|
||||
{
|
||||
base.Ended();
|
||||
_waveCounter = 0;
|
||||
_cooldown = 0f;
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
if (!RuleStarted)
|
||||
return;
|
||||
|
||||
if (_waveCounter <= 0)
|
||||
base.ActiveTick(uid, component, gameRule, frameTime);
|
||||
if (component._waveCounter <= 0)
|
||||
{
|
||||
ForceEndSelf();
|
||||
ForceEndSelf(uid, gameRule);
|
||||
return;
|
||||
}
|
||||
|
||||
var mod = GetSeverityModifier();
|
||||
|
||||
_cooldown -= frameTime;
|
||||
component._cooldown -= frameTime;
|
||||
|
||||
if (_cooldown > 0f)
|
||||
if (component._cooldown > 0f)
|
||||
return;
|
||||
|
||||
_waveCounter--;
|
||||
component._waveCounter--;
|
||||
|
||||
_cooldown += (MaximumCooldown - MinimumCooldown) * RobustRandom.NextFloat() / mod + MinimumCooldown;
|
||||
component._cooldown += (component.MaximumCooldown - component.MinimumCooldown) * RobustRandom.NextFloat() / mod + component.MinimumCooldown;
|
||||
|
||||
Box2? playableArea = null;
|
||||
var mapId = GameTicker.DefaultMap;
|
||||
@@ -79,7 +50,7 @@ namespace Content.Server.StationEvents.Events
|
||||
|
||||
if (playableArea == null)
|
||||
{
|
||||
ForceEndSelf();
|
||||
ForceEndSelf(uid, gameRule);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -88,7 +59,7 @@ namespace Content.Server.StationEvents.Events
|
||||
|
||||
var center = playableArea.Value.Center;
|
||||
|
||||
for (var i = 0; i < MeteorsPerWave; i++)
|
||||
for (var i = 0; i < component.MeteorsPerWave; i++)
|
||||
{
|
||||
var angle = new Angle(RobustRandom.NextFloat() * MathF.Tau);
|
||||
var offset = angle.RotateVec(new Vector2((maximumDistance - minimumDistance) * RobustRandom.NextFloat() + minimumDistance, 0));
|
||||
@@ -98,10 +69,10 @@ namespace Content.Server.StationEvents.Events
|
||||
_physics.SetBodyStatus(physics, BodyStatus.InAir);
|
||||
_physics.SetLinearDamping(physics, 0f);
|
||||
_physics.SetAngularDamping(physics, 0f);
|
||||
_physics.ApplyLinearImpulse(meteor, -offset.Normalized * MeteorVelocity * physics.Mass, body: physics);
|
||||
_physics.ApplyLinearImpulse(meteor, -offset.Normalized * component.MeteorVelocity * physics.Mass, body: physics);
|
||||
_physics.ApplyAngularImpulse(
|
||||
meteor,
|
||||
physics.Mass * ((MaxAngularVelocity - MinAngularVelocity) * RobustRandom.NextFloat() + MinAngularVelocity),
|
||||
physics.Mass * ((component.MaxAngularVelocity - component.MinAngularVelocity) * RobustRandom.NextFloat() + component.MinAngularVelocity),
|
||||
body: physics);
|
||||
|
||||
EnsureComp<TimedDespawnComponent>(meteor).Lifetime = 120f;
|
||||
@@ -1,19 +1,15 @@
|
||||
using System.Linq;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.StationEvents.Components;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
public sealed class MouseMigration : StationEventSystem
|
||||
public sealed class MouseMigrationRule : StationEventSystem<MouseMigrationRuleComponent>
|
||||
{
|
||||
public static List<string> SpawnedPrototypeChoices = new List<string>() //we double up for that ez fake probability
|
||||
{"MobMouse", "MobMouse1", "MobMouse2", "MobRatServant"};
|
||||
|
||||
public override string Prototype => "MouseMigration";
|
||||
|
||||
public override void Started()
|
||||
protected override void Started(EntityUid uid, MouseMigrationRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started();
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
var modifier = GetSeverityModifier();
|
||||
|
||||
@@ -23,9 +19,9 @@ public sealed class MouseMigration : StationEventSystem
|
||||
// sqrt so we dont get insane values for ramping events
|
||||
var spawnAmount = (int) (RobustRandom.Next(7, 15) * Math.Sqrt(modifier)); // A small colony of critters.
|
||||
|
||||
for (int i = 0; i < spawnAmount && i < spawnLocations.Count - 1; i++)
|
||||
for (var i = 0; i < spawnAmount && i < spawnLocations.Count - 1; i++)
|
||||
{
|
||||
var spawnChoice = RobustRandom.Pick(SpawnedPrototypeChoices);
|
||||
var spawnChoice = RobustRandom.Pick(component.SpawnedPrototypeChoices);
|
||||
if (RobustRandom.Prob(Math.Min(0.01f * modifier, 1.0f)) || i == 0) //small chance for multiple, but always at least 1
|
||||
spawnChoice = "SpawnPointGhostRatKing";
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
using Content.Server.Power.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Threading;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Random;
|
||||
using Content.Server.Station.Components;
|
||||
|
||||
namespace Content.Server.StationEvents.Events
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class PowerGridCheck : StationEventSystem
|
||||
{
|
||||
[Dependency] private readonly ApcSystem _apcSystem = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
|
||||
|
||||
public override string Prototype => "PowerGridCheck";
|
||||
|
||||
private CancellationTokenSource? _announceCancelToken;
|
||||
|
||||
private readonly List<EntityUid> _powered = new();
|
||||
private readonly List<EntityUid> _unpowered = new();
|
||||
|
||||
private const float SecondsUntilOff = 30.0f;
|
||||
|
||||
private int _numberPerSecond = 0;
|
||||
private float UpdateRate => 1.0f / _numberPerSecond;
|
||||
private float _frameTimeAccumulator = 0.0f;
|
||||
private float _endAfter = 0.0f;
|
||||
|
||||
public override void Added()
|
||||
{
|
||||
base.Added();
|
||||
_endAfter = RobustRandom.Next(60, 120);
|
||||
}
|
||||
|
||||
public override void Started()
|
||||
{
|
||||
if (StationSystem.Stations.Count == 0)
|
||||
return;
|
||||
var chosenStation = RobustRandom.Pick(StationSystem.Stations.ToList());
|
||||
|
||||
foreach (var (apc, transform) in EntityQuery<ApcComponent, TransformComponent>(true))
|
||||
{
|
||||
if (apc.MainBreakerEnabled && CompOrNull<StationMemberComponent>(transform.GridUid)?.Station == chosenStation)
|
||||
_powered.Add(apc.Owner);
|
||||
}
|
||||
|
||||
RobustRandom.Shuffle(_powered);
|
||||
|
||||
_numberPerSecond = Math.Max(1, (int)(_powered.Count / SecondsUntilOff)); // Number of APCs to turn off every second. At least one.
|
||||
|
||||
base.Started();
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
if (!RuleStarted)
|
||||
return;
|
||||
|
||||
if (Elapsed > _endAfter)
|
||||
{
|
||||
ForceEndSelf();
|
||||
return;
|
||||
}
|
||||
|
||||
var updates = 0;
|
||||
_frameTimeAccumulator += frameTime;
|
||||
if (_frameTimeAccumulator > UpdateRate)
|
||||
{
|
||||
updates = (int) (_frameTimeAccumulator / UpdateRate);
|
||||
_frameTimeAccumulator -= UpdateRate * updates;
|
||||
}
|
||||
|
||||
for (var i = 0; i < updates; i++)
|
||||
{
|
||||
if (_powered.Count == 0)
|
||||
break;
|
||||
|
||||
var selected = _powered.Pop();
|
||||
if (EntityManager.Deleted(selected)) continue;
|
||||
if (EntityManager.TryGetComponent<ApcComponent>(selected, out var apcComponent))
|
||||
{
|
||||
if (apcComponent.MainBreakerEnabled)
|
||||
_apcSystem.ApcToggleBreaker(selected, apcComponent);
|
||||
}
|
||||
_unpowered.Add(selected);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Ended()
|
||||
{
|
||||
foreach (var entity in _unpowered)
|
||||
{
|
||||
if (EntityManager.Deleted(entity)) continue;
|
||||
|
||||
if (EntityManager.TryGetComponent(entity, out ApcComponent? apcComponent))
|
||||
{
|
||||
if(!apcComponent.MainBreakerEnabled)
|
||||
_apcSystem.ApcToggleBreaker(entity, apcComponent);
|
||||
}
|
||||
}
|
||||
|
||||
// Can't use the default EndAudio
|
||||
_announceCancelToken?.Cancel();
|
||||
_announceCancelToken = new CancellationTokenSource();
|
||||
Timer.Spawn(3000, () =>
|
||||
{
|
||||
_audioSystem.PlayGlobal("/Audio/Announcements/power_on.ogg", Filter.Broadcast(), true, AudioParams.Default.WithVolume(-4f));
|
||||
}, _announceCancelToken.Token);
|
||||
_unpowered.Clear();
|
||||
|
||||
base.Ended();
|
||||
}
|
||||
}
|
||||
}
|
||||
96
Content.Server/StationEvents/Events/PowerGridCheckRule.cs
Normal file
96
Content.Server/StationEvents/Events/PowerGridCheckRule.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
using Content.Server.Power.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Threading;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
using System.Linq;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Robust.Shared.Random;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Server.StationEvents.Components;
|
||||
|
||||
namespace Content.Server.StationEvents.Events
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class PowerGridCheckRule : StationEventSystem<PowerGridCheckRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly ApcSystem _apcSystem = default!;
|
||||
|
||||
protected override void Started(EntityUid uid, PowerGridCheckRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
if (StationSystem.Stations.Count == 0)
|
||||
return;
|
||||
var chosenStation = RobustRandom.Pick(StationSystem.Stations.ToList());
|
||||
|
||||
foreach (var (apc, transform) in EntityQuery<ApcComponent, TransformComponent>(true))
|
||||
{
|
||||
if (apc.MainBreakerEnabled && CompOrNull<StationMemberComponent>(transform.GridUid)?.Station == chosenStation)
|
||||
component.Powered.Add(apc.Owner);
|
||||
}
|
||||
|
||||
RobustRandom.Shuffle(component.Powered);
|
||||
|
||||
component.NumberPerSecond = Math.Max(1, (int)(component.Powered.Count / component.SecondsUntilOff)); // Number of APCs to turn off every second. At least one.
|
||||
}
|
||||
|
||||
protected override void Ended(EntityUid uid, PowerGridCheckRuleComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args)
|
||||
{
|
||||
base.Ended(uid, component, gameRule, args);
|
||||
|
||||
foreach (var entity in component.Unpowered)
|
||||
{
|
||||
if (Deleted(entity))
|
||||
continue;
|
||||
|
||||
if (TryComp(entity, out ApcComponent? apcComponent))
|
||||
{
|
||||
if(!apcComponent.MainBreakerEnabled)
|
||||
_apcSystem.ApcToggleBreaker(entity, apcComponent);
|
||||
}
|
||||
}
|
||||
|
||||
// Can't use the default EndAudio
|
||||
component.AnnounceCancelToken?.Cancel();
|
||||
component.AnnounceCancelToken = new CancellationTokenSource();
|
||||
Timer.Spawn(3000, () =>
|
||||
{
|
||||
Audio.PlayGlobal("/Audio/Announcements/power_on.ogg", Filter.Broadcast(), true, AudioParams.Default.WithVolume(-4f));
|
||||
}, component.AnnounceCancelToken.Token);
|
||||
component.Unpowered.Clear();
|
||||
}
|
||||
|
||||
protected override void ActiveTick(EntityUid uid, PowerGridCheckRuleComponent component, GameRuleComponent gameRule, float frameTime)
|
||||
{
|
||||
base.ActiveTick(uid, component, gameRule, frameTime);
|
||||
|
||||
var updates = 0;
|
||||
component.FrameTimeAccumulator += frameTime;
|
||||
if (component.FrameTimeAccumulator > component.UpdateRate)
|
||||
{
|
||||
updates = (int) (component.FrameTimeAccumulator / component.UpdateRate);
|
||||
component.FrameTimeAccumulator -= component.UpdateRate * updates;
|
||||
}
|
||||
|
||||
for (var i = 0; i < updates; i++)
|
||||
{
|
||||
if (component.Powered.Count == 0)
|
||||
break;
|
||||
|
||||
var selected = component.Powered.Pop();
|
||||
if (Deleted(selected))
|
||||
continue;
|
||||
if (TryComp<ApcComponent>(selected, out var apcComponent))
|
||||
{
|
||||
if (apcComponent.MainBreakerEnabled)
|
||||
_apcSystem.ApcToggleBreaker(selected, apcComponent);
|
||||
}
|
||||
component.Unpowered.Add(selected);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,20 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.Ghost.Roles.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Server.StationEvents.Components;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
public sealed class RandomSentience : StationEventSystem
|
||||
public sealed class RandomSentienceRule : StationEventSystem<RandomSentienceRuleComponent>
|
||||
{
|
||||
public override string Prototype => "RandomSentience";
|
||||
|
||||
public override void Started()
|
||||
protected override void Started(EntityUid uid, RandomSentienceRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started();
|
||||
HashSet<EntityUid> stationsToNotify = new();
|
||||
|
||||
var mod = GetSeverityModifier();
|
||||
var targetList = EntityManager.EntityQuery<SentienceTargetComponent>().ToList();
|
||||
var targetList = EntityQuery<SentienceTargetComponent>().ToList();
|
||||
RobustRandom.Shuffle(targetList);
|
||||
|
||||
var toMakeSentient = (int) (RobustRandom.Next(2, 5) * Math.Sqrt(mod));
|
||||
@@ -27,10 +25,10 @@ public sealed class RandomSentience : StationEventSystem
|
||||
if (toMakeSentient-- == 0)
|
||||
break;
|
||||
|
||||
EntityManager.RemoveComponent<SentienceTargetComponent>(target.Owner);
|
||||
var ghostRole = AddComp<GhostRoleComponent>(target.Owner);
|
||||
AddComp<GhostTakeoverAvailableComponent>(target.Owner);
|
||||
ghostRole.RoleName = EntityManager.GetComponent<MetaDataComponent>(target.Owner).EntityName;
|
||||
RemComp<SentienceTargetComponent>(target.Owner);
|
||||
var ghostRole = EnsureComp<GhostRoleComponent>(target.Owner);
|
||||
EnsureComp<GhostTakeoverAvailableComponent>(target.Owner);
|
||||
ghostRole.RoleName = MetaData(target.Owner).EntityName;
|
||||
ghostRole.RoleDescription = Loc.GetString("station-event-random-sentience-role-description", ("name", ghostRole.RoleName));
|
||||
groups.Add(Loc.GetString(target.FlavorKind));
|
||||
}
|
||||
@@ -43,18 +41,15 @@ public sealed class RandomSentience : StationEventSystem
|
||||
var kind2 = groupList.Count > 1 ? groupList[1] : "???";
|
||||
var kind3 = groupList.Count > 2 ? groupList[2] : "???";
|
||||
|
||||
var entSysMgr = IoCManager.Resolve<IEntitySystemManager>();
|
||||
var stationSystem = entSysMgr.GetEntitySystem<StationSystem>();
|
||||
var chatSystem = entSysMgr.GetEntitySystem<ChatSystem>();
|
||||
foreach (var target in targetList)
|
||||
{
|
||||
var station = stationSystem.GetOwningStation(target.Owner);
|
||||
var station = StationSystem.GetOwningStation(target.Owner);
|
||||
if(station == null) continue;
|
||||
stationsToNotify.Add((EntityUid) station);
|
||||
}
|
||||
foreach (var station in stationsToNotify)
|
||||
{
|
||||
chatSystem.DispatchStationAnnouncement(
|
||||
ChatSystem.DispatchStationAnnouncement(
|
||||
station,
|
||||
Loc.GetString("station-event-random-sentience-announcement",
|
||||
("kind1", kind1), ("kind2", kind2), ("kind3", kind3), ("amount", groupList.Count),
|
||||
@@ -1,18 +0,0 @@
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
public sealed class RevenantSpawn : StationEventSystem
|
||||
{
|
||||
public override string Prototype => "RevenantSpawn";
|
||||
private static readonly string RevenantPrototype = "MobRevenant";
|
||||
|
||||
public override void Started()
|
||||
{
|
||||
base.Started();
|
||||
|
||||
if (TryFindRandomTile(out _, out _, out _, out var coords))
|
||||
{
|
||||
Sawmill.Info($"Spawning revenant at {coords}");
|
||||
EntityManager.SpawnEntity(RevenantPrototype, coords);
|
||||
}
|
||||
}
|
||||
}
|
||||
19
Content.Server/StationEvents/Events/RevenantSpawnRule.cs
Normal file
19
Content.Server/StationEvents/Events/RevenantSpawnRule.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.StationEvents.Components;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
public sealed class RevenantSpawnRule : StationEventSystem<RevenantSpawnRuleComponent>
|
||||
{
|
||||
protected override void Started(EntityUid uid, RevenantSpawnRuleComponent component, GameRuleComponent gameRule,
|
||||
GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
if (TryFindRandomTile(out _, out _, out _, out var coords))
|
||||
{
|
||||
Sawmill.Info($"Spawning revenant at {coords}");
|
||||
Spawn(component.RevenantPrototype, coords);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
using Content.Server.GameTicking.Rules.Configurations;
|
||||
using Content.Server.Radio.Components;
|
||||
using Content.Server.Radio;
|
||||
using Robust.Shared.Random;
|
||||
using Content.Server.Light.EntitySystems;
|
||||
using Content.Server.Light.Components;
|
||||
using Content.Shared.Radio.Components;
|
||||
using Content.Shared.Doors.Components;
|
||||
using Content.Shared.Doors.Systems;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
public sealed class SolarFlare : StationEventSystem
|
||||
{
|
||||
[Dependency] private readonly PoweredLightSystem _poweredLight = default!;
|
||||
[Dependency] private readonly SharedDoorSystem _door = default!;
|
||||
|
||||
public override string Prototype => "SolarFlare";
|
||||
|
||||
private SolarFlareEventRuleConfiguration _event = default!;
|
||||
private float _effectTimer = 0;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<RadioReceiveAttemptEvent>(OnRadioSendAttempt);
|
||||
}
|
||||
|
||||
public override void Added()
|
||||
{
|
||||
base.Added();
|
||||
|
||||
if (Configuration is not SolarFlareEventRuleConfiguration ev)
|
||||
return;
|
||||
|
||||
_event = ev;
|
||||
_event.EndAfter = RobustRandom.Next(ev.MinEndAfter, ev.MaxEndAfter);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
if (!RuleStarted)
|
||||
return;
|
||||
|
||||
_effectTimer -= frameTime;
|
||||
if (_effectTimer < 0)
|
||||
{
|
||||
_effectTimer += 1;
|
||||
var lightQuery = EntityQueryEnumerator<PoweredLightComponent>();
|
||||
while (lightQuery.MoveNext(out var uid, out var light))
|
||||
{
|
||||
if (RobustRandom.Prob(_event.LightBreakChancePerSecond))
|
||||
_poweredLight.TryDestroyBulb(uid, light);
|
||||
}
|
||||
var airlockQuery = EntityQueryEnumerator<AirlockComponent, DoorComponent>();
|
||||
while (airlockQuery.MoveNext(out var uid, out var airlock, out var door))
|
||||
{
|
||||
if (airlock.AutoClose && RobustRandom.Prob(_event.DoorToggleChancePerSecond))
|
||||
_door.TryToggleDoor(uid, door);
|
||||
}
|
||||
}
|
||||
|
||||
if (Elapsed > _event.EndAfter)
|
||||
{
|
||||
ForceEndSelf();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRadioSendAttempt(ref RadioReceiveAttemptEvent args)
|
||||
{
|
||||
if (RuleStarted && _event.AffectedChannels.Contains(args.Channel.ID))
|
||||
if (!_event.OnlyJamHeadsets || (HasComp<HeadsetComponent>(args.RadioReceiver) || HasComp<HeadsetComponent>(args.RadioSource)))
|
||||
args.Cancelled = true;
|
||||
}
|
||||
}
|
||||
64
Content.Server/StationEvents/Events/SolarFlareRule.cs
Normal file
64
Content.Server/StationEvents/Events/SolarFlareRule.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.Radio;
|
||||
using Robust.Shared.Random;
|
||||
using Content.Server.Light.EntitySystems;
|
||||
using Content.Server.Light.Components;
|
||||
using Content.Server.StationEvents.Components;
|
||||
using Content.Shared.Radio.Components;
|
||||
using Content.Shared.Doors.Components;
|
||||
using Content.Shared.Doors.Systems;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
public sealed class SolarFlareRule : StationEventSystem<SolarFlareRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly PoweredLightSystem _poweredLight = default!;
|
||||
[Dependency] private readonly SharedDoorSystem _door = default!;
|
||||
|
||||
private float _effectTimer = 0;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<RadioReceiveAttemptEvent>(OnRadioSendAttempt);
|
||||
}
|
||||
|
||||
protected override void ActiveTick(EntityUid uid, SolarFlareRuleComponent component, GameRuleComponent gameRule, float frameTime)
|
||||
{
|
||||
base.ActiveTick(uid, component, gameRule, frameTime);
|
||||
|
||||
_effectTimer -= frameTime;
|
||||
if (_effectTimer < 0)
|
||||
{
|
||||
_effectTimer += 1;
|
||||
var lightQuery = EntityQueryEnumerator<PoweredLightComponent>();
|
||||
while (lightQuery.MoveNext(out var lightEnt, out var light))
|
||||
{
|
||||
if (RobustRandom.Prob(component.LightBreakChancePerSecond))
|
||||
_poweredLight.TryDestroyBulb(lightEnt, light);
|
||||
}
|
||||
var airlockQuery = EntityQueryEnumerator<AirlockComponent, DoorComponent>();
|
||||
while (airlockQuery.MoveNext(out var airlockEnt, out var airlock, out var door))
|
||||
{
|
||||
if (airlock.AutoClose && RobustRandom.Prob(component.DoorToggleChancePerSecond))
|
||||
_door.TryToggleDoor(airlockEnt, door);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRadioSendAttempt(ref RadioReceiveAttemptEvent args)
|
||||
{
|
||||
var query = EntityQueryEnumerator<SolarFlareRuleComponent, GameRuleComponent>();
|
||||
while (query.MoveNext(out var uid, out var flare, out var gameRule))
|
||||
{
|
||||
if (!GameTicker.IsGameRuleActive(uid, gameRule))
|
||||
continue;
|
||||
|
||||
if (!flare.AffectedChannels.Contains(args.Channel.ID))
|
||||
continue;
|
||||
|
||||
if (!flare.OnlyJamHeadsets || (HasComp<HeadsetComponent>(args.RadioReceiver) || HasComp<HeadsetComponent>(args.RadioSource)))
|
||||
args.Cancelled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
using Content.Server.StationEvents.Components;
|
||||
using Content.Shared.Actions;
|
||||
using Robust.Shared.Random;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
public sealed class SpiderSpawn : StationEventSystem
|
||||
{
|
||||
public override string Prototype => "SpiderSpawn";
|
||||
|
||||
public override void Started()
|
||||
{
|
||||
base.Started();
|
||||
var spawnLocations = EntityManager.EntityQuery<VentCritterSpawnLocationComponent>().ToList();
|
||||
RobustRandom.Shuffle(spawnLocations);
|
||||
|
||||
var mod = Math.Sqrt(GetSeverityModifier());
|
||||
|
||||
var spawnAmount = (int) (RobustRandom.Next(4, 8) * mod);
|
||||
Sawmill.Info($"Spawning {spawnAmount} of spiders");
|
||||
foreach (var location in spawnLocations)
|
||||
{
|
||||
if (spawnAmount-- == 0)
|
||||
break;
|
||||
|
||||
var coords = EntityManager.GetComponent<TransformComponent>(location.Owner);
|
||||
|
||||
EntityManager.SpawnEntity("MobGiantSpiderAngry", coords.Coordinates);
|
||||
}
|
||||
}
|
||||
}
|
||||
28
Content.Server/StationEvents/Events/SpiderSpawnRule.cs
Normal file
28
Content.Server/StationEvents/Events/SpiderSpawnRule.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using Content.Server.StationEvents.Components;
|
||||
using System.Linq;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
public sealed class SpiderSpawnRule : StationEventSystem<SpiderSpawnRuleComponent>
|
||||
{
|
||||
protected override void Started(EntityUid uid, SpiderSpawnRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started(uid, component, gameRule, args);
|
||||
var spawnLocations = EntityQuery<VentCritterSpawnLocationComponent>().ToList();
|
||||
RobustRandom.Shuffle(spawnLocations);
|
||||
|
||||
var mod = Math.Sqrt(GetSeverityModifier());
|
||||
|
||||
var spawnAmount = (int) (RobustRandom.Next(4, 8) * mod);
|
||||
Sawmill.Info($"Spawning {spawnAmount} of spiders");
|
||||
foreach (var location in spawnLocations)
|
||||
{
|
||||
if (spawnAmount-- == 0)
|
||||
break;
|
||||
|
||||
var xform = Transform(location.Owner);
|
||||
Spawn("MobGiantSpiderAngry", xform.Coordinates);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,209 +1,197 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.GameTicking.Rules;
|
||||
using Content.Server.GameTicking.Rules.Configurations;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Server.StationEvents.Components;
|
||||
using Content.Shared.Database;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.StationEvents.Events
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
/// <summary>
|
||||
/// An abstract entity system inherited by all station events for their behavior.
|
||||
/// </summary>
|
||||
public abstract class StationEventSystem<T> : GameRuleSystem<T> where T : Component
|
||||
{
|
||||
[Dependency] protected readonly IAdminLogManager AdminLogManager = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] protected readonly IMapManager MapManager = default!;
|
||||
[Dependency] protected readonly IPrototypeManager PrototypeManager = default!;
|
||||
[Dependency] protected readonly IRobustRandom RobustRandom = default!;
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
|
||||
[Dependency] protected readonly ChatSystem ChatSystem = default!;
|
||||
[Dependency] protected readonly SharedAudioSystem Audio = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] protected readonly StationSystem StationSystem = default!;
|
||||
|
||||
protected ISawmill Sawmill = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
Sawmill = Logger.GetSawmill("stationevents");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Added(EntityUid uid, T component, GameRuleComponent gameRule, GameRuleAddedEvent args)
|
||||
{
|
||||
base.Added(uid, component, gameRule, args);
|
||||
|
||||
if (!TryComp<StationEventComponent>(uid, out var stationEvent))
|
||||
return;
|
||||
|
||||
AdminLogManager.Add(LogType.EventAnnounced, $"Event added / announced: {ToPrettyString(uid)}");
|
||||
|
||||
if (stationEvent.StartAnnouncement != null)
|
||||
{
|
||||
ChatSystem.DispatchGlobalAnnouncement(Loc.GetString(stationEvent.StartAnnouncement), playSound: false, colorOverride: Color.Gold);
|
||||
}
|
||||
|
||||
Audio.PlayGlobal(stationEvent.StartAudio, Filter.Broadcast(), true);
|
||||
stationEvent.StartTime = _timing.CurTime + stationEvent.StartDelay;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Started(EntityUid uid, T component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
if (!TryComp<StationEventComponent>(uid, out var stationEvent))
|
||||
return;
|
||||
|
||||
AdminLogManager.Add(LogType.EventStarted, LogImpact.High, $"Event started: {ToPrettyString(uid)}");
|
||||
var duration = stationEvent.MaxDuration == null
|
||||
? stationEvent.Duration
|
||||
: TimeSpan.FromSeconds(RobustRandom.NextDouble(stationEvent.Duration.TotalSeconds,
|
||||
stationEvent.MaxDuration.Value.TotalSeconds));
|
||||
stationEvent.EndTime = _timing.CurTime + duration;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Ended(EntityUid uid, T component, GameRuleComponent gameRule, GameRuleEndedEvent args)
|
||||
{
|
||||
base.Ended(uid, component, gameRule, args);
|
||||
|
||||
if (!TryComp<StationEventComponent>(uid, out var stationEvent))
|
||||
return;
|
||||
|
||||
AdminLogManager.Add(LogType.EventStopped, $"Event ended: {ToPrettyString(uid)}");
|
||||
|
||||
if (stationEvent.EndAnnouncement != null)
|
||||
{
|
||||
ChatSystem.DispatchGlobalAnnouncement(Loc.GetString(stationEvent.EndAnnouncement), playSound: false, colorOverride: Color.Gold);
|
||||
}
|
||||
|
||||
Audio.PlayGlobal(stationEvent.EndAudio, Filter.Broadcast(), true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called every tick when this event is running.
|
||||
/// Events are responsible for their own lifetime, so this handles starting and ending after time.
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
var query = EntityQueryEnumerator<StationEventComponent, GameRuleComponent>();
|
||||
while (query.MoveNext(out var uid, out var stationEvent, out var ruleData))
|
||||
{
|
||||
if (!GameTicker.IsGameRuleAdded(uid, ruleData))
|
||||
continue;
|
||||
|
||||
if (!GameTicker.IsGameRuleActive(uid, ruleData) && _timing.CurTime >= stationEvent.StartTime)
|
||||
{
|
||||
GameTicker.StartGameRule(uid, ruleData);
|
||||
}
|
||||
else if (GameTicker.IsGameRuleActive(uid, ruleData) && _timing.CurTime >= stationEvent.EndTime)
|
||||
{
|
||||
GameTicker.EndGameRule(uid, ruleData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Helper Functions
|
||||
|
||||
protected void ForceEndSelf(EntityUid uid, GameRuleComponent? component = null)
|
||||
{
|
||||
GameTicker.EndGameRule(uid, component);
|
||||
}
|
||||
|
||||
protected bool TryFindRandomTile(out Vector2i tile, out EntityUid targetStation, out EntityUid targetGrid, out EntityCoordinates targetCoords)
|
||||
{
|
||||
tile = default;
|
||||
|
||||
targetCoords = EntityCoordinates.Invalid;
|
||||
if (StationSystem.Stations.Count == 0)
|
||||
{
|
||||
targetStation = EntityUid.Invalid;
|
||||
targetGrid = EntityUid.Invalid;
|
||||
return false;
|
||||
}
|
||||
targetStation = RobustRandom.Pick(StationSystem.Stations);
|
||||
var possibleTargets = Comp<StationDataComponent>(targetStation).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()
|
||||
{
|
||||
var ev = new GetSeverityModifierEvent();
|
||||
RaiseLocalEvent(ev);
|
||||
return ev.Modifier;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised broadcast to determine what the severity modifier should be for an event, some positive number that can be multiplied with various things.
|
||||
/// Handled by usually other game rules (like the ramping scheduler).
|
||||
/// Most events should try and make use of this if possible.
|
||||
/// </summary>
|
||||
public sealed class GetSeverityModifierEvent : EntityEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// An abstract entity system inherited by all station events for their behavior.
|
||||
/// Should be multiplied/added to rather than set, for commutativity.
|
||||
/// </summary>
|
||||
public abstract class StationEventSystem : GameRuleSystem
|
||||
{
|
||||
[Dependency] protected readonly IRobustRandom RobustRandom = default!;
|
||||
[Dependency] protected readonly IAdminLogManager AdminLogManager = default!;
|
||||
[Dependency] protected readonly IPrototypeManager PrototypeManager = default!;
|
||||
[Dependency] protected readonly IMapManager MapManager = default!;
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
|
||||
[Dependency] protected readonly ChatSystem ChatSystem = default!;
|
||||
[Dependency] protected readonly StationSystem StationSystem = default!;
|
||||
|
||||
protected ISawmill Sawmill = default!;
|
||||
|
||||
/// <summary>
|
||||
/// How long has the event existed. Do not change this.
|
||||
/// </summary>
|
||||
protected float Elapsed { get; set; }
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
Sawmill = Logger.GetSawmill("stationevents");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called once to setup the event after StartAfter has elapsed, or if an event is forcibly started.
|
||||
/// </summary>
|
||||
public override void Started()
|
||||
{
|
||||
AdminLogManager.Add(LogType.EventStarted, LogImpact.High, $"Event started: {Configuration.Id}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called once as soon as an event is added, for announcements.
|
||||
/// Can also be used for some initial setup.
|
||||
/// </summary>
|
||||
public override void Added()
|
||||
{
|
||||
AdminLogManager.Add(LogType.EventAnnounced, $"Event added / announced: {Configuration.Id}");
|
||||
|
||||
if (Configuration is not StationEventRuleConfiguration ev)
|
||||
return;
|
||||
|
||||
if (ev.StartAnnouncement != null)
|
||||
{
|
||||
ChatSystem.DispatchGlobalAnnouncement(Loc.GetString(ev.StartAnnouncement), playSound: false, colorOverride: Color.Gold);
|
||||
}
|
||||
|
||||
if (ev.StartAudio != null)
|
||||
{
|
||||
SoundSystem.Play(ev.StartAudio.GetSound(), Filter.Broadcast(), ev.StartAudio.Params);
|
||||
}
|
||||
|
||||
Elapsed = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called once when the station event ends for any reason.
|
||||
/// </summary>
|
||||
public override void Ended()
|
||||
{
|
||||
AdminLogManager.Add(LogType.EventStopped, $"Event ended: {Configuration.Id}");
|
||||
|
||||
if (Configuration is not StationEventRuleConfiguration ev)
|
||||
return;
|
||||
|
||||
if (ev.EndAnnouncement != null)
|
||||
{
|
||||
ChatSystem.DispatchGlobalAnnouncement(Loc.GetString(ev.EndAnnouncement), playSound: false, colorOverride: Color.Gold);
|
||||
}
|
||||
|
||||
if (ev.EndAudio != null)
|
||||
{
|
||||
SoundSystem.Play(ev.EndAudio.GetSound(), Filter.Broadcast(), ev.EndAudio.Params);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called every tick when this event is running.
|
||||
/// Events are responsible for their own lifetime, so this handles starting and ending after time.
|
||||
/// </summary>
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
if (!RuleAdded || Configuration is not StationEventRuleConfiguration data)
|
||||
return;
|
||||
|
||||
Elapsed += frameTime;
|
||||
|
||||
if (!RuleStarted && Elapsed >= data.StartAfter)
|
||||
{
|
||||
GameTicker.StartGameRule(PrototypeManager.Index<GameRulePrototype>(Prototype));
|
||||
}
|
||||
|
||||
if (RuleStarted && Elapsed >= data.EndAfter)
|
||||
{
|
||||
GameTicker.EndGameRule(PrototypeManager.Index<GameRulePrototype>(Prototype));
|
||||
}
|
||||
}
|
||||
|
||||
#region Helper Functions
|
||||
|
||||
protected void ForceEndSelf()
|
||||
{
|
||||
GameTicker.EndGameRule(PrototypeManager.Index<GameRulePrototype>(Prototype));
|
||||
}
|
||||
|
||||
protected bool TryFindRandomTile(out Vector2i tile, out EntityUid targetStation, out EntityUid targetGrid, out EntityCoordinates targetCoords)
|
||||
{
|
||||
tile = default;
|
||||
|
||||
targetCoords = EntityCoordinates.Invalid;
|
||||
if (StationSystem.Stations.Count == 0)
|
||||
{
|
||||
targetStation = EntityUid.Invalid;
|
||||
targetGrid = EntityUid.Invalid;
|
||||
return false;
|
||||
}
|
||||
targetStation = RobustRandom.Pick(StationSystem.Stations);
|
||||
var possibleTargets = Comp<StationDataComponent>(targetStation).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(targetGrid).GetWorldPositionRotationMatrix();
|
||||
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(gridComp.Owner, Transform(targetGrid).MapUid, tile,
|
||||
mapGridComp: gridComp)
|
||||
|| _atmosphere.IsTileAirBlocked(gridComp.Owner, tile, mapGridComp: gridComp))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
found = true;
|
||||
targetCoords = gridComp.GridTileToLocal(tile);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!found) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static GameRulePrototype GetRandomEventUnweighted(IPrototypeManager? prototypeManager = null, IRobustRandom? random = null)
|
||||
{
|
||||
IoCManager.Resolve(ref prototypeManager, ref random);
|
||||
|
||||
return random.Pick(prototypeManager.EnumeratePrototypes<GameRulePrototype>()
|
||||
.Where(p => p.Configuration is StationEventRuleConfiguration).ToArray());
|
||||
}
|
||||
|
||||
public float GetSeverityModifier()
|
||||
{
|
||||
var ev = new GetSeverityModifierEvent();
|
||||
RaiseLocalEvent(ev);
|
||||
return ev.Modifier;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised broadcast to determine what the severity modifier should be for an event, some positive number that can be multiplied with various things.
|
||||
/// Handled by usually other game rules (like the ramping scheduler).
|
||||
/// Most events should try and make use of this if possible.
|
||||
/// </summary>
|
||||
public sealed class GetSeverityModifierEvent : EntityEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Should be multiplied/added to rather than set, for commutativity.
|
||||
/// </summary>
|
||||
public float Modifier = 1.0f;
|
||||
}
|
||||
public float Modifier = 1.0f;
|
||||
}
|
||||
|
||||
@@ -8,23 +8,19 @@ using Robust.Shared.Random;
|
||||
using System.Linq;
|
||||
using Content.Server.Chemistry.Components;
|
||||
using Content.Server.Fluids.EntitySystems;
|
||||
using Robust.Server.GameObjects;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.StationEvents.Components;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class VentClog : StationEventSystem
|
||||
public sealed class VentClogRule : StationEventSystem<VentClogRuleComponent>
|
||||
{
|
||||
public override string Prototype => "VentClog";
|
||||
[Dependency] private readonly SmokeSystem _smoke = default!;
|
||||
|
||||
public readonly IReadOnlyList<string> SafeishVentChemicals = new[]
|
||||
protected override void Started(EntityUid uid, VentClogRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
"Water", "Blood", "Slime", "SpaceDrugs", "SpaceCleaner", "Nutriment", "Sugar", "SpaceLube", "Ephedrine", "Ale", "Beer"
|
||||
};
|
||||
|
||||
public override void Started()
|
||||
{
|
||||
base.Started();
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
if (StationSystem.Stations.Count == 0)
|
||||
return;
|
||||
@@ -57,15 +53,14 @@ public sealed class VentClog : StationEventSystem
|
||||
}
|
||||
else
|
||||
{
|
||||
solution.AddReagent(RobustRandom.Pick(SafeishVentChemicals), 200);
|
||||
solution.AddReagent(RobustRandom.Pick(component.SafeishVentChemicals), 200);
|
||||
}
|
||||
|
||||
var foamEnt = Spawn("Foam", transform.Coordinates);
|
||||
var smoke = EnsureComp<SmokeComponent>(foamEnt);
|
||||
smoke.SpreadAmount = 20;
|
||||
EntityManager.System<SmokeSystem>().Start(foamEnt, smoke, solution, 20f);
|
||||
EntityManager.System<AudioSystem>().PlayPvs(sound, transform.Coordinates);
|
||||
_smoke.Start(foamEnt, smoke, solution, 20f);
|
||||
Audio.PlayPvs(sound, transform.Coordinates);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
using Content.Server.StationEvents.Components;
|
||||
using Content.Shared.Actions;
|
||||
using Robust.Shared.Random;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
public sealed class VentCritters : StationEventSystem
|
||||
{
|
||||
public static List<string> SpawnedPrototypeChoices = new List<string>()
|
||||
{"MobMouse", "MobMouse1", "MobMouse2"};
|
||||
|
||||
public override string Prototype => "VentCritters";
|
||||
|
||||
public override void Started()
|
||||
{
|
||||
base.Started();
|
||||
var spawnChoice = RobustRandom.Pick(SpawnedPrototypeChoices);
|
||||
var spawnLocations = EntityManager.EntityQuery<VentCritterSpawnLocationComponent>().ToList();
|
||||
RobustRandom.Shuffle(spawnLocations);
|
||||
|
||||
var spawnAmount = (int) (RobustRandom.Next(4, 12)); // A small colony of critters.
|
||||
Sawmill.Info($"Spawning {spawnAmount} of {spawnChoice}");
|
||||
foreach (var location in spawnLocations)
|
||||
{
|
||||
if (spawnAmount-- == 0)
|
||||
break;
|
||||
|
||||
var coords = EntityManager.GetComponent<TransformComponent>(location.Owner);
|
||||
|
||||
EntityManager.SpawnEntity(spawnChoice, coords.Coordinates);
|
||||
}
|
||||
}
|
||||
}
|
||||
29
Content.Server/StationEvents/Events/VentCrittersRule.cs
Normal file
29
Content.Server/StationEvents/Events/VentCrittersRule.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using Content.Server.StationEvents.Components;
|
||||
using Robust.Shared.Random;
|
||||
using System.Linq;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
public sealed class VentCrittersRule : StationEventSystem<VentCrittersRuleComponent>
|
||||
{
|
||||
protected override void Started(EntityUid uid, VentCrittersRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
var spawnChoice = RobustRandom.Pick(component.SpawnedPrototypeChoices);
|
||||
var spawnLocations = EntityManager.EntityQuery<VentCritterSpawnLocationComponent>().ToList();
|
||||
RobustRandom.Shuffle(spawnLocations);
|
||||
|
||||
var spawnAmount = RobustRandom.Next(4, 12); // A small colony of critters.
|
||||
Sawmill.Info($"Spawning {spawnAmount} of {spawnChoice}");
|
||||
foreach (var location in spawnLocations)
|
||||
{
|
||||
if (spawnAmount-- == 0)
|
||||
break;
|
||||
|
||||
var coords = Transform(location.Owner);
|
||||
Spawn(spawnChoice, coords.Coordinates);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.GameTicking.Rules;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.StationEvents.Components;
|
||||
using Content.Server.StationEvents.Events;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Shared.Configuration;
|
||||
@@ -7,35 +9,20 @@ using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.StationEvents;
|
||||
|
||||
public sealed class RampingStationEventSchedulerSystem : GameRuleSystem
|
||||
public sealed class RampingStationEventSchedulerSystem : GameRuleSystem<RampingStationEventSchedulerComponent>
|
||||
{
|
||||
public override string Prototype => "RampingStationEventScheduler";
|
||||
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly EventManagerSystem _event = default!;
|
||||
[Dependency] private readonly GameTicker _gameTicker = default!;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
private float _endTime;
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
private float _maxChaos;
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
private float _startingChaos;
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
private float _timeUntilNextEvent;
|
||||
|
||||
[ViewVariables]
|
||||
public float ChaosModifier
|
||||
public float GetChaosModifier(EntityUid uid, RampingStationEventSchedulerComponent component)
|
||||
{
|
||||
get
|
||||
{
|
||||
var roundTime = (float) _gameTicker.RoundDuration().TotalSeconds;
|
||||
if (roundTime > _endTime)
|
||||
return _maxChaos;
|
||||
var roundTime = (float) _gameTicker.RoundDuration().TotalSeconds;
|
||||
if (roundTime > component.EndTime)
|
||||
return component.MaxChaos;
|
||||
|
||||
return (_maxChaos / _endTime) * roundTime + _startingChaos;
|
||||
}
|
||||
return component.MaxChaos / component.EndTime * roundTime + component.StartingChaos;
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
@@ -45,60 +32,65 @@ public sealed class RampingStationEventSchedulerSystem : GameRuleSystem
|
||||
SubscribeLocalEvent<GetSeverityModifierEvent>(OnGetSeverityModifier);
|
||||
}
|
||||
|
||||
public override void Started()
|
||||
protected override void Started(EntityUid uid, RampingStationEventSchedulerComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
var avgChaos = _cfg.GetCVar(CCVars.EventsRampingAverageChaos);
|
||||
var avgTime = _cfg.GetCVar(CCVars.EventsRampingAverageEndTime);
|
||||
|
||||
// Worlds shittiest probability distribution
|
||||
// Got a complaint? Send them to
|
||||
_maxChaos = _random.NextFloat(avgChaos - avgChaos / 4, avgChaos + avgChaos / 4);
|
||||
component.MaxChaos = _random.NextFloat(avgChaos - avgChaos / 4, avgChaos + avgChaos / 4);
|
||||
// This is in minutes, so *60 for seconds (for the chaos calc)
|
||||
_endTime = _random.NextFloat(avgTime - avgTime / 4, avgTime + avgTime / 4) * 60f;
|
||||
_startingChaos = _maxChaos / 10;
|
||||
component.EndTime = _random.NextFloat(avgTime - avgTime / 4, avgTime + avgTime / 4) * 60f;
|
||||
component.StartingChaos = component.MaxChaos / 10;
|
||||
|
||||
PickNextEventTime();
|
||||
}
|
||||
|
||||
public override void Ended()
|
||||
{
|
||||
_endTime = 0f;
|
||||
_maxChaos = 0f;
|
||||
_startingChaos = 0f;
|
||||
_timeUntilNextEvent = 0f;
|
||||
PickNextEventTime(uid, component);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
if (!RuleStarted || !_event.EventsEnabled)
|
||||
if (!_event.EventsEnabled)
|
||||
return;
|
||||
|
||||
if (_timeUntilNextEvent > 0f)
|
||||
var query = EntityQueryEnumerator<RampingStationEventSchedulerComponent, GameRuleComponent>();
|
||||
while (query.MoveNext(out var uid, out var scheduler, out var gameRule))
|
||||
{
|
||||
_timeUntilNextEvent -= frameTime;
|
||||
return;
|
||||
}
|
||||
if (!GameTicker.IsGameRuleActive(uid, gameRule))
|
||||
return;
|
||||
|
||||
PickNextEventTime();
|
||||
_event.RunRandomEvent();
|
||||
if (scheduler.TimeUntilNextEvent > 0f)
|
||||
{
|
||||
scheduler.TimeUntilNextEvent -= frameTime;
|
||||
return;
|
||||
}
|
||||
|
||||
PickNextEventTime(uid, scheduler);
|
||||
_event.RunRandomEvent();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGetSeverityModifier(GetSeverityModifierEvent ev)
|
||||
{
|
||||
if (!RuleStarted)
|
||||
return;
|
||||
var query = EntityQueryEnumerator<RampingStationEventSchedulerComponent, GameRuleComponent>();
|
||||
while (query.MoveNext(out var uid, out var scheduler, out var gameRule))
|
||||
{
|
||||
if (!GameTicker.IsGameRuleActive(uid, gameRule))
|
||||
return;
|
||||
|
||||
ev.Modifier *= ChaosModifier;
|
||||
Logger.Info($"Ramping set modifier to {ev.Modifier}");
|
||||
ev.Modifier *= GetChaosModifier(uid, scheduler);
|
||||
Logger.Info($"Ramping set modifier to {ev.Modifier}");
|
||||
}
|
||||
}
|
||||
|
||||
private void PickNextEventTime()
|
||||
private void PickNextEventTime(EntityUid uid, RampingStationEventSchedulerComponent component)
|
||||
{
|
||||
var mod = ChaosModifier;
|
||||
var mod = GetChaosModifier(uid, component);
|
||||
|
||||
// 4-12 minutes baseline. Will get faster over time as the chaos mod increases.
|
||||
_timeUntilNextEvent = _random.NextFloat(240f / mod, 720f / mod);
|
||||
component.TimeUntilNextEvent = _random.NextFloat(240f / mod, 720f / mod);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user