* Partial work on StationSystem refactor. * WIP station jobs API. * forgor to fire off grid events. * Partial implementation of StationSpawningSystem * whoops infinite loop. * Spawners should work now. * it compiles. * tfw * Vestigial code cleanup. * fix station deletion. * attempt to make tests go brr * add latejoin spawnpoints to test maps. * make sure the station still exists while destructing spawners. * forgot an exists check. * destruction order check. * hopefully fix final test. * fail-safe radstorm. * Deep-clean job code further. This is bugged!!!!! * Fix job bug. (init order moment) * whooo cleanup * New job selection algorithm that tries to distribute fairly across stations. * small nitpicks * Give the heads their weights to replace the head field. * make overflow assign take a station list. * moment * Fixes and test #1 of many. * please fix nullspace * AssignJobs should no longer even consider showing up on a trace. * add comment. * Introduce station configs, praying i didn't miss something. * in one small change stations are now fully serializable. * Further doc comments. * whoops. * Solve bug where assignjobs didn't account for roundstart. * Fix spawning, improve the API. Caught an oversight in stationsystem that should've broke everything but didn't, whoops. * Goodbye JobController. * minor fix.. * fix test fail, remove debug logs. * quick serialization fixes. * fixes.. * sus * partialing * Update Content.Server/Station/Systems/StationJobsSystem.Roundstart.cs Co-authored-by: Kara <lunarautomaton6@gmail.com> * Use dirtying to avoid rebuilding the list 2,100 times. * add a bajillion more lines of docs (mostly in AssignJobs so i don't ever forget how it works) * Update Content.IntegrationTests/Tests/Station/StationJobsTest.cs Co-authored-by: Kara <lunarautomaton6@gmail.com> * Add the Mysteriously Missing Captain Check. * Put maprender back the way it belongs. * I love addressing reviews. * Update Content.Server/Station/Systems/StationJobsSystem.cs Co-authored-by: Kara <lunarautomaton6@gmail.com> * doc cleanup. * Fix bureaucratic error, add job slot tests. * zero cost abstractions when * cri * saner error. * Fix spawning failing certain tests due to gameticker not handling falliability correctly. Can't fix this until I refactor the rest of spawning code. * submodule gaming * Packedenger. * Documentation consistency. Co-authored-by: Kara <lunarautomaton6@gmail.com>
233 lines
7.9 KiB
C#
233 lines
7.9 KiB
C#
using System.Linq;
|
|
using Content.Server.Administration.Logs;
|
|
using Content.Server.Atmos.EntitySystems;
|
|
using Content.Server.Chat.Managers;
|
|
using Content.Server.Station.Components;
|
|
using Content.Server.Station.Systems;
|
|
using Content.Shared.Database;
|
|
using Content.Shared.Sound;
|
|
using Robust.Shared.Audio;
|
|
using Robust.Shared.Map;
|
|
using Robust.Shared.Player;
|
|
using Robust.Shared.Random;
|
|
|
|
namespace Content.Server.StationEvents.Events
|
|
{
|
|
public abstract class StationEvent
|
|
{
|
|
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;
|
|
|
|
/// <summary>
|
|
/// If the event has started and is currently running.
|
|
/// </summary>
|
|
public bool Running { get; set; }
|
|
|
|
/// <summary>
|
|
/// Human-readable name for the event.
|
|
/// </summary>
|
|
public abstract string Name { get; }
|
|
|
|
/// <summary>
|
|
/// The weight this event has in the random-selection process.
|
|
/// </summary>
|
|
public virtual float Weight => WeightNormal;
|
|
|
|
/// <summary>
|
|
/// What should be said in chat when the event starts (if anything).
|
|
/// </summary>
|
|
public virtual string? StartAnnouncement { get; set; } = null;
|
|
|
|
/// <summary>
|
|
/// What should be said in chat when the event ends (if anything).
|
|
/// </summary>
|
|
protected virtual string? EndAnnouncement { get; } = null;
|
|
|
|
/// <summary>
|
|
/// Starting audio of the event.
|
|
/// </summary>
|
|
public virtual SoundSpecifier? StartAudio { get; set; } = new SoundPathSpecifier("/Audio/Announcements/attention.ogg");
|
|
|
|
/// <summary>
|
|
/// Ending audio of the event.
|
|
/// </summary>
|
|
public virtual SoundSpecifier? EndAudio { get; } = null;
|
|
|
|
public virtual AudioParams AudioParams { get; } = AudioParams.Default.WithVolume(-10f);
|
|
|
|
/// <summary>
|
|
/// In minutes, when is the first round time this event can start
|
|
/// </summary>
|
|
public virtual int EarliestStart { get; } = 5;
|
|
|
|
/// <summary>
|
|
/// When in the lifetime to call Start().
|
|
/// </summary>
|
|
protected virtual float StartAfter { get; } = 0.0f;
|
|
|
|
/// <summary>
|
|
/// When in the lifetime the event should end.
|
|
/// </summary>
|
|
protected virtual float EndAfter { get; set; } = 0.0f;
|
|
|
|
/// <summary>
|
|
/// How long has the event existed. Do not change this.
|
|
/// </summary>
|
|
private float Elapsed { get; set; } = 0.0f;
|
|
|
|
/// <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>
|
|
public virtual int MinimumPlayers { get; } = 0;
|
|
|
|
/// <summary>
|
|
/// How many times this event has run this round
|
|
/// </summary>
|
|
public int Occurrences { get; set; } = 0;
|
|
|
|
/// <summary>
|
|
/// How many times this even can occur in a single round
|
|
/// </summary>
|
|
public virtual int? MaxOccurrences { get; } = null;
|
|
|
|
/// <summary>
|
|
/// Has the startup time elapsed?
|
|
/// </summary>
|
|
protected bool Started { get; set; } = false;
|
|
|
|
/// <summary>
|
|
/// Has this event commenced (announcement may or may not be used)?
|
|
/// </summary>
|
|
private bool Announced { get; set; } = false;
|
|
|
|
/// <summary>
|
|
/// Called once to setup the event after StartAfter has elapsed.
|
|
/// </summary>
|
|
public virtual void Startup()
|
|
{
|
|
Started = true;
|
|
Occurrences += 1;
|
|
|
|
EntitySystem.Get<AdminLogSystem>()
|
|
.Add(LogType.EventStarted, LogImpact.High, $"Event startup: {Name}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called once as soon as an event is active.
|
|
/// Can also be used for some initial setup.
|
|
/// </summary>
|
|
public virtual void Announce()
|
|
{
|
|
EntitySystem.Get<AdminLogSystem>()
|
|
.Add(LogType.EventAnnounced, $"Event announce: {Name}");
|
|
|
|
if (StartAnnouncement != null)
|
|
{
|
|
var chatManager = IoCManager.Resolve<IChatManager>();
|
|
chatManager.DispatchStationAnnouncement(StartAnnouncement, playDefaultSound: false, colorOverride: Color.Gold);
|
|
}
|
|
|
|
if (StartAudio != null)
|
|
{
|
|
SoundSystem.Play(Filter.Broadcast(), StartAudio.GetSound(), AudioParams);
|
|
}
|
|
|
|
Announced = true;
|
|
Running = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called once when the station event ends for any reason.
|
|
/// </summary>
|
|
public virtual void Shutdown()
|
|
{
|
|
EntitySystem.Get<AdminLogSystem>()
|
|
.Add(LogType.EventStopped, $"Event shutdown: {Name}");
|
|
|
|
if (EndAnnouncement != null)
|
|
{
|
|
var chatManager = IoCManager.Resolve<IChatManager>();
|
|
chatManager.DispatchStationAnnouncement(EndAnnouncement, playDefaultSound: false, colorOverride: Color.Gold);
|
|
}
|
|
|
|
if (EndAudio != null)
|
|
{
|
|
SoundSystem.Play(Filter.Broadcast(), EndAudio.GetSound(), AudioParams);
|
|
}
|
|
|
|
Started = false;
|
|
Announced = false;
|
|
Elapsed = 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called every tick when this event is running.
|
|
/// </summary>
|
|
/// <param name="frameTime"></param>
|
|
public virtual void Update(float frameTime)
|
|
{
|
|
Elapsed += frameTime;
|
|
|
|
if (!Started && Elapsed >= StartAfter)
|
|
{
|
|
Startup();
|
|
}
|
|
|
|
if (EndAfter <= Elapsed)
|
|
{
|
|
Running = false;
|
|
}
|
|
}
|
|
|
|
|
|
public static bool TryFindRandomTile(out Vector2i tile, out EntityUid targetStation, out EntityUid targetGrid, out EntityCoordinates targetCoords, IRobustRandom? robustRandom = null, IEntityManager? entityManager = null, IMapManager? mapManager = null, StationSystem? stationSystem = null)
|
|
{
|
|
tile = default;
|
|
IoCManager.Resolve(ref robustRandom, ref entityManager, ref mapManager);
|
|
entityManager.EntitySysManager.Resolve(ref stationSystem);
|
|
|
|
targetCoords = EntityCoordinates.Invalid;
|
|
targetStation = robustRandom.Pick(stationSystem.Stations);
|
|
var possibleTargets = entityManager.GetComponent<StationDataComponent>(targetStation).Grids;
|
|
if (possibleTargets.Count == 0)
|
|
{
|
|
targetGrid = EntityUid.Invalid;
|
|
return false;
|
|
}
|
|
|
|
targetGrid = robustRandom.Pick(possibleTargets);
|
|
|
|
if (!entityManager.TryGetComponent<IMapGridComponent>(targetGrid, out var gridComp))
|
|
return false;
|
|
var grid = gridComp.Grid;
|
|
|
|
var atmosphereSystem = EntitySystem.Get<AtmosphereSystem>();
|
|
var found = false;
|
|
var gridBounds = grid.WorldBounds;
|
|
var gridPos = grid.WorldPosition;
|
|
|
|
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 (atmosphereSystem.IsTileSpace(grid, tile) || atmosphereSystem.IsTileAirBlocked(grid, tile)) continue;
|
|
found = true;
|
|
targetCoords = grid.GridTileToLocal(tile);
|
|
break;
|
|
}
|
|
|
|
if (!found) return false;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|