Merge branch 'master' of https://github.com/space-wizards/space-station-14 into map-load-refactor
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
using Content.Server.Wires;
|
||||
using Content.Shared.Access;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Emag.Components;
|
||||
using Content.Shared.Wires;
|
||||
|
||||
namespace Content.Server.Access;
|
||||
@@ -31,11 +30,9 @@ public sealed partial class AccessWireAction : ComponentWireAction<AccessReaderC
|
||||
|
||||
public override bool Mend(EntityUid user, Wire wire, AccessReaderComponent comp)
|
||||
{
|
||||
if (!EntityManager.HasComponent<EmaggedComponent>(wire.Owner))
|
||||
{
|
||||
comp.Enabled = true;
|
||||
EntityManager.Dirty(wire.Owner, comp);
|
||||
}
|
||||
comp.Enabled = true;
|
||||
EntityManager.Dirty(wire.Owner, comp);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -58,7 +55,7 @@ public sealed partial class AccessWireAction : ComponentWireAction<AccessReaderC
|
||||
{
|
||||
if (!wire.IsCut)
|
||||
{
|
||||
if (EntityManager.TryGetComponent<AccessReaderComponent>(wire.Owner, out var access) && !EntityManager.HasComponent<EmaggedComponent>(wire.Owner))
|
||||
if (EntityManager.TryGetComponent<AccessReaderComponent>(wire.Owner, out var access))
|
||||
{
|
||||
access.Enabled = true;
|
||||
EntityManager.Dirty(wire.Owner, access);
|
||||
|
||||
@@ -245,6 +245,10 @@ public sealed class AccessOverriderSystem : SharedAccessOverriderSystem
|
||||
$"{ToPrettyString(player):player} has modified {ToPrettyString(accessReaderEnt.Value):entity} with the following allowed access level holders: [{string.Join(", ", addedTags.Union(removedTags))}] [{string.Join(", ", newAccessList)}]");
|
||||
|
||||
accessReaderEnt.Value.Comp.AccessLists = ConvertAccessListToHashSet(newAccessList);
|
||||
|
||||
var ev = new OnAccessOverriderAccessUpdatedEvent(player);
|
||||
RaiseLocalEvent(component.TargetAccessReaderId, ref ev);
|
||||
|
||||
Dirty(accessReaderEnt.Value);
|
||||
}
|
||||
|
||||
|
||||
@@ -118,8 +118,9 @@ namespace Content.Server.Administration.Commands
|
||||
}
|
||||
|
||||
var xform = _entManager.GetComponent<TransformComponent>(playerEntity);
|
||||
var xformSystem = _entManager.System<SharedTransformSystem>();
|
||||
xform.Coordinates = coords;
|
||||
xform.AttachToGridOrMap();
|
||||
xformSystem.AttachToGridOrMap(playerEntity, xform);
|
||||
if (_entManager.TryGetComponent(playerEntity, out PhysicsComponent? physics))
|
||||
{
|
||||
_entManager.System<SharedPhysicsSystem>().SetLinearVelocity(playerEntity, Vector2.Zero, body: physics);
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Content.Server.Database;
|
||||
|
||||
namespace Content.Server.Administration.Managers;
|
||||
|
||||
@@ -30,36 +28,15 @@ public sealed partial class BanManager
|
||||
private TimeSpan _banNotificationRateLimitStart;
|
||||
private int _banNotificationRateLimitCount;
|
||||
|
||||
private void OnDatabaseNotification(DatabaseNotification notification)
|
||||
private bool OnDatabaseNotificationEarlyFilter()
|
||||
{
|
||||
if (notification.Channel != BanNotificationChannel)
|
||||
return;
|
||||
|
||||
if (notification.Payload == null)
|
||||
{
|
||||
_sawmill.Error("Got ban notification with null payload!");
|
||||
return;
|
||||
}
|
||||
|
||||
BanNotificationData data;
|
||||
try
|
||||
{
|
||||
data = JsonSerializer.Deserialize<BanNotificationData>(notification.Payload)
|
||||
?? throw new JsonException("Content is null");
|
||||
}
|
||||
catch (JsonException e)
|
||||
{
|
||||
_sawmill.Error($"Got invalid JSON in ban notification: {e}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CheckBanRateLimit())
|
||||
{
|
||||
_sawmill.Verbose("Not processing ban notification due to rate limit");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
_taskManager.RunOnMainThread(() => ProcessBanNotification(data));
|
||||
return true;
|
||||
}
|
||||
|
||||
private async void ProcessBanNotification(BanNotificationData data)
|
||||
|
||||
@@ -53,7 +53,12 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
|
||||
{
|
||||
_netManager.RegisterNetMessage<MsgRoleBans>();
|
||||
|
||||
_db.SubscribeToNotifications(OnDatabaseNotification);
|
||||
_db.SubscribeToJsonNotification<BanNotificationData>(
|
||||
_taskManager,
|
||||
_sawmill,
|
||||
BanNotificationChannel,
|
||||
ProcessBanNotification,
|
||||
OnDatabaseNotificationEarlyFilter);
|
||||
|
||||
_userDbData.AddOnLoadPlayer(CachePlayerData);
|
||||
_userDbData.AddOnPlayerDisconnect(ClearPlayerData);
|
||||
@@ -160,6 +165,8 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
|
||||
null);
|
||||
|
||||
await _db.AddServerBanAsync(banDef);
|
||||
if (_cfg.GetCVar(CCVars.ServerBanResetLastReadRules) && target != null)
|
||||
await _db.SetLastReadRules(target.Value, null); // Reset their last read rules. They probably need a refresher!
|
||||
var adminName = banningAdmin == null
|
||||
? Loc.GetString("system-user")
|
||||
: (await _db.GetPlayerRecordByUserId(banningAdmin.Value))?.LastSeenUserName ?? Loc.GetString("system-user");
|
||||
|
||||
114
Content.Server/Administration/Managers/MultiServerKickManager.cs
Normal file
114
Content.Server/Administration/Managers/MultiServerKickManager.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Content.Server.Database;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Asynchronous;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server.Administration.Managers;
|
||||
|
||||
/// <summary>
|
||||
/// Handles kicking people that connect to multiple servers on the same DB at once.
|
||||
/// </summary>
|
||||
/// <seealso cref="CCVars.AdminAllowMultiServerPlay"/>
|
||||
public sealed class MultiServerKickManager
|
||||
{
|
||||
public const string NotificationChannel = "multi_server_kick";
|
||||
|
||||
[Dependency] private readonly IPlayerManager _playerManager = null!;
|
||||
[Dependency] private readonly IServerDbManager _dbManager = null!;
|
||||
[Dependency] private readonly ILogManager _logManager = null!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = null!;
|
||||
[Dependency] private readonly IAdminManager _adminManager = null!;
|
||||
[Dependency] private readonly ITaskManager _taskManager = null!;
|
||||
[Dependency] private readonly IServerNetManager _netManager = null!;
|
||||
[Dependency] private readonly ILocalizationManager _loc = null!;
|
||||
[Dependency] private readonly ServerDbEntryManager _serverDbEntry = null!;
|
||||
|
||||
private ISawmill _sawmill = null!;
|
||||
private bool _allowed;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_sawmill = _logManager.GetSawmill("multi_server_kick");
|
||||
|
||||
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||
_cfg.OnValueChanged(CCVars.AdminAllowMultiServerPlay, b => _allowed = b, true);
|
||||
|
||||
_dbManager.SubscribeToJsonNotification<NotificationData>(
|
||||
_taskManager,
|
||||
_sawmill,
|
||||
NotificationChannel,
|
||||
OnNotification,
|
||||
OnNotificationEarlyFilter
|
||||
);
|
||||
}
|
||||
|
||||
// ReSharper disable once AsyncVoidMethod
|
||||
private async void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
|
||||
{
|
||||
if (_allowed)
|
||||
return;
|
||||
|
||||
if (e.NewStatus != SessionStatus.InGame)
|
||||
return;
|
||||
|
||||
// Send notification to other servers so they can kick this player that just connected.
|
||||
try
|
||||
{
|
||||
await _dbManager.SendNotification(new DatabaseNotification
|
||||
{
|
||||
Channel = NotificationChannel,
|
||||
Payload = JsonSerializer.Serialize(new NotificationData
|
||||
{
|
||||
PlayerId = e.Session.UserId,
|
||||
ServerId = (await _serverDbEntry.ServerEntity).Id,
|
||||
}),
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_sawmill.Error($"Failed to send notification for multi server kick: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private bool OnNotificationEarlyFilter()
|
||||
{
|
||||
if (_allowed)
|
||||
{
|
||||
_sawmill.Verbose("Received notification for player join, but multi server play is allowed on this server. Ignoring");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ReSharper disable once AsyncVoidMethod
|
||||
private async void OnNotification(NotificationData notification)
|
||||
{
|
||||
if (!_playerManager.TryGetSessionById(new NetUserId(notification.PlayerId), out var player))
|
||||
return;
|
||||
|
||||
if (notification.ServerId == (await _serverDbEntry.ServerEntity).Id)
|
||||
return;
|
||||
|
||||
if (_adminManager.IsAdmin(player, includeDeAdmin: true))
|
||||
return;
|
||||
|
||||
_sawmill.Info($"Kicking {player} for connecting to another server. Multi-server play is not allowed.");
|
||||
_netManager.DisconnectChannel(player.Channel, _loc.GetString("multi-server-kick-reason"));
|
||||
}
|
||||
|
||||
private sealed class NotificationData
|
||||
{
|
||||
[JsonPropertyName("player_id")]
|
||||
public Guid PlayerId { get; set; }
|
||||
|
||||
[JsonPropertyName("server_id")]
|
||||
public int ServerId { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ using Content.Server.StationRecords.Systems;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Administration.Events;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Forensics.Components;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.IdentityManagement;
|
||||
@@ -82,13 +83,14 @@ public sealed class AdminSystem : EntitySystem
|
||||
Subs.CVar(_config, CCVars.PanicBunkerMinAccountAge, OnPanicBunkerMinAccountAgeChanged, true);
|
||||
Subs.CVar(_config, CCVars.PanicBunkerMinOverallMinutes, OnPanicBunkerMinOverallMinutesChanged, true);
|
||||
|
||||
SubscribeLocalEvent<IdentityChangedEvent>(OnIdentityChanged);
|
||||
SubscribeLocalEvent<PlayerAttachedEvent>(OnPlayerAttached);
|
||||
SubscribeLocalEvent<PlayerDetachedEvent>(OnPlayerDetached);
|
||||
SubscribeLocalEvent<RoleAddedEvent>(OnRoleEvent);
|
||||
SubscribeLocalEvent<RoleRemovedEvent>(OnRoleEvent);
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundRestartCleanup);
|
||||
|
||||
SubscribeLocalEvent<ActorComponent, EntityRenamedEvent>(OnPlayerRenamed);
|
||||
SubscribeLocalEvent<ActorComponent, IdentityChangedEvent>(OnIdentityChanged);
|
||||
}
|
||||
|
||||
private void OnRoundRestartCleanup(RoundRestartCleanupEvent ev)
|
||||
@@ -144,12 +146,9 @@ public sealed class AdminSystem : EntitySystem
|
||||
return value ?? null;
|
||||
}
|
||||
|
||||
private void OnIdentityChanged(ref IdentityChangedEvent ev)
|
||||
private void OnIdentityChanged(Entity<ActorComponent> ent, ref IdentityChangedEvent ev)
|
||||
{
|
||||
if (!TryComp<ActorComponent>(ev.CharacterEntity, out var actor))
|
||||
return;
|
||||
|
||||
UpdatePlayerList(actor.PlayerSession);
|
||||
UpdatePlayerList(ent.Comp.PlayerSession);
|
||||
}
|
||||
|
||||
private void OnRoleEvent(RoleEvent ev)
|
||||
|
||||
@@ -137,7 +137,7 @@ public sealed partial class AdminVerbSystem
|
||||
var board = Spawn("ChessBoard", xform.Coordinates);
|
||||
var session = _tabletopSystem.EnsureSession(Comp<TabletopGameComponent>(board));
|
||||
xform.Coordinates = EntityCoordinates.FromMap(_mapManager, session.Position);
|
||||
xform.WorldRotation = Angle.Zero;
|
||||
_transformSystem.SetWorldRotationNoLerp((args.Target, xform), Angle.Zero);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = string.Join(": ", chessName, Loc.GetString("admin-smite-chess-dimension-description"))
|
||||
@@ -892,5 +892,36 @@ public sealed partial class AdminVerbSystem
|
||||
Message = string.Join(": ", superslipName, Loc.GetString("admin-smite-super-slip-description"))
|
||||
};
|
||||
args.Verbs.Add(superslip);
|
||||
|
||||
var omniaccentName = Loc.GetString("admin-smite-omni-accent-name").ToLowerInvariant();
|
||||
Verb omniaccent = new()
|
||||
{
|
||||
Text = omniaccentName,
|
||||
Category = VerbCategory.Smite,
|
||||
Icon = new SpriteSpecifier.Rsi(new("Interface/Actions/voice-mask.rsi"), "icon"),
|
||||
Act = () =>
|
||||
{
|
||||
EnsureComp<BarkAccentComponent>(args.Target);
|
||||
EnsureComp<BleatingAccentComponent>(args.Target);
|
||||
EnsureComp<FrenchAccentComponent>(args.Target);
|
||||
EnsureComp<GermanAccentComponent>(args.Target);
|
||||
EnsureComp<LizardAccentComponent>(args.Target);
|
||||
EnsureComp<MobsterAccentComponent>(args.Target);
|
||||
EnsureComp<MothAccentComponent>(args.Target);
|
||||
EnsureComp<OwOAccentComponent>(args.Target);
|
||||
EnsureComp<SkeletonAccentComponent>(args.Target);
|
||||
EnsureComp<SouthernAccentComponent>(args.Target);
|
||||
EnsureComp<SpanishAccentComponent>(args.Target);
|
||||
EnsureComp<StutteringAccentComponent>(args.Target);
|
||||
|
||||
if (_random.Next(0, 8) == 0)
|
||||
{
|
||||
EnsureComp<BackwardsAccentComponent>(args.Target); // was asked to make this at a low chance idk
|
||||
}
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = string.Join(": ", omniaccentName, Loc.GetString("admin-smite-omni-accent-description"))
|
||||
};
|
||||
args.Verbs.Add(omniaccent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -370,7 +370,7 @@ namespace Content.Server.Administration.Systems
|
||||
|
||||
}
|
||||
|
||||
if (lawBoundComponent != null && target != null)
|
||||
if (lawBoundComponent != null && target != null && _adminManager.HasAdminFlag(player, AdminFlags.Moderator))
|
||||
{
|
||||
args.Verbs.Add(new Verb()
|
||||
{
|
||||
|
||||
@@ -1,38 +1,79 @@
|
||||
using Content.Server.Administration;
|
||||
using Content.Server.Chat;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Announcements
|
||||
namespace Content.Server.Announcements;
|
||||
|
||||
[AdminCommand(AdminFlags.Moderator)]
|
||||
public sealed class AnnounceCommand : LocalizedEntityCommands
|
||||
{
|
||||
[AdminCommand(AdminFlags.Moderator)]
|
||||
public sealed class AnnounceCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "announce";
|
||||
public string Description => "Send an in-game announcement.";
|
||||
public string Help => $"{Command} <sender> <message> or {Command} <message> to send announcement as CentCom.";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var chat = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ChatSystem>();
|
||||
[Dependency] private readonly ChatSystem _chat = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly IResourceManager _res = default!;
|
||||
|
||||
if (args.Length == 0)
|
||||
public override string Command => "announce";
|
||||
public override string Description => Loc.GetString("cmd-announce-desc");
|
||||
public override string Help => Loc.GetString("cmd-announce-help", ("command", Command));
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
switch (args.Length)
|
||||
{
|
||||
case 0:
|
||||
shell.WriteError(Loc.GetString("shell-need-minimum-one-argument"));
|
||||
return;
|
||||
case > 4:
|
||||
shell.WriteError(Loc.GetString("shell-wrong-arguments-number"));
|
||||
return;
|
||||
}
|
||||
|
||||
var message = args[0];
|
||||
var sender = Loc.GetString("cmd-announce-sender");
|
||||
var color = Color.Gold;
|
||||
var sound = new SoundPathSpecifier("/Audio/Announcements/announce.ogg");
|
||||
|
||||
// Optional sender argument
|
||||
if (args.Length >= 2)
|
||||
sender = args[1];
|
||||
|
||||
// Optional color argument
|
||||
if (args.Length >= 3)
|
||||
{
|
||||
try
|
||||
{
|
||||
shell.WriteError("Not enough arguments! Need at least 1.");
|
||||
color = Color.FromHex(args[2]);
|
||||
}
|
||||
catch
|
||||
{
|
||||
shell.WriteError(Loc.GetString("shell-invalid-color-hex"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length == 1)
|
||||
{
|
||||
chat.DispatchGlobalAnnouncement(args[0], colorOverride: Color.Gold);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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!");
|
||||
}
|
||||
|
||||
// Optional sound argument
|
||||
if (args.Length >= 4)
|
||||
sound = new SoundPathSpecifier(args[3]);
|
||||
|
||||
_chat.DispatchGlobalAnnouncement(message, sender, true, sound, color);
|
||||
shell.WriteLine(Loc.GetString("shell-command-success"));
|
||||
}
|
||||
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
return args.Length switch
|
||||
{
|
||||
1 => CompletionResult.FromHint(Loc.GetString("cmd-announce-arg-message")),
|
||||
2 => CompletionResult.FromHint(Loc.GetString("cmd-announce-arg-sender")),
|
||||
3 => CompletionResult.FromHint(Loc.GetString("cmd-announce-arg-color")),
|
||||
4 => CompletionResult.FromHintOptions(
|
||||
CompletionHelper.AudioFilePath(args[3], _proto, _res),
|
||||
Loc.GetString("cmd-announce-arg-sound")
|
||||
),
|
||||
_ => CompletionResult.Empty
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ using Content.Server.Chat.Managers;
|
||||
using Content.Server.Jittering;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Stunnable;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Anomaly;
|
||||
using Content.Shared.Anomaly.Components;
|
||||
using Content.Shared.Anomaly.Effects;
|
||||
@@ -24,8 +23,6 @@ public sealed class InnerBodyAnomalySystem : SharedInnerBodyAnomalySystem
|
||||
{
|
||||
[Dependency] private readonly IAdminLogManager _adminLog = default!;
|
||||
[Dependency] private readonly AnomalySystem _anomaly = default!;
|
||||
[Dependency] private readonly ActionContainerSystem _actionContainer = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly BodySystem _body = default!;
|
||||
[Dependency] private readonly IChatManager _chat = default!;
|
||||
|
||||
@@ -116,8 +116,11 @@ public sealed class TechAnomalySystem : EntitySystem
|
||||
|
||||
if (_random.Prob(tech.Comp.EmagSupercritProbability))
|
||||
{
|
||||
_emag.DoEmagEffect(tech, source);
|
||||
_emag.DoEmagEffect(tech, sink);
|
||||
var sourceEv = new GotEmaggedEvent(tech, EmagType.Access | EmagType.Interaction);
|
||||
RaiseLocalEvent(source, ref sourceEv);
|
||||
|
||||
var sinkEv = new GotEmaggedEvent(tech, EmagType.Access | EmagType.Interaction);
|
||||
RaiseLocalEvent(sink, ref sinkEv);
|
||||
}
|
||||
|
||||
CreateNewLink(tech, source, sink);
|
||||
|
||||
@@ -10,10 +10,8 @@ using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.Atmos.Monitor.Components;
|
||||
using Content.Shared.DeviceNetwork.Components;
|
||||
using Content.Shared.Pinpointer;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Timing;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
@@ -25,11 +23,9 @@ public sealed class AtmosAlertsComputerSystem : SharedAtmosAlertsComputerSystem
|
||||
[Dependency] private readonly AirAlarmSystem _airAlarmSystem = default!;
|
||||
[Dependency] private readonly AtmosDeviceNetworkSystem _atmosDevNet = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly TagSystem _tagSystem = default!;
|
||||
[Dependency] private readonly MapSystem _mapSystem = default!;
|
||||
[Dependency] private readonly TransformSystem _transformSystem = default!;
|
||||
[Dependency] private readonly NavMapSystem _navMapSystem = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly DeviceListSystem _deviceListSystem = default!;
|
||||
|
||||
private const float UpdateTime = 1.0f;
|
||||
@@ -54,7 +50,7 @@ public sealed class AtmosAlertsComputerSystem : SharedAtmosAlertsComputerSystem
|
||||
SubscribeLocalEvent<AtmosAlertsDeviceComponent, AnchorStateChangedEvent>(OnDeviceAnchorChanged);
|
||||
}
|
||||
|
||||
#region Event handling
|
||||
#region Event handling
|
||||
|
||||
private void OnConsoleInit(EntityUid uid, AtmosAlertsComputerComponent component, ComponentInit args)
|
||||
{
|
||||
|
||||
@@ -61,9 +61,9 @@ public sealed partial class AtmosphereSystem
|
||||
mixtures[5].Temperature = 5000f;
|
||||
|
||||
// 6: (Walk-In) Freezer
|
||||
mixtures[6].AdjustMoles(Gas.Oxygen, Atmospherics.OxygenMolesStandard);
|
||||
mixtures[6].AdjustMoles(Gas.Nitrogen, Atmospherics.NitrogenMolesStandard);
|
||||
mixtures[6].Temperature = 235f; // Little colder than an actual freezer but gives a grace period to get e.g. themomachines set up, should keep warm for a few door openings
|
||||
mixtures[6].AdjustMoles(Gas.Oxygen, Atmospherics.OxygenMolesFreezer);
|
||||
mixtures[6].AdjustMoles(Gas.Nitrogen, Atmospherics.NitrogenMolesFreezer);
|
||||
mixtures[6].Temperature = Atmospherics.FreezerTemp; // Little colder than an actual freezer but gives a grace period to get e.g. themomachines set up, should keep warm for a few door openings
|
||||
|
||||
// 7: Nitrogen (101kpa) for vox rooms
|
||||
mixtures[7].AdjustMoles(Gas.Nitrogen, Atmospherics.MolesCellStandard);
|
||||
|
||||
@@ -118,7 +118,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
return;
|
||||
|
||||
// Used by ExperiencePressureDifference to correct push/throw directions from tile-relative to physics world.
|
||||
var gridWorldRotation = xforms.GetComponent(gridAtmosphere).WorldRotation;
|
||||
var gridWorldRotation = _transformSystem.GetWorldRotation(gridAtmosphere);
|
||||
|
||||
// If we're using monstermos, smooth out the yeet direction to follow the flow
|
||||
if (MonstermosEqualization)
|
||||
|
||||
@@ -178,7 +178,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
|
||||
if (tile.Hotspot.Bypassing)
|
||||
{
|
||||
tile.Hotspot.Volume = tile.Air.ReactionResults[GasReaction.Fire] * Atmospherics.FireGrowthRate;
|
||||
tile.Hotspot.Volume = tile.Air.ReactionResults[(byte)GasReaction.Fire] * Atmospherics.FireGrowthRate;
|
||||
tile.Hotspot.Temperature = tile.Air.Temperature;
|
||||
}
|
||||
else
|
||||
@@ -187,7 +187,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
affected.Temperature = tile.Hotspot.Temperature;
|
||||
React(affected, tile);
|
||||
tile.Hotspot.Temperature = affected.Temperature;
|
||||
tile.Hotspot.Volume = affected.ReactionResults[GasReaction.Fire] * Atmospherics.FireGrowthRate;
|
||||
tile.Hotspot.Volume = affected.ReactionResults[(byte)GasReaction.Fire] * Atmospherics.FireGrowthRate;
|
||||
Merge(tile.Air, affected);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,10 +9,12 @@ using Content.Server.Power.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.Atmos.Monitor.Components;
|
||||
using Content.Shared.Atmos.Piping.Unary.Components;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.DeviceLinking;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.DeviceNetwork.Systems;
|
||||
@@ -37,6 +39,7 @@ namespace Content.Server.Atmos.Monitor.Systems;
|
||||
public sealed class AirAlarmSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AccessReaderSystem _access = default!;
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly AtmosAlarmableSystem _atmosAlarmable = default!;
|
||||
[Dependency] private readonly AtmosDeviceNetworkSystem _atmosDevNet = default!;
|
||||
[Dependency] private readonly DeviceNetworkSystem _deviceNet = default!;
|
||||
@@ -296,6 +299,7 @@ public sealed class AirAlarmSystem : EntitySystem
|
||||
addr = netConn.Address;
|
||||
}
|
||||
|
||||
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} changed {ToPrettyString(uid)} mode to {args.Mode}");
|
||||
SetMode(uid, addr, args.Mode, false);
|
||||
}
|
||||
else
|
||||
@@ -307,15 +311,26 @@ public sealed class AirAlarmSystem : EntitySystem
|
||||
private void OnUpdateAutoMode(EntityUid uid, AirAlarmComponent component, AirAlarmUpdateAutoModeMessage args)
|
||||
{
|
||||
component.AutoMode = args.Enabled;
|
||||
|
||||
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} changed {ToPrettyString(uid)} auto mode to {args.Enabled}");
|
||||
UpdateUI(uid, component);
|
||||
}
|
||||
|
||||
private void OnUpdateThreshold(EntityUid uid, AirAlarmComponent component, AirAlarmUpdateAlarmThresholdMessage args)
|
||||
{
|
||||
if (AccessCheck(uid, args.Actor, component))
|
||||
{
|
||||
if (args.Gas != null)
|
||||
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} changed {args.Address} {args.Gas} {args.Type} threshold using {ToPrettyString(uid)}");
|
||||
else
|
||||
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} changed {args.Address} {args.Type} threshold using {ToPrettyString(uid)}");
|
||||
|
||||
SetThreshold(uid, args.Address, args.Type, args.Threshold, args.Gas);
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateUI(uid, component);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnUpdateDeviceData(EntityUid uid, AirAlarmComponent component, AirAlarmUpdateDeviceDataMessage args)
|
||||
@@ -323,6 +338,8 @@ public sealed class AirAlarmSystem : EntitySystem
|
||||
if (AccessCheck(uid, args.Actor, component)
|
||||
&& _deviceList.ExistsInDeviceList(uid, args.Address))
|
||||
{
|
||||
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} changed {args.Address} settings using {ToPrettyString(uid)}");
|
||||
|
||||
SetDeviceData(uid, args.Address, args.Data);
|
||||
}
|
||||
else
|
||||
@@ -344,6 +361,7 @@ public sealed class AirAlarmSystem : EntitySystem
|
||||
case GasVentPumpData ventData:
|
||||
foreach (string addr in component.VentData.Keys)
|
||||
{
|
||||
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} copied settings to vent {addr}");
|
||||
SetData(uid, addr, args.Data);
|
||||
}
|
||||
break;
|
||||
@@ -351,6 +369,7 @@ public sealed class AirAlarmSystem : EntitySystem
|
||||
case GasVentScrubberData scrubberData:
|
||||
foreach (string addr in component.ScrubberData.Keys)
|
||||
{
|
||||
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} copied settings to scrubber {addr}");
|
||||
SetData(uid, addr, args.Data);
|
||||
}
|
||||
break;
|
||||
@@ -379,6 +398,7 @@ public sealed class AirAlarmSystem : EntitySystem
|
||||
if (!_access.IsAllowed(user.Value, uid, reader))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("air-alarm-ui-access-denied"), user.Value, user.Value);
|
||||
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Low, $"{ToPrettyString(user)} attempted to access {ToPrettyString(uid)} without access");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,9 +9,11 @@ using Content.Server.NodeContainer.EntitySystems;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.Atmos.Piping.Components;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.Power;
|
||||
using Content.Shared.Tag;
|
||||
@@ -25,6 +27,7 @@ namespace Content.Server.Atmos.Monitor.Systems;
|
||||
// a danger), and atmos (which triggers based on set thresholds).
|
||||
public sealed class AtmosMonitorSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
[Dependency] private readonly AtmosDeviceSystem _atmosDeviceSystem = default!;
|
||||
[Dependency] private readonly DeviceNetworkSystem _deviceNetSystem = default!;
|
||||
@@ -393,21 +396,74 @@ public sealed class AtmosMonitorSystem : EntitySystem
|
||||
if (!Resolve(uid, ref monitor))
|
||||
return;
|
||||
|
||||
// Used for logging after the switch statement
|
||||
string logPrefix = "";
|
||||
string logValueSuffix = "";
|
||||
AtmosAlarmThreshold? logPreviousThreshold = null;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case AtmosMonitorThresholdType.Pressure:
|
||||
logPrefix = "pressure";
|
||||
logValueSuffix = "kPa";
|
||||
logPreviousThreshold = monitor.PressureThreshold;
|
||||
|
||||
monitor.PressureThreshold = threshold;
|
||||
break;
|
||||
case AtmosMonitorThresholdType.Temperature:
|
||||
logPrefix = "temperature";
|
||||
logValueSuffix = "K";
|
||||
logPreviousThreshold = monitor.TemperatureThreshold;
|
||||
|
||||
monitor.TemperatureThreshold = threshold;
|
||||
break;
|
||||
case AtmosMonitorThresholdType.Gas:
|
||||
if (gas == null || monitor.GasThresholds == null)
|
||||
return;
|
||||
|
||||
logPrefix = ((Gas) gas).ToString();
|
||||
logValueSuffix = "kPa";
|
||||
monitor.GasThresholds.TryGetValue((Gas) gas, out logPreviousThreshold);
|
||||
|
||||
monitor.GasThresholds[(Gas) gas] = threshold;
|
||||
break;
|
||||
}
|
||||
|
||||
// Admin log each change separately rather than logging the whole state
|
||||
if (logPreviousThreshold != null)
|
||||
{
|
||||
if (threshold.Ignore != logPreviousThreshold.Ignore)
|
||||
{
|
||||
string enabled = threshold.Ignore ? "disabled" : "enabled";
|
||||
_adminLogger.Add(
|
||||
LogType.AtmosDeviceSetting,
|
||||
LogImpact.Medium,
|
||||
$"{ToPrettyString(uid)} {logPrefix} thresholds {enabled}"
|
||||
);
|
||||
}
|
||||
|
||||
foreach (var change in threshold.GetChanges(logPreviousThreshold))
|
||||
{
|
||||
if (change.Current.Enabled != change.Previous?.Enabled)
|
||||
{
|
||||
string enabled = change.Current.Enabled ? "enabled" : "disabled";
|
||||
_adminLogger.Add(
|
||||
LogType.AtmosDeviceSetting,
|
||||
LogImpact.Medium,
|
||||
$"{ToPrettyString(uid)} {logPrefix} {change.Type} {enabled}"
|
||||
);
|
||||
}
|
||||
|
||||
if (change.Current.Value != change.Previous?.Value)
|
||||
{
|
||||
_adminLogger.Add(
|
||||
LogType.AtmosDeviceSetting,
|
||||
LogImpact.Medium,
|
||||
$"{ToPrettyString(uid)} {logPrefix} {change.Type} changed from {change.Previous?.Value} {logValueSuffix} to {change.Current.Value} {logValueSuffix}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -21,6 +21,7 @@ public sealed class FireAlarmSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AtmosDeviceNetworkSystem _atmosDevNet = default!;
|
||||
[Dependency] private readonly AtmosAlarmableSystem _atmosAlarmable = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
||||
[Dependency] private readonly AccessReaderSystem _access = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
@@ -77,11 +78,18 @@ public sealed class FireAlarmSystem : EntitySystem
|
||||
|
||||
private void OnEmagged(EntityUid uid, FireAlarmComponent component, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (TryComp<AtmosAlarmableComponent>(uid, out var alarmable))
|
||||
{
|
||||
// Remove the atmos alarmable component permanently from this device.
|
||||
_atmosAlarmable.ForceAlert(uid, AtmosAlarmType.Emagged, alarmable);
|
||||
RemCompDeferred<AtmosAlarmableComponent>(uid);
|
||||
}
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (!TryComp<AtmosAlarmableComponent>(uid, out var alarmable))
|
||||
return;
|
||||
|
||||
// Remove the atmos alarmable component permanently from this device.
|
||||
_atmosAlarmable.ForceAlert(uid, AtmosAlarmType.Emagged, alarmable);
|
||||
RemCompDeferred<AtmosAlarmableComponent>(uid);
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Guidebook;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.Binary.Components
|
||||
{
|
||||
@@ -38,6 +39,7 @@ namespace Content.Server.Atmos.Piping.Binary.Components
|
||||
public float LowerThreshold { get; set; } = 0.01f;
|
||||
|
||||
[DataField("higherThreshold")]
|
||||
[GuidebookData]
|
||||
public float HigherThreshold { get; set; } = DefaultHigherThreshold;
|
||||
public static readonly float DefaultHigherThreshold = 2 * Atmospherics.MaxOutputPressure;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Guidebook;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.Trinary.Components
|
||||
{
|
||||
@@ -27,6 +28,7 @@ namespace Content.Server.Atmos.Piping.Trinary.Components
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("threshold")]
|
||||
[GuidebookData]
|
||||
public float Threshold { get; set; } = Atmospherics.OneAtmosphere;
|
||||
|
||||
[DataField("maxTransferRate")]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Guidebook;
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
@@ -61,5 +62,12 @@ namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
|
||||
[DataField("accessDeniedSound")]
|
||||
public SoundSpecifier AccessDeniedSound = new SoundPathSpecifier("/Audio/Machines/custom_deny.ogg");
|
||||
|
||||
#region GuidebookData
|
||||
|
||||
[GuidebookData]
|
||||
public float Volume => Air.Volume;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Server.Atmos.Piping.Binary.Components;
|
||||
using Content.Server.Atmos.Piping.Unary.EntitySystems;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Guidebook;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
{
|
||||
@@ -29,6 +30,7 @@ namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
public float MaxTransferRate = Atmospherics.MaxTransferRate;
|
||||
|
||||
[DataField("maxPressure")]
|
||||
[GuidebookData]
|
||||
public float MaxPressure { get; set; } = GasVolumePumpComponent.DefaultHigherThreshold;
|
||||
|
||||
[DataField("inlet")]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Guidebook;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
{
|
||||
@@ -13,6 +14,7 @@ namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
/// thermomachine to heat or cool air.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
[GuidebookData]
|
||||
public float HeatCapacity = 5000;
|
||||
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
@@ -21,6 +23,7 @@ namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
/// <summary>
|
||||
/// Tolerance for temperature setpoint hysteresis.
|
||||
/// </summary>
|
||||
[GuidebookData]
|
||||
[DataField, ViewVariables(VVAccess.ReadOnly)]
|
||||
public float TemperatureTolerance = 2f;
|
||||
|
||||
@@ -44,6 +47,7 @@ namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
/// Ignored if heater.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
[GuidebookData]
|
||||
public float MinTemperature = 73.15f;
|
||||
|
||||
/// <summary>
|
||||
@@ -51,6 +55,7 @@ namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
/// Ignored if freezer.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
[GuidebookData]
|
||||
public float MaxTemperature = 593.15f;
|
||||
|
||||
/// <summary>
|
||||
@@ -63,6 +68,7 @@ namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
/// An percentage of the energy change that is leaked into the surrounding environment rather than the inlet pipe.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
[GuidebookData]
|
||||
public float EnergyLeakPercentage;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Piping.Unary.Components;
|
||||
using Content.Shared.DeviceLinking;
|
||||
using Content.Shared.Guidebook;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
@@ -10,8 +11,12 @@ namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
[RegisterComponent]
|
||||
public sealed partial class GasVentPumpComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Identifies if the device is enabled by an air alarm. Does not indicate if the device is powered.
|
||||
/// By default, all air vents start enabled, whether linked to an alarm or not.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Enabled { get; set; } = false;
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
[ViewVariables]
|
||||
public bool IsDirty { get; set; } = false;
|
||||
@@ -35,6 +40,7 @@ namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
/// In releasing mode, do not pump when environment pressure is below this limit.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[GuidebookData]
|
||||
public float UnderPressureLockoutThreshold = 80; // this must be tuned in conjunction with atmos.mmos_spacing_speed
|
||||
|
||||
/// <summary>
|
||||
@@ -58,7 +64,7 @@ namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
[DataField]
|
||||
public bool IsPressureLockoutManuallyDisabled = false;
|
||||
/// <summary>
|
||||
/// The time when the manual pressure lockout will be reenabled.
|
||||
/// The time when the manual pressure lockout will be reenabled.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[AutoPausedField]
|
||||
@@ -101,6 +107,7 @@ namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
/// Max pressure of the target gas (NOT relative to source).
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[GuidebookData]
|
||||
public float MaxPressure = Atmospherics.MaxOutputPressure;
|
||||
|
||||
/// <summary>
|
||||
@@ -172,5 +179,12 @@ namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
InternalPressureBound = data.InternalPressureBound;
|
||||
PressureLockoutOverride = data.PressureLockoutOverride;
|
||||
}
|
||||
|
||||
#region GuidebookData
|
||||
|
||||
[GuidebookData]
|
||||
public float DefaultExternalBound => Atmospherics.OneAtmosphere;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,12 @@ namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
[Access(typeof(GasVentScrubberSystem))]
|
||||
public sealed partial class GasVentScrubberComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Identifies if the device is enabled by an air alarm. Does not indicate if the device is powered.
|
||||
/// By default, all air scrubbers start enabled, whether linked to an alarm or not.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool Enabled { get; set; } = false;
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
[DataField]
|
||||
public bool IsDirty { get; set; } = false;
|
||||
|
||||
@@ -109,7 +109,15 @@ public sealed class GasCanisterSystem : EntitySystem
|
||||
|
||||
var item = canister.GasTankSlot.Item;
|
||||
_slots.TryEjectToHands(uid, canister.GasTankSlot, args.Actor);
|
||||
_adminLogger.Add(LogType.CanisterTankEjected, LogImpact.Medium, $"Player {ToPrettyString(args.Actor):player} ejected tank {ToPrettyString(item):tank} from {ToPrettyString(uid):canister}");
|
||||
|
||||
if (canister.ReleaseValve)
|
||||
{
|
||||
_adminLogger.Add(LogType.CanisterTankEjected, LogImpact.High, $"Player {ToPrettyString(args.Actor):player} ejected tank {ToPrettyString(item):tank} from {ToPrettyString(uid):canister} while the valve was open, releasing [{GetContainedGasesString((uid, canister))}] to atmosphere");
|
||||
}
|
||||
else
|
||||
{
|
||||
_adminLogger.Add(LogType.CanisterTankEjected, LogImpact.Medium, $"Player {ToPrettyString(args.Actor):player} ejected tank {ToPrettyString(item):tank} from {ToPrettyString(uid):canister}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCanisterChangeReleasePressure(EntityUid uid, GasCanisterComponent canister, GasCanisterChangeReleasePressureMessage args)
|
||||
@@ -124,24 +132,24 @@ public sealed class GasCanisterSystem : EntitySystem
|
||||
|
||||
private void OnCanisterChangeReleaseValve(EntityUid uid, GasCanisterComponent canister, GasCanisterChangeReleaseValveMessage args)
|
||||
{
|
||||
var impact = LogImpact.High;
|
||||
// filling a jetpack with plasma is less important than filling a room with it
|
||||
impact = canister.GasTankSlot.HasItem ? LogImpact.Medium : LogImpact.High;
|
||||
var hasItem = canister.GasTankSlot.HasItem;
|
||||
var impact = hasItem ? LogImpact.Medium : LogImpact.High;
|
||||
|
||||
var containedGasDict = new Dictionary<Gas, float>();
|
||||
var containedGasArray = Enum.GetValues(typeof(Gas));
|
||||
|
||||
for (int i = 0; i < containedGasArray.Length; i++)
|
||||
{
|
||||
containedGasDict.Add((Gas)i, canister.Air[i]);
|
||||
}
|
||||
|
||||
_adminLogger.Add(LogType.CanisterValve, impact, $"{ToPrettyString(args.Actor):player} set the valve on {ToPrettyString(uid):canister} to {args.Valve:valveState} while it contained [{string.Join(", ", containedGasDict)}]");
|
||||
_adminLogger.Add(
|
||||
LogType.CanisterValve,
|
||||
impact,
|
||||
$"{ToPrettyString(args.Actor):player} {(args.Valve ? "opened" : "closed")} the valve on {ToPrettyString(uid):canister} to {(hasItem ? "inserted tank" : "environment")} while it contained [{GetContainedGasesString((uid, canister))}]");
|
||||
|
||||
canister.ReleaseValve = args.Valve;
|
||||
DirtyUI(uid, canister);
|
||||
}
|
||||
|
||||
private static string GetContainedGasesString(Entity<GasCanisterComponent> canister)
|
||||
{
|
||||
return string.Join(", ", canister.Comp.Air);
|
||||
}
|
||||
|
||||
private void OnCanisterUpdated(EntityUid uid, GasCanisterComponent canister, ref AtmosDeviceUpdateEvent args)
|
||||
{
|
||||
_atmos.React(canister.Air, canister);
|
||||
|
||||
@@ -9,6 +9,9 @@ using Content.Server.DeviceNetwork.Components;
|
||||
using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Server.NodeContainer.EntitySystems;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.Atmos.Piping.Components;
|
||||
@@ -16,6 +19,7 @@ using Content.Shared.Atmos.Piping.Unary;
|
||||
using Content.Shared.Atmos.Piping.Unary.Components;
|
||||
using Content.Shared.Atmos.Visuals;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Examine;
|
||||
@@ -30,6 +34,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
[UsedImplicitly]
|
||||
public sealed class GasVentPumpSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
[Dependency] private readonly DeviceNetworkSystem _deviceNetSystem = default!;
|
||||
[Dependency] private readonly DeviceLinkSystem _signalSystem = default!;
|
||||
@@ -40,6 +45,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
[Dependency] private readonly SharedToolSystem _toolSystem = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly PowerReceiverSystem _powerReceiverSystem = default!;
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -63,9 +69,10 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
{
|
||||
//Bingo waz here
|
||||
if (_weldable.IsWelded(uid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_powerReceiverSystem.IsPowered(uid))
|
||||
return;
|
||||
|
||||
var nodeName = vent.PumpDirection switch
|
||||
{
|
||||
@@ -207,7 +214,6 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
|
||||
private void OnPowerChanged(EntityUid uid, GasVentPumpComponent component, ref PowerChangedEvent args)
|
||||
{
|
||||
component.Enabled = args.Powered;
|
||||
UpdateState(uid, component);
|
||||
}
|
||||
|
||||
@@ -232,6 +238,44 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
if (!args.Data.TryGetValue(DeviceNetworkConstants.CmdSetState, out GasVentPumpData? setData))
|
||||
break;
|
||||
|
||||
var previous = component.ToAirAlarmData();
|
||||
|
||||
if (previous.Enabled != setData.Enabled)
|
||||
{
|
||||
string enabled = setData.Enabled ? "enabled" : "disabled" ;
|
||||
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(uid)} {enabled}");
|
||||
}
|
||||
|
||||
if (previous.PumpDirection != setData.PumpDirection)
|
||||
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(uid)} direction changed to {setData.PumpDirection}");
|
||||
|
||||
if (previous.PressureChecks != setData.PressureChecks)
|
||||
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(uid)} pressure check changed to {setData.PressureChecks}");
|
||||
|
||||
if (previous.ExternalPressureBound != setData.ExternalPressureBound)
|
||||
{
|
||||
_adminLogger.Add(
|
||||
LogType.AtmosDeviceSetting,
|
||||
LogImpact.Medium,
|
||||
$"{ToPrettyString(uid)} external pressure bound changed from {previous.ExternalPressureBound} kPa to {setData.ExternalPressureBound} kPa"
|
||||
);
|
||||
}
|
||||
|
||||
if (previous.InternalPressureBound != setData.InternalPressureBound)
|
||||
{
|
||||
_adminLogger.Add(
|
||||
LogType.AtmosDeviceSetting,
|
||||
LogImpact.Medium,
|
||||
$"{ToPrettyString(uid)} internal pressure bound changed from {previous.InternalPressureBound} kPa to {setData.InternalPressureBound} kPa"
|
||||
);
|
||||
}
|
||||
|
||||
if (previous.PressureLockoutOverride != setData.PressureLockoutOverride)
|
||||
{
|
||||
string enabled = setData.PressureLockoutOverride ? "enabled" : "disabled" ;
|
||||
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(uid)} pressure lockout override {enabled}");
|
||||
}
|
||||
|
||||
component.FromAirAlarmData(setData);
|
||||
UpdateState(uid, component);
|
||||
|
||||
@@ -277,7 +321,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
_ambientSoundSystem.SetAmbience(uid, false);
|
||||
_appearance.SetData(uid, VentPumpVisuals.State, VentPumpState.Welded, appearance);
|
||||
}
|
||||
else if (!vent.Enabled)
|
||||
else if (!_powerReceiverSystem.IsPowered(uid) || !vent.Enabled)
|
||||
{
|
||||
_ambientSoundSystem.SetAmbience(uid, false);
|
||||
_appearance.SetData(uid, VentPumpVisuals.State, VentPumpState.Off, appearance);
|
||||
|
||||
@@ -10,12 +10,15 @@ 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.Administration.Logs;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Piping.Unary.Visuals;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.Atmos.Piping.Components;
|
||||
using Content.Shared.Atmos.Piping.Unary.Components;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.Power;
|
||||
using Content.Shared.Tools.Systems;
|
||||
@@ -27,6 +30,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
[UsedImplicitly]
|
||||
public sealed class GasVentScrubberSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
[Dependency] private readonly DeviceNetworkSystem _deviceNetSystem = default!;
|
||||
[Dependency] private readonly NodeContainerSystem _nodeContainer = default!;
|
||||
@@ -34,6 +38,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
[Dependency] private readonly TransformSystem _transformSystem = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly WeldableSystem _weldable = default!;
|
||||
[Dependency] private readonly PowerReceiverSystem _powerReceiverSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -55,6 +60,9 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
|
||||
var timeDelta = args.dt;
|
||||
|
||||
if (!_powerReceiverSystem.IsPowered(uid))
|
||||
return;
|
||||
|
||||
if (!scrubber.Enabled || !_nodeContainer.TryGetNode(uid, scrubber.OutletName, out PipeNode? outlet))
|
||||
return;
|
||||
|
||||
@@ -138,7 +146,6 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
|
||||
private void OnPowerChanged(EntityUid uid, GasVentScrubberComponent component, ref PowerChangedEvent args)
|
||||
{
|
||||
component.Enabled = args.Powered;
|
||||
UpdateState(uid, component);
|
||||
}
|
||||
|
||||
@@ -163,6 +170,43 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
if (!args.Data.TryGetValue(DeviceNetworkConstants.CmdSetState, out GasVentScrubberData? setData))
|
||||
break;
|
||||
|
||||
var previous = component.ToAirAlarmData();
|
||||
|
||||
if (previous.Enabled != setData.Enabled)
|
||||
{
|
||||
string enabled = setData.Enabled ? "enabled" : "disabled" ;
|
||||
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(uid)} {enabled}");
|
||||
}
|
||||
|
||||
// TODO: IgnoreAlarms?
|
||||
|
||||
if (previous.PumpDirection != setData.PumpDirection)
|
||||
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(uid)} direction changed to {setData.PumpDirection}");
|
||||
|
||||
// TODO: This is iterating through both sets, it could probably be faster but they're both really small sets anyways
|
||||
foreach (Gas gas in previous.FilterGases)
|
||||
if (!setData.FilterGases.Contains(gas))
|
||||
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(uid)} {gas} filtering disabled");
|
||||
|
||||
foreach (Gas gas in setData.FilterGases)
|
||||
if (!previous.FilterGases.Contains(gas))
|
||||
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(uid)} {gas} filtering enabled");
|
||||
|
||||
if (previous.VolumeRate != setData.VolumeRate)
|
||||
{
|
||||
_adminLogger.Add(
|
||||
LogType.AtmosDeviceSetting,
|
||||
LogImpact.Medium,
|
||||
$"{ToPrettyString(uid)} volume rate changed from {previous.VolumeRate} L to {setData.VolumeRate} L"
|
||||
);
|
||||
}
|
||||
|
||||
if (previous.WideNet != setData.WideNet)
|
||||
{
|
||||
string enabled = setData.WideNet ? "enabled" : "disabled" ;
|
||||
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(uid)} WideNet {enabled}");
|
||||
}
|
||||
|
||||
component.FromAirAlarmData(setData);
|
||||
UpdateState(uid, component);
|
||||
|
||||
@@ -185,7 +229,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
_ambientSoundSystem.SetAmbience(uid, false);
|
||||
_appearance.SetData(uid, ScrubberVisuals.State, ScrubberState.Welded, appearance);
|
||||
}
|
||||
else if (!scrubber.Enabled)
|
||||
else if (!_powerReceiverSystem.IsPowered(uid) || !scrubber.Enabled)
|
||||
{
|
||||
_ambientSoundSystem.SetAmbience(uid, false);
|
||||
_appearance.SetData(uid, ScrubberVisuals.State, ScrubberState.Off, appearance);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Guidebook;
|
||||
|
||||
namespace Content.Server.Atmos.Portable
|
||||
{
|
||||
@@ -45,5 +46,12 @@ namespace Content.Server.Atmos.Portable
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public float TransferRate = 800;
|
||||
|
||||
#region GuidebookData
|
||||
|
||||
[GuidebookData]
|
||||
public float Volume => Air.Volume;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Piping.Portable.Components;
|
||||
using Content.Shared.Atmos.Visuals;
|
||||
using Content.Shared.Guidebook;
|
||||
|
||||
namespace Content.Server.Atmos.Portable;
|
||||
|
||||
@@ -23,12 +24,14 @@ public sealed partial class SpaceHeaterComponent : Component
|
||||
/// Maximum target temperature the device can be set to
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
[GuidebookData]
|
||||
public float MaxTemperature = Atmospherics.T20C + 20;
|
||||
|
||||
/// <summary>
|
||||
/// Minimal target temperature the device can be set to
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
[GuidebookData]
|
||||
public float MinTemperature = Atmospherics.T0C - 10;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Content.Server.Atmos.Reactions
|
||||
var oldHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true);
|
||||
var temperature = mixture.Temperature;
|
||||
var location = holder as TileAtmosphere;
|
||||
mixture.ReactionResults[GasReaction.Fire] = 0;
|
||||
mixture.ReactionResults[(byte)GasReaction.Fire] = 0;
|
||||
|
||||
// More plasma released at higher temperatures.
|
||||
var temperatureScale = 0f;
|
||||
@@ -60,7 +60,7 @@ namespace Content.Server.Atmos.Reactions
|
||||
|
||||
energyReleased += Atmospherics.FirePlasmaEnergyReleased * plasmaBurnRate;
|
||||
energyReleased /= heatScale; // adjust energy to make sure speedup doesn't cause mega temperature rise
|
||||
mixture.ReactionResults[GasReaction.Fire] += plasmaBurnRate * (1 + oxygenBurnRate);
|
||||
mixture.ReactionResults[(byte)GasReaction.Fire] += plasmaBurnRate * (1 + oxygenBurnRate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ namespace Content.Server.Atmos.Reactions
|
||||
}
|
||||
}
|
||||
|
||||
return mixture.ReactionResults[GasReaction.Fire] != 0 ? ReactionResult.Reacting : ReactionResult.NoReaction;
|
||||
return mixture.ReactionResults[(byte)GasReaction.Fire] != 0 ? ReactionResult.Reacting : ReactionResult.NoReaction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Content.Server.Atmos.Reactions
|
||||
var oldHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true);
|
||||
var temperature = mixture.Temperature;
|
||||
var location = holder as TileAtmosphere;
|
||||
mixture.ReactionResults[GasReaction.Fire] = 0f;
|
||||
mixture.ReactionResults[(byte)GasReaction.Fire] = 0f;
|
||||
var burnedFuel = 0f;
|
||||
var initialTrit = mixture.GetMoles(Gas.Tritium);
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace Content.Server.Atmos.Reactions
|
||||
// Conservation of mass is important.
|
||||
mixture.AdjustMoles(Gas.WaterVapor, burnedFuel);
|
||||
|
||||
mixture.ReactionResults[GasReaction.Fire] += burnedFuel;
|
||||
mixture.ReactionResults[(byte)GasReaction.Fire] += burnedFuel;
|
||||
}
|
||||
|
||||
energyReleased /= heatScale; // adjust energy to make sure speedup doesn't cause mega temperature rise
|
||||
@@ -65,7 +65,7 @@ namespace Content.Server.Atmos.Reactions
|
||||
}
|
||||
}
|
||||
|
||||
return mixture.ReactionResults[GasReaction.Fire] != 0 ? ReactionResult.Reacting : ReactionResult.NoReaction;
|
||||
return mixture.ReactionResults[(byte)GasReaction.Fire] != 0 ? ReactionResult.Reacting : ReactionResult.NoReaction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ public sealed class BeamSystem : SharedBeamSystem
|
||||
|
||||
_physics.SetBodyType(ent, BodyType.Dynamic, manager: manager, body: physics);
|
||||
_physics.SetCanCollide(ent, true, manager: manager, body: physics);
|
||||
_broadphase.RegenerateContacts(ent, physics, manager);
|
||||
_broadphase.RegenerateContacts((ent, physics, manager));
|
||||
|
||||
var distanceLength = distanceCorrection.Length();
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace Content.Server.Bed
|
||||
{
|
||||
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
|
||||
[Dependency] private readonly ActionsSystem _actionsSystem = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
[Dependency] private readonly SleepingSystem _sleepingSystem = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||
@@ -114,7 +115,12 @@ namespace Content.Server.Bed
|
||||
|
||||
private void OnEmagged(EntityUid uid, StasisBedComponent component, ref GotEmaggedEvent args)
|
||||
{
|
||||
args.Repeatable = true;
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
// Reset any metabolisms first so they receive the multiplier correctly
|
||||
UpdateMetabolisms(uid, component, false);
|
||||
component.Multiplier = 1 / component.Multiplier;
|
||||
|
||||
@@ -13,6 +13,7 @@ using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.Drunk;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Forensics;
|
||||
using Content.Shared.Forensics.Components;
|
||||
using Content.Shared.HealthExaminable;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Popups;
|
||||
|
||||
@@ -7,8 +7,15 @@ namespace Content.Server.Botany.Components;
|
||||
[RegisterComponent]
|
||||
public sealed partial class PlantHolderComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Game time for the next plant reagent update.
|
||||
/// </summary>
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
public TimeSpan NextUpdate = TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Time between plant reagent consumption updates.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan UpdateDelay = TimeSpan.FromSeconds(3);
|
||||
|
||||
@@ -18,18 +25,31 @@ public sealed partial class PlantHolderComponent : Component
|
||||
[DataField]
|
||||
public int MissingGas;
|
||||
|
||||
/// <summary>
|
||||
/// Time between plant growth updates.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan CycleDelay = TimeSpan.FromSeconds(15f);
|
||||
|
||||
/// <summary>
|
||||
/// Game time when the plant last did a growth update.
|
||||
/// </summary>
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
public TimeSpan LastCycle = TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Sound played when any reagent is transferred into the plant holder.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier? WateringSound;
|
||||
|
||||
[DataField]
|
||||
public bool UpdateSpriteAfterUpdate;
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if the plant holder displays plant warnings (e.g. water low) in the sprite and
|
||||
/// examine text. Used to differentiate hydroponic trays from simple soil plots.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool DrawWarnings = false;
|
||||
|
||||
@@ -60,9 +80,16 @@ public sealed partial class PlantHolderComponent : Component
|
||||
[DataField]
|
||||
public bool Harvest;
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if this plant has been clipped by seed clippers. Used to prevent a single plant
|
||||
/// from repeatedly being clipped.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool Sampled;
|
||||
|
||||
/// <summary>
|
||||
/// Multiplier for the number of entities produced at harvest.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int YieldMod = 1;
|
||||
|
||||
@@ -81,15 +108,28 @@ public sealed partial class PlantHolderComponent : Component
|
||||
[DataField]
|
||||
public SeedData? Seed;
|
||||
|
||||
/// <summary>
|
||||
/// True if the plant is losing health due to too high/low temperature.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool ImproperHeat;
|
||||
|
||||
/// <summary>
|
||||
/// True if the plant is losing health due to too high/low pressure.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool ImproperPressure;
|
||||
|
||||
/// <summary>
|
||||
/// Not currently used.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool ImproperLight;
|
||||
|
||||
/// <summary>
|
||||
/// Set to true to force a plant update (visuals, component, etc.) regardless of the current
|
||||
/// update cycle time. Typically used when some interaction affects this plant.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool ForceUpdate;
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ using Content.Shared.Random;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
@@ -25,10 +24,8 @@ public sealed partial class BotanySystem : EntitySystem
|
||||
[Dependency] private readonly AppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _hands = default!;
|
||||
[Dependency] private readonly SharedPointLightSystem _light = default!;
|
||||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly FixtureSystem _fixtureSystem = default!;
|
||||
[Dependency] private readonly RandomHelperSystem _randomHelper = default!;
|
||||
|
||||
public override void Initialize()
|
||||
@@ -84,7 +81,7 @@ public sealed partial class BotanySystem : EntitySystem
|
||||
if (!TryGetSeed(component, out var seed))
|
||||
return;
|
||||
|
||||
using (args.PushGroup(nameof(SeedComponent)))
|
||||
using (args.PushGroup(nameof(SeedComponent), 1))
|
||||
{
|
||||
var name = Loc.GetString(seed.DisplayName);
|
||||
args.PushMarkup(Loc.GetString($"seed-component-description", ("seedName", name)));
|
||||
|
||||
@@ -45,5 +45,6 @@ public sealed class LogSystem : EntitySystem
|
||||
}
|
||||
|
||||
QueueDel(uid);
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using Content.Server.Labels.Components;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
|
||||
namespace Content.Server.Botany.Systems;
|
||||
|
||||
@@ -39,6 +41,7 @@ public sealed class PlantHolderSystem : EntitySystem
|
||||
[Dependency] private readonly TagSystem _tagSystem = default!;
|
||||
[Dependency] private readonly RandomHelperSystem _randomHelper = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly ItemSlotsSystem _itemSlots = default!;
|
||||
|
||||
|
||||
public const float HydroponicsSpeedMultiplier = 1f;
|
||||
@@ -176,6 +179,10 @@ public sealed class PlantHolderSystem : EntitySystem
|
||||
}
|
||||
component.LastCycle = _gameTiming.CurTime;
|
||||
|
||||
if (TryComp<PaperLabelComponent>(args.Used, out var paperLabel))
|
||||
{
|
||||
_itemSlots.TryEjectToHands(args.Used, paperLabel.LabelSlot, args.User);
|
||||
}
|
||||
QueueDel(args.Used);
|
||||
|
||||
CheckLevelSanity(uid, component);
|
||||
|
||||
@@ -12,15 +12,22 @@ public sealed partial class StationCargoBountyDatabaseComponent : Component
|
||||
/// <summary>
|
||||
/// Maximum amount of bounties a station can have.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public int MaxBounties = 6;
|
||||
|
||||
/// <summary>
|
||||
/// A list of all the bounties currently active for a station.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public List<CargoBountyData> Bounties = new();
|
||||
|
||||
/// <summary>
|
||||
/// A list of all the bounties that have been completed or
|
||||
/// skipped for a station.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public List<CargoBountyHistoryData> History = new();
|
||||
|
||||
/// <summary>
|
||||
/// Used to determine unique order IDs
|
||||
/// </summary>
|
||||
|
||||
@@ -8,6 +8,7 @@ using Content.Shared.Cargo;
|
||||
using Content.Shared.Cargo.Components;
|
||||
using Content.Shared.Cargo.Prototypes;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.NameIdentifier;
|
||||
using Content.Shared.Paper;
|
||||
using Content.Shared.Stacks;
|
||||
@@ -16,6 +17,7 @@ using JetBrains.Annotations;
|
||||
using Robust.Server.Containers;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Cargo.Systems;
|
||||
@@ -25,6 +27,7 @@ public sealed partial class CargoSystem
|
||||
[Dependency] private readonly ContainerSystem _container = default!;
|
||||
[Dependency] private readonly NameIdentifierSystem _nameIdentifier = default!;
|
||||
[Dependency] private readonly EntityWhitelistSystem _whitelistSys = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
[ValidatePrototypeId<NameIdentifierGroupPrototype>]
|
||||
private const string BountyNameIdentifierGroup = "Bounty";
|
||||
@@ -54,7 +57,7 @@ public sealed partial class CargoSystem
|
||||
return;
|
||||
|
||||
var untilNextSkip = bountyDb.NextSkipTime - _timing.CurTime;
|
||||
_uiSystem.SetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(bountyDb.Bounties, untilNextSkip));
|
||||
_uiSystem.SetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(bountyDb.Bounties, bountyDb.History, untilNextSkip));
|
||||
}
|
||||
|
||||
private void OnPrintLabelMessage(EntityUid uid, CargoBountyConsoleComponent component, BountyPrintLabelMessage args)
|
||||
@@ -95,13 +98,13 @@ public sealed partial class CargoSystem
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryRemoveBounty(station, bounty.Value))
|
||||
if (!TryRemoveBounty(station, bounty.Value, true, args.Actor))
|
||||
return;
|
||||
|
||||
FillBountyDatabase(station);
|
||||
db.NextSkipTime = _timing.CurTime + db.SkipDelay;
|
||||
var untilNextSkip = db.NextSkipTime - _timing.CurTime;
|
||||
_uiSystem.SetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, untilNextSkip));
|
||||
_uiSystem.SetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, db.History, untilNextSkip));
|
||||
_audio.PlayPvs(component.SkipSound, uid);
|
||||
}
|
||||
|
||||
@@ -179,7 +182,7 @@ public sealed partial class CargoSystem
|
||||
continue;
|
||||
}
|
||||
|
||||
TryRemoveBounty(station, bounty.Value);
|
||||
TryRemoveBounty(station, bounty.Value, false);
|
||||
FillBountyDatabase(station);
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Low, $"Bounty \"{bounty.Value.Bounty}\" (id:{bounty.Value.Id}) was fulfilled");
|
||||
}
|
||||
@@ -434,24 +437,44 @@ public sealed partial class CargoSystem
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public bool TryRemoveBounty(EntityUid uid, string dataId, StationCargoBountyDatabaseComponent? component = null)
|
||||
public bool TryRemoveBounty(Entity<StationCargoBountyDatabaseComponent?> ent,
|
||||
string dataId,
|
||||
bool skipped,
|
||||
EntityUid? actor = null)
|
||||
{
|
||||
if (!TryGetBountyFromId(uid, dataId, out var data, component))
|
||||
if (!TryGetBountyFromId(ent.Owner, dataId, out var data, ent.Comp))
|
||||
return false;
|
||||
|
||||
return TryRemoveBounty(uid, data.Value, component);
|
||||
return TryRemoveBounty(ent, data.Value, skipped, actor);
|
||||
}
|
||||
|
||||
public bool TryRemoveBounty(EntityUid uid, CargoBountyData data, StationCargoBountyDatabaseComponent? component = null)
|
||||
public bool TryRemoveBounty(Entity<StationCargoBountyDatabaseComponent?> ent,
|
||||
CargoBountyData data,
|
||||
bool skipped,
|
||||
EntityUid? actor = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < component.Bounties.Count; i++)
|
||||
for (var i = 0; i < ent.Comp.Bounties.Count; i++)
|
||||
{
|
||||
if (component.Bounties[i].Id == data.Id)
|
||||
if (ent.Comp.Bounties[i].Id == data.Id)
|
||||
{
|
||||
component.Bounties.RemoveAt(i);
|
||||
string? actorName = null;
|
||||
if (actor != null)
|
||||
{
|
||||
var getIdentityEvent = new TryGetIdentityShortInfoEvent(ent.Owner, actor.Value);
|
||||
RaiseLocalEvent(getIdentityEvent);
|
||||
actorName = getIdentityEvent.Title;
|
||||
}
|
||||
|
||||
ent.Comp.History.Add(new CargoBountyHistoryData(data,
|
||||
skipped
|
||||
? CargoBountyHistoryData.BountyResult.Skipped
|
||||
: CargoBountyHistoryData.BountyResult.Completed,
|
||||
_gameTiming.CurTime,
|
||||
actorName));
|
||||
ent.Comp.Bounties.RemoveAt(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -492,7 +515,7 @@ public sealed partial class CargoSystem
|
||||
}
|
||||
|
||||
var untilNextSkip = db.NextSkipTime - _timing.CurTime;
|
||||
_uiSystem.SetUiState((uid, ui), CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, untilNextSkip));
|
||||
_uiSystem.SetUiState((uid, ui), CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, db.History, untilNextSkip));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ using Content.Shared.Cargo.Components;
|
||||
using Content.Shared.Cargo.Events;
|
||||
using Content.Shared.Cargo.Prototypes;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Emag.Components;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Paper;
|
||||
@@ -21,6 +21,7 @@ namespace Content.Server.Cargo.Systems
|
||||
public sealed partial class CargoSystem
|
||||
{
|
||||
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
|
||||
/// <summary>
|
||||
/// How much time to wait (in seconds) before increasing bank accounts balance.
|
||||
@@ -41,6 +42,7 @@ namespace Content.Server.Cargo.Systems
|
||||
SubscribeLocalEvent<CargoOrderConsoleComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<CargoOrderConsoleComponent, InteractUsingEvent>(OnInteractUsing);
|
||||
SubscribeLocalEvent<CargoOrderConsoleComponent, BankBalanceUpdatedEvent>(OnOrderBalanceUpdated);
|
||||
SubscribeLocalEvent<CargoOrderConsoleComponent, GotEmaggedEvent>(OnEmagged);
|
||||
Reset();
|
||||
}
|
||||
|
||||
@@ -62,6 +64,7 @@ namespace Content.Server.Cargo.Systems
|
||||
_audio.PlayPvs(component.ConfirmSound, uid);
|
||||
UpdateBankAccount(stationUid.Value, bank, (int) price);
|
||||
QueueDel(args.Used);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, CargoOrderConsoleComponent orderConsole, ComponentInit args)
|
||||
@@ -75,6 +78,17 @@ namespace Content.Server.Cargo.Systems
|
||||
_timer = 0;
|
||||
}
|
||||
|
||||
private void OnEmagged(Entity<CargoOrderConsoleComponent> ent, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (_emag.CheckFlag(ent, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void UpdateConsole(float frameTime)
|
||||
{
|
||||
_timer += frameTime;
|
||||
@@ -85,9 +99,11 @@ namespace Content.Server.Cargo.Systems
|
||||
{
|
||||
_timer -= Delay;
|
||||
|
||||
foreach (var account in EntityQuery<StationBankAccountComponent>())
|
||||
var stationQuery = EntityQueryEnumerator<StationBankAccountComponent>();
|
||||
while (stationQuery.MoveNext(out var uid, out var bank))
|
||||
{
|
||||
account.Balance += account.IncreasePerSecond * Delay;
|
||||
var balanceToAdd = bank.IncreasePerSecond * Delay;
|
||||
UpdateBankAccount(uid, bank, balanceToAdd);
|
||||
}
|
||||
|
||||
var query = EntityQueryEnumerator<CargoOrderConsoleComponent>();
|
||||
@@ -192,7 +208,7 @@ namespace Content.Server.Cargo.Systems
|
||||
order.Approved = true;
|
||||
_audio.PlayPvs(component.ConfirmSound, uid);
|
||||
|
||||
if (!HasComp<EmaggedComponent>(uid))
|
||||
if (!_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
{
|
||||
var tryGetIdentityShortInfoEvent = new TryGetIdentityShortInfoEvent(uid, player);
|
||||
RaiseLocalEvent(tryGetIdentityShortInfoEvent);
|
||||
@@ -213,7 +229,7 @@ namespace Content.Server.Cargo.Systems
|
||||
$"{ToPrettyString(player):user} approved order [orderId:{order.OrderId}, quantity:{order.OrderQuantity}, product:{order.ProductId}, requester:{order.Requester}, reason:{order.Reason}] with balance at {bank.Balance}");
|
||||
|
||||
orderDatabase.Orders.Remove(order);
|
||||
DeductFunds(bank, cost);
|
||||
UpdateBankAccount(station.Value, bank, -cost);
|
||||
UpdateOrders(station.Value);
|
||||
}
|
||||
|
||||
@@ -536,11 +552,6 @@ namespace Content.Server.Cargo.Systems
|
||||
|
||||
}
|
||||
|
||||
private void DeductFunds(StationBankAccountComponent component, int amount)
|
||||
{
|
||||
component.Balance = Math.Max(0, component.Balance - amount);
|
||||
}
|
||||
|
||||
#region Station
|
||||
|
||||
private bool TryGetOrderDatabase([NotNullWhen(true)] EntityUid? stationUid, [MaybeNullWhen(false)] out StationCargoOrderDatabaseComponent dbComp)
|
||||
|
||||
@@ -6,7 +6,7 @@ using Content.Server.Administration.Managers;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Players.RateLimiting;
|
||||
using Content.Server.Speech.Components;
|
||||
using Content.Server.Speech.Prototypes;
|
||||
using Content.Server.Speech.EntitySystems;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
|
||||
@@ -12,10 +12,7 @@ using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Timing;
|
||||
using Content.Shared.Weapons.Melee.Events;
|
||||
using Content.Server.Interaction;
|
||||
using Content.Server.Body.Components;
|
||||
using Robust.Shared.GameStates;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Robust.Server.Audio;
|
||||
|
||||
@@ -24,7 +21,6 @@ namespace Content.Server.Chemistry.EntitySystems;
|
||||
public sealed class HypospraySystem : SharedHypospraySystem
|
||||
{
|
||||
[Dependency] private readonly AudioSystem _audio = default!;
|
||||
[Dependency] private readonly InteractionSystem _interaction = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Server.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Chemistry.Events;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Projectiles;
|
||||
using Content.Shared.Tag;
|
||||
using Content.Shared.Weapons.Melee.Events;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Chemistry.EntitySystems;
|
||||
@@ -19,16 +11,11 @@ namespace Content.Server.Chemistry.EntitySystems;
|
||||
public sealed class SolutionInjectWhileEmbeddedSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly BloodstreamSystem _bloodstream = default!;
|
||||
[Dependency] private readonly InventorySystem _inventory = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
|
||||
[Dependency] private readonly TagSystem _tag = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
|
||||
SubscribeLocalEvent<SolutionInjectWhileEmbeddedComponent, MapInitEvent>(OnMapInit);
|
||||
}
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ namespace Content.Server.Cloning
|
||||
[Dependency] private readonly SharedMindSystem _mindSystem = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaSystem = default!;
|
||||
[Dependency] private readonly SharedJobSystem _jobs = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
|
||||
public readonly Dictionary<MindComponent, EntityUid> ClonesWaitingForMind = new();
|
||||
public const float EasyModeCloningCost = 0.7f;
|
||||
@@ -276,10 +277,15 @@ namespace Content.Server.Cloning
|
||||
/// </summary>
|
||||
private void OnEmagged(EntityUid uid, CloningPodComponent clonePod, ref GotEmaggedEvent args)
|
||||
{
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (!this.IsPowered(uid, EntityManager))
|
||||
return;
|
||||
|
||||
_audio.PlayPvs(clonePod.SparkSound, uid);
|
||||
_popupSystem.PopupEntity(Loc.GetString("cloning-pod-component-upgrade-emag-requirement"), uid);
|
||||
args.Handled = true;
|
||||
}
|
||||
@@ -309,7 +315,7 @@ namespace Content.Server.Cloning
|
||||
var indices = _transformSystem.GetGridTilePositionOrDefault((uid, transform));
|
||||
var tileMix = _atmosphereSystem.GetTileMixture(transform.GridUid, null, indices, true);
|
||||
|
||||
if (HasComp<EmaggedComponent>(uid))
|
||||
if (_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
{
|
||||
_audio.PlayPvs(clonePod.ScreamSound, uid);
|
||||
Spawn(clonePod.MobSpawnId, transform.Coordinates);
|
||||
@@ -327,7 +333,7 @@ namespace Content.Server.Cloning
|
||||
}
|
||||
_puddleSystem.TrySpillAt(uid, bloodSolution, out _);
|
||||
|
||||
if (!HasComp<EmaggedComponent>(uid))
|
||||
if (!_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
{
|
||||
_material.SpawnMultipleFromMaterial(_robustRandom.Next(1, (int) (clonePod.UsedBiomass / 2.5)), clonePod.RequiredMaterial, Transform(uid).Coordinates);
|
||||
}
|
||||
|
||||
@@ -3,18 +3,13 @@ using Content.Shared.Clothing.Components;
|
||||
using Content.Shared.Clothing.EntitySystems;
|
||||
using Content.Shared.IdentityManagement.Components;
|
||||
using Content.Shared.Prototypes;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Clothing.Systems;
|
||||
|
||||
public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||
[Dependency] private readonly IComponentFactory _factory = default!;
|
||||
[Dependency] private readonly IdentitySystem _identity = default!;
|
||||
|
||||
@@ -22,7 +17,6 @@ public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<ChameleonClothingComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<ChameleonClothingComponent, GetVerbsEvent<InteractionVerb>>(OnVerb);
|
||||
SubscribeLocalEvent<ChameleonClothingComponent, ChameleonPrototypeSelectedMessage>(OnSelected);
|
||||
}
|
||||
|
||||
@@ -31,40 +25,18 @@ public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem
|
||||
SetSelectedPrototype(uid, component.Default, true, component);
|
||||
}
|
||||
|
||||
private void OnVerb(EntityUid uid, ChameleonClothingComponent component, GetVerbsEvent<InteractionVerb> args)
|
||||
{
|
||||
if (!args.CanAccess || !args.CanInteract || component.User != args.User)
|
||||
return;
|
||||
|
||||
args.Verbs.Add(new InteractionVerb()
|
||||
{
|
||||
Text = Loc.GetString("chameleon-component-verb-text"),
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/settings.svg.192dpi.png")),
|
||||
Act = () => TryOpenUi(uid, args.User, component)
|
||||
});
|
||||
}
|
||||
|
||||
private void OnSelected(EntityUid uid, ChameleonClothingComponent component, ChameleonPrototypeSelectedMessage args)
|
||||
{
|
||||
SetSelectedPrototype(uid, args.SelectedId, component: component);
|
||||
}
|
||||
|
||||
private void TryOpenUi(EntityUid uid, EntityUid user, ChameleonClothingComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
if (!TryComp(user, out ActorComponent? actor))
|
||||
return;
|
||||
_uiSystem.TryToggleUi(uid, ChameleonUiKey.Key, actor.PlayerSession);
|
||||
}
|
||||
|
||||
private void UpdateUi(EntityUid uid, ChameleonClothingComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
var state = new ChameleonBoundUserInterfaceState(component.Slot, component.Default, component.RequireTag);
|
||||
_uiSystem.SetUiState(uid, ChameleonUiKey.Key, state);
|
||||
UI.SetUiState(uid, ChameleonUiKey.Key, state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -16,7 +16,6 @@ using Content.Shared.Chat;
|
||||
using Content.Shared.Communications;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.Emag.Components;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Server.GameObjects;
|
||||
@@ -177,7 +176,7 @@ namespace Content.Server.Communications
|
||||
|
||||
private bool CanUse(EntityUid user, EntityUid console)
|
||||
{
|
||||
if (TryComp<AccessReaderComponent>(console, out var accessReaderComponent) && !HasComp<EmaggedComponent>(console))
|
||||
if (TryComp<AccessReaderComponent>(console, out var accessReaderComponent))
|
||||
{
|
||||
return _accessReaderSystem.IsAllowed(user, console, accessReaderComponent);
|
||||
}
|
||||
|
||||
@@ -89,6 +89,8 @@ public sealed class FixRotationsCommand : IConsoleCommand
|
||||
valid |= tagSystem.HasTag(child, "ForceFixRotations");
|
||||
// override
|
||||
valid &= !tagSystem.HasTag(child, "ForceNoFixRotations");
|
||||
// remove diagonal entities as well
|
||||
valid &= !tagSystem.HasTag(child, "Diagonal");
|
||||
|
||||
if (!valid)
|
||||
continue;
|
||||
|
||||
@@ -304,8 +304,8 @@ namespace Content.Server.Construction
|
||||
return null;
|
||||
|
||||
// [Optional] Exit if the new entity's prototype is a parent of the original
|
||||
// E.g., if an entity with the 'AirlockCommand' prototype was to be replaced with a new entity that
|
||||
// had the 'Airlock' prototype, and DoNotReplaceInheritingEntities was true, the code block would
|
||||
// E.g., if an entity with the 'AirlockCommand' prototype was to be replaced with a new entity that
|
||||
// had the 'Airlock' prototype, and DoNotReplaceInheritingEntities was true, the code block would
|
||||
// exit here because 'AirlockCommand' is derived from 'Airlock'
|
||||
if (GetCurrentNode(uid, construction)?.DoNotReplaceInheritingEntities == true &&
|
||||
metaData.EntityPrototype?.ID != null)
|
||||
@@ -362,7 +362,7 @@ namespace Content.Server.Construction
|
||||
|
||||
// Transform transferring.
|
||||
var newTransform = Transform(newUid);
|
||||
newTransform.AttachToGridOrMap(); // in case in hands or a container
|
||||
TransformSystem.AttachToGridOrMap(newUid, newTransform); // in case in hands or a container
|
||||
newTransform.LocalRotation = transform.LocalRotation;
|
||||
newTransform.Anchored = transform.Anchored;
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@ using Robust.Server.GameObjects;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Security.Components;
|
||||
using System.Linq;
|
||||
using Content.Shared.Roles.Jobs;
|
||||
|
||||
namespace Content.Server.CriminalRecords.Systems;
|
||||
|
||||
@@ -42,6 +44,7 @@ public sealed class CriminalRecordsConsoleSystem : SharedCriminalRecordsConsoleS
|
||||
subs.Event<CriminalRecordChangeStatus>(OnChangeStatus);
|
||||
subs.Event<CriminalRecordAddHistory>(OnAddHistory);
|
||||
subs.Event<CriminalRecordDeleteHistory>(OnDeleteHistory);
|
||||
subs.Event<CriminalRecordSetStatusFilter>(OnStatusFilterPressed);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -57,6 +60,11 @@ public sealed class CriminalRecordsConsoleSystem : SharedCriminalRecordsConsoleS
|
||||
ent.Comp.ActiveKey = msg.SelectedKey;
|
||||
UpdateUserInterface(ent);
|
||||
}
|
||||
private void OnStatusFilterPressed(Entity<CriminalRecordsConsoleComponent> ent, ref CriminalRecordSetStatusFilter msg)
|
||||
{
|
||||
ent.Comp.FilterStatus = msg.FilterStatus;
|
||||
UpdateUserInterface(ent);
|
||||
}
|
||||
|
||||
private void OnFiltersChanged(Entity<CriminalRecordsConsoleComponent> ent, ref SetStationRecordFilter msg)
|
||||
{
|
||||
@@ -112,13 +120,26 @@ public sealed class CriminalRecordsConsoleSystem : SharedCriminalRecordsConsoleS
|
||||
}
|
||||
|
||||
// will probably never fail given the checks above
|
||||
name = _records.RecordName(key.Value);
|
||||
officer = Loc.GetString("criminal-records-console-unknown-officer");
|
||||
var jobName = "Unknown";
|
||||
|
||||
_records.TryGetRecord<GeneralStationRecord>(key.Value, out var entry);
|
||||
if (entry != null)
|
||||
jobName = entry.JobTitle;
|
||||
|
||||
var tryGetIdentityShortInfoEvent = new TryGetIdentityShortInfoEvent(null, mob.Value);
|
||||
RaiseLocalEvent(tryGetIdentityShortInfoEvent);
|
||||
if (tryGetIdentityShortInfoEvent.Title != null)
|
||||
officer = tryGetIdentityShortInfoEvent.Title;
|
||||
|
||||
_criminalRecords.TryChangeStatus(key.Value, msg.Status, msg.Reason, officer);
|
||||
|
||||
(string, object)[] args;
|
||||
if (reason != null)
|
||||
args = new (string, object)[] { ("name", name), ("officer", officer), ("reason", reason) };
|
||||
args = new (string, object)[] { ("name", name), ("officer", officer), ("reason", reason), ("job", jobName) };
|
||||
else
|
||||
args = new (string, object)[] { ("name", name), ("officer", officer) };
|
||||
args = new (string, object)[] { ("name", name), ("officer", officer), ("job", jobName) };
|
||||
|
||||
// figure out which radio message to send depending on transition
|
||||
var statusString = (oldStatus, msg.Status) switch
|
||||
@@ -193,8 +214,18 @@ public sealed class CriminalRecordsConsoleSystem : SharedCriminalRecordsConsoleS
|
||||
return;
|
||||
}
|
||||
|
||||
// get the listing of records to display
|
||||
var listing = _records.BuildListing((owningStation.Value, stationRecords), console.Filter);
|
||||
|
||||
// filter the listing by the selected criminal record status
|
||||
//if NONE, dont filter by status, just show all crew
|
||||
if (console.FilterStatus != SecurityStatus.None)
|
||||
{
|
||||
listing = listing
|
||||
.Where(x => _records.TryGetRecord<CriminalRecord>(new StationRecordKey(x.Key, owningStation.Value), out var record) && record.Status == console.FilterStatus)
|
||||
.ToDictionary(x => x.Key, x => x.Value);
|
||||
}
|
||||
|
||||
var state = new CriminalRecordsConsoleState(listing, console.Filter);
|
||||
if (console.ActiveKey is { } id)
|
||||
{
|
||||
@@ -205,6 +236,9 @@ public sealed class CriminalRecordsConsoleSystem : SharedCriminalRecordsConsoleS
|
||||
state.SelectedKey = id;
|
||||
}
|
||||
|
||||
// Set the Current Tab aka the filter status type for the records list
|
||||
state.FilterStatus = console.FilterStatus;
|
||||
|
||||
_ui.SetUiState(uid, CriminalRecordsConsoleKey.Key, state);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,10 +5,16 @@ namespace Content.Server.Damage.Components
|
||||
[RegisterComponent]
|
||||
public sealed partial class DamageOnLandComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Should this entity be damaged when it lands regardless of its resistances?
|
||||
/// </summary>
|
||||
[DataField("ignoreResistances")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool IgnoreResistances = false;
|
||||
|
||||
/// <summary>
|
||||
/// How much damage.
|
||||
/// </summary>
|
||||
[DataField("damage", required: true)]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public DamageSpecifier Damage = default!;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Shared.Bed.Sleep;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Events;
|
||||
using Content.Shared.Damage.ForceSay;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Mobs;
|
||||
@@ -47,7 +48,7 @@ public sealed class DamageForceSaySystem : EntitySystem
|
||||
}
|
||||
}
|
||||
|
||||
private void TryForceSay(EntityUid uid, DamageForceSayComponent component, bool useSuffix=true, string? suffixOverride = null)
|
||||
private void TryForceSay(EntityUid uid, DamageForceSayComponent component, bool useSuffix=true)
|
||||
{
|
||||
if (!TryComp<ActorComponent>(uid, out var actor))
|
||||
return;
|
||||
@@ -57,7 +58,13 @@ public sealed class DamageForceSaySystem : EntitySystem
|
||||
_timing.CurTime < component.NextAllowedTime)
|
||||
return;
|
||||
|
||||
var suffix = Loc.GetString(suffixOverride ?? component.ForceSayStringPrefix + _random.Next(1, component.ForceSayStringCount));
|
||||
var ev = new BeforeForceSayEvent(component.ForceSayStringDataset);
|
||||
RaiseLocalEvent(uid, ev);
|
||||
|
||||
if (!_prototype.TryIndex(ev.Prefix, out var prefixList))
|
||||
return;
|
||||
|
||||
var suffix = Loc.GetString(_random.Pick(prefixList.Values));
|
||||
|
||||
// set cooldown & raise event
|
||||
component.NextAllowedTime = _timing.CurTime + component.Cooldown;
|
||||
@@ -80,7 +87,7 @@ public sealed class DamageForceSaySystem : EntitySystem
|
||||
if (!args.FellAsleep)
|
||||
return;
|
||||
|
||||
TryForceSay(uid, component, true, "damage-force-say-sleep");
|
||||
TryForceSay(uid, component);
|
||||
AllowNextSpeech(uid);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using Content.Server.Damage.Components;
|
||||
using Content.Shared.CombatMode.Pacification;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Throwing;
|
||||
|
||||
namespace Content.Server.Damage.Systems
|
||||
{
|
||||
/// <summary>
|
||||
/// Damages the thrown item when it lands.
|
||||
/// </summary>
|
||||
public sealed class DamageOnLandSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
|
||||
@@ -13,19 +15,6 @@ namespace Content.Server.Damage.Systems
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<DamageOnLandComponent, LandEvent>(DamageOnLand);
|
||||
SubscribeLocalEvent<DamageOnLandComponent, AttemptPacifiedThrowEvent>(OnAttemptPacifiedThrow);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prevent Pacified entities from throwing damaging items.
|
||||
/// </summary>
|
||||
private void OnAttemptPacifiedThrow(Entity<DamageOnLandComponent> ent, ref AttemptPacifiedThrowEvent args)
|
||||
{
|
||||
// Allow healing projectiles, forbid any that do damage:
|
||||
if (ent.Comp.Damage.AnyPositive())
|
||||
{
|
||||
args.Cancel("pacified-cannot-throw");
|
||||
}
|
||||
}
|
||||
|
||||
private void DamageOnLand(EntityUid uid, DamageOnLandComponent component, ref LandEvent args)
|
||||
|
||||
@@ -222,6 +222,7 @@ namespace Content.Server.Database
|
||||
{
|
||||
var loadout = new RoleLoadout(role.RoleName)
|
||||
{
|
||||
EntityName = role.EntityName,
|
||||
};
|
||||
|
||||
foreach (var group in role.Groups)
|
||||
@@ -319,6 +320,7 @@ namespace Content.Server.Database
|
||||
var dz = new ProfileRoleLoadout()
|
||||
{
|
||||
RoleName = role,
|
||||
EntityName = loadouts.EntityName ?? string.Empty,
|
||||
};
|
||||
|
||||
foreach (var (group, groupLoadouts) in loadouts.SelectedLoadouts)
|
||||
@@ -1112,7 +1114,7 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
|
||||
.SingleOrDefaultAsync());
|
||||
}
|
||||
|
||||
public async Task SetLastReadRules(NetUserId player, DateTimeOffset date)
|
||||
public async Task SetLastReadRules(NetUserId player, DateTimeOffset? date)
|
||||
{
|
||||
await using var db = await GetDb();
|
||||
|
||||
@@ -1122,7 +1124,7 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
|
||||
return;
|
||||
}
|
||||
|
||||
dbPlayer.LastReadRules = date.UtcDateTime;
|
||||
dbPlayer.LastReadRules = date?.UtcDateTime;
|
||||
await db.DbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
@@ -1801,6 +1803,8 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
|
||||
|
||||
#endregion
|
||||
|
||||
public abstract Task SendNotification(DatabaseNotification notification);
|
||||
|
||||
// SQLite returns DateTime as Kind=Unspecified, Npgsql actually knows for sure it's Kind=Utc.
|
||||
// Normalize DateTimes here so they're always Utc. Thanks.
|
||||
protected abstract DateTime NormalizeDatabaseTime(DateTime time);
|
||||
|
||||
@@ -282,7 +282,7 @@ namespace Content.Server.Database
|
||||
#region Rules
|
||||
|
||||
Task<DateTimeOffset?> GetLastReadRules(NetUserId player);
|
||||
Task SetLastReadRules(NetUserId player, DateTimeOffset time);
|
||||
Task SetLastReadRules(NetUserId player, DateTimeOffset? time);
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -350,6 +350,15 @@ namespace Content.Server.Database
|
||||
/// <param name="notification">The notification to trigger</param>
|
||||
void InjectTestNotification(DatabaseNotification notification);
|
||||
|
||||
/// <summary>
|
||||
/// Send a notification to all other servers connected to the same database.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The local server will receive the sent notification itself again.
|
||||
/// </remarks>
|
||||
/// <param name="notification">The notification to send.</param>
|
||||
Task SendNotification(DatabaseNotification notification);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -821,7 +830,7 @@ namespace Content.Server.Database
|
||||
return RunDbCommand(() => _db.GetLastReadRules(player));
|
||||
}
|
||||
|
||||
public Task SetLastReadRules(NetUserId player, DateTimeOffset time)
|
||||
public Task SetLastReadRules(NetUserId player, DateTimeOffset? time)
|
||||
{
|
||||
DbWriteOpsMetric.Inc();
|
||||
return RunDbCommand(() => _db.SetLastReadRules(player, time));
|
||||
@@ -1045,6 +1054,12 @@ namespace Content.Server.Database
|
||||
HandleDatabaseNotification(notification);
|
||||
}
|
||||
|
||||
public Task SendNotification(DatabaseNotification notification)
|
||||
{
|
||||
DbWriteOpsMetric.Inc();
|
||||
return RunDbCommand(() => _db.SendNotification(notification));
|
||||
}
|
||||
|
||||
private async void HandleDatabaseNotification(DatabaseNotification notification)
|
||||
{
|
||||
lock (_notificationHandlers)
|
||||
|
||||
76
Content.Server/Database/ServerDbManagerExt.cs
Normal file
76
Content.Server/Database/ServerDbManagerExt.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using System.Text.Json;
|
||||
using Robust.Shared.Asynchronous;
|
||||
|
||||
namespace Content.Server.Database;
|
||||
|
||||
public static class ServerDbManagerExt
|
||||
{
|
||||
/// <summary>
|
||||
/// Subscribe to a database notification on a specific channel, formatted as JSON.
|
||||
/// </summary>
|
||||
/// <param name="dbManager">The database manager to subscribe on.</param>
|
||||
/// <param name="taskManager">The task manager used to run the main callback on the main thread.</param>
|
||||
/// <param name="sawmill">Sawmill to log any errors to.</param>
|
||||
/// <param name="channel">
|
||||
/// The notification channel to listen on. Only notifications on this channel will be handled.
|
||||
/// </param>
|
||||
/// <param name="action">
|
||||
/// The action to run on the notification data.
|
||||
/// This runs on the main thread.
|
||||
/// </param>
|
||||
/// <param name="earlyFilter">
|
||||
/// An early filter callback that runs before the JSON message is deserialized.
|
||||
/// Return false to not handle the notification.
|
||||
/// This does not run on the main thread.
|
||||
/// </param>
|
||||
/// <param name="filter">
|
||||
/// A filter callback that runs after the JSON message is deserialized.
|
||||
/// Return false to not handle the notification.
|
||||
/// This does not run on the main thread.
|
||||
/// </param>
|
||||
/// <typeparam name="TData">The type of JSON data to deserialize.</typeparam>
|
||||
public static void SubscribeToJsonNotification<TData>(
|
||||
this IServerDbManager dbManager,
|
||||
ITaskManager taskManager,
|
||||
ISawmill sawmill,
|
||||
string channel,
|
||||
Action<TData> action,
|
||||
Func<bool>? earlyFilter = null,
|
||||
Func<TData, bool>? filter = null)
|
||||
{
|
||||
dbManager.SubscribeToNotifications(notification =>
|
||||
{
|
||||
if (notification.Channel != channel)
|
||||
return;
|
||||
|
||||
if (notification.Payload == null)
|
||||
{
|
||||
sawmill.Error($"Got {channel} notification with null payload!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (earlyFilter != null && !earlyFilter())
|
||||
return;
|
||||
|
||||
TData data;
|
||||
try
|
||||
{
|
||||
data = JsonSerializer.Deserialize<TData>(notification.Payload)
|
||||
?? throw new JsonException("Content is null");
|
||||
}
|
||||
catch (JsonException e)
|
||||
{
|
||||
sawmill.Error($"Got invalid JSON in {channel} notification: {e}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (filter != null && !filter(data))
|
||||
return;
|
||||
|
||||
taskManager.RunOnMainThread(() =>
|
||||
{
|
||||
action(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Administration.Managers;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Npgsql;
|
||||
|
||||
namespace Content.Server.Database;
|
||||
@@ -17,6 +18,7 @@ public sealed partial class ServerDbPostgres
|
||||
private static readonly string[] NotificationChannels =
|
||||
[
|
||||
BanManager.BanNotificationChannel,
|
||||
MultiServerKickManager.NotificationChannel,
|
||||
];
|
||||
|
||||
private static readonly TimeSpan ReconnectWaitIncrease = TimeSpan.FromSeconds(10);
|
||||
@@ -111,6 +113,14 @@ public sealed partial class ServerDbPostgres
|
||||
});
|
||||
}
|
||||
|
||||
public override async Task SendNotification(DatabaseNotification notification)
|
||||
{
|
||||
await using var db = await GetDbImpl();
|
||||
|
||||
await db.PgDbContext.Database.ExecuteSqlAsync(
|
||||
$"SELECT pg_notify({notification.Channel}, {notification.Payload})");
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
_notificationTokenSource.Cancel();
|
||||
|
||||
@@ -537,6 +537,12 @@ namespace Content.Server.Database
|
||||
return await base.AddAdminMessage(message);
|
||||
}
|
||||
|
||||
public override Task SendNotification(DatabaseNotification notification)
|
||||
{
|
||||
// Notifications not implemented on SQLite.
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected override DateTime NormalizeDatabaseTime(DateTime time)
|
||||
{
|
||||
DebugTools.Assert(time.Kind == DateTimeKind.Unspecified);
|
||||
|
||||
@@ -1,28 +1,5 @@
|
||||
using Content.Shared.Dice;
|
||||
using Content.Shared.Popups;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Dice;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class DiceSystem : SharedDiceSystem
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
|
||||
public override void Roll(EntityUid uid, DiceComponent? die = null)
|
||||
{
|
||||
if (!Resolve(uid, ref die))
|
||||
return;
|
||||
|
||||
var roll = _random.Next(1, die.Sides + 1);
|
||||
SetCurrentSide(uid, roll, die);
|
||||
|
||||
_popup.PopupEntity(Loc.GetString("dice-component-on-roll-land", ("die", uid), ("currentSide", die.CurrentValue)), uid);
|
||||
_audio.PlayPvs(die.Sound, uid);
|
||||
}
|
||||
}
|
||||
public sealed class DiceSystem : SharedDiceSystem;
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Voting;
|
||||
using Robust.Server;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Diagnostics;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
@@ -11,7 +9,6 @@ namespace Content.Server.Discord.WebhookMessages;
|
||||
|
||||
public sealed class VoteWebhooks : IPostInjectInit
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _entSys = default!;
|
||||
[Dependency] private readonly DiscordWebhook _discord = default!;
|
||||
[Dependency] private readonly IBaseServer _baseServer = default!;
|
||||
|
||||
@@ -18,21 +18,12 @@ public sealed class AirlockSystem : SharedAirlockSystem
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<AirlockComponent, ComponentInit>(OnAirlockInit);
|
||||
SubscribeLocalEvent<AirlockComponent, SignalReceivedEvent>(OnSignalReceived);
|
||||
|
||||
SubscribeLocalEvent<AirlockComponent, PowerChangedEvent>(OnPowerChanged);
|
||||
SubscribeLocalEvent<AirlockComponent, ActivateInWorldEvent>(OnActivate, before: new[] { typeof(DoorSystem) });
|
||||
}
|
||||
|
||||
private void OnAirlockInit(EntityUid uid, AirlockComponent component, ComponentInit args)
|
||||
{
|
||||
if (TryComp<ApcPowerReceiverComponent>(uid, out var receiverComponent))
|
||||
{
|
||||
Appearance.SetData(uid, DoorVisuals.Powered, receiverComponent.Powered);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSignalReceived(EntityUid uid, AirlockComponent component, ref SignalReceivedEvent args)
|
||||
{
|
||||
if (args.Port == component.AutoClosePort && component.AutoClose)
|
||||
@@ -47,11 +38,6 @@ public sealed class AirlockSystem : SharedAirlockSystem
|
||||
component.Powered = args.Powered;
|
||||
Dirty(uid, component);
|
||||
|
||||
if (TryComp<AppearanceComponent>(uid, out var appearanceComponent))
|
||||
{
|
||||
Appearance.SetData(uid, DoorVisuals.Powered, args.Powered, appearanceComponent);
|
||||
}
|
||||
|
||||
if (!TryComp(uid, out DoorComponent? door))
|
||||
return;
|
||||
|
||||
|
||||
@@ -37,8 +37,6 @@ namespace Content.Server.Doors.Systems
|
||||
|
||||
private void PowerChanged(EntityUid uid, FirelockComponent component, ref PowerChangedEvent args)
|
||||
{
|
||||
// TODO this should REALLLLY not be door specific appearance thing.
|
||||
_appearance.SetData(uid, DoorVisuals.Powered, args.Powered);
|
||||
component.Powered = args.Powered;
|
||||
Dirty(uid, component);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ public sealed partial class ExplosionReactionEffect : EntityEffect
|
||||
/// The type of explosion. Determines damage types and tile break chance scaling.
|
||||
/// </summary>
|
||||
[DataField(required: true, customTypeSerializer: typeof(PrototypeIdSerializer<ExplosionPrototype>))]
|
||||
[JsonIgnore]
|
||||
public string ExplosionType = default!;
|
||||
|
||||
/// <summary>
|
||||
@@ -23,14 +22,12 @@ public sealed partial class ExplosionReactionEffect : EntityEffect
|
||||
/// chance.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[JsonIgnore]
|
||||
public float MaxIntensity = 5;
|
||||
|
||||
/// <summary>
|
||||
/// How quickly intensity drops off as you move away from the epicenter
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[JsonIgnore]
|
||||
public float IntensitySlope = 1;
|
||||
|
||||
/// <summary>
|
||||
@@ -41,15 +38,20 @@ public sealed partial class ExplosionReactionEffect : EntityEffect
|
||||
/// A slope of 1 and MaxTotalIntensity of 100 corresponds to a radius of around 4.5 tiles.
|
||||
/// </remarks>
|
||||
[DataField]
|
||||
[JsonIgnore]
|
||||
public float MaxTotalIntensity = 100;
|
||||
|
||||
/// <summary>
|
||||
/// The intensity of the explosion per unit reaction.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[JsonIgnore]
|
||||
public float IntensityPerUnit = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Factor used to scale the explosion intensity when calculating tile break chances. Allows for stronger
|
||||
/// explosives that don't space tiles, without having to create a new explosion-type prototype.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float TileBreakScale = 1f;
|
||||
|
||||
public override bool ShouldLog => true;
|
||||
|
||||
@@ -72,6 +74,7 @@ public sealed partial class ExplosionReactionEffect : EntityEffect
|
||||
ExplosionType,
|
||||
intensity,
|
||||
IntensitySlope,
|
||||
MaxIntensity);
|
||||
MaxIntensity,
|
||||
TileBreakScale);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,8 @@ namespace Content.Server.EntityEffects.Effects
|
||||
reagentArgs.EntityManager.System<FlammableSystem>().AdjustFireStacks(args.TargetEntity, quantity * multiplier, flammable);
|
||||
if (reagentArgs.Reagent != null)
|
||||
reagentArgs.Source?.RemoveReagent(reagentArgs.Reagent.ID, reagentArgs.Quantity);
|
||||
} else
|
||||
}
|
||||
else
|
||||
{
|
||||
args.EntityManager.System<FlammableSystem>().AdjustFireStacks(args.TargetEntity, multiplier, flammable);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.EntityEffects;
|
||||
@@ -19,13 +20,17 @@ public sealed partial class Ignite : EntityEffect
|
||||
|
||||
public override void Effect(EntityEffectBaseArgs args)
|
||||
{
|
||||
if (!args.EntityManager.TryGetComponent(args.TargetEntity, out FlammableComponent? flammable))
|
||||
return;
|
||||
|
||||
var flamSys = args.EntityManager.System<FlammableSystem>();
|
||||
if (args is EntityEffectReagentArgs reagentArgs)
|
||||
{
|
||||
flamSys.Ignite(reagentArgs.TargetEntity, reagentArgs.OrganEntity ?? reagentArgs.TargetEntity);
|
||||
} else
|
||||
flamSys.Ignite(reagentArgs.TargetEntity, reagentArgs.OrganEntity ?? reagentArgs.TargetEntity, flammable: flammable);
|
||||
}
|
||||
else
|
||||
{
|
||||
flamSys.Ignite(args.TargetEntity, args.TargetEntity);
|
||||
flamSys.Ignite(args.TargetEntity, args.TargetEntity, flammable: flammable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,6 +152,7 @@ namespace Content.Server.Entry
|
||||
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<GameTicker>().PostInitialize();
|
||||
IoCManager.Resolve<IBanManager>().Initialize();
|
||||
IoCManager.Resolve<IConnectionManager>().PostInit();
|
||||
IoCManager.Resolve<MultiServerKickManager>().Initialize();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -69,14 +69,18 @@ public sealed class ExplosionGridTileFlood : ExplosionTileFlood
|
||||
return;
|
||||
|
||||
_needToTransform = true;
|
||||
var transform = IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(Grid.Owner);
|
||||
var size = (float) Grid.TileSize;
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
|
||||
var transformSystem = entityManager.System<SharedTransformSystem>();
|
||||
var transform = entityManager.GetComponent<TransformComponent>(Grid.Owner);
|
||||
var size = (float)Grid.TileSize;
|
||||
|
||||
_matrix.M31 = size / 2;
|
||||
_matrix.M32 = size / 2;
|
||||
Matrix3x2.Invert(spaceMatrix, out var invSpace);
|
||||
_matrix *= transform.WorldMatrix * invSpace;
|
||||
var relativeAngle = transform.WorldRotation - spaceAngle;
|
||||
var (_, relativeAngle, worldMatrix) = transformSystem.GetWorldPositionRotationMatrix(transform);
|
||||
relativeAngle -= spaceAngle;
|
||||
_matrix *= worldMatrix * invSpace;
|
||||
_offset = relativeAngle.RotateVec(new Vector2(size / 4, size / 4));
|
||||
}
|
||||
|
||||
|
||||
@@ -71,8 +71,7 @@ public sealed partial class ExplosionSystem
|
||||
{
|
||||
var targetGrid = Comp<MapGridComponent>(referenceGrid.Value);
|
||||
var xform = Transform(referenceGrid.Value);
|
||||
targetAngle = xform.WorldRotation;
|
||||
targetMatrix = xform.InvWorldMatrix;
|
||||
(_, targetAngle, targetMatrix) = _transformSystem.GetWorldPositionRotationInvMatrix(xform);
|
||||
tileSize = targetGrid.TileSize;
|
||||
}
|
||||
|
||||
|
||||
@@ -89,8 +89,7 @@ public sealed partial class ExplosionSystem
|
||||
if (referenceGrid != null)
|
||||
{
|
||||
var xform = Transform(Comp<MapGridComponent>(referenceGrid.Value).Owner);
|
||||
spaceMatrix = xform.WorldMatrix;
|
||||
spaceAngle = xform.WorldRotation;
|
||||
(_, spaceAngle, spaceMatrix) = _transformSystem.GetWorldPositionRotationMatrix(xform);
|
||||
}
|
||||
|
||||
// is the explosion starting on a grid?
|
||||
|
||||
@@ -3,10 +3,8 @@ using System.Numerics;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Explosion.Components;
|
||||
using Content.Server.NodeContainer.EntitySystems;
|
||||
using Content.Server.NPC.Pathfinding;
|
||||
using Content.Shared.Armor;
|
||||
using Content.Shared.Camera;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Damage;
|
||||
@@ -18,8 +16,6 @@ using Content.Shared.GameTicking;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Projectiles;
|
||||
using Content.Shared.Throwing;
|
||||
using Content.Shared.Explosion.Components;
|
||||
using Content.Shared.Explosion.EntitySystems;
|
||||
using Robust.Server.GameStates;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
|
||||
@@ -36,7 +36,7 @@ public sealed partial class TriggerSystem
|
||||
// Re-check for contacts as we cleared them.
|
||||
else if (TryComp<PhysicsComponent>(uid, out var body))
|
||||
{
|
||||
_broadphase.RegenerateContacts(uid, body);
|
||||
_broadphase.RegenerateContacts((uid, body));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ namespace Content.Server.Explosion.EntitySystems
|
||||
|
||||
private void HandleShockTrigger(Entity<ShockOnTriggerComponent> shockOnTrigger, ref TriggerEvent args)
|
||||
{
|
||||
if (!_container.TryGetContainingContainer(shockOnTrigger, out var container))
|
||||
if (!_container.TryGetContainingContainer(shockOnTrigger.Owner, out var container))
|
||||
return;
|
||||
|
||||
var containerEnt = container.Owner;
|
||||
|
||||
@@ -50,6 +50,7 @@ public sealed class FaxSystem : EntitySystem
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly FaxecuteSystem _faxecute = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
|
||||
private const string PaperSlotId = "Paper";
|
||||
|
||||
@@ -227,7 +228,7 @@ public sealed class FaxSystem : EntitySystem
|
||||
return;
|
||||
}
|
||||
|
||||
if (component.KnownFaxes.ContainsValue(newName) && !HasComp<EmaggedComponent>(uid)) // Allow existing names if emagged for fun
|
||||
if (component.KnownFaxes.ContainsValue(newName) && !_emag.CheckFlag(uid, EmagType.Interaction)) // Allow existing names if emagged for fun
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("fax-machine-popup-name-exist"), uid);
|
||||
return;
|
||||
@@ -246,7 +247,12 @@ public sealed class FaxSystem : EntitySystem
|
||||
|
||||
private void OnEmagged(EntityUid uid, FaxMachineComponent component, ref GotEmaggedEvent args)
|
||||
{
|
||||
_audioSystem.PlayPvs(component.EmagSound, uid);
|
||||
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
if (_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
@@ -260,7 +266,7 @@ public sealed class FaxSystem : EntitySystem
|
||||
switch (command)
|
||||
{
|
||||
case FaxConstants.FaxPingCommand:
|
||||
var isForSyndie = HasComp<EmaggedComponent>(uid) &&
|
||||
var isForSyndie = _emag.CheckFlag(uid, EmagType.Interaction) &&
|
||||
args.Data.ContainsKey(FaxConstants.FaxSyndicateData);
|
||||
if (!isForSyndie && !component.ResponsePings)
|
||||
return;
|
||||
@@ -405,7 +411,7 @@ public sealed class FaxSystem : EntitySystem
|
||||
{ DeviceNetworkConstants.Command, FaxConstants.FaxPingCommand }
|
||||
};
|
||||
|
||||
if (HasComp<EmaggedComponent>(uid))
|
||||
if (_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
payload.Add(FaxConstants.FaxSyndicateData, true);
|
||||
|
||||
_deviceNetworkSystem.QueuePacket(uid, null, payload);
|
||||
@@ -445,6 +451,9 @@ public sealed class FaxSystem : EntitySystem
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
if (component.SendTimeoutRemaining > 0)
|
||||
return;
|
||||
|
||||
var sendEntity = component.PaperSlot.Item;
|
||||
if (sendEntity == null)
|
||||
return;
|
||||
@@ -489,6 +498,9 @@ public sealed class FaxSystem : EntitySystem
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
if (component.SendTimeoutRemaining > 0)
|
||||
return;
|
||||
|
||||
var sendEntity = component.PaperSlot.Item;
|
||||
if (sendEntity == null)
|
||||
return;
|
||||
|
||||
@@ -84,7 +84,7 @@ public sealed class PuddleDebugDebugOverlaySystem : SharedPuddleDebugOverlaySyst
|
||||
data.Add(new PuddleDebugOverlayData(pos, vol));
|
||||
}
|
||||
|
||||
RaiseNetworkEvent(new PuddleOverlayDebugMessage(GetNetEntity(gridUid), data.ToArray()));
|
||||
RaiseNetworkEvent(new PuddleOverlayDebugMessage(GetNetEntity(gridUid), data.ToArray()), session);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -228,7 +228,7 @@ public sealed class SmokeSystem : EntitySystem
|
||||
var xform = Transform(uid);
|
||||
_physics.SetBodyType(uid, BodyType.Dynamic, fixtures, body, xform);
|
||||
_physics.SetCanCollide(uid, true, manager: fixtures, body: body);
|
||||
_broadphase.RegenerateContacts(uid, body, fixtures, xform);
|
||||
_broadphase.RegenerateContacts((uid, body, fixtures, xform));
|
||||
}
|
||||
|
||||
var timer = EnsureComp<TimedDespawnComponent>(uid);
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
namespace Content.Server.Forensics;
|
||||
|
||||
/// <summary>
|
||||
/// This component is for mobs that have DNA.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class DnaComponent : Component
|
||||
{
|
||||
[DataField("dna"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public string DNA = String.Empty;
|
||||
}
|
||||
@@ -17,7 +17,6 @@ namespace Content.Server.Forensics
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private readonly InventorySystem _inventory = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly LabelSystem _label = default!;
|
||||
|
||||
public override void Initialize()
|
||||
|
||||
@@ -10,6 +10,7 @@ using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Forensics;
|
||||
using Content.Shared.Forensics.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Inventory;
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Server.Administration.Systems;
|
||||
using Content.Server.GameTicking.Events;
|
||||
using Content.Server.Spawners.Components;
|
||||
using Content.Server.Speech.Components;
|
||||
@@ -27,6 +28,7 @@ namespace Content.Server.GameTicking
|
||||
{
|
||||
[Dependency] private readonly IAdminManager _adminManager = default!;
|
||||
[Dependency] private readonly SharedJobSystem _jobs = default!;
|
||||
[Dependency] private readonly AdminSystem _admin = default!;
|
||||
|
||||
[ValidatePrototypeId<EntityPrototype>]
|
||||
public const string ObserverPrototypeName = "MobObserver";
|
||||
@@ -233,6 +235,7 @@ namespace Content.Server.GameTicking
|
||||
|
||||
_roles.MindAddJobRole(newMind, silent: silent, jobPrototype:jobId);
|
||||
var jobName = _jobs.MindTryGetJobName(newMind);
|
||||
_admin.UpdatePlayerList(player);
|
||||
|
||||
if (lateJoin && !silent)
|
||||
{
|
||||
@@ -420,7 +423,7 @@ namespace Content.Server.GameTicking
|
||||
{
|
||||
var gridXform = Transform(gridUid);
|
||||
|
||||
return new EntityCoordinates(gridUid, Vector2.Transform(toMap.Position, gridXform.InvWorldMatrix));
|
||||
return new EntityCoordinates(gridUid, Vector2.Transform(toMap.Position, _transform.GetInvWorldMatrix(gridXform)));
|
||||
}
|
||||
|
||||
return spawn;
|
||||
|
||||
@@ -57,7 +57,6 @@ namespace Content.Server.Ghost
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
@@ -503,7 +502,7 @@ namespace Content.Server.Ghost
|
||||
var playerEntity = mind.CurrentEntity;
|
||||
|
||||
if (playerEntity != null && viaCommand)
|
||||
_adminLogger.Add(LogType.Mind, $"{EntityManager.ToPrettyString(playerEntity.Value):player} is attempting to ghost via command");
|
||||
_adminLog.Add(LogType.Mind, $"{EntityManager.ToPrettyString(playerEntity.Value):player} is attempting to ghost via command");
|
||||
|
||||
var handleEv = new GhostAttemptHandleEvent(mind, canReturnGlobal);
|
||||
RaiseLocalEvent(handleEv);
|
||||
@@ -575,7 +574,7 @@ namespace Content.Server.Ghost
|
||||
}
|
||||
|
||||
if (playerEntity != null)
|
||||
_adminLogger.Add(LogType.Mind, $"{EntityManager.ToPrettyString(playerEntity.Value):player} ghosted{(!canReturn ? " (non-returnable)" : "")}");
|
||||
_adminLog.Add(LogType.Mind, $"{EntityManager.ToPrettyString(playerEntity.Value):player} ghosted{(!canReturn ? " (non-returnable)" : "")}");
|
||||
|
||||
var ghost = SpawnGhost((mindId, mind), position, canReturn);
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ using Content.Shared.Telephone;
|
||||
using Content.Shared.UserInterface;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.GameStates;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -35,22 +36,15 @@ public sealed class HolopadSystem : SharedHolopadSystem
|
||||
[Dependency] private readonly ChatSystem _chatSystem = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly PvsOverrideSystem _pvs = default!;
|
||||
|
||||
private float _updateTimer = 1.0f;
|
||||
|
||||
private const float UpdateTime = 1.0f;
|
||||
private const float MinTimeBetweenSyncRequests = 0.5f;
|
||||
private TimeSpan _minTimeSpanBetweenSyncRequests;
|
||||
|
||||
private HashSet<EntityUid> _pendingRequestsForSpriteState = new();
|
||||
private HashSet<EntityUid> _recentlyUpdatedHolograms = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_minTimeSpanBetweenSyncRequests = TimeSpan.FromSeconds(MinTimeBetweenSyncRequests);
|
||||
|
||||
// Holopad UI and bound user interface messages
|
||||
SubscribeLocalEvent<HolopadComponent, BeforeActivatableUIOpenEvent>(OnUIOpen);
|
||||
SubscribeLocalEvent<HolopadComponent, HolopadStartNewCallMessage>(OnHolopadStartNewCall);
|
||||
@@ -68,7 +62,6 @@ public sealed class HolopadSystem : SharedHolopadSystem
|
||||
|
||||
// Networked events
|
||||
SubscribeNetworkEvent<HolopadUserTypingChangedEvent>(OnTypingChanged);
|
||||
SubscribeNetworkEvent<PlayerSpriteStateMessage>(OnPlayerSpriteStateMessage);
|
||||
|
||||
// Component start/shutdown events
|
||||
SubscribeLocalEvent<HolopadComponent, ComponentInit>(OnHolopadInit);
|
||||
@@ -268,16 +261,11 @@ public sealed class HolopadSystem : SharedHolopadSystem
|
||||
if (source.Comp.Hologram == null)
|
||||
GenerateHologram(source);
|
||||
|
||||
// Receiver holopad holograms have to be generated now instead of waiting for their own event
|
||||
// to fire because holographic avatars get synced immediately
|
||||
if (TryComp<HolopadComponent>(args.Receiver, out var receivingHolopad) && receivingHolopad.Hologram == null)
|
||||
GenerateHologram((args.Receiver, receivingHolopad));
|
||||
|
||||
if (source.Comp.User != null)
|
||||
{
|
||||
// Re-link the user to refresh the sprite data
|
||||
LinkHolopadToUser(source, source.Comp.User.Value);
|
||||
}
|
||||
// Re-link the user to refresh the sprite data
|
||||
LinkHolopadToUser(source, source.Comp.User);
|
||||
}
|
||||
|
||||
private void OnHoloCallEnded(Entity<HolopadComponent> entity, ref TelephoneCallEndedEvent args)
|
||||
@@ -323,22 +311,6 @@ public sealed class HolopadSystem : SharedHolopadSystem
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPlayerSpriteStateMessage(PlayerSpriteStateMessage ev, EntitySessionEventArgs args)
|
||||
{
|
||||
var uid = args.SenderSession.AttachedEntity;
|
||||
|
||||
if (!Exists(uid))
|
||||
return;
|
||||
|
||||
if (!_pendingRequestsForSpriteState.Remove(uid.Value))
|
||||
return;
|
||||
|
||||
if (!TryComp<HolopadUserComponent>(uid, out var holopadUser))
|
||||
return;
|
||||
|
||||
SyncHolopadUserWithLinkedHolograms((uid.Value, holopadUser), ev.SpriteLayerData);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region: Component start/shutdown events
|
||||
@@ -485,8 +457,6 @@ public sealed class HolopadSystem : SharedHolopadSystem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_recentlyUpdatedHolograms.Clear();
|
||||
}
|
||||
|
||||
public void UpdateUIState(Entity<HolopadComponent> entity, TelephoneComponent? telephone = null)
|
||||
@@ -555,10 +525,16 @@ public sealed class HolopadSystem : SharedHolopadSystem
|
||||
QueueDel(hologram);
|
||||
}
|
||||
|
||||
private void LinkHolopadToUser(Entity<HolopadComponent> entity, EntityUid user)
|
||||
private void LinkHolopadToUser(Entity<HolopadComponent> entity, EntityUid? user)
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
UnlinkHolopadFromUser(entity, null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryComp<HolopadUserComponent>(user, out var holopadUser))
|
||||
holopadUser = AddComp<HolopadUserComponent>(user);
|
||||
holopadUser = AddComp<HolopadUserComponent>(user.Value);
|
||||
|
||||
if (user != entity.Comp.User?.Owner)
|
||||
{
|
||||
@@ -567,51 +543,44 @@ public sealed class HolopadSystem : SharedHolopadSystem
|
||||
|
||||
// Assigns the new user in their place
|
||||
holopadUser.LinkedHolopads.Add(entity);
|
||||
entity.Comp.User = (user, holopadUser);
|
||||
entity.Comp.User = (user.Value, holopadUser);
|
||||
}
|
||||
|
||||
if (TryComp<HolographicAvatarComponent>(user, out var avatar))
|
||||
{
|
||||
SyncHolopadUserWithLinkedHolograms((user, holopadUser), avatar.LayerData);
|
||||
return;
|
||||
}
|
||||
|
||||
// We have no apriori sprite data for the hologram, request
|
||||
// the current appearance of the user from the client
|
||||
RequestHolopadUserSpriteUpdate((user, holopadUser));
|
||||
// Add the new user to PVS and sync their appearance with any
|
||||
// holopads connected to the one they are using
|
||||
_pvs.AddGlobalOverride(user.Value);
|
||||
SyncHolopadHologramAppearanceWithTarget(entity, entity.Comp.User);
|
||||
}
|
||||
|
||||
private void UnlinkHolopadFromUser(Entity<HolopadComponent> entity, Entity<HolopadUserComponent>? user)
|
||||
{
|
||||
if (user == null)
|
||||
return;
|
||||
|
||||
entity.Comp.User = null;
|
||||
SyncHolopadHologramAppearanceWithTarget(entity, null);
|
||||
|
||||
foreach (var linkedHolopad in GetLinkedHolopads(entity))
|
||||
{
|
||||
if (linkedHolopad.Comp.Hologram != null)
|
||||
{
|
||||
_appearanceSystem.SetData(linkedHolopad.Comp.Hologram.Value.Owner, TypingIndicatorVisuals.IsTyping, false);
|
||||
|
||||
// Send message with no sprite data to the client
|
||||
// This will set the holgram sprite to a generic icon
|
||||
var ev = new PlayerSpriteStateMessage(GetNetEntity(linkedHolopad.Comp.Hologram.Value));
|
||||
RaiseNetworkEvent(ev);
|
||||
}
|
||||
}
|
||||
|
||||
if (!HasComp<HolopadUserComponent>(user))
|
||||
if (user == null)
|
||||
return;
|
||||
|
||||
user.Value.Comp.LinkedHolopads.Remove(entity);
|
||||
|
||||
if (!user.Value.Comp.LinkedHolopads.Any())
|
||||
if (!user.Value.Comp.LinkedHolopads.Any() &&
|
||||
user.Value.Comp.LifeStage < ComponentLifeStage.Stopping)
|
||||
{
|
||||
_pendingRequestsForSpriteState.Remove(user.Value);
|
||||
_pvs.RemoveGlobalOverride(user.Value);
|
||||
RemComp<HolopadUserComponent>(user.Value);
|
||||
}
|
||||
}
|
||||
private void SyncHolopadHologramAppearanceWithTarget(Entity<HolopadComponent> entity, Entity<HolopadUserComponent>? user)
|
||||
{
|
||||
foreach (var linkedHolopad in GetLinkedHolopads(entity))
|
||||
{
|
||||
if (linkedHolopad.Comp.Hologram == null)
|
||||
continue;
|
||||
|
||||
if (user.Value.Comp.LifeStage < ComponentLifeStage.Stopping)
|
||||
RemComp<HolopadUserComponent>(user.Value);
|
||||
if (user == null)
|
||||
_appearanceSystem.SetData(linkedHolopad.Comp.Hologram.Value.Owner, TypingIndicatorVisuals.IsTyping, false);
|
||||
|
||||
linkedHolopad.Comp.Hologram.Value.Comp.LinkedEntity = user;
|
||||
Dirty(linkedHolopad.Comp.Hologram.Value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -646,31 +615,6 @@ public sealed class HolopadSystem : SharedHolopadSystem
|
||||
Dirty(entity);
|
||||
}
|
||||
|
||||
private void RequestHolopadUserSpriteUpdate(Entity<HolopadUserComponent> user)
|
||||
{
|
||||
if (!_pendingRequestsForSpriteState.Add(user))
|
||||
return;
|
||||
|
||||
var ev = new PlayerSpriteStateRequest(GetNetEntity(user));
|
||||
RaiseNetworkEvent(ev);
|
||||
}
|
||||
|
||||
private void SyncHolopadUserWithLinkedHolograms(Entity<HolopadUserComponent> entity, PrototypeLayerData[]? spriteLayerData)
|
||||
{
|
||||
foreach (var linkedHolopad in entity.Comp.LinkedHolopads)
|
||||
{
|
||||
foreach (var receivingHolopad in GetLinkedHolopads(linkedHolopad))
|
||||
{
|
||||
if (receivingHolopad.Comp.Hologram == null || !_recentlyUpdatedHolograms.Add(receivingHolopad.Comp.Hologram.Value))
|
||||
continue;
|
||||
|
||||
var netHologram = GetNetEntity(receivingHolopad.Comp.Hologram.Value);
|
||||
var ev = new PlayerSpriteStateMessage(netHologram, spriteLayerData);
|
||||
RaiseNetworkEvent(ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ActivateProjector(Entity<HolopadComponent> entity, EntityUid user)
|
||||
{
|
||||
if (!TryComp<TelephoneComponent>(entity, out var receiverTelephone))
|
||||
|
||||
@@ -20,40 +20,6 @@ public sealed partial class HumanoidAppearanceSystem : SharedHumanoidAppearanceS
|
||||
SubscribeLocalEvent<HumanoidAppearanceComponent, GetVerbsEvent<Verb>>(OnVerbsRequest);
|
||||
}
|
||||
|
||||
// this was done enough times that it only made sense to do it here
|
||||
|
||||
/// <summary>
|
||||
/// Clones a humanoid's appearance to a target mob, provided they both have humanoid components.
|
||||
/// </summary>
|
||||
/// <param name="source">Source entity to fetch the original appearance from.</param>
|
||||
/// <param name="target">Target entity to apply the source entity's appearance to.</param>
|
||||
/// <param name="sourceHumanoid">Source entity's humanoid component.</param>
|
||||
/// <param name="targetHumanoid">Target entity's humanoid component.</param>
|
||||
public void CloneAppearance(EntityUid source, EntityUid target, HumanoidAppearanceComponent? sourceHumanoid = null,
|
||||
HumanoidAppearanceComponent? targetHumanoid = null)
|
||||
{
|
||||
if (!Resolve(source, ref sourceHumanoid) || !Resolve(target, ref targetHumanoid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
targetHumanoid.Species = sourceHumanoid.Species;
|
||||
targetHumanoid.SkinColor = sourceHumanoid.SkinColor;
|
||||
targetHumanoid.EyeColor = sourceHumanoid.EyeColor;
|
||||
targetHumanoid.Age = sourceHumanoid.Age;
|
||||
SetSex(target, sourceHumanoid.Sex, false, targetHumanoid);
|
||||
targetHumanoid.CustomBaseLayers = new(sourceHumanoid.CustomBaseLayers);
|
||||
targetHumanoid.MarkingSet = new(sourceHumanoid.MarkingSet);
|
||||
|
||||
targetHumanoid.Gender = sourceHumanoid.Gender;
|
||||
if (TryComp<GrammarComponent>(target, out var grammar))
|
||||
{
|
||||
grammar.Gender = sourceHumanoid.Gender;
|
||||
}
|
||||
|
||||
Dirty(target, targetHumanoid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a marking from a humanoid by ID.
|
||||
/// </summary>
|
||||
|
||||
@@ -103,6 +103,8 @@ public sealed class IdentitySystem : SharedIdentitySystem
|
||||
// If presumed name is null and we're using that, we set proper noun to be false ("the old woman")
|
||||
if (name != representation.TrueName && representation.PresumedName == null)
|
||||
identityGrammar.ProperNoun = false;
|
||||
|
||||
Dirty(ident, identityGrammar);
|
||||
}
|
||||
|
||||
if (name == Name(ident))
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using Content.Shared.Implants.Components;
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Shared.Implants.Components;
|
||||
using Content.Shared.Storage;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Server.Implants;
|
||||
@@ -9,17 +11,29 @@ public sealed partial class ImplanterSystem
|
||||
{
|
||||
SubscribeLocalEvent<ImplantedComponent, ComponentInit>(OnImplantedInit);
|
||||
SubscribeLocalEvent<ImplantedComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<ImplantedComponent, BeingGibbedEvent>(OnGibbed);
|
||||
}
|
||||
|
||||
private void OnImplantedInit(EntityUid uid, ImplantedComponent component, ComponentInit args)
|
||||
private void OnImplantedInit(Entity<ImplantedComponent> ent, ref ComponentInit args)
|
||||
{
|
||||
component.ImplantContainer = _container.EnsureContainer<Container>(uid, ImplanterComponent.ImplantSlotId);
|
||||
component.ImplantContainer.OccludesLight = false;
|
||||
ent.Comp.ImplantContainer = _container.EnsureContainer<Container>(ent.Owner, ImplanterComponent.ImplantSlotId);
|
||||
ent.Comp.ImplantContainer.OccludesLight = false;
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, ImplantedComponent component, ComponentShutdown args)
|
||||
private void OnShutdown(Entity<ImplantedComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
//If the entity is deleted, get rid of the implants
|
||||
_container.CleanContainer(component.ImplantContainer);
|
||||
_container.CleanContainer(ent.Comp.ImplantContainer);
|
||||
}
|
||||
|
||||
private void OnGibbed(Entity<ImplantedComponent> ent, ref BeingGibbedEvent args)
|
||||
{
|
||||
// Drop the storage implant contents before the implants are deleted by the body being gibbed
|
||||
foreach (var implant in ent.Comp.ImplantContainer.ContainedEntities)
|
||||
{
|
||||
if (TryComp<StorageComponent>(implant, out var storage))
|
||||
_container.EmptyContainer(storage.Container, destination: Transform(ent).Coordinates);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,17 +58,6 @@ public sealed partial class ImplanterSystem : SharedImplanterSystem
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we are trying to implant a implant which is already implanted
|
||||
if (implant.HasValue && !component.AllowMultipleImplants && CheckSameImplant(target, implant.Value))
|
||||
{
|
||||
var name = Identity.Name(target, EntityManager, args.User);
|
||||
var msg = Loc.GetString("implanter-component-implant-already", ("implant", implant), ("target", name));
|
||||
_popup.PopupEntity(msg, target, args.User);
|
||||
args.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//Implant self instantly, otherwise try to inject the target.
|
||||
if (args.User == target)
|
||||
Implant(target, target, uid, component);
|
||||
@@ -79,14 +68,7 @@ public sealed partial class ImplanterSystem : SharedImplanterSystem
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
public bool CheckSameImplant(EntityUid target, EntityUid implant)
|
||||
{
|
||||
if (!TryComp<ImplantedComponent>(target, out var implanted))
|
||||
return false;
|
||||
|
||||
var implantPrototype = Prototype(implant);
|
||||
return implanted.ImplantContainer.ContainedEntities.Any(entity => Prototype(entity) == implantPrototype);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to implant someone else.
|
||||
|
||||
76
Content.Server/Implants/RadioImplantSystem.cs
Normal file
76
Content.Server/Implants/RadioImplantSystem.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using Content.Server.Radio.Components;
|
||||
using Content.Shared.Implants;
|
||||
using Content.Shared.Implants.Components;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Server.Implants;
|
||||
|
||||
public sealed class RadioImplantSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<RadioImplantComponent, ImplantImplantedEvent>(OnImplantImplanted);
|
||||
SubscribeLocalEvent<RadioImplantComponent, EntGotRemovedFromContainerMessage>(OnRemove);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If implanted with a radio implant, installs the necessary intrinsic radio components
|
||||
/// </summary>
|
||||
private void OnImplantImplanted(Entity<RadioImplantComponent> ent, ref ImplantImplantedEvent args)
|
||||
{
|
||||
if (args.Implanted == null)
|
||||
return;
|
||||
|
||||
var activeRadio = EnsureComp<ActiveRadioComponent>(args.Implanted.Value);
|
||||
foreach (var channel in ent.Comp.RadioChannels)
|
||||
{
|
||||
if (activeRadio.Channels.Add(channel))
|
||||
ent.Comp.ActiveAddedChannels.Add(channel);
|
||||
}
|
||||
|
||||
EnsureComp<IntrinsicRadioReceiverComponent>(args.Implanted.Value);
|
||||
|
||||
var intrinsicRadioTransmitter = EnsureComp<IntrinsicRadioTransmitterComponent>(args.Implanted.Value);
|
||||
foreach (var channel in ent.Comp.RadioChannels)
|
||||
{
|
||||
if (intrinsicRadioTransmitter.Channels.Add(channel))
|
||||
ent.Comp.TransmitterAddedChannels.Add(channel);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes intrinsic radio components once the Radio Implant is removed
|
||||
/// </summary>
|
||||
private void OnRemove(Entity<RadioImplantComponent> ent, ref EntGotRemovedFromContainerMessage args)
|
||||
{
|
||||
if (TryComp<ActiveRadioComponent>(args.Container.Owner, out var activeRadioComponent))
|
||||
{
|
||||
foreach (var channel in ent.Comp.ActiveAddedChannels)
|
||||
{
|
||||
activeRadioComponent.Channels.Remove(channel);
|
||||
}
|
||||
ent.Comp.ActiveAddedChannels.Clear();
|
||||
|
||||
if (activeRadioComponent.Channels.Count == 0)
|
||||
{
|
||||
RemCompDeferred<ActiveRadioComponent>(args.Container.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
if (!TryComp<IntrinsicRadioTransmitterComponent>(args.Container.Owner, out var radioTransmitterComponent))
|
||||
return;
|
||||
|
||||
foreach (var channel in ent.Comp.TransmitterAddedChannels)
|
||||
{
|
||||
radioTransmitterComponent.Channels.Remove(channel);
|
||||
}
|
||||
ent.Comp.TransmitterAddedChannels.Clear();
|
||||
|
||||
if (radioTransmitterComponent.Channels.Count == 0 || activeRadioComponent?.Channels.Count == 0)
|
||||
{
|
||||
RemCompDeferred<IntrinsicRadioTransmitterComponent>(args.Container.Owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ using Content.Server.Store.Components;
|
||||
using Content.Server.Store.Systems;
|
||||
using Content.Shared.Cuffs.Components;
|
||||
using Content.Shared.Forensics;
|
||||
using Content.Shared.Forensics.Components;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Implants;
|
||||
using Content.Shared.Implants.Components;
|
||||
@@ -22,6 +23,7 @@ using System.Numerics;
|
||||
using Content.Shared.Movement.Pulling.Components;
|
||||
using Content.Shared.Movement.Pulling.Systems;
|
||||
using Content.Server.IdentityManagement;
|
||||
using Content.Server.DetailExaminable;
|
||||
using Content.Shared.Store.Components;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Map.Components;
|
||||
@@ -225,6 +227,7 @@ public sealed class SubdermalImplantSystem : SharedSubdermalImplantSystem
|
||||
{
|
||||
fingerprint.Fingerprint = _forensicsSystem.GenerateFingerprint();
|
||||
}
|
||||
RemComp<DetailExaminableComponent>(ent); // remove MRP+ custom description if one exists
|
||||
_identity.QueueIdentityUpdate(ent); // manually queue identity update since we don't raise the event
|
||||
_popup.PopupEntity(Loc.GetString("scramble-implant-activated-popup"), ent, ent);
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ public sealed class RulesManager
|
||||
{
|
||||
PopupTime = _cfg.GetCVar(CCVars.RulesWaitTime),
|
||||
CoreRules = _cfg.GetCVar(CCVars.RulesFile),
|
||||
ShouldShowRules = !isLocalhost && !hasCooldown
|
||||
ShouldShowRules = !isLocalhost && !hasCooldown,
|
||||
};
|
||||
_netManager.ServerSendMessage(showRulesMessage, e.Channel);
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@ namespace Content.Server.IoC
|
||||
IoCManager.Register<MappingManager>();
|
||||
IoCManager.Register<IWatchlistWebhookManager, WatchlistWebhookManager>();
|
||||
IoCManager.Register<ConnectionManager>();
|
||||
IoCManager.Register<MultiServerKickManager>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11
Content.Server/ItemRecall/ItemRecallSystem.cs
Normal file
11
Content.Server/ItemRecall/ItemRecallSystem.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Content.Shared.ItemRecall;
|
||||
|
||||
namespace Content.Server.ItemRecall;
|
||||
|
||||
/// <summary>
|
||||
/// System for handling the ItemRecall ability for wizards.
|
||||
/// </summary>
|
||||
public sealed partial class ItemRecallSystem : SharedItemRecallSystem
|
||||
{
|
||||
|
||||
}
|
||||
@@ -16,8 +16,10 @@ using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.UserInterface;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Emag.Components;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Lathe;
|
||||
using Content.Shared.Lathe.Prototypes;
|
||||
using Content.Shared.Materials;
|
||||
using Content.Shared.Power;
|
||||
using Content.Shared.ReagentSpeed;
|
||||
@@ -42,6 +44,7 @@ namespace Content.Server.Lathe
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly ContainerSystem _container = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _uiSys = default!;
|
||||
[Dependency] private readonly MaterialStorageSystem _materialStorage = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
@@ -55,6 +58,7 @@ namespace Content.Server.Lathe
|
||||
/// Per-tick cache
|
||||
/// </summary>
|
||||
private readonly List<GasMixture> _environments = new();
|
||||
private readonly HashSet<ProtoId<LatheRecipePrototype>> _availableRecipes = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -154,19 +158,16 @@ namespace Content.Server.Lathe
|
||||
|
||||
public List<ProtoId<LatheRecipePrototype>> GetAvailableRecipes(EntityUid uid, LatheComponent component, bool getUnavailable = false)
|
||||
{
|
||||
_availableRecipes.Clear();
|
||||
AddRecipesFromPacks(_availableRecipes, component.StaticPacks);
|
||||
var ev = new LatheGetRecipesEvent(uid, getUnavailable)
|
||||
{
|
||||
Recipes = new HashSet<ProtoId<LatheRecipePrototype>>(component.StaticRecipes)
|
||||
Recipes = _availableRecipes
|
||||
};
|
||||
RaiseLocalEvent(uid, ev);
|
||||
return ev.Recipes.ToList();
|
||||
}
|
||||
|
||||
public static List<ProtoId<LatheRecipePrototype>> GetAllBaseRecipes(LatheComponent component)
|
||||
{
|
||||
return component.StaticRecipes.Union(component.DynamicRecipes).ToList();
|
||||
}
|
||||
|
||||
public bool TryAddToQueue(EntityUid uid, LatheRecipePrototype recipe, LatheComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
@@ -275,35 +276,42 @@ namespace Content.Server.Lathe
|
||||
_uiSys.SetUiState(uid, LatheUiKey.Key, state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds every unlocked recipe from each pack to the recipes list.
|
||||
/// </summary>
|
||||
public void AddRecipesFromDynamicPacks(ref LatheGetRecipesEvent args, TechnologyDatabaseComponent database, IEnumerable<ProtoId<LatheRecipePackPrototype>> packs)
|
||||
{
|
||||
foreach (var id in packs)
|
||||
{
|
||||
var pack = _proto.Index(id);
|
||||
foreach (var recipe in pack.Recipes)
|
||||
{
|
||||
if (args.getUnavailable || database.UnlockedRecipes.Contains(recipe))
|
||||
args.Recipes.Add(recipe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGetRecipes(EntityUid uid, TechnologyDatabaseComponent component, LatheGetRecipesEvent args)
|
||||
{
|
||||
if (uid != args.Lathe || !TryComp<LatheComponent>(uid, out var latheComponent))
|
||||
return;
|
||||
|
||||
foreach (var recipe in latheComponent.DynamicRecipes)
|
||||
{
|
||||
if (!(args.getUnavailable || component.UnlockedRecipes.Contains(recipe)) || args.Recipes.Contains(recipe))
|
||||
continue;
|
||||
args.Recipes.Add(recipe);
|
||||
}
|
||||
AddRecipesFromDynamicPacks(ref args, component, latheComponent.DynamicPacks);
|
||||
}
|
||||
|
||||
private void GetEmagLatheRecipes(EntityUid uid, EmagLatheRecipesComponent component, LatheGetRecipesEvent args)
|
||||
{
|
||||
if (uid != args.Lathe || !TryComp<TechnologyDatabaseComponent>(uid, out var technologyDatabase))
|
||||
if (uid != args.Lathe)
|
||||
return;
|
||||
if (!args.getUnavailable && !HasComp<EmaggedComponent>(uid))
|
||||
|
||||
if (!args.getUnavailable && !_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
return;
|
||||
foreach (var recipe in component.EmagDynamicRecipes)
|
||||
{
|
||||
if (!(args.getUnavailable || technologyDatabase.UnlockedRecipes.Contains(recipe)) || args.Recipes.Contains(recipe))
|
||||
continue;
|
||||
args.Recipes.Add(recipe);
|
||||
}
|
||||
foreach (var recipe in component.EmagStaticRecipes)
|
||||
{
|
||||
args.Recipes.Add(recipe);
|
||||
}
|
||||
|
||||
AddRecipesFromPacks(args.Recipes, component.EmagStaticPacks);
|
||||
|
||||
if (TryComp<TechnologyDatabaseComponent>(uid, out var database))
|
||||
AddRecipesFromDynamicPacks(ref args, database, component.EmagDynamicPacks);
|
||||
}
|
||||
|
||||
private void OnHeatStartPrinting(EntityUid uid, LatheHeatProducingComponent component, LatheStartPrintingEvent args)
|
||||
|
||||
@@ -19,4 +19,17 @@ public sealed class MagicSystem : SharedMagicSystem
|
||||
{
|
||||
_chat.TrySendInGameICMessage(args.Performer, Loc.GetString(args.Speech), InGameICChatType.Speak, false);
|
||||
}
|
||||
|
||||
public override void OnVoidApplause(VoidApplauseSpellEvent ev)
|
||||
{
|
||||
base.OnVoidApplause(ev);
|
||||
|
||||
_chat.TryEmoteWithChat(ev.Performer, ev.Emote);
|
||||
|
||||
var perfXForm = Transform(ev.Performer);
|
||||
var targetXForm = Transform(ev.Target);
|
||||
|
||||
Spawn(ev.Effect, perfXForm.Coordinates);
|
||||
Spawn(ev.Effect, targetXForm.Coordinates);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ using Content.Server.EUI;
|
||||
using Content.Server.Ghost;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.PowerCell;
|
||||
using Content.Server.Traits.Assorted;
|
||||
using Content.Shared.Traits.Assorted;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Interaction;
|
||||
@@ -193,9 +193,9 @@ public sealed class DefibrillatorSystem : EntitySystem
|
||||
_chatManager.TrySendInGameICMessage(uid, Loc.GetString("defibrillator-rotten"),
|
||||
InGameICChatType.Speak, true);
|
||||
}
|
||||
else if (HasComp<UnrevivableComponent>(target))
|
||||
else if (TryComp<UnrevivableComponent>(target, out var unrevivable))
|
||||
{
|
||||
_chatManager.TrySendInGameICMessage(uid, Loc.GetString("defibrillator-unrevivable"),
|
||||
_chatManager.TrySendInGameICMessage(uid, Loc.GetString(unrevivable.ReasonMessage),
|
||||
InGameICChatType.Speak, true);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -72,7 +72,10 @@ public sealed class HealingSystem : EntitySystem
|
||||
_bloodstreamSystem.TryModifyBleedAmount(entity.Owner, healing.BloodlossModifier);
|
||||
if (isBleeding != bloodstream.BleedAmount > 0)
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("medical-item-stop-bleeding"), entity, args.User);
|
||||
var popup = (args.User == entity.Owner)
|
||||
? Loc.GetString("medical-item-stop-bleeding-self")
|
||||
: Loc.GetString("medical-item-stop-bleeding", ("target", Identity.Entity(entity.Owner, EntityManager)));
|
||||
_popupSystem.PopupEntity(popup, entity, args.User);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,15 +118,15 @@ public sealed class HealingSystem : EntitySystem
|
||||
_audio.PlayPvs(healing.HealingEndSound, entity.Owner, AudioHelpers.WithVariation(0.125f, _random).WithVolume(1f));
|
||||
|
||||
// Logic to determine the whether or not to repeat the healing action
|
||||
args.Repeat = (HasDamage(entity.Comp, healing) && !dontRepeat);
|
||||
args.Repeat = (HasDamage(entity, healing) && !dontRepeat);
|
||||
if (!args.Repeat && !dontRepeat)
|
||||
_popupSystem.PopupEntity(Loc.GetString("medical-item-finished-using", ("item", args.Used)), entity.Owner, args.User);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private bool HasDamage(DamageableComponent component, HealingComponent healing)
|
||||
private bool HasDamage(Entity<DamageableComponent> ent, HealingComponent healing)
|
||||
{
|
||||
var damageableDict = component.Damage.DamageDict;
|
||||
var damageableDict = ent.Comp.Damage.DamageDict;
|
||||
var healingDict = healing.Damage.DamageDict;
|
||||
foreach (var type in healingDict)
|
||||
{
|
||||
@@ -133,6 +136,23 @@ public sealed class HealingSystem : EntitySystem
|
||||
}
|
||||
}
|
||||
|
||||
if (TryComp<BloodstreamComponent>(ent, out var bloodstream))
|
||||
{
|
||||
// Is ent missing blood that we can restore?
|
||||
if (healing.ModifyBloodLevel > 0
|
||||
&& _solutionContainerSystem.ResolveSolution(ent.Owner, bloodstream.BloodSolutionName, ref bloodstream.BloodSolution, out var bloodSolution)
|
||||
&& bloodSolution.Volume < bloodSolution.MaxVolume)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Is ent bleeding and can we stop it?
|
||||
if (healing.BloodlossModifier < 0 && bloodstream.BleedAmount > 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -172,14 +192,7 @@ public sealed class HealingSystem : EntitySystem
|
||||
if (TryComp<StackComponent>(uid, out var stack) && stack.Count < 1)
|
||||
return false;
|
||||
|
||||
var anythingToDo =
|
||||
HasDamage(targetDamage, component) ||
|
||||
component.ModifyBloodLevel > 0 // Special case if healing item can restore lost blood...
|
||||
&& TryComp<BloodstreamComponent>(target, out var bloodstream)
|
||||
&& _solutionContainerSystem.ResolveSolution(target, bloodstream.BloodSolutionName, ref bloodstream.BloodSolution, out var bloodSolution)
|
||||
&& bloodSolution.Volume < bloodSolution.MaxVolume; // ...and there is lost blood to restore.
|
||||
|
||||
if (!anythingToDo)
|
||||
if (!HasDamage((target, targetDamage), component))
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("medical-item-cant-use", ("item", uid)), uid, user);
|
||||
return false;
|
||||
|
||||
@@ -2,7 +2,7 @@ using Content.Server.Body.Components;
|
||||
using Content.Server.Medical.Components;
|
||||
using Content.Server.PowerCell;
|
||||
using Content.Server.Temperature.Components;
|
||||
using Content.Server.Traits.Assorted;
|
||||
using Content.Shared.Traits.Assorted;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.DoAfter;
|
||||
@@ -207,7 +207,7 @@ public sealed class HealthAnalyzerSystem : EntitySystem
|
||||
bleeding = bloodstream.BleedAmount > 0;
|
||||
}
|
||||
|
||||
if (HasComp<UnrevivableComponent>(target))
|
||||
if (TryComp<UnrevivableComponent>(target, out var unrevivableComp) && unrevivableComp.Analyzable)
|
||||
unrevivable = true;
|
||||
|
||||
_uiSystem.ServerSendUiMessage(healthAnalyzer, HealthAnalyzerUiKey.Key, new HealthAnalyzerScannedUserMessage(
|
||||
|
||||
@@ -22,6 +22,7 @@ using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
@@ -42,6 +43,7 @@ public sealed class SuitSensorSystem : EntitySystem
|
||||
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -369,7 +371,7 @@ public sealed class SuitSensorSystem : EntitySystem
|
||||
userJobIcon = card.Comp.JobIcon;
|
||||
|
||||
foreach (var department in card.Comp.JobDepartments)
|
||||
userJobDepartments.Add(Loc.GetString($"department-{department}"));
|
||||
userJobDepartments.Add(Loc.GetString(_proto.Index(department).Name));
|
||||
}
|
||||
|
||||
// get health mob state
|
||||
|
||||
@@ -35,7 +35,7 @@ public sealed class MiningSystem : EntitySystem
|
||||
return;
|
||||
|
||||
var coords = Transform(uid).Coordinates;
|
||||
var toSpawn = _random.Next(proto.MinOreYield, proto.MaxOreYield);
|
||||
var toSpawn = _random.Next(proto.MinOreYield, proto.MaxOreYield+1);
|
||||
for (var i = 0; i < toSpawn; i++)
|
||||
{
|
||||
Spawn(proto.OreEntity, coords.Offset(_random.NextVector2(0.2f)));
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Server.Movement.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class EyeCursorOffsetComponent : SharedEyeCursorOffsetComponent
|
||||
{
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user