Merge remote-tracking branch 'upstream/stable' into ed-27-05-2025-upstream-sync
# Conflicts: # .github/CODEOWNERS # Content.Client/Guidebook/Controls/GuideReagentReaction.xaml.cs # Content.IntegrationTests/Tests/Chemistry/TryAllReactionsTest.cs # Content.Server/Procedural/DungeonJob/DungeonJob.OreDunGen.cs # Resources/Prototypes/Entities/Effects/chemistry_effects.yml # Resources/Prototypes/Entities/Mobs/Customization/Markings/human_hair.yml # Resources/Prototypes/GameRules/meteorswarms.yml # Resources/Prototypes/Procedural/dungeon_configs.yml
This commit is contained in:
@@ -1,9 +1,8 @@
|
||||
using Content.Server.Access.Components;
|
||||
using Content.Server.Humanoid.Systems;
|
||||
using Content.Server.PDA;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.PDA;
|
||||
using Content.Shared.Roles;
|
||||
|
||||
namespace Content.Server.Access.Systems;
|
||||
|
||||
@@ -17,10 +16,10 @@ public sealed class IdBindSystem : EntitySystem
|
||||
{
|
||||
base.Initialize();
|
||||
//Activate on mind being added
|
||||
SubscribeLocalEvent<IdBindComponent, MindAddedMessage>(TryBind);
|
||||
SubscribeLocalEvent<IdBindComponent, MapInitEvent>(TryBind, after: [typeof(RandomHumanoidSystem)]);
|
||||
}
|
||||
|
||||
private void TryBind(Entity<IdBindComponent> ent, ref MindAddedMessage args)
|
||||
private void TryBind(Entity<IdBindComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
if (!_cardSystem.TryFindIdCard(ent, out var cardId))
|
||||
return;
|
||||
@@ -31,9 +30,9 @@ public sealed class IdBindSystem : EntitySystem
|
||||
|
||||
if (!ent.Comp.BindPDAOwner)
|
||||
{
|
||||
//Remove after running once
|
||||
RemCompDeferred<IdBindComponent>(ent);
|
||||
return;
|
||||
//Remove after running once
|
||||
RemCompDeferred<IdBindComponent>(ent);
|
||||
return;
|
||||
}
|
||||
|
||||
//Get PDA from main slot and set us as owner
|
||||
|
||||
@@ -187,6 +187,6 @@ public sealed class PanicBunkerMinOverallMinutesCommand : LocalizedCommands
|
||||
}
|
||||
|
||||
_cfg.SetCVar(CCVars.PanicBunkerMinOverallMinutes, minutes);
|
||||
shell.WriteLine(Loc.GetString("panicbunker-command-overall-minutes-age-set", ("minutes", minutes)));
|
||||
shell.WriteLine(Loc.GetString("panicbunker-command-min-overall-minutes-set", ("minutes", minutes)));
|
||||
}
|
||||
}
|
||||
|
||||
68
Content.Server/Administration/Commands/StripAllCommand.cs
Normal file
68
Content.Server/Administration/Commands/StripAllCommand.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Inventory;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Server.Administration.Commands;
|
||||
|
||||
[AdminCommand(AdminFlags.Debug)]
|
||||
public sealed class StripAllCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
|
||||
[Dependency] private readonly InventorySystem _inventorySystem = default!;
|
||||
|
||||
public override string Command => "stripall";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("shell-need-exactly-one-argument"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!NetEntity.TryParse(args[0], out var targetUidNet) || !EntityManager.TryGetEntity(targetUidNet, out var targetEntity))
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("shell-entity-uid-must-be-number"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EntityManager.TryGetComponent<InventoryComponent>(targetEntity, out var inventory))
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("shell-entity-target-lacks-component", ("componentName", nameof(InventoryComponent))));
|
||||
return;
|
||||
}
|
||||
|
||||
var slots = _inventorySystem.GetSlotEnumerator((targetEntity.Value, inventory));
|
||||
while (slots.NextItem(out _, out var slot))
|
||||
{
|
||||
_inventorySystem.TryUnequip(targetEntity.Value, targetEntity.Value, slot.Name, true, true, inventory: inventory);
|
||||
}
|
||||
|
||||
if (EntityManager.TryGetComponent<HandsComponent>(targetEntity, out var hands))
|
||||
{
|
||||
foreach (var hand in _handsSystem.EnumerateHands(targetEntity.Value, hands))
|
||||
{
|
||||
_handsSystem.TryDrop(targetEntity.Value,
|
||||
hand,
|
||||
checkActionBlocker: false,
|
||||
doDropInteraction: false,
|
||||
handsComp: hands);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (args.Length == 1)
|
||||
{
|
||||
return CompletionResult.FromHintOptions(
|
||||
CompletionHelper.Components<InventoryComponent>(args[0]),
|
||||
Loc.GetString("cmd-stripall-player-completion"));
|
||||
}
|
||||
|
||||
return CompletionResult.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,10 @@ using Content.Server.Administration.UI;
|
||||
using Content.Server.Disposal.Tube;
|
||||
using Content.Server.EUI;
|
||||
using Content.Server.Ghost.Roles;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Mind.Commands;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Prayer;
|
||||
using Content.Server.Silicons.Laws;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||
@@ -15,26 +16,26 @@ using Content.Shared.Configurable;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Silicons.Laws.Components;
|
||||
using Content.Shared.Silicons.StationAi;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Server.Console;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Toolshed;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Linq;
|
||||
using Content.Server.Silicons.Laws;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Silicons.Laws.Components;
|
||||
using Robust.Server.Player;
|
||||
using Content.Shared.Silicons.StationAi;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using static Content.Shared.Configurable.ConfigurationComponent;
|
||||
|
||||
namespace Content.Server.Administration.Systems
|
||||
@@ -463,19 +464,34 @@ namespace Content.Server.Administration.Systems
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
// Set clothing verb
|
||||
if (_groupController.CanCommand(player, "setoutfit") &&
|
||||
EntityManager.HasComponent<InventoryComponent>(args.Target))
|
||||
if (TryComp<InventoryComponent>(args.Target, out var inventoryComponent))
|
||||
{
|
||||
Verb verb = new()
|
||||
// Strip all verb
|
||||
if (_groupController.CanCommand(player, "stripall"))
|
||||
{
|
||||
Text = Loc.GetString("set-outfit-verb-get-data-text"),
|
||||
Category = VerbCategory.Debug,
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/outfit.svg.192dpi.png")),
|
||||
Act = () => _euiManager.OpenEui(new SetOutfitEui(GetNetEntity(args.Target)), player),
|
||||
Impact = LogImpact.Medium
|
||||
};
|
||||
args.Verbs.Add(verb);
|
||||
args.Verbs.Add(new Verb
|
||||
{
|
||||
Text = Loc.GetString("strip-all-verb-get-data-text"),
|
||||
Category = VerbCategory.Debug,
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/outfit.svg.192dpi.png")),
|
||||
Act = () => _console.RemoteExecuteCommand(player, $"stripall \"{args.Target}\""),
|
||||
Impact = LogImpact.Medium
|
||||
});
|
||||
}
|
||||
|
||||
// set outfit verb
|
||||
if (_groupController.CanCommand(player, "setoutfit"))
|
||||
{
|
||||
Verb verb = new()
|
||||
{
|
||||
Text = Loc.GetString("set-outfit-verb-get-data-text"),
|
||||
Category = VerbCategory.Debug,
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/outfit.svg.192dpi.png")),
|
||||
Act = () => _euiManager.OpenEui(new SetOutfitEui(GetNetEntity(args.Target)), player),
|
||||
Impact = LogImpact.Medium
|
||||
};
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
}
|
||||
|
||||
// In range unoccluded verb
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
|
||||
namespace Content.Server.Atmos.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed partial class AtmosPlaqueComponent : Component
|
||||
{
|
||||
[DataField("plaqueType")] public PlaqueType Type = PlaqueType.Unset;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public PlaqueType TypeVV
|
||||
{
|
||||
get => Type;
|
||||
set
|
||||
{
|
||||
Type = value;
|
||||
IoCManager.Resolve<IEntityManager>().System<AtmosPlaqueSystem>().UpdateSign(Owner, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Shared.Atmos.Visuals;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Atmos.EntitySystems;
|
||||
|
||||
public sealed class AtmosPlaqueSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<AtmosPlaqueComponent, MapInitEvent>(OnPlaqueMapInit);
|
||||
}
|
||||
|
||||
private void OnPlaqueMapInit(EntityUid uid, AtmosPlaqueComponent component, MapInitEvent args)
|
||||
{
|
||||
var rand = _random.Next(100);
|
||||
// Let's not pat ourselves on the back too hard.
|
||||
// 1% chance of zumos
|
||||
if (rand == 0) component.Type = PlaqueType.Zumos;
|
||||
// 9% FEA
|
||||
else if (rand <= 10) component.Type = PlaqueType.Fea;
|
||||
// 45% ZAS
|
||||
else if (rand <= 55) component.Type = PlaqueType.Zas;
|
||||
// 45% LINDA
|
||||
else component.Type = PlaqueType.Linda;
|
||||
|
||||
UpdateSign(uid, component);
|
||||
}
|
||||
|
||||
public void UpdateSign(EntityUid uid, AtmosPlaqueComponent component)
|
||||
{
|
||||
var metaData = MetaData(uid);
|
||||
|
||||
var val = component.Type switch
|
||||
{
|
||||
PlaqueType.Zumos =>
|
||||
Loc.GetString("atmos-plaque-component-desc-zum"),
|
||||
PlaqueType.Fea =>
|
||||
Loc.GetString("atmos-plaque-component-desc-fea"),
|
||||
PlaqueType.Linda =>
|
||||
Loc.GetString("atmos-plaque-component-desc-linda"),
|
||||
PlaqueType.Zas =>
|
||||
Loc.GetString("atmos-plaque-component-desc-zas"),
|
||||
PlaqueType.Unset => Loc.GetString("atmos-plaque-component-desc-unset"),
|
||||
_ => Loc.GetString("atmos-plaque-component-desc-unset"),
|
||||
};
|
||||
|
||||
_metaData.SetEntityDescription(uid, val, metaData);
|
||||
|
||||
var val1 = component.Type switch
|
||||
{
|
||||
PlaqueType.Zumos =>
|
||||
Loc.GetString("atmos-plaque-component-name-zum"),
|
||||
PlaqueType.Fea =>
|
||||
Loc.GetString("atmos-plaque-component-name-fea"),
|
||||
PlaqueType.Linda =>
|
||||
Loc.GetString("atmos-plaque-component-name-linda"),
|
||||
PlaqueType.Zas =>
|
||||
Loc.GetString("atmos-plaque-component-name-zas"),
|
||||
PlaqueType.Unset => Loc.GetString("atmos-plaque-component-name-unset"),
|
||||
_ => Loc.GetString("atmos-plaque-component-name-unset"),
|
||||
};
|
||||
|
||||
_metaData.SetEntityName(uid, val1, metaData);
|
||||
|
||||
if (TryComp<AppearanceComponent>(uid, out var appearance))
|
||||
{
|
||||
var state = component.Type == PlaqueType.Zumos ? "zumosplaque" : "atmosplaque";
|
||||
|
||||
_appearance.SetData(uid, AtmosPlaqueVisuals.State, state, appearance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If you get the ZUM plaque it means your round will be blessed with good engineering luck.
|
||||
public enum PlaqueType : byte
|
||||
{
|
||||
Unset = 0,
|
||||
Zumos,
|
||||
Fea,
|
||||
Linda,
|
||||
Zas
|
||||
}
|
||||
@@ -81,7 +81,10 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
|
||||
|
||||
private void OnTileChanged(ref TileChangedEvent ev)
|
||||
{
|
||||
InvalidateTile(ev.NewTile.GridUid, ev.NewTile.GridIndices);
|
||||
foreach (var change in ev.Changes)
|
||||
{
|
||||
InvalidateTile(ev.Entity.Owner, change.GridIndices);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPrototypesReloaded(PrototypesReloadedEventArgs ev)
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Shuttles.Systems;
|
||||
using Content.Shared.Maps;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics.Events;
|
||||
|
||||
namespace Content.Server.Atmos.EntitySystems;
|
||||
|
||||
@@ -12,40 +11,29 @@ namespace Content.Server.Atmos.EntitySystems;
|
||||
/// </summary>
|
||||
public sealed class AutomaticAtmosSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<TileChangedEvent>(OnTileChanged);
|
||||
SubscribeLocalEvent<MapGridComponent, MassDataChangedEvent>(OnMassDataChanged);
|
||||
}
|
||||
|
||||
private void OnTileChanged(ref TileChangedEvent ev)
|
||||
private void OnMassDataChanged(Entity<MapGridComponent> ent, ref MassDataChangedEvent ev)
|
||||
{
|
||||
// Only if a atmos-holding tile has been added or removed.
|
||||
// Also, these calls are surprisingly slow.
|
||||
// TODO: Make tiledefmanager cache the IsSpace property, and turn this lookup-through-two-interfaces into
|
||||
// TODO: a simple array lookup, as tile IDs are likely contiguous, and there's at most 2^16 possibilities anyway.
|
||||
|
||||
var oldSpace = ev.OldTile.IsSpace(_tileDefinitionManager);
|
||||
var newSpace = ev.NewTile.IsSpace(_tileDefinitionManager);
|
||||
|
||||
if (!(oldSpace && !newSpace ||
|
||||
!oldSpace && newSpace) ||
|
||||
_atmosphereSystem.HasAtmosphere(ev.Entity))
|
||||
return;
|
||||
|
||||
if (!TryComp<PhysicsComponent>(ev.Entity, out var physics))
|
||||
if (_atmosphereSystem.HasAtmosphere(ent))
|
||||
return;
|
||||
|
||||
// We can't actually count how many tiles there are efficiently, so instead estimate with the mass.
|
||||
if (physics.Mass / ShuttleSystem.TileMassMultiplier >= 7.0f)
|
||||
if (ev.NewMass / ShuttleSystem.TileDensityMultiplier >= 7.0f)
|
||||
{
|
||||
AddComp<GridAtmosphereComponent>(ev.Entity);
|
||||
Log.Info($"Giving grid {ev.Entity} GridAtmosphereComponent.");
|
||||
AddComp<GridAtmosphereComponent>(ent);
|
||||
Log.Info($"Giving grid {ent} GridAtmosphereComponent.");
|
||||
}
|
||||
|
||||
// It's not super important to remove it should the grid become too small again.
|
||||
// If explosions ever gain the ability to outright shatter grids, do rethink this.
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,4 +47,10 @@ public sealed partial class AirAlarmComponent : Component
|
||||
/// </summary>
|
||||
[DataField("normalPort", customTypeSerializer: typeof(PrototypeIdSerializer<SourcePortPrototype>))]
|
||||
public string NormalPort = "AirNormal";
|
||||
|
||||
/// <summary>
|
||||
/// Whether the panic wire is cut, forcing the alarm into panic mode.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables]
|
||||
public bool PanicWireCut;
|
||||
}
|
||||
|
||||
@@ -466,11 +466,17 @@ public sealed class AirAlarmSystem : EntitySystem
|
||||
/// <param name="uiOnly">Whether this change is for the UI only, or if it changes the air alarm's operating mode. Defaults to true.</param>
|
||||
public void SetMode(EntityUid uid, string origin, AirAlarmMode mode, bool uiOnly = true, AirAlarmComponent? controller = null)
|
||||
{
|
||||
if (!Resolve(uid, ref controller) || controller.CurrentMode == mode)
|
||||
if (!Resolve(uid, ref controller))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (controller.PanicWireCut)
|
||||
{
|
||||
mode = AirAlarmMode.Panic;
|
||||
}
|
||||
|
||||
|
||||
controller.CurrentMode = mode;
|
||||
|
||||
// setting it to UI only means we don't have
|
||||
@@ -652,6 +658,7 @@ public sealed class AirAlarmSystem : EntitySystem
|
||||
}
|
||||
foreach (var (addr, data) in alarm.ScrubberData)
|
||||
{
|
||||
data.AirAlarmPanicWireCut = alarm.PanicWireCut;
|
||||
dataToSend.Add((addr, data));
|
||||
}
|
||||
foreach (var (addr, data) in alarm.SensorData)
|
||||
@@ -669,7 +676,7 @@ public sealed class AirAlarmSystem : EntitySystem
|
||||
_ui.SetUiState(
|
||||
uid,
|
||||
SharedAirAlarmInterfaceKey.Key,
|
||||
new AirAlarmUIState(devNet.Address, deviceCount, pressure, temperature, dataToSend, alarm.CurrentMode, highestAlarm.Value, alarm.AutoMode));
|
||||
new AirAlarmUIState(devNet.Address, deviceCount, pressure, temperature, dataToSend, alarm.CurrentMode, highestAlarm.Value, alarm.AutoMode, alarm.PanicWireCut));
|
||||
}
|
||||
|
||||
private const float Delay = 8f;
|
||||
|
||||
@@ -30,6 +30,7 @@ public sealed partial class AirAlarmPanicWire : ComponentWireAction<AirAlarmComp
|
||||
|
||||
public override bool Cut(EntityUid user, Wire wire, AirAlarmComponent comp)
|
||||
{
|
||||
comp.PanicWireCut = true;
|
||||
if (EntityManager.TryGetComponent<DeviceNetworkComponent>(wire.Owner, out var devNet))
|
||||
{
|
||||
_airAlarmSystem.SetMode(wire.Owner, devNet.Address, AirAlarmMode.Panic, false);
|
||||
@@ -40,6 +41,7 @@ public sealed partial class AirAlarmPanicWire : ComponentWireAction<AirAlarmComp
|
||||
|
||||
public override bool Mend(EntityUid user, Wire wire, AirAlarmComponent alarm)
|
||||
{
|
||||
alarm.PanicWireCut = false;
|
||||
if (EntityManager.TryGetComponent<DeviceNetworkComponent>(wire.Owner, out var devNet)
|
||||
&& alarm.CurrentMode == AirAlarmMode.Panic)
|
||||
{
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.Binary.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed partial class GasValveComponent : Component
|
||||
{
|
||||
[DataField("open")]
|
||||
public bool Open { get; set; } = true;
|
||||
|
||||
[DataField("inlet")]
|
||||
public string InletName { get; set; } = "inlet";
|
||||
|
||||
[DataField("outlet")]
|
||||
public string OutletName { get; set; } = "outlet";
|
||||
|
||||
[DataField("valveSound")]
|
||||
public SoundSpecifier ValveSound { get; private set; } = new SoundCollectionSpecifier("valveSqueak");
|
||||
}
|
||||
}
|
||||
@@ -1,93 +1,34 @@
|
||||
using Content.Server.Atmos.Piping.Binary.Components;
|
||||
using Content.Server.NodeContainer;
|
||||
using Content.Server.NodeContainer.EntitySystems;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Shared.Atmos.Piping;
|
||||
using Content.Shared.Atmos.Piping.Binary.Components;
|
||||
using Content.Shared.Atmos.Piping.Binary.Systems;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Interaction;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.Binary.EntitySystems
|
||||
namespace Content.Server.Atmos.Piping.Binary.EntitySystems;
|
||||
|
||||
public sealed class GasValveSystem : SharedGasValveSystem
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class GasValveSystem : EntitySystem
|
||||
[Dependency] private readonly SharedAmbientSoundSystem _ambientSoundSystem = default!;
|
||||
[Dependency] private readonly NodeContainerSystem _nodeContainer = default!;
|
||||
|
||||
public override void Set(EntityUid uid, GasValveComponent component, bool value)
|
||||
{
|
||||
[Dependency] private readonly SharedAmbientSoundSystem _ambientSoundSystem = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly NodeContainerSystem _nodeContainer = default!;
|
||||
base.Set(uid, component, value);
|
||||
|
||||
public override void Initialize()
|
||||
if (_nodeContainer.TryGetNodes(uid, component.InletName, component.OutletName, out PipeNode? inlet, out PipeNode? outlet))
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<GasValveComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<GasValveComponent, ActivateInWorldEvent>(OnActivate);
|
||||
SubscribeLocalEvent<GasValveComponent, ExaminedEvent>(OnExamined);
|
||||
}
|
||||
|
||||
private void OnExamined(Entity<GasValveComponent> ent, ref ExaminedEvent args)
|
||||
{
|
||||
var valve = ent.Comp;
|
||||
if (!Comp<TransformComponent>(ent).Anchored || !args.IsInDetailsRange) // Not anchored? Out of range? No status.
|
||||
return;
|
||||
|
||||
if (Loc.TryGetString("gas-valve-system-examined", out var str,
|
||||
("statusColor", valve.Open ? "green" : "orange"),
|
||||
("open", valve.Open)))
|
||||
if (component.Open)
|
||||
{
|
||||
args.PushMarkup(str);
|
||||
inlet.AddAlwaysReachable(outlet);
|
||||
outlet.AddAlwaysReachable(inlet);
|
||||
_ambientSoundSystem.SetAmbience(uid, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnStartup(EntityUid uid, GasValveComponent component, ComponentStartup args)
|
||||
{
|
||||
// We call set in startup so it sets the appearance, node state, etc.
|
||||
Set(uid, component, component.Open);
|
||||
}
|
||||
|
||||
private void OnActivate(EntityUid uid, GasValveComponent component, ActivateInWorldEvent args)
|
||||
{
|
||||
if (args.Handled || !args.Complex)
|
||||
return;
|
||||
|
||||
Toggle(uid, component);
|
||||
_audio.PlayPvs(component.ValveSound, uid, AudioParams.Default.WithVariation(0.25f));
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
public void Set(EntityUid uid, GasValveComponent component, bool value)
|
||||
{
|
||||
component.Open = value;
|
||||
|
||||
if (_nodeContainer.TryGetNodes(uid, component.InletName, component.OutletName, out PipeNode? inlet, out PipeNode? outlet))
|
||||
else
|
||||
{
|
||||
if (TryComp<AppearanceComponent>(uid, out var appearance))
|
||||
{
|
||||
_appearance.SetData(uid, FilterVisuals.Enabled, component.Open, appearance);
|
||||
}
|
||||
if (component.Open)
|
||||
{
|
||||
inlet.AddAlwaysReachable(outlet);
|
||||
outlet.AddAlwaysReachable(inlet);
|
||||
_ambientSoundSystem.SetAmbience(uid, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
inlet.RemoveAlwaysReachable(outlet);
|
||||
outlet.RemoveAlwaysReachable(inlet);
|
||||
_ambientSoundSystem.SetAmbience(uid, false);
|
||||
}
|
||||
inlet.RemoveAlwaysReachable(outlet);
|
||||
outlet.RemoveAlwaysReachable(inlet);
|
||||
_ambientSoundSystem.SetAmbience(uid, false);
|
||||
}
|
||||
}
|
||||
|
||||
public void Toggle(EntityUid uid, GasValveComponent component)
|
||||
{
|
||||
Set(uid, component, !component.Open);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Server.Atmos.Piping.Binary.Components;
|
||||
using Content.Server.DeviceLinking.Systems;
|
||||
using Content.Shared.Atmos.Piping.Binary.Components;
|
||||
using Content.Shared.DeviceLinking.Events;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.Binary.EntitySystems;
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace Content.Server.Atmos.Piping.EntitySystems;
|
||||
public sealed class AtmosPipeAppearanceSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -55,10 +56,11 @@ public sealed class AtmosPipeAppearanceSystem : EntitySystem
|
||||
|
||||
// find the cardinal directions of any connected entities
|
||||
var netConnectedDirections = PipeDirection.None;
|
||||
var tile = grid.TileIndicesFor(xform.Coordinates);
|
||||
var tile = _map.TileIndicesFor((xform.GridUid.Value, grid), xform.Coordinates);
|
||||
foreach (var neighbour in connected)
|
||||
{
|
||||
var otherTile = grid.TileIndicesFor(Transform(neighbour).Coordinates);
|
||||
// TODO z-levels, pipes across grids - we shouldn't assume that the neighboring tile's transform is on the same grid
|
||||
var otherTile = _map.TileIndicesFor((xform.GridUid.Value, grid), Transform(neighbour).Coordinates);
|
||||
|
||||
netConnectedDirections |= (otherTile - tile) switch
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
{
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Enabled { get; set; } = true;
|
||||
public bool Enabled = true;
|
||||
|
||||
/// <summary>
|
||||
/// Target volume to transfer. If <see cref="WideNet"/> is enabled, actual transfer rate will be much higher.
|
||||
@@ -25,15 +25,14 @@ namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
|
||||
private float _transferRate = 50;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("maxTransferRate")]
|
||||
[DataField]
|
||||
public float MaxTransferRate = Atmospherics.MaxTransferRate;
|
||||
|
||||
[DataField("maxPressure")]
|
||||
[DataField]
|
||||
[GuidebookData]
|
||||
public float MaxPressure { get; set; } = GasVolumePumpComponent.DefaultHigherThreshold;
|
||||
public float MaxPressure = GasVolumePumpComponent.DefaultHigherThreshold;
|
||||
|
||||
[DataField("inlet")]
|
||||
public string InletName { get; set; } = "pipe";
|
||||
public string InletName = "pipe";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Guidebook;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed partial class GasThermoMachineComponent : Component
|
||||
{
|
||||
[DataField("inlet")]
|
||||
public string InletName = "pipe";
|
||||
|
||||
/// <summary>
|
||||
/// Current electrical power consumption, in watts. Increasing power increases the ability of the
|
||||
/// thermomachine to heat or cool air.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
[GuidebookData]
|
||||
public float HeatCapacity = 5000;
|
||||
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public float TargetTemperature = Atmospherics.T20C;
|
||||
|
||||
/// <summary>
|
||||
/// Tolerance for temperature setpoint hysteresis.
|
||||
/// </summary>
|
||||
[GuidebookData]
|
||||
[DataField, ViewVariables(VVAccess.ReadOnly)]
|
||||
public float TemperatureTolerance = 2f;
|
||||
|
||||
/// <summary>
|
||||
/// Implements setpoint hysteresis to prevent heater from rapidly cycling on and off at setpoint.
|
||||
/// If true, add Sign(Cp)*TemperatureTolerance to the temperature setpoint.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public bool HysteresisState;
|
||||
|
||||
/// <summary>
|
||||
/// Coefficient of performance. Output power / input power.
|
||||
/// Positive for heaters, negative for freezers.
|
||||
/// </summary>
|
||||
[DataField("coefficientOfPerformance")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float Cp = 0.9f; // output power / input power, positive is heat
|
||||
|
||||
/// <summary>
|
||||
/// Current minimum temperature
|
||||
/// Ignored if heater.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
[GuidebookData]
|
||||
public float MinTemperature = 73.15f;
|
||||
|
||||
/// <summary>
|
||||
/// Current maximum temperature
|
||||
/// Ignored if freezer.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
[GuidebookData]
|
||||
public float MaxTemperature = 593.15f;
|
||||
|
||||
/// <summary>
|
||||
/// Last amount of energy added/removed from the attached pipe network
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public float LastEnergyDelta;
|
||||
|
||||
/// <summary>
|
||||
/// An percentage of the energy change that is leaked into the surrounding environment rather than the inlet pipe.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
[GuidebookData]
|
||||
public float EnergyLeakPercentage;
|
||||
|
||||
/// <summary>
|
||||
/// If true, heat is exclusively exchanged with the local atmosphere instead of the inlet pipe air
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Atmospheric = false;
|
||||
}
|
||||
}
|
||||
@@ -9,11 +9,8 @@ using Content.Server.Power.Components;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Piping.Unary.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Shared.UserInterface;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Atmos.Piping.Unary.Systems;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.DeviceNetwork.Events;
|
||||
using Content.Shared.Examine;
|
||||
@@ -22,36 +19,23 @@ using Content.Shared.DeviceNetwork.Components;
|
||||
namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class GasThermoMachineSystem : EntitySystem
|
||||
public sealed class GasThermoMachineSystem : SharedGasThermoMachineSystem
|
||||
{
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
|
||||
[Dependency] private readonly PowerReceiverSystem _power = default!;
|
||||
[Dependency] private readonly NodeContainerSystem _nodeContainer = default!;
|
||||
[Dependency] private readonly DeviceNetworkSystem _deviceNetwork = default!;
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<GasThermoMachineComponent, AtmosDeviceUpdateEvent>(OnThermoMachineUpdated);
|
||||
SubscribeLocalEvent<GasThermoMachineComponent, ExaminedEvent>(OnExamined);
|
||||
|
||||
// UI events
|
||||
SubscribeLocalEvent<GasThermoMachineComponent, BeforeActivatableUIOpenEvent>(OnBeforeOpened);
|
||||
SubscribeLocalEvent<GasThermoMachineComponent, GasThermomachineToggleMessage>(OnToggleMessage);
|
||||
SubscribeLocalEvent<GasThermoMachineComponent, GasThermomachineChangeTemperatureMessage>(OnChangeTemperature);
|
||||
|
||||
// Device network
|
||||
SubscribeLocalEvent<GasThermoMachineComponent, DeviceNetworkPacketEvent>(OnPacketRecv);
|
||||
}
|
||||
|
||||
private void OnBeforeOpened(Entity<GasThermoMachineComponent> ent, ref BeforeActivatableUIOpenEvent args)
|
||||
{
|
||||
DirtyUI(ent, ent.Comp);
|
||||
}
|
||||
|
||||
private void OnThermoMachineUpdated(EntityUid uid, GasThermoMachineComponent thermoMachine, ref AtmosDeviceUpdateEvent args)
|
||||
{
|
||||
thermoMachine.LastEnergyDelta = 0f;
|
||||
@@ -135,56 +119,6 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsHeater(GasThermoMachineComponent comp)
|
||||
{
|
||||
return comp.Cp >= 0;
|
||||
}
|
||||
|
||||
private void OnToggleMessage(EntityUid uid, GasThermoMachineComponent thermoMachine, GasThermomachineToggleMessage args)
|
||||
{
|
||||
var powerState = _power.TogglePower(uid);
|
||||
_adminLogger.Add(LogType.AtmosPowerChanged, $"{ToPrettyString(args.Actor)} turned {(powerState ? "On" : "Off")} {ToPrettyString(uid)}");
|
||||
DirtyUI(uid, thermoMachine);
|
||||
}
|
||||
|
||||
private void OnChangeTemperature(EntityUid uid, GasThermoMachineComponent thermoMachine, GasThermomachineChangeTemperatureMessage args)
|
||||
{
|
||||
if (IsHeater(thermoMachine))
|
||||
thermoMachine.TargetTemperature = MathF.Min(args.Temperature, thermoMachine.MaxTemperature);
|
||||
else
|
||||
thermoMachine.TargetTemperature = MathF.Max(args.Temperature, thermoMachine.MinTemperature);
|
||||
thermoMachine.TargetTemperature = MathF.Max(thermoMachine.TargetTemperature, Atmospherics.TCMB);
|
||||
_adminLogger.Add(LogType.AtmosTemperatureChanged, $"{ToPrettyString(args.Actor)} set temperature on {ToPrettyString(uid)} to {thermoMachine.TargetTemperature}");
|
||||
DirtyUI(uid, thermoMachine);
|
||||
}
|
||||
|
||||
private void DirtyUI(EntityUid uid, GasThermoMachineComponent? thermoMachine, UserInterfaceComponent? ui=null)
|
||||
{
|
||||
if (!Resolve(uid, ref thermoMachine, ref ui, false))
|
||||
return;
|
||||
|
||||
ApcPowerReceiverComponent? powerReceiver = null;
|
||||
if (!Resolve(uid, ref powerReceiver))
|
||||
return;
|
||||
|
||||
_userInterfaceSystem.SetUiState(uid, ThermomachineUiKey.Key,
|
||||
new GasThermomachineBoundUserInterfaceState(thermoMachine.MinTemperature, thermoMachine.MaxTemperature, thermoMachine.TargetTemperature, !powerReceiver.PowerDisabled, IsHeater(thermoMachine)));
|
||||
}
|
||||
|
||||
private void OnExamined(EntityUid uid, GasThermoMachineComponent thermoMachine, ExaminedEvent args)
|
||||
{
|
||||
if (!args.IsInDetailsRange)
|
||||
return;
|
||||
|
||||
if (Loc.TryGetString("gas-thermomachine-system-examined", out var str,
|
||||
("machineName", !IsHeater(thermoMachine) ? "freezer" : "heater"),
|
||||
("tempColor", !IsHeater(thermoMachine) ? "deepskyblue" : "red"),
|
||||
("temp", Math.Round(thermoMachine.TargetTemperature,2))
|
||||
))
|
||||
|
||||
args.PushMarkup(str);
|
||||
}
|
||||
|
||||
private void OnPacketRecv(EntityUid uid, GasThermoMachineComponent component, DeviceNetworkPacketEvent args)
|
||||
{
|
||||
if (!TryComp(uid, out DeviceNetworkComponent? netConn)
|
||||
|
||||
@@ -5,6 +5,7 @@ using Content.Server.Popups;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Shared.Atmos.Piping.Portable.Components;
|
||||
using Content.Shared.Atmos.Piping.Unary.Components;
|
||||
using Content.Shared.Atmos.Visuals;
|
||||
using Content.Shared.Power;
|
||||
using Content.Shared.UserInterface;
|
||||
@@ -41,6 +42,7 @@ public sealed class SpaceHeaterSystem : EntitySystem
|
||||
{
|
||||
if (!TryComp<GasThermoMachineComponent>(uid, out var thermoMachine))
|
||||
return;
|
||||
|
||||
thermoMachine.Cp = spaceHeater.HeatingCp;
|
||||
thermoMachine.HeatCapacity = spaceHeater.PowerConsumption;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using Content.Server.Actions;
|
||||
using Content.Server.Bed.Components;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Shared.Bed;
|
||||
using Content.Shared.Bed.Components;
|
||||
using Content.Shared.Bed.Sleep;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Buckle.Components;
|
||||
@@ -11,49 +11,30 @@ using Content.Shared.Damage;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Power;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Bed
|
||||
{
|
||||
public sealed class BedSystem : EntitySystem
|
||||
public sealed class BedSystem : SharedBedSystem
|
||||
{
|
||||
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
|
||||
[Dependency] private readonly ActionsSystem _actionsSystem = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
[Dependency] private readonly SleepingSystem _sleepingSystem = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
private EntityQuery<SleepingComponent> _sleepingQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<HealOnBuckleComponent, StrappedEvent>(OnStrapped);
|
||||
SubscribeLocalEvent<HealOnBuckleComponent, UnstrappedEvent>(OnUnstrapped);
|
||||
|
||||
_sleepingQuery = GetEntityQuery<SleepingComponent>();
|
||||
|
||||
SubscribeLocalEvent<StasisBedComponent, StrappedEvent>(OnStasisStrapped);
|
||||
SubscribeLocalEvent<StasisBedComponent, UnstrappedEvent>(OnStasisUnstrapped);
|
||||
SubscribeLocalEvent<StasisBedComponent, PowerChangedEvent>(OnPowerChanged);
|
||||
SubscribeLocalEvent<StasisBedComponent, GotEmaggedEvent>(OnEmagged);
|
||||
}
|
||||
|
||||
private void OnStrapped(Entity<HealOnBuckleComponent> bed, ref StrappedEvent args)
|
||||
{
|
||||
EnsureComp<HealOnBuckleHealingComponent>(bed);
|
||||
bed.Comp.NextHealTime = _timing.CurTime + TimeSpan.FromSeconds(bed.Comp.HealTime);
|
||||
_actionsSystem.AddAction(args.Buckle, ref bed.Comp.SleepAction, SleepingSystem.SleepActionId, bed);
|
||||
|
||||
// Single action entity, cannot strap multiple entities to the same bed.
|
||||
DebugTools.AssertEqual(args.Strap.Comp.BuckledEntities.Count, 1);
|
||||
}
|
||||
|
||||
private void OnUnstrapped(Entity<HealOnBuckleComponent> bed, ref UnstrappedEvent args)
|
||||
{
|
||||
_actionsSystem.RemoveAction(args.Buckle, bed.Comp.SleepAction);
|
||||
_sleepingSystem.TryWaking(args.Buckle.Owner);
|
||||
RemComp<HealOnBuckleHealingComponent>(bed);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
@@ -61,7 +42,7 @@ namespace Content.Server.Bed
|
||||
var query = EntityQueryEnumerator<HealOnBuckleHealingComponent, HealOnBuckleComponent, StrapComponent>();
|
||||
while (query.MoveNext(out var uid, out _, out var bedComponent, out var strapComponent))
|
||||
{
|
||||
if (_timing.CurTime < bedComponent.NextHealTime)
|
||||
if (Timing.CurTime < bedComponent.NextHealTime)
|
||||
continue;
|
||||
|
||||
bedComponent.NextHealTime += TimeSpan.FromSeconds(bedComponent.HealTime);
|
||||
@@ -76,7 +57,7 @@ namespace Content.Server.Bed
|
||||
|
||||
var damage = bedComponent.Damage;
|
||||
|
||||
if (HasComp<SleepingComponent>(healedEntity))
|
||||
if (_sleepingQuery.HasComp(healedEntity))
|
||||
damage *= bedComponent.SleepMultiplier;
|
||||
|
||||
_damageableSystem.TryChangeDamage(healedEntity, damage, true, origin: uid);
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
using Content.Shared.Damage;
|
||||
|
||||
namespace Content.Server.Bed.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed partial class HealOnBuckleComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Damage to apply to entities that are strapped to this entity.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public DamageSpecifier Damage = default!;
|
||||
|
||||
/// <summary>
|
||||
/// How frequently the damage should be applied, in seconds.
|
||||
/// </summary>
|
||||
[DataField(required: false)]
|
||||
public float HealTime = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Damage multiplier that gets applied if the entity is sleeping.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float SleepMultiplier = 3f;
|
||||
|
||||
public TimeSpan NextHealTime = TimeSpan.Zero; //Next heal
|
||||
|
||||
[DataField] public EntityUid? SleepAction;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace Content.Server.Bed.Components
|
||||
{
|
||||
// TODO rename this component
|
||||
[RegisterComponent]
|
||||
public sealed partial class HealOnBuckleHealingComponent : Component
|
||||
{}
|
||||
}
|
||||
@@ -47,6 +47,12 @@ namespace Content.Server.Body.Components
|
||||
[DataField]
|
||||
public EntityWhitelist? SpecialDigestible = null;
|
||||
|
||||
/// <summary>
|
||||
/// Controls whitelist behavior. If true, this stomach can digest <i>only</i> food that passes the whitelist. If false, it can digest normal food <i>and</i> any food that passes the whitelist.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool IsSpecialDigestibleExclusive = true;
|
||||
|
||||
/// <summary>
|
||||
/// Used to track how long each reagent has been in the stomach
|
||||
/// </summary>
|
||||
|
||||
@@ -12,6 +12,7 @@ using Content.Shared.Movement.Systems;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Timing;
|
||||
using System.Numerics;
|
||||
using Content.Shared.Damage.Components;
|
||||
|
||||
namespace Content.Server.Body.Systems;
|
||||
|
||||
@@ -110,6 +111,9 @@ public sealed class BodySystem : SharedBodySystem
|
||||
return new HashSet<EntityUid>();
|
||||
}
|
||||
|
||||
if (HasComp<GodmodeComponent>(bodyId))
|
||||
return new HashSet<EntityUid>();
|
||||
|
||||
var xform = Transform(bodyId);
|
||||
if (xform.MapUid is null)
|
||||
return new HashSet<EntityUid>();
|
||||
|
||||
@@ -173,6 +173,20 @@ public sealed class RespiratorSystem : EntitySystem
|
||||
_atmosSys.Merge(ev.Gas, outGas);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the entity is above their SuffocationThreshold and alive.
|
||||
/// </summary>
|
||||
public bool IsBreathing(Entity<RespiratorComponent?> ent)
|
||||
{
|
||||
if (_mobState.IsIncapacitated(ent))
|
||||
return false;
|
||||
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
return false;
|
||||
|
||||
return (ent.Comp.Saturation > ent.Comp.SuffocationThreshold);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check whether or not an entity can metabolize inhaled air without suffocating or taking damage (i.e., no toxic
|
||||
/// gasses).
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using Content.Shared.Cargo;
|
||||
using Content.Shared.Cargo.Prototypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Server.Cargo.Components;
|
||||
@@ -41,6 +43,12 @@ public sealed partial class StationCargoBountyDatabaseComponent : Component
|
||||
[DataField]
|
||||
public HashSet<string> CheckedBounties = new();
|
||||
|
||||
/// <summary>
|
||||
/// The group that bounties are pulled from.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<CargoBountyGroupPrototype> Group = "StationBounty";
|
||||
|
||||
/// <summary>
|
||||
/// The time at which players will be able to skip the next bounty.
|
||||
/// </summary>
|
||||
|
||||
@@ -31,6 +31,16 @@ public sealed partial class StationCargoOrderDatabaseComponent : Component
|
||||
[ViewVariables]
|
||||
public int NumOrdersCreated;
|
||||
|
||||
/// <summary>
|
||||
/// An all encompassing determiner of what markets can be ordered from.
|
||||
/// Not every console can order from every market, but a console can't order from a market not on this list.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public List<ProtoId<CargoMarketPrototype>> Markets = new()
|
||||
{
|
||||
"market",
|
||||
};
|
||||
|
||||
// TODO: Can probably dump this
|
||||
/// <summary>
|
||||
/// The cargo shuttle assigned to this station.
|
||||
|
||||
@@ -16,6 +16,7 @@ using Content.Shared.Whitelist;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Containers;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -292,6 +293,13 @@ public sealed partial class CargoSystem
|
||||
return IsBountyComplete(container, proto.Entries);
|
||||
}
|
||||
|
||||
public bool IsBountyComplete(EntityUid container, ProtoId<CargoBountyPrototype> prototypeId)
|
||||
{
|
||||
var prototype = _protoMan.Index(prototypeId);
|
||||
|
||||
return IsBountyComplete(container, prototype.Entries);
|
||||
}
|
||||
|
||||
public bool IsBountyComplete(EntityUid container, CargoBountyPrototype prototype)
|
||||
{
|
||||
return IsBountyComplete(container, prototype.Entries);
|
||||
@@ -392,7 +400,9 @@ public sealed partial class CargoSystem
|
||||
return false;
|
||||
|
||||
// todo: consider making the cargo bounties weighted.
|
||||
var allBounties = _protoMan.EnumeratePrototypes<CargoBountyPrototype>().ToList();
|
||||
var allBounties = _protoMan.EnumeratePrototypes<CargoBountyPrototype>()
|
||||
.Where(p => p.Group == component.Group)
|
||||
.ToList();
|
||||
var filteredBounties = new List<CargoBountyPrototype>();
|
||||
foreach (var proto in allBounties)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Server.Cargo.Components;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Shared.Cargo;
|
||||
@@ -372,7 +373,7 @@ namespace Content.Server.Cargo.Systems
|
||||
return;
|
||||
}
|
||||
|
||||
if (!component.AllowedGroups.Contains(product.Group))
|
||||
if (!GetAvailableProducts((uid, component)).Contains(args.CargoProductId))
|
||||
return;
|
||||
|
||||
if (component.SlipPrinter)
|
||||
@@ -421,7 +422,8 @@ namespace Content.Server.Cargo.Systems
|
||||
GetOutstandingOrderCount(orderDatabase, console.Account),
|
||||
orderDatabase.Capacity,
|
||||
GetNetEntity(station.Value),
|
||||
orderDatabase.Orders[console.Account]
|
||||
orderDatabase.Orders[console.Account],
|
||||
GetAvailableProducts((consoleUid, console))
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -617,6 +619,29 @@ namespace Content.Server.Cargo.Systems
|
||||
|
||||
}
|
||||
|
||||
public List<ProtoId<CargoProductPrototype>> GetAvailableProducts(Entity<CargoOrderConsoleComponent> ent)
|
||||
{
|
||||
if (_station.GetOwningStation(ent) is not { } station ||
|
||||
!TryComp<StationCargoOrderDatabaseComponent>(station, out var db))
|
||||
{
|
||||
return new List<ProtoId<CargoProductPrototype>>();
|
||||
}
|
||||
|
||||
var products = new List<ProtoId<CargoProductPrototype>>();
|
||||
|
||||
// Note that a market must be both on the station and on the console to be available.
|
||||
var markets = ent.Comp.AllowedGroups.Intersect(db.Markets).ToList();
|
||||
foreach (var product in _protoMan.EnumeratePrototypes<CargoProductPrototype>())
|
||||
{
|
||||
if (!markets.Contains(product.Group))
|
||||
continue;
|
||||
|
||||
products.Add(product.ID);
|
||||
}
|
||||
|
||||
return products;
|
||||
}
|
||||
|
||||
#region Station
|
||||
|
||||
private bool TryGetOrderDatabase([NotNullWhen(true)] EntityUid? stationUid, [MaybeNullWhen(false)] out StationCargoOrderDatabaseComponent dbComp)
|
||||
|
||||
@@ -131,14 +131,14 @@ public sealed partial class CargoSystem
|
||||
|
||||
#region Station
|
||||
|
||||
private bool SellPallets(EntityUid gridUid, out HashSet<(EntityUid, OverrideSellComponent?, double)> goods)
|
||||
private bool SellPallets(EntityUid gridUid, EntityUid station, out HashSet<(EntityUid, OverrideSellComponent?, double)> goods)
|
||||
{
|
||||
GetPalletGoods(gridUid, out var toSell, out goods);
|
||||
|
||||
if (toSell.Count == 0)
|
||||
return false;
|
||||
|
||||
var ev = new EntitySoldEvent(toSell);
|
||||
var ev = new EntitySoldEvent(toSell, station);
|
||||
RaiseLocalEvent(ref ev);
|
||||
|
||||
foreach (var ent in toSell)
|
||||
@@ -230,7 +230,7 @@ public sealed partial class CargoSystem
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SellPallets(gridUid, out var goods))
|
||||
if (!SellPallets(gridUid, station, out var goods))
|
||||
return;
|
||||
|
||||
var baseDistribution = CreateAccountDistribution((station, bankAccount));
|
||||
@@ -267,4 +267,4 @@ public sealed partial class CargoSystem
|
||||
/// deleted but after the price has been calculated.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly record struct EntitySoldEvent(HashSet<EntityUid> Sold);
|
||||
public readonly record struct EntitySoldEvent(HashSet<EntityUid> Sold, EntityUid Station);
|
||||
|
||||
@@ -38,7 +38,6 @@ public sealed partial class CargoSystem : SharedCargoSystem
|
||||
[Dependency] private readonly PricingSystem _pricing = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly ShuttleConsoleSystem _console = default!;
|
||||
[Dependency] private readonly StackSystem _stack = default!;
|
||||
[Dependency] private readonly StationSystem _station = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Salvage.JobBoard;
|
||||
using Content.Shared.Cargo.Components;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Timing;
|
||||
@@ -13,6 +14,7 @@ public sealed class PriceGunSystem : SharedPriceGunSystem
|
||||
[Dependency] private readonly PricingSystem _pricingSystem = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly CargoSystem _bountySystem = default!;
|
||||
[Dependency] private readonly SalvageJobBoardSystem _salvageJobBoard = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
|
||||
protected override bool GetPriceOrBounty(Entity<PriceGunComponent> entity, EntityUid target, EntityUid user)
|
||||
@@ -24,6 +26,10 @@ public sealed class PriceGunSystem : SharedPriceGunSystem
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("price-gun-bounty-complete"), user, user);
|
||||
}
|
||||
else if (_salvageJobBoard.FulfillsSalvageJob(target, null, out _))
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("price-gun-salvjob-complete"), user, user);
|
||||
}
|
||||
else // Otherwise appraise the price
|
||||
{
|
||||
var price = _pricingSystem.GetPrice(target);
|
||||
|
||||
@@ -25,7 +25,6 @@ namespace Content.Server.Cargo.Systems;
|
||||
/// </summary>
|
||||
public sealed class PricingSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IComponentFactory _factory = default!;
|
||||
[Dependency] private readonly IConsoleHost _consoleHost = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly BodySystem _bodySystem = default!;
|
||||
@@ -278,13 +277,13 @@ public sealed class PricingSystem : EntitySystem
|
||||
{
|
||||
double price = 0;
|
||||
|
||||
if (prototype.Components.ContainsKey(_factory.GetComponentName(typeof(MaterialComponent))) &&
|
||||
prototype.Components.TryGetValue(_factory.GetComponentName(typeof(PhysicalCompositionComponent)), out var composition))
|
||||
if (prototype.Components.ContainsKey(Factory.GetComponentName<MaterialComponent>()) &&
|
||||
prototype.Components.TryGetValue(Factory.GetComponentName<PhysicalCompositionComponent>(), out var composition))
|
||||
{
|
||||
var compositionComp = (PhysicalCompositionComponent) composition.Component;
|
||||
var matPrice = GetMaterialPrice(compositionComp);
|
||||
|
||||
if (prototype.Components.TryGetValue(_factory.GetComponentName(typeof(StackComponent)), out var stackProto))
|
||||
if (prototype.Components.TryGetValue(Factory.GetComponentName<StackComponent>(), out var stackProto))
|
||||
{
|
||||
matPrice *= ((StackComponent) stackProto.Component).Count;
|
||||
}
|
||||
@@ -311,7 +310,7 @@ public sealed class PricingSystem : EntitySystem
|
||||
{
|
||||
var price = 0.0;
|
||||
|
||||
if (prototype.Components.TryGetValue(_factory.GetComponentName(typeof(SolutionContainerManagerComponent)), out var solManager))
|
||||
if (prototype.Components.TryGetValue(Factory.GetComponentName<SolutionContainerManagerComponent>(), out var solManager))
|
||||
{
|
||||
var solComp = (SolutionContainerManagerComponent) solManager.Component;
|
||||
price += GetSolutionPrice(solComp);
|
||||
@@ -338,9 +337,9 @@ public sealed class PricingSystem : EntitySystem
|
||||
{
|
||||
var price = 0.0;
|
||||
|
||||
if (prototype.Components.TryGetValue(_factory.GetComponentName(typeof(StackPriceComponent)), out var stackpriceProto) &&
|
||||
prototype.Components.TryGetValue(_factory.GetComponentName(typeof(StackComponent)), out var stackProto) &&
|
||||
!prototype.Components.ContainsKey(_factory.GetComponentName(typeof(MaterialComponent))))
|
||||
if (prototype.Components.TryGetValue(Factory.GetComponentName<StackPriceComponent>(), out var stackpriceProto) &&
|
||||
prototype.Components.TryGetValue(Factory.GetComponentName<StackComponent>(), out var stackProto) &&
|
||||
!prototype.Components.ContainsKey(Factory.GetComponentName<MaterialComponent>()))
|
||||
{
|
||||
var stackPrice = (StackPriceComponent) stackpriceProto.Component;
|
||||
var stack = (StackComponent) stackProto.Component;
|
||||
@@ -366,7 +365,7 @@ public sealed class PricingSystem : EntitySystem
|
||||
{
|
||||
var price = 0.0;
|
||||
|
||||
if (prototype.Components.TryGetValue(_factory.GetComponentName(typeof(StaticPriceComponent)), out var staticProto))
|
||||
if (prototype.Components.TryGetValue(Factory.GetComponentName<StaticPriceComponent>(), out var staticProto))
|
||||
{
|
||||
var staticPrice = (StaticPriceComponent) staticProto.Component;
|
||||
price += staticPrice.Price;
|
||||
|
||||
@@ -2,7 +2,6 @@ using Content.Shared.Whitelist;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Server.Chemistry.EntitySystems;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.Chemistry.Dispenser;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
@@ -15,47 +14,9 @@ namespace Content.Server.Chemistry.Components
|
||||
[Access(typeof(ReagentDispenserSystem))]
|
||||
public sealed partial class ReagentDispenserComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// String with the pack name that stores the initial fill of the dispenser. The initial
|
||||
/// fill is added to the dispenser on MapInit. Note that we don't use ContainerFill because
|
||||
/// we have to generate the storage slots at MapInit first, then fill them.
|
||||
/// </summary>
|
||||
[DataField("pack", customTypeSerializer:typeof(PrototypeIdSerializer<ReagentDispenserInventoryPrototype>))]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string? PackPrototypeId = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of internal storage slots. Dispenser can't store (or dispense) more than
|
||||
/// this many chemicals (without unloading and reloading).
|
||||
/// </summary>
|
||||
[DataField("numStorageSlots")]
|
||||
public int NumSlots = 25;
|
||||
|
||||
/// <summary>
|
||||
/// For each created storage slot for the reagent containers being dispensed, apply this
|
||||
/// entity whitelist. Makes sure weird containers don't fit in the dispenser and that beakers
|
||||
/// don't accidentally get slotted into the source slots.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityWhitelist? StorageWhitelist;
|
||||
|
||||
[DataField]
|
||||
public ItemSlot BeakerSlot = new();
|
||||
|
||||
/// <summary>
|
||||
/// Prefix for automatically-generated slot name for storage, up to NumSlots.
|
||||
/// </summary>
|
||||
public static string BaseStorageSlotId = "ReagentDispenser-storageSlot";
|
||||
|
||||
/// <summary>
|
||||
/// List of storage slots that were created at MapInit.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public List<string> StorageSlotIds = new List<string>();
|
||||
|
||||
[DataField]
|
||||
public List<ItemSlot> StorageSlots = new List<ItemSlot>();
|
||||
|
||||
[DataField("clickSound"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public SoundSpecifier ClickSound = new SoundPathSpecifier("/Audio/Machines/machine_switch.ogg");
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Chemistry.Components;
|
||||
using Content.Server.Chemistry.Containers.EntitySystems;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.Chemistry.Dispenser;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.Storage.EntitySystems;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Audio;
|
||||
using Robust.Server.GameObjects;
|
||||
@@ -13,6 +14,8 @@ using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Content.Shared.Labels.Components;
|
||||
using Content.Shared.Storage;
|
||||
using Content.Server.Hands.Systems;
|
||||
|
||||
namespace Content.Server.Chemistry.EntitySystems
|
||||
{
|
||||
@@ -30,6 +33,7 @@ namespace Content.Server.Chemistry.EntitySystems
|
||||
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly OpenableSystem _openable = default!;
|
||||
[Dependency] private readonly HandsSystem _handsSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -37,15 +41,16 @@ namespace Content.Server.Chemistry.EntitySystems
|
||||
|
||||
SubscribeLocalEvent<ReagentDispenserComponent, ComponentStartup>(SubscribeUpdateUiState);
|
||||
SubscribeLocalEvent<ReagentDispenserComponent, SolutionContainerChangedEvent>(SubscribeUpdateUiState);
|
||||
SubscribeLocalEvent<ReagentDispenserComponent, EntInsertedIntoContainerMessage>(SubscribeUpdateUiState);
|
||||
SubscribeLocalEvent<ReagentDispenserComponent, EntRemovedFromContainerMessage>(SubscribeUpdateUiState);
|
||||
SubscribeLocalEvent<ReagentDispenserComponent, EntInsertedIntoContainerMessage>(SubscribeUpdateUiState, after: [typeof(SharedStorageSystem)]);
|
||||
SubscribeLocalEvent<ReagentDispenserComponent, EntRemovedFromContainerMessage>(SubscribeUpdateUiState, after: [typeof(SharedStorageSystem)]);
|
||||
SubscribeLocalEvent<ReagentDispenserComponent, BoundUIOpenedEvent>(SubscribeUpdateUiState);
|
||||
|
||||
SubscribeLocalEvent<ReagentDispenserComponent, ReagentDispenserSetDispenseAmountMessage>(OnSetDispenseAmountMessage);
|
||||
SubscribeLocalEvent<ReagentDispenserComponent, ReagentDispenserDispenseReagentMessage>(OnDispenseReagentMessage);
|
||||
SubscribeLocalEvent<ReagentDispenserComponent, ReagentDispenserEjectContainerMessage>(OnEjectReagentMessage);
|
||||
SubscribeLocalEvent<ReagentDispenserComponent, ReagentDispenserClearContainerSolutionMessage>(OnClearContainerSolutionMessage);
|
||||
|
||||
SubscribeLocalEvent<ReagentDispenserComponent, MapInitEvent>(OnMapInit, before: new []{typeof(ItemSlotsSystem)});
|
||||
SubscribeLocalEvent<ReagentDispenserComponent, MapInitEvent>(OnMapInit, before: new[] { typeof(ItemSlotsSystem) });
|
||||
}
|
||||
|
||||
private void SubscribeUpdateUiState<T>(Entity<ReagentDispenserComponent> ent, ref T ev)
|
||||
@@ -82,32 +87,31 @@ namespace Content.Server.Chemistry.EntitySystems
|
||||
|
||||
private List<ReagentInventoryItem> GetInventory(Entity<ReagentDispenserComponent> reagentDispenser)
|
||||
{
|
||||
if (!TryComp<StorageComponent>(reagentDispenser.Owner, out var storage))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var inventory = new List<ReagentInventoryItem>();
|
||||
|
||||
for (var i = 0; i < reagentDispenser.Comp.NumSlots; i++)
|
||||
foreach (var (storedContainer, storageLocation) in storage.StoredItems)
|
||||
{
|
||||
var storageSlotId = ReagentDispenserComponent.BaseStorageSlotId + i;
|
||||
var storedContainer = _itemSlotsSystem.GetItemOrNull(reagentDispenser.Owner, storageSlotId);
|
||||
|
||||
// Set label from manually-applied label, or metadata if unavailable
|
||||
string reagentLabel;
|
||||
if (TryComp<LabelComponent>(storedContainer, out var label) && !string.IsNullOrEmpty(label.CurrentLabel))
|
||||
reagentLabel = label.CurrentLabel;
|
||||
else if (storedContainer != null)
|
||||
reagentLabel = Name(storedContainer.Value);
|
||||
else
|
||||
continue;
|
||||
reagentLabel = Name(storedContainer);
|
||||
|
||||
// Get volume remaining and color of solution
|
||||
FixedPoint2 quantity = 0f;
|
||||
var reagentColor = Color.White;
|
||||
if (storedContainer != null && _solutionContainerSystem.TryGetDrainableSolution(storedContainer.Value, out _, out var sol))
|
||||
if (_solutionContainerSystem.TryGetDrainableSolution(storedContainer, out _, out var sol))
|
||||
{
|
||||
quantity = sol.Volume;
|
||||
reagentColor = sol.GetColor(_prototypeManager);
|
||||
}
|
||||
|
||||
inventory.Add(new ReagentInventoryItem(storageSlotId, reagentLabel, quantity, reagentColor));
|
||||
inventory.Add(new ReagentInventoryItem(storageLocation, reagentLabel, quantity, reagentColor));
|
||||
}
|
||||
|
||||
return inventory;
|
||||
@@ -122,22 +126,28 @@ namespace Content.Server.Chemistry.EntitySystems
|
||||
|
||||
private void OnDispenseReagentMessage(Entity<ReagentDispenserComponent> reagentDispenser, ref ReagentDispenserDispenseReagentMessage message)
|
||||
{
|
||||
if (!TryComp<StorageComponent>(reagentDispenser.Owner, out var storage))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure that the reagent is something this reagent dispenser can dispense.
|
||||
var storedContainer = _itemSlotsSystem.GetItemOrNull(reagentDispenser, message.SlotId);
|
||||
if (storedContainer == null)
|
||||
var storageLocation = message.StorageLocation;
|
||||
var storedContainer = storage.StoredItems.FirstOrDefault(kvp => kvp.Value == storageLocation).Key;
|
||||
if (storedContainer == EntityUid.Invalid)
|
||||
return;
|
||||
|
||||
var outputContainer = _itemSlotsSystem.GetItemOrNull(reagentDispenser, SharedReagentDispenser.OutputSlotName);
|
||||
if (outputContainer is not { Valid: true } || !_solutionContainerSystem.TryGetFitsInDispenser(outputContainer.Value, out var solution, out _))
|
||||
return;
|
||||
|
||||
if (_solutionContainerSystem.TryGetDrainableSolution(storedContainer.Value, out var src, out _) &&
|
||||
if (_solutionContainerSystem.TryGetDrainableSolution(storedContainer, out var src, out _) &&
|
||||
_solutionContainerSystem.TryGetRefillableSolution(outputContainer.Value, out var dst, out _))
|
||||
{
|
||||
// force open container, if applicable, to avoid confusing people on why it doesn't dispense
|
||||
_openable.SetOpen(storedContainer.Value, true);
|
||||
_openable.SetOpen(storedContainer, true);
|
||||
_solutionTransferSystem.Transfer(reagentDispenser,
|
||||
storedContainer.Value, src.Value,
|
||||
storedContainer, src.Value,
|
||||
outputContainer.Value, dst.Value,
|
||||
(int)reagentDispenser.Comp.DispenseAmount);
|
||||
}
|
||||
@@ -146,6 +156,21 @@ namespace Content.Server.Chemistry.EntitySystems
|
||||
ClickSound(reagentDispenser);
|
||||
}
|
||||
|
||||
private void OnEjectReagentMessage(Entity<ReagentDispenserComponent> reagentDispenser, ref ReagentDispenserEjectContainerMessage message)
|
||||
{
|
||||
if (!TryComp<StorageComponent>(reagentDispenser.Owner, out var storage))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var storageLocation = message.StorageLocation;
|
||||
var storedContainer = storage.StoredItems.FirstOrDefault(kvp => kvp.Value == storageLocation).Key;
|
||||
if (storedContainer == EntityUid.Invalid)
|
||||
return;
|
||||
|
||||
_handsSystem.TryPickupAnyHand(message.Actor, storedContainer);
|
||||
}
|
||||
|
||||
private void OnClearContainerSolutionMessage(Entity<ReagentDispenserComponent> reagentDispenser, ref ReagentDispenserClearContainerSolutionMessage message)
|
||||
{
|
||||
var outputContainer = _itemSlotsSystem.GetItemOrNull(reagentDispenser, SharedReagentDispenser.OutputSlotName);
|
||||
@@ -163,39 +188,11 @@ namespace Content.Server.Chemistry.EntitySystems
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Automatically generate storage slots for all NumSlots, and fill them with their initial chemicals.
|
||||
/// The actual spawning of entities happens in ItemSlotsSystem's MapInit.
|
||||
/// Initializes the beaker slot
|
||||
/// </summary>
|
||||
private void OnMapInit(EntityUid uid, ReagentDispenserComponent component, MapInitEvent args)
|
||||
private void OnMapInit(Entity<ReagentDispenserComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
// Get list of pre-loaded containers
|
||||
List<string> preLoad = new List<string>();
|
||||
if (component.PackPrototypeId is not null
|
||||
&& _prototypeManager.TryIndex(component.PackPrototypeId, out ReagentDispenserInventoryPrototype? packPrototype))
|
||||
{
|
||||
preLoad.AddRange(packPrototype.Inventory);
|
||||
}
|
||||
|
||||
// Populate storage slots with base storage slot whitelist
|
||||
for (var i = 0; i < component.NumSlots; i++)
|
||||
{
|
||||
var storageSlotId = ReagentDispenserComponent.BaseStorageSlotId + i;
|
||||
ItemSlot storageComponent = new();
|
||||
storageComponent.Whitelist = component.StorageWhitelist;
|
||||
storageComponent.Swap = false;
|
||||
storageComponent.EjectOnBreak = true;
|
||||
|
||||
// Check corresponding index in pre-loaded container (if exists) and set starting item
|
||||
if (i < preLoad.Count)
|
||||
storageComponent.StartingItem = preLoad[i];
|
||||
|
||||
component.StorageSlotIds.Add(storageSlotId);
|
||||
component.StorageSlots.Add(storageComponent);
|
||||
component.StorageSlots[i].Name = "Storage Slot " + (i+1);
|
||||
_itemSlotsSystem.AddItemSlot(uid, component.StorageSlotIds[i], component.StorageSlots[i]);
|
||||
}
|
||||
|
||||
_itemSlotsSystem.AddItemSlot(uid, SharedReagentDispenser.OutputSlotName, component.BeakerSlot);
|
||||
_itemSlotsSystem.AddItemSlot(ent.Owner, SharedReagentDispenser.OutputSlotName, ent.Comp.BeakerSlot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ namespace Content.Server.Cloning;
|
||||
/// </summary>
|
||||
public sealed partial class CloningSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IComponentFactory _componentFactory = default!;
|
||||
[Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!;
|
||||
[Dependency] private readonly InventorySystem _inventory = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
@@ -106,7 +105,7 @@ public sealed partial class CloningSystem : EntitySystem
|
||||
|
||||
foreach (var componentName in componentsToCopy)
|
||||
{
|
||||
if (!_componentFactory.TryGetRegistration(componentName, out var componentRegistration))
|
||||
if (!Factory.TryGetRegistration(componentName, out var componentRegistration))
|
||||
{
|
||||
Log.Error($"Tried to use invalid component registration for cloning: {componentName}");
|
||||
continue;
|
||||
@@ -122,7 +121,7 @@ public sealed partial class CloningSystem : EntitySystem
|
||||
|
||||
foreach (var componentName in componentsToEvent)
|
||||
{
|
||||
if (!_componentFactory.TryGetRegistration(componentName, out var componentRegistration))
|
||||
if (!Factory.TryGetRegistration(componentName, out var componentRegistration))
|
||||
{
|
||||
Log.Error($"Tried to use invalid component registration for cloning: {componentName}");
|
||||
continue;
|
||||
|
||||
@@ -10,7 +10,6 @@ namespace Content.Server.Clothing.Systems;
|
||||
public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly IComponentFactory _factory = default!;
|
||||
[Dependency] private readonly IdentitySystem _identity = default!;
|
||||
|
||||
public override void Initialize()
|
||||
@@ -68,7 +67,7 @@ public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem
|
||||
|
||||
private void UpdateIdentityBlocker(EntityUid uid, ChameleonClothingComponent component, EntityPrototype proto)
|
||||
{
|
||||
if (proto.HasComponent<IdentityBlockerComponent>(_factory))
|
||||
if (proto.HasComponent<IdentityBlockerComponent>(Factory))
|
||||
EnsureComp<IdentityBlockerComponent>(uid);
|
||||
else
|
||||
RemComp<IdentityBlockerComponent>(uid);
|
||||
|
||||
@@ -67,5 +67,12 @@ namespace Content.Server.Communications
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/Announcements/announce.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// Hides the sender identity (If they even have one).
|
||||
/// In practise this removes the "Sent by ScugMcWawa (Slugcat Captain)" at the bottom of the announcement.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool AnnounceSentBy = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,7 +259,9 @@ namespace Content.Server.Communications
|
||||
Loc.TryGetString(comp.Title, out var title);
|
||||
title ??= comp.Title;
|
||||
|
||||
msg += "\n" + Loc.GetString("comms-console-announcement-sent-by") + " " + author;
|
||||
if (comp.AnnounceSentBy)
|
||||
msg += "\n" + Loc.GetString("comms-console-announcement-sent-by") + " " + author;
|
||||
|
||||
if (comp.Global)
|
||||
{
|
||||
_chatSystem.DispatchGlobalAnnouncement(msg, title, announcementSound: comp.Sound, colorOverride: comp.Color);
|
||||
|
||||
@@ -25,6 +25,7 @@ public sealed class TileWallsCommand : IConsoleCommand
|
||||
|
||||
[ValidatePrototypeId<TagPrototype>]
|
||||
public const string WallTag = "Wall";
|
||||
public const string DiagonalTag = "Diagonal";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
@@ -85,6 +86,11 @@ public sealed class TileWallsCommand : IConsoleCommand
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tagSystem.HasTag(child, DiagonalTag))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var childTransform = _entManager.GetComponent<TransformComponent>(child);
|
||||
|
||||
if (!childTransform.Anchored)
|
||||
|
||||
@@ -25,7 +25,6 @@ namespace Content.Server.Construction
|
||||
{
|
||||
public sealed partial class ConstructionSystem
|
||||
{
|
||||
[Dependency] private readonly IComponentFactory _factory = default!;
|
||||
[Dependency] private readonly InventorySystem _inventorySystem = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
||||
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
|
||||
@@ -211,7 +210,7 @@ namespace Content.Server.Construction
|
||||
case ArbitraryInsertConstructionGraphStep arbitraryStep:
|
||||
foreach (var entity in new HashSet<EntityUid>(EnumerateNearby(user)))
|
||||
{
|
||||
if (!arbitraryStep.EntityValid(entity, EntityManager, _factory))
|
||||
if (!arbitraryStep.EntityValid(entity, EntityManager, Factory))
|
||||
continue;
|
||||
|
||||
if (used.Contains(entity))
|
||||
@@ -541,7 +540,7 @@ namespace Content.Server.Construction
|
||||
switch (step)
|
||||
{
|
||||
case EntityInsertConstructionGraphStep entityInsert:
|
||||
if (entityInsert.EntityValid(holding, EntityManager, _factory))
|
||||
if (entityInsert.EntityValid(holding, EntityManager, Factory))
|
||||
valid = true;
|
||||
break;
|
||||
case ToolConstructionGraphStep _:
|
||||
|
||||
@@ -8,8 +8,10 @@ using Content.Shared.Construction.EntitySystems;
|
||||
using Content.Shared.Construction.Steps;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Components;
|
||||
using Content.Shared.Prying.Systems;
|
||||
using Content.Shared.Radio.EntitySystems;
|
||||
using Content.Shared.Stacks;
|
||||
using Content.Shared.Temperature;
|
||||
using Content.Shared.Tools.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
@@ -271,7 +273,11 @@ namespace Content.Server.Construction
|
||||
|
||||
// Since many things inherit this step, we delegate the "is this entity valid?" logic to them.
|
||||
// While this is very OOP and I find it icky, I must admit that it simplifies the code here a lot.
|
||||
if(!insertStep.EntityValid(insert, EntityManager, _factory))
|
||||
if(!insertStep.EntityValid(insert, EntityManager, Factory))
|
||||
return HandleResult.False;
|
||||
|
||||
// Unremovable items can't be inserted, unless they are a lingering stack
|
||||
if(HasComp<UnremoveableComponent>(insert) && (!TryComp<StackComponent>(insert, out var comp) || !comp.Lingering))
|
||||
return HandleResult.False;
|
||||
|
||||
// If we're only testing whether this step would be handled by the given event, then we're done.
|
||||
|
||||
@@ -13,7 +13,6 @@ namespace Content.Server.Construction;
|
||||
|
||||
public sealed class MachineFrameSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IComponentFactory _factory = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
[Dependency] private readonly TagSystem _tag = default!;
|
||||
[Dependency] private readonly StackSystem _stack = default!;
|
||||
@@ -75,7 +74,7 @@ public sealed class MachineFrameSystem : EntitySystem
|
||||
if (component.ComponentProgress[compName] >= info.Amount)
|
||||
continue;
|
||||
|
||||
var registration = _factory.GetRegistration(compName);
|
||||
var registration = Factory.GetRegistration(compName);
|
||||
|
||||
if (!HasComp(args.Used, registration.Type))
|
||||
continue;
|
||||
@@ -294,7 +293,7 @@ public sealed class MachineFrameSystem : EntitySystem
|
||||
// I have many regrets.
|
||||
foreach (var (compName, _) in component.ComponentRequirements)
|
||||
{
|
||||
var registration = _factory.GetRegistration(compName);
|
||||
var registration = Factory.GetRegistration(compName);
|
||||
|
||||
if (!HasComp(part, registration.Type))
|
||||
continue;
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Threading.Tasks;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Construction.Prototypes;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Humanoid.Markings;
|
||||
@@ -65,7 +66,11 @@ namespace Content.Server.Database
|
||||
profiles[profile.Slot] = ConvertProfiles(profile);
|
||||
}
|
||||
|
||||
return new PlayerPreferences(profiles, prefs.SelectedCharacterSlot, Color.FromHex(prefs.AdminOOCColor));
|
||||
var constructionFavorites = new List<ProtoId<ConstructionPrototype>>(prefs.ConstructionFavorites.Count);
|
||||
foreach (var favorite in prefs.ConstructionFavorites)
|
||||
constructionFavorites.Add(new ProtoId<ConstructionPrototype>(favorite));
|
||||
|
||||
return new PlayerPreferences(profiles, prefs.SelectedCharacterSlot, Color.FromHex(prefs.AdminOOCColor), constructionFavorites);
|
||||
}
|
||||
|
||||
public async Task SaveSelectedCharacterIndexAsync(NetUserId userId, int index)
|
||||
@@ -143,7 +148,8 @@ namespace Content.Server.Database
|
||||
{
|
||||
UserId = userId.UserId,
|
||||
SelectedCharacterSlot = 0,
|
||||
AdminOOCColor = Color.Red.ToHex()
|
||||
AdminOOCColor = Color.Red.ToHex(),
|
||||
ConstructionFavorites = [],
|
||||
};
|
||||
|
||||
prefs.Profiles.Add(profile);
|
||||
@@ -152,7 +158,7 @@ namespace Content.Server.Database
|
||||
|
||||
await db.DbContext.SaveChangesAsync();
|
||||
|
||||
return new PlayerPreferences(new[] {new KeyValuePair<int, ICharacterProfile>(0, defaultProfile)}, 0, Color.FromHex(prefs.AdminOOCColor));
|
||||
return new PlayerPreferences(new[] { new KeyValuePair<int, ICharacterProfile>(0, defaultProfile) }, 0, Color.FromHex(prefs.AdminOOCColor), []);
|
||||
}
|
||||
|
||||
public async Task DeleteSlotAndSetSelectedIndex(NetUserId userId, int deleteSlot, int newSlot)
|
||||
@@ -178,6 +184,19 @@ namespace Content.Server.Database
|
||||
|
||||
}
|
||||
|
||||
public async Task SaveConstructionFavoritesAsync(NetUserId userId, List<ProtoId<ConstructionPrototype>> constructionFavorites)
|
||||
{
|
||||
await using var db = await GetDb();
|
||||
var prefs = await db.DbContext.Preference.SingleAsync(p => p.UserId == userId.UserId);
|
||||
|
||||
var favorites = new List<string>(constructionFavorites.Count);
|
||||
foreach (var favorite in constructionFavorites)
|
||||
favorites.Add(favorite.Id);
|
||||
prefs.ConstructionFavorites = favorites;
|
||||
|
||||
await db.DbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private static async Task SetSelectedCharacterSlotAsync(NetUserId userId, int newSlot, ServerDbContext db)
|
||||
{
|
||||
var prefs = await db.Preference.SingleAsync(p => p.UserId == userId.UserId);
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Threading.Tasks;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Construction.Prototypes;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Roles;
|
||||
@@ -42,6 +43,8 @@ namespace Content.Server.Database
|
||||
|
||||
Task SaveAdminOOCColorAsync(NetUserId userId, Color color);
|
||||
|
||||
Task SaveConstructionFavoritesAsync(NetUserId userId, List<ProtoId<ConstructionPrototype>> constructionFavorites);
|
||||
|
||||
// Single method for two operations for transaction.
|
||||
Task DeleteSlotAndSetSelectedIndex(NetUserId userId, int deleteSlot, int newSlot);
|
||||
Task<PlayerPreferences?> GetPlayerPreferencesAsync(NetUserId userId, CancellationToken cancel);
|
||||
@@ -489,6 +492,12 @@ namespace Content.Server.Database
|
||||
return RunDbCommand(() => _db.SaveAdminOOCColorAsync(userId, color));
|
||||
}
|
||||
|
||||
public Task SaveConstructionFavoritesAsync(NetUserId userId, List<ProtoId<ConstructionPrototype>> constructionFavorites)
|
||||
{
|
||||
DbWriteOpsMetric.Inc();
|
||||
return RunDbCommand(() => _db.SaveConstructionFavoritesAsync(userId, constructionFavorites));
|
||||
}
|
||||
|
||||
public Task<PlayerPreferences?> GetPlayerPreferencesAsync(NetUserId userId, CancellationToken cancel)
|
||||
{
|
||||
DbReadOpsMetric.Inc();
|
||||
|
||||
@@ -160,38 +160,45 @@ namespace Content.Server.Decals
|
||||
|
||||
private void OnTileChanged(ref TileChangedEvent args)
|
||||
{
|
||||
if (!args.NewTile.IsSpace(_tileDefMan))
|
||||
return;
|
||||
|
||||
if (!TryComp(args.Entity, out DecalGridComponent? grid))
|
||||
return;
|
||||
|
||||
var indices = GetChunkIndices(args.NewTile.GridIndices);
|
||||
var toDelete = new HashSet<uint>();
|
||||
if (!grid.ChunkCollection.ChunkCollection.TryGetValue(indices, out var chunk))
|
||||
return;
|
||||
|
||||
foreach (var (uid, decal) in chunk.Decals)
|
||||
foreach (var change in args.Changes)
|
||||
{
|
||||
if (new Vector2((int) Math.Floor(decal.Coordinates.X), (int) Math.Floor(decal.Coordinates.Y)) ==
|
||||
args.NewTile.GridIndices)
|
||||
if (!change.NewTile.IsSpace(_tileDefMan))
|
||||
continue;
|
||||
|
||||
var indices = GetChunkIndices(change.GridIndices);
|
||||
|
||||
if (!grid.ChunkCollection.ChunkCollection.TryGetValue(indices, out var chunk))
|
||||
continue;
|
||||
|
||||
toDelete.Clear();
|
||||
|
||||
foreach (var (uid, decal) in chunk.Decals)
|
||||
{
|
||||
toDelete.Add(uid);
|
||||
if (new Vector2((int)Math.Floor(decal.Coordinates.X), (int)Math.Floor(decal.Coordinates.Y)) ==
|
||||
change.GridIndices)
|
||||
{
|
||||
toDelete.Add(uid);
|
||||
}
|
||||
}
|
||||
|
||||
if (toDelete.Count == 0)
|
||||
continue;
|
||||
|
||||
foreach (var decalId in toDelete)
|
||||
{
|
||||
grid.DecalIndex.Remove(decalId);
|
||||
chunk.Decals.Remove(decalId);
|
||||
}
|
||||
|
||||
DirtyChunk(args.Entity, indices, chunk);
|
||||
if (chunk.Decals.Count == 0)
|
||||
grid.ChunkCollection.ChunkCollection.Remove(indices);
|
||||
}
|
||||
|
||||
if (toDelete.Count == 0)
|
||||
return;
|
||||
|
||||
foreach (var decalId in toDelete)
|
||||
{
|
||||
grid.DecalIndex.Remove(decalId);
|
||||
chunk.Decals.Remove(decalId);
|
||||
}
|
||||
|
||||
DirtyChunk(args.Entity, indices, chunk);
|
||||
if (chunk.Decals.Count == 0)
|
||||
grid.ChunkCollection.ChunkCollection.Remove(indices);
|
||||
}
|
||||
|
||||
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
|
||||
|
||||
@@ -43,7 +43,6 @@ namespace Content.Server.Destructible
|
||||
[Dependency] public readonly PuddleSystem PuddleSystem = default!;
|
||||
[Dependency] public readonly SharedContainerSystem ContainerSystem = default!;
|
||||
[Dependency] public readonly IPrototypeManager PrototypeManager = default!;
|
||||
[Dependency] public readonly IComponentFactory ComponentFactory = default!;
|
||||
[Dependency] public readonly IAdminLogManager _adminLogger = default!;
|
||||
|
||||
public override void Initialize()
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace Content.Server.Destructible.Thresholds.Behaviors
|
||||
|
||||
if (toSpawn == 0) continue;
|
||||
|
||||
if (EntityPrototypeHelpers.HasComponent<StackComponent>(entityId, system.PrototypeManager, system.ComponentFactory))
|
||||
if (EntityPrototypeHelpers.HasComponent<StackComponent>(entityId, system.PrototypeManager, system.EntityManager.ComponentFactory))
|
||||
{
|
||||
var spawned = system.EntityManager.SpawnEntity(entityId, xform.Coordinates.Offset(system.Random.NextVector2(-Offset, Offset)));
|
||||
system.StackSystem.SetCount(spawned, toSpawn);
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace Content.Server.Destructible.Thresholds.Behaviors
|
||||
if (count == 0)
|
||||
continue;
|
||||
|
||||
if (EntityPrototypeHelpers.HasComponent<StackComponent>(entityId, system.PrototypeManager, system.ComponentFactory))
|
||||
if (EntityPrototypeHelpers.HasComponent<StackComponent>(entityId, system.PrototypeManager, system.EntityManager.ComponentFactory))
|
||||
{
|
||||
var spawned = SpawnInContainer
|
||||
? system.EntityManager.SpawnNextToOrDrop(entityId, owner)
|
||||
|
||||
@@ -49,7 +49,7 @@ public sealed partial class LogicGateComponent : Component
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public ProtoId<SourcePortPrototype> OutputPort = "Output";
|
||||
|
||||
// Initial state
|
||||
// Initial state, used to not spam invoke ports
|
||||
[DataField]
|
||||
public SignalState StateA = SignalState.Low;
|
||||
|
||||
@@ -59,13 +59,3 @@ public sealed partial class LogicGateComponent : Component
|
||||
[DataField]
|
||||
public bool LastOutput;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Last state of a signal port, used to not spam invoking ports.
|
||||
/// </summary>
|
||||
public enum SignalState : byte
|
||||
{
|
||||
Momentary, // Instantaneous pulse high, compatibility behavior
|
||||
Low,
|
||||
High
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Server.DeviceLinking.Components;
|
||||
using Content.Server.DeviceNetwork;
|
||||
using Content.Server.Doors.Systems;
|
||||
using Content.Shared.DeviceLinking;
|
||||
using Content.Shared.DeviceLinking.Events;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.Doors.Components;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Server.DeviceLinking.Components;
|
||||
using Content.Shared.DeviceLinking;
|
||||
using Content.Shared.DeviceLinking.Events;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
|
||||
|
||||
14
Content.Server/Disposal/Tube/DisposalSignallerComponent.cs
Normal file
14
Content.Server/Disposal/Tube/DisposalSignallerComponent.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Content.Shared.DeviceLinking;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Disposal.Tube;
|
||||
|
||||
/// <summary>
|
||||
/// Disposal pipes with this component can be linked with devices to send a signal every time an item goes through the pipe
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(DisposalSignallerSystem))]
|
||||
public sealed partial class DisposalSignallerComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public ProtoId<SourcePortPrototype> Port = "ItemDetected";
|
||||
}
|
||||
25
Content.Server/Disposal/Tube/DisposalSignallerSystem.cs
Normal file
25
Content.Server/Disposal/Tube/DisposalSignallerSystem.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using Content.Server.DeviceLinking.Systems;
|
||||
|
||||
namespace Content.Server.Disposal.Tube;
|
||||
|
||||
public sealed class DisposalSignallerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly DeviceLinkSystem _link = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<DisposalSignallerComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<DisposalSignallerComponent, GetDisposalsNextDirectionEvent>(OnGetNextDirection, after: new[] { typeof(DisposalTubeSystem) });
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, DisposalSignallerComponent comp, ComponentInit args)
|
||||
{
|
||||
_link.EnsureSourcePorts(uid, comp.Port);
|
||||
}
|
||||
|
||||
private void OnGetNextDirection(EntityUid uid, DisposalSignallerComponent comp, ref GetDisposalsNextDirectionEvent args)
|
||||
{
|
||||
_link.InvokePort(uid, comp.Port);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Shared.EntityEffects;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.EntityEffects.EffectConditions;
|
||||
|
||||
/// <summary>
|
||||
/// Condition for if the entity is successfully breathing.
|
||||
/// </summary>
|
||||
public sealed partial class Breathing : EntityEffectCondition
|
||||
{
|
||||
/// <summary>
|
||||
/// If true, the entity must not have trouble breathing to pass.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool IsBreathing = true;
|
||||
|
||||
public override bool Condition(EntityEffectBaseArgs args)
|
||||
{
|
||||
if (!args.EntityManager.TryGetComponent(args.TargetEntity, out RespiratorComponent? respiratorComp))
|
||||
return !IsBreathing; // They do not breathe.
|
||||
|
||||
var breathingState = args.EntityManager.System<RespiratorSystem>().IsBreathing((args.TargetEntity, respiratorComp));
|
||||
return IsBreathing == breathingState;
|
||||
}
|
||||
|
||||
public override string GuidebookExplanation(IPrototypeManager prototype)
|
||||
{
|
||||
return Loc.GetString("reagent-effect-condition-guidebook-breathing",
|
||||
("isBreathing", IsBreathing));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.EntityEffects;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.EntityEffects.EffectConditions;
|
||||
|
||||
/// <summary>
|
||||
/// Condition for if the entity is or isn't wearing internals.
|
||||
/// </summary>
|
||||
public sealed partial class Internals : EntityEffectCondition
|
||||
{
|
||||
/// <summary>
|
||||
/// To pass, the entity's internals must have this same state.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool UsingInternals = true;
|
||||
|
||||
public override bool Condition(EntityEffectBaseArgs args)
|
||||
{
|
||||
if (!args.EntityManager.TryGetComponent(args.TargetEntity, out InternalsComponent? internalsComp))
|
||||
return !UsingInternals; // They have no internals to wear.
|
||||
|
||||
var internalsState = internalsComp.GasTankEntity == null;
|
||||
return UsingInternals == internalsState;
|
||||
}
|
||||
|
||||
public override string GuidebookExplanation(IPrototypeManager prototype)
|
||||
{
|
||||
return Loc.GetString("reagent-effect-condition-guidebook-internals", ("usingInternals", UsingInternals));
|
||||
}
|
||||
}
|
||||
@@ -8,34 +8,49 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototy
|
||||
namespace Content.Server.EntityEffects.Effects;
|
||||
|
||||
/// <summary>
|
||||
/// Tries to force someone to emote (scream, laugh, etc). Still respects whitelists/blacklists and other limits of the specified emote unless forced.
|
||||
/// Tries to force someone to emote (scream, laugh, etc). Still respects whitelists/blacklists and other limits unless specially forced.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed partial class Emote : EntityEffect
|
||||
{
|
||||
[DataField("emote", customTypeSerializer: typeof(PrototypeIdSerializer<EmotePrototype>))]
|
||||
public string? EmoteId;
|
||||
/// <summary>
|
||||
/// The emote the entity will preform.
|
||||
/// </summary>
|
||||
[DataField("emote", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<EmotePrototype>))]
|
||||
public string EmoteId;
|
||||
|
||||
/// <summary>
|
||||
/// If the emote should be recorded in chat.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool ShowInChat;
|
||||
|
||||
/// <summary>
|
||||
/// If the forced emote will be listed in the guidebook.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool ShowInGuidebook;
|
||||
|
||||
/// <summary>
|
||||
/// If true, the entity will preform the emote even if they normally can't.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool Force = false;
|
||||
|
||||
// JUSTIFICATION: Emoting is flavor, so same reason popup messages are not in here.
|
||||
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
|
||||
=> null;
|
||||
{
|
||||
if (!ShowInGuidebook)
|
||||
return null; // JUSTIFICATION: Emoting is mostly flavor, so same reason popup messages are not in here.
|
||||
|
||||
return Loc.GetString("reagent-effect-guidebook-emote", ("chance", Probability), ("emote", EmoteId));
|
||||
}
|
||||
|
||||
public override void Effect(EntityEffectBaseArgs args)
|
||||
{
|
||||
if (EmoteId == null)
|
||||
return;
|
||||
|
||||
var chatSys = args.EntityManager.System<ChatSystem>();
|
||||
if (ShowInChat)
|
||||
chatSys.TryEmoteWithChat(args.TargetEntity, EmoteId, ChatTransmitRange.GhostRangeLimit, forceEmote: Force);
|
||||
else
|
||||
chatSys.TryEmoteWithoutChat(args.TargetEntity, EmoteId);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
141
Content.Server/EntityEffects/Effects/EvenHealthChange.cs
Normal file
141
Content.Server/EntityEffects/Effects/EvenHealthChange.cs
Normal file
@@ -0,0 +1,141 @@
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.EntityEffects;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Localizations;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.EntityEffects.Effects;
|
||||
|
||||
/// <summary>
|
||||
/// Version of <see cref="HealthChange"/> that distributes the healing to groups
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed partial class EvenHealthChange : EntityEffect
|
||||
{
|
||||
/// <summary>
|
||||
/// Damage to heal, collected into entire damage groups.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public Dictionary<ProtoId<DamageGroupPrototype>, FixedPoint2> Damage = new();
|
||||
|
||||
/// <summary>
|
||||
/// Should this effect scale the damage by the amount of chemical in the solution?
|
||||
/// Useful for touch reactions, like styptic powder or acid.
|
||||
/// Only usable if the EntityEffectBaseArgs is an EntityEffectReagentArgs.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool ScaleByQuantity;
|
||||
|
||||
/// <summary>
|
||||
/// Should this effect ignore damage modifiers?
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool IgnoreResistances = true;
|
||||
|
||||
protected override string ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
|
||||
{
|
||||
var damages = new List<string>();
|
||||
var heals = false;
|
||||
var deals = false;
|
||||
|
||||
var damagableSystem = entSys.GetEntitySystem<DamageableSystem>();
|
||||
var universalReagentDamageModifier = damagableSystem.UniversalReagentDamageModifier;
|
||||
var universalReagentHealModifier = damagableSystem.UniversalReagentHealModifier;
|
||||
|
||||
foreach (var (group, amount) in Damage)
|
||||
{
|
||||
var groupProto = prototype.Index(group);
|
||||
|
||||
var sign = FixedPoint2.Sign(amount);
|
||||
var mod = 1f;
|
||||
|
||||
if (sign < 0)
|
||||
{
|
||||
heals = true;
|
||||
mod = universalReagentHealModifier;
|
||||
}
|
||||
else if (sign > 0)
|
||||
{
|
||||
deals = true;
|
||||
mod = universalReagentDamageModifier;
|
||||
}
|
||||
|
||||
damages.Add(
|
||||
Loc.GetString("health-change-display",
|
||||
("kind", groupProto.LocalizedName),
|
||||
("amount", MathF.Abs(amount.Float() * mod)),
|
||||
("deltasign", sign)
|
||||
));
|
||||
}
|
||||
|
||||
var healsordeals = heals ? (deals ? "both" : "heals") : (deals ? "deals" : "none");
|
||||
return Loc.GetString("reagent-effect-guidebook-even-health-change",
|
||||
("chance", Probability),
|
||||
("changes", ContentLocalizationManager.FormatList(damages)),
|
||||
("healsordeals", healsordeals));
|
||||
}
|
||||
|
||||
public override void Effect(EntityEffectBaseArgs args)
|
||||
{
|
||||
if (!args.EntityManager.TryGetComponent<DamageableComponent>(args.TargetEntity, out var damageable))
|
||||
return;
|
||||
|
||||
var protoMan = IoCManager.Resolve<IPrototypeManager>();
|
||||
|
||||
var scale = FixedPoint2.New(1);
|
||||
|
||||
if (args is EntityEffectReagentArgs reagentArgs)
|
||||
{
|
||||
scale = ScaleByQuantity ? reagentArgs.Quantity * reagentArgs.Scale : reagentArgs.Scale;
|
||||
}
|
||||
|
||||
var damagableSystem = args.EntityManager.System<DamageableSystem>();
|
||||
var universalReagentDamageModifier = damagableSystem.UniversalReagentDamageModifier;
|
||||
var universalReagentHealModifier = damagableSystem.UniversalReagentHealModifier;
|
||||
|
||||
var dspec = new DamageSpecifier();
|
||||
|
||||
foreach (var (group, amount) in Damage)
|
||||
{
|
||||
var groupProto = protoMan.Index(group);
|
||||
var groupDamage = new Dictionary<string, FixedPoint2>();
|
||||
foreach (var damageId in groupProto.DamageTypes)
|
||||
{
|
||||
var damageAmount = damageable.Damage.DamageDict.GetValueOrDefault(damageId);
|
||||
if (damageAmount != FixedPoint2.Zero)
|
||||
groupDamage.Add(damageId, damageAmount);
|
||||
}
|
||||
|
||||
var sum = groupDamage.Values.Sum();
|
||||
foreach (var (damageId, damageAmount) in groupDamage)
|
||||
{
|
||||
var existing = dspec.DamageDict.GetOrNew(damageId);
|
||||
dspec.DamageDict[damageId] = existing + damageAmount / sum * amount;
|
||||
}
|
||||
}
|
||||
|
||||
if (universalReagentDamageModifier != 1 || universalReagentHealModifier != 1)
|
||||
{
|
||||
foreach (var (type, val) in dspec.DamageDict)
|
||||
{
|
||||
if (val < 0f)
|
||||
{
|
||||
dspec.DamageDict[type] = val * universalReagentHealModifier;
|
||||
}
|
||||
if (val > 0f)
|
||||
{
|
||||
dspec.DamageDict[type] = val * universalReagentDamageModifier;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
damagableSystem.TryChangeDamage(
|
||||
args.TargetEntity,
|
||||
dspec * scale,
|
||||
IgnoreResistances,
|
||||
interruptsDoAfters: false);
|
||||
}
|
||||
}
|
||||
@@ -64,48 +64,6 @@ namespace Content.Server.EntityEffects.Effects
|
||||
|
||||
damageSpec = entSys.GetEntitySystem<DamageableSystem>().ApplyUniversalAllModifiers(damageSpec);
|
||||
|
||||
foreach (var group in prototype.EnumeratePrototypes<DamageGroupPrototype>())
|
||||
{
|
||||
if (!damageSpec.TryGetDamageInGroup(group, out var amount))
|
||||
continue;
|
||||
|
||||
var relevantTypes = damageSpec.DamageDict
|
||||
.Where(x => x.Value != FixedPoint2.Zero && group.DamageTypes.Contains(x.Key)).ToList();
|
||||
|
||||
if (relevantTypes.Count != group.DamageTypes.Count)
|
||||
continue;
|
||||
|
||||
var sum = FixedPoint2.Zero;
|
||||
foreach (var type in group.DamageTypes)
|
||||
{
|
||||
sum += damageSpec.DamageDict.GetValueOrDefault(type);
|
||||
}
|
||||
|
||||
// if the total sum of all the types equal the damage amount,
|
||||
// assume that they're evenly distributed.
|
||||
if (sum != amount)
|
||||
continue;
|
||||
|
||||
var sign = FixedPoint2.Sign(amount);
|
||||
|
||||
if (sign < 0)
|
||||
heals = true;
|
||||
if (sign > 0)
|
||||
deals = true;
|
||||
|
||||
damages.Add(
|
||||
Loc.GetString("health-change-display",
|
||||
("kind", group.LocalizedName),
|
||||
("amount", MathF.Abs(amount.Float())),
|
||||
("deltasign", sign)
|
||||
));
|
||||
|
||||
foreach (var type in group.DamageTypes)
|
||||
{
|
||||
damageSpec.DamageDict.Remove(type);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (kind, amount) in damageSpec.DamageDict)
|
||||
{
|
||||
var sign = FixedPoint2.Sign(amount);
|
||||
|
||||
@@ -233,65 +233,66 @@ public sealed partial class ExplosionSystem
|
||||
/// </summary>
|
||||
private void OnTileChanged(ref TileChangedEvent ev)
|
||||
{
|
||||
// only need to update the grid-edge map if a tile was added or removed from the grid.
|
||||
if (!ev.NewTile.Tile.IsEmpty && !ev.OldTile.IsEmpty)
|
||||
return;
|
||||
|
||||
if (!TryComp(ev.Entity, out MapGridComponent? grid))
|
||||
return;
|
||||
|
||||
var tileRef = ev.NewTile;
|
||||
|
||||
if (!_gridEdges.TryGetValue(tileRef.GridUid, out var edges))
|
||||
foreach (var change in ev.Changes)
|
||||
{
|
||||
edges = new();
|
||||
_gridEdges[tileRef.GridUid] = edges;
|
||||
}
|
||||
// only need to update the grid-edge map if a tile was added or removed from the grid.
|
||||
if (!change.NewTile.IsEmpty && !change.OldTile.IsEmpty)
|
||||
continue;
|
||||
|
||||
if (tileRef.Tile.IsEmpty)
|
||||
{
|
||||
// if the tile is empty, it cannot itself be an edge tile.
|
||||
edges.Remove(tileRef.GridIndices);
|
||||
if (!_gridEdges.TryGetValue(ev.Entity, out var edges))
|
||||
{
|
||||
edges = new();
|
||||
_gridEdges[ev.Entity] = edges;
|
||||
}
|
||||
|
||||
// add any valid neighbours to the list of edge-tiles
|
||||
if (change.NewTile.IsEmpty)
|
||||
{
|
||||
// if the tile is empty, it cannot itself be an edge tile.
|
||||
edges.Remove(change.GridIndices);
|
||||
|
||||
// add any valid neighbours to the list of edge-tiles
|
||||
for (var i = 0; i < NeighbourVectors.Length; i++)
|
||||
{
|
||||
var neighbourIndex = change.GridIndices + NeighbourVectors[i];
|
||||
|
||||
if (_mapSystem.TryGetTileRef(ev.Entity, grid, neighbourIndex, out var neighbourTile) && !neighbourTile.Tile.IsEmpty)
|
||||
{
|
||||
var oppositeDirection = (NeighborFlag)(1 << ((i + 4) % 8));
|
||||
edges[neighbourIndex] = edges.GetValueOrDefault(neighbourIndex) | oppositeDirection;
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// the tile is not empty space, but was previously. So update directly adjacent neighbours, which may no longer
|
||||
// be edge tiles.
|
||||
for (var i = 0; i < NeighbourVectors.Length; i++)
|
||||
{
|
||||
var neighbourIndex = tileRef.GridIndices + NeighbourVectors[i];
|
||||
var neighbourIndex = change.GridIndices + NeighbourVectors[i];
|
||||
|
||||
if (_mapSystem.TryGetTileRef(ev.Entity, grid, neighbourIndex, out var neighbourTile) && !neighbourTile.Tile.IsEmpty)
|
||||
if (edges.TryGetValue(neighbourIndex, out var neighborSpaceDir))
|
||||
{
|
||||
var oppositeDirection = (NeighborFlag) (1 << ((i + 4) % 8));
|
||||
edges[neighbourIndex] = edges.GetValueOrDefault(neighbourIndex) | oppositeDirection;
|
||||
var oppositeDirection = (NeighborFlag)(1 << ((i + 4) % 8));
|
||||
neighborSpaceDir &= ~oppositeDirection;
|
||||
if (neighborSpaceDir == NeighborFlag.Invalid)
|
||||
{
|
||||
// no longer an edge tile
|
||||
edges.Remove(neighbourIndex);
|
||||
continue;
|
||||
}
|
||||
|
||||
edges[neighbourIndex] = neighborSpaceDir;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
// finally check if the new tile is itself an edge tile
|
||||
if (IsEdge(grid, change.GridIndices, out var spaceDir))
|
||||
edges.Add(change.GridIndices, spaceDir);
|
||||
}
|
||||
|
||||
// the tile is not empty space, but was previously. So update directly adjacent neighbours, which may no longer
|
||||
// be edge tiles.
|
||||
for (var i = 0; i < NeighbourVectors.Length; i++)
|
||||
{
|
||||
var neighbourIndex = tileRef.GridIndices + NeighbourVectors[i];
|
||||
|
||||
if (edges.TryGetValue(neighbourIndex, out var neighborSpaceDir))
|
||||
{
|
||||
var oppositeDirection = (NeighborFlag) (1 << ((i + 4) % 8));
|
||||
neighborSpaceDir &= ~oppositeDirection;
|
||||
if (neighborSpaceDir == NeighborFlag.Invalid)
|
||||
{
|
||||
// no longer an edge tile
|
||||
edges.Remove(neighbourIndex);
|
||||
continue;
|
||||
}
|
||||
|
||||
edges[neighbourIndex] = neighborSpaceDir;
|
||||
}
|
||||
}
|
||||
|
||||
// finally check if the new tile is itself an edge tile
|
||||
if (IsEdge(grid, tileRef.GridIndices, out var spaceDir))
|
||||
edges.Add(tileRef.GridIndices, spaceDir);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Shared.Explosion.Components.OnTrigger;
|
||||
using Content.Shared.Explosion.EntitySystems;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Explosion.EntitySystems;
|
||||
|
||||
/// <summary>
|
||||
/// Releases a gas mixture to the atmosphere when triggered.
|
||||
/// Can also release gas over a set timespan to prevent trolling people
|
||||
/// with the instant-wall-of-pressure-inator.
|
||||
/// </summary>
|
||||
public sealed partial class ReleaseGasOnTriggerSystem : SharedReleaseGasOnTriggerSystem
|
||||
{
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ReleaseGasOnTriggerComponent, TriggerEvent>(OnTrigger);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shrimply sets the component to active when triggered, allowing it to release over time.
|
||||
/// </summary>
|
||||
private void OnTrigger(Entity<ReleaseGasOnTriggerComponent> ent, ref TriggerEvent args)
|
||||
{
|
||||
ent.Comp.Active = true;
|
||||
ent.Comp.NextReleaseTime = _timing.CurTime;
|
||||
ent.Comp.StartingTotalMoles = ent.Comp.Air.TotalMoles;
|
||||
UpdateAppearance(ent.Owner, true);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var curTime = _timing.CurTime;
|
||||
var query = EntityQueryEnumerator<ReleaseGasOnTriggerComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var comp))
|
||||
{
|
||||
if (!comp.Active || comp.NextReleaseTime > curTime)
|
||||
continue;
|
||||
|
||||
var giverGasMix = comp.Air.Remove(comp.StartingTotalMoles * comp.RemoveFraction);
|
||||
var environment = _atmosphereSystem.GetContainingMixture(uid, false, true);
|
||||
|
||||
if (environment == null)
|
||||
{
|
||||
UpdateAppearance(uid, false);
|
||||
RemCompDeferred<ReleaseGasOnTriggerComponent>(uid);
|
||||
continue;
|
||||
}
|
||||
|
||||
_atmosphereSystem.Merge(environment, giverGasMix);
|
||||
comp.NextReleaseTime += comp.ReleaseInterval;
|
||||
|
||||
if (comp.PressureLimit != 0 && environment.Pressure >= comp.PressureLimit ||
|
||||
comp.Air.TotalMoles <= 0)
|
||||
{
|
||||
UpdateAppearance(uid, false);
|
||||
RemCompDeferred<ReleaseGasOnTriggerComponent>(uid);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateAppearance(Entity<AppearanceComponent?> entity, bool state)
|
||||
{
|
||||
if (!Resolve(entity, ref entity.Comp, false))
|
||||
return;
|
||||
|
||||
_appearance.SetData(entity, ReleaseGasOnTriggerVisuals.Key, state);
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ namespace Content.Server.Explosion.EntitySystems;
|
||||
|
||||
public sealed class TwoStageTriggerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IComponentFactory _factory = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly ISerializationManager _serializationManager = default!;
|
||||
[Dependency] private readonly TriggerSystem _triggerSystem = default!;
|
||||
@@ -30,7 +29,7 @@ public sealed class TwoStageTriggerSystem : EntitySystem
|
||||
{
|
||||
foreach (var (name, entry) in component.SecondStageComponents)
|
||||
{
|
||||
var comp = (Component)_factory.GetComponent(name);
|
||||
var comp = (Component) Factory.GetComponent(name);
|
||||
var temp = (object)comp;
|
||||
|
||||
if (EntityManager.TryGetComponent(uid, entry.Component.GetType(), out var c))
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
using Content.Server.Antag;
|
||||
using Content.Server.Dragon;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Roles;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.CharacterInfo;
|
||||
using Content.Shared.Localizations;
|
||||
using Robust.Server.GameObjects;
|
||||
|
||||
@@ -12,16 +16,37 @@ public sealed class DragonRuleSystem : GameRuleSystem<DragonRuleComponent>
|
||||
[Dependency] private readonly TransformSystem _transform = default!;
|
||||
[Dependency] private readonly AntagSelectionSystem _antag = default!;
|
||||
[Dependency] private readonly StationSystem _station = default!;
|
||||
[Dependency] private readonly RoleSystem _roleSystem = default!;
|
||||
[Dependency] private readonly MindSystem _mind = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<DragonRuleComponent, AfterAntagEntitySelectedEvent>(AfterAntagEntitySelected);
|
||||
SubscribeLocalEvent<DragonRoleComponent, GetBriefingEvent>(UpdateBriefing);
|
||||
}
|
||||
|
||||
private void UpdateBriefing(Entity<DragonRoleComponent> entity, ref GetBriefingEvent args)
|
||||
{
|
||||
var ent = args.Mind.Comp.OwnedEntity;
|
||||
|
||||
if(ent is null)
|
||||
return;
|
||||
|
||||
args.Append(MakeBriefing(ent.Value));
|
||||
}
|
||||
|
||||
private void AfterAntagEntitySelected(Entity<DragonRuleComponent> ent, ref AfterAntagEntitySelectedEvent args)
|
||||
{
|
||||
if (!_mind.TryGetMind(args.EntityUid, out var mindId, out var mind))
|
||||
return;
|
||||
|
||||
_roleSystem.MindHasRole<DragonRoleComponent>(mindId, out var dragonRole);
|
||||
|
||||
if(dragonRole is null)
|
||||
return;
|
||||
|
||||
_antag.SendBriefing(args.EntityUid, MakeBriefing(args.EntityUid), null, null);
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
|
||||
[ValidatePrototypeId<TagPrototype>]
|
||||
private const string NukeOpsUplinkTagPrototype = "NukeOpsUplink";
|
||||
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -104,7 +105,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
|
||||
|
||||
args.AddLine(Loc.GetString("nukeops-list-start"));
|
||||
|
||||
var antags =_antag.GetAntagIdentifiers(uid);
|
||||
var antags = _antag.GetAntagIdentifiers(uid);
|
||||
|
||||
foreach (var (_, sessionData, name) in antags)
|
||||
{
|
||||
@@ -122,7 +123,9 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
|
||||
if (ev.OwningStation == GetOutpost(uid))
|
||||
{
|
||||
nukeops.WinConditions.Add(WinCondition.NukeExplodedOnNukieOutpost);
|
||||
SetWinType((uid, nukeops), WinType.CrewMajor);
|
||||
SetWinType((uid, nukeops), WinType.CrewMajor, GameTicker.IsGameRuleActive("Nukeops")); // End the round ONLY if the actual gamemode is NukeOps.
|
||||
if (!GameTicker.IsGameRuleActive("Nukeops")) // End the rule if the LoneOp shuttle got nuked, because that particular LoneOp clearly failed, and should not be considered a Syndie victory even if a future LoneOp wins.
|
||||
GameTicker.EndGameRule(uid);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -152,7 +155,27 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
|
||||
nukeops.WinConditions.Add(WinCondition.NukeExplodedOnIncorrectLocation);
|
||||
}
|
||||
|
||||
_roundEndSystem.EndRound();
|
||||
if (GameTicker.IsGameRuleActive("Nukeops")) // If it's Nukeops then end the round on any detonation
|
||||
{
|
||||
_roundEndSystem.EndRound();
|
||||
}
|
||||
else
|
||||
{ // It's a LoneOp. Only end the round if the station was destroyed
|
||||
var handled = false;
|
||||
foreach (var cond in nukeops.WinConditions)
|
||||
{
|
||||
if (cond.ToString().ToLower() == "NukeExplodedOnCorrectStation") // If this is true, then the nuke destroyed the station! It's likely everyone is very dead so keeping the round going is pointless.
|
||||
{
|
||||
_roundEndSystem.EndRound(); // end the round!
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!handled) // The round didn't end, so end the rule so it doesn't get overridden by future LoneOps.
|
||||
{
|
||||
GameTicker.EndGameRule(uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -411,10 +434,9 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
|
||||
{
|
||||
var nukeops = ent.Comp;
|
||||
|
||||
if (nukeops.RoundEndBehavior == RoundEndBehavior.Nothing || nukeops.WinType == WinType.CrewMajor || nukeops.WinType == WinType.OpsMajor)
|
||||
if (nukeops.WinType == WinType.CrewMajor || nukeops.WinType == WinType.OpsMajor) // Skip this if the round's victor has already been decided.
|
||||
return;
|
||||
|
||||
|
||||
// If there are any nuclear bombs that are active, immediately return. We're not over yet.
|
||||
foreach (var nuke in EntityQuery<NukeComponent>())
|
||||
{
|
||||
@@ -462,11 +484,16 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
|
||||
: WinCondition.AllNukiesDead);
|
||||
|
||||
SetWinType(ent, WinType.CrewMajor, false);
|
||||
|
||||
if (nukeops.RoundEndBehavior == RoundEndBehavior.Nothing) // It's still worth checking if operatives have all died, even if the round-end behaviour is nothing.
|
||||
return; // Shouldn't actually try to end the round in the case of nothing though.
|
||||
|
||||
_roundEndSystem.DoRoundEndBehavior(nukeops.RoundEndBehavior,
|
||||
nukeops.EvacShuttleTime,
|
||||
nukeops.RoundEndTextSender,
|
||||
nukeops.RoundEndTextShuttleCall,
|
||||
nukeops.RoundEndTextAnnouncement);
|
||||
nukeops.EvacShuttleTime,
|
||||
nukeops.RoundEndTextSender,
|
||||
nukeops.RoundEndTextShuttleCall,
|
||||
nukeops.RoundEndTextAnnouncement);
|
||||
|
||||
|
||||
// prevent it called multiple times
|
||||
nukeops.RoundEndBehavior = RoundEndBehavior.Nothing;
|
||||
|
||||
@@ -234,7 +234,7 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
|
||||
continue;
|
||||
|
||||
// remove their antag role
|
||||
_role.MindTryRemoveRole<RevolutionaryRoleComponent>(mindId);
|
||||
_role.MindRemoveRole<RevolutionaryRoleComponent>(mindId);
|
||||
|
||||
// make it very obvious to the rev they've been deconverted since
|
||||
// they may not see the popup due to antag and/or new player tunnel vision
|
||||
|
||||
@@ -21,14 +21,13 @@ public sealed class SecretRuleSystem : GameRuleSystem<SecretRuleComponent>
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly IComponentFactory _compFact = default!;
|
||||
|
||||
private string _ruleCompName = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_ruleCompName = _compFact.GetComponentName(typeof(GameRuleComponent));
|
||||
_ruleCompName = Factory.GetComponentName<GameRuleComponent>();
|
||||
}
|
||||
|
||||
protected override void Added(EntityUid uid, SecretRuleComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Content.Shared.EntityList;
|
||||
using Content.Shared.EntityTable.EntitySelectors;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Gatherable.Components;
|
||||
|
||||
@@ -25,11 +24,13 @@ public sealed partial class GatherableComponent : Component
|
||||
/// - Tag1
|
||||
/// - Tag2
|
||||
/// loot:
|
||||
/// Tag1: LootTableID1
|
||||
/// Tag2: LootTableID2
|
||||
/// Tag1: !type:NestedSelector
|
||||
/// tableId: LootTableID1
|
||||
/// Tag2: !type:NestedSelector
|
||||
/// tableId: LootTableID2
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public Dictionary<string, ProtoId<EntityLootTablePrototype>>? Loot = new();
|
||||
public Dictionary<string, EntityTableSelector>? Loot = new();
|
||||
|
||||
/// <summary>
|
||||
/// Random shift of the appearing entity during gathering
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
using Content.Server.Destructible;
|
||||
using Content.Server.Gatherable.Components;
|
||||
using Content.Shared.EntityTable;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Tag;
|
||||
using Content.Shared.Weapons.Melee.Events;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Gatherable;
|
||||
|
||||
public sealed partial class GatherableSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly DestructibleSystem _destructible = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly TagSystem _tagSystem = default!;
|
||||
[Dependency] private readonly TransformSystem _transform = default!;
|
||||
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
|
||||
[Dependency] private readonly EntityTableSystem _entityTable = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -76,8 +76,7 @@ public sealed partial class GatherableSystem : EntitySystem
|
||||
if (gatherer != null && !_tagSystem.HasTag(gatherer.Value, tag))
|
||||
continue;
|
||||
}
|
||||
var getLoot = _proto.Index(table);
|
||||
var spawnLoot = getLoot.GetSpawns(_random);
|
||||
var spawnLoot = _entityTable.GetSpawns(table);
|
||||
foreach (var loot in spawnLoot)
|
||||
{
|
||||
var spawnPos = pos.Offset(_random.NextVector2(component.GatherOffset));
|
||||
|
||||
@@ -33,7 +33,6 @@ namespace Content.Server.Hands.Systems
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly StackSystem _stackSystem = default!;
|
||||
[Dependency] private readonly VirtualItemSystem _virtualItemSystem = default!;
|
||||
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
||||
[Dependency] private readonly PullingSystem _pullingSystem = default!;
|
||||
@@ -54,9 +53,6 @@ namespace Content.Server.Hands.Systems
|
||||
|
||||
SubscribeLocalEvent<HandsComponent, DisarmedEvent>(OnDisarmed, before: new[] {typeof(StunSystem), typeof(SharedStaminaSystem)});
|
||||
|
||||
SubscribeLocalEvent<HandsComponent, PullStartedMessage>(HandlePullStarted);
|
||||
SubscribeLocalEvent<HandsComponent, PullStoppedMessage>(HandlePullStopped);
|
||||
|
||||
SubscribeLocalEvent<HandsComponent, BodyPartAddedEvent>(HandleBodyPartAdded);
|
||||
SubscribeLocalEvent<HandsComponent, BodyPartRemovedEvent>(HandleBodyPartRemoved);
|
||||
|
||||
@@ -142,45 +138,6 @@ namespace Content.Server.Hands.Systems
|
||||
RemoveHand(uid, args.Slot);
|
||||
}
|
||||
|
||||
#region pulling
|
||||
|
||||
private void HandlePullStarted(EntityUid uid, HandsComponent component, PullStartedMessage args)
|
||||
{
|
||||
if (args.PullerUid != uid)
|
||||
return;
|
||||
|
||||
if (TryComp<PullerComponent>(args.PullerUid, out var pullerComp) && !pullerComp.NeedsHands)
|
||||
return;
|
||||
|
||||
if (!_virtualItemSystem.TrySpawnVirtualItemInHand(args.PulledUid, uid))
|
||||
{
|
||||
DebugTools.Assert("Unable to find available hand when starting pulling??");
|
||||
}
|
||||
}
|
||||
|
||||
private void HandlePullStopped(EntityUid uid, HandsComponent component, PullStoppedMessage args)
|
||||
{
|
||||
if (args.PullerUid != uid)
|
||||
return;
|
||||
|
||||
// Try find hand that is doing this pull.
|
||||
// and clear it.
|
||||
foreach (var hand in component.Hands.Values)
|
||||
{
|
||||
if (hand.HeldEntity == null
|
||||
|| !TryComp(hand.HeldEntity, out VirtualItemComponent? virtualItem)
|
||||
|| virtualItem.BlockingEntity != args.PulledUid)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
TryDrop(args.PullerUid, hand, handsComp: component);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region interactions
|
||||
|
||||
private bool HandleThrowItem(ICommonSession? playerSession, EntityCoordinates coordinates, EntityUid entity)
|
||||
|
||||
@@ -20,7 +20,6 @@ public sealed class RandomGiftSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AudioSystem _audio = default!;
|
||||
[Dependency] private readonly HandsSystem _hands = default!;
|
||||
[Dependency] private readonly IComponentFactory _componentFactory = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
@@ -93,9 +92,9 @@ public sealed class RandomGiftSystem : EntitySystem
|
||||
{
|
||||
_possibleGiftsSafe.Clear();
|
||||
_possibleGiftsUnsafe.Clear();
|
||||
var itemCompName = _componentFactory.GetComponentName(typeof(ItemComponent));
|
||||
var mapGridCompName = _componentFactory.GetComponentName(typeof(MapGridComponent));
|
||||
var physicsCompName = _componentFactory.GetComponentName(typeof(PhysicsComponent));
|
||||
var itemCompName = Factory.GetComponentName<ItemComponent>();
|
||||
var mapGridCompName = Factory.GetComponentName<MapGridComponent>();
|
||||
var physicsCompName = Factory.GetComponentName<PhysicsComponent>();
|
||||
|
||||
if (!_prototype.TryIndex<EntityCategoryPrototype>("ForkFiltered", out var indexedFilter))
|
||||
return;
|
||||
|
||||
@@ -2,6 +2,7 @@ using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Shared.IgnitionSource;
|
||||
|
||||
namespace Content.Server.IgnitionSource;
|
||||
|
||||
public sealed partial class IgnitionSourceSystem : SharedIgnitionSourceSystem
|
||||
{
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Server.Destructible;
|
||||
using Content.Server.Examine;
|
||||
using Content.Server.Polymorph.Components;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Body.Components;
|
||||
@@ -24,6 +26,7 @@ public sealed class ImmovableRodSystem : EntitySystem
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageable = default!;
|
||||
[Dependency] private readonly DestructibleSystem _destructible = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
|
||||
@@ -127,7 +130,7 @@ public sealed class ImmovableRodSystem : EntitySystem
|
||||
return;
|
||||
}
|
||||
|
||||
QueueDel(ent);
|
||||
_destructible.DestroyEntity(ent);
|
||||
}
|
||||
|
||||
private void OnExamined(EntityUid uid, ImmovableRodComponent component, ExaminedEvent args)
|
||||
|
||||
@@ -547,6 +547,10 @@ namespace Content.Server.Kitchen.EntitySystems
|
||||
var ev = new BeingMicrowavedEvent(uid, user);
|
||||
RaiseLocalEvent(item, ev);
|
||||
|
||||
// TODO MICROWAVE SPARKS & EFFECTS
|
||||
// Various microwaveable entities should probably spawn a spark, play a sound, and generate a pop=up.
|
||||
// This should probably be handled by the microwave system, with fields in BeingMicrowavedEvent.
|
||||
|
||||
if (ev.Handled)
|
||||
{
|
||||
UpdateUserInterfaceState(uid, component);
|
||||
|
||||
@@ -20,6 +20,7 @@ using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Timing;
|
||||
using System.Linq;
|
||||
using Content.Server.Construction.Completions;
|
||||
using Content.Server.Jittering;
|
||||
using Content.Shared.Jittering;
|
||||
using Content.Shared.Power;
|
||||
@@ -38,6 +39,7 @@ namespace Content.Server.Kitchen.EntitySystems
|
||||
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||
[Dependency] private readonly SharedDestructibleSystem _destructible = default!;
|
||||
[Dependency] private readonly RandomHelperSystem _randomHelper = default!;
|
||||
[Dependency] private readonly JitteringSystem _jitter = default!;
|
||||
|
||||
@@ -123,10 +125,7 @@ namespace Content.Server.Kitchen.EntitySystems
|
||||
if (solution.Volume > containerSolution.AvailableVolume)
|
||||
continue;
|
||||
|
||||
var dev = new DestructionEventArgs();
|
||||
RaiseLocalEvent(item, dev);
|
||||
|
||||
QueueDel(item);
|
||||
_destructible.DestroyEntity(item);
|
||||
}
|
||||
|
||||
_solutionContainersSystem.TryAddSolution(containerSoln.Value, solution);
|
||||
|
||||
@@ -12,6 +12,7 @@ using Content.Shared.Verbs;
|
||||
using Content.Shared.Destructible;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Kitchen;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
@@ -124,7 +125,7 @@ public sealed class SharpSystem : EntitySystem
|
||||
if (hasBody)
|
||||
popupType = PopupType.LargeCaution;
|
||||
|
||||
_popupSystem.PopupEntity(Loc.GetString("butcherable-knife-butchered-success", ("target", args.Args.Target.Value), ("knife", uid)),
|
||||
_popupSystem.PopupEntity(Loc.GetString("butcherable-knife-butchered-success", ("target", args.Args.Target.Value), ("knife", Identity.Entity(uid, EntityManager))),
|
||||
popupEnt, args.Args.User, popupType);
|
||||
|
||||
if (hasBody)
|
||||
@@ -152,10 +153,10 @@ public sealed class SharpSystem : EntitySystem
|
||||
var disabled = false;
|
||||
string? message = null;
|
||||
|
||||
// if the user has hands
|
||||
// and the item they're holding doesn't have the SharpComponent
|
||||
// if the held item doesn't have SharpComponent
|
||||
// and the user doesn't have SharpComponent
|
||||
// disable the verb
|
||||
if (!TryComp<SharpComponent>(args.Using, out var usingSharpComp) && args.Hands != null)
|
||||
if (!TryComp<SharpComponent>(args.Using, out var usingSharpComp) && userSharpComp == null)
|
||||
{
|
||||
disabled = true;
|
||||
message = Loc.GetString("butcherable-need-knife",
|
||||
|
||||
@@ -1,24 +1,33 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.CartridgeLoader;
|
||||
using Content.Server.CartridgeLoader.Cartridges;
|
||||
using Content.Server.CartridgeLoader;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Discord;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.MassMedia.Components;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.CartridgeLoader;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.CartridgeLoader.Cartridges;
|
||||
using Content.Shared.CartridgeLoader;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.MassMedia.Components;
|
||||
using Content.Shared.MassMedia.Systems;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Content.Server.MassMedia.Systems;
|
||||
|
||||
@@ -34,11 +43,36 @@ public sealed class NewsSystem : SharedNewsSystem
|
||||
[Dependency] private readonly StationSystem _station = default!;
|
||||
[Dependency] private readonly GameTicker _ticker = default!;
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
[Dependency] private readonly DiscordWebhook _discord = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IBaseServer _baseServer = default!;
|
||||
|
||||
private WebhookIdentifier? _webhookId = null;
|
||||
private Color _webhookEmbedColor;
|
||||
private bool _webhookSendDuringRound;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
// Discord hook
|
||||
_cfg.OnValueChanged(CCVars.DiscordNewsWebhook,
|
||||
value =>
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
_discord.GetWebhook(value, data => _webhookId = data.ToIdentifier());
|
||||
}, true);
|
||||
|
||||
_cfg.OnValueChanged(CCVars.DiscordNewsWebhookEmbedColor, value =>
|
||||
{
|
||||
_webhookEmbedColor = Color.LawnGreen;
|
||||
if (Color.TryParse(value, out var color))
|
||||
_webhookEmbedColor = color;
|
||||
}, true);
|
||||
|
||||
_cfg.OnValueChanged(CCVars.DiscordNewsWebhookSendDuringRound, value => _webhookSendDuringRound = value, true);
|
||||
SubscribeLocalEvent<RoundEndMessageEvent>(OnRoundEndMessageEvent);
|
||||
|
||||
// News writer
|
||||
SubscribeLocalEvent<NewsWriterComponent, MapInitEvent>(OnMapInit);
|
||||
|
||||
@@ -177,6 +211,9 @@ public sealed class NewsSystem : SharedNewsSystem
|
||||
RaiseLocalEvent(readerUid, ref args);
|
||||
}
|
||||
|
||||
if (_webhookSendDuringRound)
|
||||
Task.Run(async () => await SendArticleToDiscordWebhook(article));
|
||||
|
||||
UpdateWriterDevices();
|
||||
}
|
||||
#endregion
|
||||
@@ -324,4 +361,62 @@ public sealed class NewsSystem : SharedNewsSystem
|
||||
{
|
||||
UpdateWriterUi(ent);
|
||||
}
|
||||
|
||||
#region Discord Hook
|
||||
|
||||
private void OnRoundEndMessageEvent(RoundEndMessageEvent ev)
|
||||
{
|
||||
if (_webhookSendDuringRound)
|
||||
return;
|
||||
|
||||
var query = EntityManager.EntityQueryEnumerator<StationNewsComponent>();
|
||||
|
||||
while (query.MoveNext(out _, out var comp))
|
||||
{
|
||||
SendArticlesListToDiscordWebhook(comp.Articles.OrderBy(article => article.ShareTime));
|
||||
}
|
||||
}
|
||||
|
||||
private async void SendArticlesListToDiscordWebhook(IOrderedEnumerable<NewsArticle> articles)
|
||||
{
|
||||
foreach (var article in articles)
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(1)); // TODO: proper discord rate limit handling
|
||||
await SendArticleToDiscordWebhook(article);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendArticleToDiscordWebhook(NewsArticle article)
|
||||
{
|
||||
if (_webhookId is null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var embed = new WebhookEmbed
|
||||
{
|
||||
Title = article.Title,
|
||||
// There is no need to cut article content. It's MaxContentLength smaller then discord's limit (4096):
|
||||
Description = FormattedMessage.RemoveMarkupPermissive(article.Content),
|
||||
Color = _webhookEmbedColor.ToArgb() & 0xFFFFFF, // HACK: way to get hex without A (transparency)
|
||||
Footer = new WebhookEmbedFooter
|
||||
{
|
||||
Text = Loc.GetString("news-discord-footer",
|
||||
("server", _baseServer.ServerName),
|
||||
("round", _ticker.RoundId),
|
||||
("author", article.Author ?? Loc.GetString("news-discord-unknown-author")),
|
||||
("time", article.ShareTime.ToString(@"hh\:mm\:ss")))
|
||||
}
|
||||
};
|
||||
var payload = new WebhookPayload { Embeds = [embed] };
|
||||
await _discord.CreateMessage(_webhookId.Value, payload);
|
||||
Log.Info("Sent news article to Discord webhook");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Error while sending discord news article:\n{e}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Server.Administration;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Server.Mind.Commands;
|
||||
@@ -10,6 +12,7 @@ namespace Content.Server.Mind.Commands;
|
||||
[AdminCommand(AdminFlags.VarEdit)]
|
||||
public sealed class RenameCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _cfgManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaSystem = default!;
|
||||
@@ -25,7 +28,7 @@ public sealed class RenameCommand : LocalizedEntityCommands
|
||||
}
|
||||
|
||||
var name = args[1];
|
||||
if (name.Length > IdCardConsoleComponent.MaxFullNameLength)
|
||||
if (name.Length > _cfgManager.GetCVar(CCVars.MaxNameLength))
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("cmd-rename-too-long"));
|
||||
return;
|
||||
|
||||
@@ -51,7 +51,7 @@ public sealed class MindShieldSystem : EntitySystem
|
||||
}
|
||||
|
||||
if (_mindSystem.TryGetMind(implanted, out var mindId, out _) &&
|
||||
_roleSystem.MindTryRemoveRole<RevolutionaryRoleComponent>(mindId))
|
||||
_roleSystem.MindRemoveRole<RevolutionaryRoleComponent>(mindId))
|
||||
{
|
||||
_adminLogManager.Add(LogType.Mind, LogImpact.Medium, $"{ToPrettyString(implanted)} was deconverted due to being implanted with a Mindshield.");
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators;
|
||||
/// </summary>
|
||||
public sealed partial class PickAccessibleComponentOperator : HTNOperator
|
||||
{
|
||||
[Dependency] private readonly IComponentFactory _factory = default!;
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
private PathfindingSystem _pathfinding = default!;
|
||||
private EntityLookupSystem _lookup = default!;
|
||||
@@ -46,7 +45,7 @@ public sealed partial class PickAccessibleComponentOperator : HTNOperator
|
||||
CancellationToken cancelToken)
|
||||
{
|
||||
// Check if the component exists
|
||||
if (!_factory.TryGetRegistration(Component, out var registration))
|
||||
if (!_entManager.ComponentFactory.TryGetRegistration(Component, out var registration))
|
||||
{
|
||||
return (false, null);
|
||||
}
|
||||
|
||||
@@ -50,10 +50,13 @@ public sealed partial class PathfindingSystem
|
||||
|
||||
private void OnTileChange(ref TileChangedEvent ev)
|
||||
{
|
||||
if (ev.OldTile.IsEmpty == ev.NewTile.Tile.IsEmpty)
|
||||
return;
|
||||
foreach (var change in ev.Changes)
|
||||
{
|
||||
if (change.OldTile.IsEmpty == change.NewTile.IsEmpty)
|
||||
continue;
|
||||
|
||||
DirtyChunk(ev.Entity, Comp<MapGridComponent>(ev.Entity).GridTileToLocal(ev.NewTile.GridIndices));
|
||||
DirtyChunk(ev.Entity, _maps.GridTileToLocal(ev.Entity, ev.Entity.Comp, change.GridIndices));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Content.Server.NPC.Queries.Considerations;
|
||||
|
||||
/// <summary>
|
||||
/// Returns if the target is below a certain temperature.
|
||||
/// </summary>
|
||||
public sealed partial class TargetLowTempCon : UtilityConsideration
|
||||
{
|
||||
/// <summary>
|
||||
/// The minimum temperature they must be.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float MinTemp;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using Content.Server.NPC.Queries.Queries;
|
||||
using Content.Server.Nutrition.Components;
|
||||
using Content.Server.Nutrition.EntitySystems;
|
||||
using Content.Server.Storage.Components;
|
||||
using Content.Server.Temperature.Components;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Examine;
|
||||
@@ -14,7 +15,6 @@ using Content.Shared.Fluids.Components;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.NPC.Systems;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
@@ -376,6 +376,13 @@ public sealed class NPCUtilitySystem : EntitySystem
|
||||
|
||||
return 0f;
|
||||
}
|
||||
case TargetLowTempCon con:
|
||||
{
|
||||
if (!TryComp<TemperatureComponent>(targetUid, out var temperature))
|
||||
return 0f;
|
||||
|
||||
return temperature.CurrentTemperature <= con.MinTemp ? 1f : 0f;
|
||||
}
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Content.Server.NodeContainer.EntitySystems
|
||||
/// </summary>
|
||||
/// <seealso cref="NodeGroupSystem"/>
|
||||
[UsedImplicitly]
|
||||
public sealed class NodeContainerSystem : EntitySystem
|
||||
public sealed class NodeContainerSystem : SharedNodeContainerSystem
|
||||
{
|
||||
[Dependency] private readonly NodeGroupSystem _nodeGroupSystem = default!;
|
||||
private EntityQuery<NodeContainerComponent> _query;
|
||||
|
||||
@@ -21,8 +21,7 @@ namespace Content.Server.Nuke
|
||||
/// <summary>
|
||||
/// Default bomb timer value in seconds.
|
||||
/// </summary>
|
||||
[DataField("timer")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public int Timer = 300;
|
||||
|
||||
/// <summary>
|
||||
@@ -36,7 +35,7 @@ namespace Content.Server.Nuke
|
||||
/// How long until the bomb can arm again after deactivation.
|
||||
/// Used to prevent announcements spam.
|
||||
/// </summary>
|
||||
[DataField("cooldown")]
|
||||
[DataField]
|
||||
public int Cooldown = 30;
|
||||
|
||||
/// <summary>
|
||||
@@ -143,32 +142,32 @@ namespace Content.Server.Nuke
|
||||
/// </summary>
|
||||
public (MapId, EntityUid?)? OriginMapGrid;
|
||||
|
||||
[DataField("codeLength")] public int CodeLength = 6;
|
||||
[ViewVariables] public string Code = string.Empty;
|
||||
[DataField] public int CodeLength = 6;
|
||||
[DataField] public string Code = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Time until explosion in seconds.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public float RemainingTime;
|
||||
|
||||
/// <summary>
|
||||
/// Time until bomb cooldown will expire in seconds.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[DataField]
|
||||
public float CooldownTime;
|
||||
|
||||
/// <summary>
|
||||
/// Current nuclear code buffer. Entered manually by players.
|
||||
/// If valid it will allow arm/disarm bomb.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[DataField]
|
||||
public string EnteredCode = "";
|
||||
|
||||
/// <summary>
|
||||
/// Current status of a nuclear bomb.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[DataField]
|
||||
public NukeStatus Status = NukeStatus.AWAIT_DISK;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -2,6 +2,7 @@ using Content.Server.AlertLevel;
|
||||
using Content.Server.Audio;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.Explosion.EntitySystems;
|
||||
using Content.Server.Kitchen.Components;
|
||||
using Content.Server.Pinpointer;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Station.Systems;
|
||||
@@ -79,11 +80,12 @@ public sealed class NukeSystem : EntitySystem
|
||||
|
||||
// Doafter events
|
||||
SubscribeLocalEvent<NukeComponent, NukeDisarmDoAfterEvent>(OnDoAfter);
|
||||
|
||||
SubscribeLocalEvent<NukeDiskComponent, BeingMicrowavedEvent>(OnMicrowaved);
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, NukeComponent component, ComponentInit args)
|
||||
{
|
||||
component.RemainingTime = component.Timer;
|
||||
_itemSlots.AddItemSlot(uid, SharedNukeComponent.NukeDiskSlotId, component.DiskSlot);
|
||||
|
||||
UpdateStatus(uid, component);
|
||||
@@ -111,11 +113,13 @@ public sealed class NukeSystem : EntitySystem
|
||||
|
||||
private void OnMapInit(EntityUid uid, NukeComponent nuke, MapInitEvent args)
|
||||
{
|
||||
nuke.RemainingTime = nuke.Timer;
|
||||
var originStation = _station.GetOwningStation(uid);
|
||||
|
||||
if (originStation != null)
|
||||
{
|
||||
nuke.OriginStation = originStation;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
var transform = Transform(uid);
|
||||
@@ -125,6 +129,19 @@ public sealed class NukeSystem : EntitySystem
|
||||
nuke.Code = GenerateRandomNumberString(nuke.CodeLength);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Slightly randomize nuke countdown timer
|
||||
/// </summary>
|
||||
private void OnMicrowaved(Entity<NukeDiskComponent> ent, ref BeingMicrowavedEvent args)
|
||||
{
|
||||
if (ent.Comp.TimeModifier != null)
|
||||
return;
|
||||
|
||||
var seconds = _random.NextGaussian(ent.Comp.MicrowaveMean.TotalSeconds, ent.Comp.MicrowaveStd.TotalSeconds);
|
||||
ent.Comp.TimeModifier = TimeSpan.FromSeconds(seconds);
|
||||
_popups.PopupEntity(Loc.GetString("nuke-disk-component-microwave"), ent.Owner, PopupType.Medium);
|
||||
}
|
||||
|
||||
private void OnRemove(EntityUid uid, NukeComponent component, ComponentRemove args)
|
||||
{
|
||||
_itemSlots.RemoveItemSlot(uid, component.DiskSlot);
|
||||
@@ -346,11 +363,11 @@ public sealed class NukeSystem : EntitySystem
|
||||
break;
|
||||
}
|
||||
|
||||
// var isValid = _codes.IsCodeValid(uid, component.EnteredCode);
|
||||
if (component.EnteredCode == component.Code)
|
||||
{
|
||||
component.Status = NukeStatus.AWAIT_ARM;
|
||||
component.RemainingTime = component.Timer;
|
||||
var modifier = CompOrNull<NukeDiskComponent>(component.DiskSlot.Item)?.TimeModifier ?? TimeSpan.Zero;
|
||||
component.RemainingTime = MathF.Max(component.Timer + (float)modifier.TotalSeconds, component.MinimumTime);
|
||||
_audio.PlayPvs(component.AccessGrantedSound, uid);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -336,6 +336,11 @@ public sealed class FoodSystem : EntitySystem
|
||||
if (ev.Cancelled)
|
||||
return;
|
||||
|
||||
var attemptEv = new DestructionAttemptEvent();
|
||||
RaiseLocalEvent(food, attemptEv);
|
||||
if (attemptEv.Cancelled)
|
||||
return;
|
||||
|
||||
var afterEvent = new AfterFullyEatenEvent(user);
|
||||
RaiseLocalEvent(food, ref afterEvent);
|
||||
|
||||
@@ -436,8 +441,10 @@ public sealed class FoodSystem : EntitySystem
|
||||
// Check if the food is in the whitelist
|
||||
if (_whitelistSystem.IsWhitelistPass(ent.Comp1.SpecialDigestible, food))
|
||||
return true;
|
||||
// They can only eat whitelist food and the food isn't in the whitelist. It's not edible.
|
||||
return false;
|
||||
|
||||
// If their diet is whitelist exclusive, then they cannot eat anything but what follows their whitelisted tags. Else, they can eat their tags AND human food.
|
||||
if (ent.Comp1.IsSpecialDigestibleExclusive)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (component.RequiresSpecialDigestion)
|
||||
|
||||
@@ -178,17 +178,32 @@ public sealed class ObjectivesSystem : SharedObjectivesSystem
|
||||
agentSummary.AppendLine(Loc.GetString(
|
||||
"objectives-objective-success",
|
||||
("objective", objectiveTitle),
|
||||
("markupColor", "green")
|
||||
("progress", progress)
|
||||
));
|
||||
completedObjectives++;
|
||||
}
|
||||
else if (progress <= 0.99f && progress >= 0.5f)
|
||||
{
|
||||
agentSummary.AppendLine(Loc.GetString(
|
||||
"objectives-objective-partial-success",
|
||||
("objective", objectiveTitle),
|
||||
("progress", progress)
|
||||
));
|
||||
}
|
||||
else if (progress < 0.5f && progress > 0f)
|
||||
{
|
||||
agentSummary.AppendLine(Loc.GetString(
|
||||
"objectives-objective-partial-failure",
|
||||
("objective", objectiveTitle),
|
||||
("progress", progress)
|
||||
));
|
||||
}
|
||||
else
|
||||
{
|
||||
agentSummary.AppendLine(Loc.GetString(
|
||||
"objectives-objective-fail",
|
||||
("objective", objectiveTitle),
|
||||
("progress", (int) (progress * 100)),
|
||||
("markupColor", "red")
|
||||
("progress", progress)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,8 +30,9 @@ public sealed class EscapeShuttleConditionSystem : EntitySystem
|
||||
return 0f;
|
||||
|
||||
// You're not escaping if you're restrained!
|
||||
// Granting 50% as to allow for partial completion of the objective.
|
||||
if (TryComp<CuffableComponent>(mind.OwnedEntity, out var cuffed) && cuffed.CuffedHandCount > 0)
|
||||
return 0f;
|
||||
return _emergencyShuttle.IsTargetEscaping(mind.OwnedEntity.Value) ? 0.5f : 0f;
|
||||
|
||||
// Any emergency shuttle counts for this objective, but not pods.
|
||||
return _emergencyShuttle.IsTargetEscaping(mind.OwnedEntity.Value) ? 1f : 0f;
|
||||
|
||||
@@ -53,6 +53,6 @@ public sealed class HelpProgressConditionSystem : EntitySystem
|
||||
|
||||
// require 50% completion for this one to be complete
|
||||
var completion = total / max;
|
||||
return completion >= 0.5f ? 1f : completion / 0.5f;
|
||||
return completion;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
if (biome.Template == null || !reloads.Modified.TryGetValue(biome.Template, out var proto))
|
||||
continue;
|
||||
|
||||
SetTemplate(uid, biome, (BiomeTemplatePrototype) proto);
|
||||
SetTemplate(uid, biome, (BiomeTemplatePrototype)proto);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,7 +257,7 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
private void OnFTLStarted(ref FTLStartedEvent ev)
|
||||
{
|
||||
var targetMap = _transform.ToMapCoordinates(ev.TargetCoordinates);
|
||||
var targetMapUid = _mapManager.GetMapEntityId(targetMap.MapId);
|
||||
var targetMapUid = _mapSystem.GetMapOrInvalid(targetMap.MapId);
|
||||
|
||||
if (!TryComp<BiomeComponent>(targetMapUid, out var biome))
|
||||
return;
|
||||
@@ -283,12 +283,12 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
{
|
||||
for (var y = Math.Floor(aabb.Bottom); y <= Math.Ceiling(aabb.Top); y++)
|
||||
{
|
||||
var index = new Vector2i((int) x, (int) y);
|
||||
var index = new Vector2i((int)x, (int)y);
|
||||
var chunk = SharedMapSystem.GetChunkIndices(index, ChunkSize);
|
||||
|
||||
var mod = biome.ModifiedTiles.GetOrNew(chunk * ChunkSize);
|
||||
|
||||
if (!mod.Add(index) || !TryGetBiomeTile(index, biome.Layers, biome.Seed, grid, out var tile))
|
||||
if (!mod.Add(index) || !TryGetBiomeTile(index, biome.Layers, biome.Seed, (ev.MapUid, grid), out var tile))
|
||||
continue;
|
||||
|
||||
// If we flag it as modified then the tile is never set so need to do it ourselves.
|
||||
@@ -493,9 +493,9 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
var layerProto = ProtoManager.Index<BiomeMarkerLayerPrototype>(layer);
|
||||
var markerSeed = seed + chunk.X * ChunkSize + chunk.Y + localIdx;
|
||||
var rand = new Random(markerSeed);
|
||||
var buffer = (int) (layerProto.Radius / 2f);
|
||||
var buffer = (int)(layerProto.Radius / 2f);
|
||||
var bounds = new Box2i(chunk + buffer, chunk + layerProto.Size - buffer);
|
||||
var count = (int) (bounds.Area / (layerProto.Radius * layerProto.Radius));
|
||||
var count = (int)(bounds.Area / (layerProto.Radius * layerProto.Radius));
|
||||
count = Math.Min(count, layerProto.MaxCount);
|
||||
|
||||
GetMarkerNodes(gridUid, component, grid, layerProto, forced, bounds, count, rand,
|
||||
@@ -607,7 +607,7 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
continue;
|
||||
|
||||
// Check if mask matches // anything blocking.
|
||||
TryGetEntity(node, biome, grid, out var proto);
|
||||
TryGetEntity(node, biome, (gridUid, grid), out var proto);
|
||||
|
||||
// If there's an existing entity and it doesn't match the mask then skip.
|
||||
if (layerProto.EntityMask.Count > 0 &&
|
||||
@@ -727,14 +727,14 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
continue;
|
||||
|
||||
// Need to ensure the tile under it has loaded for anchoring.
|
||||
if (TryGetBiomeTile(node, component.Layers, seed, grid, out var tile))
|
||||
if (TryGetBiomeTile(node, component.Layers, seed, (gridUid, grid), out var tile))
|
||||
{
|
||||
_mapSystem.SetTile(gridUid, grid, node, tile.Value);
|
||||
}
|
||||
|
||||
string? prototype;
|
||||
|
||||
if (TryGetEntity(node, component, grid, out var proto) &&
|
||||
if (TryGetEntity(node, component, (gridUid, grid), out var proto) &&
|
||||
layerProto.EntityMask.TryGetValue(proto, out var maskedProto))
|
||||
{
|
||||
prototype = maskedProto;
|
||||
@@ -793,7 +793,7 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
if (_mapSystem.TryGetTileRef(gridUid, grid, indices, out var tileRef) && !tileRef.Tile.IsEmpty)
|
||||
continue;
|
||||
|
||||
if (!TryGetBiomeTile(indices, component.Layers, seed, grid, out var biomeTile))
|
||||
if (!TryGetBiomeTile(indices, component.Layers, seed, (gridUid, grid), out var biomeTile))
|
||||
continue;
|
||||
|
||||
_tiles.Add((indices, biomeTile.Value));
|
||||
@@ -819,7 +819,7 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
// Don't mess with anything that's potentially anchored.
|
||||
var anchored = _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, indices);
|
||||
|
||||
if (anchored.MoveNext(out _) || !TryGetEntity(indices, component, grid, out var entPrototype))
|
||||
if (anchored.MoveNext(out _) || !TryGetEntity(indices, component, (gridUid, grid), out var entPrototype))
|
||||
continue;
|
||||
|
||||
// TODO: Fix non-anchored ents spawning.
|
||||
@@ -852,7 +852,7 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
// Don't mess with anything that's potentially anchored.
|
||||
var anchored = _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, indices);
|
||||
|
||||
if (anchored.MoveNext(out _) || !TryGetDecals(indices, component.Layers, seed, grid, out var decals))
|
||||
if (anchored.MoveNext(out _) || !TryGetDecals(indices, component.Layers, seed, (gridUid, grid), out var decals))
|
||||
continue;
|
||||
|
||||
foreach (var decal in decals)
|
||||
@@ -966,7 +966,7 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
continue;
|
||||
|
||||
// Don't mess with anything that's potentially anchored.
|
||||
var anchored = grid.GetAnchoredEntitiesEnumerator(indices);
|
||||
var anchored = _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, indices);
|
||||
|
||||
if (anchored.MoveNext(out _))
|
||||
{
|
||||
@@ -1011,7 +1011,7 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
return;
|
||||
|
||||
EnsureComp<MapGridComponent>(mapUid);
|
||||
var biome = (BiomeComponent) EntityManager.ComponentFactory.GetComponent(typeof(BiomeComponent));
|
||||
var biome = EntityManager.ComponentFactory.GetComponent<BiomeComponent>();
|
||||
seed ??= _random.Next();
|
||||
SetSeed(mapUid, biome, seed.Value, false);
|
||||
SetTemplate(mapUid, biome, biomeTemplate, false);
|
||||
@@ -1040,8 +1040,8 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
EnsureComp<SunShadowCycleComponent>(mapUid);
|
||||
|
||||
var moles = new float[Atmospherics.AdjustedNumberOfGases];
|
||||
moles[(int) Gas.Oxygen] = 21.824779f;
|
||||
moles[(int) Gas.Nitrogen] = 82.10312f;
|
||||
moles[(int)Gas.Oxygen] = 21.824779f;
|
||||
moles[(int)Gas.Nitrogen] = 82.10312f;
|
||||
|
||||
var mixture = new GasMixture(moles, Atmospherics.T20C);
|
||||
|
||||
@@ -1070,7 +1070,7 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!TryGetBiomeTile(tileSet.GridIndices, biome.Layers, biome.Seed, mapGrid, out var tile))
|
||||
if (!TryGetBiomeTile(tileSet.GridIndices, biome.Layers, biome.Seed, (mapUid, mapGrid), out var tile))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ public sealed class PayloadSystem : EntitySystem
|
||||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
|
||||
[Dependency] private readonly TransformSystem _transform = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly IComponentFactory _componentFactory = default!;
|
||||
[Dependency] private readonly ISerializationManager _serializationManager = default!;
|
||||
|
||||
private static readonly ProtoId<TagPrototype> PayloadTag = "Payload";
|
||||
@@ -92,13 +91,13 @@ public sealed class PayloadSystem : EntitySystem
|
||||
// ANY payload trigger that gets inserted can grant components. It is up to the construction graphs to determine trigger capacity.
|
||||
foreach (var (name, data) in trigger.Components)
|
||||
{
|
||||
if (!_componentFactory.TryGetRegistration(name, out var registration))
|
||||
if (!Factory.TryGetRegistration(name, out var registration))
|
||||
continue;
|
||||
|
||||
if (HasComp(uid, registration.Type))
|
||||
continue;
|
||||
|
||||
if (_componentFactory.GetComponent(registration.Type) is not Component component)
|
||||
if (Factory.GetComponent(registration.Type) is not Component component)
|
||||
continue;
|
||||
|
||||
var temp = (object) component;
|
||||
|
||||
@@ -101,30 +101,36 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
|
||||
|
||||
private void OnTileChanged(ref TileChangedEvent ev)
|
||||
{
|
||||
if (!ev.EmptyChanged || !_navQuery.TryComp(ev.NewTile.GridUid, out var navMap))
|
||||
if (!_navQuery.TryComp(ev.Entity, out var navMap))
|
||||
return;
|
||||
|
||||
var tile = ev.NewTile.GridIndices;
|
||||
var chunkOrigin = SharedMapSystem.GetChunkIndices(tile, ChunkSize);
|
||||
|
||||
var chunk = EnsureChunk(navMap, chunkOrigin);
|
||||
|
||||
// This could be easily replaced in the future to accommodate diagonal tiles
|
||||
var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize);
|
||||
ref var tileData = ref chunk.TileData[GetTileIndex(relative)];
|
||||
|
||||
if (ev.NewTile.IsSpace(_tileDefManager))
|
||||
foreach (var change in ev.Changes)
|
||||
{
|
||||
tileData = 0;
|
||||
if (PruneEmpty((ev.NewTile.GridUid, navMap), chunk))
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
tileData = FloorMask;
|
||||
}
|
||||
if (!change.EmptyChanged)
|
||||
continue;
|
||||
|
||||
DirtyChunk((ev.NewTile.GridUid, navMap), chunk);
|
||||
var tile = change.GridIndices;
|
||||
var chunkOrigin = SharedMapSystem.GetChunkIndices(tile, ChunkSize);
|
||||
|
||||
var chunk = EnsureChunk(navMap, chunkOrigin);
|
||||
|
||||
// This could be easily replaced in the future to accommodate diagonal tiles
|
||||
var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize);
|
||||
ref var tileData = ref chunk.TileData[GetTileIndex(relative)];
|
||||
|
||||
if (change.NewTile.IsSpace(_tileDefManager))
|
||||
{
|
||||
tileData = 0;
|
||||
if (PruneEmpty((ev.Entity, navMap), chunk))
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
tileData = FloorMask;
|
||||
}
|
||||
|
||||
DirtyChunk((ev.Entity, navMap), chunk);
|
||||
}
|
||||
}
|
||||
|
||||
private void DirtyChunk(Entity<NavMapComponent> entity, NavMapChunk chunk)
|
||||
|
||||
@@ -28,7 +28,6 @@ namespace Content.Server.Polymorph.Systems;
|
||||
|
||||
public sealed partial class PolymorphSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IComponentFactory _compFact = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
@@ -213,7 +212,7 @@ public sealed partial class PolymorphSystem : EntitySystem
|
||||
|
||||
MakeSentientCommand.MakeSentient(child, EntityManager);
|
||||
|
||||
var polymorphedComp = _compFact.GetComponent<PolymorphedEntityComponent>();
|
||||
var polymorphedComp = Factory.GetComponent<PolymorphedEntityComponent>();
|
||||
polymorphedComp.Parent = uid;
|
||||
polymorphedComp.Configuration = configuration;
|
||||
AddComp(child, polymorphedComp);
|
||||
|
||||
@@ -31,7 +31,6 @@ namespace Content.Server.Power.EntitySystems
|
||||
private readonly HashSet<ApcNet> _apcNetReconnectQueue = new();
|
||||
|
||||
private EntityQuery<ApcPowerReceiverBatteryComponent> _apcBatteryQuery;
|
||||
private EntityQuery<AppearanceComponent> _appearanceQuery;
|
||||
private EntityQuery<BatteryComponent> _batteryQuery;
|
||||
|
||||
private BatteryRampPegSolver _solver = new();
|
||||
@@ -41,7 +40,6 @@ namespace Content.Server.Power.EntitySystems
|
||||
base.Initialize();
|
||||
|
||||
_apcBatteryQuery = GetEntityQuery<ApcPowerReceiverBatteryComponent>();
|
||||
_appearanceQuery = GetEntityQuery<AppearanceComponent>();
|
||||
_batteryQuery = GetEntityQuery<BatteryComponent>();
|
||||
|
||||
UpdatesAfter.Add(typeof(NodeGroupSystem));
|
||||
@@ -317,15 +315,25 @@ namespace Content.Server.Power.EntitySystems
|
||||
_powerNetReconnectQueue.Clear();
|
||||
}
|
||||
|
||||
private bool IsPoweredCalculate(ApcPowerReceiverComponent comp)
|
||||
{
|
||||
return !comp.PowerDisabled
|
||||
&& (!comp.NeedsPower
|
||||
|| MathHelper.CloseToPercent(comp.NetworkLoad.ReceivingPower,
|
||||
comp.Load));
|
||||
}
|
||||
|
||||
public override bool IsPoweredCalculate(SharedApcPowerReceiverComponent comp)
|
||||
{
|
||||
return IsPoweredCalculate((ApcPowerReceiverComponent)comp);
|
||||
}
|
||||
|
||||
private void UpdateApcPowerReceiver(float frameTime)
|
||||
{
|
||||
var enumerator = AllEntityQuery<ApcPowerReceiverComponent>();
|
||||
while (enumerator.MoveNext(out var uid, out var apcReceiver))
|
||||
{
|
||||
var powered = !apcReceiver.PowerDisabled
|
||||
&& (!apcReceiver.NeedsPower
|
||||
|| MathHelper.CloseToPercent(apcReceiver.NetworkLoad.ReceivingPower,
|
||||
apcReceiver.Load));
|
||||
var powered = IsPoweredCalculate(apcReceiver);
|
||||
|
||||
MetaDataComponent? metadata = null;
|
||||
|
||||
@@ -381,9 +389,6 @@ namespace Content.Server.Power.EntitySystems
|
||||
|
||||
var ev = new PowerChangedEvent(powered, apcReceiver.NetworkLoad.ReceivingPower);
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
|
||||
if (_appearanceQuery.TryComp(uid, out var appearance))
|
||||
_appearance.SetData(uid, PowerDeviceVisuals.Powered, powered, appearance);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Shared.Construction.Prototypes;
|
||||
using Content.Shared.Preferences;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Preferences.Managers
|
||||
{
|
||||
@@ -22,5 +24,6 @@ namespace Content.Server.Preferences.Managers
|
||||
bool HavePreferencesLoaded(ICommonSession session);
|
||||
|
||||
Task SetProfile(NetUserId userId, int slot, ICharacterProfile profile);
|
||||
Task SetConstructionFavorites(NetUserId userId, List<ProtoId<ConstructionPrototype>> favorites);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,13 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Database;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Construction.Prototypes;
|
||||
using Content.Shared.Preferences;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Preferences.Managers
|
||||
@@ -26,6 +28,7 @@ namespace Content.Server.Preferences.Managers
|
||||
[Dependency] private readonly IDependencyCollection _dependencies = default!;
|
||||
[Dependency] private readonly ILogManager _log = default!;
|
||||
[Dependency] private readonly UserDbDataManager _userDb = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
// Cache player prefs on the server so we don't need as much async hell related to them.
|
||||
private readonly Dictionary<NetUserId, PlayerPrefData> _cachedPlayerPrefs =
|
||||
@@ -41,6 +44,7 @@ namespace Content.Server.Preferences.Managers
|
||||
_netManager.RegisterNetMessage<MsgSelectCharacter>(HandleSelectCharacterMessage);
|
||||
_netManager.RegisterNetMessage<MsgUpdateCharacter>(HandleUpdateCharacterMessage);
|
||||
_netManager.RegisterNetMessage<MsgDeleteCharacter>(HandleDeleteCharacterMessage);
|
||||
_netManager.RegisterNetMessage<MsgUpdateConstructionFavorites>(HandleUpdateConstructionFavoritesMessage);
|
||||
_sawmill = _log.GetSawmill("prefs");
|
||||
}
|
||||
|
||||
@@ -68,7 +72,7 @@ namespace Content.Server.Preferences.Managers
|
||||
return;
|
||||
}
|
||||
|
||||
prefsData.Prefs = new PlayerPreferences(curPrefs.Characters, index, curPrefs.AdminOOCColor);
|
||||
prefsData.Prefs = new PlayerPreferences(curPrefs.Characters, index, curPrefs.AdminOOCColor, curPrefs.ConstructionFavorites);
|
||||
|
||||
if (ShouldStorePrefs(message.MsgChannel.AuthType))
|
||||
{
|
||||
@@ -108,12 +112,28 @@ namespace Content.Server.Preferences.Managers
|
||||
[slot] = profile
|
||||
};
|
||||
|
||||
prefsData.Prefs = new PlayerPreferences(profiles, slot, curPrefs.AdminOOCColor);
|
||||
prefsData.Prefs = new PlayerPreferences(profiles, slot, curPrefs.AdminOOCColor, curPrefs.ConstructionFavorites);
|
||||
|
||||
if (ShouldStorePrefs(session.Channel.AuthType))
|
||||
await _db.SaveCharacterSlotAsync(userId, profile, slot);
|
||||
}
|
||||
|
||||
public async Task SetConstructionFavorites(NetUserId userId, List<ProtoId<ConstructionPrototype>> favorites)
|
||||
{
|
||||
if (!_cachedPlayerPrefs.TryGetValue(userId, out var prefsData) || !prefsData.PrefsLoaded)
|
||||
{
|
||||
_sawmill.Error($"Tried to modify user {userId} preferences before they loaded.");
|
||||
return;
|
||||
}
|
||||
|
||||
var curPrefs = prefsData.Prefs!;
|
||||
prefsData.Prefs = new PlayerPreferences(curPrefs.Characters, curPrefs.SelectedCharacterIndex, curPrefs.AdminOOCColor, favorites);
|
||||
|
||||
var session = _playerManager.GetSessionById(userId);
|
||||
if (ShouldStorePrefs(session.Channel.AuthType))
|
||||
await _db.SaveConstructionFavoritesAsync(userId, favorites);
|
||||
}
|
||||
|
||||
private async void HandleDeleteCharacterMessage(MsgDeleteCharacter message)
|
||||
{
|
||||
var slot = message.Slot;
|
||||
@@ -151,7 +171,7 @@ namespace Content.Server.Preferences.Managers
|
||||
var arr = new Dictionary<int, ICharacterProfile>(curPrefs.Characters);
|
||||
arr.Remove(slot);
|
||||
|
||||
prefsData.Prefs = new PlayerPreferences(arr, nextSlot ?? curPrefs.SelectedCharacterIndex, curPrefs.AdminOOCColor);
|
||||
prefsData.Prefs = new PlayerPreferences(arr, nextSlot ?? curPrefs.SelectedCharacterIndex, curPrefs.AdminOOCColor, curPrefs.ConstructionFavorites);
|
||||
|
||||
if (ShouldStorePrefs(message.MsgChannel.AuthType))
|
||||
{
|
||||
@@ -166,6 +186,40 @@ namespace Content.Server.Preferences.Managers
|
||||
}
|
||||
}
|
||||
|
||||
private async void HandleUpdateConstructionFavoritesMessage(MsgUpdateConstructionFavorites message)
|
||||
{
|
||||
var userId = message.MsgChannel.UserId;
|
||||
if (!_cachedPlayerPrefs.TryGetValue(userId, out var prefsData) || !prefsData.PrefsLoaded)
|
||||
{
|
||||
_sawmill.Warning($"User {userId} tried to modify preferences before they loaded.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate items in the message so that a modified client cannot freely store a gigabyte of arbitrary data.
|
||||
var validatedSet = new HashSet<ProtoId<ConstructionPrototype>>();
|
||||
foreach (var favorite in message.Favorites)
|
||||
{
|
||||
if (_prototypeManager.HasIndex(favorite))
|
||||
validatedSet.Add(favorite);
|
||||
}
|
||||
|
||||
var validatedList = message.Favorites;
|
||||
if (validatedSet.Count != message.Favorites.Count)
|
||||
{
|
||||
// A difference in counts indicates that unrecognized or duplicate IDs are present.
|
||||
_sawmill.Warning($"User {userId} sent invalid construction favorites.");
|
||||
validatedList = validatedSet.ToList();
|
||||
}
|
||||
|
||||
var curPrefs = prefsData.Prefs!;
|
||||
prefsData.Prefs = new PlayerPreferences(curPrefs.Characters, curPrefs.SelectedCharacterIndex, curPrefs.AdminOOCColor, validatedList);
|
||||
|
||||
if (ShouldStorePrefs(message.MsgChannel.AuthType))
|
||||
{
|
||||
await _db.SaveConstructionFavoritesAsync(userId, validatedList);
|
||||
}
|
||||
}
|
||||
|
||||
// Should only be called via UserDbDataManager.
|
||||
public async Task LoadData(ICommonSession session, CancellationToken cancel)
|
||||
{
|
||||
@@ -176,8 +230,8 @@ namespace Content.Server.Preferences.Managers
|
||||
{
|
||||
PrefsLoaded = true,
|
||||
Prefs = new PlayerPreferences(
|
||||
new[] {new KeyValuePair<int, ICharacterProfile>(0, HumanoidCharacterProfile.Random())},
|
||||
0, Color.Transparent)
|
||||
new[] { new KeyValuePair<int, ICharacterProfile>(0, HumanoidCharacterProfile.Random()) },
|
||||
0, Color.Transparent, [])
|
||||
};
|
||||
|
||||
_cachedPlayerPrefs[session.UserId] = prefsData;
|
||||
@@ -294,7 +348,7 @@ namespace Content.Server.Preferences.Managers
|
||||
return new PlayerPreferences(prefs.Characters.Select(p =>
|
||||
{
|
||||
return new KeyValuePair<int, ICharacterProfile>(p.Key, p.Value.Validated(session, collection));
|
||||
}), prefs.SelectedCharacterIndex, prefs.AdminOOCColor);
|
||||
}), prefs.SelectedCharacterIndex, prefs.AdminOOCColor, prefs.ConstructionFavorites);
|
||||
}
|
||||
|
||||
public IEnumerable<KeyValuePair<NetUserId, ICharacterProfile>> GetSelectedProfilesForPlayers(
|
||||
|
||||
@@ -13,14 +13,8 @@ public sealed partial class DungeonJob
|
||||
/// <summary>
|
||||
/// <see cref="AutoCablingDunGen"/>
|
||||
/// </summary>
|
||||
private async Task PostGen(AutoCablingDunGen gen, DungeonData data, Dungeon dungeon, HashSet<Vector2i> reservedTiles, Random random)
|
||||
private async Task PostGen(AutoCablingDunGen gen, Dungeon dungeon, HashSet<Vector2i> reservedTiles, Random random)
|
||||
{
|
||||
if (!data.Entities.TryGetValue(DungeonDataKey.Cabling, out var ent))
|
||||
{
|
||||
LogDataError(typeof(AutoCablingDunGen));
|
||||
return;
|
||||
}
|
||||
|
||||
// There's a lot of ways you could do this.
|
||||
// For now we'll just connect every LV cable in the dungeon.
|
||||
var cableTiles = new HashSet<Vector2i>();
|
||||
@@ -157,7 +151,7 @@ public sealed partial class DungeonJob
|
||||
if (found)
|
||||
continue;
|
||||
|
||||
_entManager.SpawnEntity(ent, _maps.GridTileToLocal(_gridUid, _grid, tile));
|
||||
_entManager.SpawnEntity(gen.Entity, _maps.GridTileToLocal(_gridUid, _grid, tile));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ public sealed partial class DungeonJob
|
||||
/// <summary>
|
||||
/// <see cref="BiomeDunGen"/>
|
||||
/// </summary>
|
||||
private async Task PostGen(BiomeDunGen dunGen, DungeonData data, Dungeon dungeon, HashSet<Vector2i> reservedTiles, Random random)
|
||||
private async Task PostGen(BiomeDunGen dunGen, Dungeon dungeon, HashSet<Vector2i> reservedTiles, Random random)
|
||||
{
|
||||
if (!_prototype.TryIndex(dunGen.BiomeTemplate, out var indexedBiome))
|
||||
return;
|
||||
@@ -31,10 +31,10 @@ public sealed partial class DungeonJob
|
||||
|
||||
if (reservedTiles.Contains(node))
|
||||
continue;
|
||||
|
||||
|
||||
if (dunGen.TileMask is not null)
|
||||
{
|
||||
if (!dunGen.TileMask.Contains(((ContentTileDefinition) _tileDefManager[tileRef.Value.Tile.TypeId]).ID))
|
||||
if (!dunGen.TileMask.Contains(((ContentTileDefinition)_tileDefManager[tileRef.Value.Tile.TypeId]).ID))
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -45,12 +45,12 @@ public sealed partial class DungeonJob
|
||||
}
|
||||
|
||||
// Need to set per-tile to override data.
|
||||
if (biomeSystem.TryGetTile(node, indexedBiome.Layers, seed, _grid, out var tile))
|
||||
if (biomeSystem.TryGetTile(node, indexedBiome.Layers, seed, (_gridUid, _grid), out var tile))
|
||||
{
|
||||
_maps.SetTile(_gridUid, _grid, node, tile.Value);
|
||||
}
|
||||
|
||||
if (biomeSystem.TryGetDecals(node, indexedBiome.Layers, seed, _grid, out var decals))
|
||||
if (biomeSystem.TryGetDecals(node, indexedBiome.Layers, seed, (_gridUid, _grid), out var decals))
|
||||
{
|
||||
foreach (var decal in decals)
|
||||
{
|
||||
@@ -58,7 +58,7 @@ public sealed partial class DungeonJob
|
||||
}
|
||||
}
|
||||
|
||||
if (biomeSystem.TryGetEntity(node, indexedBiome.Layers, tile ?? tileRef.Value.Tile, seed, _grid, out var entityProto))
|
||||
if (biomeSystem.TryGetEntity(node, indexedBiome.Layers, tile ?? tileRef.Value.Tile, seed, (_gridUid, _grid), out var entityProto))
|
||||
{
|
||||
var ent = _entManager.SpawnEntity(entityProto, new EntityCoordinates(_gridUid, node + _grid.TileSizeHalfVector));
|
||||
var xform = xformQuery.Get(ent);
|
||||
@@ -15,7 +15,7 @@ public sealed partial class DungeonJob
|
||||
/// <summary>
|
||||
/// <see cref="BiomeMarkerLayerDunGen"/>
|
||||
/// </summary>
|
||||
private async Task PostGen(BiomeMarkerLayerDunGen dunGen, DungeonData data, Dungeon dungeon, HashSet<Vector2i> reservedTiles, Random random)
|
||||
private async Task PostGen(BiomeMarkerLayerDunGen dunGen, Dungeon dungeon, HashSet<Vector2i> reservedTiles, Random random)
|
||||
{
|
||||
// If we're adding biome then disable it and just use for markers.
|
||||
if (_entManager.EnsureComponent(_gridUid, out BiomeComponent biomeComp))
|
||||
@@ -12,27 +12,13 @@ public sealed partial class DungeonJob
|
||||
/// <summary>
|
||||
/// <see cref="BoundaryWallDunGen"/>
|
||||
/// </summary>
|
||||
private async Task PostGen(BoundaryWallDunGen gen, DungeonData data, Dungeon dungeon, HashSet<Vector2i> reservedTiles, Random random)
|
||||
private async Task PostGen(BoundaryWallDunGen gen, Dungeon dungeon, HashSet<Vector2i> reservedTiles, Random random)
|
||||
{
|
||||
if (!data.Tiles.TryGetValue(DungeonDataKey.FallbackTile, out var protoTileDef) ||
|
||||
!data.Entities.TryGetValue(DungeonDataKey.Walls, out var wall))
|
||||
{
|
||||
_sawmill.Error($"Error finding dungeon data for {nameof(gen)}");
|
||||
return;
|
||||
}
|
||||
|
||||
var tileDef = _tileDefManager[protoTileDef];
|
||||
var tileDef = _tileDefManager[gen.Tile];
|
||||
var tiles = new List<(Vector2i Index, Tile Tile)>(dungeon.RoomExteriorTiles.Count);
|
||||
|
||||
if (!data.Entities.TryGetValue(DungeonDataKey.CornerWalls, out var cornerWall))
|
||||
{
|
||||
cornerWall = wall;
|
||||
}
|
||||
|
||||
if (cornerWall == default)
|
||||
{
|
||||
cornerWall = wall;
|
||||
}
|
||||
var wall = gen.Wall;
|
||||
var cornerWall = gen.CornerWall ?? gen.Wall;
|
||||
|
||||
// Spawn wall outline
|
||||
// - Tiles first
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user