Revert "setgamepreset command rework" (#30807)

Revert "setgamepreset command rework (#30756)"

This reverts commit de86fdb373.
This commit is contained in:
metalgearsloth
2024-08-09 17:39:45 +10:00
committed by GitHub
parent 1649ed45bd
commit 0b2fa941a7
4 changed files with 259 additions and 295 deletions

View File

@@ -2,7 +2,6 @@
using Content.Server.Administration;
using Content.Server.GameTicking.Presets;
using Content.Shared.Administration;
using Linguini.Shared.Util;
using Robust.Shared.Console;
using Robust.Shared.Prototypes;
@@ -20,9 +19,9 @@ namespace Content.Server.GameTicking.Commands
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (!args.Length.InRange(1, 2))
if (args.Length != 1)
{
shell.WriteError(Loc.GetString("shell-need-between-arguments", ("lower", 1), ("upper", 2), ("currentAmount", args.Length)));
shell.WriteError(Loc.GetString("shell-wrong-arguments-number-need-specific", ("properAmount", 1), ("currentAmount", args.Length)));
return;
}
@@ -34,16 +33,8 @@ namespace Content.Server.GameTicking.Commands
return;
}
var rounds = 1;
if (args.Length == 2 && !int.TryParse(args[1], out rounds))
{
shell.WriteError(Loc.GetString("set-game-preset-optional-argument-not-integer"));
return;
}
ticker.SetGamePreset(preset, false, rounds);
shell.WriteLine(Loc.GetString("set-game-preset-preset-set-finite", ("preset", preset.ID), ("rounds", rounds.ToString())));
ticker.SetGamePreset(preset);
shell.WriteLine(Loc.GetString("set-game-preset-preset-set", ("preset", preset.ID)));
}
public CompletionResult GetCompletion(IConsoleShell shell, string[] args)

View File

@@ -16,327 +16,305 @@ using Content.Shared.Mobs.Systems;
using JetBrains.Annotations;
using Robust.Shared.Player;
namespace Content.Server.GameTicking;
public sealed partial class GameTicker
namespace Content.Server.GameTicking
{
[Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!;
public const float PresetFailedCooldownIncrease = 30f;
/// <summary>
/// The selected preset that will be used at the start of the next round.
/// </summary>
public GamePresetPrototype? Preset { get; private set; }
/// <summary>
/// The preset that's currently active.
/// </summary>
public GamePresetPrototype? CurrentPreset { get; private set; }
/// <summary>
/// Countdown to the preset being reset to the server default.
/// </summary>
public int? ResetCountdown;
private bool StartPreset(ICommonSession[] origReadyPlayers, bool force)
public sealed partial class GameTicker
{
var startAttempt = new RoundStartAttemptEvent(origReadyPlayers, force);
RaiseLocalEvent(startAttempt);
[Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!;
if (!startAttempt.Cancelled)
return true;
public const float PresetFailedCooldownIncrease = 30f;
var presetTitle = CurrentPreset != null ? Loc.GetString(CurrentPreset.ModeTitle) : string.Empty;
/// <summary>
/// The selected preset that will be used at the start of the next round.
/// </summary>
public GamePresetPrototype? Preset { get; private set; }
void FailedPresetRestart()
/// <summary>
/// The preset that's currently active.
/// </summary>
public GamePresetPrototype? CurrentPreset { get; private set; }
private bool StartPreset(ICommonSession[] origReadyPlayers, bool force)
{
SendServerMessage(Loc.GetString("game-ticker-start-round-cannot-start-game-mode-restart",
("failedGameMode", presetTitle)));
RestartRound();
DelayStart(TimeSpan.FromSeconds(PresetFailedCooldownIncrease));
}
var startAttempt = new RoundStartAttemptEvent(origReadyPlayers, force);
RaiseLocalEvent(startAttempt);
if (_configurationManager.GetCVar(CCVars.GameLobbyFallbackEnabled))
{
var fallbackPresets = _configurationManager.GetCVar(CCVars.GameLobbyFallbackPreset).Split(",");
var startFailed = true;
if (!startAttempt.Cancelled)
return true;
foreach (var preset in fallbackPresets)
var presetTitle = CurrentPreset != null ? Loc.GetString(CurrentPreset.ModeTitle) : string.Empty;
void FailedPresetRestart()
{
ClearGameRules();
SetGamePreset(preset);
AddGamePresetRules();
StartGamePresetRules();
SendServerMessage(Loc.GetString("game-ticker-start-round-cannot-start-game-mode-restart",
("failedGameMode", presetTitle)));
RestartRound();
DelayStart(TimeSpan.FromSeconds(PresetFailedCooldownIncrease));
}
startAttempt.Uncancel();
RaiseLocalEvent(startAttempt);
if (_configurationManager.GetCVar(CCVars.GameLobbyFallbackEnabled))
{
var fallbackPresets = _configurationManager.GetCVar(CCVars.GameLobbyFallbackPreset).Split(",");
var startFailed = true;
if (!startAttempt.Cancelled)
foreach (var preset in fallbackPresets)
{
_chatManager.SendAdminAnnouncement(
Loc.GetString("game-ticker-start-round-cannot-start-game-mode-fallback",
("failedGameMode", presetTitle),
("fallbackMode", Loc.GetString(preset))));
RefreshLateJoinAllowed();
startFailed = false;
break;
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;
}
}
if (startFailed)
else
{
FailedPresetRestart();
return false;
}
return true;
}
else
private void InitializeGamePreset()
{
FailedPresetRestart();
return false;
SetGamePreset(LobbyEnabled ? _configurationManager.GetCVar(CCVars.GameLobbyDefaultPreset) : "sandbox");
}
return true;
}
private void InitializeGamePreset()
{
SetGamePreset(LobbyEnabled ? _configurationManager.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)
public void SetGamePreset(GamePresetPrototype? preset, bool force = false)
{
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;
}
// Do nothing if this game ticker is a dummy!
if (DummyTicker)
return;
Preset = preset;
ValidateMap();
UpdateInfoText();
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<GamePresetPrototype>())
{
foreach (var alias in proto.Alias)
if (force)
{
if (preset.Equals(alias, StringComparison.InvariantCultureIgnoreCase))
return proto;
StartRound(true);
}
}
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<GameMapPoolPrototype>(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<GameMapPoolPrototype>(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)
public void SetGamePreset(string preset, bool force = false)
{
AddGameRule(rule);
var proto = FindGamePreset(preset);
if(proto != null)
SetGamePreset(proto, force);
}
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<EntityUid>(GetAddedGameRules());
foreach (var rule in rules)
public GamePresetPrototype? FindGamePreset(string preset)
{
StartGameRule(rule);
}
}
if (_prototypeManager.TryIndex(preset, out GamePresetPrototype? presetProto))
return presetProto;
public bool OnGhostAttempt(EntityUid mindId, bool canReturnGlobal, bool viaCommand = false, MindComponent? mind = null)
{
if (!Resolve(mindId, ref mind))
return false;
var playerEntity = mind.CurrentEntity;
if (playerEntity != null && viaCommand)
_adminLogger.Add(LogType.Mind, $"{EntityManager.ToPrettyString(playerEntity.Value):player} is attempting to ghost via command");
var handleEv = new GhostAttemptHandleEvent(mind, canReturnGlobal);
RaiseLocalEvent(handleEv);
// Something else has handled the ghost attempt for us! We return its result.
if (handleEv.Handled)
return handleEv.Result;
if (mind.PreventGhosting)
{
if (mind.Session != null) // Logging is suppressed to prevent spam from ghost attempts caused by movement attempts
foreach (var proto in _prototypeManager.EnumeratePrototypes<GamePresetPrototype>())
{
_chatManager.DispatchServerMessage(mind.Session, Loc.GetString("comp-mind-ghosting-prevented"),
true);
}
return false;
}
if (TryComp<GhostComponent>(playerEntity, out var comp) && !comp.CanGhostInteract)
return false;
if (mind.VisitingEntity != default)
{
_mind.UnVisit(mindId, mind: mind);
}
var position = Exists(playerEntity)
? Transform(playerEntity.Value).Coordinates
: GetObserverSpawnPoint();
if (position == default)
return false;
// Ok, so, this is the master place for the logic for if ghosting is "too cheaty" to allow returning.
// There's no reason at this time to move it to any other place, especially given that the 'side effects required' situations would also have to be moved.
// + If CharacterDeadPhysically applies, we're physically dead. Therefore, ghosting OK, and we can return (this is critical for gibbing)
// Note that we could theoretically be ICly dead and still physically alive and vice versa.
// (For example, a zombie could be dead ICly, but may retain memories and is definitely physically active)
// + If we're in a mob that is critical, and we're supposed to be able to return if possible,
// we're succumbing - the mob is killed. Therefore, character is dead. Ghosting OK.
// (If the mob survives, that's a bug. Ghosting is kept regardless.)
var canReturn = canReturnGlobal && _mind.IsCharacterDeadPhysically(mind);
if (_configurationManager.GetCVar(CCVars.GhostKillCrit) &&
canReturnGlobal &&
TryComp(playerEntity, out MobStateComponent? mobState))
{
if (_mobState.IsCritical(playerEntity.Value, mobState))
{
canReturn = true;
//todo: what if they dont breathe lol
//cry deeply
FixedPoint2 dealtDamage = 200;
if (TryComp<DamageableComponent>(playerEntity, out var damageable)
&& TryComp<MobThresholdsComponent>(playerEntity, out var thresholds))
foreach (var alias in proto.Alias)
{
var playerDeadThreshold = _mobThresholdSystem.GetThresholdForState(playerEntity.Value, MobState.Dead, thresholds);
dealtDamage = playerDeadThreshold - damageable.TotalDamage;
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<GameMapPoolPrototype>(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<GameMapPoolPrototype>(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;
}
public void StartGamePresetRules()
{
// May be touched by the preset during init.
var rules = new List<EntityUid>(GetAddedGameRules());
foreach (var rule in rules)
{
StartGameRule(rule);
}
}
public bool OnGhostAttempt(EntityUid mindId, bool canReturnGlobal, bool viaCommand = false, MindComponent? mind = null)
{
if (!Resolve(mindId, ref mind))
return false;
var playerEntity = mind.CurrentEntity;
if (playerEntity != null && viaCommand)
_adminLogger.Add(LogType.Mind, $"{EntityManager.ToPrettyString(playerEntity.Value):player} is attempting to ghost via command");
var handleEv = new GhostAttemptHandleEvent(mind, canReturnGlobal);
RaiseLocalEvent(handleEv);
// Something else has handled the ghost attempt for us! We return its result.
if (handleEv.Handled)
return handleEv.Result;
if (mind.PreventGhosting)
{
if (mind.Session != null) // Logging is suppressed to prevent spam from ghost attempts caused by movement attempts
{
_chatManager.DispatchServerMessage(mind.Session, Loc.GetString("comp-mind-ghosting-prevented"),
true);
}
DamageSpecifier damage = new(_prototypeManager.Index<DamageTypePrototype>("Asphyxiation"), dealtDamage);
_damageable.TryChangeDamage(playerEntity, damage, true);
return false;
}
if (TryComp<GhostComponent>(playerEntity, out var comp) && !comp.CanGhostInteract)
return false;
if (mind.VisitingEntity != default)
{
_mind.UnVisit(mindId, mind: mind);
}
var position = Exists(playerEntity)
? Transform(playerEntity.Value).Coordinates
: GetObserverSpawnPoint();
if (position == default)
return false;
// Ok, so, this is the master place for the logic for if ghosting is "too cheaty" to allow returning.
// There's no reason at this time to move it to any other place, especially given that the 'side effects required' situations would also have to be moved.
// + If CharacterDeadPhysically applies, we're physically dead. Therefore, ghosting OK, and we can return (this is critical for gibbing)
// Note that we could theoretically be ICly dead and still physically alive and vice versa.
// (For example, a zombie could be dead ICly, but may retain memories and is definitely physically active)
// + If we're in a mob that is critical, and we're supposed to be able to return if possible,
// we're succumbing - the mob is killed. Therefore, character is dead. Ghosting OK.
// (If the mob survives, that's a bug. Ghosting is kept regardless.)
var canReturn = canReturnGlobal && _mind.IsCharacterDeadPhysically(mind);
if (_configurationManager.GetCVar(CCVars.GhostKillCrit) &&
canReturnGlobal &&
TryComp(playerEntity, out MobStateComponent? mobState))
{
if (_mobState.IsCritical(playerEntity.Value, mobState))
{
canReturn = true;
//todo: what if they dont breathe lol
//cry deeply
FixedPoint2 dealtDamage = 200;
if (TryComp<DamageableComponent>(playerEntity, out var damageable)
&& TryComp<MobThresholdsComponent>(playerEntity, out var thresholds))
{
var playerDeadThreshold = _mobThresholdSystem.GetThresholdForState(playerEntity.Value, MobState.Dead, thresholds);
dealtDamage = playerDeadThreshold - damageable.TotalDamage;
}
DamageSpecifier damage = new(_prototypeManager.Index<DamageTypePrototype>("Asphyxiation"), dealtDamage);
_damageable.TryChangeDamage(playerEntity, damage, true);
}
}
var ghost = _ghost.SpawnGhost((mindId, mind), position, canReturn);
if (ghost == null)
return false;
if (playerEntity != null)
_adminLogger.Add(LogType.Mind, $"{EntityManager.ToPrettyString(playerEntity.Value):player} ghosted{(!canReturn ? " (non-returnable)" : "")}");
return true;
}
var ghost = _ghost.SpawnGhost((mindId, mind), position, canReturn);
if (ghost == null)
return false;
if (playerEntity != null)
_adminLogger.Add(LogType.Mind, $"{EntityManager.ToPrettyString(playerEntity.Value):player} ghosted{(!canReturn ? " (non-returnable)" : "")}");
return true;
}
private void IncrementRoundNumber()
{
var playerIds = _playerGameStatuses.Keys.Select(player => player.UserId).ToArray();
var serverName = _configurationManager.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 () =>
private void IncrementRoundNumber()
{
var server = await _dbEntryManager.ServerEntity;
return await _db.AddNewRound(server, playerIds);
});
var playerIds = _playerGameStatuses.Keys.Select(player => player.UserId).ToArray();
var serverName = _configurationManager.GetCVar(CCVars.AdminLogsServerName);
_taskManager.BlockWaitOnTask(task);
RoundId = task.GetAwaiter().GetResult();
// 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();
}
}
}
public sealed class GhostAttemptHandleEvent : HandledEntityEventArgs
{
public MindComponent Mind { get; }
public bool CanReturnGlobal { get; }
public bool Result { get; set; }
public GhostAttemptHandleEvent(MindComponent mind, bool canReturnGlobal)
public sealed class GhostAttemptHandleEvent : HandledEntityEventArgs
{
Mind = mind;
CanReturnGlobal = canReturnGlobal;
public MindComponent Mind { get; }
public bool CanReturnGlobal { get; }
public bool Result { get; set; }
public GhostAttemptHandleEvent(MindComponent mind, bool canReturnGlobal)
{
Mind = mind;
CanReturnGlobal = canReturnGlobal;
}
}
}

View File

@@ -487,9 +487,6 @@ namespace Content.Server.GameTicking
if (_serverUpdates.RoundEnded())
return;
// Check if the GamePreset needs to be reset
TryResetPreset();
_sawmill.Info("Restarting round!");
SendServerMessage(Loc.GetString("game-ticker-restart-round"));

View File

@@ -1,7 +1,5 @@
set-game-preset-command-description = Sets the game preset for the specified number of upcoming rounds.
set-game-preset-command-help-text = setgamepreset <id> [number of rounds, defaulting to 1]
set-game-preset-optional-argument-not-integer = If argument 2 is provided it must be a number.
set-game-preset-command-description = Sets the game preset for the current round.
set-game-preset-command-help-text = setgamepreset <id>
set-game-preset-preset-error = Unable to find game preset "{$preset}"
#set-game-preset-preset-set = Set game preset to "{$preset}"
set-game-preset-preset-set-finite = Set game preset to "{$preset}" for the next {$rounds} rounds.
set-game-preset-preset-set = Set game preset to "{$preset}"