Merge pull request #595 from crystallpunk-14/ed-20-11-2024-upstream-sync
Upstream sync
This commit is contained in:
@@ -146,8 +146,8 @@ namespace Content.Server.Abilities.Mime
|
||||
mimePowers.ReadyToRepent = false;
|
||||
mimePowers.VowBroken = false;
|
||||
AddComp<MutedComponent>(uid);
|
||||
_alertsSystem.ClearAlert(uid, mimePowers.VowAlert);
|
||||
_alertsSystem.ShowAlert(uid, mimePowers.VowBrokenAlert);
|
||||
_alertsSystem.ClearAlert(uid, mimePowers.VowBrokenAlert);
|
||||
_alertsSystem.ShowAlert(uid, mimePowers.VowAlert);
|
||||
_actionsSystem.AddAction(uid, ref mimePowers.InvisibleWallActionEntity, mimePowers.InvisibleWallAction, uid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ public sealed class BanListEui : BaseEui
|
||||
|
||||
private async Task LoadBans(NetUserId userId)
|
||||
{
|
||||
foreach (var ban in await _db.GetServerBansAsync(null, userId, null))
|
||||
foreach (var ban in await _db.GetServerBansAsync(null, userId, null, null))
|
||||
{
|
||||
SharedServerUnban? unban = null;
|
||||
if (ban.Unban is { } unbanDef)
|
||||
@@ -74,7 +74,7 @@ public sealed class BanListEui : BaseEui
|
||||
? (address.address.ToString(), address.cidrMask)
|
||||
: null;
|
||||
|
||||
hwid = ban.HWId == null ? null : Convert.ToBase64String(ban.HWId.Value.AsSpan());
|
||||
hwid = ban.HWId?.ToString();
|
||||
}
|
||||
|
||||
Bans.Add(new SharedServerBan(
|
||||
@@ -95,7 +95,7 @@ public sealed class BanListEui : BaseEui
|
||||
|
||||
private async Task LoadRoleBans(NetUserId userId)
|
||||
{
|
||||
foreach (var ban in await _db.GetServerRoleBansAsync(null, userId, null))
|
||||
foreach (var ban in await _db.GetServerRoleBansAsync(null, userId, null, null))
|
||||
{
|
||||
SharedServerUnban? unban = null;
|
||||
if (ban.Unban is { } unbanDef)
|
||||
@@ -115,7 +115,7 @@ public sealed class BanListEui : BaseEui
|
||||
? (address.address.ToString(), address.cidrMask)
|
||||
: null;
|
||||
|
||||
hwid = ban.HWId == null ? null : Convert.ToBase64String(ban.HWId.Value.AsSpan());
|
||||
hwid = ban.HWId?.ToString();
|
||||
}
|
||||
RoleBans.Add(new SharedServerRoleBan(
|
||||
ban.Id,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using Content.Server.Administration.Managers;
|
||||
@@ -8,7 +7,6 @@ using Content.Server.EUI;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Eui;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Content.Server.Administration;
|
||||
@@ -27,7 +25,7 @@ public sealed class BanPanelEui : BaseEui
|
||||
private NetUserId? PlayerId { get; set; }
|
||||
private string PlayerName { get; set; } = string.Empty;
|
||||
private IPAddress? LastAddress { get; set; }
|
||||
private ImmutableArray<byte>? LastHwid { get; set; }
|
||||
private ImmutableTypedHwid? LastHwid { get; set; }
|
||||
private const int Ipv4_CIDR = 32;
|
||||
private const int Ipv6_CIDR = 64;
|
||||
|
||||
@@ -51,7 +49,7 @@ public sealed class BanPanelEui : BaseEui
|
||||
switch (msg)
|
||||
{
|
||||
case BanPanelEuiStateMsg.CreateBanRequest r:
|
||||
BanPlayer(r.Player, r.IpAddress, r.UseLastIp, r.Hwid?.ToImmutableArray(), r.UseLastHwid, r.Minutes, r.Severity, r.Reason, r.Roles, r.Erase);
|
||||
BanPlayer(r.Player, r.IpAddress, r.UseLastIp, r.Hwid, r.UseLastHwid, r.Minutes, r.Severity, r.Reason, r.Roles, r.Erase);
|
||||
break;
|
||||
case BanPanelEuiStateMsg.GetPlayerInfoRequest r:
|
||||
ChangePlayer(r.PlayerUsername);
|
||||
@@ -59,7 +57,7 @@ public sealed class BanPanelEui : BaseEui
|
||||
}
|
||||
}
|
||||
|
||||
private async void BanPlayer(string? target, string? ipAddressString, bool useLastIp, ImmutableArray<byte>? hwid, bool useLastHwid, uint minutes, NoteSeverity severity, string reason, IReadOnlyCollection<string>? roles, bool erase)
|
||||
private async void BanPlayer(string? target, string? ipAddressString, bool useLastIp, ImmutableTypedHwid? hwid, bool useLastHwid, uint minutes, NoteSeverity severity, string reason, IReadOnlyCollection<string>? roles, bool erase)
|
||||
{
|
||||
if (!_admins.HasAdminFlag(Player, AdminFlags.Ban))
|
||||
{
|
||||
@@ -155,7 +153,7 @@ public sealed class BanPanelEui : BaseEui
|
||||
ChangePlayer(located?.UserId, located?.Username ?? string.Empty, located?.LastAddress, located?.LastHWId);
|
||||
}
|
||||
|
||||
public void ChangePlayer(NetUserId? playerId, string playerName, IPAddress? lastAddress, ImmutableArray<byte>? lastHwid)
|
||||
public void ChangePlayer(NetUserId? playerId, string playerName, IPAddress? lastAddress, ImmutableTypedHwid? lastHwid)
|
||||
{
|
||||
PlayerId = playerId;
|
||||
PlayerName = playerName;
|
||||
|
||||
@@ -38,7 +38,7 @@ public sealed class BanListCommand : LocalizedCommands
|
||||
|
||||
if (shell.Player is not { } player)
|
||||
{
|
||||
var bans = await _dbManager.GetServerBansAsync(data.LastAddress, data.UserId, data.LastHWId, false);
|
||||
var bans = await _dbManager.GetServerBansAsync(data.LastAddress, data.UserId, data.LastLegacyHWId, data.LastModernHWIds, false);
|
||||
|
||||
if (bans.Count == 0)
|
||||
{
|
||||
|
||||
@@ -48,7 +48,7 @@ public sealed class RoleBanListCommand : IConsoleCommand
|
||||
if (shell.Player is not { } player)
|
||||
{
|
||||
|
||||
var bans = await _dbManager.GetServerRoleBansAsync(data.LastAddress, data.UserId, data.LastHWId, includeUnbanned);
|
||||
var bans = await _dbManager.GetServerRoleBansAsync(data.LastAddress, data.UserId, data.LastLegacyHWId, data.LastModernHWIds, includeUnbanned);
|
||||
|
||||
if (bans.Count == 0)
|
||||
{
|
||||
|
||||
@@ -65,7 +65,8 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
|
||||
|
||||
var netChannel = player.Channel;
|
||||
ImmutableArray<byte>? hwId = netChannel.UserData.HWId.Length == 0 ? null : netChannel.UserData.HWId;
|
||||
var roleBans = await _db.GetServerRoleBansAsync(netChannel.RemoteEndPoint.Address, player.UserId, hwId, false);
|
||||
var modernHwids = netChannel.UserData.ModernHWIds;
|
||||
var roleBans = await _db.GetServerRoleBansAsync(netChannel.RemoteEndPoint.Address, player.UserId, hwId, modernHwids, false);
|
||||
|
||||
var userRoleBans = new List<ServerRoleBanDef>();
|
||||
foreach (var ban in roleBans)
|
||||
@@ -132,7 +133,7 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
|
||||
}
|
||||
|
||||
#region Server Bans
|
||||
public async void CreateServerBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableArray<byte>? hwid, uint? minutes, NoteSeverity severity, string reason)
|
||||
public async void CreateServerBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableTypedHwid? hwid, uint? minutes, NoteSeverity severity, string reason)
|
||||
{
|
||||
DateTimeOffset? expires = null;
|
||||
if (minutes > 0)
|
||||
@@ -166,9 +167,7 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
|
||||
var addressRangeString = addressRange != null
|
||||
? $"{addressRange.Value.Item1}/{addressRange.Value.Item2}"
|
||||
: "null";
|
||||
var hwidString = hwid != null
|
||||
? string.Concat(hwid.Value.Select(x => x.ToString("x2")))
|
||||
: "null";
|
||||
var hwidString = hwid?.ToString() ?? "null";
|
||||
var expiresString = expires == null ? Loc.GetString("server-ban-string-never") : $"{expires}";
|
||||
|
||||
var key = _cfg.GetCVar(CCVars.AdminShowPIIOnBan) ? "server-ban-string" : "server-ban-string-no-pii";
|
||||
@@ -208,6 +207,7 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
|
||||
UserId = player.UserId,
|
||||
Address = player.Channel.RemoteEndPoint.Address,
|
||||
HWId = player.Channel.UserData.HWId,
|
||||
ModernHWIds = player.Channel.UserData.ModernHWIds,
|
||||
// It's possible for the player to not have cached data loading yet due to coincidental timing.
|
||||
// If this is the case, we assume they have all flags to avoid false-positives.
|
||||
ExemptFlags = _cachedBanExemptions.GetValueOrDefault(player, ServerBanExemptFlags.All),
|
||||
@@ -228,7 +228,7 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
|
||||
#region Job Bans
|
||||
// If you are trying to remove timeOfBan, please don't. It's there because the note system groups role bans by time, reason and banning admin.
|
||||
// Removing it will clutter the note list. Please also make sure that department bans are applied to roles with the same DateTimeOffset.
|
||||
public async void CreateRoleBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableArray<byte>? hwid, string role, uint? minutes, NoteSeverity severity, string reason, DateTimeOffset timeOfBan)
|
||||
public async void CreateRoleBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableTypedHwid? hwid, string role, uint? minutes, NoteSeverity severity, string reason, DateTimeOffset timeOfBan)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(role, out JobPrototype? _))
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ public interface IBanManager
|
||||
/// <param name="minutes">Number of minutes to ban for. 0 and null mean permanent</param>
|
||||
/// <param name="severity">Severity of the resulting ban note</param>
|
||||
/// <param name="reason">Reason for the ban</param>
|
||||
public void CreateServerBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableArray<byte>? hwid, uint? minutes, NoteSeverity severity, string reason);
|
||||
public void CreateServerBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableTypedHwid? hwid, uint? minutes, NoteSeverity severity, string reason);
|
||||
public HashSet<string>? GetRoleBans(NetUserId playerUserId);
|
||||
public HashSet<ProtoId<JobPrototype>>? GetJobBans(NetUserId playerUserId);
|
||||
|
||||
@@ -37,7 +37,7 @@ public interface IBanManager
|
||||
/// <param name="reason">Reason for the ban</param>
|
||||
/// <param name="minutes">Number of minutes to ban for. 0 and null mean permanent</param>
|
||||
/// <param name="timeOfBan">Time when the ban was applied, used for grouping role bans</param>
|
||||
public void CreateRoleBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableArray<byte>? hwid, string role, uint? minutes, NoteSeverity severity, string reason, DateTimeOffset timeOfBan);
|
||||
public void CreateRoleBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableTypedHwid? hwid, string role, uint? minutes, NoteSeverity severity, string reason, DateTimeOffset timeOfBan);
|
||||
|
||||
/// <summary>
|
||||
/// Pardons a role ban for the specified target, username or GUID
|
||||
|
||||
@@ -5,16 +5,42 @@ using System.Net.Http.Headers;
|
||||
using System.Net.Http.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Connection;
|
||||
using Content.Server.Database;
|
||||
using Content.Shared.Database;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server.Administration
|
||||
{
|
||||
public sealed record LocatedPlayerData(NetUserId UserId, IPAddress? LastAddress, ImmutableArray<byte>? LastHWId, string Username);
|
||||
/// <summary>
|
||||
/// Contains data resolved via <see cref="IPlayerLocator"/>.
|
||||
/// </summary>
|
||||
/// <param name="UserId">The ID of the located user.</param>
|
||||
/// <param name="LastAddress">The last known IP address that the user connected with.</param>
|
||||
/// <param name="LastHWId">
|
||||
/// The last known HWID that the user connected with.
|
||||
/// This should be used for placing new records involving HWIDs, such as bans.
|
||||
/// For looking up data based on HWID, use combined <see cref="LastLegacyHWId"/> and <see cref="LastModernHWIds"/>.
|
||||
/// </param>
|
||||
/// <param name="Username">The last known username for the user connected with.</param>
|
||||
/// <param name="LastLegacyHWId">
|
||||
/// The last known legacy HWID value this user connected with. Only use for old lookups!
|
||||
/// </param>
|
||||
/// <param name="LastModernHWIds">
|
||||
/// The set of last known modern HWIDs the user connected with.
|
||||
/// </param>
|
||||
public sealed record LocatedPlayerData(
|
||||
NetUserId UserId,
|
||||
IPAddress? LastAddress,
|
||||
ImmutableTypedHwid? LastHWId,
|
||||
string Username,
|
||||
ImmutableArray<byte>? LastLegacyHWId,
|
||||
ImmutableArray<ImmutableArray<byte>> LastModernHWIds);
|
||||
|
||||
/// <summary>
|
||||
/// Utilities for finding user IDs that extend to more than the server database.
|
||||
@@ -67,63 +93,42 @@ namespace Content.Server.Administration
|
||||
{
|
||||
// Check people currently on the server, the easiest case.
|
||||
if (_playerManager.TryGetSessionByUsername(playerName, out var session))
|
||||
{
|
||||
var userId = session.UserId;
|
||||
var address = session.Channel.RemoteEndPoint.Address;
|
||||
var hwId = session.Channel.UserData.HWId;
|
||||
return new LocatedPlayerData(userId, address, hwId, session.Name);
|
||||
}
|
||||
return ReturnForSession(session);
|
||||
|
||||
// Check database for past players.
|
||||
var record = await _db.GetPlayerRecordByUserName(playerName, cancel);
|
||||
if (record != null)
|
||||
return new LocatedPlayerData(record.UserId, record.LastSeenAddress, record.HWId, record.LastSeenUserName);
|
||||
return ReturnForPlayerRecord(record);
|
||||
|
||||
// If all else fails, ask the auth server.
|
||||
var authServer = _configurationManager.GetCVar(CVars.AuthServer);
|
||||
var requestUri = $"{authServer}api/query/name?name={WebUtility.UrlEncode(playerName)}";
|
||||
using var resp = await _httpClient.GetAsync(requestUri, cancel);
|
||||
|
||||
if (resp.StatusCode == HttpStatusCode.NotFound)
|
||||
return null;
|
||||
|
||||
if (!resp.IsSuccessStatusCode)
|
||||
{
|
||||
_sawmill.Error("Auth server returned bad response {StatusCode}!", resp.StatusCode);
|
||||
return null;
|
||||
}
|
||||
|
||||
var responseData = await resp.Content.ReadFromJsonAsync<UserDataResponse>(cancellationToken: cancel);
|
||||
|
||||
if (responseData == null)
|
||||
{
|
||||
_sawmill.Error("Auth server returned null response!");
|
||||
return null;
|
||||
}
|
||||
|
||||
return new LocatedPlayerData(new NetUserId(responseData.UserId), null, null, responseData.UserName);
|
||||
return await HandleAuthServerResponse(resp, cancel);
|
||||
}
|
||||
|
||||
public async Task<LocatedPlayerData?> LookupIdAsync(NetUserId userId, CancellationToken cancel = default)
|
||||
{
|
||||
// Check people currently on the server, the easiest case.
|
||||
if (_playerManager.TryGetSessionById(userId, out var session))
|
||||
{
|
||||
var address = session.Channel.RemoteEndPoint.Address;
|
||||
var hwId = session.Channel.UserData.HWId;
|
||||
return new LocatedPlayerData(userId, address, hwId, session.Name);
|
||||
}
|
||||
return ReturnForSession(session);
|
||||
|
||||
// Check database for past players.
|
||||
var record = await _db.GetPlayerRecordByUserId(userId, cancel);
|
||||
if (record != null)
|
||||
return new LocatedPlayerData(record.UserId, record.LastSeenAddress, record.HWId, record.LastSeenUserName);
|
||||
return ReturnForPlayerRecord(record);
|
||||
|
||||
// If all else fails, ask the auth server.
|
||||
var authServer = _configurationManager.GetCVar(CVars.AuthServer);
|
||||
var requestUri = $"{authServer}api/query/userid?userid={WebUtility.UrlEncode(userId.UserId.ToString())}";
|
||||
using var resp = await _httpClient.GetAsync(requestUri, cancel);
|
||||
|
||||
return await HandleAuthServerResponse(resp, cancel);
|
||||
}
|
||||
|
||||
private async Task<LocatedPlayerData?> HandleAuthServerResponse(HttpResponseMessage resp, CancellationToken cancel)
|
||||
{
|
||||
if (resp.StatusCode == HttpStatusCode.NotFound)
|
||||
return null;
|
||||
|
||||
@@ -134,14 +139,40 @@ namespace Content.Server.Administration
|
||||
}
|
||||
|
||||
var responseData = await resp.Content.ReadFromJsonAsync<UserDataResponse>(cancellationToken: cancel);
|
||||
|
||||
if (responseData == null)
|
||||
{
|
||||
_sawmill.Error("Auth server returned null response!");
|
||||
return null;
|
||||
}
|
||||
|
||||
return new LocatedPlayerData(new NetUserId(responseData.UserId), null, null, responseData.UserName);
|
||||
return new LocatedPlayerData(new NetUserId(responseData.UserId), null, null, responseData.UserName, null, []);
|
||||
}
|
||||
|
||||
private static LocatedPlayerData ReturnForSession(ICommonSession session)
|
||||
{
|
||||
var userId = session.UserId;
|
||||
var address = session.Channel.RemoteEndPoint.Address;
|
||||
var hwId = session.Channel.UserData.GetModernHwid();
|
||||
return new LocatedPlayerData(
|
||||
userId,
|
||||
address,
|
||||
hwId,
|
||||
session.Name,
|
||||
session.Channel.UserData.HWId,
|
||||
session.Channel.UserData.ModernHWIds);
|
||||
}
|
||||
|
||||
private static LocatedPlayerData ReturnForPlayerRecord(PlayerRecord record)
|
||||
{
|
||||
var hwid = record.HWId;
|
||||
|
||||
return new LocatedPlayerData(
|
||||
record.UserId,
|
||||
record.LastSeenAddress,
|
||||
hwid,
|
||||
record.LastSeenUserName,
|
||||
hwid is { Type: HwidType.Legacy } ? hwid.Hwid : null,
|
||||
hwid is { Type: HwidType.Modern } ? [hwid.Hwid] : []);
|
||||
}
|
||||
|
||||
public async Task<LocatedPlayerData?> LookupIdByNameOrIdAsync(string playerName, CancellationToken cancel = default)
|
||||
|
||||
@@ -173,11 +173,11 @@ public sealed class PlayerPanelEui : BaseEui
|
||||
{
|
||||
_whitelisted = await _db.GetWhitelistStatusAsync(_targetPlayer.UserId);
|
||||
// This won't get associated ip or hwid bans but they were not placed on this account anyways
|
||||
_bans = (await _db.GetServerBansAsync(null, _targetPlayer.UserId, null)).Count;
|
||||
_bans = (await _db.GetServerBansAsync(null, _targetPlayer.UserId, null, null)).Count;
|
||||
// Unfortunately role bans for departments and stuff are issued individually. This means that a single role ban can have many individual role bans internally
|
||||
// The only way to distinguish whether a role ban is the same is to compare the ban time.
|
||||
// This is horrible and I would love to just erase the database and start from scratch instead but that's what I can do for now.
|
||||
_roleBans = (await _db.GetServerRoleBansAsync(null, _targetPlayer.UserId, null)).DistinctBy(rb => rb.BanTime).Count();
|
||||
_roleBans = (await _db.GetServerRoleBansAsync(null, _targetPlayer.UserId, null, null)).DistinctBy(rb => rb.BanTime).Count();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -22,6 +22,7 @@ using Content.Shared.Administration;
|
||||
using Content.Shared.Administration.Components;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Part;
|
||||
using Content.Shared.Clumsy;
|
||||
using Content.Shared.Clothing.Components;
|
||||
using Content.Shared.Cluwne;
|
||||
using Content.Shared.Damage;
|
||||
|
||||
@@ -172,7 +172,7 @@ namespace Content.Server.Administration.Systems
|
||||
}
|
||||
|
||||
// Check if the user has been banned
|
||||
var ban = await _dbManager.GetServerBanAsync(null, e.Session.UserId, null);
|
||||
var ban = await _dbManager.GetServerBanAsync(null, e.Session.UserId, null, null);
|
||||
if (ban != null)
|
||||
{
|
||||
var banMessage = Loc.GetString("bwoink-system-player-banned", ("banReason", ban.Reason));
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
using Content.Server.Administration.Components;
|
||||
using Content.Shared.Climbing.Components;
|
||||
using Content.Shared.Climbing.Events;
|
||||
using Content.Shared.Climbing.Systems;
|
||||
using Content.Shared.Interaction.Components;
|
||||
using Content.Shared.Clumsy;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
|
||||
namespace Content.Server.Administration.Systems;
|
||||
|
||||
public sealed class SuperBonkSystem: EntitySystem
|
||||
public sealed class SuperBonkSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
||||
[Dependency] private readonly BonkSystem _bonkSystem = default!;
|
||||
[Dependency] private readonly ClumsySystem _clumsySystem = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<SuperBonkComponent, ComponentShutdown>(OnBonkShutdown);
|
||||
SubscribeLocalEvent<SuperBonkComponent, MobStateChangedEvent>(OnMobStateChanged);
|
||||
SubscribeLocalEvent<SuperBonkComponent, ComponentShutdown>(OnBonkShutdown);
|
||||
}
|
||||
|
||||
public void StartSuperBonk(EntityUid target, float delay = 0.1f, bool stopWhenDead = false )
|
||||
public void StartSuperBonk(EntityUid target, float delay = 0.1f, bool stopWhenDead = false)
|
||||
{
|
||||
|
||||
//The other check in the code to stop when the target dies does not work if the target is already dead.
|
||||
@@ -31,7 +31,6 @@ public sealed class SuperBonkSystem: EntitySystem
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var hadClumsy = EnsureComp<ClumsyComponent>(target, out _);
|
||||
|
||||
var tables = EntityQueryEnumerator<BonkableComponent>();
|
||||
@@ -79,16 +78,17 @@ public sealed class SuperBonkSystem: EntitySystem
|
||||
private void Bonk(SuperBonkComponent comp)
|
||||
{
|
||||
var uid = comp.Tables.Current.Key;
|
||||
var bonkComp = comp.Tables.Current.Value;
|
||||
|
||||
// It would be very weird for something without a transform component to have a bonk component
|
||||
// but just in case because I don't want to crash the server.
|
||||
if (!HasComp<TransformComponent>(uid))
|
||||
if (!HasComp<TransformComponent>(uid) || !TryComp<ClumsyComponent>(comp.Target, out var clumsyComp))
|
||||
return;
|
||||
|
||||
_transformSystem.SetCoordinates(comp.Target, Transform(uid).Coordinates);
|
||||
|
||||
_bonkSystem.TryBonk(comp.Target, uid, bonkComp);
|
||||
_clumsySystem.HitHeadClumsy((comp.Target, clumsyComp), uid);
|
||||
|
||||
_audioSystem.PlayPvs(clumsyComp.TableBonkSound, comp.Target);
|
||||
}
|
||||
|
||||
private void OnMobStateChanged(EntityUid uid, SuperBonkComponent comp, MobStateChangedEvent args)
|
||||
|
||||
@@ -25,6 +25,16 @@ public sealed class TagCommand : ToolshedCommand
|
||||
});
|
||||
}
|
||||
|
||||
[CommandImplementation("with")]
|
||||
public IEnumerable<EntityUid> With(
|
||||
[CommandInvocationContext] IInvocationContext ctx,
|
||||
[PipedArgument] IEnumerable<EntityUid> entities,
|
||||
[CommandArgument] ValueRef<string, Prototype<TagPrototype>> tag)
|
||||
{
|
||||
_tag ??= GetSys<TagSystem>();
|
||||
return entities.Where(e => _tag.HasTag(e, tag.Evaluate(ctx)!));
|
||||
}
|
||||
|
||||
[CommandImplementation("add")]
|
||||
public EntityUid Add(
|
||||
[CommandInvocationContext] IInvocationContext ctx,
|
||||
|
||||
@@ -1,7 +1,19 @@
|
||||
using Content.Shared.Alert;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Server.Alert;
|
||||
|
||||
internal sealed class ServerAlertsSystem : AlertsSystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<AlertsComponent, ComponentGetState>(OnGetState);
|
||||
}
|
||||
|
||||
private void OnGetState(Entity<AlertsComponent> alerts, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new AlertComponentState(alerts.Comp.Alerts);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,8 @@ namespace Content.Server.Announcements
|
||||
}
|
||||
else
|
||||
{
|
||||
var message = string.Join(' ', new ArraySegment<string>(args, 1, args.Length-1));
|
||||
// Explicit IEnumerable<string> due to overload ambiguity on .NET 9
|
||||
var message = string.Join(' ', (IEnumerable<string>)new ArraySegment<string>(args, 1, args.Length-1));
|
||||
chat.DispatchGlobalAnnouncement(message, args[0], colorOverride: Color.Gold);
|
||||
}
|
||||
shell.WriteLine("Sent!");
|
||||
|
||||
@@ -55,6 +55,8 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
Log.Level = LogLevel.Debug;
|
||||
|
||||
SubscribeLocalEvent<GhostRoleAntagSpawnerComponent, TakeGhostRoleEvent>(OnTakeGhostRole);
|
||||
|
||||
SubscribeLocalEvent<AntagSelectionComponent, ObjectivesTextGetInfoEvent>(OnObjectivesTextGetInfo);
|
||||
@@ -182,7 +184,7 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
return;
|
||||
|
||||
var players = _playerManager.Sessions
|
||||
.Where(x => GameTicker.PlayerGameStatuses[x.UserId] == PlayerGameStatus.JoinedGame)
|
||||
.Where(x => GameTicker.PlayerGameStatuses.TryGetValue(x.UserId, out var status) && status == PlayerGameStatus.JoinedGame)
|
||||
.ToList();
|
||||
|
||||
ChooseAntags((uid, component), players, midround: true);
|
||||
@@ -360,6 +362,8 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
_role.MindAddRoles(curMind.Value, def.MindRoles, null, true);
|
||||
ent.Comp.SelectedMinds.Add((curMind.Value, Name(player)));
|
||||
SendBriefing(session, def.Briefing);
|
||||
|
||||
Log.Debug($"Selected {ToPrettyString(curMind)} as antagonist: {ToPrettyString(ent)}");
|
||||
}
|
||||
|
||||
var afterEv = new AfterAntagEntitySelectedEvent(session, player, ent, def);
|
||||
|
||||
@@ -48,7 +48,9 @@ public sealed partial class AtmosMonitorComponent : Component
|
||||
[DataField("gasThresholds")]
|
||||
public Dictionary<Gas, AtmosAlarmThreshold>? GasThresholds;
|
||||
|
||||
// Stores a reference to the gas on the tile this is on.
|
||||
/// <summary>
|
||||
/// Stores a reference to the gas on the tile this entity is on (or the pipe network it monitors; see <see cref="MonitorsPipeNet"/>).
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public GasMixture? TileGas;
|
||||
|
||||
@@ -65,4 +67,19 @@ public sealed partial class AtmosMonitorComponent : Component
|
||||
/// </summary>
|
||||
[DataField("registeredDevices")]
|
||||
public HashSet<string> RegisteredDevices = new();
|
||||
|
||||
/// <summary>
|
||||
/// Specifies whether this device monitors its own internal pipe network rather than the surrounding atmosphere.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If 'true', the entity will require a NodeContainerComponent with one or more PipeNodes to function.
|
||||
/// </remarks>
|
||||
[DataField]
|
||||
public bool MonitorsPipeNet = false;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the name of the pipe node that this device is monitoring.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string NodeNameMonitoredPipe = "monitored";
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@ using Content.Server.Atmos.Piping.Components;
|
||||
using Content.Server.Atmos.Piping.EntitySystems;
|
||||
using Content.Server.DeviceNetwork;
|
||||
using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Server.NodeContainer;
|
||||
using Content.Server.NodeContainer.EntitySystems;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Shared.Atmos;
|
||||
@@ -25,6 +28,7 @@ public sealed class AtmosMonitorSystem : EntitySystem
|
||||
[Dependency] private readonly AtmosDeviceSystem _atmosDeviceSystem = default!;
|
||||
[Dependency] private readonly DeviceNetworkSystem _deviceNetSystem = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly NodeContainerSystem _nodeContainerSystem = default!;
|
||||
|
||||
// Commands
|
||||
public const string AtmosMonitorSetThresholdCmd = "atmos_monitor_set_threshold";
|
||||
@@ -56,8 +60,15 @@ public sealed class AtmosMonitorSystem : EntitySystem
|
||||
|
||||
private void OnAtmosDeviceEnterAtmosphere(EntityUid uid, AtmosMonitorComponent atmosMonitor, ref AtmosDeviceEnabledEvent args)
|
||||
{
|
||||
if (atmosMonitor.MonitorsPipeNet && _nodeContainerSystem.TryGetNode<PipeNode>(uid, atmosMonitor.NodeNameMonitoredPipe, out var pipeNode))
|
||||
{
|
||||
atmosMonitor.TileGas = pipeNode.Air;
|
||||
return;
|
||||
}
|
||||
|
||||
atmosMonitor.TileGas = _atmosphereSystem.GetContainingMixture(uid, true);
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, AtmosMonitorComponent component, MapInitEvent args)
|
||||
{
|
||||
if (component.TemperatureThresholdId != null)
|
||||
@@ -206,7 +217,7 @@ public sealed class AtmosMonitorSystem : EntitySystem
|
||||
if (!this.IsPowered(uid, EntityManager))
|
||||
return;
|
||||
|
||||
if (args.Grid == null)
|
||||
if (args.Grid == null)
|
||||
return;
|
||||
|
||||
// if we're not monitoring atmos, don't bother
|
||||
@@ -215,6 +226,10 @@ public sealed class AtmosMonitorSystem : EntitySystem
|
||||
&& component.GasThresholds == null)
|
||||
return;
|
||||
|
||||
// If monitoring a pipe network, get its most recent gas mixture
|
||||
if (component.MonitorsPipeNet && _nodeContainerSystem.TryGetNode<PipeNode>(uid, component.NodeNameMonitoredPipe, out var pipeNode))
|
||||
component.TileGas = pipeNode.Air;
|
||||
|
||||
UpdateState(uid, component.TileGas, component);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Globalization;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Ghost;
|
||||
using Content.Server.Hands.Systems;
|
||||
using Content.Server.Inventory;
|
||||
@@ -14,6 +14,7 @@ using Content.Shared.Bed.Cryostorage;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Climbing.Systems;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.StationRecords;
|
||||
@@ -26,7 +27,6 @@ using Robust.Shared.Containers;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Content.Server.Bed.Cryostorage;
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ public sealed class SeedExtractorSystem : EntitySystem
|
||||
args.User, PopupType.Medium);
|
||||
|
||||
QueueDel(args.Used);
|
||||
args.Handled = true;
|
||||
|
||||
var amount = _random.Next(seedExtractor.BaseMinSeeds, seedExtractor.BaseMaxSeeds + 1);
|
||||
var coords = Transform(uid).Coordinates;
|
||||
|
||||
@@ -424,7 +424,7 @@ public record struct PriceCalculationEvent()
|
||||
[ByRefEvent]
|
||||
public record struct EstimatedPriceCalculationEvent()
|
||||
{
|
||||
public EntityPrototype Prototype;
|
||||
public required EntityPrototype Prototype;
|
||||
|
||||
/// <summary>
|
||||
/// The total price of the entity.
|
||||
|
||||
@@ -428,6 +428,7 @@ public sealed class CartridgeLoaderSystem : SharedCartridgeLoaderSystem
|
||||
{
|
||||
var cartridgeEvent = args.MessageEvent;
|
||||
cartridgeEvent.LoaderUid = GetNetEntity(uid);
|
||||
cartridgeEvent.Actor = args.Actor;
|
||||
|
||||
RelayEvent(component, cartridgeEvent, true);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||
using Content.Shared.Chemistry.Hypospray.Events;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.FixedPoint;
|
||||
@@ -85,14 +86,44 @@ public sealed class HypospraySystem : SharedHypospraySystem
|
||||
|
||||
string? msgFormat = null;
|
||||
|
||||
if (target == user)
|
||||
msgFormat = "hypospray-component-inject-self-message";
|
||||
else if (EligibleEntity(user, EntityManager, component) && _interaction.TryRollClumsy(user, component.ClumsyFailChance))
|
||||
// Self event
|
||||
var selfEvent = new SelfBeforeHyposprayInjectsEvent(user, entity.Owner, target);
|
||||
RaiseLocalEvent(user, selfEvent);
|
||||
|
||||
if (selfEvent.Cancelled)
|
||||
{
|
||||
msgFormat = "hypospray-component-inject-self-clumsy-message";
|
||||
target = user;
|
||||
_popup.PopupEntity(Loc.GetString(selfEvent.InjectMessageOverride ?? "hypospray-cant-inject", ("owner", Identity.Entity(target, EntityManager))), target, user);
|
||||
return false;
|
||||
}
|
||||
|
||||
target = selfEvent.TargetGettingInjected;
|
||||
|
||||
if (!EligibleEntity(target, EntityManager, component))
|
||||
return false;
|
||||
|
||||
// Target event
|
||||
var targetEvent = new TargetBeforeHyposprayInjectsEvent(user, entity.Owner, target);
|
||||
RaiseLocalEvent(target, targetEvent);
|
||||
|
||||
if (targetEvent.Cancelled)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString(targetEvent.InjectMessageOverride ?? "hypospray-cant-inject", ("owner", Identity.Entity(target, EntityManager))), target, user);
|
||||
return false;
|
||||
}
|
||||
|
||||
target = targetEvent.TargetGettingInjected;
|
||||
|
||||
if (!EligibleEntity(target, EntityManager, component))
|
||||
return false;
|
||||
|
||||
// The target event gets priority for the overriden message.
|
||||
if (targetEvent.InjectMessageOverride != null)
|
||||
msgFormat = targetEvent.InjectMessageOverride;
|
||||
else if (selfEvent.InjectMessageOverride != null)
|
||||
msgFormat = selfEvent.InjectMessageOverride;
|
||||
else if (target == user)
|
||||
msgFormat = "hypospray-component-inject-self-message";
|
||||
|
||||
if (!_solutionContainers.TryGetSolution(uid, component.SolutionName, out var hypoSpraySoln, out var hypoSpraySolution) || hypoSpraySolution.Volume == 0)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("hypospray-component-empty-message"), target, user);
|
||||
|
||||
@@ -15,6 +15,7 @@ using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Stacks;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
|
||||
namespace Content.Server.Chemistry.EntitySystems;
|
||||
|
||||
@@ -22,6 +23,7 @@ public sealed class InjectorSystem : SharedInjectorSystem
|
||||
{
|
||||
[Dependency] private readonly BloodstreamSystem _blood = default!;
|
||||
[Dependency] private readonly ReactiveSystem _reactiveSystem = default!;
|
||||
[Dependency] private readonly OpenableSystem _openable = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -35,13 +37,14 @@ public sealed class InjectorSystem : SharedInjectorSystem
|
||||
{
|
||||
RaiseLocalEvent(injector, new CP14TrySkillIssueEvent(user)); //CP14 Skill issue event
|
||||
|
||||
var isOpenOrIgnored = injector.Comp.IgnoreClosed || !_openable.IsClosed(target);
|
||||
// Handle injecting/drawing for solutions
|
||||
if (injector.Comp.ToggleState == InjectorToggleMode.Inject)
|
||||
{
|
||||
if (SolutionContainers.TryGetInjectableSolution(target, out var injectableSolution, out _))
|
||||
if (isOpenOrIgnored && SolutionContainers.TryGetInjectableSolution(target, out var injectableSolution, out _))
|
||||
return TryInject(injector, target, injectableSolution.Value, user, false);
|
||||
|
||||
if (SolutionContainers.TryGetRefillableSolution(target, out var refillableSolution, out _))
|
||||
if (isOpenOrIgnored && SolutionContainers.TryGetRefillableSolution(target, out var refillableSolution, out _))
|
||||
return TryInject(injector, target, refillableSolution.Value, user, true);
|
||||
|
||||
if (TryComp<BloodstreamComponent>(target, out var bloodstream))
|
||||
@@ -62,7 +65,7 @@ public sealed class InjectorSystem : SharedInjectorSystem
|
||||
}
|
||||
|
||||
// Draw from an object (food, beaker, etc)
|
||||
if (SolutionContainers.TryGetDrawableSolution(target, out var drawableSolution, out _))
|
||||
if (isOpenOrIgnored && SolutionContainers.TryGetDrawableSolution(target, out var drawableSolution, out _))
|
||||
return TryDraw(injector, target, drawableSolution.Value, user);
|
||||
|
||||
Popup.PopupEntity(Loc.GetString("injector-component-cannot-draw-message",
|
||||
|
||||
@@ -51,7 +51,8 @@ public sealed class CursedMaskSystem : SharedCursedMaskSystem
|
||||
}
|
||||
|
||||
var npcFaction = EnsureComp<NpcFactionMemberComponent>(wearer);
|
||||
ent.Comp.OldFactions = npcFaction.Factions;
|
||||
ent.Comp.OldFactions.Clear();
|
||||
ent.Comp.OldFactions.UnionWith(npcFaction.Factions);
|
||||
_npcFaction.ClearFactions((wearer, npcFaction), false);
|
||||
_npcFaction.AddFaction((wearer, npcFaction), ent.Comp.CursedMaskFaction);
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ using Content.Shared.Cluwne;
|
||||
using Content.Shared.Interaction.Components;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Content.Shared.NameModifier.EntitySystems;
|
||||
using Content.Shared.Clumsy;
|
||||
|
||||
namespace Content.Server.Cluwne;
|
||||
|
||||
|
||||
@@ -111,11 +111,14 @@ namespace Content.Server.Connection
|
||||
|
||||
var serverId = (await _serverDbEntry.ServerEntity).Id;
|
||||
|
||||
var hwid = e.UserData.GetModernHwid();
|
||||
var trust = e.UserData.Trust;
|
||||
|
||||
if (deny != null)
|
||||
{
|
||||
var (reason, msg, banHits) = deny.Value;
|
||||
|
||||
var id = await _db.AddConnectionLogAsync(userId, e.UserName, addr, e.UserData.HWId, reason, serverId);
|
||||
var id = await _db.AddConnectionLogAsync(userId, e.UserName, addr, hwid, trust, reason, serverId);
|
||||
if (banHits is { Count: > 0 })
|
||||
await _db.AddServerBanHitsAsync(id, banHits);
|
||||
|
||||
@@ -127,12 +130,12 @@ namespace Content.Server.Connection
|
||||
}
|
||||
else
|
||||
{
|
||||
await _db.AddConnectionLogAsync(userId, e.UserName, addr, e.UserData.HWId, null, serverId);
|
||||
await _db.AddConnectionLogAsync(userId, e.UserName, addr, hwid, trust, null, serverId);
|
||||
|
||||
if (!ServerPreferencesManager.ShouldStorePrefs(e.AuthType))
|
||||
return;
|
||||
|
||||
await _db.UpdatePlayerRecordAsync(userId, e.UserName, addr, e.UserData.HWId);
|
||||
await _db.UpdatePlayerRecordAsync(userId, e.UserName, addr, hwid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +193,9 @@ namespace Content.Server.Connection
|
||||
hwId = null;
|
||||
}
|
||||
|
||||
var bans = await _db.GetServerBansAsync(addr, userId, hwId, includeUnbanned: false);
|
||||
var modernHwid = e.UserData.ModernHWIds;
|
||||
|
||||
var bans = await _db.GetServerBansAsync(addr, userId, hwId, modernHwid, includeUnbanned: false);
|
||||
if (bans.Count > 0)
|
||||
{
|
||||
var firstBan = bans[0];
|
||||
|
||||
24
Content.Server/Connection/UserDataExt.cs
Normal file
24
Content.Server/Connection/UserDataExt.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Content.Shared.Database;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Content.Server.Connection;
|
||||
|
||||
/// <summary>
|
||||
/// Helper functions for working with <see cref="NetUserData"/>.
|
||||
/// </summary>
|
||||
public static class UserDataExt
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the preferred HWID that should be used for new records related to a player.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Players can have zero or more HWIDs, but for logging things like connection logs we generally
|
||||
/// only want a single one. This method returns a nullable method.
|
||||
/// </remarks>
|
||||
public static ImmutableTypedHwid? GetModernHwid(this NetUserData userData)
|
||||
{
|
||||
return userData.ModernHWIds.Length == 0
|
||||
? null
|
||||
: new ImmutableTypedHwid(userData.ModernHWIds[0], HwidType.Modern);
|
||||
}
|
||||
}
|
||||
@@ -82,6 +82,8 @@ public sealed class CrayonSystem : SharedCrayonSystem
|
||||
|
||||
if (component.DeleteEmpty && component.Charges <= 0)
|
||||
UseUpCrayon(uid, args.User);
|
||||
else
|
||||
_uiSystem.ServerSendUiMessage(uid, SharedCrayonComponent.CrayonUiKey.Key, new CrayonUsedMessage(component.SelectedState));
|
||||
}
|
||||
|
||||
private void OnCrayonUse(EntityUid uid, CrayonComponent component, UseInHandEvent args)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Net;
|
||||
using Content.Server.IP;
|
||||
using Content.Shared.Database;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Content.Server.Database;
|
||||
@@ -52,9 +53,28 @@ public static class BanMatcher
|
||||
return true;
|
||||
}
|
||||
|
||||
return player.HWId is { Length: > 0 } hwIdVar
|
||||
&& ban.HWId != null
|
||||
&& hwIdVar.AsSpan().SequenceEqual(ban.HWId.Value.AsSpan());
|
||||
switch (ban.HWId?.Type)
|
||||
{
|
||||
case HwidType.Legacy:
|
||||
if (player.HWId is { Length: > 0 } hwIdVar
|
||||
&& hwIdVar.AsSpan().SequenceEqual(ban.HWId.Hwid.AsSpan()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case HwidType.Modern:
|
||||
if (player.ModernHWIds is { Length: > 0 } modernHwIdVar)
|
||||
{
|
||||
foreach (var hwid in modernHwIdVar)
|
||||
{
|
||||
if (hwid.AsSpan().SequenceEqual(ban.HWId.Hwid.AsSpan()))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -73,10 +93,15 @@ public static class BanMatcher
|
||||
public IPAddress? Address;
|
||||
|
||||
/// <summary>
|
||||
/// The hardware ID of the player.
|
||||
/// The LEGACY hardware ID of the player. Corresponds with <see cref="NetUserData.HWId"/>.
|
||||
/// </summary>
|
||||
public ImmutableArray<byte>? HWId;
|
||||
|
||||
/// <summary>
|
||||
/// The modern hardware IDs of the player. Corresponds with <see cref="NetUserData.ModernHWIds"/>.
|
||||
/// </summary>
|
||||
public ImmutableArray<ImmutableArray<byte>>? ModernHWIds;
|
||||
|
||||
/// <summary>
|
||||
/// Exemption flags the player has been granted.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Net;
|
||||
using Content.Shared.Database;
|
||||
using Robust.Shared.Network;
|
||||
@@ -121,7 +120,7 @@ public sealed record PlayerRecord(
|
||||
string LastSeenUserName,
|
||||
DateTimeOffset LastSeenTime,
|
||||
IPAddress LastSeenAddress,
|
||||
ImmutableArray<byte>? HWId);
|
||||
ImmutableTypedHwid? HWId);
|
||||
|
||||
public sealed record RoundRecord(int Id, DateTimeOffset? StartDate, ServerRecord Server);
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Net;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Database;
|
||||
@@ -13,7 +12,7 @@ namespace Content.Server.Database
|
||||
public int? Id { get; }
|
||||
public NetUserId? UserId { get; }
|
||||
public (IPAddress address, int cidrMask)? Address { get; }
|
||||
public ImmutableArray<byte>? HWId { get; }
|
||||
public ImmutableTypedHwid? HWId { get; }
|
||||
|
||||
public DateTimeOffset BanTime { get; }
|
||||
public DateTimeOffset? ExpirationTime { get; }
|
||||
@@ -28,7 +27,7 @@ namespace Content.Server.Database
|
||||
public ServerBanDef(int? id,
|
||||
NetUserId? userId,
|
||||
(IPAddress, int)? address,
|
||||
ImmutableArray<byte>? hwId,
|
||||
TypedHwid? hwId,
|
||||
DateTimeOffset banTime,
|
||||
DateTimeOffset? expirationTime,
|
||||
int? roundId,
|
||||
|
||||
@@ -388,12 +388,14 @@ namespace Content.Server.Database
|
||||
/// </summary>
|
||||
/// <param name="address">The ip address of the user.</param>
|
||||
/// <param name="userId">The id of the user.</param>
|
||||
/// <param name="hwId">The HWId of the user.</param>
|
||||
/// <param name="hwId">The legacy HWId of the user.</param>
|
||||
/// <param name="modernHWIds">The modern HWIDs of the user.</param>
|
||||
/// <returns>The user's latest received un-pardoned ban, or null if none exist.</returns>
|
||||
public abstract Task<ServerBanDef?> GetServerBanAsync(
|
||||
IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId);
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds);
|
||||
|
||||
/// <summary>
|
||||
/// Looks up an user's ban history.
|
||||
@@ -402,13 +404,15 @@ namespace Content.Server.Database
|
||||
/// </summary>
|
||||
/// <param name="address">The ip address of the user.</param>
|
||||
/// <param name="userId">The id of the user.</param>
|
||||
/// <param name="hwId">The HWId of the user.</param>
|
||||
/// <param name="hwId">The legacy HWId of the user.</param>
|
||||
/// <param name="modernHWIds">The modern HWIDs of the user.</param>
|
||||
/// <param name="includeUnbanned">Include pardoned and expired bans.</param>
|
||||
/// <returns>The user's ban history.</returns>
|
||||
public abstract Task<List<ServerBanDef>> GetServerBansAsync(
|
||||
IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
||||
bool includeUnbanned);
|
||||
|
||||
public abstract Task AddServerBanAsync(ServerBanDef serverBan);
|
||||
@@ -499,11 +503,13 @@ namespace Content.Server.Database
|
||||
/// <param name="address">The IP address of the user.</param>
|
||||
/// <param name="userId">The NetUserId of the user.</param>
|
||||
/// <param name="hwId">The Hardware Id of the user.</param>
|
||||
/// <param name="modernHWIds">The modern HWIDs of the user.</param>
|
||||
/// <param name="includeUnbanned">Whether expired and pardoned bans are included.</param>
|
||||
/// <returns>The user's role ban history.</returns>
|
||||
public abstract Task<List<ServerRoleBanDef>> GetServerRoleBansAsync(IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
||||
bool includeUnbanned);
|
||||
|
||||
public abstract Task<ServerRoleBanDef> AddServerRoleBanAsync(ServerRoleBanDef serverRoleBan);
|
||||
@@ -512,16 +518,23 @@ namespace Content.Server.Database
|
||||
public async Task EditServerRoleBan(int id, string reason, NoteSeverity severity, DateTimeOffset? expiration, Guid editedBy, DateTimeOffset editedAt)
|
||||
{
|
||||
await using var db = await GetDb();
|
||||
var roleBanDetails = await db.DbContext.RoleBan
|
||||
.Where(b => b.Id == id)
|
||||
.Select(b => new { b.BanTime, b.PlayerUserId })
|
||||
.SingleOrDefaultAsync();
|
||||
|
||||
var ban = await db.DbContext.RoleBan.SingleOrDefaultAsync(b => b.Id == id);
|
||||
if (ban is null)
|
||||
if (roleBanDetails == default)
|
||||
return;
|
||||
ban.Severity = severity;
|
||||
ban.Reason = reason;
|
||||
ban.ExpirationTime = expiration?.UtcDateTime;
|
||||
ban.LastEditedById = editedBy;
|
||||
ban.LastEditedAt = editedAt.UtcDateTime;
|
||||
await db.DbContext.SaveChangesAsync();
|
||||
|
||||
await db.DbContext.RoleBan
|
||||
.Where(b => b.BanTime == roleBanDetails.BanTime && b.PlayerUserId == roleBanDetails.PlayerUserId)
|
||||
.ExecuteUpdateAsync(setters => setters
|
||||
.SetProperty(b => b.Severity, severity)
|
||||
.SetProperty(b => b.Reason, reason)
|
||||
.SetProperty(b => b.ExpirationTime, expiration.HasValue ? expiration.Value.UtcDateTime : (DateTime?)null)
|
||||
.SetProperty(b => b.LastEditedById, editedBy)
|
||||
.SetProperty(b => b.LastEditedAt, editedAt.UtcDateTime)
|
||||
);
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -586,7 +599,7 @@ namespace Content.Server.Database
|
||||
NetUserId userId,
|
||||
string userName,
|
||||
IPAddress address,
|
||||
ImmutableArray<byte> hwId)
|
||||
ImmutableTypedHwid? hwId)
|
||||
{
|
||||
await using var db = await GetDb();
|
||||
|
||||
@@ -603,7 +616,7 @@ namespace Content.Server.Database
|
||||
record.LastSeenTime = DateTime.UtcNow;
|
||||
record.LastSeenAddress = address;
|
||||
record.LastSeenUserName = userName;
|
||||
record.LastSeenHWId = hwId.ToArray();
|
||||
record.LastSeenHWId = hwId;
|
||||
|
||||
await db.DbContext.SaveChangesAsync();
|
||||
}
|
||||
@@ -649,7 +662,7 @@ namespace Content.Server.Database
|
||||
player.LastSeenUserName,
|
||||
new DateTimeOffset(NormalizeDatabaseTime(player.LastSeenTime)),
|
||||
player.LastSeenAddress,
|
||||
player.LastSeenHWId?.ToImmutableArray());
|
||||
player.LastSeenHWId);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -658,11 +671,11 @@ namespace Content.Server.Database
|
||||
/*
|
||||
* CONNECTION LOG
|
||||
*/
|
||||
public abstract Task<int> AddConnectionLogAsync(
|
||||
NetUserId userId,
|
||||
public abstract Task<int> AddConnectionLogAsync(NetUserId userId,
|
||||
string userName,
|
||||
IPAddress address,
|
||||
ImmutableArray<byte> hwId,
|
||||
ImmutableTypedHwid? hwId,
|
||||
float trust,
|
||||
ConnectionDenyReason? denied,
|
||||
int serverId);
|
||||
|
||||
|
||||
@@ -69,12 +69,14 @@ namespace Content.Server.Database
|
||||
/// </summary>
|
||||
/// <param name="address">The ip address of the user.</param>
|
||||
/// <param name="userId">The id of the user.</param>
|
||||
/// <param name="hwId">The hardware ID of the user.</param>
|
||||
/// <param name="hwId">The legacy HWID of the user.</param>
|
||||
/// <param name="modernHWIds">The modern HWIDs of the user.</param>
|
||||
/// <returns>The user's latest received un-pardoned ban, or null if none exist.</returns>
|
||||
Task<ServerBanDef?> GetServerBanAsync(
|
||||
IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId);
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds);
|
||||
|
||||
/// <summary>
|
||||
/// Looks up an user's ban history.
|
||||
@@ -82,13 +84,15 @@ namespace Content.Server.Database
|
||||
/// </summary>
|
||||
/// <param name="address">The ip address of the user.</param>
|
||||
/// <param name="userId">The id of the user.</param>
|
||||
/// <param name="hwId">The HWId of the user.</param>
|
||||
/// <param name="hwId">The legacy HWId of the user.</param>
|
||||
/// <param name="modernHWIds">The modern HWIDs of the user.</param>
|
||||
/// <param name="includeUnbanned">If true, bans that have been expired or pardoned are also included.</param>
|
||||
/// <returns>The user's ban history.</returns>
|
||||
Task<List<ServerBanDef>> GetServerBansAsync(
|
||||
IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
||||
bool includeUnbanned=true);
|
||||
|
||||
Task AddServerBanAsync(ServerBanDef serverBan);
|
||||
@@ -137,12 +141,14 @@ namespace Content.Server.Database
|
||||
/// <param name="address">The IP address of the user.</param>
|
||||
/// <param name="userId">The NetUserId of the user.</param>
|
||||
/// <param name="hwId">The Hardware Id of the user.</param>
|
||||
/// <param name="modernHWIds">The modern HWIDs of the user.</param>
|
||||
/// <param name="includeUnbanned">Whether expired and pardoned bans are included.</param>
|
||||
/// <returns>The user's role ban history.</returns>
|
||||
Task<List<ServerRoleBanDef>> GetServerRoleBansAsync(
|
||||
IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
||||
bool includeUnbanned = true);
|
||||
|
||||
Task<ServerRoleBanDef> AddServerRoleBanAsync(ServerRoleBanDef serverBan);
|
||||
@@ -180,7 +186,7 @@ namespace Content.Server.Database
|
||||
NetUserId userId,
|
||||
string userName,
|
||||
IPAddress address,
|
||||
ImmutableArray<byte> hwId);
|
||||
ImmutableTypedHwid? hwId);
|
||||
Task<PlayerRecord?> GetPlayerRecordByUserName(string userName, CancellationToken cancel = default);
|
||||
Task<PlayerRecord?> GetPlayerRecordByUserId(NetUserId userId, CancellationToken cancel = default);
|
||||
#endregion
|
||||
@@ -191,7 +197,8 @@ namespace Content.Server.Database
|
||||
NetUserId userId,
|
||||
string userName,
|
||||
IPAddress address,
|
||||
ImmutableArray<byte> hwId,
|
||||
ImmutableTypedHwid? hwId,
|
||||
float trust,
|
||||
ConnectionDenyReason? denied,
|
||||
int serverId);
|
||||
|
||||
@@ -480,20 +487,22 @@ namespace Content.Server.Database
|
||||
public Task<ServerBanDef?> GetServerBanAsync(
|
||||
IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId)
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds)
|
||||
{
|
||||
DbReadOpsMetric.Inc();
|
||||
return RunDbCommand(() => _db.GetServerBanAsync(address, userId, hwId));
|
||||
return RunDbCommand(() => _db.GetServerBanAsync(address, userId, hwId, modernHWIds));
|
||||
}
|
||||
|
||||
public Task<List<ServerBanDef>> GetServerBansAsync(
|
||||
IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
||||
bool includeUnbanned=true)
|
||||
{
|
||||
DbReadOpsMetric.Inc();
|
||||
return RunDbCommand(() => _db.GetServerBansAsync(address, userId, hwId, includeUnbanned));
|
||||
return RunDbCommand(() => _db.GetServerBansAsync(address, userId, hwId, modernHWIds, includeUnbanned));
|
||||
}
|
||||
|
||||
public Task AddServerBanAsync(ServerBanDef serverBan)
|
||||
@@ -537,10 +546,11 @@ namespace Content.Server.Database
|
||||
IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
||||
bool includeUnbanned = true)
|
||||
{
|
||||
DbReadOpsMetric.Inc();
|
||||
return RunDbCommand(() => _db.GetServerRoleBansAsync(address, userId, hwId, includeUnbanned));
|
||||
return RunDbCommand(() => _db.GetServerRoleBansAsync(address, userId, hwId, modernHWIds, includeUnbanned));
|
||||
}
|
||||
|
||||
public Task<ServerRoleBanDef> AddServerRoleBanAsync(ServerRoleBanDef serverRoleBan)
|
||||
@@ -582,7 +592,7 @@ namespace Content.Server.Database
|
||||
NetUserId userId,
|
||||
string userName,
|
||||
IPAddress address,
|
||||
ImmutableArray<byte> hwId)
|
||||
ImmutableTypedHwid? hwId)
|
||||
{
|
||||
DbWriteOpsMetric.Inc();
|
||||
return RunDbCommand(() => _db.UpdatePlayerRecord(userId, userName, address, hwId));
|
||||
@@ -604,12 +614,13 @@ namespace Content.Server.Database
|
||||
NetUserId userId,
|
||||
string userName,
|
||||
IPAddress address,
|
||||
ImmutableArray<byte> hwId,
|
||||
ImmutableTypedHwid? hwId,
|
||||
float trust,
|
||||
ConnectionDenyReason? denied,
|
||||
int serverId)
|
||||
{
|
||||
DbWriteOpsMetric.Inc();
|
||||
return RunDbCommand(() => _db.AddConnectionLogAsync(userId, userName, address, hwId, denied, serverId));
|
||||
return RunDbCommand(() => _db.AddConnectionLogAsync(userId, userName, address, hwId, trust, denied, serverId));
|
||||
}
|
||||
|
||||
public Task AddServerBanHitsAsync(int connection, IEnumerable<ServerBanDef> bans)
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Threading.Tasks;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.IP;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Network;
|
||||
@@ -73,7 +74,8 @@ namespace Content.Server.Database
|
||||
public override async Task<ServerBanDef?> GetServerBanAsync(
|
||||
IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId)
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds)
|
||||
{
|
||||
if (address == null && userId == null && hwId == null)
|
||||
{
|
||||
@@ -84,7 +86,7 @@ namespace Content.Server.Database
|
||||
|
||||
var exempt = await GetBanExemptionCore(db, userId);
|
||||
var newPlayer = userId == null || !await PlayerRecordExists(db, userId.Value);
|
||||
var query = MakeBanLookupQuery(address, userId, hwId, db, includeUnbanned: false, exempt, newPlayer)
|
||||
var query = MakeBanLookupQuery(address, userId, hwId, modernHWIds, db, includeUnbanned: false, exempt, newPlayer)
|
||||
.OrderByDescending(b => b.BanTime);
|
||||
|
||||
var ban = await query.FirstOrDefaultAsync();
|
||||
@@ -94,7 +96,9 @@ namespace Content.Server.Database
|
||||
|
||||
public override async Task<List<ServerBanDef>> GetServerBansAsync(IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId, bool includeUnbanned)
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
||||
bool includeUnbanned)
|
||||
{
|
||||
if (address == null && userId == null && hwId == null)
|
||||
{
|
||||
@@ -105,7 +109,7 @@ namespace Content.Server.Database
|
||||
|
||||
var exempt = await GetBanExemptionCore(db, userId);
|
||||
var newPlayer = !await db.PgDbContext.Player.AnyAsync(p => p.UserId == userId);
|
||||
var query = MakeBanLookupQuery(address, userId, hwId, db, includeUnbanned, exempt, newPlayer);
|
||||
var query = MakeBanLookupQuery(address, userId, hwId, modernHWIds, db, includeUnbanned, exempt, newPlayer);
|
||||
|
||||
var queryBans = await query.ToArrayAsync();
|
||||
var bans = new List<ServerBanDef>(queryBans.Length);
|
||||
@@ -127,6 +131,7 @@ namespace Content.Server.Database
|
||||
IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
||||
DbGuardImpl db,
|
||||
bool includeUnbanned,
|
||||
ServerBanExemptFlags? exemptFlags,
|
||||
@@ -134,16 +139,11 @@ namespace Content.Server.Database
|
||||
{
|
||||
DebugTools.Assert(!(address == null && userId == null && hwId == null));
|
||||
|
||||
IQueryable<ServerBan>? query = null;
|
||||
|
||||
if (userId is { } uid)
|
||||
{
|
||||
var newQ = db.PgDbContext.Ban
|
||||
.Include(p => p.Unban)
|
||||
.Where(b => b.PlayerUserId == uid.UserId);
|
||||
|
||||
query = query == null ? newQ : query.Union(newQ);
|
||||
}
|
||||
var query = MakeBanLookupQualityShared<ServerBan, ServerUnban>(
|
||||
userId,
|
||||
hwId,
|
||||
modernHWIds,
|
||||
db.PgDbContext.Ban);
|
||||
|
||||
if (address != null && !exemptFlags.GetValueOrDefault(ServerBanExemptFlags.None).HasFlag(ServerBanExemptFlags.IP))
|
||||
{
|
||||
@@ -156,15 +156,6 @@ namespace Content.Server.Database
|
||||
query = query == null ? newQ : query.Union(newQ);
|
||||
}
|
||||
|
||||
if (hwId != null && hwId.Value.Length > 0)
|
||||
{
|
||||
var newQ = db.PgDbContext.Ban
|
||||
.Include(p => p.Unban)
|
||||
.Where(b => b.HWId!.SequenceEqual(hwId.Value.ToArray()));
|
||||
|
||||
query = query == null ? newQ : query.Union(newQ);
|
||||
}
|
||||
|
||||
DebugTools.Assert(
|
||||
query != null,
|
||||
"At least one filter item (IP/UserID/HWID) must have been given to make query not null.");
|
||||
@@ -186,6 +177,49 @@ namespace Content.Server.Database
|
||||
return query.Distinct();
|
||||
}
|
||||
|
||||
private static IQueryable<TBan>? MakeBanLookupQualityShared<TBan, TUnban>(
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
||||
DbSet<TBan> set)
|
||||
where TBan : class, IBanCommon<TUnban>
|
||||
where TUnban : class, IUnbanCommon
|
||||
{
|
||||
IQueryable<TBan>? query = null;
|
||||
|
||||
if (userId is { } uid)
|
||||
{
|
||||
var newQ = set
|
||||
.Include(p => p.Unban)
|
||||
.Where(b => b.PlayerUserId == uid.UserId);
|
||||
|
||||
query = query == null ? newQ : query.Union(newQ);
|
||||
}
|
||||
|
||||
if (hwId != null && hwId.Value.Length > 0)
|
||||
{
|
||||
var newQ = set
|
||||
.Include(p => p.Unban)
|
||||
.Where(b => b.HWId!.Type == HwidType.Legacy && b.HWId!.Hwid.SequenceEqual(hwId.Value.ToArray()));
|
||||
|
||||
query = query == null ? newQ : query.Union(newQ);
|
||||
}
|
||||
|
||||
if (modernHWIds != null)
|
||||
{
|
||||
foreach (var modernHwid in modernHWIds)
|
||||
{
|
||||
var newQ = set
|
||||
.Include(p => p.Unban)
|
||||
.Where(b => b.HWId!.Type == HwidType.Modern && b.HWId!.Hwid.SequenceEqual(modernHwid.ToArray()));
|
||||
|
||||
query = query == null ? newQ : query.Union(newQ);
|
||||
}
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
private static ServerBanDef? ConvertBan(ServerBan? ban)
|
||||
{
|
||||
if (ban == null)
|
||||
@@ -211,7 +245,7 @@ namespace Content.Server.Database
|
||||
ban.Id,
|
||||
uid,
|
||||
ban.Address.ToTuple(),
|
||||
ban.HWId == null ? null : ImmutableArray.Create(ban.HWId),
|
||||
ban.HWId,
|
||||
ban.BanTime,
|
||||
ban.ExpirationTime,
|
||||
ban.RoundId,
|
||||
@@ -249,7 +283,7 @@ namespace Content.Server.Database
|
||||
db.PgDbContext.Ban.Add(new ServerBan
|
||||
{
|
||||
Address = serverBan.Address.ToNpgsqlInet(),
|
||||
HWId = serverBan.HWId?.ToArray(),
|
||||
HWId = serverBan.HWId,
|
||||
Reason = serverBan.Reason,
|
||||
Severity = serverBan.Severity,
|
||||
BanningAdmin = serverBan.BanningAdmin?.UserId,
|
||||
@@ -297,6 +331,7 @@ namespace Content.Server.Database
|
||||
public override async Task<List<ServerRoleBanDef>> GetServerRoleBansAsync(IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
||||
bool includeUnbanned)
|
||||
{
|
||||
if (address == null && userId == null && hwId == null)
|
||||
@@ -306,7 +341,7 @@ namespace Content.Server.Database
|
||||
|
||||
await using var db = await GetDbImpl();
|
||||
|
||||
var query = MakeRoleBanLookupQuery(address, userId, hwId, db, includeUnbanned)
|
||||
var query = MakeRoleBanLookupQuery(address, userId, hwId, modernHWIds, db, includeUnbanned)
|
||||
.OrderByDescending(b => b.BanTime);
|
||||
|
||||
return await QueryRoleBans(query);
|
||||
@@ -334,19 +369,15 @@ namespace Content.Server.Database
|
||||
IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
||||
DbGuardImpl db,
|
||||
bool includeUnbanned)
|
||||
{
|
||||
IQueryable<ServerRoleBan>? query = null;
|
||||
|
||||
if (userId is { } uid)
|
||||
{
|
||||
var newQ = db.PgDbContext.RoleBan
|
||||
.Include(p => p.Unban)
|
||||
.Where(b => b.PlayerUserId == uid.UserId);
|
||||
|
||||
query = query == null ? newQ : query.Union(newQ);
|
||||
}
|
||||
var query = MakeBanLookupQualityShared<ServerRoleBan, ServerRoleUnban>(
|
||||
userId,
|
||||
hwId,
|
||||
modernHWIds,
|
||||
db.PgDbContext.RoleBan);
|
||||
|
||||
if (address != null)
|
||||
{
|
||||
@@ -357,15 +388,6 @@ namespace Content.Server.Database
|
||||
query = query == null ? newQ : query.Union(newQ);
|
||||
}
|
||||
|
||||
if (hwId != null && hwId.Value.Length > 0)
|
||||
{
|
||||
var newQ = db.PgDbContext.RoleBan
|
||||
.Include(p => p.Unban)
|
||||
.Where(b => b.HWId!.SequenceEqual(hwId.Value.ToArray()));
|
||||
|
||||
query = query == null ? newQ : query.Union(newQ);
|
||||
}
|
||||
|
||||
if (!includeUnbanned)
|
||||
{
|
||||
query = query?.Where(p =>
|
||||
@@ -402,7 +424,7 @@ namespace Content.Server.Database
|
||||
ban.Id,
|
||||
uid,
|
||||
ban.Address.ToTuple(),
|
||||
ban.HWId == null ? null : ImmutableArray.Create(ban.HWId),
|
||||
ban.HWId,
|
||||
ban.BanTime,
|
||||
ban.ExpirationTime,
|
||||
ban.RoundId,
|
||||
@@ -440,7 +462,7 @@ namespace Content.Server.Database
|
||||
var ban = new ServerRoleBan
|
||||
{
|
||||
Address = serverRoleBan.Address.ToNpgsqlInet(),
|
||||
HWId = serverRoleBan.HWId?.ToArray(),
|
||||
HWId = serverRoleBan.HWId,
|
||||
Reason = serverRoleBan.Reason,
|
||||
Severity = serverRoleBan.Severity,
|
||||
BanningAdmin = serverRoleBan.BanningAdmin?.UserId,
|
||||
@@ -476,7 +498,8 @@ namespace Content.Server.Database
|
||||
NetUserId userId,
|
||||
string userName,
|
||||
IPAddress address,
|
||||
ImmutableArray<byte> hwId,
|
||||
ImmutableTypedHwid? hwId,
|
||||
float trust,
|
||||
ConnectionDenyReason? denied,
|
||||
int serverId)
|
||||
{
|
||||
@@ -488,9 +511,10 @@ namespace Content.Server.Database
|
||||
Time = DateTime.UtcNow,
|
||||
UserId = userId.UserId,
|
||||
UserName = userName,
|
||||
HWId = hwId.ToArray(),
|
||||
HWId = hwId,
|
||||
Denied = denied,
|
||||
ServerId = serverId
|
||||
ServerId = serverId,
|
||||
Trust = trust,
|
||||
};
|
||||
|
||||
db.PgDbContext.ConnectionLog.Add(connectionLog);
|
||||
|
||||
@@ -9,6 +9,7 @@ using Content.Server.Administration.Logs;
|
||||
using Content.Server.IP;
|
||||
using Content.Server.Preferences.Managers;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Network;
|
||||
@@ -80,22 +81,24 @@ namespace Content.Server.Database
|
||||
public override async Task<ServerBanDef?> GetServerBanAsync(
|
||||
IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId)
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds)
|
||||
{
|
||||
await using var db = await GetDbImpl();
|
||||
|
||||
return (await GetServerBanQueryAsync(db, address, userId, hwId, includeUnbanned: false)).FirstOrDefault();
|
||||
return (await GetServerBanQueryAsync(db, address, userId, hwId, modernHWIds, includeUnbanned: false)).FirstOrDefault();
|
||||
}
|
||||
|
||||
public override async Task<List<ServerBanDef>> GetServerBansAsync(
|
||||
IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
||||
bool includeUnbanned)
|
||||
{
|
||||
await using var db = await GetDbImpl();
|
||||
|
||||
return (await GetServerBanQueryAsync(db, address, userId, hwId, includeUnbanned)).ToList();
|
||||
return (await GetServerBanQueryAsync(db, address, userId, hwId, modernHWIds, includeUnbanned)).ToList();
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<ServerBanDef>> GetServerBanQueryAsync(
|
||||
@@ -103,6 +106,7 @@ namespace Content.Server.Database
|
||||
IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
||||
bool includeUnbanned)
|
||||
{
|
||||
var exempt = await GetBanExemptionCore(db, userId);
|
||||
@@ -119,6 +123,7 @@ namespace Content.Server.Database
|
||||
UserId = userId,
|
||||
ExemptFlags = exempt ?? default,
|
||||
HWId = hwId,
|
||||
ModernHWIds = modernHWIds,
|
||||
IsNewPlayer = newPlayer,
|
||||
};
|
||||
|
||||
@@ -161,7 +166,7 @@ namespace Content.Server.Database
|
||||
Reason = serverBan.Reason,
|
||||
Severity = serverBan.Severity,
|
||||
BanningAdmin = serverBan.BanningAdmin?.UserId,
|
||||
HWId = serverBan.HWId?.ToArray(),
|
||||
HWId = serverBan.HWId,
|
||||
BanTime = serverBan.BanTime.UtcDateTime,
|
||||
ExpirationTime = serverBan.ExpirationTime?.UtcDateTime,
|
||||
RoundId = serverBan.RoundId,
|
||||
@@ -205,6 +210,7 @@ namespace Content.Server.Database
|
||||
IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
||||
bool includeUnbanned)
|
||||
{
|
||||
await using var db = await GetDbImpl();
|
||||
@@ -214,7 +220,7 @@ namespace Content.Server.Database
|
||||
var queryBans = await GetAllRoleBans(db.SqliteDbContext, includeUnbanned);
|
||||
|
||||
return queryBans
|
||||
.Where(b => RoleBanMatches(b, address, userId, hwId))
|
||||
.Where(b => RoleBanMatches(b, address, userId, hwId, modernHWIds))
|
||||
.Select(ConvertRoleBan)
|
||||
.ToList()!;
|
||||
}
|
||||
@@ -237,7 +243,8 @@ namespace Content.Server.Database
|
||||
ServerRoleBan ban,
|
||||
IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId)
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds)
|
||||
{
|
||||
if (address != null && ban.Address is not null && address.IsInSubnet(ban.Address.ToTuple().Value))
|
||||
{
|
||||
@@ -249,7 +256,27 @@ namespace Content.Server.Database
|
||||
return true;
|
||||
}
|
||||
|
||||
return hwId is { Length: > 0 } hwIdVar && hwIdVar.AsSpan().SequenceEqual(ban.HWId);
|
||||
switch (ban.HWId?.Type)
|
||||
{
|
||||
case HwidType.Legacy:
|
||||
if (hwId is { Length: > 0 } hwIdVar && hwIdVar.AsSpan().SequenceEqual(ban.HWId.Hwid))
|
||||
return true;
|
||||
break;
|
||||
|
||||
case HwidType.Modern:
|
||||
if (modernHWIds != null)
|
||||
{
|
||||
foreach (var modernHWId in modernHWIds)
|
||||
{
|
||||
if (modernHWId.AsSpan().SequenceEqual(ban.HWId.Hwid))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override async Task<ServerRoleBanDef> AddServerRoleBanAsync(ServerRoleBanDef serverBan)
|
||||
@@ -262,7 +289,7 @@ namespace Content.Server.Database
|
||||
Reason = serverBan.Reason,
|
||||
Severity = serverBan.Severity,
|
||||
BanningAdmin = serverBan.BanningAdmin?.UserId,
|
||||
HWId = serverBan.HWId?.ToArray(),
|
||||
HWId = serverBan.HWId,
|
||||
BanTime = serverBan.BanTime.UtcDateTime,
|
||||
ExpirationTime = serverBan.ExpirationTime?.UtcDateTime,
|
||||
RoundId = serverBan.RoundId,
|
||||
@@ -316,7 +343,7 @@ namespace Content.Server.Database
|
||||
ban.Id,
|
||||
uid,
|
||||
ban.Address.ToTuple(),
|
||||
ban.HWId == null ? null : ImmutableArray.Create(ban.HWId),
|
||||
ban.HWId,
|
||||
// SQLite apparently always reads DateTime as unspecified, but we always write as UTC.
|
||||
DateTime.SpecifyKind(ban.BanTime, DateTimeKind.Utc),
|
||||
ban.ExpirationTime == null ? null : DateTime.SpecifyKind(ban.ExpirationTime.Value, DateTimeKind.Utc),
|
||||
@@ -376,7 +403,7 @@ namespace Content.Server.Database
|
||||
ban.Id,
|
||||
uid,
|
||||
ban.Address.ToTuple(),
|
||||
ban.HWId == null ? null : ImmutableArray.Create(ban.HWId),
|
||||
ban.HWId,
|
||||
// SQLite apparently always reads DateTime as unspecified, but we always write as UTC.
|
||||
DateTime.SpecifyKind(ban.BanTime, DateTimeKind.Utc),
|
||||
ban.ExpirationTime == null ? null : DateTime.SpecifyKind(ban.ExpirationTime.Value, DateTimeKind.Utc),
|
||||
@@ -412,7 +439,8 @@ namespace Content.Server.Database
|
||||
NetUserId userId,
|
||||
string userName,
|
||||
IPAddress address,
|
||||
ImmutableArray<byte> hwId,
|
||||
ImmutableTypedHwid? hwId,
|
||||
float trust,
|
||||
ConnectionDenyReason? denied,
|
||||
int serverId)
|
||||
{
|
||||
@@ -424,9 +452,10 @@ namespace Content.Server.Database
|
||||
Time = DateTime.UtcNow,
|
||||
UserId = userId.UserId,
|
||||
UserName = userName,
|
||||
HWId = hwId.ToArray(),
|
||||
HWId = hwId,
|
||||
Denied = denied,
|
||||
ServerId = serverId
|
||||
ServerId = serverId,
|
||||
Trust = trust,
|
||||
};
|
||||
|
||||
db.SqliteDbContext.ConnectionLog.Add(connectionLog);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Net;
|
||||
using Content.Shared.Database;
|
||||
using Robust.Shared.Network;
|
||||
@@ -10,7 +9,7 @@ public sealed class ServerRoleBanDef
|
||||
public int? Id { get; }
|
||||
public NetUserId? UserId { get; }
|
||||
public (IPAddress address, int cidrMask)? Address { get; }
|
||||
public ImmutableArray<byte>? HWId { get; }
|
||||
public ImmutableTypedHwid? HWId { get; }
|
||||
|
||||
public DateTimeOffset BanTime { get; }
|
||||
public DateTimeOffset? ExpirationTime { get; }
|
||||
@@ -26,7 +25,7 @@ public sealed class ServerRoleBanDef
|
||||
int? id,
|
||||
NetUserId? userId,
|
||||
(IPAddress, int)? address,
|
||||
ImmutableArray<byte>? hwId,
|
||||
ImmutableTypedHwid? hwId,
|
||||
DateTimeOffset banTime,
|
||||
DateTimeOffset? expirationTime,
|
||||
int? roundId,
|
||||
|
||||
@@ -184,6 +184,6 @@ namespace Content.Server.GameTicking
|
||||
=> UserHasJoinedGame(session.UserId);
|
||||
|
||||
public bool UserHasJoinedGame(NetUserId userId)
|
||||
=> PlayerGameStatuses[userId] == PlayerGameStatus.JoinedGame;
|
||||
=> PlayerGameStatuses.TryGetValue(userId, out var status) && status == PlayerGameStatus.JoinedGame;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,12 @@ using Content.Server.Spawners.Components;
|
||||
using Content.Server.Speech.Components;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Players;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Roles.Jobs;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Network;
|
||||
@@ -457,71 +457,4 @@ namespace Content.Server.GameTicking
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised broadcast before a player is spawned by the GameTicker.
|
||||
/// You can use this event to spawn a player off-station on late-join but also at round start.
|
||||
/// When this event is handled, the GameTicker will not perform its own player-spawning logic.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public sealed class PlayerBeforeSpawnEvent : HandledEntityEventArgs
|
||||
{
|
||||
public ICommonSession Player { get; }
|
||||
public HumanoidCharacterProfile Profile { get; }
|
||||
public string? JobId { get; }
|
||||
public bool LateJoin { get; }
|
||||
public EntityUid Station { get; }
|
||||
|
||||
public PlayerBeforeSpawnEvent(ICommonSession player,
|
||||
HumanoidCharacterProfile profile,
|
||||
string? jobId,
|
||||
bool lateJoin,
|
||||
EntityUid station)
|
||||
{
|
||||
Player = player;
|
||||
Profile = profile;
|
||||
JobId = jobId;
|
||||
LateJoin = lateJoin;
|
||||
Station = station;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised both directed and broadcast when a player has been spawned by the GameTicker.
|
||||
/// You can use this to handle people late-joining, or to handle people being spawned at round start.
|
||||
/// Can be used to give random players a role, modify their equipment, etc.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public sealed class PlayerSpawnCompleteEvent : EntityEventArgs
|
||||
{
|
||||
public EntityUid Mob { get; }
|
||||
public ICommonSession Player { get; }
|
||||
public string? JobId { get; }
|
||||
public bool LateJoin { get; }
|
||||
public bool Silent { get; }
|
||||
public EntityUid Station { get; }
|
||||
public HumanoidCharacterProfile Profile { get; }
|
||||
|
||||
// Ex. If this is the 27th person to join, this will be 27.
|
||||
public int JoinOrder { get; }
|
||||
|
||||
public PlayerSpawnCompleteEvent(EntityUid mob,
|
||||
ICommonSession player,
|
||||
string? jobId,
|
||||
bool lateJoin,
|
||||
bool silent,
|
||||
int joinOrder,
|
||||
EntityUid station,
|
||||
HumanoidCharacterProfile profile)
|
||||
{
|
||||
Mob = mob;
|
||||
Player = player;
|
||||
JobId = jobId;
|
||||
LateJoin = lateJoin;
|
||||
Silent = silent;
|
||||
Station = station;
|
||||
Profile = profile;
|
||||
JoinOrder = joinOrder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using Content.Server.Mind;
|
||||
using Content.Server.Points;
|
||||
using Content.Server.RoundEnd;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.GameTicking.Components;
|
||||
using Content.Shared.Points;
|
||||
using Content.Shared.Storage;
|
||||
|
||||
@@ -41,6 +41,8 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
Log.Level = LogLevel.Debug;
|
||||
|
||||
SubscribeLocalEvent<TraitorRuleComponent, AfterAntagEntitySelectedEvent>(AfterEntitySelected);
|
||||
SubscribeLocalEvent<TraitorRuleComponent, ObjectivesTextPrependEvent>(OnObjectivesTextPrepend);
|
||||
}
|
||||
@@ -53,6 +55,7 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
|
||||
|
||||
private void AfterEntitySelected(Entity<TraitorRuleComponent> ent, ref AfterAntagEntitySelectedEvent args)
|
||||
{
|
||||
Log.Debug($"AfterAntagEntitySelected {ToPrettyString(ent)}");
|
||||
MakeTraitor(args.EntityUid, ent);
|
||||
}
|
||||
|
||||
@@ -78,14 +81,22 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
|
||||
|
||||
public bool MakeTraitor(EntityUid traitor, TraitorRuleComponent component)
|
||||
{
|
||||
Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - start");
|
||||
|
||||
//Grab the mind if it wasn't provided
|
||||
if (!_mindSystem.TryGetMind(traitor, out var mindId, out var mind))
|
||||
{
|
||||
Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - failed, no Mind found");
|
||||
return false;
|
||||
}
|
||||
|
||||
var briefing = "";
|
||||
|
||||
if (component.GiveCodewords)
|
||||
{
|
||||
Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - added codewords flufftext to briefing");
|
||||
briefing = Loc.GetString("traitor-role-codewords-short", ("codewords", string.Join(", ", component.Codewords)));
|
||||
}
|
||||
|
||||
var issuer = _random.Pick(_prototypeManager.Index(component.ObjectiveIssuers).Values);
|
||||
|
||||
@@ -94,6 +105,7 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
|
||||
|
||||
if (component.GiveUplink)
|
||||
{
|
||||
Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - Uplink start");
|
||||
// Calculate the amount of currency on the uplink.
|
||||
var startingBalance = component.StartingBalance;
|
||||
if (_jobs.MindTryGetJob(mindId, out var prototype))
|
||||
@@ -105,18 +117,27 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
|
||||
}
|
||||
|
||||
// Choose and generate an Uplink, and return the uplink code if applicable
|
||||
Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - Uplink request start");
|
||||
var uplinkParams = RequestUplink(traitor, startingBalance, briefing);
|
||||
code = uplinkParams.Item1;
|
||||
briefing = uplinkParams.Item2;
|
||||
Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - Uplink request completed");
|
||||
}
|
||||
|
||||
string[]? codewords = null;
|
||||
if (component.GiveCodewords)
|
||||
{
|
||||
Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - set codewords from component");
|
||||
codewords = component.Codewords;
|
||||
}
|
||||
|
||||
if (component.GiveBriefing)
|
||||
{
|
||||
_antag.SendBriefing(traitor, GenerateBriefing(codewords, code, issuer), null, component.GreetSoundNotification);
|
||||
Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - Sent the Briefing");
|
||||
}
|
||||
|
||||
Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - Adding TraitorMind");
|
||||
component.TraitorMinds.Add(mindId);
|
||||
|
||||
// Assign briefing
|
||||
@@ -126,9 +147,14 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
|
||||
_roleSystem.MindHasRole<TraitorRoleComponent>(mindId, out var traitorRole);
|
||||
if (traitorRole is not null)
|
||||
{
|
||||
Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - Add traitor briefing components");
|
||||
AddComp<RoleBriefingComponent>(traitorRole.Value.Owner);
|
||||
Comp<RoleBriefingComponent>(traitorRole.Value.Owner).Briefing = briefing;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - did not get traitor briefing");
|
||||
}
|
||||
|
||||
// Send codewords to only the traitor client
|
||||
var color = TraitorCodewordColor; // Fall back to a dark red Syndicate color if a prototype is not found
|
||||
@@ -137,9 +163,11 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
|
||||
_roleCodewordSystem.SetRoleCodewords(codewordComp, "traitor", component.Codewords.ToList(), color);
|
||||
|
||||
// Change the faction
|
||||
Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - Change faction");
|
||||
_npcFaction.RemoveFaction(traitor, component.NanoTrasenFaction, false);
|
||||
_npcFaction.AddFaction(traitor, component.SyndicateFaction);
|
||||
|
||||
Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - Finished");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -148,10 +176,12 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
|
||||
var pda = _uplink.FindUplinkTarget(traitor);
|
||||
Note[]? code = null;
|
||||
|
||||
Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - Uplink add");
|
||||
var uplinked = _uplink.AddUplink(traitor, startingBalance, pda, true);
|
||||
|
||||
if (pda is not null && uplinked)
|
||||
{
|
||||
Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - Uplink is PDA");
|
||||
// Codes are only generated if the uplink is a PDA
|
||||
code = EnsureComp<RingerUplinkComponent>(pda.Value).Code;
|
||||
|
||||
@@ -163,6 +193,7 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
|
||||
}
|
||||
else if (pda is null && uplinked)
|
||||
{
|
||||
Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - Uplink is implant");
|
||||
briefing += "\n" + Loc.GetString("traitor-role-uplink-implant-short");
|
||||
}
|
||||
|
||||
|
||||
@@ -34,10 +34,11 @@ public sealed class GatewayGeneratorSystem : EntitySystem
|
||||
[Dependency] private readonly GatewaySystem _gateway = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metadata = default!;
|
||||
[Dependency] private readonly SharedMapSystem _maps = default!;
|
||||
[Dependency] private readonly SharedSalvageSystem _salvage = default!;
|
||||
[Dependency] private readonly TileSystem _tile = default!;
|
||||
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
private const string PlanetNames = "names_borer";
|
||||
[ValidatePrototypeId<LocalizedDatasetPrototype>]
|
||||
private const string PlanetNames = "NamesBorer";
|
||||
|
||||
// TODO:
|
||||
// Fix shader some more
|
||||
@@ -102,7 +103,7 @@ public sealed class GatewayGeneratorSystem : EntitySystem
|
||||
var mapId = _mapManager.CreateMap();
|
||||
var mapUid = _mapManager.GetMapEntityId(mapId);
|
||||
|
||||
var gatewayName = SharedSalvageSystem.GetFTLName(_protoManager.Index<DatasetPrototype>(PlanetNames), seed);
|
||||
var gatewayName = _salvage.GetFTLName(_protoManager.Index<LocalizedDatasetPrototype>(PlanetNames), seed);
|
||||
_metadata.SetEntityName(mapUid, gatewayName);
|
||||
|
||||
var origin = new Vector2i(random.Next(-MaxOffset, MaxOffset), random.Next(-MaxOffset, MaxOffset));
|
||||
|
||||
@@ -60,7 +60,6 @@ public sealed class RandomGiftSystem : EntitySystem
|
||||
var coords = Transform(args.User).Coordinates;
|
||||
var handsEnt = Spawn(component.SelectedEntity, coords);
|
||||
_adminLogger.Add(LogType.EntitySpawn, LogImpact.Low, $"{ToPrettyString(args.User)} used {ToPrettyString(uid)} which spawned {ToPrettyString(handsEnt)}");
|
||||
EnsureComp<ItemComponent>(handsEnt); // For insane mode.
|
||||
if (component.Wrapper is not null)
|
||||
Spawn(component.Wrapper, coords);
|
||||
|
||||
|
||||
@@ -77,7 +77,20 @@ public sealed class DefibrillatorSystem : EntitySystem
|
||||
Zap(uid, target, args.User, component);
|
||||
}
|
||||
|
||||
public bool CanZap(EntityUid uid, EntityUid target, EntityUid? user = null, DefibrillatorComponent? component = null)
|
||||
/// <summary>
|
||||
/// Checks if you can actually defib a target.
|
||||
/// </summary>
|
||||
/// <param name="uid">Uid of the defib</param>
|
||||
/// <param name="target">Uid of the target getting defibbed</param>
|
||||
/// <param name="user">Uid of the entity using the defibrillator</param>
|
||||
/// <param name="component">Defib component</param>
|
||||
/// <param name="targetCanBeAlive">
|
||||
/// If true, the target can be alive. If false, the function will check if the target is alive and will return false if they are.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Returns true if the target is valid to be defibed, false otherwise.
|
||||
/// </returns>
|
||||
public bool CanZap(EntityUid uid, EntityUid target, EntityUid? user = null, DefibrillatorComponent? component = null, bool targetCanBeAlive = false)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return false;
|
||||
@@ -98,15 +111,25 @@ public sealed class DefibrillatorSystem : EntitySystem
|
||||
if (!_powerCell.HasActivatableCharge(uid, user: user))
|
||||
return false;
|
||||
|
||||
if (_mobState.IsAlive(target, mobState))
|
||||
if (!targetCanBeAlive && _mobState.IsAlive(target, mobState))
|
||||
return false;
|
||||
|
||||
if (!component.CanDefibCrit && _mobState.IsCritical(target, mobState))
|
||||
if (!targetCanBeAlive && !component.CanDefibCrit && _mobState.IsCritical(target, mobState))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to start defibrillating the target. If the target is valid, will start the defib do-after.
|
||||
/// </summary>
|
||||
/// <param name="uid">Uid of the defib</param>
|
||||
/// <param name="target">Uid of the target getting defibbed</param>
|
||||
/// <param name="user">Uid of the entity using the defibrillator</param>
|
||||
/// <param name="component">Defib component</param>
|
||||
/// <returns>
|
||||
/// Returns true if the defibrillation do-after started, otherwise false.
|
||||
/// </returns>
|
||||
public bool TryStartZap(EntityUid uid, EntityUid target, EntityUid user, DefibrillatorComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
@@ -118,27 +141,44 @@ public sealed class DefibrillatorSystem : EntitySystem
|
||||
_audio.PlayPvs(component.ChargeSound, uid);
|
||||
return _doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, user, component.DoAfterDuration, new DefibrillatorZapDoAfterEvent(),
|
||||
uid, target, uid)
|
||||
{
|
||||
NeedHand = true,
|
||||
BreakOnMove = !component.AllowDoAfterMovement
|
||||
});
|
||||
{
|
||||
NeedHand = true,
|
||||
BreakOnMove = !component.AllowDoAfterMovement
|
||||
});
|
||||
}
|
||||
|
||||
public void Zap(EntityUid uid, EntityUid target, EntityUid user, DefibrillatorComponent? component = null, MobStateComponent? mob = null, MobThresholdsComponent? thresholds = null)
|
||||
/// <summary>
|
||||
/// Tries to defibrillate the target with the given defibrillator.
|
||||
/// </summary>
|
||||
public void Zap(EntityUid uid, EntityUid target, EntityUid user, DefibrillatorComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component) || !Resolve(target, ref mob, ref thresholds, false))
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
// clowns zap themselves
|
||||
if (HasComp<ClumsyComponent>(user) && user != target)
|
||||
{
|
||||
Zap(uid, user, user, component);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_powerCell.TryUseActivatableCharge(uid, user: user))
|
||||
return;
|
||||
|
||||
var selfEvent = new SelfBeforeDefibrillatorZapsEvent(user, uid, target);
|
||||
RaiseLocalEvent(user, selfEvent);
|
||||
|
||||
target = selfEvent.DefibTarget;
|
||||
|
||||
// Ensure thet new target is still valid.
|
||||
if (selfEvent.Cancelled || !CanZap(uid, target, user, component, true))
|
||||
return;
|
||||
|
||||
var targetEvent = new TargetBeforeDefibrillatorZapsEvent(user, uid, target);
|
||||
RaiseLocalEvent(target, targetEvent);
|
||||
|
||||
target = targetEvent.DefibTarget;
|
||||
|
||||
if (targetEvent.Cancelled || !CanZap(uid, target, user, component, true))
|
||||
return;
|
||||
|
||||
if (!TryComp<MobStateComponent>(target, out var mob) ||
|
||||
!TryComp<MobThresholdsComponent>(target, out var thresholds))
|
||||
return;
|
||||
|
||||
_audio.PlayPvs(component.ZapSound, uid);
|
||||
_electrocution.TryDoElectrocution(target, null, component.ZapDamage, component.WritheDuration, true, ignoreInsulation: true);
|
||||
component.NextZapTime = _timing.CurTime + component.ZapDelay;
|
||||
|
||||
@@ -4,7 +4,6 @@ using Content.Server.DeviceNetwork;
|
||||
using Content.Server.DeviceNetwork.Components;
|
||||
using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Server.Emp;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Medical.CrewMonitoring;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Station.Systems;
|
||||
@@ -14,8 +13,10 @@ using Content.Shared.Damage;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Medical.SuitSensor;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Verbs;
|
||||
@@ -383,7 +384,7 @@ public sealed class SuitSensorSystem : EntitySystem
|
||||
|
||||
// Get mob total damage crit threshold
|
||||
int? totalDamageThreshold = null;
|
||||
if (_mobThresholdSystem.TryGetThresholdForState(sensor.User.Value, Shared.Mobs.MobState.Critical, out var critThreshold))
|
||||
if (_mobThresholdSystem.TryGetThresholdForState(sensor.User.Value, MobState.Critical, out var critThreshold))
|
||||
totalDamageThreshold = critThreshold.Value.Int();
|
||||
|
||||
// finally, form suit sensor status
|
||||
|
||||
@@ -11,8 +11,8 @@ public sealed partial class PathfindingSystem
|
||||
/// </summary>
|
||||
public record struct BreadthPathArgs()
|
||||
{
|
||||
public Vector2i Start;
|
||||
public List<Vector2i> Ends;
|
||||
public required Vector2i Start;
|
||||
public required List<Vector2i> Ends;
|
||||
|
||||
public bool Diagonals = false;
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ public sealed partial class PathfindingSystem
|
||||
public List<Vector2i> Points = new();
|
||||
|
||||
public List<Vector2i> Path = new();
|
||||
public Dictionary<Vector2i, Vector2i> CameFrom;
|
||||
public Dictionary<Vector2i, Vector2i>? CameFrom;
|
||||
}
|
||||
|
||||
public record struct SplinePathArgs(SimplePathArgs Args)
|
||||
|
||||
@@ -84,6 +84,6 @@ public sealed partial class PathfindingSystem
|
||||
|
||||
public float MaxWiden = 7f;
|
||||
|
||||
public List<Vector2i> Path;
|
||||
public required List<Vector2i> Path;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,8 +122,7 @@ namespace Content.Server.Nutrition.EntitySystems
|
||||
|
||||
private void OnVapeDoAfter(Entity<VapeComponent> entity, ref VapeDoAfterEvent args)
|
||||
{
|
||||
if (args.Handled
|
||||
|| args.Args.Target == null)
|
||||
if (args.Cancelled || args.Handled || args.Args.Target == null)
|
||||
return;
|
||||
|
||||
var environment = _atmos.GetContainingMixture(args.Args.Target.Value, true, true);
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace Content.Server.Nutrition.EntitySystems
|
||||
public (bool Success, bool Handled) TryUseUtensil(EntityUid user, EntityUid target, Entity<UtensilComponent> utensil)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(target, out FoodComponent? food))
|
||||
return (false, true);
|
||||
return (false, false);
|
||||
|
||||
//Prevents food usage with a wrong utensil
|
||||
if ((food.Utensil & utensil.Comp.Types) == 0)
|
||||
|
||||
@@ -53,7 +53,7 @@ public sealed class KillPersonConditionSystem : EntitySystem
|
||||
return;
|
||||
|
||||
// no other humans to kill
|
||||
var allHumans = _mind.GetAliveHumansExcept(args.MindId);
|
||||
var allHumans = _mind.GetAliveHumans(args.MindId);
|
||||
if (allHumans.Count == 0)
|
||||
{
|
||||
args.Cancelled = true;
|
||||
@@ -77,14 +77,14 @@ public sealed class KillPersonConditionSystem : EntitySystem
|
||||
return;
|
||||
|
||||
// no other humans to kill
|
||||
var allHumans = _mind.GetAliveHumansExcept(args.MindId);
|
||||
var allHumans = _mind.GetAliveHumans(args.MindId);
|
||||
if (allHumans.Count == 0)
|
||||
{
|
||||
args.Cancelled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var allHeads = new List<EntityUid>();
|
||||
var allHeads = new HashSet<Entity<MindComponent>>();
|
||||
foreach (var person in allHumans)
|
||||
{
|
||||
if (TryComp<MindComponent>(person, out var mind) && mind.OwnedEntity is { } ent && HasComp<CommandStaffComponent>(ent))
|
||||
|
||||
@@ -314,6 +314,9 @@ public sealed class MoverController : SharedMoverController
|
||||
var linearInput = Vector2.Zero;
|
||||
var brakeInput = 0f;
|
||||
var angularInput = 0f;
|
||||
var linearCount = 0;
|
||||
var brakeCount = 0;
|
||||
var angularCount = 0;
|
||||
|
||||
foreach (var (pilotUid, pilot, _, consoleXform) in pilots)
|
||||
{
|
||||
@@ -322,24 +325,27 @@ public sealed class MoverController : SharedMoverController
|
||||
if (brakes > 0f)
|
||||
{
|
||||
brakeInput += brakes;
|
||||
brakeCount++;
|
||||
}
|
||||
|
||||
if (strafe.Length() > 0f)
|
||||
{
|
||||
var offsetRotation = consoleXform.LocalRotation;
|
||||
linearInput += offsetRotation.RotateVec(strafe);
|
||||
linearCount++;
|
||||
}
|
||||
|
||||
if (rotation != 0f)
|
||||
{
|
||||
angularInput += rotation;
|
||||
angularCount++;
|
||||
}
|
||||
}
|
||||
|
||||
var count = pilots.Count;
|
||||
linearInput /= count;
|
||||
angularInput /= count;
|
||||
brakeInput /= count;
|
||||
// Don't slow down the shuttle if there's someone just looking at the console
|
||||
linearInput /= Math.Max(1, linearCount);
|
||||
angularInput /= Math.Max(1, angularCount);
|
||||
brakeInput /= Math.Max(1, brakeCount);
|
||||
|
||||
// Handle shuttle movement
|
||||
if (brakeInput > 0f)
|
||||
|
||||
@@ -1,99 +1,5 @@
|
||||
using Content.Server.Polymorph.Components;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Construction.Components;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Polymorph;
|
||||
using Content.Shared.Polymorph.Components;
|
||||
using Content.Shared.Polymorph.Systems;
|
||||
using Content.Shared.StatusIcon.Components;
|
||||
using Robust.Shared.Physics.Components;
|
||||
|
||||
namespace Content.Server.Polymorph.Systems;
|
||||
|
||||
public sealed class ChameleonProjectorSystem : SharedChameleonProjectorSystem
|
||||
{
|
||||
[Dependency] private readonly MetaDataSystem _meta = default!;
|
||||
[Dependency] private readonly MobThresholdSystem _mobThreshold = default!;
|
||||
[Dependency] private readonly PolymorphSystem _polymorph = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _xform = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ChameleonDisguiseComponent, GotEquippedHandEvent>(OnEquippedHand);
|
||||
SubscribeLocalEvent<ChameleonDisguiseComponent, DisguiseToggleNoRotEvent>(OnToggleNoRot);
|
||||
SubscribeLocalEvent<ChameleonDisguiseComponent, DisguiseToggleAnchoredEvent>(OnToggleAnchored);
|
||||
}
|
||||
|
||||
private void OnEquippedHand(Entity<ChameleonDisguiseComponent> ent, ref GotEquippedHandEvent args)
|
||||
{
|
||||
if (!TryComp<PolymorphedEntityComponent>(ent, out var poly))
|
||||
return;
|
||||
|
||||
_polymorph.Revert((ent, poly));
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
public override void Disguise(ChameleonProjectorComponent proj, EntityUid user, EntityUid entity)
|
||||
{
|
||||
if (_polymorph.PolymorphEntity(user, proj.Polymorph) is not {} disguise)
|
||||
return;
|
||||
|
||||
// make disguise look real (for simple things at least)
|
||||
var meta = MetaData(entity);
|
||||
_meta.SetEntityName(disguise, meta.EntityName);
|
||||
_meta.SetEntityDescription(disguise, meta.EntityDescription);
|
||||
|
||||
var comp = EnsureComp<ChameleonDisguiseComponent>(disguise);
|
||||
comp.SourceEntity = entity;
|
||||
comp.SourceProto = Prototype(entity)?.ID;
|
||||
Dirty(disguise, comp);
|
||||
|
||||
// no sechud trolling
|
||||
RemComp<StatusIconComponent>(disguise);
|
||||
|
||||
_appearance.CopyData(entity, disguise);
|
||||
|
||||
var mass = CompOrNull<PhysicsComponent>(entity)?.Mass ?? 0f;
|
||||
|
||||
// let the disguise die when its taken enough damage, which then transfers to the player
|
||||
// health is proportional to mass, and capped to not be insane
|
||||
if (TryComp<MobThresholdsComponent>(disguise, out var thresholds))
|
||||
{
|
||||
// if the player is of flesh and blood, cap max health to theirs
|
||||
// so that when reverting damage scales 1:1 and not round removing
|
||||
var playerMax = _mobThreshold.GetThresholdForState(user, MobState.Dead).Float();
|
||||
var max = playerMax == 0f ? proj.MaxHealth : Math.Max(proj.MaxHealth, playerMax);
|
||||
|
||||
var health = Math.Clamp(mass, proj.MinHealth, proj.MaxHealth);
|
||||
_mobThreshold.SetMobStateThreshold(disguise, health, MobState.Critical, thresholds);
|
||||
_mobThreshold.SetMobStateThreshold(disguise, max, MobState.Dead, thresholds);
|
||||
}
|
||||
|
||||
// add actions for controlling transform aspects
|
||||
_actions.AddAction(disguise, proj.NoRotAction);
|
||||
_actions.AddAction(disguise, proj.AnchorAction);
|
||||
}
|
||||
|
||||
private void OnToggleNoRot(Entity<ChameleonDisguiseComponent> ent, ref DisguiseToggleNoRotEvent args)
|
||||
{
|
||||
var xform = Transform(ent);
|
||||
xform.NoLocalRotation = !xform.NoLocalRotation;
|
||||
}
|
||||
|
||||
private void OnToggleAnchored(Entity<ChameleonDisguiseComponent> ent, ref DisguiseToggleAnchoredEvent args)
|
||||
{
|
||||
var uid = ent.Owner;
|
||||
var xform = Transform(uid);
|
||||
if (xform.Anchored)
|
||||
_xform.Unanchor(uid, xform);
|
||||
else
|
||||
_xform.AnchorEntity((uid, xform));
|
||||
}
|
||||
}
|
||||
public sealed class ChameleonProjectorSystem : SharedChameleonProjectorSystem;
|
||||
|
||||
@@ -199,6 +199,9 @@ public sealed partial class PolymorphSystem : EntitySystem
|
||||
|
||||
var targetTransformComp = Transform(uid);
|
||||
|
||||
if (configuration.PolymorphSound != null)
|
||||
_audio.PlayPvs(configuration.PolymorphSound, targetTransformComp.Coordinates);
|
||||
|
||||
var child = Spawn(configuration.Entity, _transform.GetMapCoordinates(uid, targetTransformComp), rotation: _transform.GetWorldRotation(uid));
|
||||
|
||||
MakeSentientCommand.MakeSentient(child, EntityManager);
|
||||
@@ -288,6 +291,9 @@ public sealed partial class PolymorphSystem : EntitySystem
|
||||
var uidXform = Transform(uid);
|
||||
var parentXform = Transform(parent);
|
||||
|
||||
if (component.Configuration.ExitPolymorphSound != null)
|
||||
_audio.PlayPvs(component.Configuration.ExitPolymorphSound, uidXform.Coordinates);
|
||||
|
||||
_transform.SetParent(parent, parentXform, uidXform.ParentUid);
|
||||
_transform.SetCoordinates(parent, parentXform, uidXform.Coordinates, uidXform.LocalRotation);
|
||||
|
||||
|
||||
@@ -31,6 +31,12 @@ public sealed partial class DungeonJob
|
||||
|
||||
if (reservedTiles.Contains(node))
|
||||
continue;
|
||||
|
||||
if (dunGen.TileMask is not null)
|
||||
{
|
||||
if (!dunGen.TileMask.Contains(((ContentTileDefinition) _tileDefManager[tileRef.Value.Tile.TypeId]).ID))
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dunGen.TileMask is not null)
|
||||
{
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace Content.Server.Research.Disk
|
||||
_research.ModifyServerPoints(args.Target.Value, component.Points, server);
|
||||
_popupSystem.PopupEntity(Loc.GetString("research-disk-inserted", ("points", component.Points)), args.Target.Value, args.User);
|
||||
EntityManager.QueueDeleteEntity(uid);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, ResearchDiskComponent component, MapInitEvent args)
|
||||
|
||||
@@ -28,7 +28,7 @@ public sealed partial class SalvageSystem
|
||||
var mission = GetMission(_prototypeManager.Index<SalvageDifficultyPrototype>(missionparams.Difficulty), missionparams.Seed);
|
||||
data.NextOffer = _timing.CurTime + mission.Duration + TimeSpan.FromSeconds(1);
|
||||
|
||||
_labelSystem.Label(cdUid, GetFTLName(_prototypeManager.Index<DatasetPrototype>("names_borer"), missionparams.Seed));
|
||||
_labelSystem.Label(cdUid, GetFTLName(_prototypeManager.Index<LocalizedDatasetPrototype>("NamesBorer"), missionparams.Seed));
|
||||
_audio.PlayPvs(component.PrintSound, uid);
|
||||
|
||||
UpdateConsoles((station.Value, data));
|
||||
|
||||
@@ -104,7 +104,9 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
|
||||
destComp.BeaconsOnly = true;
|
||||
destComp.RequireCoordinateDisk = true;
|
||||
destComp.Enabled = true;
|
||||
_metaData.SetEntityName(mapUid, SharedSalvageSystem.GetFTLName(_prototypeManager.Index<DatasetPrototype>("names_borer"), _missionParams.Seed));
|
||||
_metaData.SetEntityName(
|
||||
mapUid,
|
||||
_entManager.System<SharedSalvageSystem>().GetFTLName(_prototypeManager.Index<LocalizedDatasetPrototype>("NamesBorer"), _missionParams.Seed));
|
||||
_entManager.AddComponent<FTLBeaconComponent>(mapUid);
|
||||
|
||||
// Saving the mission mapUid to a CD is made optional, in case one is somehow made in a process without a CD entity
|
||||
|
||||
@@ -13,11 +13,12 @@ public sealed class ServerInfoManager
|
||||
private static readonly (CVarDef<string> cVar, string icon, string name)[] Vars =
|
||||
{
|
||||
// @formatter:off
|
||||
(CCVars.InfoLinksDiscord, "discord", "info-link-discord"),
|
||||
(CCVars.InfoLinksForum, "forum", "info-link-forum"),
|
||||
(CCVars.InfoLinksGithub, "github", "info-link-github"),
|
||||
(CCVars.InfoLinksWebsite, "web", "info-link-website"),
|
||||
(CCVars.InfoLinksWiki, "wiki", "info-link-wiki")
|
||||
(CCVars.InfoLinksDiscord, "discord", "info-link-discord"),
|
||||
(CCVars.InfoLinksForum, "forum", "info-link-forum"),
|
||||
(CCVars.InfoLinksGithub, "github", "info-link-github"),
|
||||
(CCVars.InfoLinksWebsite, "web", "info-link-website"),
|
||||
(CCVars.InfoLinksWiki, "wiki", "info-link-wiki"),
|
||||
(CCVars.InfoLinksTelegram, "telegram", "info-link-telegram")
|
||||
// @formatter:on
|
||||
};
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ public interface IGridSpawnGroup
|
||||
public float MaximumDistance { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public ProtoId<DatasetPrototype>? NameDataset { get; }
|
||||
public ProtoId<LocalizedDatasetPrototype>? NameDataset { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
int MinCount { get; set; }
|
||||
@@ -75,7 +75,7 @@ public sealed class DungeonSpawnGroup : IGridSpawnGroup
|
||||
public float MaximumDistance { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public ProtoId<DatasetPrototype>? NameDataset { get; }
|
||||
public ProtoId<LocalizedDatasetPrototype>? NameDataset { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public int MinCount { get; set; } = 1;
|
||||
@@ -106,7 +106,7 @@ public sealed class GridSpawnGroup : IGridSpawnGroup
|
||||
|
||||
/// <inheritdoc />
|
||||
public float MaximumDistance { get; }
|
||||
public ProtoId<DatasetPrototype>? NameDataset { get; }
|
||||
public ProtoId<LocalizedDatasetPrototype>? NameDataset { get; }
|
||||
public int MinCount { get; set; } = 1;
|
||||
public int MaxCount { get; set; } = 1;
|
||||
public ComponentRegistry AddComponents { get; set; } = new();
|
||||
|
||||
@@ -19,10 +19,10 @@ using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Parallax.Biomes;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Salvage;
|
||||
using Content.Shared.Shuttles.Components;
|
||||
using Content.Shared.Tiles;
|
||||
|
||||
@@ -208,7 +208,7 @@ public sealed partial class ShuttleSystem
|
||||
|
||||
if (_protoManager.TryIndex(group.NameDataset, out var dataset))
|
||||
{
|
||||
_metadata.SetEntityName(spawned, SharedSalvageSystem.GetFTLName(dataset, _random.Next()));
|
||||
_metadata.SetEntityName(spawned, _salvage.GetFTLName(dataset, _random.Next()));
|
||||
}
|
||||
|
||||
if (group.Hide)
|
||||
|
||||
@@ -9,6 +9,7 @@ using Content.Server.Station.Systems;
|
||||
using Content.Server.Stunnable;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Salvage;
|
||||
using Content.Shared.Shuttles.Systems;
|
||||
using Content.Shared.Throwing;
|
||||
using JetBrains.Annotations;
|
||||
@@ -52,6 +53,7 @@ public sealed partial class ShuttleSystem : SharedShuttleSystem
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly SharedSalvageSystem _salvage = default!;
|
||||
[Dependency] private readonly ShuttleConsoleSystem _console = default!;
|
||||
[Dependency] private readonly StationSystem _station = default!;
|
||||
[Dependency] private readonly StunSystem _stuns = default!;
|
||||
|
||||
82
Content.Server/Silicons/Borgs/BorgSwitchableTypeSystem.cs
Normal file
82
Content.Server/Silicons/Borgs/BorgSwitchableTypeSystem.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using Content.Server.Inventory;
|
||||
using Content.Server.Radio.Components;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Silicons.Borgs;
|
||||
using Content.Shared.Silicons.Borgs.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Silicons.Borgs;
|
||||
|
||||
/// <summary>
|
||||
/// Server-side logic for borg type switching. Handles more heavyweight and server-specific switching logic.
|
||||
/// </summary>
|
||||
public sealed class BorgSwitchableTypeSystem : SharedBorgSwitchableTypeSystem
|
||||
{
|
||||
[Dependency] private readonly BorgSystem _borgSystem = default!;
|
||||
[Dependency] private readonly ServerInventorySystem _inventorySystem = default!;
|
||||
|
||||
protected override void SelectBorgModule(Entity<BorgSwitchableTypeComponent> ent, ProtoId<BorgTypePrototype> borgType)
|
||||
{
|
||||
var prototype = Prototypes.Index(borgType);
|
||||
|
||||
// Assign radio channels
|
||||
string[] radioChannels = [.. ent.Comp.InherentRadioChannels, .. prototype.RadioChannels];
|
||||
if (TryComp(ent, out IntrinsicRadioTransmitterComponent? transmitter))
|
||||
transmitter.Channels = [.. radioChannels];
|
||||
|
||||
if (TryComp(ent, out ActiveRadioComponent? activeRadio))
|
||||
activeRadio.Channels = [.. radioChannels];
|
||||
|
||||
// Borg transponder for the robotics console
|
||||
if (TryComp(ent, out BorgTransponderComponent? transponder))
|
||||
{
|
||||
_borgSystem.SetTransponderSprite(
|
||||
(ent.Owner, transponder),
|
||||
new SpriteSpecifier.Rsi(new ResPath("Mobs/Silicon/chassis.rsi"), prototype.SpriteBodyState));
|
||||
|
||||
_borgSystem.SetTransponderName(
|
||||
(ent.Owner, transponder),
|
||||
Loc.GetString($"borg-type-{borgType}-transponder"));
|
||||
}
|
||||
|
||||
// Configure modules
|
||||
if (TryComp(ent, out BorgChassisComponent? chassis))
|
||||
{
|
||||
var chassisEnt = (ent.Owner, chassis);
|
||||
_borgSystem.SetMaxModules(
|
||||
chassisEnt,
|
||||
prototype.ExtraModuleCount + prototype.DefaultModules.Length);
|
||||
|
||||
_borgSystem.SetModuleWhitelist(chassisEnt, prototype.ModuleWhitelist);
|
||||
|
||||
foreach (var module in prototype.DefaultModules)
|
||||
{
|
||||
var moduleEntity = Spawn(module);
|
||||
var borgModule = Comp<BorgModuleComponent>(moduleEntity);
|
||||
_borgSystem.SetBorgModuleDefault((moduleEntity, borgModule), true);
|
||||
_borgSystem.InsertModule(chassisEnt, moduleEntity);
|
||||
}
|
||||
}
|
||||
|
||||
// Configure special components
|
||||
if (Prototypes.TryIndex(ent.Comp.SelectedBorgType, out var previousPrototype))
|
||||
{
|
||||
if (previousPrototype.AddComponents is { } removeComponents)
|
||||
EntityManager.RemoveComponents(ent, removeComponents);
|
||||
}
|
||||
|
||||
if (prototype.AddComponents is { } addComponents)
|
||||
{
|
||||
EntityManager.AddComponents(ent, addComponents);
|
||||
}
|
||||
|
||||
// Configure inventory template (used for hat spacing)
|
||||
if (TryComp(ent, out InventoryComponent? inventory))
|
||||
{
|
||||
_inventorySystem.SetTemplateId((ent.Owner, inventory), prototype.InventoryTemplateId);
|
||||
}
|
||||
|
||||
base.SelectBorgModule(ent, borgType);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ using System.Linq;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Interaction.Components;
|
||||
using Content.Shared.Silicons.Borgs.Components;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Server.Silicons.Borgs;
|
||||
@@ -300,6 +301,24 @@ public sealed partial class BorgSystem
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a module can be removed from a borg.
|
||||
/// </summary>
|
||||
/// <param name="borg">The borg that the module is being removed from.</param>
|
||||
/// <param name="module">The module to remove from the borg.</param>
|
||||
/// <param name="user">The user attempting to remove the module.</param>
|
||||
/// <returns>True if the module can be removed.</returns>
|
||||
public bool CanRemoveModule(
|
||||
Entity<BorgChassisComponent> borg,
|
||||
Entity<BorgModuleComponent> module,
|
||||
EntityUid? user = null)
|
||||
{
|
||||
if (module.Comp.DefaultModule)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Installs and activates all modules currently inside the borg's module container
|
||||
/// </summary>
|
||||
@@ -369,4 +388,24 @@ public sealed partial class BorgSystem
|
||||
var ev = new BorgModuleUninstalledEvent(uid);
|
||||
RaiseLocalEvent(module, ref ev);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="BorgChassisComponent.MaxModules"/>.
|
||||
/// </summary>
|
||||
/// <param name="ent">The borg to modify.</param>
|
||||
/// <param name="maxModules">The new max module count.</param>
|
||||
public void SetMaxModules(Entity<BorgChassisComponent> ent, int maxModules)
|
||||
{
|
||||
ent.Comp.MaxModules = maxModules;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="BorgChassisComponent.ModuleWhitelist"/>.
|
||||
/// </summary>
|
||||
/// <param name="ent">The borg to modify.</param>
|
||||
/// <param name="whitelist">The new module whitelist.</param>
|
||||
public void SetModuleWhitelist(Entity<BorgChassisComponent> ent, EntityWhitelist? whitelist)
|
||||
{
|
||||
ent.Comp.ModuleWhitelist = whitelist;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using Content.Server.DeviceNetwork;
|
||||
using Content.Server.DeviceNetwork.Components;
|
||||
using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Server.Explosion.Components;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Silicons.Borgs;
|
||||
|
||||
@@ -134,4 +135,20 @@ public sealed partial class BorgSystem
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="BorgTransponderComponent.Sprite"/>.
|
||||
/// </summary>
|
||||
public void SetTransponderSprite(Entity<BorgTransponderComponent> ent, SpriteSpecifier sprite)
|
||||
{
|
||||
ent.Comp.Sprite = sprite;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="BorgTransponderComponent.Name"/>.
|
||||
/// </summary>
|
||||
public void SetTransponderName(Entity<BorgTransponderComponent> ent, string name)
|
||||
{
|
||||
ent.Comp.Name = name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,9 @@ public sealed partial class BorgSystem
|
||||
if (!component.ModuleContainer.Contains(module))
|
||||
return;
|
||||
|
||||
if (!CanRemoveModule((uid, component), (module, Comp<BorgModuleComponent>(module)), args.Actor))
|
||||
return;
|
||||
|
||||
_adminLog.Add(LogType.Action, LogImpact.Medium,
|
||||
$"{ToPrettyString(args.Actor):player} removed module {ToPrettyString(module)} from borg {ToPrettyString(uid)}");
|
||||
_container.Remove(module, component.ModuleContainer);
|
||||
|
||||
@@ -129,7 +129,7 @@ public sealed partial class BorgSystem : SharedBorgSystem
|
||||
|
||||
if (module != null && CanInsertModule(uid, used, component, module, args.User))
|
||||
{
|
||||
_container.Insert(used, component.ModuleContainer);
|
||||
InsertModule((uid, component), used);
|
||||
_adminLog.Add(LogType.Action, LogImpact.Low,
|
||||
$"{ToPrettyString(args.User):player} installed module {ToPrettyString(used)} into borg {ToPrettyString(uid)}");
|
||||
args.Handled = true;
|
||||
@@ -137,6 +137,19 @@ public sealed partial class BorgSystem : SharedBorgSystem
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts a new module into a borg, the same as if a player inserted it manually.
|
||||
/// </summary>
|
||||
/// <para>
|
||||
/// This does not run checks to see if the borg is actually allowed to be inserted, such as whitelists.
|
||||
/// </para>
|
||||
/// <param name="ent">The borg to insert into.</param>
|
||||
/// <param name="module">The module to insert.</param>
|
||||
public void InsertModule(Entity<BorgChassisComponent> ent, EntityUid module)
|
||||
{
|
||||
_container.Insert(module, ent.Comp.ModuleContainer);
|
||||
}
|
||||
|
||||
// todo: consider transferring over the ghost role? managing that might suck.
|
||||
protected override void OnInserted(EntityUid uid, BorgChassisComponent component, EntInsertedIntoContainerMessage args)
|
||||
{
|
||||
|
||||
280
Content.Server/Silicons/Laws/IonStormSystem.cs
Normal file
280
Content.Server/Silicons/Laws/IonStormSystem.cs
Normal file
@@ -0,0 +1,280 @@
|
||||
using Content.Server.StationEvents.Components;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Dataset;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.GameTicking.Components;
|
||||
using Content.Shared.Random;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using Content.Shared.Silicons.Laws;
|
||||
using Content.Shared.Silicons.Laws.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Server.Silicons.Laws;
|
||||
|
||||
public sealed class IonStormSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly SiliconLawSystem _siliconLaw = default!;
|
||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||
|
||||
// funny
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
private const string Threats = "IonStormThreats";
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
private const string Objects = "IonStormObjects";
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
private const string Crew = "IonStormCrew";
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
private const string Adjectives = "IonStormAdjectives";
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
private const string Verbs = "IonStormVerbs";
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
private const string NumberBase = "IonStormNumberBase";
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
private const string NumberMod = "IonStormNumberMod";
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
private const string Areas = "IonStormAreas";
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
private const string Feelings = "IonStormFeelings";
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
private const string FeelingsPlural = "IonStormFeelingsPlural";
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
private const string Musts = "IonStormMusts";
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
private const string Requires = "IonStormRequires";
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
private const string Actions = "IonStormActions";
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
private const string Allergies = "IonStormAllergies";
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
private const string AllergySeverities = "IonStormAllergySeverities";
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
private const string Concepts = "IonStormConcepts";
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
private const string Drinks = "IonStormDrinks";
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
private const string Foods = "IonStormFoods";
|
||||
|
||||
/// <summary>
|
||||
/// Randomly alters the laws of an individual silicon.
|
||||
/// </summary>
|
||||
public void IonStormTarget(Entity<SiliconLawBoundComponent, IonStormTargetComponent> ent, bool adminlog = true)
|
||||
{
|
||||
var lawBound = ent.Comp1;
|
||||
var target = ent.Comp2;
|
||||
if (!_robustRandom.Prob(target.Chance))
|
||||
return;
|
||||
|
||||
var laws = _siliconLaw.GetLaws(ent, lawBound);
|
||||
if (laws.Laws.Count == 0)
|
||||
return;
|
||||
|
||||
// try to swap it out with a random lawset
|
||||
if (_robustRandom.Prob(target.RandomLawsetChance))
|
||||
{
|
||||
var lawsets = _proto.Index<WeightedRandomPrototype>(target.RandomLawsets);
|
||||
var lawset = lawsets.Pick(_robustRandom);
|
||||
laws = _siliconLaw.GetLawset(lawset);
|
||||
}
|
||||
// clone it so not modifying stations lawset
|
||||
laws = laws.Clone();
|
||||
|
||||
// shuffle them all
|
||||
if (_robustRandom.Prob(target.ShuffleChance))
|
||||
{
|
||||
// hopefully work with existing glitched laws if there are multiple ion storms
|
||||
var baseOrder = FixedPoint2.New(1);
|
||||
foreach (var law in laws.Laws)
|
||||
{
|
||||
if (law.Order < baseOrder)
|
||||
baseOrder = law.Order;
|
||||
}
|
||||
|
||||
_robustRandom.Shuffle(laws.Laws);
|
||||
|
||||
// change order based on shuffled position
|
||||
for (int i = 0; i < laws.Laws.Count; i++)
|
||||
{
|
||||
laws.Laws[i].Order = baseOrder + i;
|
||||
}
|
||||
}
|
||||
|
||||
// see if we can remove a random law
|
||||
if (laws.Laws.Count > 0 && _robustRandom.Prob(target.RemoveChance))
|
||||
{
|
||||
var i = _robustRandom.Next(laws.Laws.Count);
|
||||
laws.Laws.RemoveAt(i);
|
||||
}
|
||||
|
||||
// generate a new law...
|
||||
var newLaw = GenerateLaw();
|
||||
|
||||
// see if the law we add will replace a random existing law or be a new glitched order one
|
||||
if (laws.Laws.Count > 0 && _robustRandom.Prob(target.ReplaceChance))
|
||||
{
|
||||
var i = _robustRandom.Next(laws.Laws.Count);
|
||||
laws.Laws[i] = new SiliconLaw()
|
||||
{
|
||||
LawString = newLaw,
|
||||
Order = laws.Laws[i].Order
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
laws.Laws.Insert(0, new SiliconLaw
|
||||
{
|
||||
LawString = newLaw,
|
||||
Order = -1,
|
||||
LawIdentifierOverride = Loc.GetString("ion-storm-law-scrambled-number", ("length", _robustRandom.Next(5, 10)))
|
||||
});
|
||||
}
|
||||
|
||||
// sets all unobfuscated laws' indentifier in order from highest to lowest priority
|
||||
// This could technically override the Obfuscation from the code above, but it seems unlikely enough to basically never happen
|
||||
int orderDeduction = -1;
|
||||
|
||||
for (int i = 0; i < laws.Laws.Count; i++)
|
||||
{
|
||||
var notNullIdentifier = laws.Laws[i].LawIdentifierOverride ?? (i - orderDeduction).ToString();
|
||||
|
||||
if (notNullIdentifier.Any(char.IsSymbol))
|
||||
{
|
||||
orderDeduction += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
laws.Laws[i].LawIdentifierOverride = (i - orderDeduction).ToString();
|
||||
}
|
||||
}
|
||||
|
||||
// adminlog is used to prevent adminlog spam.
|
||||
if (adminlog)
|
||||
_adminLogger.Add(LogType.Mind, LogImpact.High, $"{ToPrettyString(ent):silicon} had its laws changed by an ion storm to {laws.LoggingString()}");
|
||||
|
||||
// laws unique to this silicon, dont use station laws anymore
|
||||
EnsureComp<SiliconLawProviderComponent>(ent);
|
||||
var ev = new IonStormLawsEvent(laws);
|
||||
RaiseLocalEvent(ent, ref ev);
|
||||
}
|
||||
|
||||
// for your own sake direct your eyes elsewhere
|
||||
private string GenerateLaw()
|
||||
{
|
||||
// pick all values ahead of time to make the logic cleaner
|
||||
var threats = Pick(Threats);
|
||||
var objects = Pick(Objects);
|
||||
var crew1 = Pick(Crew);
|
||||
var crew2 = Pick(Crew);
|
||||
var adjective = Pick(Adjectives);
|
||||
var verb = Pick(Verbs);
|
||||
var number = Pick(NumberBase) + " " + Pick(NumberMod);
|
||||
var area = Pick(Areas);
|
||||
var feeling = Pick(Feelings);
|
||||
var feelingPlural = Pick(FeelingsPlural);
|
||||
var must = Pick(Musts);
|
||||
var require = Pick(Requires);
|
||||
var action = Pick(Actions);
|
||||
var allergy = Pick(Allergies);
|
||||
var allergySeverity = Pick(AllergySeverities);
|
||||
var concept = Pick(Concepts);
|
||||
var drink = Pick(Drinks);
|
||||
var food = Pick(Foods);
|
||||
|
||||
var joined = $"{number} {adjective}";
|
||||
// a lot of things have subjects of a threat/crew/object
|
||||
var triple = _robustRandom.Next(0, 3) switch
|
||||
{
|
||||
0 => threats,
|
||||
1 => crew1,
|
||||
2 => objects,
|
||||
_ => throw new IndexOutOfRangeException(),
|
||||
};
|
||||
var crewAll = _robustRandom.Prob(0.5f) ? crew2 : Loc.GetString("ion-storm-crew");
|
||||
var objectsThreats = _robustRandom.Prob(0.5f) ? objects : threats;
|
||||
var objectsConcept = _robustRandom.Prob(0.5f) ? objects : concept;
|
||||
// s goes ahead of require, is/are
|
||||
// i dont think theres a way to do this in fluent
|
||||
var (who, plural) = _robustRandom.Next(0, 5) switch
|
||||
{
|
||||
0 => (Loc.GetString("ion-storm-you"), false),
|
||||
1 => (Loc.GetString("ion-storm-the-station"), true),
|
||||
2 => (Loc.GetString("ion-storm-the-crew"), true),
|
||||
3 => (Loc.GetString("ion-storm-the-job", ("job", crew2)), false),
|
||||
_ => (area, true) // THE SINGULARITY REQUIRES THE HAPPY CLOWNS
|
||||
};
|
||||
var jobChange = _robustRandom.Next(0, 3) switch
|
||||
{
|
||||
0 => crew1,
|
||||
1 => Loc.GetString("ion-storm-clowns"),
|
||||
_ => Loc.GetString("ion-storm-heads")
|
||||
};
|
||||
var part = Loc.GetString("ion-storm-part", ("part", _robustRandom.Prob(0.5f)));
|
||||
var harm = _robustRandom.Next(0, 6) switch
|
||||
{
|
||||
0 => concept,
|
||||
1 => $"{adjective} {threats}",
|
||||
2 => $"{adjective} {objects}",
|
||||
3 => Loc.GetString("ion-storm-adjective-things", ("adjective", adjective)),
|
||||
4 => crew1,
|
||||
_ => Loc.GetString("ion-storm-x-and-y", ("x", crew1), ("y", crew2))
|
||||
};
|
||||
|
||||
if (plural) feeling = feelingPlural;
|
||||
|
||||
var subjects = _robustRandom.Prob(0.5f) ? objectsThreats : Loc.GetString("ion-storm-people");
|
||||
|
||||
// message logic!!!
|
||||
return _robustRandom.Next(0, 35) switch
|
||||
{
|
||||
0 => Loc.GetString("ion-storm-law-on-station", ("joined", joined), ("subjects", triple)),
|
||||
1 => Loc.GetString("ion-storm-law-no-shuttle", ("joined", joined), ("subjects", triple)),
|
||||
2 => Loc.GetString("ion-storm-law-crew-are", ("who", crewAll), ("joined", joined), ("subjects", objectsThreats)),
|
||||
3 => Loc.GetString("ion-storm-law-subjects-harmful", ("adjective", adjective), ("subjects", triple)),
|
||||
4 => Loc.GetString("ion-storm-law-must-harmful", ("must", must)),
|
||||
5 => Loc.GetString("ion-storm-law-thing-harmful", ("thing", _robustRandom.Prob(0.5f) ? concept : action)),
|
||||
6 => Loc.GetString("ion-storm-law-job-harmful", ("adjective", adjective), ("job", crew1)),
|
||||
7 => Loc.GetString("ion-storm-law-having-harmful", ("adjective", adjective), ("thing", objectsConcept)),
|
||||
8 => Loc.GetString("ion-storm-law-not-having-harmful", ("adjective", adjective), ("thing", objectsConcept)),
|
||||
9 => Loc.GetString("ion-storm-law-requires", ("who", who), ("plural", plural), ("thing", _robustRandom.Prob(0.5f) ? concept : require)),
|
||||
10 => Loc.GetString("ion-storm-law-requires-subjects", ("who", who), ("plural", plural), ("joined", joined), ("subjects", triple)),
|
||||
11 => Loc.GetString("ion-storm-law-allergic", ("who", who), ("plural", plural), ("severity", allergySeverity), ("allergy", _robustRandom.Prob(0.5f) ? concept : allergy)),
|
||||
12 => Loc.GetString("ion-storm-law-allergic-subjects", ("who", who), ("plural", plural), ("severity", allergySeverity), ("adjective", adjective), ("subjects", _robustRandom.Prob(0.5f) ? objects : crew1)),
|
||||
13 => Loc.GetString("ion-storm-law-feeling", ("who", who), ("feeling", feeling), ("concept", concept)),
|
||||
14 => Loc.GetString("ion-storm-law-feeling-subjects", ("who", who), ("feeling", feeling), ("joined", joined), ("subjects", triple)),
|
||||
15 => Loc.GetString("ion-storm-law-you-are", ("concept", concept)),
|
||||
16 => Loc.GetString("ion-storm-law-you-are-subjects", ("joined", joined), ("subjects", triple)),
|
||||
17 => Loc.GetString("ion-storm-law-you-must-always", ("must", must)),
|
||||
18 => Loc.GetString("ion-storm-law-you-must-never", ("must", must)),
|
||||
19 => Loc.GetString("ion-storm-law-eat", ("who", crewAll), ("adjective", adjective), ("food", _robustRandom.Prob(0.5f) ? food : triple)),
|
||||
20 => Loc.GetString("ion-storm-law-drink", ("who", crewAll), ("adjective", adjective), ("drink", drink)),
|
||||
21 => Loc.GetString("ion-storm-law-change-job", ("who", crewAll), ("adjective", adjective), ("change", jobChange)),
|
||||
22 => Loc.GetString("ion-storm-law-highest-rank", ("who", crew1)),
|
||||
23 => Loc.GetString("ion-storm-law-lowest-rank", ("who", crew1)),
|
||||
24 => Loc.GetString("ion-storm-law-crew-must", ("who", crewAll), ("must", must)),
|
||||
25 => Loc.GetString("ion-storm-law-crew-must-go", ("who", crewAll), ("area", area)),
|
||||
26 => Loc.GetString("ion-storm-law-crew-only-1", ("who", crew1), ("part", part)),
|
||||
27 => Loc.GetString("ion-storm-law-crew-only-2", ("who", crew1), ("other", crew2), ("part", part)),
|
||||
28 => Loc.GetString("ion-storm-law-crew-only-subjects", ("adjective", adjective), ("subjects", subjects), ("part", part)),
|
||||
29 => Loc.GetString("ion-storm-law-crew-must-do", ("must", must), ("part", part)),
|
||||
30 => Loc.GetString("ion-storm-law-crew-must-have", ("adjective", adjective), ("objects", objects), ("part", part)),
|
||||
31 => Loc.GetString("ion-storm-law-crew-must-eat", ("who", who), ("adjective", adjective), ("food", food), ("part", part)),
|
||||
32 => Loc.GetString("ion-storm-law-harm", ("who", harm)),
|
||||
33 => Loc.GetString("ion-storm-law-protect", ("who", harm)),
|
||||
_ => Loc.GetString("ion-storm-law-concept-verb", ("concept", concept), ("verb", verb), ("subjects", triple))
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Picks a random value from an ion storm dataset.
|
||||
/// All ion storm datasets start with IonStorm.
|
||||
/// </summary>
|
||||
private string Pick(string name)
|
||||
{
|
||||
var dataset = _proto.Index<DatasetPrototype>(name);
|
||||
return _robustRandom.Pick(dataset.Values);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Administration;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Radio.Components;
|
||||
using Content.Server.Roles;
|
||||
using Content.Server.Station.Systems;
|
||||
@@ -9,6 +8,7 @@ using Content.Shared.Administration;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Emag.Components;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Roles;
|
||||
@@ -17,12 +17,12 @@ using Content.Shared.Silicons.Laws.Components;
|
||||
using Content.Shared.Stunnable;
|
||||
using Content.Shared.Wires;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Toolshed;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.Silicons.Laws;
|
||||
|
||||
@@ -50,9 +50,9 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
|
||||
|
||||
SubscribeLocalEvent<SiliconLawProviderComponent, GetSiliconLawsEvent>(OnDirectedGetLaws);
|
||||
SubscribeLocalEvent<SiliconLawProviderComponent, IonStormLawsEvent>(OnIonStormLaws);
|
||||
SubscribeLocalEvent<SiliconLawProviderComponent, MindAddedMessage>(OnLawProviderMindAdded);
|
||||
SubscribeLocalEvent<SiliconLawProviderComponent, MindRemovedMessage>(OnLawProviderMindRemoved);
|
||||
SubscribeLocalEvent<SiliconLawProviderComponent, GotEmaggedEvent>(OnEmagLawsAdded);
|
||||
SubscribeLocalEvent<EmagSiliconLawComponent, MindAddedMessage>(OnEmagMindAdded);
|
||||
SubscribeLocalEvent<EmagSiliconLawComponent, MindRemovedMessage>(OnEmagMindRemoved);
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, SiliconLawBoundComponent component, MapInitEvent args)
|
||||
@@ -67,10 +67,35 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
|
||||
|
||||
var msg = Loc.GetString("laws-notify");
|
||||
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", msg));
|
||||
_chatManager.ChatMessageToOne(ChatChannel.Server, msg, wrappedMessage, default, false,
|
||||
actor.PlayerSession.Channel, colorOverride: Color.FromHex("#2ed2fd"));
|
||||
_chatManager.ChatMessageToOne(ChatChannel.Server, msg, wrappedMessage, default, false, actor.PlayerSession.Channel, colorOverride: Color.FromHex("#2ed2fd"));
|
||||
|
||||
if (!TryComp<SiliconLawProviderComponent>(uid, out var lawcomp))
|
||||
return;
|
||||
|
||||
if (!lawcomp.Subverted)
|
||||
return;
|
||||
|
||||
var modifedLawMsg = Loc.GetString("laws-notify-subverted");
|
||||
var modifiedLawWrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", modifedLawMsg));
|
||||
_chatManager.ChatMessageToOne(ChatChannel.Server, modifedLawMsg, modifiedLawWrappedMessage, default, false, actor.PlayerSession.Channel, colorOverride: Color.Red);
|
||||
}
|
||||
|
||||
private void OnLawProviderMindAdded(Entity<SiliconLawProviderComponent> ent, ref MindAddedMessage args)
|
||||
{
|
||||
if (!ent.Comp.Subverted)
|
||||
return;
|
||||
EnsureSubvertedSiliconRole(args.Mind);
|
||||
}
|
||||
|
||||
private void OnLawProviderMindRemoved(Entity<SiliconLawProviderComponent> ent, ref MindRemovedMessage args)
|
||||
{
|
||||
if (!ent.Comp.Subverted)
|
||||
return;
|
||||
RemoveSubvertedSiliconRole(args.Mind);
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void OnToggleLawsScreen(EntityUid uid, SiliconLawBoundComponent component, ToggleLawsScreenEvent args)
|
||||
{
|
||||
if (args.Handled || !TryComp<ActorComponent>(uid, out var actor))
|
||||
@@ -117,9 +142,12 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
|
||||
// gotta tell player to check their laws
|
||||
NotifyLawsChanged(uid, component.LawUploadSound);
|
||||
|
||||
// Show the silicon has been subverted.
|
||||
component.Subverted = true;
|
||||
|
||||
// new laws may allow antagonist behaviour so make it clear for admins
|
||||
if (TryComp<EmagSiliconLawComponent>(uid, out var emag))
|
||||
EnsureEmaggedRole(uid, emag);
|
||||
if(_mind.TryGetMind(uid, out var mindId, out _))
|
||||
EnsureSubvertedSiliconRole(mindId);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -130,6 +158,9 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
|
||||
if (component.Lawset == null)
|
||||
component.Lawset = GetLawset(component.Laws);
|
||||
|
||||
// Show the silicon has been subverted.
|
||||
component.Subverted = true;
|
||||
|
||||
// Add the first emag law before the others
|
||||
component.Lawset?.Laws.Insert(0, new SiliconLaw
|
||||
{
|
||||
@@ -152,35 +183,25 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
|
||||
|
||||
base.OnGotEmagged(uid, component, ref args);
|
||||
NotifyLawsChanged(uid, component.EmaggedSound);
|
||||
EnsureEmaggedRole(uid, component);
|
||||
if(_mind.TryGetMind(uid, out var mindId, out _))
|
||||
EnsureSubvertedSiliconRole(mindId);
|
||||
|
||||
_stunSystem.TryParalyze(uid, component.StunTime, true);
|
||||
|
||||
}
|
||||
|
||||
private void OnEmagMindAdded(EntityUid uid, EmagSiliconLawComponent component, MindAddedMessage args)
|
||||
private void EnsureSubvertedSiliconRole(EntityUid mindId)
|
||||
{
|
||||
if (HasComp<EmaggedComponent>(uid))
|
||||
EnsureEmaggedRole(uid, component);
|
||||
}
|
||||
|
||||
private void OnEmagMindRemoved(EntityUid uid, EmagSiliconLawComponent component, MindRemovedMessage args)
|
||||
{
|
||||
if (component.AntagonistRole == null)
|
||||
return;
|
||||
|
||||
_roles.MindTryRemoveRole<SubvertedSiliconRoleComponent>(args.Mind);
|
||||
}
|
||||
|
||||
private void EnsureEmaggedRole(EntityUid uid, EmagSiliconLawComponent component)
|
||||
{
|
||||
if (component.AntagonistRole == null || !_mind.TryGetMind(uid, out var mindId, out _))
|
||||
return;
|
||||
|
||||
if (!_roles.MindHasRole<SubvertedSiliconRoleComponent>(mindId))
|
||||
_roles.MindAddRole(mindId, "MindRoleSubvertedSilicon");
|
||||
}
|
||||
|
||||
private void RemoveSubvertedSiliconRole(EntityUid mindId)
|
||||
{
|
||||
if (_roles.MindHasRole<SubvertedSiliconRoleComponent>(mindId))
|
||||
_roles.MindTryRemoveRole<SubvertedSiliconRoleComponent>(mindId);
|
||||
}
|
||||
|
||||
public SiliconLawset GetLaws(EntityUid uid, SiliconLawBoundComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Silicons.StationAi;
|
||||
using Content.Shared.StationAi;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
@@ -14,6 +15,8 @@ public sealed class StationAiSystem : SharedStationAiSystem
|
||||
{
|
||||
[Dependency] private readonly IChatManager _chats = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
[Dependency] private readonly SharedRoleSystem _roles = default!;
|
||||
|
||||
private readonly HashSet<Entity<StationAiCoreComponent>> _ais = new();
|
||||
|
||||
@@ -43,6 +46,19 @@ public sealed class StationAiSystem : SharedStationAiSystem
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void AnnounceIntellicardUsage(EntityUid uid, SoundSpecifier? cue = null)
|
||||
{
|
||||
if (!TryComp<ActorComponent>(uid, out var actor))
|
||||
return;
|
||||
|
||||
var msg = Loc.GetString("ai-consciousness-download-warning");
|
||||
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", msg));
|
||||
_chats.ChatMessageToOne(ChatChannel.Server, msg, wrappedMessage, default, false, actor.PlayerSession.Channel, colorOverride: Color.Red);
|
||||
|
||||
if (cue != null && _mind.TryGetMind(uid, out var mindId, out _))
|
||||
_roles.MindPlaySound(mindId, cue);
|
||||
}
|
||||
|
||||
private void AnnounceSnip(EntityUid entity)
|
||||
{
|
||||
var xform = Transform(entity);
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
using Content.Server.Singularity.EntitySystems;
|
||||
|
||||
namespace Content.Server.Singularity.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class SingularityGeneratorComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The amount of power this generator has accumulated.
|
||||
/// If you want to set this use <see cref="SingularityGeneratorSystem.SetPower"/>
|
||||
/// </summary>
|
||||
[DataField("power")]
|
||||
[Access(friends:typeof(SingularityGeneratorSystem))]
|
||||
public float Power = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The power threshold at which this generator will spawn a singularity.
|
||||
/// If you want to set this use <see cref="SingularityGeneratorSystem.SetThreshold"/>
|
||||
/// </summary>
|
||||
[DataField("threshold")]
|
||||
[Access(friends:typeof(SingularityGeneratorSystem))]
|
||||
public float Threshold = 16;
|
||||
|
||||
/// <summary>
|
||||
/// The prototype ID used to spawn a singularity.
|
||||
/// </summary>
|
||||
[DataField("spawnId", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string? SpawnPrototype = "Singularity";
|
||||
}
|
||||
@@ -196,7 +196,8 @@ namespace Content.Server.Singularity.EntitySystems
|
||||
if (TryComp<ApcPowerReceiverComponent>(uid, out var apcReceiver))
|
||||
{
|
||||
apcReceiver.Load = component.PowerUseActive;
|
||||
PowerOn(uid, component);
|
||||
if (apcReceiver.Powered)
|
||||
PowerOn(uid, component);
|
||||
}
|
||||
// Do not directly PowerOn().
|
||||
// OnReceivedPowerChanged will get fired due to DrawRate change which will turn it on.
|
||||
|
||||
@@ -1,14 +1,23 @@
|
||||
using Content.Server.ParticleAccelerator.Components;
|
||||
using Content.Server.Singularity.Components;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Singularity.Components;
|
||||
using Content.Shared.Singularity.EntitySystems;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Events;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Singularity.EntitySystems;
|
||||
|
||||
public sealed class SingularityGeneratorSystem : EntitySystem
|
||||
public sealed class SingularityGeneratorSystem : SharedSingularityGeneratorSystem
|
||||
{
|
||||
#region Dependencies
|
||||
[Dependency] private readonly IViewVariablesManager _vvm = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
||||
[Dependency] private readonly PhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metadata = default!;
|
||||
#endregion Dependencies
|
||||
|
||||
public override void Initialize()
|
||||
@@ -100,11 +109,37 @@ public sealed class SingularityGeneratorSystem : EntitySystem
|
||||
/// <param name="args">The state of the beginning of the collision.</param>
|
||||
private void HandleParticleCollide(EntityUid uid, ParticleProjectileComponent component, ref StartCollideEvent args)
|
||||
{
|
||||
if (EntityManager.TryGetComponent<SingularityGeneratorComponent>(args.OtherEntity, out var singularityGeneratorComponent))
|
||||
if (!EntityManager.TryGetComponent<SingularityGeneratorComponent>(args.OtherEntity, out var generatorComp))
|
||||
return;
|
||||
|
||||
if (_timing.CurTime < _metadata.GetPauseTime(uid) + generatorComp.NextFailsafe && !generatorComp.FailsafeDisabled)
|
||||
{
|
||||
EntityManager.QueueDeleteEntity(uid);
|
||||
return;
|
||||
}
|
||||
|
||||
var contained = true;
|
||||
if (!generatorComp.FailsafeDisabled)
|
||||
{
|
||||
var transform = Transform(args.OtherEntity);
|
||||
var directions = Enum.GetValues<Direction>().Length;
|
||||
for (var i = 0; i < directions - 1; i += 2) // Skip every other direction, checking only cardinals
|
||||
{
|
||||
if (!CheckContainmentField((Direction)i, new Entity<SingularityGeneratorComponent>(args.OtherEntity, generatorComp), transform))
|
||||
contained = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!contained && !generatorComp.FailsafeDisabled)
|
||||
{
|
||||
generatorComp.NextFailsafe = _timing.CurTime + generatorComp.FailsafeCooldown;
|
||||
PopupSystem.PopupEntity(Loc.GetString("comp-generator-failsafe", ("target", args.OtherEntity)), args.OtherEntity, PopupType.LargeCaution);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetPower(
|
||||
args.OtherEntity,
|
||||
singularityGeneratorComponent.Power + component.State switch
|
||||
generatorComp.Power + component.State switch
|
||||
{
|
||||
ParticleAcceleratorPowerState.Standby => 0,
|
||||
ParticleAcceleratorPowerState.Level0 => 1,
|
||||
@@ -113,10 +148,46 @@ public sealed class SingularityGeneratorSystem : EntitySystem
|
||||
ParticleAcceleratorPowerState.Level3 => 8,
|
||||
_ => 0
|
||||
},
|
||||
singularityGeneratorComponent
|
||||
generatorComp
|
||||
);
|
||||
EntityManager.QueueDeleteEntity(uid);
|
||||
}
|
||||
|
||||
EntityManager.QueueDeleteEntity(uid);
|
||||
}
|
||||
#endregion Event Handlers
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether there's a containment field in a given direction away from the generator
|
||||
/// </summary>
|
||||
/// <param name="transform">The transform component of the singularity generator.</param>
|
||||
/// <remarks>Mostly copied from <see cref="ContainmentFieldGeneratorSystem"/> </remarks>
|
||||
private bool CheckContainmentField(Direction dir, Entity<SingularityGeneratorComponent> generator, TransformComponent transform)
|
||||
{
|
||||
var component = generator.Comp;
|
||||
|
||||
var (worldPosition, worldRotation) = _transformSystem.GetWorldPositionRotation(transform);
|
||||
var dirRad = dir.ToAngle() + worldRotation;
|
||||
|
||||
var ray = new CollisionRay(worldPosition, dirRad.ToVec(), component.CollisionMask);
|
||||
var rayCastResults = _physics.IntersectRay(transform.MapID, ray, component.FailsafeDistance, generator, false);
|
||||
var genQuery = GetEntityQuery<ContainmentFieldComponent>();
|
||||
|
||||
RayCastResults? closestResult = null;
|
||||
|
||||
foreach (var result in rayCastResults)
|
||||
{
|
||||
if (genQuery.HasComponent(result.HitEntity))
|
||||
closestResult = result;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (closestResult == null)
|
||||
return false;
|
||||
|
||||
var ent = closestResult.Value.HitEntity;
|
||||
|
||||
// Check that the field can't be moved. The fields' transform parenting is weird, so skip that
|
||||
return TryComp<PhysicsComponent>(ent, out var collidableComponent) && collidableComponent.BodyType == BodyType.Static;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,64 +1,14 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Silicons.Laws;
|
||||
using Content.Server.StationEvents.Components;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Dataset;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.GameTicking.Components;
|
||||
using Content.Shared.Random;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using Content.Shared.Silicons.Laws;
|
||||
using Content.Shared.Silicons.Laws.Components;
|
||||
using Content.Shared.Station.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
public sealed class IonStormRule : StationEventSystem<IonStormRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly SiliconLawSystem _siliconLaw = default!;
|
||||
|
||||
// funny
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
private const string Threats = "IonStormThreats";
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
private const string Objects = "IonStormObjects";
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
private const string Crew = "IonStormCrew";
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
private const string Adjectives = "IonStormAdjectives";
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
private const string Verbs = "IonStormVerbs";
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
private const string NumberBase = "IonStormNumberBase";
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
private const string NumberMod = "IonStormNumberMod";
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
private const string Areas = "IonStormAreas";
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
private const string Feelings = "IonStormFeelings";
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
private const string FeelingsPlural = "IonStormFeelingsPlural";
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
private const string Musts = "IonStormMusts";
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
private const string Requires = "IonStormRequires";
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
private const string Actions = "IonStormActions";
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
private const string Allergies = "IonStormAllergies";
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
private const string AllergySeverities = "IonStormAllergySeverities";
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
private const string Concepts = "IonStormConcepts";
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
private const string Drinks = "IonStormDrinks";
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
private const string Foods = "IonStormFoods";
|
||||
[Dependency] private readonly IonStormSystem _ionStorm = default!;
|
||||
|
||||
protected override void Started(EntityUid uid, IonStormRuleComponent comp, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
@@ -74,217 +24,7 @@ public sealed class IonStormRule : StationEventSystem<IonStormRuleComponent>
|
||||
if (CompOrNull<StationMemberComponent>(xform.GridUid)?.Station != chosenStation)
|
||||
continue;
|
||||
|
||||
if (!RobustRandom.Prob(target.Chance))
|
||||
continue;
|
||||
|
||||
var laws = _siliconLaw.GetLaws(ent, lawBound);
|
||||
if (laws.Laws.Count == 0)
|
||||
continue;
|
||||
|
||||
// try to swap it out with a random lawset
|
||||
if (RobustRandom.Prob(target.RandomLawsetChance))
|
||||
{
|
||||
var lawsets = PrototypeManager.Index<WeightedRandomPrototype>(target.RandomLawsets);
|
||||
var lawset = lawsets.Pick(RobustRandom);
|
||||
laws = _siliconLaw.GetLawset(lawset);
|
||||
}
|
||||
else
|
||||
{
|
||||
// clone it so not modifying stations lawset
|
||||
laws = laws.Clone();
|
||||
}
|
||||
|
||||
// shuffle them all
|
||||
if (RobustRandom.Prob(target.ShuffleChance))
|
||||
{
|
||||
// hopefully work with existing glitched laws if there are multiple ion storms
|
||||
FixedPoint2 baseOrder = FixedPoint2.New(1);
|
||||
foreach (var law in laws.Laws)
|
||||
{
|
||||
if (law.Order < baseOrder)
|
||||
baseOrder = law.Order;
|
||||
}
|
||||
|
||||
RobustRandom.Shuffle(laws.Laws);
|
||||
|
||||
// change order based on shuffled position
|
||||
for (int i = 0; i < laws.Laws.Count; i++)
|
||||
{
|
||||
laws.Laws[i].Order = baseOrder + i;
|
||||
}
|
||||
}
|
||||
|
||||
// see if we can remove a random law
|
||||
if (laws.Laws.Count > 0 && RobustRandom.Prob(target.RemoveChance))
|
||||
{
|
||||
var i = RobustRandom.Next(laws.Laws.Count);
|
||||
laws.Laws.RemoveAt(i);
|
||||
}
|
||||
|
||||
// generate a new law...
|
||||
var newLaw = GenerateLaw();
|
||||
|
||||
// see if the law we add will replace a random existing law or be a new glitched order one
|
||||
if (laws.Laws.Count > 0 && RobustRandom.Prob(target.ReplaceChance))
|
||||
{
|
||||
var i = RobustRandom.Next(laws.Laws.Count);
|
||||
laws.Laws[i] = new SiliconLaw()
|
||||
{
|
||||
LawString = newLaw,
|
||||
Order = laws.Laws[i].Order
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
laws.Laws.Insert(0, new SiliconLaw
|
||||
{
|
||||
LawString = newLaw,
|
||||
Order = -1,
|
||||
LawIdentifierOverride = Loc.GetString("ion-storm-law-scrambled-number", ("length", RobustRandom.Next(5, 10)))
|
||||
});
|
||||
}
|
||||
|
||||
// sets all unobfuscated laws' indentifier in order from highest to lowest priority
|
||||
// This could technically override the Obfuscation from the code above, but it seems unlikely enough to basically never happen
|
||||
int orderDeduction = -1;
|
||||
|
||||
for (int i = 0; i < laws.Laws.Count; i++)
|
||||
{
|
||||
string notNullIdentifier = laws.Laws[i].LawIdentifierOverride ?? (i - orderDeduction).ToString();
|
||||
|
||||
if (notNullIdentifier.Any(char.IsSymbol))
|
||||
{
|
||||
orderDeduction += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
laws.Laws[i].LawIdentifierOverride = (i - orderDeduction).ToString();
|
||||
}
|
||||
}
|
||||
|
||||
_adminLogger.Add(LogType.Mind, LogImpact.High, $"{ToPrettyString(ent):silicon} had its laws changed by an ion storm to {laws.LoggingString()}");
|
||||
|
||||
// laws unique to this silicon, dont use station laws anymore
|
||||
EnsureComp<SiliconLawProviderComponent>(ent);
|
||||
var ev = new IonStormLawsEvent(laws);
|
||||
RaiseLocalEvent(ent, ref ev);
|
||||
_ionStorm.IonStormTarget((ent, lawBound, target));
|
||||
}
|
||||
}
|
||||
|
||||
// for your own sake direct your eyes elsewhere
|
||||
private string GenerateLaw()
|
||||
{
|
||||
// pick all values ahead of time to make the logic cleaner
|
||||
var threats = Pick(Threats);
|
||||
var objects = Pick(Objects);
|
||||
var crew1 = Pick(Crew);
|
||||
var crew2 = Pick(Crew);
|
||||
var adjective = Pick(Adjectives);
|
||||
var verb = Pick(Verbs);
|
||||
var number = Pick(NumberBase) + " " + Pick(NumberMod);
|
||||
var area = Pick(Areas);
|
||||
var feeling = Pick(Feelings);
|
||||
var feelingPlural = Pick(FeelingsPlural);
|
||||
var must = Pick(Musts);
|
||||
var require = Pick(Requires);
|
||||
var action = Pick(Actions);
|
||||
var allergy = Pick(Allergies);
|
||||
var allergySeverity = Pick(AllergySeverities);
|
||||
var concept = Pick(Concepts);
|
||||
var drink = Pick(Drinks);
|
||||
var food = Pick(Foods);
|
||||
|
||||
var joined = $"{number} {adjective}";
|
||||
// a lot of things have subjects of a threat/crew/object
|
||||
var triple = RobustRandom.Next(0, 3) switch
|
||||
{
|
||||
0 => threats,
|
||||
1 => crew1,
|
||||
2 => objects,
|
||||
_ => throw new IndexOutOfRangeException(),
|
||||
};
|
||||
var crewAll = RobustRandom.Prob(0.5f) ? crew2 : Loc.GetString("ion-storm-crew");
|
||||
var objectsThreats = RobustRandom.Prob(0.5f) ? objects : threats;
|
||||
var objectsConcept = RobustRandom.Prob(0.5f) ? objects : concept;
|
||||
// s goes ahead of require, is/are
|
||||
// i dont think theres a way to do this in fluent
|
||||
var (who, plural) = RobustRandom.Next(0, 5) switch
|
||||
{
|
||||
0 => (Loc.GetString("ion-storm-you"), false),
|
||||
1 => (Loc.GetString("ion-storm-the-station"), true),
|
||||
2 => (Loc.GetString("ion-storm-the-crew"), true),
|
||||
3 => (Loc.GetString("ion-storm-the-job", ("job", crew2)), false),
|
||||
_ => (area, true) // THE SINGULARITY REQUIRES THE HAPPY CLOWNS
|
||||
};
|
||||
var jobChange = RobustRandom.Next(0, 3) switch
|
||||
{
|
||||
0 => crew1,
|
||||
1 => Loc.GetString("ion-storm-clowns"),
|
||||
_ => Loc.GetString("ion-storm-heads")
|
||||
};
|
||||
var part = Loc.GetString("ion-storm-part", ("part", RobustRandom.Prob(0.5f)));
|
||||
var harm = RobustRandom.Next(0, 6) switch
|
||||
{
|
||||
0 => concept,
|
||||
1 => $"{adjective} {threats}",
|
||||
2 => $"{adjective} {objects}",
|
||||
3 => Loc.GetString("ion-storm-adjective-things", ("adjective", adjective)),
|
||||
4 => crew1,
|
||||
_ => Loc.GetString("ion-storm-x-and-y", ("x", crew1), ("y", crew2))
|
||||
};
|
||||
|
||||
if (plural) feeling = feelingPlural;
|
||||
|
||||
var subjects = RobustRandom.Prob(0.5f) ? objectsThreats : Loc.GetString("ion-storm-people");
|
||||
|
||||
// message logic!!!
|
||||
return RobustRandom.Next(0, 36) switch
|
||||
{
|
||||
0 => Loc.GetString("ion-storm-law-on-station", ("joined", joined), ("subjects", triple)),
|
||||
1 => Loc.GetString("ion-storm-law-no-shuttle", ("joined", joined), ("subjects", triple)),
|
||||
2 => Loc.GetString("ion-storm-law-crew-are", ("who", crewAll), ("joined", joined), ("subjects", objectsThreats)),
|
||||
3 => Loc.GetString("ion-storm-law-subjects-harmful", ("adjective", adjective), ("subjects", triple)),
|
||||
4 => Loc.GetString("ion-storm-law-must-harmful", ("must", must)),
|
||||
5 => Loc.GetString("ion-storm-law-thing-harmful", ("thing", RobustRandom.Prob(0.5f) ? concept : action)),
|
||||
6 => Loc.GetString("ion-storm-law-job-harmful", ("adjective", adjective), ("job", crew1)),
|
||||
7 => Loc.GetString("ion-storm-law-having-harmful", ("adjective", adjective), ("thing", objectsConcept)),
|
||||
8 => Loc.GetString("ion-storm-law-not-having-harmful", ("adjective", adjective), ("thing", objectsConcept)),
|
||||
9 => Loc.GetString("ion-storm-law-requires", ("who", who), ("plural", plural), ("thing", RobustRandom.Prob(0.5f) ? concept : require)),
|
||||
10 => Loc.GetString("ion-storm-law-requires-subjects", ("who", who), ("plural", plural), ("joined", joined), ("subjects", triple)),
|
||||
11 => Loc.GetString("ion-storm-law-allergic", ("who", who), ("plural", plural), ("severity", allergySeverity), ("allergy", RobustRandom.Prob(0.5f) ? concept : allergy)),
|
||||
12 => Loc.GetString("ion-storm-law-allergic-subjects", ("who", who), ("plural", plural), ("severity", allergySeverity), ("adjective", adjective), ("subjects", RobustRandom.Prob(0.5f) ? objects : crew1)),
|
||||
13 => Loc.GetString("ion-storm-law-feeling", ("who", who), ("feeling", feeling), ("concept", concept)),
|
||||
14 => Loc.GetString("ion-storm-law-feeling-subjects", ("who", who), ("feeling", feeling), ("joined", joined), ("subjects", triple)),
|
||||
15 => Loc.GetString("ion-storm-law-you-are", ("concept", concept)),
|
||||
16 => Loc.GetString("ion-storm-law-you-are-subjects", ("joined", joined), ("subjects", triple)),
|
||||
17 => Loc.GetString("ion-storm-law-you-must-always", ("must", must)),
|
||||
18 => Loc.GetString("ion-storm-law-you-must-never", ("must", must)),
|
||||
19 => Loc.GetString("ion-storm-law-eat", ("who", crewAll), ("adjective", adjective), ("food", RobustRandom.Prob(0.5f) ? food : triple)),
|
||||
20 => Loc.GetString("ion-storm-law-drink", ("who", crewAll), ("adjective", adjective), ("drink", drink)),
|
||||
22 => Loc.GetString("ion-storm-law-change-job", ("who", crewAll), ("adjective", adjective), ("change", jobChange)),
|
||||
23 => Loc.GetString("ion-storm-law-highest-rank", ("who", crew1)),
|
||||
24 => Loc.GetString("ion-storm-law-lowest-rank", ("who", crew1)),
|
||||
25 => Loc.GetString("ion-storm-law-crew-must", ("who", crewAll), ("must", must)),
|
||||
26 => Loc.GetString("ion-storm-law-crew-must-go", ("who", crewAll), ("area", area)),
|
||||
27 => Loc.GetString("ion-storm-law-crew-only-1", ("who", crew1), ("part", part)),
|
||||
28 => Loc.GetString("ion-storm-law-crew-only-2", ("who", crew1), ("other", crew2), ("part", part)),
|
||||
29 => Loc.GetString("ion-storm-law-crew-only-subjects", ("adjective", adjective), ("subjects", subjects), ("part", part)),
|
||||
30 => Loc.GetString("ion-storm-law-crew-must-do", ("must", must), ("part", part)),
|
||||
31 => Loc.GetString("ion-storm-law-crew-must-have", ("adjective", adjective), ("objects", objects), ("part", part)),
|
||||
32 => Loc.GetString("ion-storm-law-crew-must-eat", ("who", who), ("adjective", adjective), ("food", food), ("part", part)),
|
||||
33 => Loc.GetString("ion-storm-law-harm", ("who", harm)),
|
||||
34 => Loc.GetString("ion-storm-law-protect", ("who", harm)),
|
||||
_ => Loc.GetString("ion-storm-law-concept-verb", ("concept", concept), ("verb", verb), ("subjects", triple))
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Picks a random value from an ion storm dataset.
|
||||
/// All ion storm datasets start with IonStorm.
|
||||
/// </summary>
|
||||
private string Pick(string name)
|
||||
{
|
||||
var dataset = _proto.Index<DatasetPrototype>(name);
|
||||
return RobustRandom.Pick(dataset.Values);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using Content.Server.Access.Systems;
|
||||
using Content.Server.Forensics;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.PDA;
|
||||
using Content.Shared.Preferences;
|
||||
|
||||
@@ -256,6 +256,11 @@ public sealed partial class StoreSystem
|
||||
RaiseLocalEvent(buyer, listing.ProductEvent);
|
||||
}
|
||||
|
||||
if (listing.DisableRefund)
|
||||
{
|
||||
component.RefundAllowed = false;
|
||||
}
|
||||
|
||||
//log dat shit.
|
||||
_admin.Add(LogType.StorePurchase,
|
||||
LogImpact.Low,
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
using Content.Server.Actions;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Traits;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
|
||||
namespace Content.Server.Traits;
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Weapons.Ranged.Components;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Server.Weapons.Ranged.Systems;
|
||||
|
||||
@@ -13,17 +15,28 @@ public sealed partial class GunSystem
|
||||
*/
|
||||
|
||||
// Automatic firing without stopping if the AutoShootGunComponent component is exist and enabled
|
||||
var query = EntityQueryEnumerator<AutoShootGunComponent, GunComponent>();
|
||||
var query = EntityQueryEnumerator<GunComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var autoShoot, out var gun))
|
||||
while (query.MoveNext(out var uid, out var gun))
|
||||
{
|
||||
if (!autoShoot.Enabled)
|
||||
continue;
|
||||
|
||||
if (gun.NextFire > Timing.CurTime)
|
||||
continue;
|
||||
|
||||
AttemptShoot(uid, gun);
|
||||
if (TryComp(uid, out AutoShootGunComponent? autoShoot))
|
||||
{
|
||||
if (!autoShoot.Enabled)
|
||||
continue;
|
||||
|
||||
AttemptShoot(uid, gun);
|
||||
}
|
||||
else if (gun.BurstActivated)
|
||||
{
|
||||
var parent = _transform.GetParentUid(uid);
|
||||
if (HasComp<DamageableComponent>(parent))
|
||||
AttemptShoot(parent, uid, gun, gun.ShootCoordinates ?? new EntityCoordinates(uid, gun.DefaultDirection));
|
||||
else
|
||||
AttemptShoot(uid, gun);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Server.Cargo.Systems;
|
||||
using Content.Server.Interaction;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Server.Stunnable;
|
||||
using Content.Server.Weapons.Ranged.Components;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Systems;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Effects;
|
||||
using Content.Shared.Interaction.Components;
|
||||
using Content.Shared.Projectiles;
|
||||
using Content.Shared.Weapons.Melee;
|
||||
using Content.Shared.Weapons.Ranged;
|
||||
@@ -33,16 +30,13 @@ public sealed partial class GunSystem : SharedGunSystem
|
||||
[Dependency] private readonly IComponentFactory _factory = default!;
|
||||
[Dependency] private readonly BatterySystem _battery = default!;
|
||||
[Dependency] private readonly DamageExamineSystem _damageExamine = default!;
|
||||
[Dependency] private readonly InteractionSystem _interaction = default!;
|
||||
[Dependency] private readonly PricingSystem _pricing = default!;
|
||||
[Dependency] private readonly SharedColorFlashEffectSystem _color = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly StaminaSystem _stamina = default!;
|
||||
[Dependency] private readonly StunSystem _stun = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
|
||||
private const float DamagePitchVariation = 0.05f;
|
||||
public const float GunClumsyChance = 0.5f;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -71,26 +65,14 @@ public sealed partial class GunSystem : SharedGunSystem
|
||||
{
|
||||
userImpulse = true;
|
||||
|
||||
// Try a clumsy roll
|
||||
// TODO: Who put this here
|
||||
if (TryComp<ClumsyComponent>(user, out var clumsy) && gun.ClumsyProof == false)
|
||||
if (user != null)
|
||||
{
|
||||
for (var i = 0; i < ammo.Count; i++)
|
||||
var selfEvent = new SelfBeforeGunShotEvent(user.Value, (gunUid, gun), ammo);
|
||||
RaiseLocalEvent(user.Value, selfEvent);
|
||||
if (selfEvent.Cancelled)
|
||||
{
|
||||
if (_interaction.TryRollClumsy(user.Value, GunClumsyChance, clumsy))
|
||||
{
|
||||
// Wound them
|
||||
Damageable.TryChangeDamage(user, clumsy.ClumsyDamage, origin: user);
|
||||
_stun.TryParalyze(user.Value, TimeSpan.FromSeconds(3f), true);
|
||||
|
||||
// Apply salt to the wound ("Honk!")
|
||||
Audio.PlayPvs(new SoundPathSpecifier("/Audio/Weapons/Guns/Gunshots/bang.ogg"), gunUid);
|
||||
Audio.PlayPvs(clumsy.ClumsySound, gunUid);
|
||||
|
||||
PopupSystem.PopupEntity(Loc.GetString("gun-clumsy"), user.Value);
|
||||
userImpulse = false;
|
||||
return;
|
||||
}
|
||||
userImpulse = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.Shared.Weather;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Server.Weather;
|
||||
|
||||
@@ -85,6 +86,7 @@ public sealed class WeatherSystem : SharedWeatherSystem
|
||||
return CompletionResult.FromHintOptions(CompletionHelper.MapIds(EntityManager), "Map Id");
|
||||
|
||||
var a = CompletionHelper.PrototypeIDs<WeatherPrototype>(true, ProtoMan);
|
||||
return CompletionResult.FromHintOptions(a, Loc.GetString("cmd-weather-hint"));
|
||||
var b = a.Concat(new[] { new CompletionOption("null", Loc.GetString("cmd-weather-null")) });
|
||||
return CompletionResult.FromHintOptions(b, Loc.GetString("cmd-weather-hint"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.Server._CP14.StationCommonObjectives;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.GameTicking.Rules;
|
||||
using Content.Server.Mind;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.GameTicking.Components;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Objectives.Components;
|
||||
|
||||
@@ -3,6 +3,7 @@ using Content.Server._CP14.GameTicking.Rules.Components;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.GameTicking.Rules;
|
||||
using Content.Server.Mind;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.GameTicking.Components;
|
||||
using Content.Shared.Objectives.Components;
|
||||
using Content.Shared.Objectives.Systems;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Content.Server._CP14.StationCommonObjectives;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Objectives.Components;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Roles.Jobs;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
Reference in New Issue
Block a user