Merge pull request #477 from crystallpunk-14/ed-03-10-2024-upstream
Ed 03 10 2024 upstream
This commit is contained in:
@@ -20,7 +20,6 @@ public sealed partial class ContentAudioSystem
|
||||
{
|
||||
[Dependency] private readonly IBaseClient _client = default!;
|
||||
[Dependency] private readonly ClientGameTicker _gameTicker = default!;
|
||||
[Dependency] private readonly IStateManager _stateManager = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
|
||||
private readonly AudioParams _lobbySoundtrackParams = new(-5f, 1, 0, 0, 0, false, 0f);
|
||||
@@ -71,7 +70,7 @@ public sealed partial class ContentAudioSystem
|
||||
Subs.CVar(_configManager, CCVars.LobbyMusicEnabled, LobbyMusicCVarChanged);
|
||||
Subs.CVar(_configManager, CCVars.LobbyMusicVolume, LobbyMusicVolumeCVarChanged);
|
||||
|
||||
_stateManager.OnStateChanged += StateManagerOnStateChanged;
|
||||
_state.OnStateChanged += StateManagerOnStateChanged;
|
||||
|
||||
_client.PlayerLeaveServer += OnLeave;
|
||||
|
||||
@@ -115,7 +114,7 @@ public sealed partial class ContentAudioSystem
|
||||
|
||||
private void LobbyMusicCVarChanged(bool musicEnabled)
|
||||
{
|
||||
if (musicEnabled && _stateManager.CurrentState is LobbyState)
|
||||
if (musicEnabled && _state.CurrentState is LobbyState)
|
||||
{
|
||||
StartLobbyMusic();
|
||||
}
|
||||
@@ -234,7 +233,7 @@ public sealed partial class ContentAudioSystem
|
||||
|
||||
private void ShutdownLobbyMusic()
|
||||
{
|
||||
_stateManager.OnStateChanged -= StateManagerOnStateChanged;
|
||||
_state.OnStateChanged -= StateManagerOnStateChanged;
|
||||
|
||||
_client.PlayerLeaveServer -= OnLeave;
|
||||
|
||||
|
||||
21
Content.Client/Cargo/Systems/ClientPriceGunSystem.cs
Normal file
21
Content.Client/Cargo/Systems/ClientPriceGunSystem.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using Content.Shared.Timing;
|
||||
using Content.Shared.Cargo.Systems;
|
||||
|
||||
namespace Content.Client.Cargo.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// This handles...
|
||||
/// </summary>
|
||||
public sealed class ClientPriceGunSystem : SharedPriceGunSystem
|
||||
{
|
||||
[Dependency] private readonly UseDelaySystem _useDelay = default!;
|
||||
|
||||
protected override bool GetPriceOrBounty(EntityUid priceGunUid, EntityUid target, EntityUid user)
|
||||
{
|
||||
if (!TryComp(priceGunUid, out UseDelayComponent? useDelay) || _useDelay.IsDelayed((priceGunUid, useDelay)))
|
||||
return false;
|
||||
|
||||
// It feels worse if the cooldown is predicted but the popup isn't! So only do the cooldown reset on the server.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,16 @@ internal sealed class ChatManager : IChatManager
|
||||
_sawmill.Level = LogLevel.Info;
|
||||
}
|
||||
|
||||
public void SendAdminAlert(string message)
|
||||
{
|
||||
// See server-side manager. This just exists for shared code.
|
||||
}
|
||||
|
||||
public void SendAdminAlert(EntityUid player, string message)
|
||||
{
|
||||
// See server-side manager. This just exists for shared code.
|
||||
}
|
||||
|
||||
public void SendMessage(string text, ChatSelectChannel channel)
|
||||
{
|
||||
var str = text.ToString();
|
||||
|
||||
@@ -2,10 +2,8 @@ using Content.Shared.Chat;
|
||||
|
||||
namespace Content.Client.Chat.Managers
|
||||
{
|
||||
public interface IChatManager
|
||||
public interface IChatManager : ISharedChatManager
|
||||
{
|
||||
void Initialize();
|
||||
|
||||
public void SendMessage(string text, ChatSelectChannel channel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,7 +327,8 @@ public sealed class ClientClothingSystem : ClothingSystem
|
||||
if (layerData.State is not null && inventory.SpeciesId is not null && layerData.State.EndsWith(inventory.SpeciesId))
|
||||
continue;
|
||||
|
||||
_displacement.TryAddDisplacement(displacementData, sprite, index, key, revealedLayers);
|
||||
if (_displacement.TryAddDisplacement(displacementData, sprite, index, key, revealedLayers))
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,6 @@ namespace Content.Client.Entry
|
||||
[Dependency] private readonly IResourceManager _resourceManager = default!;
|
||||
[Dependency] private readonly IReplayLoadManager _replayLoad = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly ContentReplayPlaybackManager _replayMan = default!;
|
||||
[Dependency] private readonly DebugMonitorManager _debugMonitorManager = default!;
|
||||
|
||||
public override void Init()
|
||||
@@ -192,7 +191,7 @@ namespace Content.Client.Entry
|
||||
_resourceManager,
|
||||
ReplayConstants.ReplayZipFolder.ToRootedPath());
|
||||
|
||||
_replayMan.LastLoad = (null, ReplayConstants.ReplayZipFolder.ToRootedPath());
|
||||
_playbackMan.LastLoad = (null, ReplayConstants.ReplayZipFolder.ToRootedPath());
|
||||
_replayLoad.LoadAndStartReplay(reader);
|
||||
}
|
||||
else if (_gameController.LaunchState.FromLauncher)
|
||||
|
||||
@@ -98,7 +98,7 @@ namespace Content.Client.Inventory
|
||||
}
|
||||
}
|
||||
|
||||
if (EntMan.TryGetComponent<HandsComponent>(Owner, out var handsComp))
|
||||
if (EntMan.TryGetComponent<HandsComponent>(Owner, out var handsComp) && handsComp.CanBeStripped)
|
||||
{
|
||||
// good ol hands shit code. there is a GuiHands comparer that does the same thing... but these are hands
|
||||
// and not gui hands... which are different...
|
||||
|
||||
@@ -18,8 +18,11 @@ using Content.Client.Viewport;
|
||||
using Content.Client.Voting;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Client.Lobby;
|
||||
using Content.Client.Players.RateLimiting;
|
||||
using Content.Shared.Administration.Managers;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Players.PlayTimeTracking;
|
||||
using Content.Shared.Players.RateLimiting;
|
||||
|
||||
namespace Content.Client.IoC
|
||||
{
|
||||
@@ -31,6 +34,7 @@ namespace Content.Client.IoC
|
||||
|
||||
collection.Register<IParallaxManager, ParallaxManager>();
|
||||
collection.Register<IChatManager, ChatManager>();
|
||||
collection.Register<ISharedChatManager, ChatManager>();
|
||||
collection.Register<IClientPreferencesManager, ClientPreferencesManager>();
|
||||
collection.Register<IStylesheetManager, StylesheetManager>();
|
||||
collection.Register<IScreenshotHook, ScreenshotHook>();
|
||||
@@ -47,10 +51,12 @@ namespace Content.Client.IoC
|
||||
collection.Register<ExtendedDisconnectInformationManager>();
|
||||
collection.Register<JobRequirementsManager>();
|
||||
collection.Register<DocumentParsingManager>();
|
||||
collection.Register<ContentReplayPlaybackManager, ContentReplayPlaybackManager>();
|
||||
collection.Register<ContentReplayPlaybackManager>();
|
||||
collection.Register<ISharedPlaytimeManager, JobRequirementsManager>();
|
||||
collection.Register<MappingManager>();
|
||||
collection.Register<DebugMonitorManager>();
|
||||
collection.Register<PlayerRateLimitManager>();
|
||||
collection.Register<SharedPlayerRateLimitManager, PlayerRateLimitManager>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,6 +319,8 @@ namespace Content.Client.Paper.UI
|
||||
|
||||
private void RunOnSaved()
|
||||
{
|
||||
// Prevent further saving while text processing still in
|
||||
SaveButton.Disabled = true;
|
||||
OnSaved?.Invoke(Rope.Collapse(Input.TextRope));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
using Content.Shared.Players.RateLimiting;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Client.Players.RateLimiting;
|
||||
|
||||
public sealed class PlayerRateLimitManager : SharedPlayerRateLimitManager
|
||||
{
|
||||
public override RateLimitStatus CountAction(ICommonSession player, string key)
|
||||
{
|
||||
// TODO Rate-Limit
|
||||
// Add support for rate limit prediction
|
||||
// I.e., dont mis-predict just because somebody is clicking too quickly.
|
||||
return RateLimitStatus.Allowed;
|
||||
}
|
||||
|
||||
public override void Register(string key, RateLimitRegistration registration)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared.Doors.Components;
|
||||
using Content.Shared.Electrocution;
|
||||
using Content.Shared.Silicons.StationAi;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -6,25 +7,69 @@ namespace Content.Client.Silicons.StationAi;
|
||||
|
||||
public sealed partial class StationAiSystem
|
||||
{
|
||||
private readonly ResPath _aiActionsRsi = new ResPath("/Textures/Interface/Actions/actions_ai.rsi");
|
||||
|
||||
private void InitializeAirlock()
|
||||
{
|
||||
SubscribeLocalEvent<DoorBoltComponent, GetStationAiRadialEvent>(OnDoorBoltGetRadial);
|
||||
SubscribeLocalEvent<AirlockComponent, GetStationAiRadialEvent>(OnEmergencyAccessGetRadial);
|
||||
SubscribeLocalEvent<ElectrifiedComponent, GetStationAiRadialEvent>(OnDoorElectrifiedGetRadial);
|
||||
}
|
||||
|
||||
private void OnDoorBoltGetRadial(Entity<DoorBoltComponent> ent, ref GetStationAiRadialEvent args)
|
||||
{
|
||||
args.Actions.Add(new StationAiRadial()
|
||||
{
|
||||
Sprite = ent.Comp.BoltsDown ?
|
||||
new SpriteSpecifier.Rsi(
|
||||
new ResPath("/Textures/Structures/Doors/Airlocks/Standard/basic.rsi"), "open") :
|
||||
new SpriteSpecifier.Rsi(
|
||||
new ResPath("/Textures/Structures/Doors/Airlocks/Standard/basic.rsi"), "closed"),
|
||||
Tooltip = ent.Comp.BoltsDown ? Loc.GetString("bolt-open") : Loc.GetString("bolt-close"),
|
||||
Event = new StationAiBoltEvent()
|
||||
args.Actions.Add(
|
||||
new StationAiRadial
|
||||
{
|
||||
Bolted = !ent.Comp.BoltsDown,
|
||||
Sprite = ent.Comp.BoltsDown
|
||||
? new SpriteSpecifier.Rsi(_aiActionsRsi, "unbolt_door")
|
||||
: new SpriteSpecifier.Rsi(_aiActionsRsi, "bolt_door"),
|
||||
Tooltip = ent.Comp.BoltsDown
|
||||
? Loc.GetString("bolt-open")
|
||||
: Loc.GetString("bolt-close"),
|
||||
Event = new StationAiBoltEvent
|
||||
{
|
||||
Bolted = !ent.Comp.BoltsDown,
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
private void OnEmergencyAccessGetRadial(Entity<AirlockComponent> ent, ref GetStationAiRadialEvent args)
|
||||
{
|
||||
args.Actions.Add(
|
||||
new StationAiRadial
|
||||
{
|
||||
Sprite = ent.Comp.EmergencyAccess
|
||||
? new SpriteSpecifier.Rsi(_aiActionsRsi, "emergency_off")
|
||||
: new SpriteSpecifier.Rsi(_aiActionsRsi, "emergency_on"),
|
||||
Tooltip = ent.Comp.EmergencyAccess
|
||||
? Loc.GetString("emergency-access-off")
|
||||
: Loc.GetString("emergency-access-on"),
|
||||
Event = new StationAiEmergencyAccessEvent
|
||||
{
|
||||
EmergencyAccess = !ent.Comp.EmergencyAccess,
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private void OnDoorElectrifiedGetRadial(Entity<ElectrifiedComponent> ent, ref GetStationAiRadialEvent args)
|
||||
{
|
||||
args.Actions.Add(
|
||||
new StationAiRadial
|
||||
{
|
||||
Sprite = ent.Comp.Enabled
|
||||
? new SpriteSpecifier.Rsi(_aiActionsRsi, "door_overcharge_off")
|
||||
: new SpriteSpecifier.Rsi(_aiActionsRsi, "door_overcharge_on"),
|
||||
Tooltip = ent.Comp.Enabled
|
||||
? Loc.GetString("electrify-door-off")
|
||||
: Loc.GetString("electrify-door-on"),
|
||||
Event = new StationAiElectrifiedEvent
|
||||
{
|
||||
Electrified = !ent.Comp.Enabled,
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -398,10 +398,6 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
{
|
||||
QueueWindowUpdate();
|
||||
|
||||
// TODO ACTIONS allow buttons to persist across state applications
|
||||
// Then we don't have to interrupt drags any time the buttons get rebuilt.
|
||||
_menuDragHelper.EndDrag();
|
||||
|
||||
if (_actionsSystem != null)
|
||||
_container?.SetActionData(_actionsSystem, _actions.ToArray());
|
||||
}
|
||||
|
||||
@@ -28,14 +28,26 @@ public class ActionButtonContainer : GridContainer
|
||||
get => (ActionButton) GetChild(index);
|
||||
}
|
||||
|
||||
private void BuildActionButtons(int count)
|
||||
public void SetActionData(ActionsSystem system, params EntityUid?[] actionTypes)
|
||||
{
|
||||
var uniqueCount = Math.Min(system.GetClientActions().Count(), actionTypes.Length + 1);
|
||||
var keys = ContentKeyFunctions.GetHotbarBoundKeys();
|
||||
|
||||
Children.Clear();
|
||||
for (var index = 0; index < count; index++)
|
||||
for (var i = 0; i < uniqueCount; i++)
|
||||
{
|
||||
Children.Add(MakeButton(index));
|
||||
if (i >= ChildCount)
|
||||
{
|
||||
AddChild(MakeButton(i));
|
||||
}
|
||||
|
||||
if (!actionTypes.TryGetValue(i, out var action))
|
||||
action = null;
|
||||
((ActionButton) GetChild(i)).UpdateData(action, system);
|
||||
}
|
||||
|
||||
for (var i = ChildCount - 1; i >= uniqueCount; i--)
|
||||
{
|
||||
RemoveChild(GetChild(i));
|
||||
}
|
||||
|
||||
ActionButton MakeButton(int index)
|
||||
@@ -55,20 +67,6 @@ public class ActionButtonContainer : GridContainer
|
||||
}
|
||||
}
|
||||
|
||||
public void SetActionData(ActionsSystem system, params EntityUid?[] actionTypes)
|
||||
{
|
||||
var uniqueCount = Math.Min(system.GetClientActions().Count(), actionTypes.Length + 1);
|
||||
if (ChildCount != uniqueCount)
|
||||
BuildActionButtons(uniqueCount);
|
||||
|
||||
for (var i = 0; i < uniqueCount; i++)
|
||||
{
|
||||
if (!actionTypes.TryGetValue(i, out var action))
|
||||
action = null;
|
||||
((ActionButton) GetChild(i)).UpdateData(action, system);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearActionData()
|
||||
{
|
||||
foreach (var button in Children)
|
||||
|
||||
@@ -35,9 +35,9 @@ public sealed partial class TestPair
|
||||
mapData.GridCoords = new EntityCoordinates(mapData.Grid, 0, 0);
|
||||
var plating = tileDefinitionManager[tile];
|
||||
var platingTile = new Tile(plating.TileId);
|
||||
mapData.Grid.Comp.SetTile(mapData.GridCoords, platingTile);
|
||||
Server.System<SharedMapSystem>().SetTile(mapData.Grid.Owner, mapData.Grid.Comp, mapData.GridCoords, platingTile);
|
||||
mapData.MapCoords = new MapCoordinates(0, 0, mapData.MapId);
|
||||
mapData.Tile = mapData.Grid.Comp.GetAllTiles().First();
|
||||
mapData.Tile = Server.System<SharedMapSystem>().GetAllTiles(mapData.Grid.Owner, mapData.Grid.Comp).First();
|
||||
});
|
||||
|
||||
TestMap = mapData;
|
||||
|
||||
@@ -36,7 +36,9 @@ public static partial class PoolManager
|
||||
(CCVars.ConfigPresetDevelopment.Name, "false"),
|
||||
(CCVars.AdminLogsEnabled.Name, "false"),
|
||||
(CCVars.AutosaveEnabled.Name, "false"),
|
||||
(CVars.NetBufferSize.Name, "0")
|
||||
(CVars.NetBufferSize.Name, "0"),
|
||||
(CCVars.InteractionRateLimitCount.Name, "9999999"),
|
||||
(CCVars.InteractionRateLimitPeriod.Name, "0.1"),
|
||||
};
|
||||
|
||||
public static async Task SetupCVars(RobustIntegrationTest.IntegrationInstance instance, PoolSettings settings)
|
||||
|
||||
@@ -52,7 +52,6 @@ public sealed partial class AdminVerbSystem
|
||||
[Dependency] private readonly StationJobsSystem _stationJobsSystem = default!;
|
||||
[Dependency] private readonly JointSystem _jointSystem = default!;
|
||||
[Dependency] private readonly BatterySystem _batterySystem = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _xformSystem = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaSystem = default!;
|
||||
[Dependency] private readonly GunSystem _gun = default!;
|
||||
|
||||
@@ -90,22 +89,22 @@ public sealed partial class AdminVerbSystem
|
||||
args.Verbs.Add(bolt);
|
||||
}
|
||||
|
||||
if (TryComp<AirlockComponent>(args.Target, out var airlock))
|
||||
if (TryComp<AirlockComponent>(args.Target, out var airlockComp))
|
||||
{
|
||||
Verb emergencyAccess = new()
|
||||
{
|
||||
Text = airlock.EmergencyAccess ? "Emergency Access Off" : "Emergency Access On",
|
||||
Text = airlockComp.EmergencyAccess ? "Emergency Access Off" : "Emergency Access On",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/emergency_access.png")),
|
||||
Act = () =>
|
||||
{
|
||||
_airlockSystem.ToggleEmergencyAccess(args.Target, airlock);
|
||||
_airlockSystem.SetEmergencyAccess((args.Target, airlockComp), !airlockComp.EmergencyAccess);
|
||||
},
|
||||
Impact = LogImpact.Medium,
|
||||
Message = Loc.GetString(airlock.EmergencyAccess
|
||||
Message = Loc.GetString(airlockComp.EmergencyAccess
|
||||
? "admin-trick-emergency-access-off-description"
|
||||
: "admin-trick-emergency-access-on-description"),
|
||||
Priority = (int) (airlock.EmergencyAccess ? TricksVerbPriorities.EmergencyAccessOff : TricksVerbPriorities.EmergencyAccessOn),
|
||||
Priority = (int) (airlockComp.EmergencyAccess ? TricksVerbPriorities.EmergencyAccessOff : TricksVerbPriorities.EmergencyAccessOn),
|
||||
};
|
||||
args.Verbs.Add(emergencyAccess);
|
||||
}
|
||||
@@ -327,7 +326,7 @@ public sealed partial class AdminVerbSystem
|
||||
Act = () =>
|
||||
{
|
||||
var (mapUid, gridUid) = _adminTestArenaSystem.AssertArenaLoaded(player);
|
||||
_xformSystem.SetCoordinates(args.Target, new EntityCoordinates(gridUid ?? mapUid, Vector2.One));
|
||||
_transformSystem.SetCoordinates(args.Target, new EntityCoordinates(gridUid ?? mapUid, Vector2.One));
|
||||
},
|
||||
Impact = LogImpact.Medium,
|
||||
Message = Loc.GetString("admin-trick-send-to-test-arena-description"),
|
||||
@@ -533,7 +532,7 @@ public sealed partial class AdminVerbSystem
|
||||
if (shuttle is null)
|
||||
return;
|
||||
|
||||
_xformSystem.SetCoordinates(args.User, new EntityCoordinates(shuttle.Value, Vector2.Zero));
|
||||
_transformSystem.SetCoordinates(args.User, new EntityCoordinates(shuttle.Value, Vector2.Zero));
|
||||
},
|
||||
Impact = LogImpact.Low,
|
||||
Message = Loc.GetString("admin-trick-locate-cargo-shuttle-description"),
|
||||
|
||||
@@ -35,10 +35,8 @@ using Robust.Shared.Toolshed;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Linq;
|
||||
using Content.Server.Silicons.Laws;
|
||||
using Content.Shared.Silicons.Laws;
|
||||
using Content.Shared.Silicons.Laws.Components;
|
||||
using Robust.Server.Player;
|
||||
using Content.Shared.Mind;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using static Content.Shared.Configurable.ConfigurationComponent;
|
||||
|
||||
@@ -63,7 +61,6 @@ namespace Content.Server.Administration.Systems
|
||||
[Dependency] private readonly ArtifactSystem _artifactSystem = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||
[Dependency] private readonly PrayerSystem _prayerSystem = default!;
|
||||
[Dependency] private readonly EuiManager _eui = default!;
|
||||
[Dependency] private readonly MindSystem _mindSystem = default!;
|
||||
[Dependency] private readonly ToolshedManager _toolshed = default!;
|
||||
[Dependency] private readonly RejuvenateSystem _rejuvenate = default!;
|
||||
@@ -294,7 +291,7 @@ namespace Content.Server.Administration.Systems
|
||||
Act = () =>
|
||||
{
|
||||
var ui = new AdminLogsEui();
|
||||
_eui.OpenEui(ui, player);
|
||||
_euiManager.OpenEui(ui, player);
|
||||
ui.SetLogFilter(search:args.Target.Id.ToString());
|
||||
},
|
||||
Impact = LogImpact.Low
|
||||
|
||||
@@ -15,6 +15,7 @@ using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Players.RateLimiting;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared;
|
||||
@@ -104,12 +105,10 @@ namespace Content.Server.Administration.Systems
|
||||
|
||||
_rateLimit.Register(
|
||||
RateLimitKey,
|
||||
new RateLimitRegistration
|
||||
{
|
||||
CVarLimitPeriodLength = CCVars.AhelpRateLimitPeriod,
|
||||
CVarLimitCount = CCVars.AhelpRateLimitCount,
|
||||
PlayerLimitedAction = PlayerRateLimitedAction
|
||||
});
|
||||
new RateLimitRegistration(CCVars.AhelpRateLimitPeriod,
|
||||
CCVars.AhelpRateLimitCount,
|
||||
PlayerRateLimitedAction)
|
||||
);
|
||||
}
|
||||
|
||||
private void PlayerRateLimitedAction(ICommonSession obj)
|
||||
|
||||
@@ -281,6 +281,17 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares two TileAtmospheres to see if they are within acceptable ranges for group processing to be enabled.
|
||||
/// </summary>
|
||||
public GasCompareResult CompareExchange(TileAtmosphere sample, TileAtmosphere otherSample)
|
||||
{
|
||||
if (sample.AirArchived == null || otherSample.AirArchived == null)
|
||||
return GasCompareResult.NoExchange;
|
||||
|
||||
return CompareExchange(sample.AirArchived, otherSample.AirArchived);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares two gas mixtures to see if they are within acceptable ranges for group processing to be enabled.
|
||||
/// </summary>
|
||||
|
||||
@@ -270,7 +270,7 @@ public sealed partial class AtmosphereSystem
|
||||
{
|
||||
DebugTools.AssertNotNull(tile.Air);
|
||||
DebugTools.Assert(tile.Air?.Immutable == false);
|
||||
Array.Clear(tile.MolesArchived);
|
||||
tile.AirArchived = null;
|
||||
tile.ArchivedCycle = 0;
|
||||
|
||||
var count = 0;
|
||||
|
||||
@@ -210,7 +210,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
MovedByPressureComponent.ProbabilityOffset);
|
||||
|
||||
// Can we yeet the thing (due to probability, strength, etc.)
|
||||
if (moveProb > MovedByPressureComponent.ProbabilityOffset && _robustRandom.Prob(MathF.Min(moveProb / 100f, 1f))
|
||||
if (moveProb > MovedByPressureComponent.ProbabilityOffset && _random.Prob(MathF.Min(moveProb / 100f, 1f))
|
||||
&& !float.IsPositiveInfinity(component.MoveResist)
|
||||
&& (physics.BodyType != BodyType.Static
|
||||
&& (maxForce >= (component.MoveResist * MovedByPressureComponent.MoveForcePushRatio)))
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
}
|
||||
|
||||
shouldShareAir = true;
|
||||
} else if (CompareExchange(tile.Air, enemyTile.Air) != GasCompareResult.NoExchange)
|
||||
} else if (CompareExchange(tile, enemyTile) != GasCompareResult.NoExchange)
|
||||
{
|
||||
AddActiveTile(gridAtmosphere, enemyTile);
|
||||
if (ExcitedGroups)
|
||||
@@ -117,9 +117,8 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
private void Archive(TileAtmosphere tile, int fireCount)
|
||||
{
|
||||
if (tile.Air != null)
|
||||
tile.Air.Moles.AsSpan().CopyTo(tile.MolesArchived.AsSpan());
|
||||
tile.AirArchived = new GasMixture(tile.Air);
|
||||
|
||||
tile.TemperatureArchived = tile.Temperature;
|
||||
tile.ArchivedCycle = fireCount;
|
||||
}
|
||||
|
||||
@@ -184,10 +183,10 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
/// </summary>
|
||||
public float GetHeatCapacityArchived(TileAtmosphere tile)
|
||||
{
|
||||
if (tile.Air == null)
|
||||
if (tile.AirArchived == null)
|
||||
return tile.HeatCapacity;
|
||||
|
||||
return GetHeatCapacityCalculation(tile.MolesArchived!, tile.Space);
|
||||
return GetHeatCapacity(tile.AirArchived);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -195,10 +194,11 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
/// </summary>
|
||||
public float Share(TileAtmosphere tileReceiver, TileAtmosphere tileSharer, int atmosAdjacentTurfs)
|
||||
{
|
||||
if (tileReceiver.Air is not {} receiver || tileSharer.Air is not {} sharer)
|
||||
if (tileReceiver.Air is not {} receiver || tileSharer.Air is not {} sharer ||
|
||||
tileReceiver.AirArchived == null || tileSharer.AirArchived == null)
|
||||
return 0f;
|
||||
|
||||
var temperatureDelta = tileReceiver.TemperatureArchived - tileSharer.TemperatureArchived;
|
||||
var temperatureDelta = tileReceiver.AirArchived.Temperature - tileSharer.AirArchived.Temperature;
|
||||
var absTemperatureDelta = Math.Abs(temperatureDelta);
|
||||
var oldHeatCapacity = 0f;
|
||||
var oldSharerHeatCapacity = 0f;
|
||||
@@ -249,12 +249,12 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
// Transfer of thermal energy (via changed heat capacity) between self and sharer.
|
||||
if (!receiver.Immutable && newHeatCapacity > Atmospherics.MinimumHeatCapacity)
|
||||
{
|
||||
receiver.Temperature = ((oldHeatCapacity * receiver.Temperature) - (heatCapacityToSharer * tileReceiver.TemperatureArchived) + (heatCapacitySharerToThis * tileSharer.TemperatureArchived)) / newHeatCapacity;
|
||||
receiver.Temperature = ((oldHeatCapacity * receiver.Temperature) - (heatCapacityToSharer * tileReceiver.AirArchived.Temperature) + (heatCapacitySharerToThis * tileSharer.AirArchived.Temperature)) / newHeatCapacity;
|
||||
}
|
||||
|
||||
if (!sharer.Immutable && newSharerHeatCapacity > Atmospherics.MinimumHeatCapacity)
|
||||
{
|
||||
sharer.Temperature = ((oldSharerHeatCapacity * sharer.Temperature) - (heatCapacitySharerToThis * tileSharer.TemperatureArchived) + (heatCapacityToSharer * tileReceiver.TemperatureArchived)) / newSharerHeatCapacity;
|
||||
sharer.Temperature = ((oldSharerHeatCapacity * sharer.Temperature) - (heatCapacitySharerToThis * tileSharer.AirArchived.Temperature) + (heatCapacityToSharer * tileReceiver.AirArchived.Temperature)) / newSharerHeatCapacity;
|
||||
}
|
||||
|
||||
// Thermal energy of the system (self and sharer) is unchanged.
|
||||
@@ -273,7 +273,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
var moles = receiver.TotalMoles;
|
||||
var theirMoles = sharer.TotalMoles;
|
||||
|
||||
return (tileReceiver.TemperatureArchived * (moles + movedMoles)) - (tileSharer.TemperatureArchived * (theirMoles - movedMoles)) * Atmospherics.R / receiver.Volume;
|
||||
return (tileReceiver.AirArchived.Temperature * (moles + movedMoles)) - (tileSharer.AirArchived.Temperature * (theirMoles - movedMoles)) * Atmospherics.R / receiver.Volume;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -281,10 +281,11 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
/// </summary>
|
||||
public float TemperatureShare(TileAtmosphere tileReceiver, TileAtmosphere tileSharer, float conductionCoefficient)
|
||||
{
|
||||
if (tileReceiver.Air is not { } receiver || tileSharer.Air is not { } sharer)
|
||||
if (tileReceiver.Air is not { } receiver || tileSharer.Air is not { } sharer ||
|
||||
tileReceiver.AirArchived == null || tileSharer.AirArchived == null)
|
||||
return 0f;
|
||||
|
||||
var temperatureDelta = tileReceiver.TemperatureArchived - tileSharer.TemperatureArchived;
|
||||
var temperatureDelta = tileReceiver.AirArchived.Temperature - tileSharer.AirArchived.Temperature;
|
||||
if (MathF.Abs(temperatureDelta) > Atmospherics.MinimumTemperatureDeltaToConsider)
|
||||
{
|
||||
var heatCapacity = GetHeatCapacityArchived(tileReceiver);
|
||||
@@ -310,10 +311,10 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
/// </summary>
|
||||
public float TemperatureShare(TileAtmosphere tileReceiver, float conductionCoefficient, float sharerTemperature, float sharerHeatCapacity)
|
||||
{
|
||||
if (tileReceiver.Air is not {} receiver)
|
||||
if (tileReceiver.Air is not {} receiver || tileReceiver.AirArchived == null)
|
||||
return 0;
|
||||
|
||||
var temperatureDelta = tileReceiver.TemperatureArchived - sharerTemperature;
|
||||
var temperatureDelta = tileReceiver.AirArchived.Temperature - sharerTemperature;
|
||||
if (MathF.Abs(temperatureDelta) > Atmospherics.MinimumTemperatureDeltaToConsider)
|
||||
{
|
||||
var heatCapacity = GetHeatCapacityArchived(tileReceiver);
|
||||
|
||||
@@ -355,7 +355,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
continue;
|
||||
|
||||
DebugTools.Assert(otherTile2.AdjacentBits.IsFlagSet(direction.GetOpposite()));
|
||||
if (otherTile2.Air != null && CompareExchange(otherTile2.Air, tile.Air) == GasCompareResult.NoExchange)
|
||||
if (otherTile2.Air != null && CompareExchange(otherTile2, tile) == GasCompareResult.NoExchange)
|
||||
continue;
|
||||
|
||||
AddActiveTile(gridAtmosphere, otherTile2);
|
||||
@@ -689,7 +689,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
|
||||
var chance = MathHelper.Clamp(0.01f + (sum / SpacingMaxWind) * 0.3f, 0.003f, 0.3f);
|
||||
|
||||
if (sum > 20 && _robustRandom.Prob(chance))
|
||||
if (sum > 20 && _random.Prob(chance))
|
||||
PryTile(mapGrid, tile.GridIndices);
|
||||
}
|
||||
|
||||
|
||||
@@ -231,7 +231,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
tile.MapAtmosphere = false;
|
||||
atmos.MapTiles.Remove(tile);
|
||||
tile.Air = null;
|
||||
Array.Clear(tile.MolesArchived);
|
||||
tile.AirArchived = null;
|
||||
tile.ArchivedCycle = 0;
|
||||
tile.LastShare = 0f;
|
||||
tile.Space = false;
|
||||
@@ -261,7 +261,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
return;
|
||||
|
||||
tile.Air = null;
|
||||
Array.Clear(tile.MolesArchived);
|
||||
tile.AirArchived = null;
|
||||
tile.ArchivedCycle = 0;
|
||||
tile.LastShare = 0f;
|
||||
tile.Hotspot = new Hotspot();
|
||||
|
||||
@@ -131,7 +131,10 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
|
||||
private void TemperatureShareMutualSolid(TileAtmosphere tile, TileAtmosphere other, float conductionCoefficient)
|
||||
{
|
||||
var deltaTemperature = (tile.TemperatureArchived - other.TemperatureArchived);
|
||||
if (tile.AirArchived == null || other.AirArchived == null)
|
||||
return;
|
||||
|
||||
var deltaTemperature = (tile.AirArchived.Temperature - other.AirArchived.Temperature);
|
||||
if (MathF.Abs(deltaTemperature) > Atmospherics.MinimumTemperatureDeltaToConsider
|
||||
&& tile.HeatCapacity != 0f && other.HeatCapacity != 0f)
|
||||
{
|
||||
@@ -145,11 +148,14 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
|
||||
public void RadiateToSpace(TileAtmosphere tile)
|
||||
{
|
||||
if (tile.AirArchived == null)
|
||||
return;
|
||||
|
||||
// Considering 0ºC as the break even point for radiation in and out.
|
||||
if (tile.Temperature > Atmospherics.T0C)
|
||||
{
|
||||
// Hardcoded space temperature.
|
||||
var deltaTemperature = (tile.TemperatureArchived - Atmospherics.TCMB);
|
||||
var deltaTemperature = (tile.AirArchived.Temperature - Atmospherics.TCMB);
|
||||
if ((tile.HeatCapacity > 0) && (MathF.Abs(deltaTemperature) > Atmospherics.MinimumTemperatureDeltaToConsider))
|
||||
{
|
||||
var heat = tile.ThermalConductivity * deltaTemperature * (tile.HeatCapacity *
|
||||
|
||||
@@ -14,7 +14,6 @@ using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Server.Atmos.EntitySystems;
|
||||
@@ -27,7 +26,6 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLog = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly InternalsSystem _internals = default!;
|
||||
@@ -39,7 +37,6 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
|
||||
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
||||
[Dependency] private readonly TileSystem _tile = default!;
|
||||
[Dependency] private readonly MapSystem _map = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] public readonly PuddleSystem Puddle = default!;
|
||||
|
||||
private const float ExposedUpdateDelay = 1f;
|
||||
@@ -124,6 +121,6 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
|
||||
|
||||
private void CacheDecals()
|
||||
{
|
||||
_burntDecals = _prototypeManager.EnumeratePrototypes<DecalPrototype>().Where(x => x.Tags.Contains("burnt")).Select(x => x.ID).ToArray();
|
||||
_burntDecals = _protoMan.EnumeratePrototypes<DecalPrototype>().Where(x => x.Tags.Contains("burnt")).Select(x => x.ID).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,9 +22,6 @@ namespace Content.Server.Atmos
|
||||
[ViewVariables]
|
||||
public float Temperature { get; set; } = Atmospherics.T20C;
|
||||
|
||||
[ViewVariables]
|
||||
public float TemperatureArchived { get; set; } = Atmospherics.T20C;
|
||||
|
||||
[ViewVariables]
|
||||
public TileAtmosphere? PressureSpecificTarget { get; set; }
|
||||
|
||||
@@ -93,12 +90,16 @@ namespace Content.Server.Atmos
|
||||
[Access(typeof(AtmosphereSystem), Other = AccessPermissions.ReadExecute)] // FIXME Friends
|
||||
public GasMixture? Air { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Like Air, but a copy stored each atmos tick before tile processing takes place. This lets us update Air
|
||||
/// in-place without affecting the results based on update order.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public GasMixture? AirArchived;
|
||||
|
||||
[DataField("lastShare")]
|
||||
public float LastShare;
|
||||
|
||||
[ViewVariables]
|
||||
public readonly float[] MolesArchived = new float[Atmospherics.AdjustedNumberOfGases];
|
||||
|
||||
GasMixture IGasMixtureHolder.Air
|
||||
{
|
||||
get => Air ?? new GasMixture(Atmospherics.CellVolume){ Temperature = Temperature };
|
||||
@@ -139,6 +140,7 @@ namespace Content.Server.Atmos
|
||||
GridIndex = gridIndex;
|
||||
GridIndices = gridIndices;
|
||||
Air = mixture;
|
||||
AirArchived = Air != null ? Air.Clone() : null;
|
||||
Space = space;
|
||||
|
||||
if(immutable)
|
||||
@@ -153,7 +155,7 @@ namespace Content.Server.Atmos
|
||||
NoGridTile = other.NoGridTile;
|
||||
MapAtmosphere = other.MapAtmosphere;
|
||||
Air = other.Air?.Clone();
|
||||
Array.Copy(other.MolesArchived, MolesArchived, MolesArchived.Length);
|
||||
AirArchived = Air != null ? Air.Clone() : null;
|
||||
}
|
||||
|
||||
public TileAtmosphere()
|
||||
|
||||
@@ -14,7 +14,6 @@ public sealed class LungSystem : EntitySystem
|
||||
[Dependency] private readonly AtmosphereSystem _atmos = default!;
|
||||
[Dependency] private readonly InternalsSystem _internals = default!;
|
||||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
|
||||
public static string LungSolutionName = "Lung";
|
||||
|
||||
@@ -29,7 +28,7 @@ public sealed class LungSystem : EntitySystem
|
||||
|
||||
private void OnGotUnequipped(Entity<BreathToolComponent> ent, ref GotUnequippedEvent args)
|
||||
{
|
||||
_atmosphereSystem.DisconnectInternals(ent);
|
||||
_atmos.DisconnectInternals(ent);
|
||||
}
|
||||
|
||||
private void OnGotEquipped(Entity<BreathToolComponent> ent, ref GotEquippedEvent args)
|
||||
@@ -93,7 +92,7 @@ public sealed class LungSystem : EntitySystem
|
||||
if (moles <= 0)
|
||||
continue;
|
||||
|
||||
var reagent = _atmosphereSystem.GasReagents[i];
|
||||
var reagent = _atmos.GasReagents[i];
|
||||
if (reagent is null)
|
||||
continue;
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ public sealed class MutationSystem : EntitySystem
|
||||
{
|
||||
foreach (var mutation in _randomMutations.mutations)
|
||||
{
|
||||
if (Random(mutation.BaseOdds * severity))
|
||||
if (Random(Math.Min(mutation.BaseOdds * severity, 1.0f)))
|
||||
{
|
||||
if (mutation.AppliesToPlant)
|
||||
{
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
namespace Content.Server.Cargo.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for the price gun, which calculates the price of any object it appraises.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class PriceGunComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1,73 +1,34 @@
|
||||
using Content.Server.Cargo.Components;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Timing;
|
||||
using Content.Shared.Verbs;
|
||||
using Content.Shared.Cargo.Systems;
|
||||
|
||||
namespace Content.Server.Cargo.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// This handles...
|
||||
/// </summary>
|
||||
public sealed class PriceGunSystem : EntitySystem
|
||||
public sealed class PriceGunSystem : SharedPriceGunSystem
|
||||
{
|
||||
[Dependency] private readonly UseDelaySystem _useDelay = default!;
|
||||
[Dependency] private readonly PricingSystem _pricingSystem = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly CargoSystem _bountySystem = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
protected override bool GetPriceOrBounty(EntityUid priceGunUid, EntityUid target, EntityUid user)
|
||||
{
|
||||
SubscribeLocalEvent<PriceGunComponent, AfterInteractEvent>(OnAfterInteract);
|
||||
SubscribeLocalEvent<PriceGunComponent, GetVerbsEvent<UtilityVerb>>(OnUtilityVerb);
|
||||
}
|
||||
|
||||
private void OnUtilityVerb(EntityUid uid, PriceGunComponent component, GetVerbsEvent<UtilityVerb> args)
|
||||
{
|
||||
if (!args.CanAccess || !args.CanInteract || args.Using == null)
|
||||
return;
|
||||
|
||||
if (!TryComp(uid, out UseDelayComponent? useDelay) || _useDelay.IsDelayed((uid, useDelay)))
|
||||
return;
|
||||
|
||||
var price = _pricingSystem.GetPrice(args.Target);
|
||||
|
||||
var verb = new UtilityVerb()
|
||||
{
|
||||
Act = () =>
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("price-gun-pricing-result", ("object", Identity.Entity(args.Target, EntityManager)), ("price", $"{price:F2}")), args.User, args.User);
|
||||
_useDelay.TryResetDelay((uid, useDelay));
|
||||
},
|
||||
Text = Loc.GetString("price-gun-verb-text"),
|
||||
Message = Loc.GetString("price-gun-verb-message", ("object", Identity.Entity(args.Target, EntityManager)))
|
||||
};
|
||||
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
private void OnAfterInteract(EntityUid uid, PriceGunComponent component, AfterInteractEvent args)
|
||||
{
|
||||
if (!args.CanReach || args.Target == null || args.Handled)
|
||||
return;
|
||||
|
||||
if (!TryComp(uid, out UseDelayComponent? useDelay) || _useDelay.IsDelayed((uid, useDelay)))
|
||||
return;
|
||||
if (!TryComp(priceGunUid, out UseDelayComponent? useDelay) || _useDelay.IsDelayed((priceGunUid, useDelay)))
|
||||
return false;
|
||||
|
||||
// Check if we're scanning a bounty crate
|
||||
if (_bountySystem.IsBountyComplete(args.Target.Value, out _))
|
||||
if (_bountySystem.IsBountyComplete(target, out _))
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("price-gun-bounty-complete"), args.User, args.User);
|
||||
_popupSystem.PopupEntity(Loc.GetString("price-gun-bounty-complete"), user, user);
|
||||
}
|
||||
else // Otherwise appraise the price
|
||||
{
|
||||
double price = _pricingSystem.GetPrice(args.Target.Value);
|
||||
_popupSystem.PopupEntity(Loc.GetString("price-gun-pricing-result", ("object", Identity.Entity(args.Target.Value, EntityManager)), ("price", $"{price:F2}")), args.User, args.User);
|
||||
var price = _pricingSystem.GetPrice(target);
|
||||
_popupSystem.PopupEntity(Loc.GetString("price-gun-pricing-result", ("object", Identity.Entity(target, EntityManager)), ("price", $"{price:F2}")), user, user);
|
||||
}
|
||||
|
||||
_useDelay.TryResetDelay((uid, useDelay));
|
||||
args.Handled = true;
|
||||
_useDelay.TryResetDelay((priceGunUid, useDelay));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Content.Server.CartridgeLoader.Cartridges;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class MedTekCartridgeComponent : Component
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using Content.Server.Medical.Components;
|
||||
using Content.Shared.CartridgeLoader;
|
||||
|
||||
namespace Content.Server.CartridgeLoader.Cartridges;
|
||||
|
||||
public sealed class MedTekCartridgeSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly CartridgeLoaderSystem _cartridgeLoaderSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<MedTekCartridgeComponent, CartridgeAddedEvent>(OnCartridgeAdded);
|
||||
SubscribeLocalEvent<MedTekCartridgeComponent, CartridgeRemovedEvent>(OnCartridgeRemoved);
|
||||
}
|
||||
|
||||
private void OnCartridgeAdded(Entity<MedTekCartridgeComponent> ent, ref CartridgeAddedEvent args)
|
||||
{
|
||||
var healthAnalyzer = EnsureComp<HealthAnalyzerComponent>(args.Loader);
|
||||
}
|
||||
|
||||
private void OnCartridgeRemoved(Entity<MedTekCartridgeComponent> ent, ref CartridgeRemovedEvent args)
|
||||
{
|
||||
// only remove when the program itself is removed
|
||||
if (!_cartridgeLoaderSystem.HasProgram<MedTekCartridgeComponent>(args.Loader))
|
||||
{
|
||||
RemComp<HealthAnalyzerComponent>(args.Loader);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using Content.Server.Players.RateLimiting;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Players.RateLimiting;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server.Chat.Managers;
|
||||
@@ -12,15 +12,13 @@ internal sealed partial class ChatManager
|
||||
private void RegisterRateLimits()
|
||||
{
|
||||
_rateLimitManager.Register(RateLimitKey,
|
||||
new RateLimitRegistration
|
||||
{
|
||||
CVarLimitPeriodLength = CCVars.ChatRateLimitPeriod,
|
||||
CVarLimitCount = CCVars.ChatRateLimitCount,
|
||||
CVarAdminAnnounceDelay = CCVars.ChatRateLimitAnnounceAdminsDelay,
|
||||
PlayerLimitedAction = RateLimitPlayerLimited,
|
||||
AdminAnnounceAction = RateLimitAlertAdmins,
|
||||
AdminLogType = LogType.ChatRateLimited,
|
||||
});
|
||||
new RateLimitRegistration(CCVars.ChatRateLimitPeriod,
|
||||
CCVars.ChatRateLimitCount,
|
||||
RateLimitPlayerLimited,
|
||||
CCVars.ChatRateLimitAnnounceAdminsDelay,
|
||||
RateLimitAlertAdmins,
|
||||
LogType.ChatRateLimited)
|
||||
);
|
||||
}
|
||||
|
||||
private void RateLimitPlayerLimited(ICommonSession player)
|
||||
@@ -30,8 +28,7 @@ internal sealed partial class ChatManager
|
||||
|
||||
private void RateLimitAlertAdmins(ICommonSession player)
|
||||
{
|
||||
if (_configurationManager.GetCVar(CCVars.ChatRateLimitAnnounceAdmins))
|
||||
SendAdminAlert(Loc.GetString("chat-manager-rate-limit-admin-announcement", ("player", player.Name)));
|
||||
SendAdminAlert(Loc.GetString("chat-manager-rate-limit-admin-announcement", ("player", player.Name)));
|
||||
}
|
||||
|
||||
public RateLimitStatus HandleRateLimit(ICommonSession player)
|
||||
|
||||
@@ -12,6 +12,7 @@ using Content.Shared.CCVar;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Players.RateLimiting;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Server.Players;
|
||||
using Content.Server.Players.RateLimiting;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Players.RateLimiting;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server.Chat.Managers
|
||||
{
|
||||
public interface IChatManager
|
||||
public interface IChatManager : ISharedChatManager
|
||||
{
|
||||
void Initialize();
|
||||
|
||||
/// <summary>
|
||||
/// Dispatch a server announcement to every connected player.
|
||||
/// </summary>
|
||||
@@ -26,8 +23,6 @@ namespace Content.Server.Chat.Managers
|
||||
void SendHookOOC(string sender, string message);
|
||||
void SendAdminAnnouncement(string message, AdminFlags? flagBlacklist = null, AdminFlags? flagWhitelist = null);
|
||||
void SendAdminAnnouncementMessage(ICommonSession player, string message, bool suppressLog = true);
|
||||
void SendAdminAlert(string message);
|
||||
void SendAdminAlert(EntityUid player, string message);
|
||||
|
||||
void ChatMessageToOne(ChatChannel channel, string message, string wrappedMessage, EntityUid source, bool hideChat,
|
||||
INetChannel client, Color? colorOverride = null, bool recordReplay = false, string? audioPath = null, float audioVolume = 0, NetUserId? author = null);
|
||||
|
||||
@@ -20,6 +20,7 @@ using Content.Shared.Ghost;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Players;
|
||||
using Content.Shared.Players.RateLimiting;
|
||||
using Content.Shared.Radio;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Server.Player;
|
||||
|
||||
@@ -46,7 +46,6 @@ namespace Content.Server.Connection
|
||||
/// </summary>
|
||||
public sealed partial class ConnectionManager : IConnectionManager
|
||||
{
|
||||
[Dependency] private readonly IServerDbManager _dbManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _plyMgr = default!;
|
||||
[Dependency] private readonly IServerNetManager _netMgr = default!;
|
||||
[Dependency] private readonly IServerDbManager _db = default!;
|
||||
@@ -205,7 +204,7 @@ namespace Content.Server.Connection
|
||||
return null;
|
||||
}
|
||||
|
||||
var adminData = await _dbManager.GetAdminDataForAsync(e.UserId);
|
||||
var adminData = await _db.GetAdminDataForAsync(e.UserId);
|
||||
|
||||
if (_cfg.GetCVar(CCVars.PanicBunkerEnabled) && adminData == null)
|
||||
{
|
||||
@@ -213,7 +212,7 @@ namespace Content.Server.Connection
|
||||
var customReason = _cfg.GetCVar(CCVars.PanicBunkerCustomReason);
|
||||
|
||||
var minMinutesAge = _cfg.GetCVar(CCVars.PanicBunkerMinAccountAge);
|
||||
var record = await _dbManager.GetPlayerRecordByUserId(userId);
|
||||
var record = await _db.GetPlayerRecordByUserId(userId);
|
||||
var validAccountAge = record != null &&
|
||||
record.FirstSeenTime.CompareTo(DateTimeOffset.UtcNow - TimeSpan.FromMinutes(minMinutesAge)) <= 0;
|
||||
var bypassAllowed = _cfg.GetCVar(CCVars.BypassBunkerWhitelist) && await _db.GetWhitelistStatusAsync(userId);
|
||||
@@ -317,7 +316,7 @@ namespace Content.Server.Connection
|
||||
var maxPlaytimeMinutes = _cfg.GetCVar(CCVars.BabyJailMaxOverallMinutes);
|
||||
|
||||
// Wait some time to lookup data
|
||||
var record = await _dbManager.GetPlayerRecordByUserId(userId);
|
||||
var record = await _db.GetPlayerRecordByUserId(userId);
|
||||
|
||||
// No player record = new account or the DB is having a skill issue
|
||||
if (record == null)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Server.Electrocution;
|
||||
using Content.Shared.Electrocution;
|
||||
using Content.Shared.Construction;
|
||||
|
||||
namespace Content.Server.Construction.Completions;
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace Content.Server.Decals
|
||||
[Dependency] private readonly IConfigurationManager _conf = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly MapSystem _mapSystem = default!;
|
||||
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
|
||||
|
||||
private readonly Dictionary<NetEntity, HashSet<Vector2i>> _dirtyChunks = new();
|
||||
private readonly Dictionary<ICommonSession, Dictionary<NetEntity, HashSet<Vector2i>>> _previousSentChunks = new();
|
||||
@@ -106,7 +106,7 @@ namespace Content.Server.Decals
|
||||
return;
|
||||
|
||||
// Transfer decals over to the new grid.
|
||||
var enumerator = Comp<MapGridComponent>(ev.Grid).GetAllTilesEnumerator();
|
||||
var enumerator = _mapSystem.GetAllTilesEnumerator(ev.Grid, Comp<MapGridComponent>(ev.Grid));
|
||||
|
||||
var oldChunkCollection = oldComp.ChunkCollection.ChunkCollection;
|
||||
var chunkCollection = newComp.ChunkCollection.ChunkCollection;
|
||||
|
||||
183
Content.Server/Discord/WebhookMessages/VoteWebhooks.cs
Normal file
183
Content.Server/Discord/WebhookMessages/VoteWebhooks.cs
Normal file
@@ -0,0 +1,183 @@
|
||||
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;
|
||||
|
||||
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!;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
public WebhookState? CreateWebhookIfConfigured(VoteOptions voteOptions, string? webhookUrl = null, string? customVoteName = null, string? customVoteMessage = null)
|
||||
{
|
||||
// All this webhook code is complete garbage.
|
||||
// I tried to clean it up somewhat, at least to fix the glaring bugs in it.
|
||||
// Jesus christ man what is with our code review process.
|
||||
|
||||
if (string.IsNullOrEmpty(webhookUrl))
|
||||
return null;
|
||||
|
||||
// Set up the webhook payload
|
||||
var serverName = _baseServer.ServerName;
|
||||
|
||||
var fields = new List<WebhookEmbedField>();
|
||||
|
||||
foreach (var voteOption in voteOptions.Options)
|
||||
{
|
||||
var newVote = new WebhookEmbedField
|
||||
{
|
||||
Name = voteOption.text,
|
||||
Value = Loc.GetString("custom-vote-webhook-option-pending")
|
||||
};
|
||||
fields.Add(newVote);
|
||||
}
|
||||
|
||||
var gameTicker = _entSys.GetEntitySystemOrNull<GameTicker>();
|
||||
_sawmill = Logger.GetSawmill("discord");
|
||||
|
||||
var runLevel = gameTicker != null ? Loc.GetString($"game-run-level-{gameTicker.RunLevel}") : "";
|
||||
var runId = gameTicker != null ? gameTicker.RoundId : 0;
|
||||
|
||||
var voteName = customVoteName ?? Loc.GetString("custom-vote-webhook-name");
|
||||
var description = customVoteMessage ?? voteOptions.Title;
|
||||
|
||||
var payload = new WebhookPayload()
|
||||
{
|
||||
Username = voteName,
|
||||
Embeds = new List<WebhookEmbed>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Title = voteOptions.InitiatorText,
|
||||
Color = 13438992, // #CD1010
|
||||
Description = description,
|
||||
Footer = new WebhookEmbedFooter
|
||||
{
|
||||
Text = Loc.GetString(
|
||||
"custom-vote-webhook-footer",
|
||||
("serverName", serverName),
|
||||
("roundId", runId),
|
||||
("runLevel", runLevel)),
|
||||
},
|
||||
|
||||
Fields = fields,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var state = new WebhookState
|
||||
{
|
||||
WebhookUrl = webhookUrl,
|
||||
Payload = payload,
|
||||
};
|
||||
|
||||
CreateWebhookMessage(state, payload);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
public void UpdateWebhookIfConfigured(WebhookState? state, VoteFinishedEventArgs finished)
|
||||
{
|
||||
if (state == null)
|
||||
return;
|
||||
|
||||
var embed = state.Payload.Embeds![0];
|
||||
embed.Color = 2353993; // #23EB49
|
||||
|
||||
for (var i = 0; i < finished.Votes.Count; i++)
|
||||
{
|
||||
var oldName = embed.Fields[i].Name;
|
||||
var newValue = finished.Votes[i].ToString();
|
||||
embed.Fields[i] = new WebhookEmbedField { Name = oldName, Value = newValue, Inline = true };
|
||||
}
|
||||
|
||||
state.Payload.Embeds[0] = embed;
|
||||
|
||||
UpdateWebhookMessage(state, state.Payload, state.MessageId);
|
||||
}
|
||||
|
||||
public void UpdateCancelledWebhookIfConfigured(WebhookState? state, string? customCancelReason = null)
|
||||
{
|
||||
if (state == null)
|
||||
return;
|
||||
|
||||
var embed = state.Payload.Embeds![0];
|
||||
embed.Color = 13356304; // #CBCD10
|
||||
if (customCancelReason == null)
|
||||
embed.Description += "\n\n" + Loc.GetString("custom-vote-webhook-cancelled");
|
||||
else
|
||||
embed.Description += "\n\n" + customCancelReason;
|
||||
|
||||
for (var i = 0; i < embed.Fields.Count; i++)
|
||||
{
|
||||
var oldName = embed.Fields[i].Name;
|
||||
embed.Fields[i] = new WebhookEmbedField { Name = oldName, Value = Loc.GetString("custom-vote-webhook-option-cancelled"), Inline = true };
|
||||
}
|
||||
|
||||
state.Payload.Embeds[0] = embed;
|
||||
|
||||
UpdateWebhookMessage(state, state.Payload, state.MessageId);
|
||||
}
|
||||
|
||||
// Sends the payload's message.
|
||||
public async void CreateWebhookMessage(WebhookState state, WebhookPayload payload)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (await _discord.GetWebhook(state.WebhookUrl) is not { } identifier)
|
||||
return;
|
||||
|
||||
state.Identifier = identifier.ToIdentifier();
|
||||
_sawmill.Debug(JsonSerializer.Serialize(payload));
|
||||
|
||||
var request = await _discord.CreateMessage(identifier.ToIdentifier(), payload);
|
||||
var content = await request.Content.ReadAsStringAsync();
|
||||
state.MessageId = ulong.Parse(JsonNode.Parse(content)?["id"]!.GetValue<string>()!);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sawmill.Error($"Error while sending vote webhook to Discord: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
// Edits a pre-existing payload message, given an ID
|
||||
public async void UpdateWebhookMessage(WebhookState state, WebhookPayload payload, ulong id)
|
||||
{
|
||||
if (state.MessageId == 0)
|
||||
{
|
||||
_sawmill.Warning("Failed to deliver update to custom vote webhook: message ID was zero. This likely indicates a previous connection error sending the original message.");
|
||||
return;
|
||||
}
|
||||
|
||||
DebugTools.Assert(state.Identifier != default);
|
||||
|
||||
try
|
||||
{
|
||||
await _discord.EditMessage(state.Identifier, id, payload);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sawmill.Error($"Error while updating vote webhook on Discord: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class WebhookState
|
||||
{
|
||||
public required string WebhookUrl;
|
||||
public required WebhookPayload Payload;
|
||||
public WebhookIdentifier Identifier;
|
||||
public ulong MessageId;
|
||||
}
|
||||
|
||||
void IPostInjectInit.PostInject() { }
|
||||
}
|
||||
@@ -2,7 +2,6 @@ using Content.Server.Doors.Systems;
|
||||
using Content.Server.Wires;
|
||||
using Content.Shared.Doors;
|
||||
using Content.Shared.Doors.Components;
|
||||
using Content.Shared.Doors.Systems;
|
||||
using Content.Shared.Wires;
|
||||
|
||||
namespace Content.Server.Doors;
|
||||
|
||||
@@ -488,4 +488,15 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
|
||||
}
|
||||
_audio.PlayPvs(electrified.ShockNoises, targetUid, AudioParams.Default.WithVolume(electrified.ShockVolume));
|
||||
}
|
||||
|
||||
public void SetElectrifiedWireCut(Entity<ElectrifiedComponent> ent, bool value)
|
||||
{
|
||||
if (ent.Comp.IsWireCut == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ent.Comp.IsWireCut = value;
|
||||
Dirty(ent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ public sealed partial class ExplosionSystem
|
||||
Dictionary<Vector2i, NeighborFlag> edges = new();
|
||||
_gridEdges[ev.EntityUid] = edges;
|
||||
|
||||
foreach (var tileRef in grid.GetAllTiles())
|
||||
foreach (var tileRef in _map.GetAllTiles(ev.EntityUid, grid))
|
||||
{
|
||||
if (IsEdge(grid, tileRef.GridIndices, out var dir))
|
||||
edges.Add(tileRef.GridIndices, dir);
|
||||
|
||||
@@ -666,6 +666,7 @@ sealed class Explosion
|
||||
|
||||
private readonly IEntityManager _entMan;
|
||||
private readonly ExplosionSystem _system;
|
||||
private readonly SharedMapSystem _mapSystem;
|
||||
|
||||
public readonly EntityUid VisualEnt;
|
||||
|
||||
@@ -688,11 +689,13 @@ sealed class Explosion
|
||||
IEntityManager entMan,
|
||||
IMapManager mapMan,
|
||||
EntityUid visualEnt,
|
||||
EntityUid? cause)
|
||||
EntityUid? cause,
|
||||
SharedMapSystem mapSystem)
|
||||
{
|
||||
VisualEnt = visualEnt;
|
||||
Cause = cause;
|
||||
_system = system;
|
||||
_mapSystem = mapSystem;
|
||||
ExplosionType = explosionType;
|
||||
_tileSetIntensity = tileSetIntensity;
|
||||
Epicenter = epicenter;
|
||||
@@ -899,7 +902,7 @@ sealed class Explosion
|
||||
{
|
||||
if (list.Count > 0 && _entMan.EntityExists(grid.Owner))
|
||||
{
|
||||
grid.SetTiles(list);
|
||||
_mapSystem.SetTiles(grid.Owner, grid, list);
|
||||
}
|
||||
}
|
||||
_tileUpdateDict.Clear();
|
||||
|
||||
@@ -389,7 +389,8 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
|
||||
EntityManager,
|
||||
_mapManager,
|
||||
visualEnt,
|
||||
queued.Cause);
|
||||
queued.Cause,
|
||||
_map);
|
||||
}
|
||||
|
||||
private void CameraShake(float range, MapCoordinates epicenter, float totalIntensity)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Content.Server.DoAfter;
|
||||
using Content.Server.Fluids.Components;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Audio;
|
||||
@@ -13,9 +12,7 @@ using Content.Shared.Fluids.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Tag;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -32,19 +29,27 @@ public sealed class DrainSystem : SharedDrainSystem
|
||||
[Dependency] private readonly TagSystem _tagSystem = default!;
|
||||
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private readonly PuddleSystem _puddleSystem = default!;
|
||||
[Dependency] private readonly TransformSystem _transform = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
private readonly HashSet<Entity<PuddleComponent>> _puddles = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<DrainComponent, MapInitEvent>(OnDrainMapInit);
|
||||
SubscribeLocalEvent<DrainComponent, GetVerbsEvent<Verb>>(AddEmptyVerb);
|
||||
SubscribeLocalEvent<DrainComponent, ExaminedEvent>(OnExamined);
|
||||
SubscribeLocalEvent<DrainComponent, AfterInteractUsingEvent>(OnInteract);
|
||||
SubscribeLocalEvent<DrainComponent, DrainDoAfterEvent>(OnDoAfter);
|
||||
}
|
||||
|
||||
private void OnDrainMapInit(Entity<DrainComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
// Randomise puddle drains so roundstart ones don't all dump at the same time.
|
||||
ent.Comp.Accumulator = _random.NextFloat(ent.Comp.DrainFrequency);
|
||||
}
|
||||
|
||||
private void AddEmptyVerb(Entity<DrainComponent> entity, ref GetVerbsEvent<Verb> args)
|
||||
{
|
||||
if (!args.CanAccess || !args.CanInteract || args.Using == null)
|
||||
@@ -118,9 +123,6 @@ public sealed class DrainSystem : SharedDrainSystem
|
||||
{
|
||||
base.Update(frameTime);
|
||||
var managerQuery = GetEntityQuery<SolutionContainerManagerComponent>();
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
var puddleQuery = GetEntityQuery<PuddleComponent>();
|
||||
var puddles = new ValueList<(Entity<PuddleComponent> Entity, string Solution)>();
|
||||
|
||||
var query = EntityQueryEnumerator<DrainComponent>();
|
||||
while (query.MoveNext(out var uid, out var drain))
|
||||
@@ -158,22 +160,10 @@ public sealed class DrainSystem : SharedDrainSystem
|
||||
// This will ensure that UnitsPerSecond is per second...
|
||||
var amount = drain.UnitsPerSecond * drain.DrainFrequency;
|
||||
|
||||
if (!xformQuery.TryGetComponent(uid, out var xform))
|
||||
continue;
|
||||
_puddles.Clear();
|
||||
_lookup.GetEntitiesInRange(Transform(uid).Coordinates, drain.Range, _puddles);
|
||||
|
||||
puddles.Clear();
|
||||
|
||||
foreach (var entity in _lookup.GetEntitiesInRange(_transform.GetMapCoordinates(uid, xform), drain.Range))
|
||||
{
|
||||
// No InRangeUnobstructed because there's no collision group that fits right now
|
||||
// and these are placed by mappers and not buildable/movable so shouldnt really be a problem...
|
||||
if (puddleQuery.TryGetComponent(entity, out var puddle))
|
||||
{
|
||||
puddles.Add(((entity, puddle), puddle.SolutionName));
|
||||
}
|
||||
}
|
||||
|
||||
if (puddles.Count == 0)
|
||||
if (_puddles.Count == 0)
|
||||
{
|
||||
_ambientSoundSystem.SetAmbience(uid, false);
|
||||
continue;
|
||||
@@ -181,13 +171,13 @@ public sealed class DrainSystem : SharedDrainSystem
|
||||
|
||||
_ambientSoundSystem.SetAmbience(uid, true);
|
||||
|
||||
amount /= puddles.Count;
|
||||
amount /= _puddles.Count;
|
||||
|
||||
foreach (var (puddle, solution) in puddles)
|
||||
foreach (var puddle in _puddles)
|
||||
{
|
||||
// Queue the solution deletion if it's empty. EvaporationSystem might also do this
|
||||
// but queuedelete should be pretty safe.
|
||||
if (!_solutionContainerSystem.ResolveSolution(puddle.Owner, solution, ref puddle.Comp.Solution, out var puddleSolution))
|
||||
if (!_solutionContainerSystem.ResolveSolution(puddle.Owner, puddle.Comp.SolutionName, ref puddle.Comp.Solution, out var puddleSolution))
|
||||
{
|
||||
EntityManager.QueueDeleteEntity(puddle);
|
||||
continue;
|
||||
|
||||
@@ -14,6 +14,7 @@ using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Server.Fluids.EntitySystems;
|
||||
|
||||
@@ -35,6 +36,19 @@ public sealed class SpraySystem : EntitySystem
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<SprayComponent, AfterInteractEvent>(OnAfterInteract);
|
||||
SubscribeLocalEvent<SprayComponent, UserActivateInWorldEvent>(OnActivateInWorld);
|
||||
}
|
||||
|
||||
private void OnActivateInWorld(Entity<SprayComponent> entity, ref UserActivateInWorldEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
|
||||
var targetMapPos = _transform.GetMapCoordinates(GetEntityQuery<TransformComponent>().GetComponent(args.Target));
|
||||
|
||||
Spray(entity, args.User, targetMapPos);
|
||||
}
|
||||
|
||||
private void OnAfterInteract(Entity<SprayComponent> entity, ref AfterInteractEvent args)
|
||||
@@ -44,29 +58,36 @@ public sealed class SpraySystem : EntitySystem
|
||||
|
||||
args.Handled = true;
|
||||
|
||||
var clickPos = _transform.ToMapCoordinates(args.ClickLocation);
|
||||
|
||||
Spray(entity, args.User, clickPos);
|
||||
}
|
||||
|
||||
public void Spray(Entity<SprayComponent> entity, EntityUid user, MapCoordinates mapcoord)
|
||||
{
|
||||
if (!_solutionContainer.TryGetSolution(entity.Owner, SprayComponent.SolutionName, out var soln, out var solution))
|
||||
return;
|
||||
|
||||
var ev = new SprayAttemptEvent(args.User);
|
||||
var ev = new SprayAttemptEvent(user);
|
||||
RaiseLocalEvent(entity, ref ev);
|
||||
if (ev.Cancelled)
|
||||
return;
|
||||
|
||||
if (!TryComp<UseDelayComponent>(entity, out var useDelay)
|
||||
|| _useDelay.IsDelayed((entity, useDelay)))
|
||||
if (TryComp<UseDelayComponent>(entity, out var useDelay)
|
||||
&& _useDelay.IsDelayed((entity, useDelay)))
|
||||
return;
|
||||
|
||||
if (solution.Volume <= 0)
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("spray-component-is-empty-message"), entity.Owner, args.User);
|
||||
_popupSystem.PopupEntity(Loc.GetString("spray-component-is-empty-message"), entity.Owner, user);
|
||||
return;
|
||||
}
|
||||
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
var userXform = xformQuery.GetComponent(args.User);
|
||||
var userXform = xformQuery.GetComponent(user);
|
||||
|
||||
var userMapPos = _transform.GetMapCoordinates(userXform);
|
||||
var clickMapPos = args.ClickLocation.ToMap(EntityManager, _transform);
|
||||
var clickMapPos = mapcoord;
|
||||
|
||||
var diffPos = clickMapPos.Position - userMapPos.Position;
|
||||
if (diffPos == Vector2.Zero || diffPos == Vector2Helpers.NaN)
|
||||
@@ -88,8 +109,6 @@ public sealed class SpraySystem : EntitySystem
|
||||
|
||||
var amount = Math.Max(Math.Min((solution.Volume / entity.Comp.TransferAmount).Int(), entity.Comp.VaporAmount), 1);
|
||||
var spread = entity.Comp.VaporSpread / amount;
|
||||
// TODO: Just use usedelay homie.
|
||||
var cooldownTime = 0f;
|
||||
|
||||
for (var i = 0; i < amount; i++)
|
||||
{
|
||||
@@ -131,20 +150,19 @@ public sealed class SpraySystem : EntitySystem
|
||||
// impulse direction is defined in world-coordinates, not local coordinates
|
||||
var impulseDirection = rotation.ToVec();
|
||||
var time = diffLength / entity.Comp.SprayVelocity;
|
||||
cooldownTime = MathF.Max(time, cooldownTime);
|
||||
|
||||
_vapor.Start(ent, vaporXform, impulseDirection * diffLength, entity.Comp.SprayVelocity, target, time, args.User);
|
||||
_vapor.Start(ent, vaporXform, impulseDirection * diffLength, entity.Comp.SprayVelocity, target, time, user);
|
||||
|
||||
if (TryComp<PhysicsComponent>(args.User, out var body))
|
||||
if (TryComp<PhysicsComponent>(user, out var body))
|
||||
{
|
||||
if (_gravity.IsWeightless(args.User, body))
|
||||
_physics.ApplyLinearImpulse(args.User, -impulseDirection.Normalized() * entity.Comp.PushbackAmount, body: body);
|
||||
if (_gravity.IsWeightless(user, body))
|
||||
_physics.ApplyLinearImpulse(user, -impulseDirection.Normalized() * entity.Comp.PushbackAmount, body: body);
|
||||
}
|
||||
}
|
||||
|
||||
_audio.PlayPvs(entity.Comp.SpraySound, entity, entity.Comp.SpraySound.Params.WithVariation(0.125f));
|
||||
|
||||
_useDelay.SetLength(entity.Owner, TimeSpan.FromSeconds(cooldownTime));
|
||||
_useDelay.TryResetDelay((entity, useDelay));
|
||||
if (useDelay != null)
|
||||
_useDelay.TryResetDelay((entity, useDelay));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace Content.Server.GameTicking
|
||||
|
||||
private void InitializeCVars()
|
||||
{
|
||||
Subs.CVar(_configurationManager, CCVars.GameLobbyEnabled, value =>
|
||||
Subs.CVar(_cfg, CCVars.GameLobbyEnabled, value =>
|
||||
{
|
||||
LobbyEnabled = value;
|
||||
foreach (var (userId, status) in _playerGameStatuses)
|
||||
@@ -47,23 +47,23 @@ namespace Content.Server.GameTicking
|
||||
LobbyEnabled ? PlayerGameStatus.NotReadyToPlay : PlayerGameStatus.ReadyToPlay;
|
||||
}
|
||||
}, true);
|
||||
Subs.CVar(_configurationManager, CCVars.GameDummyTicker, value => DummyTicker = value, true);
|
||||
Subs.CVar(_configurationManager, CCVars.GameLobbyDuration, value => LobbyDuration = TimeSpan.FromSeconds(value), true);
|
||||
Subs.CVar(_configurationManager, CCVars.GameDisallowLateJoins,
|
||||
Subs.CVar(_cfg, CCVars.GameDummyTicker, value => DummyTicker = value, true);
|
||||
Subs.CVar(_cfg, CCVars.GameLobbyDuration, value => LobbyDuration = TimeSpan.FromSeconds(value), true);
|
||||
Subs.CVar(_cfg, CCVars.GameDisallowLateJoins,
|
||||
value => { DisallowLateJoin = value; UpdateLateJoinStatus(); }, true);
|
||||
Subs.CVar(_configurationManager, CCVars.AdminLogsServerName, value =>
|
||||
Subs.CVar(_cfg, CCVars.AdminLogsServerName, value =>
|
||||
{
|
||||
// TODO why tf is the server name on admin logs
|
||||
ServerName = value;
|
||||
}, true);
|
||||
Subs.CVar(_configurationManager, CCVars.DiscordRoundUpdateWebhook, value =>
|
||||
Subs.CVar(_cfg, CCVars.DiscordRoundUpdateWebhook, value =>
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
_discord.GetWebhook(value, data => _webhookIdentifier = data.ToIdentifier());
|
||||
}
|
||||
}, true);
|
||||
Subs.CVar(_configurationManager, CCVars.DiscordRoundEndRoleWebhook, value =>
|
||||
Subs.CVar(_cfg, CCVars.DiscordRoundEndRoleWebhook, value =>
|
||||
{
|
||||
DiscordRoundEndRole = value;
|
||||
|
||||
@@ -72,9 +72,9 @@ namespace Content.Server.GameTicking
|
||||
DiscordRoundEndRole = null;
|
||||
}
|
||||
}, true);
|
||||
Subs.CVar(_configurationManager, CCVars.RoundEndSoundCollection, value => RoundEndSoundCollection = value, true);
|
||||
Subs.CVar(_cfg, CCVars.RoundEndSoundCollection, value => RoundEndSoundCollection = value, true);
|
||||
#if EXCEPTION_TOLERANCE
|
||||
Subs.CVar(_configurationManager, CCVars.RoundStartFailShutdownCount, value => RoundStartFailShutdownCount = value, true);
|
||||
Subs.CVar(_cfg, CCVars.RoundStartFailShutdownCount, value => RoundStartFailShutdownCount = value, true);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,9 +41,9 @@ namespace Content.Server.GameTicking
|
||||
DelayStart(TimeSpan.FromSeconds(PresetFailedCooldownIncrease));
|
||||
}
|
||||
|
||||
if (_configurationManager.GetCVar(CCVars.GameLobbyFallbackEnabled))
|
||||
if (_cfg.GetCVar(CCVars.GameLobbyFallbackEnabled))
|
||||
{
|
||||
var fallbackPresets = _configurationManager.GetCVar(CCVars.GameLobbyFallbackPreset).Split(",");
|
||||
var fallbackPresets = _cfg.GetCVar(CCVars.GameLobbyFallbackPreset).Split(",");
|
||||
var startFailed = true;
|
||||
|
||||
foreach (var preset in fallbackPresets)
|
||||
@@ -86,7 +86,7 @@ namespace Content.Server.GameTicking
|
||||
|
||||
private void InitializeGamePreset()
|
||||
{
|
||||
SetGamePreset(LobbyEnabled ? _configurationManager.GetCVar(CCVars.GameLobbyDefaultPreset) : "sandbox");
|
||||
SetGamePreset(LobbyEnabled ? _cfg.GetCVar(CCVars.GameLobbyDefaultPreset) : "sandbox");
|
||||
}
|
||||
|
||||
public void SetGamePreset(GamePresetPrototype? preset, bool force = false)
|
||||
@@ -190,7 +190,7 @@ namespace Content.Server.GameTicking
|
||||
private void IncrementRoundNumber()
|
||||
{
|
||||
var playerIds = _playerGameStatuses.Keys.Select(player => player.UserId).ToArray();
|
||||
var serverName = _configurationManager.GetCVar(CCVars.AdminLogsServerName);
|
||||
var serverName = _cfg.GetCVar(CCVars.AdminLogsServerName);
|
||||
|
||||
// TODO FIXME AAAAAAAAAAAAAAAAAAAH THIS IS BROKEN
|
||||
// Task.Run as a terrible dirty workaround to avoid synchronization context deadlock from .Result here.
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Database;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.GameTicking;
|
||||
@@ -9,7 +7,6 @@ using Content.Shared.Preferences;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
@@ -21,8 +18,6 @@ namespace Content.Server.GameTicking
|
||||
public sealed partial class GameTicker
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IServerDbManager _dbManager = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
|
||||
|
||||
private void InitializePlayer()
|
||||
{
|
||||
@@ -64,7 +59,7 @@ namespace Content.Server.GameTicking
|
||||
// timer time must be > tick length
|
||||
Timer.Spawn(0, () => _playerManager.JoinGame(args.Session));
|
||||
|
||||
var record = await _dbManager.GetPlayerRecordByUserId(args.Session.UserId);
|
||||
var record = await _db.GetPlayerRecordByUserId(args.Session.UserId);
|
||||
var firstConnection = record != null &&
|
||||
Math.Abs((record.FirstSeenTime - record.LastSeenTime).TotalMinutes) < 1;
|
||||
|
||||
@@ -74,8 +69,8 @@ namespace Content.Server.GameTicking
|
||||
|
||||
RaiseNetworkEvent(GetConnectionStatusMsg(), session.Channel);
|
||||
|
||||
if (firstConnection && _configurationManager.GetCVar(CCVars.AdminNewPlayerJoinSound))
|
||||
_audioSystem.PlayGlobal(new SoundPathSpecifier("/Audio/Effects/newplayerping.ogg"),
|
||||
if (firstConnection && _cfg.GetCVar(CCVars.AdminNewPlayerJoinSound))
|
||||
_audio.PlayGlobal(new SoundPathSpecifier("/Audio/Effects/newplayerping.ogg"),
|
||||
Filter.Empty().AddPlayers(_adminManager.ActiveAdmins), false,
|
||||
audioParams: new AudioParams { Volume = -5f });
|
||||
|
||||
|
||||
@@ -127,8 +127,8 @@ public sealed partial class GameTicker
|
||||
metadata["gamemode"] = new ValueDataNode(CurrentPreset != null ? Loc.GetString(CurrentPreset.ModeTitle) : string.Empty);
|
||||
metadata["roundEndPlayers"] = _serialman.WriteValue(_replayRoundPlayerInfo);
|
||||
metadata["roundEndText"] = new ValueDataNode(_replayRoundText);
|
||||
metadata["server_id"] = new ValueDataNode(_configurationManager.GetCVar(CCVars.ServerId));
|
||||
metadata["server_name"] = new ValueDataNode(_configurationManager.GetCVar(CCVars.AdminLogsServerName));
|
||||
metadata["server_id"] = new ValueDataNode(_cfg.GetCVar(CCVars.ServerId));
|
||||
metadata["server_name"] = new ValueDataNode(_cfg.GetCVar(CCVars.AdminLogsServerName));
|
||||
metadata["roundId"] = new ValueDataNode(RoundId.ToString());
|
||||
}
|
||||
|
||||
|
||||
@@ -364,7 +364,7 @@ namespace Content.Server.GameTicking
|
||||
var listOfPlayerInfo = new List<RoundEndMessageEvent.RoundEndPlayerInfo>();
|
||||
// Grab the great big book of all the Minds, we'll need them for this.
|
||||
var allMinds = EntityQueryEnumerator<MindComponent>();
|
||||
var pvsOverride = _configurationManager.GetCVar(CCVars.RoundEndPVSOverrides);
|
||||
var pvsOverride = _cfg.GetCVar(CCVars.RoundEndPVSOverrides);
|
||||
while (allMinds.MoveNext(out var mindId, out var mind))
|
||||
{
|
||||
// TODO don't list redundant observer roles?
|
||||
|
||||
@@ -8,19 +8,15 @@ using Content.Server.Maps;
|
||||
using Content.Server.Players.PlayTimeTracking;
|
||||
using Content.Server.Preferences.Managers;
|
||||
using Content.Server.ServerUpdates;
|
||||
using Content.Server.Shuttles.Systems;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Server;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.GameStates;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -39,7 +35,6 @@ namespace Content.Server.GameTicking
|
||||
[Dependency] private readonly IBanManager _banManager = default!;
|
||||
[Dependency] private readonly IBaseServer _baseServer = default!;
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
[Dependency] private readonly IConsoleHost _consoleHost = default!;
|
||||
[Dependency] private readonly IGameMapManager _gameMapManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
@@ -46,11 +46,9 @@ namespace Content.Server.Ghost
|
||||
[Dependency] private readonly JobSystem _jobs = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly MindSystem _minds = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mindSystem = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly GameTicker _ticker = default!;
|
||||
[Dependency] private readonly TransformSystem _transformSystem = default!;
|
||||
[Dependency] private readonly VisibilitySystem _visibilitySystem = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
@@ -170,7 +168,7 @@ namespace Content.Server.Ghost
|
||||
// Allow this entity to be seen by other ghosts.
|
||||
var visibility = EnsureComp<VisibilityComponent>(uid);
|
||||
|
||||
if (_ticker.RunLevel != GameRunLevel.PostRound)
|
||||
if (_gameTicker.RunLevel != GameRunLevel.PostRound)
|
||||
{
|
||||
_visibilitySystem.AddLayer((uid, visibility), (int) VisibilityFlags.Ghost, false);
|
||||
_visibilitySystem.RemoveLayer((uid, visibility), (int) VisibilityFlags.Normal, false);
|
||||
@@ -277,7 +275,7 @@ namespace Content.Server.Ghost
|
||||
return;
|
||||
}
|
||||
|
||||
_mindSystem.UnVisit(actor.PlayerSession);
|
||||
_mind.UnVisit(actor.PlayerSession);
|
||||
}
|
||||
|
||||
#region Warp
|
||||
@@ -455,7 +453,7 @@ namespace Content.Server.Ghost
|
||||
spawnPosition = null;
|
||||
|
||||
// If it's bad, look for a valid point to spawn
|
||||
spawnPosition ??= _ticker.GetObserverSpawnPoint();
|
||||
spawnPosition ??= _gameTicker.GetObserverSpawnPoint();
|
||||
|
||||
// Make sure the new point is valid too
|
||||
if (!IsValidSpawnPosition(spawnPosition))
|
||||
|
||||
@@ -25,6 +25,7 @@ public sealed class ImmovableRodSystem : EntitySystem
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageable = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
@@ -39,7 +40,7 @@ public sealed class ImmovableRodSystem : EntitySystem
|
||||
if (!TryComp<MapGridComponent>(trans.GridUid, out var grid))
|
||||
continue;
|
||||
|
||||
grid.SetTile(trans.Coordinates, Tile.Empty);
|
||||
_map.SetTile(trans.GridUid.Value, grid, trans.Coordinates, Tile.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -74,14 +74,12 @@ public sealed class SubdermalImplantSystem : SharedSubdermalImplantSystem
|
||||
return;
|
||||
|
||||
// same as store code, but message is only shown to yourself
|
||||
args.Handled = _store.TryAddCurrency(_store.GetCurrencyValue(args.Used, currency), uid, store);
|
||||
|
||||
if (!args.Handled)
|
||||
if (!_store.TryAddCurrency((args.Used, currency), (uid, store)))
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
var msg = Loc.GetString("store-currency-inserted-implant", ("used", args.Used));
|
||||
_popup.PopupEntity(msg, args.User, args.User);
|
||||
QueueDel(args.Used);
|
||||
}
|
||||
|
||||
private void OnFreedomImplant(EntityUid uid, SubdermalImplantComponent component, UseFreedomImplantEvent args)
|
||||
|
||||
@@ -7,6 +7,7 @@ using Content.Server.Chat.Managers;
|
||||
using Content.Server.Connection;
|
||||
using Content.Server.Database;
|
||||
using Content.Server.Discord;
|
||||
using Content.Server.Discord.WebhookMessages;
|
||||
using Content.Server.EUI;
|
||||
using Content.Server.GhostKick;
|
||||
using Content.Server.Info;
|
||||
@@ -14,8 +15,6 @@ using Content.Server.Mapping;
|
||||
using Content.Server.Maps;
|
||||
using Content.Server.MoMMI;
|
||||
using Content.Server.NodeContainer.NodeGroups;
|
||||
using Content.Server.Objectives;
|
||||
using Content.Server.Players;
|
||||
using Content.Server.Players.JobWhitelist;
|
||||
using Content.Server.Players.PlayTimeTracking;
|
||||
using Content.Server.Players.RateLimiting;
|
||||
@@ -26,8 +25,10 @@ using Content.Server.Voting.Managers;
|
||||
using Content.Server.Worldgen.Tools;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Administration.Managers;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Kitchen;
|
||||
using Content.Shared.Players.PlayTimeTracking;
|
||||
using Content.Shared.Players.RateLimiting;
|
||||
|
||||
namespace Content.Server.IoC
|
||||
{
|
||||
@@ -36,6 +37,7 @@ namespace Content.Server.IoC
|
||||
public static void Register()
|
||||
{
|
||||
IoCManager.Register<IChatManager, ChatManager>();
|
||||
IoCManager.Register<ISharedChatManager, ChatManager>();
|
||||
IoCManager.Register<IChatSanitizationManager, ChatSanitizationManager>();
|
||||
IoCManager.Register<IMoMMILink, MoMMILink>();
|
||||
IoCManager.Register<IServerPreferencesManager, ServerPreferencesManager>();
|
||||
@@ -63,11 +65,13 @@ namespace Content.Server.IoC
|
||||
IoCManager.Register<ServerInfoManager>();
|
||||
IoCManager.Register<PoissonDiskSampler>();
|
||||
IoCManager.Register<DiscordWebhook>();
|
||||
IoCManager.Register<VoteWebhooks>();
|
||||
IoCManager.Register<ServerDbEntryManager>();
|
||||
IoCManager.Register<ISharedPlaytimeManager, PlayTimeTrackingManager>();
|
||||
IoCManager.Register<ServerApi>();
|
||||
IoCManager.Register<JobWhitelistManager>();
|
||||
IoCManager.Register<PlayerRateLimitManager>();
|
||||
IoCManager.Register<SharedPlayerRateLimitManager, PlayerRateLimitManager>();
|
||||
IoCManager.Register<MappingManager>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ using Content.Server.CartridgeLoader;
|
||||
using Content.Server.CartridgeLoader.Cartridges;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Interaction;
|
||||
using Content.Server.MassMedia.Components;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Station.Systems;
|
||||
@@ -27,7 +26,6 @@ public sealed class NewsSystem : SharedNewsSystem
|
||||
{
|
||||
[Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly InteractionSystem _interaction = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _ui = default!;
|
||||
[Dependency] private readonly CartridgeLoaderSystem _cartridgeLoaderSystem = default!;
|
||||
@@ -35,7 +33,6 @@ public sealed class NewsSystem : SharedNewsSystem
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly StationSystem _station = default!;
|
||||
[Dependency] private readonly GameTicker _ticker = default!;
|
||||
[Dependency] private readonly AccessReaderSystem _accessReader = default!;
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
|
||||
@@ -54,7 +54,7 @@ public sealed partial class HealthAnalyzerComponent : Component
|
||||
/// Sound played on scanning end
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier? ScanningEndSound;
|
||||
public SoundSpecifier ScanningEndSound = new SoundPathSpecifier("/Audio/Items/Medical/healthscanner.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// Whether to show up the popup
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Atmos.Piping.Components;
|
||||
using Content.Server.Atmos.Piping.Unary.EntitySystems;
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Server.Medical.Components;
|
||||
using Content.Server.NodeContainer;
|
||||
using Content.Server.NodeContainer.EntitySystems;
|
||||
using Content.Server.NodeContainer.NodeGroups;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Temperature.Components;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Atmos;
|
||||
@@ -18,7 +15,6 @@ using Content.Shared.UserInterface;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.Climbing.Systems;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Database;
|
||||
@@ -46,7 +42,6 @@ public sealed partial class CryoPodSystem : SharedCryoPodSystem
|
||||
[Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;
|
||||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
|
||||
[Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||
[Dependency] private readonly SharedToolSystem _toolSystem = default!;
|
||||
@@ -196,7 +191,7 @@ public sealed partial class CryoPodSystem : SharedCryoPodSystem
|
||||
}
|
||||
|
||||
// TODO: This should be a state my dude
|
||||
_userInterfaceSystem.ServerSendUiMessage(
|
||||
_uiSystem.ServerSendUiMessage(
|
||||
entity.Owner,
|
||||
HealthAnalyzerUiKey.Key,
|
||||
new HealthAnalyzerScannedUserMessage(GetNetEntity(entity.Comp.BodyContainer.ContainedEntity),
|
||||
|
||||
@@ -13,11 +13,9 @@ using Content.Shared.Item.ItemToggle.Components;
|
||||
using Content.Shared.MedicalScanner;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.PowerCell;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Medical;
|
||||
@@ -104,7 +102,8 @@ public sealed class HealthAnalyzerSystem : EntitySystem
|
||||
if (args.Handled || args.Cancelled || args.Target == null || !_cell.HasDrawCharge(uid, user: args.User))
|
||||
return;
|
||||
|
||||
_audio.PlayPvs(uid.Comp.ScanningEndSound, uid);
|
||||
if (!uid.Comp.Silent)
|
||||
_audio.PlayPvs(uid.Comp.ScanningEndSound, uid);
|
||||
|
||||
OpenUserInterface(args.User, uid);
|
||||
BeginAnalyzingEntity(uid, args.Target.Value);
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Content.Server.NPC.Queries.Considerations;
|
||||
|
||||
/// <summary>
|
||||
/// Returns 1f if the target is on fire or 0f if not.
|
||||
/// </summary>
|
||||
public sealed partial class TargetOnFireCon : UtilityConsideration
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Fluids.EntitySystems;
|
||||
using Content.Server.NPC.Queries;
|
||||
using Content.Server.NPC.Queries.Considerations;
|
||||
@@ -351,6 +352,12 @@ public sealed class NPCUtilitySystem : EntitySystem
|
||||
|
||||
return 0f;
|
||||
}
|
||||
case TargetOnFireCon:
|
||||
{
|
||||
if (TryComp(targetUid, out FlammableComponent? fire) && fire.OnFire)
|
||||
return 1f;
|
||||
return 0f;
|
||||
}
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ namespace Content.Server.Nutrition.EntitySystems
|
||||
[Dependency] private readonly FoodSystem _foodSystem = default!;
|
||||
[Dependency] private readonly ExplosionSystem _explosionSystem = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
|
||||
private void InitializeVapes()
|
||||
{
|
||||
@@ -127,7 +126,7 @@ namespace Content.Server.Nutrition.EntitySystems
|
||||
|| args.Args.Target == null)
|
||||
return;
|
||||
|
||||
var environment = _atmosphereSystem.GetContainingMixture(args.Args.Target.Value, true, true);
|
||||
var environment = _atmos.GetContainingMixture(args.Args.Target.Value, true, true);
|
||||
if (environment == null)
|
||||
{
|
||||
return;
|
||||
@@ -139,7 +138,7 @@ namespace Content.Server.Nutrition.EntitySystems
|
||||
var merger = new GasMixture(1) { Temperature = args.Solution.Temperature };
|
||||
merger.SetMoles(entity.Comp.GasType, args.Solution.Volume.Value / entity.Comp.ReductionFactor);
|
||||
|
||||
_atmosphereSystem.Merge(environment, merger);
|
||||
_atmos.Merge(environment, merger);
|
||||
|
||||
args.Solution.RemoveAllSolution();
|
||||
|
||||
|
||||
@@ -962,7 +962,7 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
}
|
||||
}
|
||||
|
||||
grid.SetTiles(tiles);
|
||||
_mapSystem.SetTiles(gridUid, grid, tiles);
|
||||
tiles.Clear();
|
||||
component.LoadedChunks.Remove(chunk);
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ using Content.Shared.CCVar;
|
||||
using Content.Shared.Power;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server.ParticleAccelerator.EntitySystems;
|
||||
@@ -18,7 +17,6 @@ public sealed partial class ParticleAcceleratorSystem
|
||||
{
|
||||
[Dependency] private readonly IAdminManager _adminManager = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
private void InitializeControlBoxSystem()
|
||||
{
|
||||
@@ -175,7 +173,7 @@ public sealed partial class ParticleAcceleratorSystem
|
||||
if (strength >= alertMinPowerState)
|
||||
{
|
||||
var pos = Transform(uid);
|
||||
if (_timing.CurTime > comp.EffectCooldown)
|
||||
if (_gameTiming.CurTime > comp.EffectCooldown)
|
||||
{
|
||||
_chat.SendAdminAlert(player,
|
||||
Loc.GetString("particle-accelerator-admin-power-strength-warning",
|
||||
@@ -186,7 +184,7 @@ public sealed partial class ParticleAcceleratorSystem
|
||||
Filter.Empty().AddPlayers(_adminManager.ActiveAdmins),
|
||||
false,
|
||||
AudioParams.Default.WithVolume(-8f));
|
||||
comp.EffectCooldown = _timing.CurTime + comp.CooldownDuration;
|
||||
comp.EffectCooldown = _gameTiming.CurTime + comp.CooldownDuration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ public sealed class ChaoticJumpSystem : VirtualController
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _xform = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -73,6 +72,6 @@ public sealed class ChaoticJumpSystem : VirtualController
|
||||
|
||||
Spawn(component.Effect, transform.Coordinates);
|
||||
|
||||
_xform.SetWorldPosition(uid, targetPos);
|
||||
_transform.SetWorldPosition(uid, targetPos);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Players.RateLimiting;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
@@ -10,26 +11,7 @@ using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Players.RateLimiting;
|
||||
|
||||
/// <summary>
|
||||
/// General-purpose system to rate limit actions taken by clients, such as chat messages.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Different categories of rate limits must be registered ahead of time by calling <see cref="Register"/>.
|
||||
/// Once registered, you can simply call <see cref="CountAction"/> to count a rate-limited action for a player.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This system is intended for rate limiting player actions over short periods,
|
||||
/// to ward against spam that can cause technical issues such as admin client load.
|
||||
/// It should not be used for in-game actions or similar.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Rate limits are reset when a client reconnects.
|
||||
/// This should not be an issue for the reasonably short rate limit periods this system is intended for.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="RateLimitRegistration"/>
|
||||
public sealed class PlayerRateLimitManager
|
||||
public sealed class PlayerRateLimitManager : SharedPlayerRateLimitManager
|
||||
{
|
||||
[Dependency] private readonly IAdminLogManager _adminLog = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
@@ -39,18 +21,7 @@ public sealed class PlayerRateLimitManager
|
||||
private readonly Dictionary<string, RegistrationData> _registrations = new();
|
||||
private readonly Dictionary<ICommonSession, Dictionary<string, RateLimitDatum>> _rateLimitData = new();
|
||||
|
||||
/// <summary>
|
||||
/// Count and validate an action performed by a player against rate limits.
|
||||
/// </summary>
|
||||
/// <param name="player">The player performing the action.</param>
|
||||
/// <param name="key">The key string that was previously used to register a rate limit category.</param>
|
||||
/// <returns>Whether the action counted should be blocked due to surpassing rate limits or not.</returns>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// <paramref name="player"/> is not a connected player
|
||||
/// OR <paramref name="key"/> is not a registered rate limit category.
|
||||
/// </exception>
|
||||
/// <seealso cref="Register"/>
|
||||
public RateLimitStatus CountAction(ICommonSession player, string key)
|
||||
public override RateLimitStatus CountAction(ICommonSession player, string key)
|
||||
{
|
||||
if (player.Status == SessionStatus.Disconnected)
|
||||
throw new ArgumentException("Player is not connected");
|
||||
@@ -74,7 +45,8 @@ public sealed class PlayerRateLimitManager
|
||||
return RateLimitStatus.Allowed;
|
||||
|
||||
// Breached rate limits, inform admins if configured.
|
||||
if (registration.AdminAnnounceDelay is { } cvarAnnounceDelay)
|
||||
// Negative delays can be used to disable admin announcements.
|
||||
if (registration.AdminAnnounceDelay is {TotalSeconds: >= 0} cvarAnnounceDelay)
|
||||
{
|
||||
if (datum.NextAdminAnnounce < time)
|
||||
{
|
||||
@@ -85,7 +57,7 @@ public sealed class PlayerRateLimitManager
|
||||
|
||||
if (!datum.Announced)
|
||||
{
|
||||
registration.Registration.PlayerLimitedAction(player);
|
||||
registration.Registration.PlayerLimitedAction?.Invoke(player);
|
||||
_adminLog.Add(
|
||||
registration.Registration.AdminLogType,
|
||||
LogImpact.Medium,
|
||||
@@ -97,17 +69,7 @@ public sealed class PlayerRateLimitManager
|
||||
return RateLimitStatus.Blocked;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register a new rate limit category.
|
||||
/// </summary>
|
||||
/// <param name="key">
|
||||
/// The key string that will be referred to later with <see cref="CountAction"/>.
|
||||
/// Must be unique and should probably just be a constant somewhere.
|
||||
/// </param>
|
||||
/// <param name="registration">The data specifying the rate limit's parameters.</param>
|
||||
/// <exception cref="InvalidOperationException"><paramref name="key"/> has already been registered.</exception>
|
||||
/// <exception cref="ArgumentException"><paramref name="registration"/> is invalid.</exception>
|
||||
public void Register(string key, RateLimitRegistration registration)
|
||||
public override void Register(string key, RateLimitRegistration registration)
|
||||
{
|
||||
if (_registrations.ContainsKey(key))
|
||||
throw new InvalidOperationException($"Key already registered: {key}");
|
||||
@@ -135,7 +97,7 @@ public sealed class PlayerRateLimitManager
|
||||
if (registration.CVarAdminAnnounceDelay != null)
|
||||
{
|
||||
_cfg.OnValueChanged(
|
||||
registration.CVarLimitCount,
|
||||
registration.CVarAdminAnnounceDelay,
|
||||
i => data.AdminAnnounceDelay = TimeSpan.FromSeconds(i),
|
||||
invokeImmediately: true);
|
||||
}
|
||||
@@ -143,10 +105,7 @@ public sealed class PlayerRateLimitManager
|
||||
_registrations.Add(key, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the manager's functionality at game startup.
|
||||
/// </summary>
|
||||
public void Initialize()
|
||||
public override void Initialize()
|
||||
{
|
||||
_playerManager.PlayerStatusChanged += PlayerManagerOnPlayerStatusChanged;
|
||||
}
|
||||
@@ -189,66 +148,3 @@ public sealed class PlayerRateLimitManager
|
||||
public TimeSpan NextAdminAnnounce;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains all data necessary to register a rate limit with <see cref="PlayerRateLimitManager.Register"/>.
|
||||
/// </summary>
|
||||
public sealed class RateLimitRegistration
|
||||
{
|
||||
/// <summary>
|
||||
/// CVar that controls the period over which the rate limit is counted, measured in seconds.
|
||||
/// </summary>
|
||||
public required CVarDef<int> CVarLimitPeriodLength { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// CVar that controls how many actions are allowed in a single rate limit period.
|
||||
/// </summary>
|
||||
public required CVarDef<int> CVarLimitCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// An action that gets invoked when this rate limit has been breached by a player.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This can be used for informing players or taking administrative action.
|
||||
/// </remarks>
|
||||
public required Action<ICommonSession> PlayerLimitedAction { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// CVar that controls the minimum delay between admin notifications, measured in seconds.
|
||||
/// This can be omitted to have no admin notification system.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If set, <see cref="AdminAnnounceAction"/> must be set too.
|
||||
/// </remarks>
|
||||
public CVarDef<int>? CVarAdminAnnounceDelay { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// An action that gets invoked when a rate limit was breached and admins should be notified.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If set, <see cref="CVarAdminAnnounceDelay"/> must be set too.
|
||||
/// </remarks>
|
||||
public Action<ICommonSession>? AdminAnnounceAction { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Log type used to log rate limit violations to the admin logs system.
|
||||
/// </summary>
|
||||
public LogType AdminLogType { get; init; } = LogType.RateLimited;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of a rate-limited operation.
|
||||
/// </summary>
|
||||
/// <seealso cref="PlayerRateLimitManager.CountAction"/>
|
||||
public enum RateLimitStatus : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// The action was not blocked by the rate limit.
|
||||
/// </summary>
|
||||
Allowed,
|
||||
|
||||
/// <summary>
|
||||
/// The action was blocked by the rate limit.
|
||||
/// </summary>
|
||||
Blocked,
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ namespace Content.Server.Pointing.EntitySystems
|
||||
[Dependency] private readonly VisibilitySystem _visibilitySystem = default!;
|
||||
[Dependency] private readonly SharedMindSystem _minds = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly ExamineSystemShared _examine = default!;
|
||||
|
||||
@@ -73,8 +74,13 @@ namespace Content.Server.Pointing.EntitySystems
|
||||
}
|
||||
|
||||
// TODO: FOV
|
||||
private void SendMessage(EntityUid source, IEnumerable<ICommonSession> viewers, EntityUid pointed, string selfMessage,
|
||||
string viewerMessage, string? viewerPointedAtMessage = null)
|
||||
private void SendMessage(
|
||||
EntityUid source,
|
||||
IEnumerable<ICommonSession> viewers,
|
||||
EntityUid pointed,
|
||||
string selfMessage,
|
||||
string viewerMessage,
|
||||
string? viewerPointedAtMessage = null)
|
||||
{
|
||||
var netSource = GetNetEntity(source);
|
||||
|
||||
@@ -226,8 +232,8 @@ namespace Content.Server.Pointing.EntitySystems
|
||||
|
||||
if (_mapManager.TryFindGridAt(mapCoordsPointed, out var gridUid, out var grid))
|
||||
{
|
||||
position = $"EntId={gridUid} {grid.WorldToTile(mapCoordsPointed.Position)}";
|
||||
tileRef = grid.GetTileRef(grid.WorldToTile(mapCoordsPointed.Position));
|
||||
position = $"EntId={gridUid} {_map.WorldToTile(gridUid, grid, mapCoordsPointed.Position)}";
|
||||
tileRef = _map.GetTileRef(gridUid, grid, _map.WorldToTile(gridUid, grid, mapCoordsPointed.Position));
|
||||
}
|
||||
|
||||
var tileDef = _tileDefinitionManager[tileRef?.Tile.TypeId ?? 0];
|
||||
|
||||
@@ -11,6 +11,8 @@ namespace Content.Server.Power.EntitySystems;
|
||||
public sealed partial class CableSystem
|
||||
{
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
|
||||
private void InitializeCablePlacer()
|
||||
{
|
||||
@@ -26,11 +28,12 @@ public sealed partial class CableSystem
|
||||
if (component.CablePrototypeId == null)
|
||||
return;
|
||||
|
||||
if(!TryComp<MapGridComponent>(args.ClickLocation.GetGridUid(EntityManager), out var grid))
|
||||
if(!TryComp<MapGridComponent>(_transform.GetGrid(args.ClickLocation), out var grid))
|
||||
return;
|
||||
|
||||
var gridUid = _transform.GetGrid(args.ClickLocation)!.Value;
|
||||
var snapPos = grid.TileIndicesFor(args.ClickLocation);
|
||||
var tileDef = (ContentTileDefinition) _tileManager[grid.GetTileRef(snapPos).Tile.TypeId];
|
||||
var tileDef = (ContentTileDefinition) _tileManager[_map.GetTileRef(gridUid, grid,snapPos).Tile.TypeId];
|
||||
|
||||
if ((!tileDef.IsSubFloor || !tileDef.Sturdy) && placer.Comp.CP14OnlySubfloor) //CP14 if only subfloor
|
||||
return;
|
||||
|
||||
@@ -17,7 +17,6 @@ public sealed partial class CableSystem : EntitySystem
|
||||
[Dependency] private readonly SharedToolSystem _toolSystem = default!;
|
||||
[Dependency] private readonly StackSystem _stack = default!;
|
||||
[Dependency] private readonly ElectrocutionSystem _electrocutionSystem = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogs = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -51,7 +50,7 @@ public sealed partial class CableSystem : EntitySystem
|
||||
if (_electrocutionSystem.TryDoElectrifiedAct(uid, args.User))
|
||||
return;
|
||||
|
||||
_adminLogs.Add(LogType.CableCut, LogImpact.Medium, $"The {ToPrettyString(uid)} at {xform.Coordinates} was cut by {ToPrettyString(args.User)}.");
|
||||
_adminLogger.Add(LogType.CableCut, LogImpact.Medium, $"The {ToPrettyString(uid)} at {xform.Coordinates} was cut by {ToPrettyString(args.User)}.");
|
||||
|
||||
Spawn(cable.CableDroppedOnCutPrototype, xform.Coordinates);
|
||||
QueueDel(uid);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Server.Electrocution;
|
||||
using Content.Shared.Electrocution;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Wires;
|
||||
using Content.Shared.Power;
|
||||
@@ -104,6 +105,7 @@ public sealed partial class PowerWireAction : BaseWireAction
|
||||
&& !EntityManager.TryGetComponent(used, out electrified))
|
||||
return;
|
||||
|
||||
_electrocutionSystem.SetElectrifiedWireCut((used, electrified), setting);
|
||||
electrified.Enabled = setting;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Administration;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Procedural;
|
||||
using Content.Shared.Procedural.DungeonGenerators;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
@@ -44,7 +42,7 @@ public sealed partial class DungeonSystem
|
||||
}
|
||||
|
||||
var position = new Vector2i(posX, posY);
|
||||
var dungeonUid = _mapManager.GetMapEntityId(mapId);
|
||||
var dungeonUid = _maps.GetMapOrInvalid(mapId);
|
||||
|
||||
if (!TryComp<MapGridComponent>(dungeonUid, out var dungeonGrid))
|
||||
{
|
||||
@@ -118,7 +116,7 @@ public sealed partial class DungeonSystem
|
||||
}
|
||||
|
||||
var mapId = new MapId(mapInt);
|
||||
var mapUid = _mapManager.GetMapEntityId(mapId);
|
||||
var mapUid = _maps.GetMapOrInvalid(mapId);
|
||||
|
||||
if (!_prototype.TryIndex<DungeonRoomPackPrototype>(args[1], out var pack))
|
||||
{
|
||||
@@ -156,7 +154,7 @@ public sealed partial class DungeonSystem
|
||||
}
|
||||
}
|
||||
|
||||
grid.SetTiles(tiles);
|
||||
_maps.SetTiles(mapUid, grid, tiles);
|
||||
shell.WriteLine(Loc.GetString("cmd-dungen_pack_vis"));
|
||||
}
|
||||
|
||||
@@ -174,7 +172,7 @@ public sealed partial class DungeonSystem
|
||||
}
|
||||
|
||||
var mapId = new MapId(mapInt);
|
||||
var mapUid = _mapManager.GetMapEntityId(mapId);
|
||||
var mapUid =_maps.GetMapOrInvalid(mapId);
|
||||
|
||||
if (!_prototype.TryIndex<DungeonPresetPrototype>(args[1], out var preset))
|
||||
{
|
||||
@@ -197,7 +195,7 @@ public sealed partial class DungeonSystem
|
||||
}
|
||||
}
|
||||
|
||||
grid.SetTiles(tiles);
|
||||
_maps.SetTiles(mapUid, grid, tiles);
|
||||
shell.WriteLine(Loc.GetString("cmd-dungen_pack_vis"));
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ namespace Content.Shared.Remotes
|
||||
case OperatingMode.ToggleEmergencyAccess:
|
||||
if (airlockComp != null)
|
||||
{
|
||||
_airlock.ToggleEmergencyAccess(args.Target.Value, airlockComp);
|
||||
_airlock.SetEmergencyAccess((args.Target.Value, airlockComp), !airlockComp.EmergencyAccess);
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Medium,
|
||||
$"{ToPrettyString(args.User):player} used {ToPrettyString(args.Used)} on {ToPrettyString(args.Target.Value)} to set emergency access {(airlockComp.EmergencyAccess ? "on" : "off")}");
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ public sealed class EventHorizonSystem : SharedEventHorizonSystem
|
||||
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _xformSystem = default!;
|
||||
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
|
||||
[Dependency] private readonly TagSystem _tagSystem = default!;
|
||||
#endregion Dependencies
|
||||
|
||||
@@ -254,7 +255,7 @@ public sealed class EventHorizonSystem : SharedEventHorizonSystem
|
||||
|
||||
var ev = new TilesConsumedByEventHorizonEvent(tiles, gridId, grid, hungry, eventHorizon);
|
||||
RaiseLocalEvent(hungry, ref ev);
|
||||
grid.SetTiles(tiles);
|
||||
_mapSystem.SetTiles(gridId, grid, tiles);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -100,6 +100,13 @@ namespace Content.Server.Stack
|
||||
/// </summary>
|
||||
public List<EntityUid> SpawnMultiple(string entityPrototype, int amount, EntityCoordinates spawnPosition)
|
||||
{
|
||||
if (amount <= 0)
|
||||
{
|
||||
Log.Error(
|
||||
$"Attempted to spawn an invalid stack: {entityPrototype}, {amount}. Trace: {Environment.StackTrace}");
|
||||
return new();
|
||||
}
|
||||
|
||||
var spawns = CalculateSpawns(entityPrototype, amount);
|
||||
|
||||
var spawnedEnts = new List<EntityUid>();
|
||||
@@ -116,6 +123,13 @@ namespace Content.Server.Stack
|
||||
/// <inheritdoc cref="SpawnMultiple(string,int,EntityCoordinates)"/>
|
||||
public List<EntityUid> SpawnMultiple(string entityPrototype, int amount, EntityUid target)
|
||||
{
|
||||
if (amount <= 0)
|
||||
{
|
||||
Log.Error(
|
||||
$"Attempted to spawn an invalid stack: {entityPrototype}, {amount}. Trace: {Environment.StackTrace}");
|
||||
return new();
|
||||
}
|
||||
|
||||
var spawns = CalculateSpawns(entityPrototype, amount);
|
||||
|
||||
var spawnedEnts = new List<EntityUid>();
|
||||
|
||||
@@ -8,6 +8,11 @@ namespace Content.Server.Store.Components;
|
||||
/// Identifies a component that can be inserted into a store
|
||||
/// to increase its balance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note that if this entity is a stack of items, then this is meant to represent the value per stack item, not
|
||||
/// the whole stack. This also means that in general, the actual value should not be modified from the initial
|
||||
/// prototype value because otherwise stack merging/splitting may modify the total value.
|
||||
/// </remarks>
|
||||
[RegisterComponent]
|
||||
public sealed partial class CurrencyComponent : Component
|
||||
{
|
||||
@@ -16,6 +21,12 @@ public sealed partial class CurrencyComponent : Component
|
||||
/// The string is the currency type that will be added.
|
||||
/// The FixedPoint2 is the value of each individual currency entity.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note that if this entity is a stack of items, then this is meant to represent the value per stack item, not
|
||||
/// the whole stack. This also means that in general, the actual value should not be modified from the initial
|
||||
/// prototype value
|
||||
/// because otherwise stack merging/splitting may modify the total value.
|
||||
/// </remarks>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("price", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<FixedPoint2, CurrencyPrototype>))]
|
||||
public Dictionary<string, FixedPoint2> Price = new();
|
||||
|
||||
@@ -30,7 +30,6 @@ public sealed partial class StoreSystem
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly StackSystem _stack = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _ui = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
private void InitializeUi()
|
||||
{
|
||||
@@ -260,7 +259,7 @@ public sealed partial class StoreSystem
|
||||
//log dat shit.
|
||||
_admin.Add(LogType.StorePurchase,
|
||||
LogImpact.Low,
|
||||
$"{ToPrettyString(buyer):player} purchased listing \"{ListingLocalisationHelpers.GetLocalisedNameOrEntityName(listing, _prototypeManager)}\" from {ToPrettyString(uid)}");
|
||||
$"{ToPrettyString(buyer):player} purchased listing \"{ListingLocalisationHelpers.GetLocalisedNameOrEntityName(listing, _proto)}\" from {ToPrettyString(uid)}");
|
||||
|
||||
listing.PurchaseAmount++; //track how many times something has been purchased
|
||||
_audio.PlayEntity(component.BuySuccessSound, msg.Actor, uid); //cha-ching!
|
||||
@@ -284,6 +283,9 @@ public sealed partial class StoreSystem
|
||||
/// </remarks>
|
||||
private void OnRequestWithdraw(EntityUid uid, StoreComponent component, StoreRequestWithdrawMessage msg)
|
||||
{
|
||||
if (msg.Amount <= 0)
|
||||
return;
|
||||
|
||||
//make sure we have enough cash in the bank and we actually support this currency
|
||||
if (!component.Balance.TryGetValue(msg.Currency, out var currentAmount) || currentAmount < msg.Amount)
|
||||
return;
|
||||
@@ -307,7 +309,8 @@ public sealed partial class StoreSystem
|
||||
var cashId = proto.Cash[value];
|
||||
var amountToSpawn = (int) MathF.Floor((float) (amountRemaining / value));
|
||||
var ents = _stack.SpawnMultiple(cashId, amountToSpawn, coordinates);
|
||||
_hands.PickupOrDrop(buyer, ents.First());
|
||||
if (ents.FirstOrDefault() is {} ent)
|
||||
_hands.PickupOrDrop(buyer, ent);
|
||||
amountRemaining -= value * amountToSpawn;
|
||||
}
|
||||
|
||||
|
||||
@@ -92,14 +92,12 @@ public sealed partial class StoreSystem : EntitySystem
|
||||
if (ev.Cancelled)
|
||||
return;
|
||||
|
||||
args.Handled = TryAddCurrency(GetCurrencyValue(uid, component), args.Target.Value, store);
|
||||
if (!TryAddCurrency((uid, component), (args.Target.Value, store)))
|
||||
return;
|
||||
|
||||
if (args.Handled)
|
||||
{
|
||||
var msg = Loc.GetString("store-currency-inserted", ("used", args.Used), ("target", args.Target));
|
||||
_popup.PopupEntity(msg, args.Target.Value, args.User);
|
||||
QueueDel(args.Used);
|
||||
}
|
||||
args.Handled = true;
|
||||
var msg = Loc.GetString("store-currency-inserted", ("used", args.Used), ("target", args.Target));
|
||||
_popup.PopupEntity(msg, args.Target.Value, args.User);
|
||||
}
|
||||
|
||||
private void OnImplantActivate(EntityUid uid, StoreComponent component, OpenUplinkImplantEvent args)
|
||||
@@ -111,6 +109,10 @@ public sealed partial class StoreSystem : EntitySystem
|
||||
/// Gets the value from an entity's currency component.
|
||||
/// Scales with stacks.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If this result is intended to be used with <see cref="TryAddCurrency(Robust.Shared.GameObjects.Entity{Content.Server.Store.Components.CurrencyComponent?},Robust.Shared.GameObjects.Entity{Content.Shared.Store.Components.StoreComponent?})"/>,
|
||||
/// consider using <see cref="TryAddCurrency(Robust.Shared.GameObjects.Entity{Content.Server.Store.Components.CurrencyComponent?},Robust.Shared.GameObjects.Entity{Content.Shared.Store.Components.StoreComponent?})"/> instead to ensure that the currency is consumed in the process.
|
||||
/// </remarks>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="component"></param>
|
||||
/// <returns>The value of the currency</returns>
|
||||
@@ -121,19 +123,34 @@ public sealed partial class StoreSystem : EntitySystem
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to add a currency to a store's balance.
|
||||
/// Tries to add a currency to a store's balance. Note that if successful, this will consume the currency in the process.
|
||||
/// </summary>
|
||||
/// <param name="currencyEnt"></param>
|
||||
/// <param name="storeEnt"></param>
|
||||
/// <param name="currency">The currency to add</param>
|
||||
/// <param name="store">The store to add it to</param>
|
||||
/// <returns>Whether or not the currency was succesfully added</returns>
|
||||
[PublicAPI]
|
||||
public bool TryAddCurrency(EntityUid currencyEnt, EntityUid storeEnt, StoreComponent? store = null, CurrencyComponent? currency = null)
|
||||
public bool TryAddCurrency(Entity<CurrencyComponent?> currency, Entity<StoreComponent?> store)
|
||||
{
|
||||
if (!Resolve(currencyEnt, ref currency) || !Resolve(storeEnt, ref store))
|
||||
if (!Resolve(currency.Owner, ref currency.Comp))
|
||||
return false;
|
||||
return TryAddCurrency(GetCurrencyValue(currencyEnt, currency), storeEnt, store);
|
||||
|
||||
if (!Resolve(store.Owner, ref store.Comp))
|
||||
return false;
|
||||
|
||||
var value = currency.Comp.Price;
|
||||
if (TryComp(currency.Owner, out StackComponent? stack) && stack.Count != 1)
|
||||
{
|
||||
value = currency.Comp.Price
|
||||
.ToDictionary(v => v.Key, p => p.Value * stack.Count);
|
||||
}
|
||||
|
||||
if (!TryAddCurrency(value, store, store.Comp))
|
||||
return false;
|
||||
|
||||
// Avoid having the currency accidentally be re-used. E.g., if multiple clients try to use the currency in the
|
||||
// same tick
|
||||
currency.Comp.Price.Clear();
|
||||
if (stack != null)
|
||||
_stack.SetCount(currency.Owner, 0, stack);
|
||||
|
||||
QueueDel(currency);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -19,7 +19,7 @@ public sealed class SubFloorHideSystem : SharedSubFloorHideSystem
|
||||
var xform = Transform(uid);
|
||||
|
||||
if (TryComp<MapGridComponent>(xform.GridUid, out var grid)
|
||||
&& HasFloorCover(grid, grid.TileIndicesFor(xform.Coordinates)))
|
||||
&& HasFloorCover(xform.GridUid.Value, grid, Map.TileIndicesFor(xform.GridUid.Value, grid, xform.Coordinates)))
|
||||
{
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Linq;
|
||||
using Content.Server.Administration;
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Server.Database;
|
||||
using Content.Server.Discord.WebhookMessages;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.GameTicking.Presets;
|
||||
using Content.Server.Maps;
|
||||
@@ -27,6 +28,7 @@ namespace Content.Server.Voting.Managers
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly IBanManager _bans = default!;
|
||||
[Dependency] private readonly IServerDbManager _dbManager = default!;
|
||||
[Dependency] private readonly VoteWebhooks _voteWebhooks = default!;
|
||||
|
||||
private VotingSystem? _votingSystem;
|
||||
private RoleSystem? _roleSystem;
|
||||
@@ -449,10 +451,13 @@ namespace Content.Server.Voting.Managers
|
||||
var vote = CreateVote(options);
|
||||
_adminLogger.Add(LogType.Vote, LogImpact.Extreme, $"Votekick for {located.Username} ({targetEntityName}) due to {reason} started, initiated by {initiator}.");
|
||||
|
||||
// Create Discord webhook
|
||||
var webhookState = _voteWebhooks.CreateWebhookIfConfigured(options, _cfg.GetCVar(CCVars.DiscordVotekickWebhook), Loc.GetString("votekick-webhook-name"), options.Title + "\n" + Loc.GetString("votekick-webhook-description", ("initiator", initiatorName), ("target", targetSession)));
|
||||
|
||||
// Time out the vote now that we know it will happen
|
||||
TimeoutStandardVote(StandardVoteType.Votekick);
|
||||
|
||||
vote.OnFinished += (_, _) =>
|
||||
vote.OnFinished += (_, eventArgs) =>
|
||||
{
|
||||
|
||||
var votesYes = vote.VotesPerOption["yes"];
|
||||
@@ -487,6 +492,7 @@ namespace Content.Server.Voting.Managers
|
||||
{
|
||||
_adminLogger.Add(LogType.Vote, LogImpact.Extreme, $"Votekick for {located.Username} attempted to pass, but an admin was online. Yes: {votesYes} / No: {votesNo}. Yes: {yesVotersString} / No: {noVotersString}");
|
||||
AnnounceCancelledVotekickForVoters(targetEntityName);
|
||||
_voteWebhooks.UpdateCancelledWebhookIfConfigured(webhookState, Loc.GetString("votekick-webhook-cancelled-admin-online"));
|
||||
return;
|
||||
}
|
||||
// Check if the target is an antag and the vote reason is raiding (this is to prevent false positives)
|
||||
@@ -494,6 +500,7 @@ namespace Content.Server.Voting.Managers
|
||||
{
|
||||
_adminLogger.Add(LogType.Vote, LogImpact.Extreme, $"Votekick for {located.Username} due to {reason} finished, created by {initiator}, but was cancelled due to the target being an antagonist.");
|
||||
AnnounceCancelledVotekickForVoters(targetEntityName);
|
||||
_voteWebhooks.UpdateCancelledWebhookIfConfigured(webhookState, Loc.GetString("votekick-webhook-cancelled-antag-target"));
|
||||
return;
|
||||
}
|
||||
// Check if the target is an admin/de-admined admin
|
||||
@@ -501,6 +508,7 @@ namespace Content.Server.Voting.Managers
|
||||
{
|
||||
_adminLogger.Add(LogType.Vote, LogImpact.Extreme, $"Votekick for {located.Username} due to {reason} finished, created by {initiator}, but was cancelled due to the target being a de-admined admin.");
|
||||
AnnounceCancelledVotekickForVoters(targetEntityName);
|
||||
_voteWebhooks.UpdateCancelledWebhookIfConfigured(webhookState, Loc.GetString("votekick-webhook-cancelled-admin-target"));
|
||||
return;
|
||||
}
|
||||
else
|
||||
@@ -515,6 +523,9 @@ namespace Content.Server.Voting.Managers
|
||||
severity = NoteSeverity.High;
|
||||
}
|
||||
|
||||
// Discord webhook, success
|
||||
_voteWebhooks.UpdateWebhookIfConfigured(webhookState, eventArgs);
|
||||
|
||||
uint minutes = (uint)_cfg.GetCVar(CCVars.VotekickBanDuration);
|
||||
|
||||
_bans.CreateServerBan(targetUid, target, null, null, targetHWid, minutes, severity, reason);
|
||||
@@ -522,6 +533,10 @@ namespace Content.Server.Voting.Managers
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
// Discord webhook, failure
|
||||
_voteWebhooks.UpdateWebhookIfConfigured(webhookState, eventArgs);
|
||||
|
||||
_adminLogger.Add(LogType.Vote, LogImpact.Extreme, $"Votekick failed: Yes: {votesYes} / No: {votesNo}. Yes: {yesVotersString} / No: {noVotersString}");
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("ui-vote-votekick-failure", ("target", targetEntityName), ("reason", reason)));
|
||||
}
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using Content.Server.Administration;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Discord;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Discord.WebhookMessages;
|
||||
using Content.Server.Voting.Managers;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
@@ -76,11 +73,9 @@ namespace Content.Server.Voting
|
||||
{
|
||||
[Dependency] private readonly IVoteManager _voteManager = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly DiscordWebhook _discord = default!;
|
||||
[Dependency] private readonly GameTicker _gameTicker = default!;
|
||||
[Dependency] private readonly IBaseServer _baseServer = default!;
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
[Dependency] private readonly VoteWebhooks _voteWebhooks = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
@@ -120,7 +115,7 @@ namespace Content.Server.Voting
|
||||
|
||||
var vote = _voteManager.CreateVote(options);
|
||||
|
||||
var webhookState = CreateWebhookIfConfigured(options);
|
||||
var webhookState = _voteWebhooks.CreateWebhookIfConfigured(options, _cfg.GetCVar(CCVars.DiscordVoteWebhook));
|
||||
|
||||
vote.OnFinished += (_, eventArgs) =>
|
||||
{
|
||||
@@ -136,12 +131,12 @@ namespace Content.Server.Voting
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("cmd-customvote-on-finished-win", ("winner", args[(int) eventArgs.Winner])));
|
||||
}
|
||||
|
||||
UpdateWebhookIfConfigured(webhookState, eventArgs);
|
||||
_voteWebhooks.UpdateWebhookIfConfigured(webhookState, eventArgs);
|
||||
};
|
||||
|
||||
vote.OnCancelled += _ =>
|
||||
{
|
||||
UpdateCancelledWebhookIfConfigured(webhookState);
|
||||
_voteWebhooks.UpdateCancelledWebhookIfConfigured(webhookState);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -156,159 +151,6 @@ namespace Content.Server.Voting
|
||||
var n = args.Length - 1;
|
||||
return CompletionResult.FromHint(Loc.GetString("cmd-customvote-arg-option-n", ("n", n)));
|
||||
}
|
||||
|
||||
private WebhookState? CreateWebhookIfConfigured(VoteOptions voteOptions)
|
||||
{
|
||||
// All this webhook code is complete garbage.
|
||||
// I tried to clean it up somewhat, at least to fix the glaring bugs in it.
|
||||
// Jesus christ man what is with our code review process.
|
||||
|
||||
var webhookUrl = _cfg.GetCVar(CCVars.DiscordVoteWebhook);
|
||||
if (string.IsNullOrEmpty(webhookUrl))
|
||||
return null;
|
||||
|
||||
// Set up the webhook payload
|
||||
var serverName = _baseServer.ServerName;
|
||||
|
||||
var fields = new List<WebhookEmbedField>();
|
||||
|
||||
foreach (var voteOption in voteOptions.Options)
|
||||
{
|
||||
var newVote = new WebhookEmbedField
|
||||
{
|
||||
Name = voteOption.text,
|
||||
Value = Loc.GetString("custom-vote-webhook-option-pending")
|
||||
};
|
||||
fields.Add(newVote);
|
||||
}
|
||||
|
||||
var runLevel = Loc.GetString($"game-run-level-{_gameTicker.RunLevel}");
|
||||
|
||||
var payload = new WebhookPayload()
|
||||
{
|
||||
Username = Loc.GetString("custom-vote-webhook-name"),
|
||||
Embeds = new List<WebhookEmbed>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Title = voteOptions.InitiatorText,
|
||||
Color = 13438992, // #CD1010
|
||||
Description = voteOptions.Title,
|
||||
Footer = new WebhookEmbedFooter
|
||||
{
|
||||
Text = Loc.GetString(
|
||||
"custom-vote-webhook-footer",
|
||||
("serverName", serverName),
|
||||
("roundId", _gameTicker.RoundId),
|
||||
("runLevel", runLevel)),
|
||||
},
|
||||
|
||||
Fields = fields,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var state = new WebhookState
|
||||
{
|
||||
WebhookUrl = webhookUrl,
|
||||
Payload = payload,
|
||||
};
|
||||
|
||||
CreateWebhookMessage(state, payload);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
private void UpdateWebhookIfConfigured(WebhookState? state, VoteFinishedEventArgs finished)
|
||||
{
|
||||
if (state == null)
|
||||
return;
|
||||
|
||||
var embed = state.Payload.Embeds![0];
|
||||
embed.Color = 2353993; // #23EB49
|
||||
|
||||
for (var i = 0; i < finished.Votes.Count; i++)
|
||||
{
|
||||
var oldName = embed.Fields[i].Name;
|
||||
var newValue = finished.Votes[i].ToString();
|
||||
embed.Fields[i] = new WebhookEmbedField { Name = oldName, Value = newValue, Inline = true};
|
||||
}
|
||||
|
||||
state.Payload.Embeds[0] = embed;
|
||||
|
||||
UpdateWebhookMessage(state, state.Payload, state.MessageId);
|
||||
}
|
||||
|
||||
private void UpdateCancelledWebhookIfConfigured(WebhookState? state)
|
||||
{
|
||||
if (state == null)
|
||||
return;
|
||||
|
||||
var embed = state.Payload.Embeds![0];
|
||||
embed.Color = 13356304; // #CBCD10
|
||||
embed.Description += "\n\n" + Loc.GetString("custom-vote-webhook-cancelled");
|
||||
|
||||
for (var i = 0; i < embed.Fields.Count; i++)
|
||||
{
|
||||
var oldName = embed.Fields[i].Name;
|
||||
embed.Fields[i] = new WebhookEmbedField { Name = oldName, Value = Loc.GetString("custom-vote-webhook-option-cancelled"), Inline = true};
|
||||
}
|
||||
|
||||
state.Payload.Embeds[0] = embed;
|
||||
|
||||
UpdateWebhookMessage(state, state.Payload, state.MessageId);
|
||||
}
|
||||
|
||||
// Sends the payload's message.
|
||||
private async void CreateWebhookMessage(WebhookState state, WebhookPayload payload)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (await _discord.GetWebhook(state.WebhookUrl) is not { } identifier)
|
||||
return;
|
||||
|
||||
state.Identifier = identifier.ToIdentifier();
|
||||
|
||||
_sawmill.Debug(JsonSerializer.Serialize(payload));
|
||||
|
||||
var request = await _discord.CreateMessage(identifier.ToIdentifier(), payload);
|
||||
var content = await request.Content.ReadAsStringAsync();
|
||||
state.MessageId = ulong.Parse(JsonNode.Parse(content)?["id"]!.GetValue<string>()!);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sawmill.Error($"Error while sending vote webhook to Discord: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
// Edits a pre-existing payload message, given an ID
|
||||
private async void UpdateWebhookMessage(WebhookState state, WebhookPayload payload, ulong id)
|
||||
{
|
||||
if (state.MessageId == 0)
|
||||
{
|
||||
_sawmill.Warning("Failed to deliver update to custom vote webhook: message ID was zero. This likely indicates a previous connection error sending the original message.");
|
||||
return;
|
||||
}
|
||||
|
||||
DebugTools.Assert(state.Identifier != default);
|
||||
|
||||
try
|
||||
{
|
||||
await _discord.EditMessage(state.Identifier, id, payload);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sawmill.Error($"Error while updating vote webhook on Discord: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class WebhookState
|
||||
{
|
||||
public required string WebhookUrl;
|
||||
public required WebhookPayload Payload;
|
||||
public WebhookIdentifier Identifier;
|
||||
public ulong MessageId;
|
||||
}
|
||||
}
|
||||
|
||||
[AnyCommand]
|
||||
|
||||
@@ -15,6 +15,7 @@ public sealed class BlobFloorPlanBuilderSystem : BaseWorldSystem
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDefinition = default!;
|
||||
[Dependency] private readonly TileSystem _tiles = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
@@ -25,10 +26,10 @@ public sealed class BlobFloorPlanBuilderSystem : BaseWorldSystem
|
||||
private void OnBlobFloorPlanBuilderStartup(EntityUid uid, BlobFloorPlanBuilderComponent component,
|
||||
ComponentStartup args)
|
||||
{
|
||||
PlaceFloorplanTiles(component, Comp<MapGridComponent>(uid));
|
||||
PlaceFloorplanTiles(uid, component, Comp<MapGridComponent>(uid));
|
||||
}
|
||||
|
||||
private void PlaceFloorplanTiles(BlobFloorPlanBuilderComponent comp, MapGridComponent grid)
|
||||
private void PlaceFloorplanTiles(EntityUid gridUid, BlobFloorPlanBuilderComponent comp, MapGridComponent grid)
|
||||
{
|
||||
// NO MORE THAN TWO ALLOCATIONS THANK YOU VERY MUCH.
|
||||
// TODO: Just put these on a field instead then?
|
||||
@@ -82,7 +83,7 @@ public sealed class BlobFloorPlanBuilderSystem : BaseWorldSystem
|
||||
}
|
||||
}
|
||||
|
||||
grid.SetTiles(taken.Select(x => (x.Key, x.Value)).ToList());
|
||||
_map.SetTiles(gridUid, grid, taken.Select(x => (x.Key, x.Value)).ToList());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ public sealed class SimpleFloorPlanPopulatorSystem : BaseWorldSystem
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDefinition = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
@@ -25,7 +26,7 @@ public sealed class SimpleFloorPlanPopulatorSystem : BaseWorldSystem
|
||||
{
|
||||
var placeables = new List<string?>(4);
|
||||
var grid = Comp<MapGridComponent>(uid);
|
||||
var enumerator = grid.GetAllTilesEnumerator();
|
||||
var enumerator = _map.GetAllTilesEnumerator(uid, grid);
|
||||
while (enumerator.MoveNext(out var tile))
|
||||
{
|
||||
var coords = grid.GridTileToLocal(tile.Value.GridIndices);
|
||||
|
||||
@@ -33,7 +33,6 @@ public abstract class SharedAnomalySystem : EntitySystem
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] protected readonly SharedPopupSystem Popup = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
public override void Initialize()
|
||||
@@ -372,7 +371,7 @@ public abstract class SharedAnomalySystem : EntitySystem
|
||||
if (tilerefs.Count == 0)
|
||||
break;
|
||||
|
||||
var tileref = _random.Pick(tilerefs);
|
||||
var tileref = Random.Pick(tilerefs);
|
||||
var distance = MathF.Sqrt(MathF.Pow(tileref.X - xform.LocalPosition.X, 2) + MathF.Pow(tileref.Y - xform.LocalPosition.Y, 2));
|
||||
|
||||
//cut outer & inner circle
|
||||
|
||||
@@ -478,6 +478,12 @@ namespace Content.Shared.CCVar
|
||||
public static readonly CVarDef<string> DiscordVoteWebhook =
|
||||
CVarDef.Create("discord.vote_webhook", string.Empty, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// URL of the Discord webhook which will relay all votekick votes. If left empty, disables the webhook.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<string> DiscordVotekickWebhook =
|
||||
CVarDef.Create("discord.votekick_webhook", string.Empty, CVar.SERVERONLY);
|
||||
|
||||
/// URL of the Discord webhook which will relay round restart messages.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<string> DiscordRoundUpdateWebhook =
|
||||
@@ -922,8 +928,8 @@ namespace Content.Shared.CCVar
|
||||
/// After the period has passed, the count resets.
|
||||
/// </summary>
|
||||
/// <seealso cref="AhelpRateLimitCount"/>
|
||||
public static readonly CVarDef<int> AhelpRateLimitPeriod =
|
||||
CVarDef.Create("ahelp.rate_limit_period", 2, CVar.SERVERONLY);
|
||||
public static readonly CVarDef<float> AhelpRateLimitPeriod =
|
||||
CVarDef.Create("ahelp.rate_limit_period", 2f, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// How many ahelp messages are allowed in a single rate limit period.
|
||||
@@ -1856,8 +1862,8 @@ namespace Content.Shared.CCVar
|
||||
/// After the period has passed, the count resets.
|
||||
/// </summary>
|
||||
/// <seealso cref="ChatRateLimitCount"/>
|
||||
public static readonly CVarDef<int> ChatRateLimitPeriod =
|
||||
CVarDef.Create("chat.rate_limit_period", 2, CVar.SERVERONLY);
|
||||
public static readonly CVarDef<float> ChatRateLimitPeriod =
|
||||
CVarDef.Create("chat.rate_limit_period", 2f, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// How many chat messages are allowed in a single rate limit period.
|
||||
@@ -1867,19 +1873,12 @@ namespace Content.Shared.CCVar
|
||||
/// <see cref="ChatRateLimitCount"/> divided by <see cref="ChatRateLimitCount"/>.
|
||||
/// </remarks>
|
||||
/// <seealso cref="ChatRateLimitPeriod"/>
|
||||
/// <seealso cref="ChatRateLimitAnnounceAdmins"/>
|
||||
public static readonly CVarDef<int> ChatRateLimitCount =
|
||||
CVarDef.Create("chat.rate_limit_count", 10, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// If true, announce when a player breached chat rate limit to game administrators.
|
||||
/// </summary>
|
||||
/// <seealso cref="ChatRateLimitAnnounceAdminsDelay"/>
|
||||
public static readonly CVarDef<bool> ChatRateLimitAnnounceAdmins =
|
||||
CVarDef.Create("chat.rate_limit_announce_admins", true, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Minimum delay (in seconds) between announcements from <see cref="ChatRateLimitAnnounceAdmins"/>.
|
||||
/// Minimum delay (in seconds) between notifying admins about chat message rate limit violations.
|
||||
/// A negative value disables admin announcements.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> ChatRateLimitAnnounceAdminsDelay =
|
||||
CVarDef.Create("chat.rate_limit_announce_admins_delay", 15, CVar.SERVERONLY);
|
||||
@@ -2075,6 +2074,34 @@ namespace Content.Shared.CCVar
|
||||
public static readonly CVarDef<bool> ToggleWalk =
|
||||
CVarDef.Create("control.toggle_walk", false, CVar.CLIENTONLY | CVar.ARCHIVE);
|
||||
|
||||
/*
|
||||
* Interactions
|
||||
*/
|
||||
|
||||
// The rationale behind the default limit is simply that I can easily get to 7 interactions per second by just
|
||||
// trying to spam toggle a light switch or lever (though the UseDelay component limits the actual effect of the
|
||||
// interaction). I don't want to accidentally spam admins with alerts just because somebody is spamming a
|
||||
// key manually, nor do we want to alert them just because the player is having network issues and the server
|
||||
// receives multiple interactions at once. But we also want to try catch people with modified clients that spam
|
||||
// many interactions on the same tick. Hence, a very short period, with a relatively high count.
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of interactions that a player can perform within <see cref="InteractionRateLimitCount"/> seconds
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> InteractionRateLimitCount =
|
||||
CVarDef.Create("interaction.rate_limit_count", 5, CVar.SERVER | CVar.REPLICATED);
|
||||
|
||||
/// <seealso cref="InteractionRateLimitCount"/>
|
||||
public static readonly CVarDef<float> InteractionRateLimitPeriod =
|
||||
CVarDef.Create("interaction.rate_limit_period", 0.5f, CVar.SERVER | CVar.REPLICATED);
|
||||
|
||||
/// <summary>
|
||||
/// Minimum delay (in seconds) between notifying admins about interaction rate limit violations. A negative
|
||||
/// value disables admin announcements.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> InteractionRateLimitAnnounceAdminsDelay =
|
||||
CVarDef.Create("interaction.rate_limit_announce_admins_delay", 120, CVar.SERVERONLY);
|
||||
|
||||
/*
|
||||
* STORAGE
|
||||
*/
|
||||
|
||||
11
Content.Shared/Cargo/Components/PriceGunComponent.cs
Normal file
11
Content.Shared/Cargo/Components/PriceGunComponent.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Cargo.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for the price gun, which calculates the price of any object it appraises.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class PriceGunComponent : Component
|
||||
{
|
||||
}
|
||||
55
Content.Shared/Cargo/SharedPriceGunSystem.cs
Normal file
55
Content.Shared/Cargo/SharedPriceGunSystem.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using Content.Shared.Cargo.Components;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Verbs;
|
||||
|
||||
namespace Content.Shared.Cargo.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// The price gun system! If this component is on an entity, you can scan objects (Click or use verb) to see their price.
|
||||
/// </summary>
|
||||
public abstract class SharedPriceGunSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<PriceGunComponent, GetVerbsEvent<UtilityVerb>>(OnUtilityVerb);
|
||||
SubscribeLocalEvent<PriceGunComponent, AfterInteractEvent>(OnAfterInteract);
|
||||
}
|
||||
|
||||
private void OnUtilityVerb(EntityUid uid, PriceGunComponent component, GetVerbsEvent<UtilityVerb> args)
|
||||
{
|
||||
if (!args.CanAccess || !args.CanInteract || args.Using == null)
|
||||
return;
|
||||
|
||||
var verb = new UtilityVerb()
|
||||
{
|
||||
Act = () =>
|
||||
{
|
||||
GetPriceOrBounty(uid, args.Target, args.User);
|
||||
},
|
||||
Text = Loc.GetString("price-gun-verb-text"),
|
||||
Message = Loc.GetString("price-gun-verb-message", ("object", Identity.Entity(args.Target, EntityManager)))
|
||||
};
|
||||
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
private void OnAfterInteract(Entity<PriceGunComponent> entity, ref AfterInteractEvent args)
|
||||
{
|
||||
if (!args.CanReach || args.Target == null || args.Handled)
|
||||
return;
|
||||
|
||||
args.Handled |= GetPriceOrBounty(entity, args.Target.Value, args.User);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the price or confirm if the item is a bounty. Will give a popup of the result to the passed user.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// This is abstract for prediction. When the bounty system / cargo systems that are necessary are moved to shared,
|
||||
/// combine all the server, client, and shared stuff into one non abstract file.
|
||||
/// </remarks>
|
||||
protected abstract bool GetPriceOrBounty(EntityUid priceGunUid, EntityUid target, EntityUid user);
|
||||
}
|
||||
8
Content.Shared/Chat/ISharedChatManager.cs
Normal file
8
Content.Shared/Chat/ISharedChatManager.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Content.Shared.Chat;
|
||||
|
||||
public interface ISharedChatManager
|
||||
{
|
||||
void Initialize();
|
||||
void SendAdminAlert(string message);
|
||||
void SendAdminAlert(EntityUid player, string message);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Shared.DeviceLinking;
|
||||
using Content.Shared.Doors.Systems;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
@@ -23,6 +24,18 @@ public sealed partial class AirlockComponent : Component
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool EmergencyAccess = false;
|
||||
|
||||
/// <summary>
|
||||
/// Sound to play when the airlock emergency access is turned on.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier EmergencyOnSound = new SoundPathSpecifier("/Audio/Machines/airlock_emergencyon.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// Sound to play when the airlock emergency access is turned off.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier EmergencyOffSound = new SoundPathSpecifier("/Audio/Machines/airlock_emergencyoff.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// Pry modifier for a powered airlock.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared.Doors.Components;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Prying.Components;
|
||||
using Content.Shared.Wires;
|
||||
@@ -10,7 +11,9 @@ public abstract class SharedAirlockSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] protected readonly SharedAppearanceSystem Appearance = default!;
|
||||
[Dependency] protected readonly SharedAudioSystem Audio = default!;
|
||||
[Dependency] protected readonly SharedDoorSystem DoorSystem = default!;
|
||||
[Dependency] protected readonly SharedPopupSystem Popup = default!;
|
||||
[Dependency] private readonly SharedWiresSystem _wiresSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
@@ -131,11 +134,23 @@ public abstract class SharedAirlockSystem : EntitySystem
|
||||
Appearance.SetData(uid, DoorVisuals.EmergencyLights, component.EmergencyAccess);
|
||||
}
|
||||
|
||||
public void ToggleEmergencyAccess(EntityUid uid, AirlockComponent component)
|
||||
public void SetEmergencyAccess(Entity<AirlockComponent> ent, bool value, EntityUid? user = null, bool predicted = false)
|
||||
{
|
||||
component.EmergencyAccess = !component.EmergencyAccess;
|
||||
Dirty(uid, component); // This only runs on the server apparently so we need this.
|
||||
UpdateEmergencyLightStatus(uid, component);
|
||||
if(!ent.Comp.Powered)
|
||||
return;
|
||||
|
||||
if (ent.Comp.EmergencyAccess == value)
|
||||
return;
|
||||
|
||||
ent.Comp.EmergencyAccess = value;
|
||||
Dirty(ent, ent.Comp); // This only runs on the server apparently so we need this.
|
||||
UpdateEmergencyLightStatus(ent, ent.Comp);
|
||||
|
||||
var sound = ent.Comp.EmergencyAccess ? ent.Comp.EmergencyOnSound : ent.Comp.EmergencyOffSound;
|
||||
if (predicted)
|
||||
Audio.PlayPredicted(sound, ent, user: user);
|
||||
else
|
||||
Audio.PlayPvs(sound, ent);
|
||||
}
|
||||
|
||||
public void SetAutoCloseDelayModifier(AirlockComponent component, float value)
|
||||
|
||||
@@ -77,8 +77,20 @@ public abstract partial class SharedDoorSystem
|
||||
|
||||
public void SetBoltsDown(Entity<DoorBoltComponent> ent, bool value, EntityUid? user = null, bool predicted = false)
|
||||
{
|
||||
TrySetBoltDown(ent, value, user, predicted);
|
||||
}
|
||||
|
||||
public bool TrySetBoltDown(
|
||||
Entity<DoorBoltComponent> ent,
|
||||
bool value,
|
||||
EntityUid? user = null,
|
||||
bool predicted = false
|
||||
)
|
||||
{
|
||||
if (!_powerReceiver.IsPowered(ent.Owner))
|
||||
return false;
|
||||
if (ent.Comp.BoltsDown == value)
|
||||
return;
|
||||
return false;
|
||||
|
||||
ent.Comp.BoltsDown = value;
|
||||
Dirty(ent, ent.Comp);
|
||||
@@ -89,6 +101,7 @@ public abstract partial class SharedDoorSystem
|
||||
Audio.PlayPredicted(sound, ent, user: user);
|
||||
else
|
||||
Audio.PlayPvs(sound, ent);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnStateChanged(Entity<DoorBoltComponent> entity, ref DoorStateChangedEvent args)
|
||||
|
||||
@@ -9,6 +9,7 @@ using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Physics;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Power.EntitySystems;
|
||||
using Content.Shared.Prying.Components;
|
||||
using Content.Shared.Prying.Systems;
|
||||
using Content.Shared.Stunnable;
|
||||
@@ -22,6 +23,7 @@ using Robust.Shared.Timing;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics;
|
||||
|
||||
namespace Content.Shared.Doors.Systems;
|
||||
|
||||
@@ -42,26 +44,19 @@ public abstract partial class SharedDoorSystem : EntitySystem
|
||||
[Dependency] private readonly PryingSystem _pryingSystem = default!;
|
||||
[Dependency] protected readonly SharedPopupSystem Popup = default!;
|
||||
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
|
||||
[Dependency] private readonly SharedPowerReceiverSystem _powerReceiver = default!;
|
||||
|
||||
|
||||
[ValidatePrototypeId<TagPrototype>]
|
||||
public const string DoorBumpTag = "DoorBumpOpener";
|
||||
|
||||
/// <summary>
|
||||
/// A body must have an intersection percentage larger than this in order to be considered as colliding with a
|
||||
/// door. Used for safety close-blocking and crushing.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The intersection percentage relies on WORLD AABBs. So if this is too small, and the grid is rotated 45
|
||||
/// degrees, then an entity outside of the airlock may be crushed.
|
||||
/// </remarks>
|
||||
public const float IntersectPercentage = 0.2f;
|
||||
|
||||
/// <summary>
|
||||
/// A set of doors that are currently opening, closing, or just queued to open/close after some delay.
|
||||
/// </summary>
|
||||
private readonly HashSet<Entity<DoorComponent>> _activeDoors = new();
|
||||
|
||||
private readonly HashSet<Entity<PhysicsComponent>> _doorIntersecting = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -161,7 +156,6 @@ public abstract partial class SharedDoorSystem : EntitySystem
|
||||
_activeDoors.Add(ent);
|
||||
|
||||
RaiseLocalEvent(ent, new DoorStateChangedEvent(door.State));
|
||||
AppearanceSystem.SetData(ent, DoorVisuals.State, door.State);
|
||||
}
|
||||
|
||||
protected bool SetState(EntityUid uid, DoorState state, DoorComponent? door = null)
|
||||
@@ -209,6 +203,7 @@ public abstract partial class SharedDoorSystem : EntitySystem
|
||||
door.State = state;
|
||||
Dirty(uid, door);
|
||||
RaiseLocalEvent(uid, new DoorStateChangedEvent(state));
|
||||
|
||||
AppearanceSystem.SetData(uid, DoorVisuals.State, door.State);
|
||||
return true;
|
||||
}
|
||||
@@ -555,20 +550,24 @@ public abstract partial class SharedDoorSystem : EntitySystem
|
||||
if (!TryComp<MapGridComponent>(xform.GridUid, out var mapGridComp))
|
||||
yield break;
|
||||
var tileRef = _mapSystem.GetTileRef(xform.GridUid.Value, mapGridComp, xform.Coordinates);
|
||||
var doorWorldBounds = _entityLookup.GetWorldBounds(tileRef);
|
||||
|
||||
_doorIntersecting.Clear();
|
||||
_entityLookup.GetLocalEntitiesIntersecting(xform.GridUid.Value, tileRef.GridIndices, _doorIntersecting, gridComp: mapGridComp, flags: (LookupFlags.All & ~LookupFlags.Sensors));
|
||||
|
||||
// TODO SLOTH fix electro's code.
|
||||
// ReSharper disable once InconsistentNaming
|
||||
var doorAABB = _entityLookup.GetWorldAABB(uid);
|
||||
|
||||
foreach (var otherPhysics in PhysicsSystem.GetCollidingEntities(Transform(uid).MapID, doorWorldBounds))
|
||||
foreach (var otherPhysics in _doorIntersecting)
|
||||
{
|
||||
if (otherPhysics.Comp == physics)
|
||||
continue;
|
||||
|
||||
if (!otherPhysics.Comp.CanCollide)
|
||||
continue;
|
||||
|
||||
//TODO: Make only shutters ignore these objects upon colliding instead of all airlocks
|
||||
// Excludes Glasslayer for windows, GlassAirlockLayer for windoors, TableLayer for tables
|
||||
if (!otherPhysics.Comp.CanCollide || otherPhysics.Comp.CollisionLayer == (int) CollisionGroup.GlassLayer || otherPhysics.Comp.CollisionLayer == (int) CollisionGroup.GlassAirlockLayer || otherPhysics.Comp.CollisionLayer == (int) CollisionGroup.TableLayer)
|
||||
if (otherPhysics.Comp.CollisionLayer == (int) CollisionGroup.GlassLayer || otherPhysics.Comp.CollisionLayer == (int) CollisionGroup.GlassAirlockLayer || otherPhysics.Comp.CollisionLayer == (int) CollisionGroup.TableLayer)
|
||||
continue;
|
||||
|
||||
//If the colliding entity is a slippable item ignore it by the airlock
|
||||
@@ -582,9 +581,6 @@ public abstract partial class SharedDoorSystem : EntitySystem
|
||||
if ((physics.CollisionMask & otherPhysics.Comp.CollisionLayer) == 0 && (otherPhysics.Comp.CollisionMask & physics.CollisionLayer) == 0)
|
||||
continue;
|
||||
|
||||
if (_entityLookup.GetWorldAABB(otherPhysics.Owner).IntersectPercentage(doorAABB) < IntersectPercentage)
|
||||
continue;
|
||||
|
||||
yield return otherPhysics.Owner;
|
||||
}
|
||||
}
|
||||
@@ -612,7 +608,7 @@ public abstract partial class SharedDoorSystem : EntitySystem
|
||||
var otherUid = args.OtherEntity;
|
||||
|
||||
if (Tags.HasTag(otherUid, DoorBumpTag))
|
||||
TryOpen(uid, door, otherUid, quiet: door.State == DoorState.Denying);
|
||||
TryOpen(uid, door, otherUid, quiet: door.State == DoorState.Denying, predicted: true);
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -712,7 +708,7 @@ public abstract partial class SharedDoorSystem : EntitySystem
|
||||
var (uid, door, physics) = ent;
|
||||
if (door.BumpOpen)
|
||||
{
|
||||
foreach (var other in PhysicsSystem.GetContactingEntities(uid, physics, approximate: true))
|
||||
foreach (var other in PhysicsSystem.GetContactingEntities(uid, physics))
|
||||
{
|
||||
if (Tags.HasTag(other, DoorBumpTag) && TryOpen(uid, door, other, quiet: true))
|
||||
break;
|
||||
|
||||
@@ -1,121 +1,131 @@
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
namespace Content.Server.Electrocution;
|
||||
namespace Content.Shared.Electrocution;
|
||||
|
||||
/// <summary>
|
||||
/// Component for things that shock users on touch.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class ElectrifiedComponent : Component
|
||||
{
|
||||
[DataField("enabled")]
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool Enabled = true;
|
||||
|
||||
/// <summary>
|
||||
/// Should player get damage on collide
|
||||
/// </summary>
|
||||
[DataField("onBump")]
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool OnBump = true;
|
||||
|
||||
/// <summary>
|
||||
/// Should player get damage on attack
|
||||
/// </summary>
|
||||
[DataField("onAttacked")]
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool OnAttacked = true;
|
||||
|
||||
/// <summary>
|
||||
/// When true - disables power if a window is present in the same tile
|
||||
/// </summary>
|
||||
[DataField("noWindowInTile")]
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool NoWindowInTile = false;
|
||||
|
||||
/// <summary>
|
||||
/// Should player get damage on interact with empty hand
|
||||
/// </summary>
|
||||
[DataField("onHandInteract")]
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool OnHandInteract = true;
|
||||
|
||||
/// <summary>
|
||||
/// Should player get damage on interact while holding an object in their hand
|
||||
/// </summary>
|
||||
[DataField("onInteractUsing")]
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool OnInteractUsing = true;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the entity requires power to function
|
||||
/// </summary>
|
||||
[DataField("requirePower")]
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool RequirePower = true;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the entity uses APC power
|
||||
/// </summary>
|
||||
[DataField("usesApcPower")]
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool UsesApcPower = false;
|
||||
|
||||
/// <summary>
|
||||
/// Identifier for the high voltage node.
|
||||
/// </summary>
|
||||
[DataField("highVoltageNode")]
|
||||
[DataField, AutoNetworkedField]
|
||||
public string? HighVoltageNode;
|
||||
|
||||
/// <summary>
|
||||
/// Identifier for the medium voltage node.
|
||||
/// </summary>
|
||||
[DataField("mediumVoltageNode")]
|
||||
[DataField, AutoNetworkedField]
|
||||
public string? MediumVoltageNode;
|
||||
|
||||
/// <summary>
|
||||
/// Identifier for the low voltage node.
|
||||
/// </summary>
|
||||
[DataField("lowVoltageNode")]
|
||||
[DataField, AutoNetworkedField]
|
||||
public string? LowVoltageNode;
|
||||
|
||||
/// <summary>
|
||||
/// Damage multiplier for HV electrocution
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[DataField, AutoNetworkedField]
|
||||
public float HighVoltageDamageMultiplier = 3f;
|
||||
|
||||
/// <summary>
|
||||
/// Shock time multiplier for HV electrocution
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[DataField, AutoNetworkedField]
|
||||
public float HighVoltageTimeMultiplier = 1.5f;
|
||||
|
||||
/// <summary>
|
||||
/// Damage multiplier for MV electrocution
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[DataField, AutoNetworkedField]
|
||||
public float MediumVoltageDamageMultiplier = 2f;
|
||||
|
||||
/// <summary>
|
||||
/// Shock time multiplier for MV electrocution
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[DataField, AutoNetworkedField]
|
||||
public float MediumVoltageTimeMultiplier = 1.25f;
|
||||
|
||||
[DataField("shockDamage")]
|
||||
[DataField, AutoNetworkedField]
|
||||
public float ShockDamage = 7.5f;
|
||||
|
||||
/// <summary>
|
||||
/// Shock time, in seconds.
|
||||
/// </summary>
|
||||
[DataField("shockTime")]
|
||||
[DataField, AutoNetworkedField]
|
||||
public float ShockTime = 8f;
|
||||
|
||||
[DataField("siemensCoefficient")]
|
||||
[DataField, AutoNetworkedField]
|
||||
public float SiemensCoefficient = 1f;
|
||||
|
||||
[DataField("shockNoises")]
|
||||
[DataField, AutoNetworkedField]
|
||||
public SoundSpecifier ShockNoises = new SoundCollectionSpecifier("sparks");
|
||||
|
||||
[DataField("playSoundOnShock")]
|
||||
[DataField, AutoNetworkedField]
|
||||
public SoundPathSpecifier AirlockElectrifyDisabled = new("/Audio/Machines/airlock_electrify_on.ogg");
|
||||
|
||||
[DataField, AutoNetworkedField]
|
||||
public SoundPathSpecifier AirlockElectrifyEnabled = new("/Audio/Machines/airlock_electrify_off.ogg");
|
||||
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool PlaySoundOnShock = true;
|
||||
|
||||
[DataField("shockVolume")]
|
||||
[DataField, AutoNetworkedField]
|
||||
public float ShockVolume = 20;
|
||||
|
||||
[DataField]
|
||||
[DataField, AutoNetworkedField]
|
||||
public float Probability = 1f;
|
||||
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool IsWireCut = false;
|
||||
}
|
||||
@@ -23,6 +23,20 @@ namespace Content.Shared.Electrocution
|
||||
Dirty(uid, insulated);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets electrified value of component and marks dirty if required.
|
||||
/// </summary>
|
||||
public void SetElectrified(Entity<ElectrifiedComponent> ent, bool value)
|
||||
{
|
||||
if (ent.Comp.Enabled == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ent.Comp.Enabled = value;
|
||||
Dirty(ent, ent.Comp);
|
||||
}
|
||||
|
||||
/// <param name="uid">Entity being electrocuted.</param>
|
||||
/// <param name="sourceUid">Source entity of the electrocution.</param>
|
||||
/// <param name="shockDamage">How much shock damage the entity takes.</param>
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.Shared.CombatMode;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Popups;
|
||||
@@ -155,7 +156,7 @@ public sealed class SharedExecutionSystem : EntitySystem
|
||||
if (predict)
|
||||
{
|
||||
_popup.PopupClient(
|
||||
Loc.GetString(locString, ("attacker", attacker), ("victim", victim), ("weapon", weapon)),
|
||||
Loc.GetString(locString, ("attacker", Identity.Entity(attacker, EntityManager)), ("victim", Identity.Entity(victim, EntityManager)), ("weapon", weapon)),
|
||||
attacker,
|
||||
attacker,
|
||||
PopupType.MediumCaution
|
||||
@@ -164,7 +165,7 @@ public sealed class SharedExecutionSystem : EntitySystem
|
||||
else
|
||||
{
|
||||
_popup.PopupEntity(
|
||||
Loc.GetString(locString, ("attacker", attacker), ("victim", victim), ("weapon", weapon)),
|
||||
Loc.GetString(locString, ("attacker", Identity.Entity(attacker, EntityManager)), ("victim", Identity.Entity(victim, EntityManager)), ("weapon", weapon)),
|
||||
attacker,
|
||||
attacker,
|
||||
PopupType.MediumCaution
|
||||
@@ -175,7 +176,7 @@ public sealed class SharedExecutionSystem : EntitySystem
|
||||
private void ShowExecutionExternalPopup(string locString, EntityUid attacker, EntityUid victim, EntityUid weapon)
|
||||
{
|
||||
_popup.PopupEntity(
|
||||
Loc.GetString(locString, ("attacker", attacker), ("victim", victim), ("weapon", weapon)),
|
||||
Loc.GetString(locString, ("attacker", Identity.Entity(attacker, EntityManager)), ("victim", Identity.Entity(victim, EntityManager)), ("weapon", weapon)),
|
||||
attacker,
|
||||
Filter.PvsExcept(attacker),
|
||||
true,
|
||||
|
||||
@@ -52,7 +52,7 @@ public sealed partial class DrainComponent : Component
|
||||
/// drain puddles from.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float Range = 2f;
|
||||
public float Range = 2.5f;
|
||||
|
||||
/// <summary>
|
||||
/// How often in seconds the drain checks for puddles around it.
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace Content.Shared.Friction
|
||||
[Dependency] private readonly SharedGravitySystem _gravity = default!;
|
||||
[Dependency] private readonly SharedMoverController _mover = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
|
||||
private EntityQuery<TileFrictionModifierComponent> _frictionQuery;
|
||||
private EntityQuery<TransformComponent> _xformQuery;
|
||||
@@ -185,7 +186,7 @@ namespace Content.Shared.Friction
|
||||
: DefaultFriction;
|
||||
}
|
||||
|
||||
var tile = grid.GetTileRef(xform.Coordinates);
|
||||
var tile = _map.GetTileRef(xform.GridUid.Value, grid, xform.Coordinates);
|
||||
|
||||
// If it's a map but on an empty tile then just assume it has gravity.
|
||||
if (tile.Tile.IsEmpty &&
|
||||
|
||||
@@ -80,6 +80,12 @@ public sealed partial class HandsComponent : Component
|
||||
|
||||
[DataField]
|
||||
public DisplacementData? HandDisplacement;
|
||||
|
||||
/// <summary>
|
||||
/// If false, hands cannot be stripped, and they do not show up in the stripping menu.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool CanBeStripped = true;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user