using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading.Tasks;
using Content.Server.GameTicking.Presets;
using Content.Server.Maps;
using Content.Shared.CCVar;
using JetBrains.Annotations;
using Robust.Shared.Player;
namespace Content.Server.GameTicking;
public sealed partial class GameTicker
{
public const float PresetFailedCooldownIncrease = 30f;
///
/// The selected preset that will be used at the start of the next round.
///
public GamePresetPrototype? Preset { get; private set; }
///
/// The preset that's currently active.
///
public GamePresetPrototype? CurrentPreset { get; private set; }
///
/// Countdown to the preset being reset to the server default.
///
public int? ResetCountdown;
private bool StartPreset(ICommonSession[] origReadyPlayers, bool force)
{
var startAttempt = new RoundStartAttemptEvent(origReadyPlayers, force);
RaiseLocalEvent(startAttempt);
if (!startAttempt.Cancelled)
return true;
var presetTitle = CurrentPreset != null ? Loc.GetString(CurrentPreset.ModeTitle) : string.Empty;
void FailedPresetRestart()
{
SendServerMessage(Loc.GetString("game-ticker-start-round-cannot-start-game-mode-restart",
("failedGameMode", presetTitle)));
RestartRound();
DelayStart(TimeSpan.FromSeconds(PresetFailedCooldownIncrease));
}
if (_cfg.GetCVar(CCVars.GameLobbyFallbackEnabled))
{
var fallbackPresets = _cfg.GetCVar(CCVars.GameLobbyFallbackPreset).Split(",");
var startFailed = true;
foreach (var preset in fallbackPresets)
{
ClearGameRules();
SetGamePreset(preset);
AddGamePresetRules();
StartGamePresetRules();
startAttempt.Uncancel();
RaiseLocalEvent(startAttempt);
if (!startAttempt.Cancelled)
{
_chatManager.SendAdminAnnouncement(
Loc.GetString("game-ticker-start-round-cannot-start-game-mode-fallback",
("failedGameMode", presetTitle),
("fallbackMode", Loc.GetString(preset))));
RefreshLateJoinAllowed();
startFailed = false;
break;
}
}
if (startFailed)
{
FailedPresetRestart();
return false;
}
}
else
{
FailedPresetRestart();
return false;
}
return true;
}
private void InitializeGamePreset()
{
SetGamePreset(LobbyEnabled ? _cfg.GetCVar(CCVars.GameLobbyDefaultPreset) : "sandbox");
}
public void SetGamePreset(GamePresetPrototype? preset, bool force = false, int? resetDelay = null)
{
// Do nothing if this game ticker is a dummy!
if (DummyTicker)
return;
if (resetDelay is not null)
{
ResetCountdown = resetDelay.Value;
// Reset counter is checked and changed at the end of each round
// So if the game is in the lobby, the first requested round will happen before the check, and we need one less check
if (CurrentPreset is null)
ResetCountdown = resetDelay.Value - 1;
}
else
{
ResetCountdown = null;
}
Preset = preset;
ValidateMap();
UpdateInfoText();
if (force)
{
StartRound(true);
}
}
public void SetGamePreset(string preset, bool force = false)
{
var proto = FindGamePreset(preset);
if(proto != null)
SetGamePreset(proto, force);
}
public GamePresetPrototype? FindGamePreset(string preset)
{
if (_prototypeManager.TryIndex(preset, out GamePresetPrototype? presetProto))
return presetProto;
foreach (var proto in _prototypeManager.EnumeratePrototypes())
{
foreach (var alias in proto.Alias)
{
if (preset.Equals(alias, StringComparison.InvariantCultureIgnoreCase))
return proto;
}
}
return null;
}
public bool TryFindGamePreset(string preset, [NotNullWhen(true)] out GamePresetPrototype? prototype)
{
prototype = FindGamePreset(preset);
return prototype != null;
}
public bool IsMapEligible(GameMapPrototype map)
{
if (Preset == null)
return true;
if (Preset.MapPool == null || !_prototypeManager.TryIndex(Preset.MapPool, out var pool))
return true;
return pool.Maps.Contains(map.ID);
}
private void ValidateMap()
{
if (Preset == null || _gameMapManager.GetSelectedMap() is not { } map)
return;
if (Preset.MapPool == null ||
!_prototypeManager.TryIndex(Preset.MapPool, out var pool))
return;
if (pool.Maps.Contains(map.ID))
return;
_gameMapManager.SelectMapRandom();
}
[PublicAPI]
private bool AddGamePresetRules()
{
if (DummyTicker || Preset == null)
return false;
CurrentPreset = Preset;
foreach (var rule in Preset.Rules)
{
AddGameRule(rule);
}
return true;
}
private void TryResetPreset()
{
if (ResetCountdown is null || ResetCountdown-- > 0)
return;
InitializeGamePreset();
ResetCountdown = null;
}
public void StartGamePresetRules()
{
// May be touched by the preset during init.
var rules = new List(GetAddedGameRules());
foreach (var rule in rules)
{
StartGameRule(rule);
}
}
private void IncrementRoundNumber()
{
var playerIds = _playerGameStatuses.Keys.Select(player => player.UserId).ToArray();
var serverName = _cfg.GetCVar(CCVars.AdminLogsServerName);
// TODO FIXME AAAAAAAAAAAAAAAAAAAH THIS IS BROKEN
// Task.Run as a terrible dirty workaround to avoid synchronization context deadlock from .Result here.
// This whole setup logic should be made asynchronous so we can properly wait on the DB AAAAAAAAAAAAAH
var task = Task.Run(async () =>
{
var server = await _dbEntryManager.ServerEntity;
return await _db.AddNewRound(server, playerIds);
});
_taskManager.BlockWaitOnTask(task);
RoundId = task.GetAwaiter().GetResult();
}
}