Merge remote-tracking branch 'upstream/master' into ed-13-06-2024-upstream2
# Conflicts: # Content.Client/Guidebook/GuidebookSystem.cs # Content.Server/Damage/Systems/DamageOtherOnHitSystem.cs # Content.Shared/Guidebook/GuideEntry.cs # Content.Shared/Preferences/HumanoidCharacterProfile.cs # Resources/Prototypes/Accents/word_replacements.yml # Resources/Prototypes/Maps/arenas.yml # Resources/Prototypes/Maps/atlas.yml # Resources/Prototypes/Maps/bagel.yml # Resources/Prototypes/Maps/box.yml # Resources/Prototypes/Maps/cluster.yml # Resources/Prototypes/Maps/core.yml # Resources/Prototypes/Maps/debug.yml # Resources/Prototypes/Maps/europa.yml # Resources/Prototypes/Maps/fland.yml # Resources/Prototypes/Maps/marathon.yml # Resources/Prototypes/Maps/meta.yml # Resources/Prototypes/Maps/oasis.yml # Resources/Prototypes/Maps/omega.yml # Resources/Prototypes/Maps/origin.yml # Resources/Prototypes/Maps/packed.yml # Resources/Prototypes/Maps/reach.yml # Resources/Prototypes/Maps/saltern.yml # Resources/Prototypes/Maps/train.yml # Resources/Prototypes/lobbyscreens.yml
This commit is contained in:
139
Content.Server/Administration/Commands/BabyJailCommand.cs
Normal file
139
Content.Server/Administration/Commands/BabyJailCommand.cs
Normal file
@@ -0,0 +1,139 @@
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
/*
|
||||
* TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
|
||||
*/
|
||||
|
||||
namespace Content.Server.Administration.Commands;
|
||||
|
||||
[AdminCommand(AdminFlags.Server)]
|
||||
public sealed class BabyJailCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
public override string Command => "babyjail";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var toggle = Toggle(CCVars.BabyJailEnabled, shell, args, _cfg);
|
||||
if (toggle == null)
|
||||
return;
|
||||
|
||||
shell.WriteLine(Loc.GetString(toggle.Value ? "babyjail-command-enabled" : "babyjail-command-disabled"));
|
||||
}
|
||||
|
||||
public static bool? Toggle(CVarDef<bool> cvar, IConsoleShell shell, string[] args, IConfigurationManager config)
|
||||
{
|
||||
if (args.Length > 1)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("shell-need-between-arguments",("lower", 0), ("upper", 1)));
|
||||
return null;
|
||||
}
|
||||
|
||||
var enabled = config.GetCVar(cvar);
|
||||
|
||||
switch (args.Length)
|
||||
{
|
||||
case 0:
|
||||
enabled = !enabled;
|
||||
break;
|
||||
case 1 when !bool.TryParse(args[0], out enabled):
|
||||
shell.WriteError(Loc.GetString("shell-argument-must-be-boolean"));
|
||||
return null;
|
||||
}
|
||||
|
||||
config.SetCVar(cvar, enabled);
|
||||
|
||||
return enabled;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[AdminCommand(AdminFlags.Server)]
|
||||
public sealed class BabyJailShowReasonCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
public override string Command => "babyjail_show_reason";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var toggle = BabyJailCommand.Toggle(CCVars.BabyJailShowReason, shell, args, _cfg);
|
||||
if (toggle == null)
|
||||
return;
|
||||
|
||||
shell.WriteLine(Loc.GetString(toggle.Value
|
||||
? "babyjail-command-show-reason-enabled"
|
||||
: "babyjail-command-show-reason-disabled"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
[AdminCommand(AdminFlags.Server)]
|
||||
public sealed class BabyJailMinAccountAgeCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
public override string Command => "babyjail_max_account_age";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
switch (args.Length)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
var current = _cfg.GetCVar(CCVars.BabyJailMaxAccountAge);
|
||||
shell.WriteLine(Loc.GetString("babyjail-command-max-account-age-is", ("minutes", current)));
|
||||
break;
|
||||
}
|
||||
case > 1:
|
||||
shell.WriteError(Loc.GetString("shell-need-between-arguments",("lower", 0), ("upper", 1)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!int.TryParse(args[0], out var minutes))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("shell-argument-must-be-number"));
|
||||
return;
|
||||
}
|
||||
|
||||
_cfg.SetCVar(CCVars.BabyJailMaxAccountAge, minutes);
|
||||
shell.WriteLine(Loc.GetString("babyjail-command-max-account-age-set", ("minutes", minutes)));
|
||||
}
|
||||
}
|
||||
|
||||
[AdminCommand(AdminFlags.Server)]
|
||||
public sealed class BabyJailMinOverallHoursCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
public override string Command => "babyjail_max_overall_minutes";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
switch (args.Length)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
var current = _cfg.GetCVar(CCVars.BabyJailMaxOverallMinutes);
|
||||
shell.WriteLine(Loc.GetString("babyjail-command-max-overall-minutes-is", ("minutes", current)));
|
||||
break;
|
||||
}
|
||||
case > 1:
|
||||
shell.WriteError(Loc.GetString("shell-need-between-arguments",("lower", 0), ("upper", 1)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!int.TryParse(args[0], out var hours))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("shell-argument-must-be-number"));
|
||||
return;
|
||||
}
|
||||
|
||||
_cfg.SetCVar(CCVars.BabyJailMaxOverallMinutes, hours);
|
||||
shell.WriteLine(Loc.GetString("babyjail-command-overall-minutes-set", ("hours", hours)));
|
||||
}
|
||||
}
|
||||
@@ -139,7 +139,7 @@ public sealed class PanicBunkerMinAccountAgeCommand : LocalizedCommands
|
||||
if (args.Length == 0)
|
||||
{
|
||||
var current = _cfg.GetCVar(CCVars.PanicBunkerMinAccountAge);
|
||||
shell.WriteLine(Loc.GetString("panicbunker-command-min-account-age-is", ("hours", current / 60)));
|
||||
shell.WriteLine(Loc.GetString("panicbunker-command-min-account-age-is", ("minutes", current)));
|
||||
}
|
||||
|
||||
if (args.Length > 1)
|
||||
@@ -148,30 +148,30 @@ public sealed class PanicBunkerMinAccountAgeCommand : LocalizedCommands
|
||||
return;
|
||||
}
|
||||
|
||||
if (!int.TryParse(args[0], out var hours))
|
||||
if (!int.TryParse(args[0], out var minutes))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("shell-argument-must-be-number"));
|
||||
return;
|
||||
}
|
||||
|
||||
_cfg.SetCVar(CCVars.PanicBunkerMinAccountAge, hours * 60);
|
||||
shell.WriteLine(Loc.GetString("panicbunker-command-min-account-age-set", ("hours", hours)));
|
||||
_cfg.SetCVar(CCVars.PanicBunkerMinAccountAge, minutes);
|
||||
shell.WriteLine(Loc.GetString("panicbunker-command-min-account-age-set", ("minutes", minutes)));
|
||||
}
|
||||
}
|
||||
|
||||
[AdminCommand(AdminFlags.Server)]
|
||||
public sealed class PanicBunkerMinOverallHoursCommand : LocalizedCommands
|
||||
public sealed class PanicBunkerMinOverallMinutesCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
public override string Command => "panicbunker_min_overall_hours";
|
||||
public override string Command => "panicbunker_min_overall_minutes";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length == 0)
|
||||
{
|
||||
var current = _cfg.GetCVar(CCVars.PanicBunkerMinOverallHours);
|
||||
shell.WriteLine(Loc.GetString("panicbunker-command-min-overall-hours-is", ("minutes", current)));
|
||||
var current = _cfg.GetCVar(CCVars.PanicBunkerMinOverallMinutes);
|
||||
shell.WriteLine(Loc.GetString("panicbunker-command-min-overall-minutes-is", ("minutes", current)));
|
||||
}
|
||||
|
||||
if (args.Length > 1)
|
||||
@@ -180,13 +180,13 @@ public sealed class PanicBunkerMinOverallHoursCommand : LocalizedCommands
|
||||
return;
|
||||
}
|
||||
|
||||
if (!int.TryParse(args[0], out var hours))
|
||||
if (!int.TryParse(args[0], out var minutes))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("shell-argument-must-be-number"));
|
||||
return;
|
||||
}
|
||||
|
||||
_cfg.SetCVar(CCVars.PanicBunkerMinOverallHours, hours);
|
||||
shell.WriteLine(Loc.GetString("panicbunker-command-overall-hours-age-set", ("hours", hours)));
|
||||
_cfg.SetCVar(CCVars.PanicBunkerMinOverallMinutes, minutes);
|
||||
shell.WriteLine(Loc.GetString("panicbunker-command-overall-minutes-age-set", ("minutes", minutes)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ public sealed class PlayTimeAddRoleCommand : IConsoleCommand
|
||||
}
|
||||
|
||||
_playTimeTracking.AddTimeToTracker(player, role, TimeSpan.FromMinutes(minutes));
|
||||
var time = _playTimeTracking.GetOverallPlaytime(player);
|
||||
var time = _playTimeTracking.GetPlayTimeForTracker(player, role);
|
||||
shell.WriteLine(Loc.GetString("cmd-playtime_addrole-succeed",
|
||||
("username", userName),
|
||||
("role", role),
|
||||
|
||||
@@ -65,6 +65,10 @@ public sealed partial class AdminLogManager
|
||||
{
|
||||
players.Add(actor.PlayerSession.UserId.UserId);
|
||||
}
|
||||
else if (value is SerializablePlayer player)
|
||||
{
|
||||
players.Add(player.Player.UserId.UserId);
|
||||
}
|
||||
}
|
||||
|
||||
return (JsonSerializer.SerializeToDocument(parsed, _jsonOptions), players);
|
||||
|
||||
@@ -7,6 +7,7 @@ using Content.Server.Database;
|
||||
using Content.Server.Players;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Info;
|
||||
using Content.Shared.Players;
|
||||
using Robust.Server.Console;
|
||||
using Robust.Server.Player;
|
||||
|
||||
@@ -41,7 +41,7 @@ public sealed partial class ServerApi : IPostInjectInit
|
||||
CCVars.PanicBunkerCountDeadminnedAdmins.Name,
|
||||
CCVars.PanicBunkerShowReason.Name,
|
||||
CCVars.PanicBunkerMinAccountAge.Name,
|
||||
CCVars.PanicBunkerMinOverallHours.Name,
|
||||
CCVars.PanicBunkerMinOverallMinutes.Name,
|
||||
CCVars.PanicBunkerCustomReason.Name,
|
||||
];
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ using Content.Server.Chat.Managers;
|
||||
using Content.Server.Forensics;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Hands.Systems;
|
||||
using Content.Server.IdentityManagement;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Players.PlayTimeTracking;
|
||||
using Content.Server.Popups;
|
||||
@@ -62,6 +61,7 @@ namespace Content.Server.Administration.Systems
|
||||
|
||||
private readonly HashSet<NetUserId> _roundActivePlayers = new();
|
||||
public readonly PanicBunkerStatus PanicBunker = new();
|
||||
public readonly BabyJailStatus BabyJail = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -69,14 +69,26 @@ namespace Content.Server.Administration.Systems
|
||||
|
||||
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||
_adminManager.OnPermsChanged += OnAdminPermsChanged;
|
||||
_playTime.SessionPlayTimeUpdated += OnSessionPlayTimeUpdated;
|
||||
|
||||
// Panic Bunker Settings
|
||||
Subs.CVar(_config, CCVars.PanicBunkerEnabled, OnPanicBunkerChanged, true);
|
||||
Subs.CVar(_config, CCVars.PanicBunkerDisableWithAdmins, OnPanicBunkerDisableWithAdminsChanged, true);
|
||||
Subs.CVar(_config, CCVars.PanicBunkerEnableWithoutAdmins, OnPanicBunkerEnableWithoutAdminsChanged, true);
|
||||
Subs.CVar(_config, CCVars.PanicBunkerCountDeadminnedAdmins, OnPanicBunkerCountDeadminnedAdminsChanged, true);
|
||||
Subs.CVar(_config, CCVars.PanicBunkerShowReason, OnShowReasonChanged, true);
|
||||
Subs.CVar(_config, CCVars.PanicBunkerShowReason, OnPanicBunkerShowReasonChanged, true);
|
||||
Subs.CVar(_config, CCVars.PanicBunkerMinAccountAge, OnPanicBunkerMinAccountAgeChanged, true);
|
||||
Subs.CVar(_config, CCVars.PanicBunkerMinOverallHours, OnPanicBunkerMinOverallHoursChanged, true);
|
||||
Subs.CVar(_config, CCVars.PanicBunkerMinOverallMinutes, OnPanicBunkerMinOverallMinutesChanged, true);
|
||||
|
||||
/*
|
||||
* TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
|
||||
*/
|
||||
|
||||
// Baby Jail Settings
|
||||
Subs.CVar(_config, CCVars.BabyJailEnabled, OnBabyJailChanged, true);
|
||||
Subs.CVar(_config, CCVars.BabyJailShowReason, OnBabyJailShowReasonChanged, true);
|
||||
Subs.CVar(_config, CCVars.BabyJailMaxAccountAge, OnBabyJailMaxAccountAgeChanged, true);
|
||||
Subs.CVar(_config, CCVars.BabyJailMaxOverallMinutes, OnBabyJailMaxOverallMinutesChanged, true);
|
||||
|
||||
SubscribeLocalEvent<IdentityChangedEvent>(OnIdentityChanged);
|
||||
SubscribeLocalEvent<PlayerAttachedEvent>(OnPlayerAttached);
|
||||
@@ -188,6 +200,7 @@ namespace Content.Server.Administration.Systems
|
||||
base.Shutdown();
|
||||
_playerManager.PlayerStatusChanged -= OnPlayerStatusChanged;
|
||||
_adminManager.OnPermsChanged -= OnAdminPermsChanged;
|
||||
_playTime.SessionPlayTimeUpdated -= OnSessionPlayTimeUpdated;
|
||||
}
|
||||
|
||||
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
|
||||
@@ -249,6 +262,17 @@ namespace Content.Server.Administration.Systems
|
||||
SendPanicBunkerStatusAll();
|
||||
}
|
||||
|
||||
private void OnBabyJailChanged(bool enabled)
|
||||
{
|
||||
BabyJail.Enabled = enabled;
|
||||
_chat.SendAdminAlert(Loc.GetString(enabled
|
||||
? "admin-ui-baby-jail-enabled-admin-alert"
|
||||
: "admin-ui-baby-jail-disabled-admin-alert"
|
||||
));
|
||||
|
||||
SendBabyJailStatusAll();
|
||||
}
|
||||
|
||||
private void OnPanicBunkerDisableWithAdminsChanged(bool enabled)
|
||||
{
|
||||
PanicBunker.DisableWithAdmins = enabled;
|
||||
@@ -267,24 +291,42 @@ namespace Content.Server.Administration.Systems
|
||||
UpdatePanicBunker();
|
||||
}
|
||||
|
||||
private void OnShowReasonChanged(bool enabled)
|
||||
private void OnPanicBunkerShowReasonChanged(bool enabled)
|
||||
{
|
||||
PanicBunker.ShowReason = enabled;
|
||||
SendPanicBunkerStatusAll();
|
||||
}
|
||||
|
||||
private void OnBabyJailShowReasonChanged(bool enabled)
|
||||
{
|
||||
BabyJail.ShowReason = enabled;
|
||||
SendBabyJailStatusAll();
|
||||
}
|
||||
|
||||
private void OnPanicBunkerMinAccountAgeChanged(int minutes)
|
||||
{
|
||||
PanicBunker.MinAccountAgeHours = minutes / 60;
|
||||
PanicBunker.MinAccountAgeMinutes = minutes;
|
||||
SendPanicBunkerStatusAll();
|
||||
}
|
||||
|
||||
private void OnPanicBunkerMinOverallHoursChanged(int hours)
|
||||
private void OnBabyJailMaxAccountAgeChanged(int minutes)
|
||||
{
|
||||
PanicBunker.MinOverallHours = hours;
|
||||
BabyJail.MaxAccountAgeMinutes = minutes;
|
||||
SendBabyJailStatusAll();
|
||||
}
|
||||
|
||||
private void OnPanicBunkerMinOverallMinutesChanged(int minutes)
|
||||
{
|
||||
PanicBunker.MinOverallMinutes = minutes;
|
||||
SendPanicBunkerStatusAll();
|
||||
}
|
||||
|
||||
private void OnBabyJailMaxOverallMinutesChanged(int minutes)
|
||||
{
|
||||
BabyJail.MaxOverallMinutes = minutes;
|
||||
SendBabyJailStatusAll();
|
||||
}
|
||||
|
||||
private void UpdatePanicBunker()
|
||||
{
|
||||
var admins = PanicBunker.CountDeadminnedAdmins
|
||||
@@ -326,6 +368,15 @@ namespace Content.Server.Administration.Systems
|
||||
}
|
||||
}
|
||||
|
||||
private void SendBabyJailStatusAll()
|
||||
{
|
||||
var ev = new BabyJailChangedEvent(BabyJail);
|
||||
foreach (var admin in _adminManager.AllAdmins)
|
||||
{
|
||||
RaiseNetworkEvent(ev, admin);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Erases a player from the round.
|
||||
/// This removes them and any trace of them from the round, deleting their
|
||||
@@ -396,5 +447,10 @@ namespace Content.Server.Administration.Systems
|
||||
|
||||
_gameTicker.SpawnObserver(player);
|
||||
}
|
||||
|
||||
private void OnSessionPlayTimeUpdated(ICommonSession session)
|
||||
{
|
||||
UpdatePlayerList(session);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,59 +131,6 @@ namespace Content.Server.Administration.Systems
|
||||
prayerVerb.Impact = LogImpact.Low;
|
||||
args.Verbs.Add(prayerVerb);
|
||||
|
||||
// Freeze
|
||||
var frozen = TryComp<AdminFrozenComponent>(args.Target, out var frozenComp);
|
||||
var frozenAndMuted = frozenComp?.Muted ?? false;
|
||||
|
||||
if (!frozen)
|
||||
{
|
||||
args.Verbs.Add(new Verb
|
||||
{
|
||||
Priority = -1, // This is just so it doesn't change position in the menu between freeze/unfreeze.
|
||||
Text = Loc.GetString("admin-verbs-freeze"),
|
||||
Category = VerbCategory.Admin,
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/snow.svg.192dpi.png")),
|
||||
Act = () =>
|
||||
{
|
||||
EnsureComp<AdminFrozenComponent>(args.Target);
|
||||
},
|
||||
Impact = LogImpact.Medium,
|
||||
});
|
||||
}
|
||||
|
||||
if (!frozenAndMuted)
|
||||
{
|
||||
// allow you to additionally mute someone when they are already frozen
|
||||
args.Verbs.Add(new Verb
|
||||
{
|
||||
Priority = -1, // This is just so it doesn't change position in the menu between freeze/unfreeze.
|
||||
Text = Loc.GetString("admin-verbs-freeze-and-mute"),
|
||||
Category = VerbCategory.Admin,
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/snow.svg.192dpi.png")),
|
||||
Act = () =>
|
||||
{
|
||||
_freeze.FreezeAndMute(args.Target);
|
||||
},
|
||||
Impact = LogImpact.Medium,
|
||||
});
|
||||
}
|
||||
|
||||
if (frozen)
|
||||
{
|
||||
args.Verbs.Add(new Verb
|
||||
{
|
||||
Priority = -1, // This is just so it doesn't change position in the menu between freeze/unfreeze.
|
||||
Text = Loc.GetString("admin-verbs-unfreeze"),
|
||||
Category = VerbCategory.Admin,
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/snow.svg.192dpi.png")),
|
||||
Act = () =>
|
||||
{
|
||||
RemComp<AdminFrozenComponent>(args.Target);
|
||||
},
|
||||
Impact = LogImpact.Medium,
|
||||
});
|
||||
}
|
||||
|
||||
// Erase
|
||||
args.Verbs.Add(new Verb
|
||||
{
|
||||
@@ -263,6 +210,60 @@ namespace Content.Server.Administration.Systems
|
||||
});
|
||||
}
|
||||
|
||||
// Freeze
|
||||
var frozen = TryComp<AdminFrozenComponent>(args.Target, out var frozenComp);
|
||||
var frozenAndMuted = frozenComp?.Muted ?? false;
|
||||
|
||||
if (!frozen)
|
||||
{
|
||||
args.Verbs.Add(new Verb
|
||||
{
|
||||
Priority = -1, // This is just so it doesn't change position in the menu between freeze/unfreeze.
|
||||
Text = Loc.GetString("admin-verbs-freeze"),
|
||||
Category = VerbCategory.Admin,
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/snow.svg.192dpi.png")),
|
||||
Act = () =>
|
||||
{
|
||||
EnsureComp<AdminFrozenComponent>(args.Target);
|
||||
},
|
||||
Impact = LogImpact.Medium,
|
||||
});
|
||||
}
|
||||
|
||||
if (!frozenAndMuted)
|
||||
{
|
||||
// allow you to additionally mute someone when they are already frozen
|
||||
args.Verbs.Add(new Verb
|
||||
{
|
||||
Priority = -1, // This is just so it doesn't change position in the menu between freeze/unfreeze.
|
||||
Text = Loc.GetString("admin-verbs-freeze-and-mute"),
|
||||
Category = VerbCategory.Admin,
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/snow.svg.192dpi.png")),
|
||||
Act = () =>
|
||||
{
|
||||
_freeze.FreezeAndMute(args.Target);
|
||||
},
|
||||
Impact = LogImpact.Medium,
|
||||
});
|
||||
}
|
||||
|
||||
if (frozen)
|
||||
{
|
||||
args.Verbs.Add(new Verb
|
||||
{
|
||||
Priority = -1, // This is just so it doesn't change position in the menu between freeze/unfreeze.
|
||||
Text = Loc.GetString("admin-verbs-unfreeze"),
|
||||
Category = VerbCategory.Admin,
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/snow.svg.192dpi.png")),
|
||||
Act = () =>
|
||||
{
|
||||
RemComp<AdminFrozenComponent>(args.Target);
|
||||
},
|
||||
Impact = LogImpact.Medium,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Admin Logs
|
||||
if (_adminManager.HasAdminFlag(player, AdminFlags.Logs))
|
||||
{
|
||||
|
||||
@@ -182,7 +182,7 @@ public sealed class AmeNodeGroup : BaseNodeGroup
|
||||
// Fuel is squared so more fuel vastly increases power and efficiency
|
||||
// We divide by the number of cores so a larger AME is less efficient at the same fuel settings
|
||||
// this results in all AMEs having the same efficiency at the same fuel-per-core setting
|
||||
return 2000000f * fuel * fuel / cores;
|
||||
return 20000f * fuel * fuel / cores;
|
||||
}
|
||||
|
||||
public int GetTotalStability()
|
||||
|
||||
@@ -264,7 +264,7 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
/// </summary>
|
||||
public void MakeAntag(Entity<AntagSelectionComponent> ent, ICommonSession? session, AntagSelectionDefinition def, bool ignoreSpawner = false)
|
||||
{
|
||||
var antagEnt = (EntityUid?) null;
|
||||
EntityUid? antagEnt = null;
|
||||
var isSpawner = false;
|
||||
|
||||
if (session != null)
|
||||
@@ -285,17 +285,16 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
{
|
||||
var getEntEv = new AntagSelectEntityEvent(session, ent);
|
||||
RaiseLocalEvent(ent, ref getEntEv, true);
|
||||
|
||||
if (!getEntEv.Handled)
|
||||
{
|
||||
throw new InvalidOperationException($"Attempted to make {session} antagonist in gamerule {ToPrettyString(ent)} but there was no valid entity for player.");
|
||||
}
|
||||
|
||||
antagEnt = getEntEv.Entity;
|
||||
}
|
||||
|
||||
if (antagEnt is not { } player)
|
||||
{
|
||||
Log.Error($"Attempted to make {session} antagonist in gamerule {ToPrettyString(ent)} but there was no valid entity for player.");
|
||||
if (session != null)
|
||||
ent.Comp.SelectedSessions.Remove(session);
|
||||
return;
|
||||
}
|
||||
|
||||
var getPosEv = new AntagSelectLocationEvent(session, ent);
|
||||
RaiseLocalEvent(ent, ref getPosEv, true);
|
||||
@@ -313,6 +312,8 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
if (!TryComp<GhostRoleAntagSpawnerComponent>(player, out var spawnerComp))
|
||||
{
|
||||
Log.Error($"Antag spawner {player} does not have a GhostRoleAntagSpawnerComponent.");
|
||||
if (session != null)
|
||||
ent.Comp.SelectedSessions.Remove(session);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -374,6 +375,9 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
/// </summary>
|
||||
public bool IsSessionValid(Entity<AntagSelectionComponent> ent, ICommonSession? session, AntagSelectionDefinition def, EntityUid? mind = null)
|
||||
{
|
||||
// TODO ROLE TIMERS
|
||||
// Check if antag role requirements are met
|
||||
|
||||
if (session == null)
|
||||
return true;
|
||||
|
||||
|
||||
@@ -10,21 +10,21 @@ public sealed partial class AtmosphereSystem
|
||||
SubscribeLocalEvent<BreathToolComponent, ComponentShutdown>(OnBreathToolShutdown);
|
||||
}
|
||||
|
||||
private void OnBreathToolShutdown(EntityUid uid, BreathToolComponent component, ComponentShutdown args)
|
||||
private void OnBreathToolShutdown(Entity<BreathToolComponent> entity, ref ComponentShutdown args)
|
||||
{
|
||||
DisconnectInternals(component);
|
||||
DisconnectInternals(entity);
|
||||
}
|
||||
|
||||
public void DisconnectInternals(BreathToolComponent component)
|
||||
public void DisconnectInternals(Entity<BreathToolComponent> entity)
|
||||
{
|
||||
var old = component.ConnectedInternalsEntity;
|
||||
component.ConnectedInternalsEntity = null;
|
||||
var old = entity.Comp.ConnectedInternalsEntity;
|
||||
entity.Comp.ConnectedInternalsEntity = null;
|
||||
|
||||
if (TryComp<InternalsComponent>(old, out var internalsComponent))
|
||||
{
|
||||
_internals.DisconnectBreathTool((old.Value, internalsComponent));
|
||||
_internals.DisconnectBreathTool((old.Value, internalsComponent), entity.Owner);
|
||||
}
|
||||
|
||||
component.IsFunctional = false;
|
||||
entity.Comp.IsFunctional = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,7 +220,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
public bool CanConnectToInternals(GasTankComponent component)
|
||||
{
|
||||
var internals = GetInternalsComponent(component, component.User);
|
||||
return internals != null && internals.BreathToolEntity != null && !component.IsValveOpen;
|
||||
return internals != null && internals.BreathTools.Count != 0 && !component.IsValveOpen;
|
||||
}
|
||||
|
||||
public void ConnectToInternals(Entity<GasTankComponent> ent)
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Content.Server.Body.Components
|
||||
public EntityUid? GasTankEntity;
|
||||
|
||||
[ViewVariables]
|
||||
public EntityUid? BreathToolEntity;
|
||||
public HashSet<EntityUid> BreathTools { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Toggle Internals delay when the target is not you.
|
||||
|
||||
@@ -44,7 +44,7 @@ public sealed class InternalsSystem : EntitySystem
|
||||
|
||||
private void OnStartingGear(EntityUid uid, InternalsComponent component, ref StartingGearEquippedEvent args)
|
||||
{
|
||||
if (component.BreathToolEntity == null)
|
||||
if (component.BreathTools.Count == 0)
|
||||
return;
|
||||
|
||||
if (component.GasTankEntity != null)
|
||||
@@ -111,7 +111,7 @@ public sealed class InternalsSystem : EntitySystem
|
||||
}
|
||||
|
||||
// If they're not on then check if we have a mask to use
|
||||
if (internals.BreathToolEntity is null)
|
||||
if (internals.BreathTools.Count == 0)
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("internals-no-breath-tool"), uid, user);
|
||||
return;
|
||||
@@ -178,28 +178,24 @@ public sealed class InternalsSystem : EntitySystem
|
||||
_alerts.ShowAlert(ent, ent.Comp.InternalsAlert, GetSeverity(ent));
|
||||
}
|
||||
}
|
||||
public void DisconnectBreathTool(Entity<InternalsComponent> ent)
|
||||
public void DisconnectBreathTool(Entity<InternalsComponent> ent, EntityUid toolEntity)
|
||||
{
|
||||
var old = ent.Comp.BreathToolEntity;
|
||||
ent.Comp.BreathToolEntity = null;
|
||||
ent.Comp.BreathTools.Remove(toolEntity);
|
||||
|
||||
if (TryComp(old, out BreathToolComponent? breathTool))
|
||||
{
|
||||
_atmos.DisconnectInternals(breathTool);
|
||||
if (TryComp(toolEntity, out BreathToolComponent? breathTool))
|
||||
_atmos.DisconnectInternals((toolEntity, breathTool));
|
||||
|
||||
if (ent.Comp.BreathTools.Count == 0)
|
||||
DisconnectTank(ent);
|
||||
}
|
||||
|
||||
_alerts.ShowAlert(ent, ent.Comp.InternalsAlert, GetSeverity(ent));
|
||||
}
|
||||
|
||||
public void ConnectBreathTool(Entity<InternalsComponent> ent, EntityUid toolEntity)
|
||||
{
|
||||
if (TryComp(ent.Comp.BreathToolEntity, out BreathToolComponent? tool))
|
||||
{
|
||||
_atmos.DisconnectInternals(tool);
|
||||
}
|
||||
if (!ent.Comp.BreathTools.Add(toolEntity))
|
||||
return;
|
||||
|
||||
ent.Comp.BreathToolEntity = toolEntity;
|
||||
_alerts.ShowAlert(ent, ent.Comp.InternalsAlert, GetSeverity(ent));
|
||||
}
|
||||
|
||||
@@ -217,7 +213,7 @@ public sealed class InternalsSystem : EntitySystem
|
||||
|
||||
public bool TryConnectTank(Entity<InternalsComponent> ent, EntityUid tankEntity)
|
||||
{
|
||||
if (ent.Comp.BreathToolEntity is null)
|
||||
if (ent.Comp.BreathTools.Count == 0)
|
||||
return false;
|
||||
|
||||
if (TryComp(ent.Comp.GasTankEntity, out GasTankComponent? tank))
|
||||
@@ -236,14 +232,14 @@ public sealed class InternalsSystem : EntitySystem
|
||||
|
||||
public bool AreInternalsWorking(InternalsComponent component)
|
||||
{
|
||||
return TryComp(component.BreathToolEntity, out BreathToolComponent? breathTool)
|
||||
return TryComp(component.BreathTools.FirstOrNull(), out BreathToolComponent? breathTool)
|
||||
&& breathTool.IsFunctional
|
||||
&& HasComp<GasTankComponent>(component.GasTankEntity);
|
||||
}
|
||||
|
||||
private short GetSeverity(InternalsComponent component)
|
||||
{
|
||||
if (component.BreathToolEntity is null || !AreInternalsWorking(component))
|
||||
if (component.BreathTools.Count == 0 || !AreInternalsWorking(component))
|
||||
return 2;
|
||||
|
||||
// If pressure in the tank is below low pressure threshold, flash warning on internals UI
|
||||
@@ -266,7 +262,7 @@ public sealed class InternalsSystem : EntitySystem
|
||||
// 3. in-hand tanks
|
||||
// 4. pocket/belt tanks
|
||||
|
||||
if (!Resolve(user, ref user.Comp1, ref user.Comp2, ref user.Comp3))
|
||||
if (!Resolve(user, ref user.Comp2, ref user.Comp3))
|
||||
return null;
|
||||
|
||||
if (_inventory.TryGetSlotEntity(user, "back", out var backEntity, user.Comp2, user.Comp3) &&
|
||||
|
||||
@@ -59,7 +59,7 @@ public sealed class LungSystem : EntitySystem
|
||||
{
|
||||
if (args.IsToggled || args.IsEquip)
|
||||
{
|
||||
_atmos.DisconnectInternals(ent.Comp);
|
||||
_atmos.DisconnectInternals(ent);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -95,7 +95,7 @@ public sealed class RespiratorSystem : EntitySystem
|
||||
if (_gameTiming.CurTime >= respirator.LastGaspEmoteTime + respirator.GaspEmoteCooldown)
|
||||
{
|
||||
respirator.LastGaspEmoteTime = _gameTiming.CurTime;
|
||||
_chat.TryEmoteWithChat(uid, respirator.GaspEmote, ignoreActionBlocker: true);
|
||||
_chat.TryEmoteWithChat(uid, respirator.GaspEmote, ChatTransmitRange.HideChat, ignoreActionBlocker: true);
|
||||
}
|
||||
|
||||
TakeSuffocationDamage((uid, respirator));
|
||||
|
||||
@@ -124,6 +124,7 @@ public sealed partial class CargoSystem
|
||||
("item", Loc.GetString(entry.Name)))}");
|
||||
msg.PushNewline();
|
||||
}
|
||||
msg.AddMarkup(Loc.GetString("bounty-console-manifest-reward", ("reward", prototype.Reward)));
|
||||
_paperSystem.SetContent(uid, msg.ToMarkup(), paper);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Text;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Examine;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Speech.Components;
|
||||
using Content.Server.Speech.EntitySystems;
|
||||
@@ -14,6 +15,7 @@ using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.IdentityManagement;
|
||||
@@ -60,6 +62,7 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
||||
[Dependency] private readonly ReplacementAccentSystem _wordreplacement = default!;
|
||||
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
|
||||
[Dependency] private readonly ExamineSystemShared _examineSystem = default!;
|
||||
|
||||
public const int VoiceRange = 10; // how far voice goes in world units
|
||||
public const int WhisperClearRange = 2; // how far whisper goes while still being understandable, in world units
|
||||
@@ -504,8 +507,7 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
if (data.Range <= WhisperClearRange)
|
||||
_chatManager.ChatMessageToOne(ChatChannel.Whisper, message, wrappedMessage, source, false, session.Channel);
|
||||
//If listener is too far, they only hear fragments of the message
|
||||
//Collisiongroup.Opaque is not ideal for this use. Preferably, there should be a check specifically with "Can Ent1 see Ent2" in mind
|
||||
else if (_interactionSystem.InRangeUnobstructed(source, listener, WhisperMuffledRange, Shared.Physics.CollisionGroup.Opaque)) //Shared.Physics.CollisionGroup.Opaque
|
||||
else if (_examineSystem.InRangeUnOccluded(source, listener, WhisperMuffledRange))
|
||||
_chatManager.ChatMessageToOne(ChatChannel.Whisper, obfuscatedMessage, wrappedobfuscatedMessage, source, false, session.Channel);
|
||||
//If listener is too far and has no line of sight, they can't identify the whisperer's identity
|
||||
else
|
||||
|
||||
@@ -199,9 +199,13 @@ namespace Content.Server.Communications
|
||||
if (_emergency.EmergencyShuttleArrived || !_roundEndSystem.CanCallOrRecall())
|
||||
return false;
|
||||
|
||||
// Ensure that we can communicate with the shuttle (either call or recall)
|
||||
if (!comp.CanShuttle)
|
||||
return false;
|
||||
|
||||
// Calling shuttle checks
|
||||
if (_roundEndSystem.ExpectedCountdownEnd is null)
|
||||
return comp.CanShuttle;
|
||||
return true;
|
||||
|
||||
// Recalling shuttle checks
|
||||
var recallThreshold = _cfg.GetCVar(CCVars.EmergencyRecallTurningPoint);
|
||||
|
||||
@@ -13,6 +13,9 @@ using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
/*
|
||||
* TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
|
||||
*/
|
||||
|
||||
namespace Content.Server.Connection
|
||||
{
|
||||
@@ -125,6 +128,10 @@ namespace Content.Server.Connection
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: Jesus H Christ what is this utter mess of a function
|
||||
* TODO: Break this apart into is constituent steps.
|
||||
*/
|
||||
private async Task<(ConnectionDenyReason, string, List<ServerBanDef>? bansHit)?> ShouldDeny(
|
||||
NetConnectingArgs e)
|
||||
{
|
||||
@@ -179,9 +186,9 @@ namespace Content.Server.Connection
|
||||
("reason", Loc.GetString("panic-bunker-account-reason-account", ("minutes", minMinutesAge)))), null);
|
||||
}
|
||||
|
||||
var minOverallHours = _cfg.GetCVar(CCVars.PanicBunkerMinOverallHours);
|
||||
var minOverallMinutes = _cfg.GetCVar(CCVars.PanicBunkerMinOverallMinutes);
|
||||
var overallTime = ( await _db.GetPlayTimes(e.UserId)).Find(p => p.Tracker == PlayTimeTrackingShared.TrackerOverall);
|
||||
var haveMinOverallTime = overallTime != null && overallTime.TimeSpent.TotalHours > minOverallHours;
|
||||
var haveMinOverallTime = overallTime != null && overallTime.TimeSpent.TotalMinutes > minOverallMinutes;
|
||||
|
||||
// Use the custom reason if it exists & they don't have the minimum time
|
||||
if (customReason != string.Empty && !haveMinOverallTime && !bypassAllowed)
|
||||
@@ -193,7 +200,7 @@ namespace Content.Server.Connection
|
||||
{
|
||||
return (ConnectionDenyReason.Panic,
|
||||
Loc.GetString("panic-bunker-account-denied-reason",
|
||||
("reason", Loc.GetString("panic-bunker-account-reason-overall", ("hours", minOverallHours)))), null);
|
||||
("reason", Loc.GetString("panic-bunker-account-reason-overall", ("minutes", minOverallMinutes)))), null);
|
||||
}
|
||||
|
||||
if (!validAccountAge || !haveMinOverallTime && !bypassAllowed)
|
||||
@@ -202,6 +209,14 @@ namespace Content.Server.Connection
|
||||
}
|
||||
}
|
||||
|
||||
if (_cfg.GetCVar(CCVars.BabyJailEnabled) && adminData == null)
|
||||
{
|
||||
var result = await IsInvalidConnectionDueToBabyJail(userId, e);
|
||||
|
||||
if (result.IsInvalid)
|
||||
return (ConnectionDenyReason.BabyJail, result.Reason, null);
|
||||
}
|
||||
|
||||
var wasInGame = EntitySystem.TryGet<GameTicker>(out var ticker) &&
|
||||
ticker.PlayerGameStatuses.TryGetValue(userId, out var status) &&
|
||||
status == PlayerGameStatus.JoinedGame;
|
||||
@@ -231,6 +246,57 @@ namespace Content.Server.Connection
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<(bool IsInvalid, string Reason)> IsInvalidConnectionDueToBabyJail(NetUserId userId, NetConnectingArgs e)
|
||||
{
|
||||
// If you're whitelisted then bypass this whole thing
|
||||
if (await _db.GetWhitelistStatusAsync(userId))
|
||||
return (false, "");
|
||||
|
||||
// Initial cvar retrieval
|
||||
var showReason = _cfg.GetCVar(CCVars.BabyJailShowReason);
|
||||
var reason = _cfg.GetCVar(CCVars.BabyJailCustomReason);
|
||||
var maxAccountAgeMinutes = _cfg.GetCVar(CCVars.BabyJailMaxAccountAge);
|
||||
var maxPlaytimeMinutes = _cfg.GetCVar(CCVars.BabyJailMaxOverallMinutes);
|
||||
|
||||
// Wait some time to lookup data
|
||||
var record = await _dbManager.GetPlayerRecordByUserId(userId);
|
||||
|
||||
var isAccountAgeInvalid = record == null || record.FirstSeenTime.CompareTo(DateTimeOffset.Now - TimeSpan.FromMinutes(maxAccountAgeMinutes)) <= 0;
|
||||
if (isAccountAgeInvalid && showReason)
|
||||
{
|
||||
var locAccountReason = reason != string.Empty
|
||||
? reason
|
||||
: Loc.GetString("baby-jail-account-denied-reason",
|
||||
("reason",
|
||||
Loc.GetString(
|
||||
"baby-jail-account-reason-account",
|
||||
("minutes", maxAccountAgeMinutes))));
|
||||
|
||||
return (true, locAccountReason);
|
||||
}
|
||||
|
||||
var overallTime = ( await _db.GetPlayTimes(e.UserId)).Find(p => p.Tracker == PlayTimeTrackingShared.TrackerOverall);
|
||||
var isTotalPlaytimeInvalid = overallTime == null || overallTime.TimeSpent.TotalMinutes >= maxPlaytimeMinutes;
|
||||
|
||||
if (isTotalPlaytimeInvalid && showReason)
|
||||
{
|
||||
var locPlaytimeReason = reason != string.Empty
|
||||
? reason
|
||||
: Loc.GetString("baby-jail-account-denied-reason",
|
||||
("reason",
|
||||
Loc.GetString(
|
||||
"baby-jail-account-reason-overall",
|
||||
("minutes", maxPlaytimeMinutes))));
|
||||
|
||||
return (true, locPlaytimeReason);
|
||||
}
|
||||
|
||||
if (!showReason && isTotalPlaytimeInvalid || isAccountAgeInvalid)
|
||||
return (true, Loc.GetString("baby-jail-account-denied"));
|
||||
|
||||
return (false, "");
|
||||
}
|
||||
|
||||
private bool HasTemporaryBypass(NetUserId user)
|
||||
{
|
||||
return _temporaryBypasses.TryGetValue(user, out var time) && time > _gameTiming.RealTime;
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
using Content.Shared.Construction.Components;
|
||||
|
||||
namespace Content.Server.Construction.Components
|
||||
{
|
||||
[RequiresExplicitImplementation]
|
||||
public interface IRefreshParts
|
||||
{
|
||||
void RefreshParts(IEnumerable<MachinePartComponent> parts);
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,17 @@
|
||||
using Robust.Shared.Containers;
|
||||
using Content.Shared.Construction.Components;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Construction.Components
|
||||
namespace Content.Server.Construction.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class MachineComponent : Component
|
||||
{
|
||||
[RegisterComponent, ComponentProtoName("Machine")]
|
||||
public sealed partial class MachineComponent : Component
|
||||
{
|
||||
[DataField("board", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string? BoardPrototype { get; private set; }
|
||||
[DataField]
|
||||
public EntProtoId<MachineBoardComponent>? Board { get; private set; }
|
||||
|
||||
[ViewVariables]
|
||||
public Container BoardContainer = default!;
|
||||
[ViewVariables]
|
||||
public Container PartContainer = default!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The different types of scaling that are available for machine upgrades
|
||||
/// </summary>
|
||||
public enum MachineUpgradeScalingType : byte
|
||||
{
|
||||
Linear,
|
||||
Exponential
|
||||
}
|
||||
[ViewVariables]
|
||||
public Container BoardContainer = default!;
|
||||
[ViewVariables]
|
||||
public Container PartContainer = default!;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
using Content.Shared.Construction.Components;
|
||||
using Content.Shared.Construction.Prototypes;
|
||||
using Content.Shared.Stacks;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Construction.Components
|
||||
{
|
||||
@@ -14,29 +15,23 @@ namespace Content.Server.Construction.Components
|
||||
[ViewVariables]
|
||||
public bool HasBoard => BoardContainer?.ContainedEntities.Count != 0;
|
||||
|
||||
[DataField("progress", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<int, MachinePartPrototype>))]
|
||||
public Dictionary<string, int> Progress = new();
|
||||
|
||||
[ViewVariables]
|
||||
public readonly Dictionary<string, int> MaterialProgress = new();
|
||||
public readonly Dictionary<ProtoId<StackPrototype>, int> MaterialProgress = new();
|
||||
|
||||
[ViewVariables]
|
||||
public readonly Dictionary<string, int> ComponentProgress = new();
|
||||
|
||||
[ViewVariables]
|
||||
public readonly Dictionary<string, int> TagProgress = new();
|
||||
|
||||
[DataField("requirements", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<int, MachinePartPrototype>))]
|
||||
public Dictionary<string, int> Requirements = new();
|
||||
public readonly Dictionary<ProtoId<TagPrototype>, int> TagProgress = new();
|
||||
|
||||
[ViewVariables]
|
||||
public Dictionary<string, int> MaterialRequirements = new();
|
||||
public Dictionary<ProtoId<StackPrototype>, int> MaterialRequirements = new();
|
||||
|
||||
[ViewVariables]
|
||||
public Dictionary<string, GenericPartInfo> ComponentRequirements = new();
|
||||
|
||||
[ViewVariables]
|
||||
public Dictionary<string, GenericPartInfo> TagRequirements = new();
|
||||
public Dictionary<ProtoId<TagPrototype>, GenericPartInfo> TagRequirements = new();
|
||||
|
||||
[ViewVariables]
|
||||
public Container BoardContainer = default!;
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
namespace Content.Server.Construction.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class PartExchangerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// How long it takes to exchange the parts
|
||||
/// </summary>
|
||||
[DataField("exchangeDuration")]
|
||||
public float ExchangeDuration = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the distance check is needed.
|
||||
/// Good for BRPED.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// I fucking hate BRPED and if you ever add it
|
||||
/// i will personally kill your dog.
|
||||
/// </remarks>
|
||||
[DataField("doDistanceCheck")]
|
||||
public bool DoDistanceCheck = true;
|
||||
|
||||
[DataField("exchangeSound")]
|
||||
public SoundSpecifier ExchangeSound = new SoundPathSpecifier("/Audio/Items/rped.ogg");
|
||||
|
||||
public EntityUid? AudioStream;
|
||||
}
|
||||
@@ -2,6 +2,7 @@ using Content.Server.Construction.Components;
|
||||
using Content.Shared.Construction;
|
||||
using Content.Shared.Examine;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Construction.Conditions
|
||||
@@ -17,7 +18,7 @@ namespace Content.Server.Construction.Conditions
|
||||
public SpriteSpecifier? GuideIconBoard { get; private set; }
|
||||
|
||||
[DataField("guideIconParts")]
|
||||
public SpriteSpecifier? GuideIconPart { get; private set; }
|
||||
public SpriteSpecifier? GuideIconParts { get; private set; }
|
||||
|
||||
|
||||
public bool Condition(EntityUid uid, IEntityManager entityManager)
|
||||
@@ -33,6 +34,8 @@ namespace Content.Server.Construction.Conditions
|
||||
var entity = args.Examined;
|
||||
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
var protoManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
var constructionSys = entityManager.System<ConstructionSystem>();
|
||||
|
||||
if (!entityManager.TryGetComponent(entity, out MachineFrameComponent? machineFrame))
|
||||
return false;
|
||||
@@ -47,17 +50,6 @@ namespace Content.Server.Construction.Conditions
|
||||
return false;
|
||||
|
||||
args.PushMarkup(Loc.GetString("construction-condition-machine-frame-requirement-label"));
|
||||
foreach (var (part, required) in machineFrame.Requirements)
|
||||
{
|
||||
var amount = required - machineFrame.Progress[part];
|
||||
|
||||
if(amount == 0)
|
||||
continue;
|
||||
|
||||
args.PushMarkup(Loc.GetString("construction-condition-machine-frame-required-element-entry",
|
||||
("amount", amount),
|
||||
("elementName", Loc.GetString(part))));
|
||||
}
|
||||
|
||||
foreach (var (material, required) in machineFrame.MaterialRequirements)
|
||||
{
|
||||
@@ -65,10 +57,12 @@ namespace Content.Server.Construction.Conditions
|
||||
|
||||
if(amount == 0)
|
||||
continue;
|
||||
var stack = protoManager.Index(material);
|
||||
var stackEnt = protoManager.Index(stack.Spawn);
|
||||
|
||||
args.PushMarkup(Loc.GetString("construction-condition-machine-frame-required-element-entry",
|
||||
("amount", amount),
|
||||
("elementName", Loc.GetString(material))));
|
||||
("elementName", stackEnt.Name)));
|
||||
}
|
||||
|
||||
foreach (var (compName, info) in machineFrame.ComponentRequirements)
|
||||
@@ -78,9 +72,10 @@ namespace Content.Server.Construction.Conditions
|
||||
if(amount == 0)
|
||||
continue;
|
||||
|
||||
var examineName = constructionSys.GetExamineName(info);
|
||||
args.PushMarkup(Loc.GetString("construction-condition-machine-frame-required-element-entry",
|
||||
("amount", info.Amount),
|
||||
("elementName", Loc.GetString(info.ExamineName))));
|
||||
("elementName", examineName)));
|
||||
}
|
||||
|
||||
foreach (var (tagName, info) in machineFrame.TagRequirements)
|
||||
@@ -90,9 +85,10 @@ namespace Content.Server.Construction.Conditions
|
||||
if(amount == 0)
|
||||
continue;
|
||||
|
||||
var examineName = constructionSys.GetExamineName(info);
|
||||
args.PushMarkup(Loc.GetString("construction-condition-machine-frame-required-element-entry",
|
||||
("amount", info.Amount),
|
||||
("elementName", Loc.GetString(info.ExamineName)))
|
||||
("elementName", examineName))
|
||||
+ "\n");
|
||||
}
|
||||
|
||||
@@ -111,7 +107,7 @@ namespace Content.Server.Construction.Conditions
|
||||
yield return new ConstructionGuideEntry()
|
||||
{
|
||||
Localization = "construction-step-condition-machine-frame-parts",
|
||||
Icon = GuideIconPart,
|
||||
Icon = GuideIconParts,
|
||||
EntryNumber = 0, // Set this to anything so the guide generation takes this as a numbered step.
|
||||
};
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace Content.Server.Construction
|
||||
|
||||
// If the set graph prototype does not exist, also return null. This could be due to admemes changing values
|
||||
// in ViewVariables, so even though the construction state is invalid, just return null.
|
||||
return _prototypeManager.TryIndex(construction.Graph, out ConstructionGraphPrototype? graph) ? graph : null;
|
||||
return PrototypeManager.TryIndex(construction.Graph, out ConstructionGraphPrototype? graph) ? graph : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -300,7 +300,7 @@ namespace Content.Server.Construction
|
||||
}
|
||||
|
||||
// Exit if the new entity's prototype is the same as the original, or the prototype is invalid
|
||||
if (newEntity == metaData.EntityPrototype?.ID || !_prototypeManager.HasIndex<EntityPrototype>(newEntity))
|
||||
if (newEntity == metaData.EntityPrototype?.ID || !PrototypeManager.HasIndex<EntityPrototype>(newEntity))
|
||||
return null;
|
||||
|
||||
// [Optional] Exit if the new entity's prototype is a parent of the original
|
||||
@@ -310,7 +310,7 @@ namespace Content.Server.Construction
|
||||
if (GetCurrentNode(uid, construction)?.DoNotReplaceInheritingEntities == true &&
|
||||
metaData.EntityPrototype?.ID != null)
|
||||
{
|
||||
var parents = _prototypeManager.EnumerateParents<EntityPrototype>(metaData.EntityPrototype.ID)?.ToList();
|
||||
var parents = PrototypeManager.EnumerateParents<EntityPrototype>(metaData.EntityPrototype.ID)?.ToList();
|
||||
|
||||
if (parents != null && parents.Any(x => x.ID == newEntity))
|
||||
return null;
|
||||
@@ -427,7 +427,7 @@ namespace Content.Server.Construction
|
||||
if (!Resolve(uid, ref construction))
|
||||
return false;
|
||||
|
||||
if (!_prototypeManager.TryIndex<ConstructionGraphPrototype>(graphId, out var graph))
|
||||
if (!PrototypeManager.TryIndex<ConstructionGraphPrototype>(graphId, out var graph))
|
||||
return false;
|
||||
|
||||
if(GetNodeFromGraph(graph, nodeId) is not {})
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace Content.Server.Construction
|
||||
|
||||
private void OnGuideRequested(RequestConstructionGuide msg, EntitySessionEventArgs args)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(msg.ConstructionId, out ConstructionPrototype? prototype))
|
||||
if (!PrototypeManager.TryIndex(msg.ConstructionId, out ConstructionPrototype? prototype))
|
||||
return;
|
||||
|
||||
if(GetGuide(prototype) is {} guide)
|
||||
@@ -41,7 +41,7 @@ namespace Content.Server.Construction
|
||||
component.Node == component.DeconstructionNode)
|
||||
return;
|
||||
|
||||
if (!_prototypeManager.TryIndex(component.Graph, out ConstructionGraphPrototype? graph))
|
||||
if (!PrototypeManager.TryIndex(component.Graph, out ConstructionGraphPrototype? graph))
|
||||
return;
|
||||
|
||||
if (component.DeconstructionNode == null)
|
||||
@@ -145,7 +145,7 @@ namespace Content.Server.Construction
|
||||
return guide;
|
||||
|
||||
// If the graph doesn't actually exist, do nothing.
|
||||
if (!_prototypeManager.TryIndex(construction.Graph, out ConstructionGraphPrototype? graph))
|
||||
if (!PrototypeManager.TryIndex(construction.Graph, out ConstructionGraphPrototype? graph))
|
||||
return null;
|
||||
|
||||
// If either the start node or the target node are missing, do nothing.
|
||||
|
||||
@@ -325,13 +325,13 @@ namespace Content.Server.Construction
|
||||
// LEGACY CODE. See warning at the top of the file!
|
||||
public async Task<bool> TryStartItemConstruction(string prototype, EntityUid user)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(prototype, out ConstructionPrototype? constructionPrototype))
|
||||
if (!PrototypeManager.TryIndex(prototype, out ConstructionPrototype? constructionPrototype))
|
||||
{
|
||||
Log.Error($"Tried to start construction of invalid recipe '{prototype}'!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_prototypeManager.TryIndex(constructionPrototype.Graph,
|
||||
if (!PrototypeManager.TryIndex(constructionPrototype.Graph,
|
||||
out ConstructionGraphPrototype? constructionGraph))
|
||||
{
|
||||
Log.Error(
|
||||
@@ -404,14 +404,14 @@ namespace Content.Server.Construction
|
||||
// LEGACY CODE. See warning at the top of the file!
|
||||
private async void HandleStartStructureConstruction(TryStartStructureConstructionMessage ev, EntitySessionEventArgs args)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(ev.PrototypeName, out ConstructionPrototype? constructionPrototype))
|
||||
if (!PrototypeManager.TryIndex(ev.PrototypeName, out ConstructionPrototype? constructionPrototype))
|
||||
{
|
||||
Log.Error($"Tried to start construction of invalid recipe '{ev.PrototypeName}'!");
|
||||
RaiseNetworkEvent(new AckStructureConstructionMessage(ev.Ack));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_prototypeManager.TryIndex(constructionPrototype.Graph, out ConstructionGraphPrototype? constructionGraph))
|
||||
if (!PrototypeManager.TryIndex(constructionPrototype.Graph, out ConstructionGraphPrototype? constructionGraph))
|
||||
{
|
||||
Log.Error($"Invalid construction graph '{constructionPrototype.Graph}' in recipe '{ev.PrototypeName}'!");
|
||||
RaiseNetworkEvent(new AckStructureConstructionMessage(ev.Ack));
|
||||
|
||||
@@ -1,23 +1,15 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Construction.Components;
|
||||
using Content.Server.Examine;
|
||||
using Content.Shared.Construction.Components;
|
||||
using Content.Shared.Construction.Prototypes;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Construction;
|
||||
|
||||
public sealed partial class ConstructionSystem
|
||||
{
|
||||
[Dependency] private readonly ExamineSystem _examineSystem = default!;
|
||||
|
||||
private void InitializeMachines()
|
||||
{
|
||||
SubscribeLocalEvent<MachineComponent, ComponentInit>(OnMachineInit);
|
||||
SubscribeLocalEvent<MachineComponent, MapInitEvent>(OnMachineMapInit);
|
||||
SubscribeLocalEvent<MachineComponent, GetVerbsEvent<ExamineVerb>>(OnMachineExaminableVerb);
|
||||
}
|
||||
|
||||
private void OnMachineInit(EntityUid uid, MachineComponent component, ComponentInit args)
|
||||
@@ -29,84 +21,6 @@ public sealed partial class ConstructionSystem
|
||||
private void OnMachineMapInit(EntityUid uid, MachineComponent component, MapInitEvent args)
|
||||
{
|
||||
CreateBoardAndStockParts(uid, component);
|
||||
RefreshParts(uid, component);
|
||||
}
|
||||
|
||||
private void OnMachineExaminableVerb(EntityUid uid, MachineComponent component, GetVerbsEvent<ExamineVerb> args)
|
||||
{
|
||||
if (!args.CanInteract || !args.CanAccess)
|
||||
return;
|
||||
|
||||
var markup = new FormattedMessage();
|
||||
RaiseLocalEvent(uid, new UpgradeExamineEvent(ref markup));
|
||||
if (markup.IsEmpty)
|
||||
return; // Not upgradable.
|
||||
|
||||
markup = FormattedMessage.FromMarkup(markup.ToMarkup().TrimEnd('\n')); // Cursed workaround to https://github.com/space-wizards/RobustToolbox/issues/3371
|
||||
|
||||
var verb = new ExamineVerb()
|
||||
{
|
||||
Act = () =>
|
||||
{
|
||||
_examineSystem.SendExamineTooltip(args.User, uid, markup, getVerbs: false, centerAtCursor: false);
|
||||
},
|
||||
Text = Loc.GetString("machine-upgrade-examinable-verb-text"),
|
||||
Message = Loc.GetString("machine-upgrade-examinable-verb-message"),
|
||||
Category = VerbCategory.Examine,
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/pickup.svg.192dpi.png"))
|
||||
};
|
||||
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
public List<MachinePartComponent> GetAllParts(EntityUid uid, MachineComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return new List<MachinePartComponent>();
|
||||
|
||||
return GetAllParts(component);
|
||||
}
|
||||
|
||||
public List<MachinePartComponent> GetAllParts(MachineComponent component)
|
||||
{
|
||||
var parts = new List<MachinePartComponent>();
|
||||
|
||||
foreach (var entity in component.PartContainer.ContainedEntities)
|
||||
{
|
||||
if (TryComp<MachinePartComponent>(entity, out var machinePart))
|
||||
parts.Add(machinePart);
|
||||
}
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
public Dictionary<string, float> GetPartsRatings(List<MachinePartComponent> parts)
|
||||
{
|
||||
var output = new Dictionary<string, float>();
|
||||
foreach (var type in _prototypeManager.EnumeratePrototypes<MachinePartPrototype>())
|
||||
{
|
||||
var amount = 0f;
|
||||
var sumRating = 0f;
|
||||
foreach (var part in parts.Where(part => part.PartType == type.ID))
|
||||
{
|
||||
amount++;
|
||||
sumRating += part.Rating;
|
||||
}
|
||||
var rating = amount != 0 ? sumRating / amount : 0;
|
||||
output.Add(type.ID, rating);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public void RefreshParts(EntityUid uid, MachineComponent component)
|
||||
{
|
||||
var parts = GetAllParts(component);
|
||||
EntityManager.EventBus.RaiseLocalEvent(uid, new RefreshPartsEvent
|
||||
{
|
||||
Parts = parts,
|
||||
PartRatings = GetPartsRatings(parts),
|
||||
}, true);
|
||||
}
|
||||
|
||||
private void CreateBoardAndStockParts(EntityUid uid, MachineComponent component)
|
||||
@@ -115,54 +29,37 @@ public sealed partial class ConstructionSystem
|
||||
var boardContainer = _container.EnsureContainer<Container>(uid, MachineFrameComponent.BoardContainerName);
|
||||
var partContainer = _container.EnsureContainer<Container>(uid, MachineFrameComponent.PartContainerName);
|
||||
|
||||
if (string.IsNullOrEmpty(component.BoardPrototype))
|
||||
if (string.IsNullOrEmpty(component.Board))
|
||||
return;
|
||||
|
||||
// We're done here, let's suppose all containers are correct just so we don't screw SaveLoadSave.
|
||||
if (boardContainer.ContainedEntities.Count > 0)
|
||||
return;
|
||||
|
||||
var board = EntityManager.SpawnEntity(component.BoardPrototype, Transform(uid).Coordinates);
|
||||
|
||||
if (!_container.Insert(board, component.BoardContainer))
|
||||
var xform = Transform(uid);
|
||||
if (!TrySpawnInContainer(component.Board, uid, MachineFrameComponent.BoardContainerName, out var board))
|
||||
{
|
||||
throw new Exception($"Couldn't insert board with prototype {component.BoardPrototype} to machine with prototype {MetaData(uid).EntityPrototype?.ID ?? "N/A"}!");
|
||||
throw new Exception($"Couldn't insert board with prototype {component.Board} to machine with prototype {Prototype(uid)?.ID ?? "N/A"}!");
|
||||
}
|
||||
|
||||
if (!TryComp<MachineBoardComponent>(board, out var machineBoard))
|
||||
{
|
||||
throw new Exception($"Entity with prototype {component.BoardPrototype} doesn't have a {nameof(MachineBoardComponent)}!");
|
||||
throw new Exception($"Entity with prototype {component.Board} doesn't have a {nameof(MachineBoardComponent)}!");
|
||||
}
|
||||
|
||||
var xform = Transform(uid);
|
||||
foreach (var (part, amount) in machineBoard.Requirements)
|
||||
foreach (var (stackType, amount) in machineBoard.StackRequirements)
|
||||
{
|
||||
var partProto = _prototypeManager.Index<MachinePartPrototype>(part);
|
||||
for (var i = 0; i < amount; i++)
|
||||
{
|
||||
var p = EntityManager.SpawnEntity(partProto.StockPartPrototype, xform.Coordinates);
|
||||
|
||||
if (!_container.Insert(p, partContainer))
|
||||
throw new Exception($"Couldn't insert machine part of type {part} to machine with prototype {partProto.StockPartPrototype}!");
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (stackType, amount) in machineBoard.MaterialRequirements)
|
||||
{
|
||||
var stack = _stackSystem.Spawn(amount, stackType, Transform(uid).Coordinates);
|
||||
|
||||
var stack = _stackSystem.Spawn(amount, stackType, xform.Coordinates);
|
||||
if (!_container.Insert(stack, partContainer))
|
||||
throw new Exception($"Couldn't insert machine material of type {stackType} to machine with prototype {MetaData(uid).EntityPrototype?.ID ?? "N/A"}");
|
||||
throw new Exception($"Couldn't insert machine material of type {stackType} to machine with prototype {Prototype(uid)?.ID ?? "N/A"}");
|
||||
}
|
||||
|
||||
foreach (var (compName, info) in machineBoard.ComponentRequirements)
|
||||
{
|
||||
for (var i = 0; i < info.Amount; i++)
|
||||
{
|
||||
var c = EntityManager.SpawnEntity(info.DefaultPrototype, Transform(uid).Coordinates);
|
||||
|
||||
if(!_container.Insert(c, partContainer))
|
||||
throw new Exception($"Couldn't insert machine component part with default prototype '{compName}' to machine with prototype {MetaData(uid).EntityPrototype?.ID ?? "N/A"}");
|
||||
if(!TrySpawnInContainer(info.DefaultPrototype, uid, MachineFrameComponent.PartContainerName, out _))
|
||||
throw new Exception($"Couldn't insert machine component part with default prototype '{compName}' to machine with prototype {Prototype(uid)?.ID ?? "N/A"}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,58 +67,9 @@ public sealed partial class ConstructionSystem
|
||||
{
|
||||
for (var i = 0; i < info.Amount; i++)
|
||||
{
|
||||
var c = EntityManager.SpawnEntity(info.DefaultPrototype, Transform(uid).Coordinates);
|
||||
|
||||
if(!_container.Insert(c, partContainer))
|
||||
throw new Exception($"Couldn't insert machine component part with default prototype '{tagName}' to machine with prototype {MetaData(uid).EntityPrototype?.ID ?? "N/A"}");
|
||||
if(!TrySpawnInContainer(info.DefaultPrototype, uid, MachineFrameComponent.PartContainerName, out _))
|
||||
throw new Exception($"Couldn't insert machine component part with default prototype '{tagName}' to machine with prototype {Prototype(uid)?.ID ?? "N/A"}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class RefreshPartsEvent : EntityEventArgs
|
||||
{
|
||||
public IReadOnlyList<MachinePartComponent> Parts = new List<MachinePartComponent>();
|
||||
|
||||
public Dictionary<string, float> PartRatings = new();
|
||||
}
|
||||
|
||||
public sealed class UpgradeExamineEvent : EntityEventArgs
|
||||
{
|
||||
private FormattedMessage Message;
|
||||
|
||||
public UpgradeExamineEvent(ref FormattedMessage message)
|
||||
{
|
||||
Message = message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a line to the upgrade examine tooltip with a percentage-based increase or decrease.
|
||||
/// </summary>
|
||||
public void AddPercentageUpgrade(string upgradedLocId, float multiplier)
|
||||
{
|
||||
var percent = Math.Round(100 * MathF.Abs(multiplier - 1), 2);
|
||||
var locId = multiplier switch {
|
||||
< 1 => "machine-upgrade-decreased-by-percentage",
|
||||
1 or float.NaN => "machine-upgrade-not-upgraded",
|
||||
> 1 => "machine-upgrade-increased-by-percentage",
|
||||
};
|
||||
var upgraded = Loc.GetString(upgradedLocId);
|
||||
this.Message.AddMarkup(Loc.GetString(locId, ("upgraded", upgraded), ("percent", percent)) + '\n');
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a line to the upgrade examine tooltip with a numeric increase or decrease.
|
||||
/// </summary>
|
||||
public void AddNumberUpgrade(string upgradedLocId, int number)
|
||||
{
|
||||
var difference = Math.Abs(number);
|
||||
var locId = number switch {
|
||||
< 0 => "machine-upgrade-decreased-by-amount",
|
||||
0 => "machine-upgrade-not-upgraded",
|
||||
> 0 => "machine-upgrade-increased-by-amount",
|
||||
};
|
||||
var upgraded = Loc.GetString(upgradedLocId);
|
||||
this.Message.AddMarkup(Loc.GetString(locId, ("upgraded", upgraded), ("difference", difference)) + '\n');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ namespace Content.Server.Construction
|
||||
[UsedImplicitly]
|
||||
public sealed partial class ConstructionSystem : SharedConstructionSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private readonly ContainerSystem _container = default!;
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.Server.Power.EntitySystems;
|
||||
using Content.Shared.Construction;
|
||||
using Content.Shared.Construction.Components;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Construction;
|
||||
@@ -30,21 +31,24 @@ public sealed class FlatpackSystem : SharedFlatpackSystem
|
||||
if (!this.IsPowered(ent, EntityManager) || comp.Packing)
|
||||
return;
|
||||
|
||||
if (!_itemSlots.TryGetSlot(uid, comp.SlotId, out var itemSlot) || itemSlot.Item is not { } machineBoard)
|
||||
if (!_itemSlots.TryGetSlot(uid, comp.SlotId, out var itemSlot) || itemSlot.Item is not { } board)
|
||||
return;
|
||||
|
||||
Dictionary<string, int>? cost = null;
|
||||
if (TryComp<MachineBoardComponent>(machineBoard, out var machineBoardComponent))
|
||||
cost = GetFlatpackCreationCost(ent, (machineBoard, machineBoardComponent));
|
||||
if (HasComp<ComputerBoardComponent>(machineBoard))
|
||||
cost = GetFlatpackCreationCost(ent);
|
||||
|
||||
if (cost is null)
|
||||
Dictionary<string, int> cost;
|
||||
if (TryComp<MachineBoardComponent>(board, out var machine))
|
||||
cost = GetFlatpackCreationCost(ent, (board, machine));
|
||||
else if (TryComp<ComputerBoardComponent>(board, out var computer) && computer.Prototype != null)
|
||||
cost = GetFlatpackCreationCost(ent, null);
|
||||
else
|
||||
{
|
||||
Log.Error($"Encountered invalid flatpack board while packing: {ToPrettyString(board)}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!MaterialStorage.CanChangeMaterialAmount(uid, cost))
|
||||
return;
|
||||
|
||||
_itemSlots.SetLock(uid, comp.SlotId, true);
|
||||
comp.Packing = true;
|
||||
comp.PackEndTime = _timing.CurTime + comp.PackDuration;
|
||||
Appearance.SetData(uid, FlatpackCreatorVisuals.Packing, true);
|
||||
@@ -63,6 +67,7 @@ public sealed class FlatpackSystem : SharedFlatpackSystem
|
||||
{
|
||||
var (uid, comp) = ent;
|
||||
|
||||
_itemSlots.SetLock(uid, comp.SlotId, false);
|
||||
comp.Packing = false;
|
||||
Appearance.SetData(uid, FlatpackCreatorVisuals.Packing, false);
|
||||
_ambientSound.SetAmbience(uid, false);
|
||||
@@ -71,24 +76,33 @@ public sealed class FlatpackSystem : SharedFlatpackSystem
|
||||
if (interrupted)
|
||||
return;
|
||||
|
||||
if (!_itemSlots.TryGetSlot(uid, comp.SlotId, out var itemSlot) || itemSlot.Item is not { } machineBoard)
|
||||
if (!_itemSlots.TryGetSlot(uid, comp.SlotId, out var itemSlot) || itemSlot.Item is not { } board)
|
||||
return;
|
||||
|
||||
Dictionary<string, int>? cost = null;
|
||||
if (TryComp<MachineBoardComponent>(machineBoard, out var machineBoardComponent))
|
||||
cost = GetFlatpackCreationCost(ent, (machineBoard, machineBoardComponent));
|
||||
if (HasComp<ComputerBoardComponent>(machineBoard))
|
||||
cost = GetFlatpackCreationCost(ent);
|
||||
|
||||
if (cost is null)
|
||||
Dictionary<string, int> cost;
|
||||
EntProtoId proto;
|
||||
if (TryComp<MachineBoardComponent>(board, out var machine))
|
||||
{
|
||||
cost = GetFlatpackCreationCost(ent, (board, machine));
|
||||
proto = machine.Prototype;
|
||||
}
|
||||
else if (TryComp<ComputerBoardComponent>(board, out var computer) && computer.Prototype != null)
|
||||
{
|
||||
cost = GetFlatpackCreationCost(ent, null);
|
||||
proto = computer.Prototype;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"Encountered invalid flatpack board while packing: {ToPrettyString(board)}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!MaterialStorage.TryChangeMaterialAmount((ent, null), cost))
|
||||
return;
|
||||
|
||||
var flatpack = Spawn(comp.BaseFlatpackPrototype, Transform(ent).Coordinates);
|
||||
SetupFlatpack(flatpack, machineBoard);
|
||||
Del(machineBoard);
|
||||
SetupFlatpack(flatpack, proto, board);
|
||||
Del(board);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
|
||||
@@ -7,7 +7,7 @@ using Content.Shared.Stacks;
|
||||
using Content.Shared.Tag;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Construction;
|
||||
|
||||
@@ -62,24 +62,7 @@ public sealed class MachineFrameSystem : EntitySystem
|
||||
// If this changes in the future, then RegenerateProgress() also needs to be updated.
|
||||
// Note that one entity is ALLOWED to satisfy more than one kind of component or tag requirements. This is
|
||||
// necessary in order to avoid weird entity-ordering shenanigans in RegenerateProgress().
|
||||
var stack = CompOrNull<StackComponent>(args.Used);
|
||||
var machinePart = CompOrNull<MachinePartComponent>(args.Used);
|
||||
if (stack != null && machinePart != null)
|
||||
{
|
||||
if (TryInsertPartStack(uid, args.Used, component, machinePart, stack))
|
||||
args.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle parts
|
||||
if (machinePart != null)
|
||||
{
|
||||
if (TryInsertPart(uid, args.Used, component, machinePart))
|
||||
args.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (stack != null)
|
||||
if (TryComp<StackComponent>(args.Used, out var stack))
|
||||
{
|
||||
if (TryInsertStack(uid, args.Used, component, stack))
|
||||
args.Handled = true;
|
||||
@@ -172,67 +155,6 @@ public sealed class MachineFrameSystem : EntitySystem
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <returns>Whether or not the function had any effect. Does not indicate success.</returns>
|
||||
private bool TryInsertPart(EntityUid uid, EntityUid used, MachineFrameComponent component, MachinePartComponent machinePart)
|
||||
{
|
||||
DebugTools.Assert(!HasComp<StackComponent>(uid));
|
||||
if (!component.Requirements.ContainsKey(machinePart.PartType))
|
||||
return false;
|
||||
|
||||
if (component.Progress[machinePart.PartType] >= component.Requirements[machinePart.PartType])
|
||||
return false;
|
||||
|
||||
if (!_container.TryRemoveFromContainer(used))
|
||||
return false;
|
||||
|
||||
if (!_container.Insert(used, component.PartContainer))
|
||||
return true;
|
||||
|
||||
component.Progress[machinePart.PartType]++;
|
||||
if (IsComplete(component))
|
||||
_popupSystem.PopupEntity(Loc.GetString("machine-frame-component-on-complete"), uid);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <returns>Whether or not the function had any effect. Does not indicate success.</returns>
|
||||
private bool TryInsertPartStack(EntityUid uid, EntityUid used, MachineFrameComponent component, MachinePartComponent machinePart, StackComponent stack)
|
||||
{
|
||||
if (!component.Requirements.ContainsKey(machinePart.PartType))
|
||||
return false;
|
||||
|
||||
var progress = component.Progress[machinePart.PartType];
|
||||
var requirement = component.Requirements[machinePart.PartType];
|
||||
|
||||
var needed = requirement - progress;
|
||||
if (needed <= 0)
|
||||
return false;
|
||||
|
||||
var count = stack.Count;
|
||||
if (count < needed)
|
||||
{
|
||||
if (!_container.Insert(used, component.PartContainer))
|
||||
return true;
|
||||
|
||||
component.Progress[machinePart.PartType] += count;
|
||||
return true;
|
||||
}
|
||||
|
||||
var splitStack = _stack.Split(used, needed, Transform(uid).Coordinates, stack);
|
||||
|
||||
if (splitStack == null)
|
||||
return false;
|
||||
|
||||
if (!_container.Insert(splitStack.Value, component.PartContainer))
|
||||
return true;
|
||||
|
||||
component.Progress[machinePart.PartType] += needed;
|
||||
if (IsComplete(component))
|
||||
_popupSystem.PopupEntity(Loc.GetString("machine-frame-component-on-complete"), uid);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <returns>Whether or not the function had any effect. Does not indicate success.</returns>
|
||||
private bool TryInsertStack(EntityUid uid, EntityUid used, MachineFrameComponent component, StackComponent stack)
|
||||
{
|
||||
@@ -281,12 +203,6 @@ public sealed class MachineFrameSystem : EntitySystem
|
||||
if (!component.HasBoard)
|
||||
return false;
|
||||
|
||||
foreach (var (part, amount) in component.Requirements)
|
||||
{
|
||||
if (component.Progress[part] < amount)
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var (type, amount) in component.MaterialRequirements)
|
||||
{
|
||||
if (component.MaterialProgress[type] < amount)
|
||||
@@ -310,21 +226,14 @@ public sealed class MachineFrameSystem : EntitySystem
|
||||
|
||||
public void ResetProgressAndRequirements(MachineFrameComponent component, MachineBoardComponent machineBoard)
|
||||
{
|
||||
component.Requirements = new Dictionary<string, int>(machineBoard.Requirements);
|
||||
component.MaterialRequirements = new Dictionary<string, int>(machineBoard.MaterialIdRequirements);
|
||||
component.MaterialRequirements = new Dictionary<ProtoId<StackPrototype>, int>(machineBoard.StackRequirements);
|
||||
component.ComponentRequirements = new Dictionary<string, GenericPartInfo>(machineBoard.ComponentRequirements);
|
||||
component.TagRequirements = new Dictionary<string, GenericPartInfo>(machineBoard.TagRequirements);
|
||||
component.TagRequirements = new Dictionary<ProtoId<TagPrototype>, GenericPartInfo>(machineBoard.TagRequirements);
|
||||
|
||||
component.Progress.Clear();
|
||||
component.MaterialProgress.Clear();
|
||||
component.ComponentProgress.Clear();
|
||||
component.TagProgress.Clear();
|
||||
|
||||
foreach (var (machinePart, _) in component.Requirements)
|
||||
{
|
||||
component.Progress[machinePart] = 0;
|
||||
}
|
||||
|
||||
foreach (var (stackType, _) in component.MaterialRequirements)
|
||||
{
|
||||
component.MaterialProgress[stackType] = 0;
|
||||
@@ -349,7 +258,6 @@ public sealed class MachineFrameSystem : EntitySystem
|
||||
component.MaterialRequirements.Clear();
|
||||
component.ComponentRequirements.Clear();
|
||||
component.TagRequirements.Clear();
|
||||
component.Progress.Clear();
|
||||
component.MaterialProgress.Clear();
|
||||
component.ComponentProgress.Clear();
|
||||
component.TagProgress.Clear();
|
||||
@@ -368,19 +276,6 @@ public sealed class MachineFrameSystem : EntitySystem
|
||||
|
||||
foreach (var part in component.PartContainer.ContainedEntities)
|
||||
{
|
||||
if (TryComp<MachinePartComponent>(part, out var machinePart))
|
||||
{
|
||||
// Check this is part of the requirements...
|
||||
if (!component.Requirements.ContainsKey(machinePart.PartType))
|
||||
continue;
|
||||
|
||||
if (!component.Progress.ContainsKey(machinePart.PartType))
|
||||
component.Progress[machinePart.PartType] = 1;
|
||||
else
|
||||
component.Progress[machinePart.PartType]++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (TryComp<StackComponent>(part, out var stack))
|
||||
{
|
||||
var type = stack.StackTypeId;
|
||||
@@ -404,9 +299,7 @@ public sealed class MachineFrameSystem : EntitySystem
|
||||
if (!HasComp(part, registration.Type))
|
||||
continue;
|
||||
|
||||
if (!component.ComponentProgress.ContainsKey(compName))
|
||||
component.ComponentProgress[compName] = 1;
|
||||
else
|
||||
if (!component.ComponentProgress.TryAdd(compName, 1))
|
||||
component.ComponentProgress[compName]++;
|
||||
}
|
||||
|
||||
@@ -419,18 +312,17 @@ public sealed class MachineFrameSystem : EntitySystem
|
||||
if (!_tag.HasTag(tagComp, tagName))
|
||||
continue;
|
||||
|
||||
if (!component.TagProgress.ContainsKey(tagName))
|
||||
component.TagProgress[tagName] = 1;
|
||||
else
|
||||
if (!component.TagProgress.TryAdd(tagName, 1))
|
||||
component.TagProgress[tagName]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
private void OnMachineFrameExamined(EntityUid uid, MachineFrameComponent component, ExaminedEvent args)
|
||||
{
|
||||
if (!args.IsInDetailsRange)
|
||||
if (!args.IsInDetailsRange || !component.HasBoard)
|
||||
return;
|
||||
if (component.HasBoard)
|
||||
args.PushMarkup(Loc.GetString("machine-frame-component-on-examine-label", ("board", EntityManager.GetComponent<MetaDataComponent>(component.BoardContainer.ContainedEntities[0]).EntityName)));
|
||||
|
||||
var board = component.BoardContainer.ContainedEntities[0];
|
||||
args.PushMarkup(Loc.GetString("machine-frame-component-on-examine-label", ("board", Name(board))));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,180 +0,0 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Construction.Components;
|
||||
using Content.Server.Storage.EntitySystems;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Construction.Components;
|
||||
using Content.Shared.Exchanger;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Storage;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Utility;
|
||||
using Content.Shared.Wires;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Collections;
|
||||
|
||||
namespace Content.Server.Construction;
|
||||
|
||||
public sealed class PartExchangerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ConstructionSystem _construction = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly StorageSystem _storage = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<PartExchangerComponent, AfterInteractEvent>(OnAfterInteract);
|
||||
SubscribeLocalEvent<PartExchangerComponent, ExchangerDoAfterEvent>(OnDoAfter);
|
||||
}
|
||||
|
||||
private void OnDoAfter(EntityUid uid, PartExchangerComponent component, DoAfterEvent args)
|
||||
{
|
||||
if (args.Cancelled)
|
||||
{
|
||||
component.AudioStream = _audio.Stop(component.AudioStream);
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Handled || args.Args.Target == null)
|
||||
return;
|
||||
|
||||
if (!TryComp<StorageComponent>(uid, out var storage))
|
||||
return; //the parts are stored in here
|
||||
|
||||
var machinePartQuery = GetEntityQuery<MachinePartComponent>();
|
||||
var machineParts = new List<(EntityUid, MachinePartComponent)>();
|
||||
|
||||
foreach (var item in storage.Container.ContainedEntities) //get parts in RPED
|
||||
{
|
||||
if (machinePartQuery.TryGetComponent(item, out var part))
|
||||
machineParts.Add((item, part));
|
||||
}
|
||||
|
||||
TryExchangeMachineParts(args.Args.Target.Value, uid, machineParts);
|
||||
TryConstructMachineParts(args.Args.Target.Value, uid, machineParts);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void TryExchangeMachineParts(EntityUid uid, EntityUid storageUid, List<(EntityUid part, MachinePartComponent partComp)> machineParts)
|
||||
{
|
||||
if (!TryComp<MachineComponent>(uid, out var machine))
|
||||
return;
|
||||
|
||||
var machinePartQuery = GetEntityQuery<MachinePartComponent>();
|
||||
var board = machine.BoardContainer.ContainedEntities.FirstOrNull();
|
||||
|
||||
if (board == null || !TryComp<MachineBoardComponent>(board, out var macBoardComp))
|
||||
return;
|
||||
|
||||
foreach (var item in new ValueList<EntityUid>(machine.PartContainer.ContainedEntities)) //clone so don't modify during enumeration
|
||||
{
|
||||
if (machinePartQuery.TryGetComponent(item, out var part))
|
||||
{
|
||||
machineParts.Add((item, part));
|
||||
_container.RemoveEntity(uid, item);
|
||||
}
|
||||
}
|
||||
|
||||
machineParts.Sort((x, y) => y.partComp.Rating.CompareTo(x.partComp.Rating));
|
||||
|
||||
var updatedParts = new List<(EntityUid part, MachinePartComponent partComp)>();
|
||||
foreach (var (type, amount) in macBoardComp.Requirements)
|
||||
{
|
||||
var target = machineParts.Where(p => p.partComp.PartType == type).Take(amount);
|
||||
updatedParts.AddRange(target);
|
||||
}
|
||||
foreach (var part in updatedParts)
|
||||
{
|
||||
_container.Insert(part.part, machine.PartContainer);
|
||||
machineParts.Remove(part);
|
||||
}
|
||||
|
||||
//put the unused parts back into rped. (this also does the "swapping")
|
||||
foreach (var (unused, _) in machineParts)
|
||||
{
|
||||
_storage.Insert(storageUid, unused, out _, playSound: false);
|
||||
}
|
||||
_construction.RefreshParts(uid, machine);
|
||||
}
|
||||
|
||||
private void TryConstructMachineParts(EntityUid uid, EntityUid storageEnt, List<(EntityUid part, MachinePartComponent partComp)> machineParts)
|
||||
{
|
||||
if (!TryComp<MachineFrameComponent>(uid, out var machine))
|
||||
return;
|
||||
|
||||
var machinePartQuery = GetEntityQuery<MachinePartComponent>();
|
||||
var board = machine.BoardContainer.ContainedEntities.FirstOrNull();
|
||||
|
||||
if (!machine.HasBoard || !TryComp<MachineBoardComponent>(board, out var macBoardComp))
|
||||
return;
|
||||
|
||||
foreach (var item in new ValueList<EntityUid>(machine.PartContainer.ContainedEntities)) //clone so don't modify during enumeration
|
||||
{
|
||||
if (machinePartQuery.TryGetComponent(item, out var part))
|
||||
{
|
||||
machineParts.Add((item, part));
|
||||
_container.RemoveEntity(uid, item);
|
||||
machine.Progress[part.PartType]--;
|
||||
}
|
||||
}
|
||||
|
||||
machineParts.Sort((x, y) => y.partComp.Rating.CompareTo(x.partComp.Rating));
|
||||
|
||||
var updatedParts = new List<(EntityUid part, MachinePartComponent partComp)>();
|
||||
foreach (var (type, amount) in macBoardComp.Requirements)
|
||||
{
|
||||
var target = machineParts.Where(p => p.partComp.PartType == type).Take(amount);
|
||||
updatedParts.AddRange(target);
|
||||
}
|
||||
foreach (var pair in updatedParts)
|
||||
{
|
||||
var part = pair.partComp;
|
||||
var partEnt = pair.part;
|
||||
|
||||
if (!machine.Requirements.ContainsKey(part.PartType))
|
||||
continue;
|
||||
|
||||
_container.Insert(partEnt, machine.PartContainer);
|
||||
machine.Progress[part.PartType]++;
|
||||
machineParts.Remove(pair);
|
||||
}
|
||||
|
||||
//put the unused parts back into rped. (this also does the "swapping")
|
||||
foreach (var (unused, _) in machineParts)
|
||||
{
|
||||
_storage.Insert(storageEnt, unused, out _, playSound: false);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAfterInteract(EntityUid uid, PartExchangerComponent component, AfterInteractEvent args)
|
||||
{
|
||||
if (component.DoDistanceCheck && !args.CanReach)
|
||||
return;
|
||||
|
||||
if (args.Target == null)
|
||||
return;
|
||||
|
||||
if (!HasComp<MachineComponent>(args.Target) && !HasComp<MachineFrameComponent>(args.Target))
|
||||
return;
|
||||
|
||||
if (TryComp<WiresPanelComponent>(args.Target, out var panel) && !panel.Open)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("construction-step-condition-wire-panel-open"),
|
||||
args.Target.Value);
|
||||
return;
|
||||
}
|
||||
|
||||
component.AudioStream = _audio.PlayPvs(component.ExchangeSound, uid).Value.Entity;
|
||||
|
||||
_doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, args.User, component.ExchangeDuration, new ExchangerDoAfterEvent(), uid, target: args.Target, used: uid)
|
||||
{
|
||||
BreakOnDamage = true,
|
||||
BreakOnMove = true
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -33,29 +33,32 @@ namespace Content.Server.Damage.Systems
|
||||
|
||||
private void OnDoHit(EntityUid uid, DamageOtherOnHitComponent component, ThrowDoHitEvent args)
|
||||
{
|
||||
//CrystallPunk Melee upgrade
|
||||
var damage = component.Damage;
|
||||
|
||||
if (TryComp<CP14SharpenedComponent>(uid, out var sharp))
|
||||
damage *= sharp.Sharpness;
|
||||
|
||||
var dmg = _damageable.TryChangeDamage(args.Target, damage, component.IgnoreResistances, origin: args.Component.Thrower);
|
||||
//CrystallPunk Melee pgrade end
|
||||
|
||||
// Log damage only for mobs. Useful for when people throw spears at each other, but also avoids log-spam when explosions send glass shards flying.
|
||||
if (dmg != null && HasComp<MobStateComponent>(args.Target))
|
||||
_adminLogger.Add(LogType.ThrowHit, $"{ToPrettyString(args.Target):target} received {dmg.GetTotal():damage} damage from collision");
|
||||
|
||||
if (dmg is { Empty: false })
|
||||
if (!TerminatingOrDeleted(args.Target))
|
||||
{
|
||||
_color.RaiseEffect(Color.Red, new List<EntityUid>() { args.Target }, Filter.Pvs(args.Target, entityManager: EntityManager));
|
||||
}
|
||||
//CrystallPunk Melee upgrade
|
||||
var damage = component.Damage;
|
||||
|
||||
_guns.PlayImpactSound(args.Target, dmg, null, false);
|
||||
if (TryComp<PhysicsComponent>(uid, out var body) && body.LinearVelocity.LengthSquared() > 0f)
|
||||
{
|
||||
var direction = body.LinearVelocity.Normalized();
|
||||
_sharedCameraRecoil.KickCamera(args.Target, direction);
|
||||
if (TryComp<CP14SharpenedComponent>(uid, out var sharp))
|
||||
damage *= sharp.Sharpness;
|
||||
|
||||
var dmg = _damageable.TryChangeDamage(args.Target, damage, component.IgnoreResistances, origin: args.Component.Thrower);
|
||||
//CrystallPunk Melee pgrade end
|
||||
|
||||
// Log damage only for mobs. Useful for when people throw spears at each other, but also avoids log-spam when explosions send glass shards flying.
|
||||
if (dmg != null && HasComp<MobStateComponent>(args.Target))
|
||||
_adminLogger.Add(LogType.ThrowHit, $"{ToPrettyString(args.Target):target} received {dmg.GetTotal():damage} damage from collision");
|
||||
|
||||
if (dmg is { Empty: false })
|
||||
{
|
||||
_color.RaiseEffect(Color.Red, new List<EntityUid>() { args.Target }, Filter.Pvs(args.Target, entityManager: EntityManager));
|
||||
}
|
||||
|
||||
_guns.PlayImpactSound(args.Target, dmg, null, false);
|
||||
if (TryComp<PhysicsComponent>(uid, out var body) && body.LinearVelocity.LengthSquared() > 0f)
|
||||
{
|
||||
var direction = body.LinearVelocity.Normalized();
|
||||
_sharedCameraRecoil.KickCamera(args.Target, direction);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: If more stuff touches this then handle it after.
|
||||
|
||||
@@ -15,6 +15,7 @@ using Content.Shared.Humanoid.Markings;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Preferences.Loadouts;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Traits;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Network;
|
||||
@@ -183,9 +184,9 @@ namespace Content.Server.Database
|
||||
|
||||
private static HumanoidCharacterProfile ConvertProfiles(Profile profile)
|
||||
{
|
||||
var jobs = profile.Jobs.ToDictionary(j => j.JobName, j => (JobPriority) j.Priority);
|
||||
var antags = profile.Antags.Select(a => a.AntagName);
|
||||
var traits = profile.Traits.Select(t => t.TraitName);
|
||||
var jobs = profile.Jobs.ToDictionary(j => new ProtoId<JobPrototype>(j.JobName), j => (JobPriority) j.Priority);
|
||||
var antags = profile.Antags.Select(a => new ProtoId<AntagPrototype>(a.AntagName));
|
||||
var traits = profile.Traits.Select(t => new ProtoId<TraitPrototype>(t.TraitName));
|
||||
|
||||
var sex = Sex.Male;
|
||||
if (Enum.TryParse<Sex>(profile.Sex, true, out var sexVal))
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Devour;
|
||||
@@ -15,6 +16,7 @@ public sealed class DevourSystem : SharedDevourSystem
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<DevourerComponent, DevourDoAfterEvent>(OnDoAfter);
|
||||
SubscribeLocalEvent<DevourerComponent, BeingGibbedEvent>(OnGibContents);
|
||||
}
|
||||
|
||||
private void OnDoAfter(EntityUid uid, DevourerComponent component, DevourDoAfterEvent args)
|
||||
@@ -45,5 +47,15 @@ public sealed class DevourSystem : SharedDevourSystem
|
||||
|
||||
_audioSystem.PlayPvs(component.SoundDevour, uid);
|
||||
}
|
||||
|
||||
private void OnGibContents(EntityUid uid, DevourerComponent component, ref BeingGibbedEvent args)
|
||||
{
|
||||
if (!component.ShouldStoreDevoured)
|
||||
return;
|
||||
|
||||
// For some reason we have two different systems that should handle gibbing,
|
||||
// and for some another reason GibbingSystem, which should empty all containers, doesn't get involved in this process
|
||||
ContainerSystem.EmptyContainer(component.Stomach);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -68,7 +68,6 @@ namespace Content.Server.Entry
|
||||
factory.RegisterIgnore(IgnoredComponents.List);
|
||||
|
||||
prototypes.RegisterIgnore("parallax");
|
||||
prototypes.RegisterIgnore("guideEntry");
|
||||
|
||||
ServerContentIoC.Register();
|
||||
|
||||
|
||||
@@ -171,7 +171,8 @@ public sealed partial class TriggerSystem
|
||||
if (args.Handled || HasComp<AutomatedTimerComponent>(uid) || component.UseVerbInstead)
|
||||
return;
|
||||
|
||||
_popupSystem.PopupEntity(Loc.GetString("trigger-activated", ("device", uid)), args.User, args.User);
|
||||
if (component.DoPopup)
|
||||
_popupSystem.PopupEntity(Loc.GetString("trigger-activated", ("device", uid)), args.User, args.User);
|
||||
|
||||
HandleTimerTrigger(
|
||||
uid,
|
||||
|
||||
@@ -13,6 +13,7 @@ using Content.Shared.Inventory;
|
||||
using Content.Shared.Weapons.Melee.Events;
|
||||
using Robust.Shared.Random;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Forensics
|
||||
{
|
||||
@@ -125,7 +126,7 @@ namespace Content.Server.Forensics
|
||||
var verb = new UtilityVerb()
|
||||
{
|
||||
Act = () => TryStartCleaning(entity, user, target),
|
||||
IconEntity = GetNetEntity(entity),
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/bubbles.svg.192dpi.png")),
|
||||
Text = Loc.GetString(Loc.GetString("forensics-verb-text")),
|
||||
Message = Loc.GetString(Loc.GetString("forensics-verb-message")),
|
||||
// This is important because if its true using the cleaning device will count as touching the object.
|
||||
|
||||
@@ -239,7 +239,7 @@ namespace Content.Server.GameTicking
|
||||
HumanoidCharacterProfile profile;
|
||||
if (_prefsManager.TryGetCachedPreferences(userId, out var preferences))
|
||||
{
|
||||
profile = (HumanoidCharacterProfile) preferences.GetProfile(preferences.SelectedCharacterIndex);
|
||||
profile = (HumanoidCharacterProfile) preferences.SelectedCharacter;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -46,6 +46,12 @@ namespace Content.Server.GameTicking
|
||||
jObject["players"] = _playerManager.PlayerCount;
|
||||
jObject["soft_max_players"] = _cfg.GetCVar(CCVars.SoftMaxPlayers);
|
||||
jObject["panic_bunker"] = _cfg.GetCVar(CCVars.PanicBunkerEnabled);
|
||||
|
||||
/*
|
||||
* TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
|
||||
*/
|
||||
|
||||
jObject["baby_jail"] = _cfg.GetCVar(CCVars.BabyJailEnabled);
|
||||
jObject["run_level"] = (int) _runLevel;
|
||||
if (preset != null)
|
||||
jObject["preset"] = Loc.GetString(preset.ModeTitle);
|
||||
|
||||
@@ -154,8 +154,8 @@ namespace Content.Server.Ghost
|
||||
|
||||
if (_ticker.RunLevel != GameRunLevel.PostRound)
|
||||
{
|
||||
_visibilitySystem.AddLayer(uid, visibility, (int) VisibilityFlags.Ghost, false);
|
||||
_visibilitySystem.RemoveLayer(uid, visibility, (int) VisibilityFlags.Normal, false);
|
||||
_visibilitySystem.AddLayer((uid, visibility), (int) VisibilityFlags.Ghost, false);
|
||||
_visibilitySystem.RemoveLayer((uid, visibility), (int) VisibilityFlags.Normal, false);
|
||||
_visibilitySystem.RefreshVisibility(uid, visibilityComponent: visibility);
|
||||
}
|
||||
|
||||
@@ -174,8 +174,8 @@ namespace Content.Server.Ghost
|
||||
// Entity can't be seen by ghosts anymore.
|
||||
if (TryComp(uid, out VisibilityComponent? visibility))
|
||||
{
|
||||
_visibilitySystem.RemoveLayer(uid, visibility, (int) VisibilityFlags.Ghost, false);
|
||||
_visibilitySystem.AddLayer(uid, visibility, (int) VisibilityFlags.Normal, false);
|
||||
_visibilitySystem.RemoveLayer((uid, visibility), (int) VisibilityFlags.Ghost, false);
|
||||
_visibilitySystem.AddLayer((uid, visibility), (int) VisibilityFlags.Normal, false);
|
||||
_visibilitySystem.RefreshVisibility(uid, visibilityComponent: visibility);
|
||||
}
|
||||
|
||||
@@ -382,13 +382,13 @@ namespace Content.Server.Ghost
|
||||
{
|
||||
if (visible)
|
||||
{
|
||||
_visibilitySystem.AddLayer(uid, vis, (int) VisibilityFlags.Normal, false);
|
||||
_visibilitySystem.RemoveLayer(uid, vis, (int) VisibilityFlags.Ghost, false);
|
||||
_visibilitySystem.AddLayer((uid, vis), (int) VisibilityFlags.Normal, false);
|
||||
_visibilitySystem.RemoveLayer((uid, vis), (int) VisibilityFlags.Ghost, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_visibilitySystem.AddLayer(uid, vis, (int) VisibilityFlags.Ghost, false);
|
||||
_visibilitySystem.RemoveLayer(uid, vis, (int) VisibilityFlags.Normal, false);
|
||||
_visibilitySystem.AddLayer((uid, vis), (int) VisibilityFlags.Ghost, false);
|
||||
_visibilitySystem.RemoveLayer((uid, vis), (int) VisibilityFlags.Normal, false);
|
||||
}
|
||||
_visibilitySystem.RefreshVisibility(uid, visibilityComponent: vis);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,10 @@ namespace Content.Server.Ghost.Roles.Components
|
||||
|
||||
[DataField("rules")] private string _roleRules = "ghost-role-component-default-rules";
|
||||
|
||||
// TODO ROLE TIMERS
|
||||
// Actually make use of / enforce this requirement?
|
||||
// Why is this even here.
|
||||
// Move to ghost role prototype & respect CCvars.GameRoleTimerOverride
|
||||
[DataField("requirements")]
|
||||
public HashSet<JobRequirement>? Requirements;
|
||||
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Info;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
|
||||
namespace Content.Server.Info;
|
||||
|
||||
public sealed class InfoSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IResourceManager _res = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeNetworkEvent<RequestRulesMessage>(OnRequestRules);
|
||||
}
|
||||
|
||||
private void OnRequestRules(RequestRulesMessage message, EntitySessionEventArgs eventArgs)
|
||||
{
|
||||
Log.Debug("Client requested rules.");
|
||||
var title = Loc.GetString(_cfg.GetCVar(CCVars.RulesHeader));
|
||||
var path = _cfg.GetCVar(CCVars.RulesFile);
|
||||
var rules = "Server could not read its rules.";
|
||||
try
|
||||
{
|
||||
rules = _res.ContentFileReadAllText($"/ServerInfo/{path}");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Log.Debug("Could not read server rules file.");
|
||||
}
|
||||
var response = new RulesMessage(title, rules);
|
||||
RaiseNetworkEvent(response, eventArgs.SenderSession.Channel);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Net;
|
||||
using System.Net;
|
||||
using Content.Server.Database;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Info;
|
||||
@@ -7,7 +7,7 @@ using Robust.Shared.Network;
|
||||
|
||||
namespace Content.Server.Info;
|
||||
|
||||
public sealed class RulesManager : SharedRulesManager
|
||||
public sealed class RulesManager
|
||||
{
|
||||
[Dependency] private readonly IServerDbManager _dbManager = default!;
|
||||
[Dependency] private readonly INetManager _netManager = default!;
|
||||
@@ -17,26 +17,22 @@ public sealed class RulesManager : SharedRulesManager
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_netManager.RegisterNetMessage<ShouldShowRulesPopupMessage>();
|
||||
_netManager.Connected += OnConnected;
|
||||
_netManager.RegisterNetMessage<ShowRulesPopupMessage>();
|
||||
_netManager.RegisterNetMessage<RulesAcceptedMessage>(OnRulesAccepted);
|
||||
_netManager.Connected += OnConnected;
|
||||
}
|
||||
|
||||
private async void OnConnected(object? sender, NetChannelArgs e)
|
||||
{
|
||||
if (IPAddress.IsLoopback(e.Channel.RemoteEndPoint.Address) && _cfg.GetCVar(CCVars.RulesExemptLocal))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var lastRead = await _dbManager.GetLastReadRules(e.Channel.UserId);
|
||||
if (lastRead > LastValidReadTime)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var message = new ShouldShowRulesPopupMessage();
|
||||
var message = new ShowRulesPopupMessage();
|
||||
message.PopupTime = _cfg.GetCVar(CCVars.RulesWaitTime);
|
||||
_netManager.ServerSendMessage(message, e.Channel);
|
||||
}
|
||||
|
||||
|
||||
@@ -47,20 +47,16 @@ public sealed class ShowRulesCommand : IConsoleCommand
|
||||
}
|
||||
}
|
||||
|
||||
var locator = IoCManager.Resolve<IPlayerLocator>();
|
||||
var located = await locator.LookupIdByNameOrIdAsync(target);
|
||||
if (located == null)
|
||||
|
||||
var message = new ShowRulesPopupMessage { PopupTime = seconds };
|
||||
|
||||
if (!IoCManager.Resolve<IPlayerManager>().TryGetSessionByUsername(target, out var player))
|
||||
{
|
||||
shell.WriteError("Unable to find a player with that name.");
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
var netManager = IoCManager.Resolve<INetManager>();
|
||||
|
||||
var message = new SharedRulesManager.ShowRulesPopupMessage();
|
||||
message.PopupTime = seconds;
|
||||
|
||||
var player = IoCManager.Resolve<IPlayerManager>().GetSessionById(located.UserId);
|
||||
netManager.ServerSendMessage(message, player.Channel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using Content.Server.Interaction;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Stunnable;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Instruments;
|
||||
using Content.Shared.Instruments.UI;
|
||||
using Content.Shared.Physics;
|
||||
@@ -30,6 +31,7 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly TransformSystem _transform = default!;
|
||||
[Dependency] private readonly InteractionSystem _interactions = default!;
|
||||
[Dependency] private readonly ExamineSystemShared _examineSystem = default!;
|
||||
|
||||
private const float MaxInstrumentBandRange = 10f;
|
||||
|
||||
@@ -250,9 +252,8 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem
|
||||
continue;
|
||||
|
||||
// Maybe a bit expensive but oh well GetBands is queued and has a timer anyway.
|
||||
// Make sure the instrument is visible, uses the Opaque collision group so this works across windows etc.
|
||||
if (!_interactions.InRangeUnobstructed(uid, entity, MaxInstrumentBandRange,
|
||||
CollisionGroup.Opaque, e => e == playerUid || e == originPlayer))
|
||||
// Make sure the instrument is visible
|
||||
if (!_examineSystem.InRangeUnOccluded(uid, entity, MaxInstrumentBandRange, e => e == playerUid || e == originPlayer))
|
||||
continue;
|
||||
|
||||
if (!metadataQuery.TryGetComponent(playerUid, out var playerMetadata)
|
||||
|
||||
@@ -35,6 +35,7 @@ using Robust.Shared.Player;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Content.Shared.Stacks;
|
||||
|
||||
namespace Content.Server.Kitchen.EntitySystems
|
||||
{
|
||||
@@ -58,6 +59,8 @@ namespace Content.Server.Kitchen.EntitySystems
|
||||
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
|
||||
[Dependency] private readonly HandsSystem _handsSystem = default!;
|
||||
[Dependency] private readonly SharedItemSystem _item = default!;
|
||||
[Dependency] private readonly SharedStackSystem _stack = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
|
||||
[ValidatePrototypeId<EntityPrototype>]
|
||||
private const string MalfunctionSpark = "Spark";
|
||||
@@ -199,16 +202,41 @@ namespace Content.Server.Kitchen.EntitySystems
|
||||
{
|
||||
foreach (var item in component.Storage.ContainedEntities)
|
||||
{
|
||||
var metaData = MetaData(item);
|
||||
if (metaData.EntityPrototype == null)
|
||||
string? itemID = null;
|
||||
|
||||
// If an entity has a stack component, use the stacktype instead of prototype id
|
||||
if (TryComp<StackComponent>(item, out var stackComp))
|
||||
{
|
||||
itemID = _prototype.Index<StackPrototype>(stackComp.StackTypeId).Spawn;
|
||||
}
|
||||
else
|
||||
{
|
||||
var metaData = MetaData(item);
|
||||
if (metaData.EntityPrototype == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
itemID = metaData.EntityPrototype.ID;
|
||||
}
|
||||
|
||||
if (itemID != recipeSolid.Key)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (metaData.EntityPrototype.ID == recipeSolid.Key)
|
||||
if (stackComp is not null)
|
||||
{
|
||||
if (stackComp.Count == 1)
|
||||
{
|
||||
_container.Remove(item, component.Storage);
|
||||
}
|
||||
_stack.Use(item, 1, stackComp);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
_container.Remove(item, component.Storage);
|
||||
EntityManager.DeleteEntity(item);
|
||||
Del(item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -448,17 +476,35 @@ namespace Content.Server.Kitchen.EntitySystems
|
||||
|
||||
AddComp<ActivelyMicrowavedComponent>(item);
|
||||
|
||||
var metaData = MetaData(item); //this simply begs for cooking refactor
|
||||
if (metaData.EntityPrototype == null)
|
||||
continue;
|
||||
string? solidID = null;
|
||||
int amountToAdd = 1;
|
||||
|
||||
if (solidsDict.ContainsKey(metaData.EntityPrototype.ID))
|
||||
// If a microwave recipe uses a stacked item, use the default stack prototype id instead of prototype id
|
||||
if (TryComp<StackComponent>(item, out var stackComp))
|
||||
{
|
||||
solidsDict[metaData.EntityPrototype.ID]++;
|
||||
solidID = _prototype.Index<StackPrototype>(stackComp.StackTypeId).Spawn;
|
||||
amountToAdd = stackComp.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
solidsDict.Add(metaData.EntityPrototype.ID, 1);
|
||||
var metaData = MetaData(item); //this simply begs for cooking refactor
|
||||
if (metaData.EntityPrototype is not null)
|
||||
solidID = metaData.EntityPrototype.ID;
|
||||
}
|
||||
|
||||
if (solidID is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (solidsDict.ContainsKey(solidID))
|
||||
{
|
||||
solidsDict[solidID] += amountToAdd;
|
||||
}
|
||||
else
|
||||
{
|
||||
solidsDict.Add(solidID, amountToAdd);
|
||||
}
|
||||
|
||||
if (!TryComp<SolutionContainerManagerComponent>(item, out var solMan))
|
||||
|
||||
6
Content.Server/MapText/MapTextComponent.cs
Normal file
6
Content.Server/MapText/MapTextComponent.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
using Content.Shared.MapText;
|
||||
|
||||
namespace Content.Server.MapText;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class MapTextComponent : SharedMapTextComponent;
|
||||
28
Content.Server/MapText/MapTextSystem.cs
Normal file
28
Content.Server/MapText/MapTextSystem.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using Content.Shared.MapText;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Server.MapText;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed class MapTextSystem : SharedMapTextSystem
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<MapTextComponent, ComponentGetState>(GetCompState);
|
||||
}
|
||||
|
||||
private void GetCompState(Entity<MapTextComponent> ent, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new MapTextComponentState
|
||||
{
|
||||
Text = ent.Comp.Text,
|
||||
LocText = ent.Comp.LocText,
|
||||
Color = ent.Comp.Color,
|
||||
FontId = ent.Comp.FontId,
|
||||
FontSize = ent.Comp.FontSize,
|
||||
Offset = ent.Comp.Offset
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -70,7 +70,6 @@ public sealed class HealingSystem : EntitySystem
|
||||
_bloodstreamSystem.TryModifyBleedAmount(entity.Owner, healing.BloodlossModifier);
|
||||
if (isBleeding != bloodstream.BleedAmount > 0)
|
||||
{
|
||||
dontRepeat = true;
|
||||
_popupSystem.PopupEntity(Loc.GetString("medical-item-stop-bleeding"), entity, args.User);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,11 +159,10 @@ public sealed partial class ParticleAcceleratorSystem
|
||||
var impact = strength switch
|
||||
{
|
||||
ParticleAcceleratorPowerState.Standby => LogImpact.Low,
|
||||
ParticleAcceleratorPowerState.Level0 => LogImpact.Medium,
|
||||
ParticleAcceleratorPowerState.Level1 => LogImpact.High,
|
||||
ParticleAcceleratorPowerState.Level2
|
||||
or ParticleAcceleratorPowerState.Level3
|
||||
or _ => LogImpact.Extreme,
|
||||
ParticleAcceleratorPowerState.Level0
|
||||
or ParticleAcceleratorPowerState.Level1
|
||||
or ParticleAcceleratorPowerState.Level2 => LogImpact.Medium,
|
||||
ParticleAcceleratorPowerState.Level3 => LogImpact.Extreme,
|
||||
};
|
||||
|
||||
_adminLogger.Add(LogType.Action, impact, $"{ToPrettyString(player):player} has set the strength of {ToPrettyString(uid)} to {strength}");
|
||||
|
||||
@@ -49,7 +49,7 @@ public sealed partial class ParticleAcceleratorSystem
|
||||
ParticleAcceleratorPowerState.Level0 => 1,
|
||||
ParticleAcceleratorPowerState.Level1 => 2,
|
||||
ParticleAcceleratorPowerState.Level2 => 3,
|
||||
ParticleAcceleratorPowerState.Level3 => 10,
|
||||
ParticleAcceleratorPowerState.Level3 => 6,
|
||||
_ => 0,
|
||||
} * 10;
|
||||
}
|
||||
|
||||
@@ -431,6 +431,10 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
|
||||
return beacon != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string describing the rough distance and direction
|
||||
/// to the position of <paramref name="ent"/> from the nearest beacon.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public string GetNearestBeaconString(Entity<TransformComponent?> ent)
|
||||
{
|
||||
@@ -440,6 +444,11 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
|
||||
return GetNearestBeaconString(_transformSystem.GetMapCoordinates(ent, ent.Comp));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string describing the rough distance and direction
|
||||
/// to <paramref name="coordinates"/> from the nearest beacon.
|
||||
/// </summary>
|
||||
|
||||
public string GetNearestBeaconString(MapCoordinates coordinates)
|
||||
{
|
||||
if (!TryGetNearestBeacon(coordinates, out var beacon, out var pos))
|
||||
@@ -451,10 +460,11 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
|
||||
|
||||
// get the angle between the two positions, adjusted for the grid rotation so that
|
||||
// we properly preserve north in relation to the grid.
|
||||
var dir = (pos.Value.Position - coordinates.Position).ToWorldAngle();
|
||||
var offset = coordinates.Position - pos.Value.Position;
|
||||
var dir = offset.ToWorldAngle();
|
||||
var adjustedDir = (dir - gridOffset).GetDir();
|
||||
|
||||
var length = (pos.Value.Position - coordinates.Position).Length();
|
||||
var length = offset.Length();
|
||||
if (length < CloseDistance)
|
||||
{
|
||||
return Loc.GetString("nav-beacon-pos-format",
|
||||
|
||||
@@ -81,6 +81,8 @@ public sealed class PlayTimeTrackingManager : ISharedPlaytimeManager, IPostInjec
|
||||
|
||||
public event CalcPlayTimeTrackersCallback? CalcTrackers;
|
||||
|
||||
public event Action<ICommonSession>? SessionPlayTimeUpdated;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_sawmill = Logger.GetSawmill("play_time");
|
||||
@@ -217,6 +219,7 @@ public sealed class PlayTimeTrackingManager : ISharedPlaytimeManager, IPostInjec
|
||||
};
|
||||
|
||||
_net.ServerSendMessage(msg, pSession.Channel);
|
||||
SessionPlayTimeUpdated?.Invoke(pSession);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -370,6 +373,19 @@ public sealed class PlayTimeTrackingManager : ISharedPlaytimeManager, IPostInjec
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryGetTrackerTime(ICommonSession id, string tracker, [NotNullWhen(true)] out TimeSpan? time)
|
||||
{
|
||||
time = null;
|
||||
if (!TryGetTrackerTimes(id, out var times))
|
||||
return false;
|
||||
|
||||
if (!times.TryGetValue(tracker, out var t))
|
||||
return false;
|
||||
|
||||
time = t;
|
||||
return true;
|
||||
}
|
||||
|
||||
public Dictionary<string, TimeSpan> GetTrackerTimes(ICommonSession id)
|
||||
{
|
||||
if (!_playTimeData.TryGetValue(id, out var data) || !data.Initialized)
|
||||
|
||||
@@ -35,6 +35,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
|
||||
[Dependency] private readonly MindSystem _minds = default!;
|
||||
[Dependency] private readonly PlayTimeTrackingManager _tracking = default!;
|
||||
[Dependency] private readonly IAdminManager _adminManager = default!;
|
||||
[Dependency] private readonly SharedRoleSystem _role = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -197,7 +198,6 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
|
||||
public bool IsAllowed(ICommonSession player, string role)
|
||||
{
|
||||
if (!_prototypes.TryIndex<JobPrototype>(role, out var job) ||
|
||||
job.Requirements == null ||
|
||||
!_cfg.GetCVar(CCVars.GameRoleTimers))
|
||||
return true;
|
||||
|
||||
@@ -224,19 +224,8 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
|
||||
|
||||
foreach (var job in _prototypes.EnumeratePrototypes<JobPrototype>())
|
||||
{
|
||||
if (job.Requirements != null)
|
||||
{
|
||||
foreach (var requirement in job.Requirements)
|
||||
{
|
||||
if (JobRequirements.TryRequirementMet(requirement, playTimes, out _, EntityManager, _prototypes))
|
||||
continue;
|
||||
|
||||
goto NoRole;
|
||||
}
|
||||
}
|
||||
|
||||
roles.Add(job.ID);
|
||||
NoRole:;
|
||||
if (JobRequirements.TryRequirementsMet(job, playTimes, out _, EntityManager, _prototypes))
|
||||
roles.Add(job.ID);
|
||||
}
|
||||
|
||||
return roles;
|
||||
@@ -257,22 +246,14 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
|
||||
|
||||
for (var i = 0; i < jobs.Count; i++)
|
||||
{
|
||||
var job = jobs[i];
|
||||
|
||||
if (!_prototypes.TryIndex(job, out var jobber) ||
|
||||
jobber.Requirements == null ||
|
||||
jobber.Requirements.Count == 0)
|
||||
continue;
|
||||
|
||||
foreach (var requirement in jobber.Requirements)
|
||||
if (_prototypes.TryIndex(jobs[i], out var job)
|
||||
&& JobRequirements.TryRequirementsMet(job, playTimes, out _, EntityManager, _prototypes))
|
||||
{
|
||||
if (JobRequirements.TryRequirementMet(requirement, playTimes, out _, EntityManager, _prototypes))
|
||||
continue;
|
||||
|
||||
jobs.RemoveSwap(i);
|
||||
i--;
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
|
||||
jobs.RemoveSwap(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -174,7 +174,7 @@ namespace Content.Server.Pointing.EntitySystems
|
||||
{
|
||||
var arrowVisibility = EntityManager.EnsureComponent<VisibilityComponent>(arrow);
|
||||
layer = playerVisibility.Layer;
|
||||
_visibilitySystem.SetLayer(arrow, arrowVisibility, layer);
|
||||
_visibilitySystem.SetLayer((arrow, arrowVisibility), (ushort) layer);
|
||||
}
|
||||
|
||||
// Get players that are in range and whose visibility layer matches the arrow's.
|
||||
|
||||
@@ -56,7 +56,9 @@ public sealed class PrayerSystem : EntitySystem
|
||||
|
||||
_quickDialog.OpenDialog(actor.PlayerSession, Loc.GetString(comp.Verb), Loc.GetString("prayer-popup-notify-pray-ui-message"), (string message) =>
|
||||
{
|
||||
Pray(actor.PlayerSession, comp, message);
|
||||
// Make sure the player's entity and the Prayable entity+component still exist
|
||||
if (actor?.PlayerSession != null && HasComp<PrayableComponent>(uid))
|
||||
Pray(actor.PlayerSession, comp, message);
|
||||
});
|
||||
},
|
||||
Impact = LogImpact.Low,
|
||||
|
||||
@@ -305,11 +305,7 @@ namespace Content.Server.Preferences.Managers
|
||||
return usernames
|
||||
.Select(p => (_cachedPlayerPrefs[p].Prefs, p))
|
||||
.Where(p => p.Prefs != null)
|
||||
.Select(p =>
|
||||
{
|
||||
var idx = p.Prefs!.SelectedCharacterIndex;
|
||||
return new KeyValuePair<NetUserId, ICharacterProfile>(p.p, p.Prefs!.GetProfile(idx));
|
||||
});
|
||||
.Select(p => new KeyValuePair<NetUserId, ICharacterProfile>(p.p, p.Prefs!.SelectedCharacter));
|
||||
}
|
||||
|
||||
internal static bool ShouldStorePrefs(LoginType loginType)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared.Dataset;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
@@ -47,13 +48,19 @@ public sealed class RandomMetadataSystem : EntitySystem
|
||||
var outputSegments = new List<string>();
|
||||
foreach (var segment in segments)
|
||||
{
|
||||
if (_prototype.TryIndex<DatasetPrototype>(segment, out var proto)) {
|
||||
if (_prototype.TryIndex<LocalizedDatasetPrototype>(segment, out var localizedProto))
|
||||
{
|
||||
outputSegments.Add(_random.Pick(localizedProto));
|
||||
}
|
||||
else if (_prototype.TryIndex<DatasetPrototype>(segment, out var proto))
|
||||
{
|
||||
var random = _random.Pick(proto.Values);
|
||||
if (Loc.TryGetString(random, out var localizedSegment))
|
||||
outputSegments.Add(localizedSegment);
|
||||
else
|
||||
outputSegments.Add(random);
|
||||
} else if (Loc.TryGetString(segment, out var localizedSegment))
|
||||
}
|
||||
else if (Loc.TryGetString(segment, out var localizedSegment))
|
||||
outputSegments.Add(localizedSegment);
|
||||
else
|
||||
outputSegments.Add(segment);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Shared.Eye;
|
||||
using Content.Shared.Revenant.Components;
|
||||
using Content.Shared.Revenant.EntitySystems;
|
||||
@@ -17,8 +17,8 @@ public sealed class CorporealSystem : SharedCorporealSystem
|
||||
|
||||
if (TryComp<VisibilityComponent>(uid, out var visibility))
|
||||
{
|
||||
_visibilitySystem.RemoveLayer(uid, visibility, (int) VisibilityFlags.Ghost, false);
|
||||
_visibilitySystem.AddLayer(uid, visibility, (int) VisibilityFlags.Normal, false);
|
||||
_visibilitySystem.RemoveLayer((uid, visibility), (int) VisibilityFlags.Ghost, false);
|
||||
_visibilitySystem.AddLayer((uid, visibility), (int) VisibilityFlags.Normal, false);
|
||||
_visibilitySystem.RefreshVisibility(uid, visibility);
|
||||
}
|
||||
}
|
||||
@@ -29,8 +29,8 @@ public sealed class CorporealSystem : SharedCorporealSystem
|
||||
|
||||
if (TryComp<VisibilityComponent>(uid, out var visibility) && _ticker.RunLevel != GameRunLevel.PostRound)
|
||||
{
|
||||
_visibilitySystem.AddLayer(uid, visibility, (int) VisibilityFlags.Ghost, false);
|
||||
_visibilitySystem.RemoveLayer(uid, visibility, (int) VisibilityFlags.Normal, false);
|
||||
_visibilitySystem.AddLayer((uid, visibility), (int) VisibilityFlags.Ghost, false);
|
||||
_visibilitySystem.RemoveLayer((uid, visibility), (int) VisibilityFlags.Normal, false);
|
||||
_visibilitySystem.RefreshVisibility(uid, visibility);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,8 +78,8 @@ public sealed partial class RevenantSystem : EntitySystem
|
||||
|
||||
if (_ticker.RunLevel == GameRunLevel.PostRound && TryComp<VisibilityComponent>(uid, out var visibility))
|
||||
{
|
||||
_visibility.AddLayer(uid, visibility, (int) VisibilityFlags.Ghost, false);
|
||||
_visibility.RemoveLayer(uid, visibility, (int) VisibilityFlags.Normal, false);
|
||||
_visibility.AddLayer((uid, visibility), (int) VisibilityFlags.Ghost, false);
|
||||
_visibility.RemoveLayer((uid, visibility), (int) VisibilityFlags.Normal, false);
|
||||
_visibility.RefreshVisibility(uid, visibility);
|
||||
}
|
||||
|
||||
@@ -192,13 +192,13 @@ public sealed partial class RevenantSystem : EntitySystem
|
||||
{
|
||||
if (visible)
|
||||
{
|
||||
_visibility.AddLayer(uid, vis, (int) VisibilityFlags.Normal, false);
|
||||
_visibility.RemoveLayer(uid, vis, (int) VisibilityFlags.Ghost, false);
|
||||
_visibility.AddLayer((uid, vis), (int) VisibilityFlags.Normal, false);
|
||||
_visibility.RemoveLayer((uid, vis), (int) VisibilityFlags.Ghost, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_visibility.AddLayer(uid, vis, (int) VisibilityFlags.Ghost, false);
|
||||
_visibility.RemoveLayer(uid, vis, (int) VisibilityFlags.Normal, false);
|
||||
_visibility.AddLayer((uid, vis), (int) VisibilityFlags.Ghost, false);
|
||||
_visibility.RemoveLayer((uid, vis), (int) VisibilityFlags.Normal, false);
|
||||
}
|
||||
_visibility.RefreshVisibility(uid, vis);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,6 @@ public sealed class RoleBriefingSystem : EntitySystem
|
||||
|
||||
private void OnGetBriefing(EntityUid uid, RoleBriefingComponent comp, ref GetBriefingEvent args)
|
||||
{
|
||||
args.Append(comp.Briefing);
|
||||
args.Append(Loc.GetString(comp.Briefing));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,10 +116,12 @@ public sealed class EventHorizonSystem : SharedEventHorizonSystem
|
||||
/// </summary>
|
||||
public void ConsumeEntity(EntityUid hungry, EntityUid morsel, EventHorizonComponent eventHorizon, BaseContainer? outerContainer = null)
|
||||
{
|
||||
if (!EntityManager.IsQueuedForDeletion(morsel) // I saw it log twice a few times for some reason?
|
||||
&& (HasComp<MindContainerComponent>(morsel)
|
||||
if (EntityManager.IsQueuedForDeletion(morsel)) // already handled, and we're substepping
|
||||
return;
|
||||
|
||||
if (HasComp<MindContainerComponent>(morsel)
|
||||
|| _tagSystem.HasTag(morsel, "HighRiskItem")
|
||||
|| HasComp<ContainmentFieldGeneratorComponent>(morsel)))
|
||||
|| HasComp<ContainmentFieldGeneratorComponent>(morsel))
|
||||
{
|
||||
_adminLogger.Add(LogType.EntityDelete, LogImpact.Extreme, $"{ToPrettyString(morsel)} entered the event horizon of {ToPrettyString(hungry)} and was deleted");
|
||||
}
|
||||
|
||||
@@ -55,14 +55,12 @@ public sealed class SingularitySystem : SharedSingularitySystem
|
||||
|
||||
var vvHandle = Vvm.GetTypeHandler<SingularityComponent>();
|
||||
vvHandle.AddPath(nameof(SingularityComponent.Energy), (_, comp) => comp.Energy, SetEnergy);
|
||||
vvHandle.AddPath(nameof(SingularityComponent.TargetUpdatePeriod), (_, comp) => comp.TargetUpdatePeriod, SetUpdatePeriod);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
var vvHandle = Vvm.GetTypeHandler<SingularityComponent>();
|
||||
vvHandle.RemovePath(nameof(SingularityComponent.Energy));
|
||||
vvHandle.RemovePath(nameof(SingularityComponent.TargetUpdatePeriod));
|
||||
base.Shutdown();
|
||||
}
|
||||
|
||||
@@ -78,39 +76,10 @@ public sealed class SingularitySystem : SharedSingularitySystem
|
||||
var query = EntityQueryEnumerator<SingularityComponent>();
|
||||
while (query.MoveNext(out var uid, out var singularity))
|
||||
{
|
||||
var curTime = _timing.CurTime;
|
||||
if (singularity.NextUpdateTime <= curTime)
|
||||
Update(uid, curTime - singularity.LastUpdateTime, singularity);
|
||||
AdjustEnergy(uid, -singularity.EnergyDrain * frameTime, singularity: singularity);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the gradual energy loss and dissipation of singularity.
|
||||
/// </summary>
|
||||
/// <param name="uid">The uid of the singularity to update.</param>
|
||||
/// <param name="singularity">The state of the singularity to update.</param>
|
||||
public void Update(EntityUid uid, SingularityComponent? singularity = null)
|
||||
{
|
||||
if (Resolve(uid, ref singularity))
|
||||
Update(uid, _timing.CurTime - singularity.LastUpdateTime, singularity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the gradual energy loss and dissipation of a singularity.
|
||||
/// </summary>
|
||||
/// <param name="uid">The uid of the singularity to update.</param>
|
||||
/// <param name="frameTime">The amount of time that has elapsed since the last update.</param>
|
||||
/// <param name="singularity">The state of the singularity to update.</param>
|
||||
public void Update(EntityUid uid, TimeSpan frameTime, SingularityComponent? singularity = null)
|
||||
{
|
||||
if(!Resolve(uid, ref singularity))
|
||||
return;
|
||||
|
||||
singularity.LastUpdateTime = _timing.CurTime;
|
||||
singularity.NextUpdateTime = singularity.LastUpdateTime + singularity.TargetUpdatePeriod;
|
||||
AdjustEnergy(uid, -singularity.EnergyDrain * (float)frameTime.TotalSeconds, singularity: singularity);
|
||||
}
|
||||
|
||||
#region Getters/Setters
|
||||
|
||||
/// <summary>
|
||||
@@ -132,10 +101,12 @@ public sealed class SingularitySystem : SharedSingularitySystem
|
||||
singularity.Energy = value;
|
||||
SetLevel(uid, value switch
|
||||
{
|
||||
>= 2400 => 6,
|
||||
>= 1600 => 5,
|
||||
>= 900 => 4,
|
||||
>= 300 => 3,
|
||||
// Normally, a level 6 singularity requires the supermatter + 3000 energy.
|
||||
// The required amount of energy has been bumped up to compensate for the lack of the supermatter.
|
||||
>= 5000 => 6,
|
||||
>= 2000 => 5,
|
||||
>= 1000 => 4,
|
||||
>= 500 => 3,
|
||||
>= 200 => 2,
|
||||
> 0 => 1,
|
||||
_ => 0
|
||||
@@ -164,28 +135,6 @@ public sealed class SingularitySystem : SharedSingularitySystem
|
||||
SetEnergy(uid, MathHelper.Clamp(newValue, min, max), singularity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setter for <see cref="SingularityComponent.TargetUpdatePeriod"/>.
|
||||
/// If the new target time implies that the singularity should have updated it does so immediately.
|
||||
/// </summary>
|
||||
/// <param name="uid">The uid of the singularity to set the update period for.</param>
|
||||
/// <param name="value">The new update period for the singularity.</param>
|
||||
/// <param name="singularity">The state of the singularity to set the update period for.</param>
|
||||
public void SetUpdatePeriod(EntityUid uid, TimeSpan value, SingularityComponent? singularity = null)
|
||||
{
|
||||
if(!Resolve(uid, ref singularity))
|
||||
return;
|
||||
|
||||
if (MathHelper.CloseTo(singularity.TargetUpdatePeriod.TotalSeconds, value.TotalSeconds))
|
||||
return;
|
||||
|
||||
singularity.TargetUpdatePeriod = value;
|
||||
singularity.NextUpdateTime = singularity.LastUpdateTime + singularity.TargetUpdatePeriod;
|
||||
|
||||
var curTime = _timing.CurTime;
|
||||
if (singularity.NextUpdateTime <= curTime)
|
||||
Update(uid, curTime - singularity.LastUpdateTime, singularity);
|
||||
}
|
||||
|
||||
#endregion Getters/Setters
|
||||
|
||||
@@ -201,9 +150,6 @@ public sealed class SingularitySystem : SharedSingularitySystem
|
||||
/// <param name="args">The event arguments.</param>
|
||||
protected override void OnSingularityStartup(EntityUid uid, SingularityComponent comp, ComponentStartup args)
|
||||
{
|
||||
comp.LastUpdateTime = _timing.CurTime;
|
||||
comp.NextUpdateTime = comp.LastUpdateTime + comp.TargetUpdatePeriod;
|
||||
|
||||
MetaDataComponent? metaData = null;
|
||||
if (Resolve(uid, ref metaData) && metaData.EntityLifeStage <= EntityLifeStage.Initializing)
|
||||
_audio.PlayPvs(comp.FormationSound, uid);
|
||||
@@ -221,7 +167,7 @@ public sealed class SingularitySystem : SharedSingularitySystem
|
||||
/// <param name="args">The event arguments.</param>
|
||||
public void OnDistortionStartup(EntityUid uid, SingularityDistortionComponent comp, ComponentStartup args)
|
||||
{
|
||||
_pvs.AddGlobalOverride(GetNetEntity(uid));
|
||||
_pvs.AddGlobalOverride(uid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -267,6 +213,10 @@ public sealed class SingularitySystem : SharedSingularitySystem
|
||||
/// <param name="args">The event arguments.</param>
|
||||
public void OnConsumedEntity(EntityUid uid, SingularityComponent comp, ref EntityConsumedByEventHorizonEvent args)
|
||||
{
|
||||
// Don't double count singulo food
|
||||
if (HasComp<SinguloFoodComponent>(args.Entity))
|
||||
return;
|
||||
|
||||
AdjustEnergy(uid, BaseEntityEnergy, singularity: comp);
|
||||
}
|
||||
|
||||
@@ -319,11 +269,11 @@ public sealed class SingularitySystem : SharedSingularitySystem
|
||||
{
|
||||
comp.EnergyDrain = args.NewValue switch
|
||||
{
|
||||
6 => 20,
|
||||
5 => 15,
|
||||
4 => 12,
|
||||
3 => 8,
|
||||
2 => 2,
|
||||
6 => 0,
|
||||
5 => 0,
|
||||
4 => 20,
|
||||
3 => 10,
|
||||
2 => 5,
|
||||
1 => 1,
|
||||
_ => 0
|
||||
};
|
||||
|
||||
@@ -6,11 +6,8 @@ namespace Content.Server.Spawners.Components;
|
||||
[RegisterComponent]
|
||||
public sealed partial class SpawnPointComponent : Component, ISpawnPoint
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("job_id")]
|
||||
private string? _jobId;
|
||||
public ProtoId<JobPrototype>? Job;
|
||||
|
||||
/// <summary>
|
||||
/// The type of spawn point
|
||||
@@ -18,11 +15,9 @@ public sealed partial class SpawnPointComponent : Component, ISpawnPoint
|
||||
[DataField("spawn_type"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public SpawnPointType SpawnType { get; set; } = SpawnPointType.Unset;
|
||||
|
||||
public JobPrototype? Job => string.IsNullOrEmpty(_jobId) ? null : _prototypeManager.Index<JobPrototype>(_jobId);
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{_jobId} {SpawnType}";
|
||||
return $"{Job} {SpawnType}";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ public sealed class SpawnPointSystem : EntitySystem
|
||||
|
||||
if (_gameTicker.RunLevel != GameRunLevel.InRound &&
|
||||
spawnPoint.SpawnType == SpawnPointType.Job &&
|
||||
(args.Job == null || spawnPoint.Job?.ID == args.Job.Prototype))
|
||||
(args.Job == null || spawnPoint.Job == args.Job.Prototype))
|
||||
{
|
||||
possiblePositions.Add(xform.Coordinates);
|
||||
}
|
||||
|
||||
@@ -71,6 +71,15 @@ namespace Content.Server.Stack
|
||||
return entity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawns a stack of a certain stack type. See <see cref="StackPrototype"/>.
|
||||
/// </summary>
|
||||
public EntityUid Spawn(int amount, ProtoId<StackPrototype> id, EntityCoordinates spawnPosition)
|
||||
{
|
||||
var proto = _prototypeManager.Index(id);
|
||||
return Spawn(amount, proto, spawnPosition);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawns a stack of a certain stack type. See <see cref="StackPrototype"/>.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Server.Station.Systems;
|
||||
using System.Linq;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Roles;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Network;
|
||||
@@ -14,25 +15,21 @@ namespace Content.Server.Station.Components;
|
||||
[RegisterComponent, Access(typeof(StationJobsSystem)), PublicAPI]
|
||||
public sealed partial class StationJobsComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Total *round-start* jobs at station start.
|
||||
/// </summary>
|
||||
[DataField("roundStartTotalJobs")] public int RoundStartTotalJobs;
|
||||
|
||||
/// <summary>
|
||||
/// Total *mid-round* jobs at station start.
|
||||
/// This is inferred automatically from <see cref="SetupAvailableJobs"/>.
|
||||
/// </summary>
|
||||
[DataField("midRoundTotalJobs")] public int MidRoundTotalJobs;
|
||||
[ViewVariables] public int MidRoundTotalJobs;
|
||||
|
||||
/// <summary>
|
||||
/// Current total jobs.
|
||||
/// </summary>
|
||||
[DataField("totalJobs")] public int TotalJobs;
|
||||
[DataField] public int TotalJobs;
|
||||
|
||||
/// <summary>
|
||||
/// Station is running on extended access.
|
||||
/// </summary>
|
||||
[DataField("extendedAccess")] public bool ExtendedAccess;
|
||||
[DataField] public bool ExtendedAccess;
|
||||
|
||||
/// <summary>
|
||||
/// If there are less than or equal this amount of players in the game at round start,
|
||||
@@ -41,7 +38,7 @@ public sealed partial class StationJobsComponent : Component
|
||||
/// <remarks>
|
||||
/// Set to -1 to disable extended access.
|
||||
/// </remarks>
|
||||
[DataField("extendedAccessThreshold")]
|
||||
[DataField]
|
||||
public int ExtendedAccessThreshold { get; set; } = 15;
|
||||
|
||||
/// <summary>
|
||||
@@ -54,28 +51,20 @@ public sealed partial class StationJobsComponent : Component
|
||||
public float? PercentJobsRemaining => MidRoundTotalJobs > 0 ? TotalJobs / (float) MidRoundTotalJobs : null;
|
||||
|
||||
/// <summary>
|
||||
/// The current list of jobs.
|
||||
/// The current list of jobs of available jobs. Null implies that is no limit.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This should not be mutated or used directly unless you really know what you're doing, go through StationJobsSystem.
|
||||
/// </remarks>
|
||||
[DataField("jobList", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<uint?, JobPrototype>))]
|
||||
public Dictionary<string, uint?> JobList = new();
|
||||
|
||||
/// <summary>
|
||||
/// The round-start list of jobs.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This should not be mutated, ever.
|
||||
/// </remarks>
|
||||
[DataField("roundStartJobList", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<uint?, JobPrototype>))]
|
||||
public Dictionary<string, uint?> RoundStartJobList = new();
|
||||
[DataField]
|
||||
public Dictionary<ProtoId<JobPrototype>, int?> JobList = new();
|
||||
|
||||
/// <summary>
|
||||
/// Overflow jobs that round-start can spawn infinitely many of.
|
||||
/// This is inferred automatically from <see cref="SetupAvailableJobs"/>.
|
||||
/// </summary>
|
||||
[DataField("overflowJobs", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<JobPrototype>))]
|
||||
public HashSet<string> OverflowJobs = new();
|
||||
[ViewVariables]
|
||||
public IReadOnlySet<ProtoId<JobPrototype>> OverflowJobs = default!;
|
||||
|
||||
/// <summary>
|
||||
/// A dictionary relating a NetUserId to the jobs they have on station.
|
||||
@@ -84,7 +73,10 @@ public sealed partial class StationJobsComponent : Component
|
||||
[DataField]
|
||||
public Dictionary<NetUserId, List<ProtoId<JobPrototype>>> PlayerJobs = new();
|
||||
|
||||
[DataField("availableJobs", required: true,
|
||||
customTypeSerializer: typeof(PrototypeIdDictionarySerializer<List<int?>, JobPrototype>))]
|
||||
public Dictionary<string, List<int?>> SetupAvailableJobs = default!;
|
||||
/// <summary>
|
||||
/// Mapping of jobs to an int[2] array that specifies jobs available at round start, and midround.
|
||||
/// Negative values implies that there is no limit.
|
||||
/// </summary>
|
||||
[DataField("availableJobs", required: true)]
|
||||
public Dictionary<ProtoId<JobPrototype>, int[]> SetupAvailableJobs = default!;
|
||||
}
|
||||
|
||||
@@ -52,23 +52,23 @@ public sealed partial class StationJobsSystem
|
||||
/// as there may end up being more round-start slots than available slots, which can cause weird behavior.
|
||||
/// A warning to all who enter ye cursed lands: This function is long and mildly incomprehensible. Best used without touching.
|
||||
/// </remarks>
|
||||
public Dictionary<NetUserId, (string?, EntityUid)> AssignJobs(Dictionary<NetUserId, HumanoidCharacterProfile> profiles, IReadOnlyList<EntityUid> stations, bool useRoundStartJobs = true)
|
||||
public Dictionary<NetUserId, (ProtoId<JobPrototype>?, EntityUid)> AssignJobs(Dictionary<NetUserId, HumanoidCharacterProfile> profiles, IReadOnlyList<EntityUid> stations, bool useRoundStartJobs = true)
|
||||
{
|
||||
DebugTools.Assert(stations.Count > 0);
|
||||
|
||||
InitializeRoundStart();
|
||||
|
||||
if (profiles.Count == 0)
|
||||
return new Dictionary<NetUserId, (string?, EntityUid)>();
|
||||
return new();
|
||||
|
||||
// We need to modify this collection later, so make a copy of it.
|
||||
profiles = profiles.ShallowClone();
|
||||
|
||||
// Player <-> (job, station)
|
||||
var assigned = new Dictionary<NetUserId, (string?, EntityUid)>(profiles.Count);
|
||||
var assigned = new Dictionary<NetUserId, (ProtoId<JobPrototype>?, EntityUid)>(profiles.Count);
|
||||
|
||||
// The jobs left on the stations. This collection is modified as jobs are assigned to track what's available.
|
||||
var stationJobs = new Dictionary<EntityUid, Dictionary<string, uint?>>();
|
||||
var stationJobs = new Dictionary<EntityUid, Dictionary<ProtoId<JobPrototype>, int?>>();
|
||||
foreach (var station in stations)
|
||||
{
|
||||
if (useRoundStartJobs)
|
||||
@@ -83,15 +83,15 @@ public sealed partial class StationJobsSystem
|
||||
|
||||
|
||||
// We reuse this collection. It tracks what jobs we're currently trying to select players for.
|
||||
var currentlySelectingJobs = new Dictionary<EntityUid, Dictionary<string, uint?>>(stations.Count);
|
||||
var currentlySelectingJobs = new Dictionary<EntityUid, Dictionary<ProtoId<JobPrototype>, int?>>(stations.Count);
|
||||
foreach (var station in stations)
|
||||
{
|
||||
currentlySelectingJobs.Add(station, new Dictionary<string, uint?>());
|
||||
currentlySelectingJobs.Add(station, new Dictionary<ProtoId<JobPrototype>, int?>());
|
||||
}
|
||||
|
||||
// And these.
|
||||
// Tracks what players are available for a given job in the current iteration of selection.
|
||||
var jobPlayerOptions = new Dictionary<string, HashSet<NetUserId>>();
|
||||
var jobPlayerOptions = new Dictionary<ProtoId<JobPrototype>, HashSet<NetUserId>>();
|
||||
// Tracks the total number of slots for the given stations in the current iteration of selection.
|
||||
var stationTotalSlots = new Dictionary<EntityUid, int>(stations.Count);
|
||||
// The share of the players each station gets in the current iteration of job selection.
|
||||
@@ -112,7 +112,7 @@ public sealed partial class StationJobsSystem
|
||||
var optionsRemaining = 0;
|
||||
|
||||
// Assigns a player to the given station, updating all the bookkeeping while at it.
|
||||
void AssignPlayer(NetUserId player, string job, EntityUid station)
|
||||
void AssignPlayer(NetUserId player, ProtoId<JobPrototype> job, EntityUid station)
|
||||
{
|
||||
// Remove the player from all possible jobs as that's faster than actually checking what they have selected.
|
||||
foreach (var (k, players) in jobPlayerOptions)
|
||||
@@ -273,8 +273,11 @@ public sealed partial class StationJobsSystem
|
||||
/// <param name="allPlayersToAssign">All players that might need an overflow assigned.</param>
|
||||
/// <param name="profiles">Player character profiles.</param>
|
||||
/// <param name="stations">The stations to consider for spawn location.</param>
|
||||
public void AssignOverflowJobs(ref Dictionary<NetUserId, (string?, EntityUid)> assignedJobs,
|
||||
IEnumerable<NetUserId> allPlayersToAssign, IReadOnlyDictionary<NetUserId, HumanoidCharacterProfile> profiles, IReadOnlyList<EntityUid> stations)
|
||||
public void AssignOverflowJobs(
|
||||
ref Dictionary<NetUserId, (ProtoId<JobPrototype>?, EntityUid)> assignedJobs,
|
||||
IEnumerable<NetUserId> allPlayersToAssign,
|
||||
IReadOnlyDictionary<NetUserId, HumanoidCharacterProfile> profiles,
|
||||
IReadOnlyList<EntityUid> stations)
|
||||
{
|
||||
var givenStations = stations.ToList();
|
||||
if (givenStations.Count == 0)
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Linq;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Roles;
|
||||
@@ -31,12 +32,25 @@ public sealed partial class StationJobsSystem : EntitySystem
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<StationInitializedEvent>(OnStationInitialized);
|
||||
SubscribeLocalEvent<StationJobsComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<StationJobsComponent, StationRenamedEvent>(OnStationRenamed);
|
||||
SubscribeLocalEvent<StationJobsComponent, ComponentShutdown>(OnStationDeletion);
|
||||
SubscribeLocalEvent<PlayerJoinedLobbyEvent>(OnPlayerJoinedLobby);
|
||||
Subs.CVar(_configurationManager, CCVars.GameDisallowLateJoins, _ => UpdateJobsAvailable(), true);
|
||||
}
|
||||
|
||||
private void OnInit(Entity<StationJobsComponent> ent, ref ComponentInit args)
|
||||
{
|
||||
ent.Comp.MidRoundTotalJobs = ent.Comp.SetupAvailableJobs.Values
|
||||
.Select(x => Math.Max(x[1], 0))
|
||||
.Sum();
|
||||
|
||||
ent.Comp.OverflowJobs = ent.Comp.SetupAvailableJobs
|
||||
.Where(x => x.Value[0] < 0)
|
||||
.Select(x => x.Key)
|
||||
.ToHashSet();
|
||||
}
|
||||
|
||||
public override void Update(float _)
|
||||
{
|
||||
if (_availableJobsDirty)
|
||||
@@ -57,28 +71,11 @@ public sealed partial class StationJobsSystem : EntitySystem
|
||||
if (!TryComp<StationJobsComponent>(msg.Station, out var stationJobs))
|
||||
return;
|
||||
|
||||
var mapJobList = stationJobs.SetupAvailableJobs;
|
||||
stationJobs.JobList = stationJobs.SetupAvailableJobs.ToDictionary(
|
||||
x => x.Key,
|
||||
x=> (int?)(x.Value[1] < 0 ? null : x.Value[1]));
|
||||
|
||||
stationJobs.RoundStartTotalJobs = mapJobList.Values.Where(x => x[0] is not null && x[0] > 0).Sum(x => x[0]!.Value);
|
||||
stationJobs.MidRoundTotalJobs = mapJobList.Values.Where(x => x[1] is not null && x[1] > 0).Sum(x => x[1]!.Value);
|
||||
|
||||
stationJobs.TotalJobs = stationJobs.MidRoundTotalJobs;
|
||||
|
||||
stationJobs.JobList = mapJobList.ToDictionary(x => x.Key, x =>
|
||||
{
|
||||
if (x.Value[1] <= -1)
|
||||
return null;
|
||||
return (uint?) x.Value[1];
|
||||
});
|
||||
|
||||
stationJobs.RoundStartJobList = mapJobList.ToDictionary(x => x.Key, x =>
|
||||
{
|
||||
if (x.Value[0] <= -1)
|
||||
return null;
|
||||
return (uint?) x.Value[0];
|
||||
});
|
||||
|
||||
stationJobs.OverflowJobs = stationJobs.OverflowJobs.ToHashSet();
|
||||
stationJobs.TotalJobs = stationJobs.JobList.Values.Select(x => x ?? 0).Sum();
|
||||
|
||||
UpdateJobsAvailable();
|
||||
}
|
||||
@@ -141,7 +138,11 @@ public sealed partial class StationJobsSystem : EntitySystem
|
||||
/// <param name="stationJobs">Resolve pattern, station jobs component of the station.</param>
|
||||
/// <returns>Whether or not slot adjustment was a success.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown when the given station is not a station.</exception>
|
||||
public bool TryAdjustJobSlot(EntityUid station, string jobPrototypeId, int amount, bool createSlot = false, bool clamp = false,
|
||||
public bool TryAdjustJobSlot(EntityUid station,
|
||||
string jobPrototypeId,
|
||||
int amount,
|
||||
bool createSlot = false,
|
||||
bool clamp = false,
|
||||
StationJobsComponent? stationJobs = null)
|
||||
{
|
||||
if (!Resolve(station, ref stationJobs))
|
||||
@@ -156,7 +157,11 @@ public sealed partial class StationJobsSystem : EntitySystem
|
||||
// - Return false when you remove from a job that doesn't exist.
|
||||
// - Return false when you remove and exceed the number of slots available.
|
||||
// And additionally, if adding would add a job not previously on the manifest when createSlot is false, return false and do nothing.
|
||||
switch (jobList.ContainsKey(jobPrototypeId))
|
||||
|
||||
if (amount == 0)
|
||||
return true;
|
||||
|
||||
switch (jobList.TryGetValue(jobPrototypeId, out var available))
|
||||
{
|
||||
case false when amount < 0:
|
||||
return false;
|
||||
@@ -164,31 +169,20 @@ public sealed partial class StationJobsSystem : EntitySystem
|
||||
if (!createSlot)
|
||||
return false;
|
||||
stationJobs.TotalJobs += amount;
|
||||
jobList[jobPrototypeId] = (uint?)amount;
|
||||
jobList[jobPrototypeId] = amount;
|
||||
UpdateJobsAvailable();
|
||||
return true;
|
||||
case true:
|
||||
// Job is unlimited so just say we adjusted it and do nothing.
|
||||
if (jobList[jobPrototypeId] == null)
|
||||
if (available is not {} avail)
|
||||
return true;
|
||||
|
||||
// Would remove more jobs than we have available.
|
||||
if (amount < 0 && (jobList[jobPrototypeId] + amount < 0 && !clamp))
|
||||
if (available + amount < 0 && !clamp)
|
||||
return false;
|
||||
|
||||
stationJobs.TotalJobs += amount;
|
||||
|
||||
//C# type handling moment
|
||||
if (amount > 0)
|
||||
jobList[jobPrototypeId] += (uint)amount;
|
||||
else
|
||||
{
|
||||
if ((int)jobList[jobPrototypeId]!.Value - Math.Abs(amount) <= 0)
|
||||
jobList[jobPrototypeId] = 0;
|
||||
else
|
||||
jobList[jobPrototypeId] -= (uint) Math.Abs(amount);
|
||||
}
|
||||
|
||||
jobList[jobPrototypeId] = Math.Max(avail + amount, 0);
|
||||
stationJobs.TotalJobs = jobList.Values.Select(x => x ?? 0).Sum();
|
||||
UpdateJobsAvailable();
|
||||
return true;
|
||||
}
|
||||
@@ -239,7 +233,10 @@ public sealed partial class StationJobsSystem : EntitySystem
|
||||
/// <param name="stationJobs">Resolve pattern, station jobs component of the station.</param>
|
||||
/// <returns>Whether or not setting the value succeeded.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown when the given station is not a station.</exception>
|
||||
public bool TrySetJobSlot(EntityUid station, string jobPrototypeId, int amount, bool createSlot = false,
|
||||
public bool TrySetJobSlot(EntityUid station,
|
||||
string jobPrototypeId,
|
||||
int amount,
|
||||
bool createSlot = false,
|
||||
StationJobsComponent? stationJobs = null)
|
||||
{
|
||||
if (!Resolve(station, ref stationJobs))
|
||||
@@ -255,13 +252,13 @@ public sealed partial class StationJobsSystem : EntitySystem
|
||||
if (!createSlot)
|
||||
return false;
|
||||
stationJobs.TotalJobs += amount;
|
||||
jobList[jobPrototypeId] = (uint?)amount;
|
||||
jobList[jobPrototypeId] = amount;
|
||||
UpdateJobsAvailable();
|
||||
return true;
|
||||
case true:
|
||||
stationJobs.TotalJobs += amount - (int) (jobList[jobPrototypeId] ?? 0);
|
||||
stationJobs.TotalJobs += amount - (jobList[jobPrototypeId] ?? 0);
|
||||
|
||||
jobList[jobPrototypeId] = (uint)amount;
|
||||
jobList[jobPrototypeId] = amount;
|
||||
UpdateJobsAvailable();
|
||||
return true;
|
||||
}
|
||||
@@ -289,8 +286,8 @@ public sealed partial class StationJobsSystem : EntitySystem
|
||||
throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
|
||||
|
||||
// Subtract out the job we're fixing to make have unlimited slots.
|
||||
if (stationJobs.JobList.ContainsKey(jobPrototypeId) && stationJobs.JobList[jobPrototypeId] != null)
|
||||
stationJobs.TotalJobs -= (int)stationJobs.JobList[jobPrototypeId]!.Value;
|
||||
if (stationJobs.JobList.TryGetValue(jobPrototypeId, out var existing))
|
||||
stationJobs.TotalJobs -= existing ?? 0;
|
||||
|
||||
stationJobs.JobList[jobPrototypeId] = null;
|
||||
|
||||
@@ -319,8 +316,7 @@ public sealed partial class StationJobsSystem : EntitySystem
|
||||
if (!Resolve(station, ref stationJobs))
|
||||
throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
|
||||
|
||||
var res = stationJobs.JobList.TryGetValue(jobPrototypeId, out var job) && job == null;
|
||||
return res;
|
||||
return stationJobs.JobList.TryGetValue(jobPrototypeId, out var job) && job == null;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="TryGetJobSlot(Robust.Shared.GameObjects.EntityUid,string,out System.Nullable{uint},Content.Server.Station.Components.StationJobsComponent?)"/>
|
||||
@@ -328,7 +324,7 @@ public sealed partial class StationJobsSystem : EntitySystem
|
||||
/// <param name="job">Job to get slot info for.</param>
|
||||
/// <param name="slots">The number of slots remaining. Null if infinite.</param>
|
||||
/// <param name="stationJobs">Resolve pattern, station jobs component of the station.</param>
|
||||
public bool TryGetJobSlot(EntityUid station, JobPrototype job, out uint? slots, StationJobsComponent? stationJobs = null)
|
||||
public bool TryGetJobSlot(EntityUid station, JobPrototype job, out int? slots, StationJobsComponent? stationJobs = null)
|
||||
{
|
||||
return TryGetJobSlot(station, job.ID, out slots, stationJobs);
|
||||
}
|
||||
@@ -343,21 +339,12 @@ public sealed partial class StationJobsSystem : EntitySystem
|
||||
/// <returns>Whether or not the slot exists.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown when the given station is not a station.</exception>
|
||||
/// <remarks>slots will be null if the slot doesn't exist, as well, so make sure to check the return value.</remarks>
|
||||
public bool TryGetJobSlot(EntityUid station, string jobPrototypeId, out uint? slots, StationJobsComponent? stationJobs = null)
|
||||
public bool TryGetJobSlot(EntityUid station, string jobPrototypeId, out int? slots, StationJobsComponent? stationJobs = null)
|
||||
{
|
||||
if (!Resolve(station, ref stationJobs))
|
||||
throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
|
||||
|
||||
if (stationJobs.JobList.TryGetValue(jobPrototypeId, out var job))
|
||||
{
|
||||
slots = job;
|
||||
return true;
|
||||
}
|
||||
else // Else if slot isn't present return null.
|
||||
{
|
||||
slots = null;
|
||||
return false;
|
||||
}
|
||||
return stationJobs.JobList.TryGetValue(jobPrototypeId, out slots);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -367,12 +354,14 @@ public sealed partial class StationJobsSystem : EntitySystem
|
||||
/// <param name="stationJobs">Resolve pattern, station jobs component of the station.</param>
|
||||
/// <returns>Set containing all jobs available.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown when the given station is not a station.</exception>
|
||||
public IReadOnlySet<string> GetAvailableJobs(EntityUid station, StationJobsComponent? stationJobs = null)
|
||||
public IEnumerable<ProtoId<JobPrototype>> GetAvailableJobs(EntityUid station, StationJobsComponent? stationJobs = null)
|
||||
{
|
||||
if (!Resolve(station, ref stationJobs))
|
||||
throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
|
||||
|
||||
return stationJobs.JobList.Where(x => x.Value != 0).Select(x => x.Key).ToHashSet();
|
||||
return stationJobs.JobList
|
||||
.Where(x => x.Value != 0)
|
||||
.Select(x => x.Key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -382,12 +371,12 @@ public sealed partial class StationJobsSystem : EntitySystem
|
||||
/// <param name="stationJobs">Resolve pattern, station jobs component of the station.</param>
|
||||
/// <returns>Set containing all overflow jobs available.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown when the given station is not a station.</exception>
|
||||
public IReadOnlySet<string> GetOverflowJobs(EntityUid station, StationJobsComponent? stationJobs = null)
|
||||
public IReadOnlySet<ProtoId<JobPrototype>> GetOverflowJobs(EntityUid station, StationJobsComponent? stationJobs = null)
|
||||
{
|
||||
if (!Resolve(station, ref stationJobs))
|
||||
throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
|
||||
|
||||
return stationJobs.OverflowJobs.ToHashSet();
|
||||
return stationJobs.OverflowJobs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -397,7 +386,7 @@ public sealed partial class StationJobsSystem : EntitySystem
|
||||
/// <param name="stationJobs">Resolve pattern, station jobs component of the station.</param>
|
||||
/// <returns>List of all jobs on the station.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown when the given station is not a station.</exception>
|
||||
public IReadOnlyDictionary<string, uint?> GetJobs(EntityUid station, StationJobsComponent? stationJobs = null)
|
||||
public IReadOnlyDictionary<ProtoId<JobPrototype>, int?> GetJobs(EntityUid station, StationJobsComponent? stationJobs = null)
|
||||
{
|
||||
if (!Resolve(station, ref stationJobs))
|
||||
throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
|
||||
@@ -412,12 +401,14 @@ public sealed partial class StationJobsSystem : EntitySystem
|
||||
/// <param name="stationJobs">Resolve pattern, station jobs component of the station.</param>
|
||||
/// <returns>List of all round-start jobs.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown when the given station is not a station.</exception>
|
||||
public IReadOnlyDictionary<string, uint?> GetRoundStartJobs(EntityUid station, StationJobsComponent? stationJobs = null)
|
||||
public Dictionary<ProtoId<JobPrototype>, int?> GetRoundStartJobs(EntityUid station, StationJobsComponent? stationJobs = null)
|
||||
{
|
||||
if (!Resolve(station, ref stationJobs))
|
||||
throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
|
||||
|
||||
return stationJobs.RoundStartJobList;
|
||||
return stationJobs.SetupAvailableJobs.ToDictionary(
|
||||
x => x.Key,
|
||||
x=> (int?)(x.Value[0] < 0 ? null : x.Value[0]));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -428,13 +419,13 @@ public sealed partial class StationJobsSystem : EntitySystem
|
||||
/// <param name="pickOverflows">Whether or not to pick from the overflow list.</param>
|
||||
/// <param name="disallowedJobs">A set of disallowed jobs, if any.</param>
|
||||
/// <returns>The selected job, if any.</returns>
|
||||
public string? PickBestAvailableJobWithPriority(EntityUid station, IReadOnlyDictionary<string, JobPriority> jobPriorities, bool pickOverflows, IReadOnlySet<ProtoId<JobPrototype>>? disallowedJobs = null)
|
||||
public ProtoId<JobPrototype>? PickBestAvailableJobWithPriority(EntityUid station, IReadOnlyDictionary<ProtoId<JobPrototype>, JobPriority> jobPriorities, bool pickOverflows, IReadOnlySet<ProtoId<JobPrototype>>? disallowedJobs = null)
|
||||
{
|
||||
if (station == EntityUid.Invalid)
|
||||
return null;
|
||||
|
||||
var available = GetAvailableJobs(station);
|
||||
bool TryPick(JobPriority priority, [NotNullWhen(true)] out string? jobId)
|
||||
bool TryPick(JobPriority priority, [NotNullWhen(true)] out ProtoId<JobPrototype>? jobId)
|
||||
{
|
||||
var filtered = jobPriorities
|
||||
.Where(p =>
|
||||
@@ -474,7 +465,10 @@ public sealed partial class StationJobsSystem : EntitySystem
|
||||
return null;
|
||||
|
||||
var overflows = GetOverflowJobs(station);
|
||||
return overflows.Count != 0 ? _random.Pick(overflows) : null;
|
||||
if (overflows.Count == 0)
|
||||
return null;
|
||||
|
||||
return _random.Pick(overflows);
|
||||
}
|
||||
|
||||
#endregion Public API
|
||||
@@ -483,7 +477,7 @@ public sealed partial class StationJobsSystem : EntitySystem
|
||||
|
||||
private bool _availableJobsDirty;
|
||||
|
||||
private TickerJobsAvailableEvent _cachedAvailableJobs = new (new Dictionary<NetEntity, string>(), new Dictionary<NetEntity, Dictionary<string, uint?>>());
|
||||
private TickerJobsAvailableEvent _cachedAvailableJobs = new(new(), new());
|
||||
|
||||
/// <summary>
|
||||
/// Assembles an event from the current available-to-play jobs.
|
||||
@@ -494,9 +488,9 @@ public sealed partial class StationJobsSystem : EntitySystem
|
||||
{
|
||||
// If late join is disallowed, return no available jobs.
|
||||
if (_gameTicker.DisallowLateJoin)
|
||||
return new TickerJobsAvailableEvent(new Dictionary<NetEntity, string>(), new Dictionary<NetEntity, Dictionary<string, uint?>>());
|
||||
return new TickerJobsAvailableEvent(new(), new());
|
||||
|
||||
var jobs = new Dictionary<NetEntity, Dictionary<string, uint?>>();
|
||||
var jobs = new Dictionary<NetEntity, Dictionary<ProtoId<JobPrototype>, int?>>();
|
||||
var stationNames = new Dictionary<NetEntity, string>();
|
||||
|
||||
var query = EntityQueryEnumerator<StationJobsComponent>();
|
||||
|
||||
@@ -98,7 +98,7 @@ public sealed class HandTeleporterSystem : EntitySystem
|
||||
if (xform.ParentUid != xform.GridUid) // Still, don't portal.
|
||||
return;
|
||||
|
||||
if (xform.ParentUid != Transform(component.FirstPortal!.Value).ParentUid)
|
||||
if (!component.AllowPortalsOnDifferentGrids && xform.ParentUid != Transform(component.FirstPortal!.Value).ParentUid)
|
||||
{
|
||||
// Whoops. Fizzle time. Crime time too because yippee I'm not refactoring this logic right now (I started to, I'm not going to.)
|
||||
FizzlePortals(uid, component, user, true);
|
||||
|
||||
Reference in New Issue
Block a user