2024-03-17 21:30:27 +01:00
using System.Linq ;
2022-10-18 19:51:47 -07:00
using Content.Server.GameTicking ;
2024-08-05 05:17:53 +02:00
using Content.Server.RoundEnd ;
2023-04-25 20:23:14 -04:00
using Content.Server.StationEvents.Components ;
2022-10-18 19:51:47 -07:00
using Content.Shared.CCVar ;
using Robust.Server.Player ;
using Robust.Shared.Configuration ;
using Robust.Shared.Prototypes ;
using Robust.Shared.Random ;
2024-08-14 01:21:01 -04:00
using Content.Shared.EntityTable.EntitySelectors ;
using Content.Shared.EntityTable ;
2022-10-18 19:51:47 -07:00
namespace Content.Server.StationEvents ;
public sealed class EventManagerSystem : EntitySystem
{
[Dependency] private readonly IConfigurationManager _configurationManager = default ! ;
[Dependency] private readonly IPlayerManager _playerManager = default ! ;
[Dependency] private readonly IRobustRandom _random = default ! ;
[Dependency] private readonly IPrototypeManager _prototype = default ! ;
2024-08-14 01:21:01 -04:00
[Dependency] private readonly EntityTableSystem _entityTable = default ! ;
2022-10-18 19:51:47 -07:00
[Dependency] public readonly GameTicker GameTicker = default ! ;
2024-08-05 05:17:53 +02:00
[Dependency] private readonly RoundEndSystem _roundEnd = default ! ;
2022-10-18 19:51:47 -07:00
public bool EventsEnabled { get ; private set ; }
private void SetEnabled ( bool value ) = > EventsEnabled = value ;
public override void Initialize ( )
{
base . Initialize ( ) ;
2024-02-13 22:48:39 +01:00
Subs . CVar ( _configurationManager , CCVars . EventsEnabled , SetEnabled , true ) ;
2022-10-18 19:51:47 -07:00
}
/// <summary>
/// Randomly runs a valid event.
/// </summary>
2024-08-14 01:21:01 -04:00
[Obsolete("use overload taking EnityTableSelector instead or risk unexpected results")]
public void RunRandomEvent ( )
2022-10-18 19:51:47 -07:00
{
var randomEvent = PickRandomEvent ( ) ;
2023-04-25 20:23:14 -04:00
if ( randomEvent = = null )
2022-10-18 19:51:47 -07:00
{
var errStr = Loc . GetString ( "station-event-system-run-random-event-no-valid-events" ) ;
2024-03-17 21:30:27 +01:00
Log . Error ( errStr ) ;
2024-08-14 01:21:01 -04:00
return ;
2022-10-18 19:51:47 -07:00
}
2024-08-14 01:21:01 -04:00
GameTicker . AddGameRule ( randomEvent ) ;
}
/// <summary>
/// Randomly runs an event from provided EntityTableSelector.
/// </summary>
public void RunRandomEvent ( EntityTableSelector limitedEventsTable )
{
if ( ! TryBuildLimitedEvents ( limitedEventsTable , out var limitedEvents ) )
{
Log . Warning ( "Provided event table could not build dict!" ) ;
return ;
}
var randomLimitedEvent = FindEvent ( limitedEvents ) ; // this picks the event, It might be better to use the GetSpawns to do it, but that will be a major rebalancing fuck.
if ( randomLimitedEvent = = null )
{
Log . Warning ( "The selected random event is null!" ) ;
return ;
}
if ( ! _prototype . TryIndex ( randomLimitedEvent , out _ ) )
{
Log . Warning ( "A requested event is not available!" ) ;
return ;
}
GameTicker . AddGameRule ( randomLimitedEvent ) ;
}
/// <summary>
/// Returns true if the provided EntityTableSelector gives at least one prototype with a StationEvent comp.
/// </summary>
public bool TryBuildLimitedEvents ( EntityTableSelector limitedEventsTable , out Dictionary < EntityPrototype , StationEventComponent > limitedEvents )
{
limitedEvents = new Dictionary < EntityPrototype , StationEventComponent > ( ) ;
var availableEvents = AvailableEvents ( ) ; // handles the player counts and individual event restrictions
if ( availableEvents . Count = = 0 )
{
Log . Warning ( "No events were available to run!" ) ;
return false ;
}
var selectedEvents = _entityTable . GetSpawns ( limitedEventsTable ) ;
if ( selectedEvents . Any ( ) ! = true ) // This is here so if you fuck up the table it wont die.
return false ;
foreach ( var eventid in selectedEvents )
{
if ( ! _prototype . TryIndex ( eventid , out var eventproto ) )
{
Log . Warning ( "An event ID has no prototype index!" ) ;
continue ;
}
if ( limitedEvents . ContainsKey ( eventproto ) ) // This stops it from dying if you add duplicate entries in a fucked table
continue ;
if ( eventproto . Abstract )
continue ;
if ( ! eventproto . TryGetComponent < StationEventComponent > ( out var stationEvent , EntityManager . ComponentFactory ) )
continue ;
if ( ! availableEvents . ContainsKey ( eventproto ) )
continue ;
limitedEvents . Add ( eventproto , stationEvent ) ;
}
if ( ! limitedEvents . Any ( ) )
return false ;
return true ;
2022-10-18 19:51:47 -07:00
}
/// <summary>
/// Randomly picks a valid event.
/// </summary>
2023-04-25 20:23:14 -04:00
public string? PickRandomEvent ( )
2022-10-18 19:51:47 -07:00
{
var availableEvents = AvailableEvents ( ) ;
2024-03-17 21:30:27 +01:00
Log . Info ( $"Picking from {availableEvents.Count} total available events" ) ;
2022-10-18 19:51:47 -07:00
return FindEvent ( availableEvents ) ;
}
/// <summary>
/// Pick a random event from the available events at this time, also considering their weightings.
/// </summary>
/// <returns></returns>
2024-05-06 00:33:30 -07:00
public string? FindEvent ( Dictionary < EntityPrototype , StationEventComponent > availableEvents )
2022-10-18 19:51:47 -07:00
{
if ( availableEvents . Count = = 0 )
{
2024-03-17 21:30:27 +01:00
Log . Warning ( "No events were available to run!" ) ;
2022-10-18 19:51:47 -07:00
return null ;
}
2024-12-05 23:52:02 -05:00
var sumOfWeights = 0.0f ;
2022-10-18 19:51:47 -07:00
2023-04-25 20:23:14 -04:00
foreach ( var stationEvent in availableEvents . Values )
2022-10-18 19:51:47 -07:00
{
2024-12-05 23:52:02 -05:00
sumOfWeights + = stationEvent . Weight ;
2022-10-18 19:51:47 -07:00
}
2024-12-05 23:52:02 -05:00
sumOfWeights = _random . NextFloat ( sumOfWeights ) ;
2022-10-18 19:51:47 -07:00
2023-04-25 20:23:14 -04:00
foreach ( var ( proto , stationEvent ) in availableEvents )
2022-10-18 19:51:47 -07:00
{
2024-12-05 23:52:02 -05:00
sumOfWeights - = stationEvent . Weight ;
2022-10-18 19:51:47 -07:00
2024-12-05 23:52:02 -05:00
if ( sumOfWeights < = 0.0f )
2022-10-18 19:51:47 -07:00
{
2023-04-25 20:23:14 -04:00
return proto . ID ;
2022-10-18 19:51:47 -07:00
}
}
2024-03-17 21:30:27 +01:00
Log . Error ( "Event was not found after weighted pick process!" ) ;
2022-10-18 19:51:47 -07:00
return null ;
}
/// <summary>
/// Gets the events that have met their player count, time-until start, etc.
/// </summary>
2024-05-06 00:33:30 -07:00
/// <param name="playerCountOverride">Override for player count, if using this to simulate events rather than in an actual round.</param>
/// <param name="currentTimeOverride">Override for round time, if using this to simulate events rather than in an actual round.</param>
2022-10-18 19:51:47 -07:00
/// <returns></returns>
2024-05-06 00:33:30 -07:00
public Dictionary < EntityPrototype , StationEventComponent > AvailableEvents (
bool ignoreEarliestStart = false ,
int? playerCountOverride = null ,
TimeSpan ? currentTimeOverride = null )
2022-10-18 19:51:47 -07:00
{
2024-05-06 00:33:30 -07:00
var playerCount = playerCountOverride ? ? _playerManager . PlayerCount ;
2022-10-18 19:51:47 -07:00
// playerCount does a lock so we'll just keep the variable here
2024-05-06 00:33:30 -07:00
var currentTime = currentTimeOverride ? ? ( ! ignoreEarliestStart
2023-04-25 20:23:14 -04:00
? GameTicker . RoundDuration ( )
2024-05-06 00:33:30 -07:00
: TimeSpan . Zero ) ;
2022-10-18 19:51:47 -07:00
2023-04-25 20:23:14 -04:00
var result = new Dictionary < EntityPrototype , StationEventComponent > ( ) ;
2022-10-18 19:51:47 -07:00
2023-04-25 20:23:14 -04:00
foreach ( var ( proto , stationEvent ) in AllEvents ( ) )
2022-10-18 19:51:47 -07:00
{
2023-04-25 20:23:14 -04:00
if ( CanRun ( proto , stationEvent , playerCount , currentTime ) )
2022-10-18 19:51:47 -07:00
{
2023-04-25 20:23:14 -04:00
result . Add ( proto , stationEvent ) ;
2022-10-18 19:51:47 -07:00
}
}
return result ;
}
2023-04-25 20:23:14 -04:00
public Dictionary < EntityPrototype , StationEventComponent > AllEvents ( )
{
var allEvents = new Dictionary < EntityPrototype , StationEventComponent > ( ) ;
foreach ( var prototype in _prototype . EnumeratePrototypes < EntityPrototype > ( ) )
{
if ( prototype . Abstract )
continue ;
2025-02-24 20:45:00 +03:00
if ( ! prototype . TryGetComponent < StationEventComponent > ( out var stationEvent , EntityManager . ComponentFactory ) )
2023-04-25 20:23:14 -04:00
continue ;
allEvents . Add ( prototype , stationEvent ) ;
}
return allEvents ;
}
private int GetOccurrences ( EntityPrototype stationEvent )
2022-10-18 19:51:47 -07:00
{
2023-04-25 20:23:14 -04:00
return GetOccurrences ( stationEvent . ID ) ;
2022-10-18 19:51:47 -07:00
}
2023-04-25 20:23:14 -04:00
private int GetOccurrences ( string stationEvent )
2022-10-18 19:51:47 -07:00
{
2023-04-25 20:23:14 -04:00
return GameTicker . AllPreviousGameRules . Count ( p = > p . Item2 = = stationEvent ) ;
2022-10-18 19:51:47 -07:00
}
2023-04-25 20:23:14 -04:00
public TimeSpan TimeSinceLastEvent ( EntityPrototype stationEvent )
2022-10-18 19:51:47 -07:00
{
foreach ( var ( time , rule ) in GameTicker . AllPreviousGameRules . Reverse ( ) )
{
2023-04-25 20:23:14 -04:00
if ( rule = = stationEvent . ID )
2022-10-18 19:51:47 -07:00
return time ;
}
return TimeSpan . Zero ;
}
2023-04-25 20:23:14 -04:00
private bool CanRun ( EntityPrototype prototype , StationEventComponent stationEvent , int playerCount , TimeSpan currentTime )
2022-10-18 19:51:47 -07:00
{
2023-04-25 20:23:14 -04:00
if ( GameTicker . IsGameRuleActive ( prototype . ID ) )
2022-10-18 19:51:47 -07:00
return false ;
2023-04-25 20:23:14 -04:00
if ( stationEvent . MaxOccurrences . HasValue & & GetOccurrences ( prototype ) > = stationEvent . MaxOccurrences . Value )
2022-10-18 19:51:47 -07:00
{
return false ;
}
if ( playerCount < stationEvent . MinimumPlayers )
{
return false ;
}
if ( currentTime ! = TimeSpan . Zero & & currentTime . TotalMinutes < stationEvent . EarliestStart )
{
return false ;
}
2023-04-25 20:23:14 -04:00
var lastRun = TimeSinceLastEvent ( prototype ) ;
2022-10-18 19:51:47 -07:00
if ( lastRun ! = TimeSpan . Zero & & currentTime . TotalMinutes <
stationEvent . ReoccurrenceDelay + lastRun . TotalMinutes )
{
return false ;
}
2024-08-05 05:17:53 +02:00
if ( _roundEnd . IsRoundEndRequested ( ) & & ! stationEvent . OccursDuringRoundEnd )
{
return false ;
}
2022-10-18 19:51:47 -07:00
return true ;
}
}