mirror of
https://github.com/DeltaV-Station/Delta-v.git
synced 2026-09-29 23:57:29 +00:00
setgamepreset command rework (#30756)
* gameticker.gamepreset namespace * setgamepreset now has a finite duration * comments, cleanup
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
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;
|
||||
|
||||
@@ -19,9 +20,9 @@ namespace Content.Server.GameTicking.Commands
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
if (!args.Length.InRange(1, 2))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("shell-wrong-arguments-number-need-specific", ("properAmount", 1), ("currentAmount", args.Length)));
|
||||
shell.WriteError(Loc.GetString("shell-need-between-arguments", ("lower", 1), ("upper", 2), ("currentAmount", args.Length)));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -33,8 +34,16 @@ namespace Content.Server.GameTicking.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
ticker.SetGamePreset(preset);
|
||||
shell.WriteLine(Loc.GetString("set-game-preset-preset-set", ("preset", preset.ID)));
|
||||
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())));
|
||||
}
|
||||
|
||||
public CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
|
||||
@@ -16,305 +16,327 @@ using Content.Shared.Mobs.Systems;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server.GameTicking
|
||||
namespace Content.Server.GameTicking;
|
||||
|
||||
public sealed partial class GameTicker
|
||||
{
|
||||
public sealed partial class GameTicker
|
||||
[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)
|
||||
{
|
||||
[Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!;
|
||||
var startAttempt = new RoundStartAttemptEvent(origReadyPlayers, force);
|
||||
RaiseLocalEvent(startAttempt);
|
||||
|
||||
public const float PresetFailedCooldownIncrease = 30f;
|
||||
if (!startAttempt.Cancelled)
|
||||
return true;
|
||||
|
||||
/// <summary>
|
||||
/// The selected preset that will be used at the start of the next round.
|
||||
/// </summary>
|
||||
public GamePresetPrototype? Preset { get; private set; }
|
||||
var presetTitle = CurrentPreset != null ? Loc.GetString(CurrentPreset.ModeTitle) : string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The preset that's currently active.
|
||||
/// </summary>
|
||||
public GamePresetPrototype? CurrentPreset { get; private set; }
|
||||
|
||||
private bool StartPreset(ICommonSession[] origReadyPlayers, bool force)
|
||||
void FailedPresetRestart()
|
||||
{
|
||||
var startAttempt = new RoundStartAttemptEvent(origReadyPlayers, force);
|
||||
RaiseLocalEvent(startAttempt);
|
||||
SendServerMessage(Loc.GetString("game-ticker-start-round-cannot-start-game-mode-restart",
|
||||
("failedGameMode", presetTitle)));
|
||||
RestartRound();
|
||||
DelayStart(TimeSpan.FromSeconds(PresetFailedCooldownIncrease));
|
||||
}
|
||||
|
||||
if (!startAttempt.Cancelled)
|
||||
return true;
|
||||
if (_configurationManager.GetCVar(CCVars.GameLobbyFallbackEnabled))
|
||||
{
|
||||
var fallbackPresets = _configurationManager.GetCVar(CCVars.GameLobbyFallbackPreset).Split(",");
|
||||
var startFailed = true;
|
||||
|
||||
var presetTitle = CurrentPreset != null ? Loc.GetString(CurrentPreset.ModeTitle) : string.Empty;
|
||||
|
||||
void FailedPresetRestart()
|
||||
foreach (var preset in fallbackPresets)
|
||||
{
|
||||
SendServerMessage(Loc.GetString("game-ticker-start-round-cannot-start-game-mode-restart",
|
||||
("failedGameMode", presetTitle)));
|
||||
RestartRound();
|
||||
DelayStart(TimeSpan.FromSeconds(PresetFailedCooldownIncrease));
|
||||
}
|
||||
ClearGameRules();
|
||||
SetGamePreset(preset);
|
||||
AddGamePresetRules();
|
||||
StartGamePresetRules();
|
||||
|
||||
if (_configurationManager.GetCVar(CCVars.GameLobbyFallbackEnabled))
|
||||
{
|
||||
var fallbackPresets = _configurationManager.GetCVar(CCVars.GameLobbyFallbackPreset).Split(",");
|
||||
var startFailed = true;
|
||||
startAttempt.Uncancel();
|
||||
RaiseLocalEvent(startAttempt);
|
||||
|
||||
foreach (var preset in fallbackPresets)
|
||||
if (!startAttempt.Cancelled)
|
||||
{
|
||||
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;
|
||||
_chatManager.SendAdminAnnouncement(
|
||||
Loc.GetString("game-ticker-start-round-cannot-start-game-mode-fallback",
|
||||
("failedGameMode", presetTitle),
|
||||
("fallbackMode", Loc.GetString(preset))));
|
||||
RefreshLateJoinAllowed();
|
||||
startFailed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
if (startFailed)
|
||||
{
|
||||
FailedPresetRestart();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void InitializeGamePreset()
|
||||
else
|
||||
{
|
||||
SetGamePreset(LobbyEnabled ? _configurationManager.GetCVar(CCVars.GameLobbyDefaultPreset) : "sandbox");
|
||||
FailedPresetRestart();
|
||||
return false;
|
||||
}
|
||||
|
||||
public void SetGamePreset(GamePresetPrototype? preset, bool force = false)
|
||||
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)
|
||||
{
|
||||
// Do nothing if this game ticker is a dummy!
|
||||
if (DummyTicker)
|
||||
return;
|
||||
|
||||
Preset = preset;
|
||||
ValidateMap();
|
||||
UpdateInfoText();
|
||||
|
||||
if (force)
|
||||
{
|
||||
StartRound(true);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
public void SetGamePreset(string preset, bool force = false)
|
||||
Preset = preset;
|
||||
ValidateMap();
|
||||
UpdateInfoText();
|
||||
|
||||
if (force)
|
||||
{
|
||||
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 (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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 () =>
|
||||
{
|
||||
var server = await _dbEntryManager.ServerEntity;
|
||||
return await _db.AddNewRound(server, playerIds);
|
||||
});
|
||||
|
||||
_taskManager.BlockWaitOnTask(task);
|
||||
RoundId = task.GetAwaiter().GetResult();
|
||||
StartRound(true);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class GhostAttemptHandleEvent : HandledEntityEventArgs
|
||||
public void SetGamePreset(string preset, bool force = false)
|
||||
{
|
||||
public MindComponent Mind { get; }
|
||||
public bool CanReturnGlobal { get; }
|
||||
public bool Result { get; set; }
|
||||
var proto = FindGamePreset(preset);
|
||||
if(proto != null)
|
||||
SetGamePreset(proto, force);
|
||||
}
|
||||
|
||||
public GhostAttemptHandleEvent(MindComponent mind, bool canReturnGlobal)
|
||||
public GamePresetPrototype? FindGamePreset(string preset)
|
||||
{
|
||||
if (_prototypeManager.TryIndex(preset, out GamePresetPrototype? presetProto))
|
||||
return presetProto;
|
||||
|
||||
foreach (var proto in _prototypeManager.EnumeratePrototypes<GamePresetPrototype>())
|
||||
{
|
||||
Mind = mind;
|
||||
CanReturnGlobal = canReturnGlobal;
|
||||
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<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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 () =>
|
||||
{
|
||||
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)
|
||||
{
|
||||
Mind = mind;
|
||||
CanReturnGlobal = canReturnGlobal;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -487,6 +487,9 @@ 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"));
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
set-game-preset-command-description = Sets the game preset for the current round.
|
||||
set-game-preset-command-help-text = setgamepreset <id>
|
||||
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-preset-error = Unable to find game preset "{$preset}"
|
||||
set-game-preset-preset-set = Set game preset to "{$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.
|
||||
|
||||
Reference in New Issue
Block a user