Merge remote-tracking branch 'space-wizards/master'
# Conflicts: # Content.Client/Construction/UI/ConstructionMenu.xaml # Content.Client/Construction/UI/ConstructionMenu.xaml.cs # Content.Client/Construction/UI/ConstructionMenuPresenter.cs # Content.Server/Warps/WarpPointSystem.cs # Content.Server/Weapons/Ranged/Systems/GunSystem.Battery.cs # Content.Server/Weapons/Ranged/Systems/GunSystem.cs # Content.Shared/Construction/Steps/ArbitraryInsertConstructionGraphStep.cs # Content.Shared/Construction/Steps/MaterialConstructionGraphStep.cs # Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs # Content.Shared/Inventory/InventorySystem.Relay.cs # Content.Shared/Weapons/Reflect/ReflectSystem.cs # Resources/Locale/en-US/_strings/plants/mutations.ftl # Resources/Locale/en-US/_strings/stack/stacks.ftl # Resources/Locale/en-US/_strings/teleportation/teleportation-menu-gui.ftl # Resources/Prototypes/Catalog/Fills/Backpacks/duffelbag.yml # Resources/Prototypes/Catalog/Fills/Lockers/heads.yml # Resources/Prototypes/Catalog/VendingMachines/Inventories/winterdrobe.yml # Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml # Resources/Prototypes/Entities/Clothing/OuterClothing/armor.yml # Resources/Prototypes/Entities/Objects/Power/powercells.yml # Resources/Prototypes/Entities/Objects/Specific/Cargo/cargo_pallet.yml # Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Projectiles/shotgun.yml # Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml # Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml # Resources/Prototypes/Entities/Stations/base.yml # Resources/Prototypes/Entities/Structures/Machines/nuke.yml # Resources/Prototypes/Reagents/toxins.yml # Resources/Prototypes/Recipes/Construction/Graphs/clothing/clown_banana.yml # Resources/Prototypes/Recipes/Construction/Graphs/machines/computer.yml # Resources/Prototypes/Recipes/Construction/Graphs/mechs/hamtr_construction.yml # Resources/Prototypes/Recipes/Construction/Graphs/mechs/honker_construction.yml # Resources/Prototypes/Recipes/Construction/Graphs/mechs/ripley_construction.yml # Resources/Prototypes/Recipes/Construction/Graphs/mechs/vim_construction.yml # Resources/Prototypes/Recipes/Construction/Graphs/structures/airlock.yml # Resources/Prototypes/Recipes/Construction/Graphs/structures/airlock_clockwork.yml # Resources/Prototypes/Recipes/Construction/Graphs/structures/blast_door.yml # Resources/Prototypes/Recipes/Construction/Graphs/structures/conveyor.yml # Resources/Prototypes/Recipes/Construction/Graphs/structures/firelock.yml # Resources/Prototypes/Recipes/Construction/Graphs/structures/glassbox.yml # Resources/Prototypes/Recipes/Construction/Graphs/structures/secretdoor.yml # Resources/Prototypes/Recipes/Construction/Graphs/structures/shutter.yml # Resources/Prototypes/Recipes/Construction/Graphs/structures/shuttle.yml # Resources/Prototypes/Recipes/Construction/Graphs/structures/windoor.yml # Resources/Prototypes/Recipes/Construction/Graphs/tools/logic_gate.yml # Resources/Prototypes/Recipes/Construction/Graphs/utilities/APC.yml # Resources/Prototypes/Recipes/Construction/Graphs/utilities/air_alarms.yml # Resources/Prototypes/Recipes/Construction/Graphs/utilities/disposal_machines.yml # Resources/Prototypes/Recipes/Construction/Graphs/utilities/intercom.yml # Resources/Prototypes/Recipes/Construction/Graphs/utilities/lighting.yml # Resources/Prototypes/Recipes/Construction/Graphs/utilities/solarpanel.yml # Resources/Prototypes/Recipes/Construction/Graphs/utilities/station_maps.yml # Resources/Prototypes/Recipes/Construction/Graphs/utilities/wallmount_generator.yml # Resources/Prototypes/Recipes/Construction/Graphs/utilities/wallmount_substation.yml # Resources/Prototypes/Recipes/Construction/Graphs/weapons/bladed_hats.yml # Resources/Prototypes/Recipes/Construction/Graphs/weapons/bola.yml # Resources/Prototypes/Recipes/Construction/Graphs/weapons/improvised_arrow.yml # Resources/Prototypes/Recipes/Construction/Graphs/weapons/modular_grenade.yml # Resources/Prototypes/Recipes/Construction/Graphs/weapons/modular_mine.yml # Resources/Prototypes/Recipes/Construction/Graphs/weapons/shiv.yml # Resources/Prototypes/Recipes/Construction/Graphs/weapons/spear.yml # Resources/Prototypes/Recipes/Construction/structures.yml # Resources/Prototypes/Recipes/Crafting/Graphs/bots/cleanbot.yml # Resources/Prototypes/Recipes/Crafting/Graphs/bots/firebot.yml # Resources/Prototypes/Recipes/Crafting/Graphs/bots/honkbot.yml # Resources/Prototypes/Recipes/Crafting/Graphs/bots/medibot.yml # Resources/Prototypes/Recipes/Crafting/Graphs/bots/mimebot.yml # Resources/Prototypes/Recipes/Crafting/Graphs/bots/supplybot.yml # Resources/Prototypes/Recipes/Crafting/Graphs/improvised/firebomb.yml # Resources/Prototypes/Recipes/Crafting/Graphs/improvised/improvised_shotgun.yml # Resources/Prototypes/Recipes/Crafting/Graphs/improvised/improvised_shotgun_shell.yml # Resources/Prototypes/Recipes/Crafting/Graphs/improvised/makeshiftstunprod.yml # Resources/Prototypes/Recipes/Crafting/Graphs/improvised/pipebomb.yml # Resources/Prototypes/Recipes/Crafting/Graphs/improvised/pneumatic_cannon.yml # Resources/Prototypes/Recipes/Crafting/Graphs/improvised/potato.yml # Resources/Prototypes/Recipes/Crafting/Graphs/storage/cratefreezer.yml # Resources/Prototypes/Recipes/Crafting/Graphs/storage/tallbox.yml # Resources/Prototypes/Recipes/Crafting/Graphs/strawhat.yml # Resources/Prototypes/Recipes/Crafting/Graphs/toys.yml # Resources/Prototypes/Recipes/Crafting/improvised.yml # Resources/Prototypes/Recipes/Reactions/medicine.yml # Resources/Prototypes/Roles/Jobs/CentComm/official.yml # Resources/Textures/Clothing/Neck/mantles/cemantle.rsi/equipped-NECK.png # Resources/Textures/Clothing/Neck/mantles/cemantle.rsi/icon.png # Resources/Textures/Clothing/Neck/mantles/cemantle.rsi/meta.json # Resources/Textures/Clothing/Uniforms/Jumpskirt/mime.rsi/equipped-INNERCLOTHING.png # Resources/Textures/Clothing/Uniforms/Jumpskirt/mime.rsi/icon.png # Resources/Textures/Clothing/Uniforms/Jumpskirt/mime.rsi/inhand-left.png # Resources/Textures/Clothing/Uniforms/Jumpskirt/mime.rsi/inhand-right.png # Resources/Textures/Clothing/Uniforms/Jumpskirt/mime.rsi/meta.json # Resources/Textures/Clothing/Uniforms/Jumpsuit/mime.rsi/equipped-INNERCLOTHING.png # Resources/Textures/Clothing/Uniforms/Jumpsuit/mime.rsi/icon.png # Resources/Textures/Clothing/Uniforms/Jumpsuit/mime.rsi/inhand-left.png # Resources/Textures/Clothing/Uniforms/Jumpsuit/mime.rsi/inhand-right.png # Resources/Textures/Clothing/Uniforms/Jumpsuit/mime.rsi/meta.json
This commit is contained in:
commit
95b86d7a4b
776 changed files with 89359 additions and 26409 deletions
7
.github/CODEOWNERS
vendored
7
.github/CODEOWNERS
vendored
|
|
@ -29,13 +29,6 @@
|
|||
/Pow3r/ @PJB3005
|
||||
/Content.Server/Power/Pow3r/ @PJB3005
|
||||
|
||||
# notafet
|
||||
/Content.*/Atmos/ @Partmedia
|
||||
/Content.*/Botany/ @Partmedia
|
||||
|
||||
# Jezi
|
||||
/Content.*/Medical @Jezithyr
|
||||
/Content.*/Body @Jezithyr
|
||||
|
||||
# Sloth
|
||||
/Content.*/Audio @metalgearsloth
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import subprocess
|
|||
import sys
|
||||
import os
|
||||
import shutil
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
|
|
@ -104,7 +105,21 @@ def reset_solution():
|
|||
with SOLUTION_PATH.open("w") as f:
|
||||
f.write(content)
|
||||
|
||||
def check_for_zip_download():
|
||||
# Check if .git exists,
|
||||
cur_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
||||
if not os.path.isdir(os.path.join(cur_dir, ".git")):
|
||||
print("It appears that you downloaded this repository directly from GitHub. (Using the .zip download option) \n"
|
||||
"When downloading straight from GitHub, it leaves out important information that git needs to function. "
|
||||
"Such as information to download the engine or even the ability to even be able to create contributions. \n"
|
||||
"Please read and follow https://docs.spacestation14.com/en/general-development/setup/setting-up-a-development-environment.html \n"
|
||||
"If you just want a Sandbox Server, you are following the wrong guide! You can download a premade server following the instructions here:"
|
||||
"https://docs.spacestation14.com/en/general-development/setup/server-hosting-tutorial.html \n"
|
||||
"Closing automatically in 30 seconds.")
|
||||
time.sleep(30)
|
||||
exit(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
check_for_zip_download()
|
||||
install_hooks()
|
||||
update_submodules()
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using Content.IntegrationTests;
|
|||
using Content.IntegrationTests.Pair;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Warps;
|
||||
using Content.Shared.Warps;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.EntitySerialization;
|
||||
|
|
|
|||
|
|
@ -203,6 +203,7 @@ namespace Content.Client.Actions
|
|||
return;
|
||||
|
||||
OnActionAdded?.Invoke(actionId);
|
||||
ActionsUpdated?.Invoke();
|
||||
}
|
||||
|
||||
protected override void ActionRemoved(EntityUid performer, EntityUid actionId, ActionsComponent comp, BaseActionComponent action)
|
||||
|
|
@ -211,6 +212,7 @@ namespace Content.Client.Actions
|
|||
return;
|
||||
|
||||
OnActionRemoved?.Invoke(actionId);
|
||||
ActionsUpdated?.Invoke();
|
||||
}
|
||||
|
||||
public IEnumerable<(EntityUid Id, BaseActionComponent Comp)> GetClientActions()
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ using System.Linq;
|
|||
using Content.Shared.Alert;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
|
@ -15,6 +16,7 @@ public sealed class ClientAlertsSystem : AlertsSystem
|
|||
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _ui = default!;
|
||||
|
||||
public event EventHandler? ClearAlerts;
|
||||
public event EventHandler<IReadOnlyDictionary<AlertKey, AlertState>>? SyncAlerts;
|
||||
|
|
@ -27,6 +29,12 @@ public sealed class ClientAlertsSystem : AlertsSystem
|
|||
SubscribeLocalEvent<AlertsComponent, LocalPlayerDetachedEvent>(OnPlayerDetached);
|
||||
SubscribeLocalEvent<AlertsComponent, ComponentHandleState>(OnHandleState);
|
||||
}
|
||||
|
||||
protected override void HandledAlert()
|
||||
{
|
||||
_ui.ClickSound();
|
||||
}
|
||||
|
||||
protected override void LoadPrototypes()
|
||||
{
|
||||
base.LoadPrototypes();
|
||||
|
|
@ -52,8 +60,24 @@ public sealed class ClientAlertsSystem : AlertsSystem
|
|||
if (args.Current is not AlertComponentState cast)
|
||||
return;
|
||||
|
||||
// Save all client-sided alerts to later put back in
|
||||
var clientAlerts = new Dictionary<AlertKey, AlertState>();
|
||||
foreach (var alert in alerts.Comp.Alerts)
|
||||
{
|
||||
if (alert.Key.AlertType != null && TryGet(alert.Key.AlertType.Value, out var alertProto))
|
||||
{
|
||||
if (alertProto.ClientHandled)
|
||||
clientAlerts[alert.Key] = alert.Value;
|
||||
}
|
||||
}
|
||||
|
||||
alerts.Comp.Alerts = new(cast.Alerts);
|
||||
|
||||
foreach (var alert in clientAlerts)
|
||||
{
|
||||
alerts.Comp.Alerts[alert.Key] = alert.Value;
|
||||
}
|
||||
|
||||
UpdateHud(alerts);
|
||||
}
|
||||
|
||||
|
|
|
|||
29
Content.Client/Atmos/EntitySystems/GasTankSystem.cs
Normal file
29
Content.Client/Atmos/EntitySystems/GasTankSystem.cs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Atmos.EntitySystems;
|
||||
|
||||
namespace Content.Client.Atmos.EntitySystems;
|
||||
|
||||
public sealed class GasTankSystem : SharedGasTankSystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<GasTankComponent, AfterAutoHandleStateEvent>(OnGasTankState);
|
||||
}
|
||||
|
||||
private void OnGasTankState(Entity<GasTankComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
if (UI.TryGetOpenUi(ent.Owner, SharedGasTankUiKey.Key, out var bui))
|
||||
{
|
||||
bui.Update<GasTankBoundUserInterfaceState>();
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateUserInterface(Entity<GasTankComponent> ent)
|
||||
{
|
||||
if (UI.TryGetOpenUi(ent.Owner, SharedGasTankUiKey.Key, out var bui))
|
||||
{
|
||||
bui.Update<GasTankBoundUserInterfaceState>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
using Content.Client.Atmos.UI;
|
||||
using Content.Shared.Atmos.Piping.Binary.Components;
|
||||
using Content.Shared.Atmos.Piping.Unary.Components;
|
||||
using Content.Shared.Atmos.Piping.Unary.Systems;
|
||||
using Content.Shared.NodeContainer;
|
||||
|
||||
namespace Content.Client.Atmos.Piping.Unary.Systems;
|
||||
|
||||
public sealed class GasCanisterSystem : SharedGasCanisterSystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<GasCanisterComponent, AfterAutoHandleStateEvent>(OnGasState);
|
||||
}
|
||||
|
||||
private void OnGasState(Entity<GasCanisterComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
if (UI.TryGetOpenUi<GasCanisterBoundUserInterface>(ent.Owner, GasCanisterUiKey.Key, out var bui))
|
||||
{
|
||||
bui.Update<GasCanisterBoundUserInterfaceState>();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void DirtyUI(EntityUid uid, GasCanisterComponent? component = null, NodeContainerComponent? nodes = null)
|
||||
{
|
||||
if (UI.TryGetOpenUi<GasCanisterBoundUserInterface>(uid, GasCanisterUiKey.Key, out var bui))
|
||||
{
|
||||
bui.Update<GasCanisterBoundUserInterfaceState>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,7 @@
|
|||
using Content.Shared.Atmos.Piping.Binary.Components;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Atmos.Piping.Binary.Components;
|
||||
using Content.Shared.Atmos.Piping.Unary.Components;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
|
|
@ -32,22 +35,22 @@ namespace Content.Client.Atmos.UI
|
|||
|
||||
private void OnTankEjectPressed()
|
||||
{
|
||||
SendMessage(new GasCanisterHoldingTankEjectMessage());
|
||||
SendPredictedMessage(new GasCanisterHoldingTankEjectMessage());
|
||||
}
|
||||
|
||||
private void OnReleasePressureSet(float value)
|
||||
{
|
||||
SendMessage(new GasCanisterChangeReleasePressureMessage(value));
|
||||
SendPredictedMessage(new GasCanisterChangeReleasePressureMessage(value));
|
||||
}
|
||||
|
||||
private void OnReleaseValveOpenPressed()
|
||||
{
|
||||
SendMessage(new GasCanisterChangeReleaseValveMessage(true));
|
||||
SendPredictedMessage(new GasCanisterChangeReleaseValveMessage(true));
|
||||
}
|
||||
|
||||
private void OnReleaseValveClosePressed()
|
||||
{
|
||||
SendMessage(new GasCanisterChangeReleaseValveMessage(false));
|
||||
SendPredictedMessage(new GasCanisterChangeReleaseValveMessage(false));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -57,17 +60,21 @@ namespace Content.Client.Atmos.UI
|
|||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
if (_window == null || state is not GasCanisterBoundUserInterfaceState cast)
|
||||
if (_window == null || state is not GasCanisterBoundUserInterfaceState cast || !EntMan.TryGetComponent(Owner, out GasCanisterComponent? component))
|
||||
return;
|
||||
|
||||
_window.SetCanisterLabel(cast.CanisterLabel);
|
||||
var canisterLabel = Identity.Name(Owner, EntMan);
|
||||
var tankLabel = component.GasTankSlot.Item != null ? Identity.Name(component.GasTankSlot.Item.Value, EntMan) : null;
|
||||
|
||||
_window.SetCanisterLabel(canisterLabel);
|
||||
_window.SetCanisterPressure(cast.CanisterPressure);
|
||||
_window.SetPortStatus(cast.PortStatus);
|
||||
_window.SetTankLabel(cast.TankLabel);
|
||||
|
||||
_window.SetTankLabel(tankLabel);
|
||||
_window.SetTankPressure(cast.TankPressure);
|
||||
_window.SetReleasePressureRange(cast.ReleasePressureMin, cast.ReleasePressureMax);
|
||||
_window.SetReleasePressure(cast.ReleasePressure);
|
||||
_window.SetReleaseValve(cast.ReleaseValve);
|
||||
_window.SetReleasePressureRange(component.MinReleasePressure, component.MaxReleasePressure);
|
||||
_window.SetReleasePressure(component.ReleasePressure);
|
||||
_window.SetReleaseValve(component.ReleaseValve);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
|
|
|
|||
|
|
@ -13,9 +13,6 @@ namespace Content.Client.Atmos.UI;
|
|||
[UsedImplicitly]
|
||||
public sealed class GasPressurePumpBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
|
||||
{
|
||||
[ViewVariables]
|
||||
private const float MaxPressure = Atmospherics.MaxOutputPressure;
|
||||
|
||||
[ViewVariables]
|
||||
private GasPressurePumpWindow? _window;
|
||||
|
||||
|
|
|
|||
24
Content.Client/Body/Systems/InternalsSystem.cs
Normal file
24
Content.Client/Body/Systems/InternalsSystem.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Systems;
|
||||
|
||||
namespace Content.Client.Body.Systems;
|
||||
|
||||
public sealed class InternalsSystem : SharedInternalsSystem
|
||||
{
|
||||
[Dependency] private readonly SharedUserInterfaceSystem _ui = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<InternalsComponent, AfterAutoHandleStateEvent>(OnInternalsAfterState);
|
||||
}
|
||||
|
||||
private void OnInternalsAfterState(Entity<InternalsComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
if (ent.Comp.GasTankEntity != null && _ui.TryGetOpenUi(ent.Comp.GasTankEntity.Value, SharedGasTankUiKey.Key, out var bui))
|
||||
{
|
||||
bui.Update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -116,7 +116,10 @@ namespace Content.Client.Chemistry.UI
|
|||
("1", ChemMasterReagentAmount.U1, StyleBase.ButtonOpenBoth),
|
||||
("5", ChemMasterReagentAmount.U5, StyleBase.ButtonOpenBoth),
|
||||
("10", ChemMasterReagentAmount.U10, StyleBase.ButtonOpenBoth),
|
||||
("15", ChemMasterReagentAmount.U15, StyleBase.ButtonOpenBoth),
|
||||
("20", ChemMasterReagentAmount.U20, StyleBase.ButtonOpenBoth),
|
||||
("25", ChemMasterReagentAmount.U25, StyleBase.ButtonOpenBoth),
|
||||
("30", ChemMasterReagentAmount.U30, StyleBase.ButtonOpenBoth),
|
||||
("50", ChemMasterReagentAmount.U50, StyleBase.ButtonOpenBoth),
|
||||
("100", ChemMasterReagentAmount.U100, StyleBase.ButtonOpenBoth),
|
||||
(Loc.GetString("chem-master-window-buffer-all-amount"), ChemMasterReagentAmount.All, StyleBase.ButtonOpenLeft),
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
using System.Linq;
|
||||
using Content.Shared.Construction.Prototypes;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Placement;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Construction
|
||||
{
|
||||
|
|
@ -45,7 +47,14 @@ namespace Content.Client.Construction
|
|||
public override void StartHijack(PlacementManager manager)
|
||||
{
|
||||
base.StartHijack(manager);
|
||||
manager.CurrentTextures = _prototype?.Layers.Select(sprite => sprite.DirFrame0()).ToList();
|
||||
|
||||
if (_prototype is null || !_constructionSystem.TryGetRecipePrototype(_prototype.ID, out var targetProtoId))
|
||||
return;
|
||||
|
||||
if (!IoCManager.Resolve<IPrototypeManager>().TryIndex(targetProtoId, out EntityPrototype? proto))
|
||||
return;
|
||||
|
||||
manager.CurrentTextures = SpriteComponent.GetPrototypeTextures(proto, IoCManager.Resolve<IResourceCache>()).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Client.Popups;
|
||||
using Content.Shared.Construction;
|
||||
using Content.Shared.Construction.Prototypes;
|
||||
using Content.Shared.Construction.Steps;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Wall;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
|
|
@ -15,6 +14,7 @@ using Robust.Shared.Input.Binding;
|
|||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Construction
|
||||
{
|
||||
|
|
@ -25,7 +25,6 @@ namespace Content.Client.Construction
|
|||
public sealed class ConstructionSystem : SharedConstructionSystem
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly ExamineSystemShared _examineSystem = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
|
|
@ -33,6 +32,8 @@ namespace Content.Client.Construction
|
|||
private readonly Dictionary<int, EntityUid> _ghosts = new();
|
||||
private readonly Dictionary<string, ConstructionGuide> _guideCache = new();
|
||||
|
||||
private readonly Dictionary<string, string> _recipesMetadataCache = [];
|
||||
|
||||
public bool CraftingEnabled { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -40,6 +41,8 @@ namespace Content.Client.Construction
|
|||
{
|
||||
base.Initialize();
|
||||
|
||||
WarmupRecipesCache();
|
||||
|
||||
UpdatesOutsidePrediction = true;
|
||||
SubscribeLocalEvent<LocalPlayerAttachedEvent>(HandlePlayerAttached);
|
||||
SubscribeNetworkEvent<AckStructureConstructionMessage>(HandleAckStructure);
|
||||
|
|
@ -63,6 +66,77 @@ namespace Content.Client.Construction
|
|||
ClearGhost(component.GhostId);
|
||||
}
|
||||
|
||||
public bool TryGetRecipePrototype(string constructionProtoId, [NotNullWhen(true)] out string? targetProtoId)
|
||||
{
|
||||
if (_recipesMetadataCache.TryGetValue(constructionProtoId, out targetProtoId))
|
||||
return true;
|
||||
|
||||
targetProtoId = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private void WarmupRecipesCache()
|
||||
{
|
||||
foreach (var constructionProto in PrototypeManager.EnumeratePrototypes<ConstructionPrototype>())
|
||||
{
|
||||
if (!PrototypeManager.TryIndex(constructionProto.Graph, out var graphProto))
|
||||
continue;
|
||||
|
||||
if (constructionProto.TargetNode is not { } targetNodeId)
|
||||
continue;
|
||||
|
||||
if (!graphProto.Nodes.TryGetValue(targetNodeId, out var targetNode))
|
||||
continue;
|
||||
|
||||
// Recursion is for wimps.
|
||||
var stack = new Stack<ConstructionGraphNode>();
|
||||
stack.Push(targetNode);
|
||||
|
||||
do
|
||||
{
|
||||
var node = stack.Pop();
|
||||
|
||||
// I never realized if this uid affects anything...
|
||||
// EntityUid? userUid = args.SenderSession.State.ControlledEntity.HasValue
|
||||
// ? GetEntity(args.SenderSession.State.ControlledEntity.Value)
|
||||
// : null;
|
||||
|
||||
// We try to get the id of the target prototype, if it fails, we try going through the edges.
|
||||
if (node.Entity.GetId(null, null, new(EntityManager)) is not { } entityId)
|
||||
{
|
||||
// If the stack is not empty, there is a high probability that the loop will go to infinity.
|
||||
if (stack.Count == 0)
|
||||
{
|
||||
foreach (var edge in node.Edges)
|
||||
{
|
||||
if (graphProto.Nodes.TryGetValue(edge.Target, out var graphNode))
|
||||
stack.Push(graphNode);
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we got the id of the prototype, we exit the “recursion” by clearing the stack.
|
||||
stack.Clear();
|
||||
|
||||
if (!PrototypeManager.TryIndex(constructionProto.ID, out ConstructionPrototype? recipe))
|
||||
continue;
|
||||
|
||||
if (!PrototypeManager.TryIndex(entityId, out var proto))
|
||||
continue;
|
||||
|
||||
var name = recipe.SetName.HasValue ? Loc.GetString(recipe.SetName) : proto.Name;
|
||||
var desc = recipe.SetDescription.HasValue ? Loc.GetString(recipe.SetDescription) : proto.Description;
|
||||
|
||||
recipe.Name = name;
|
||||
recipe.Description = desc;
|
||||
|
||||
_recipesMetadataCache.Add(constructionProto.ID, entityId);
|
||||
} while (stack.Count > 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnConstructionGuideReceived(ResponseConstructionGuide ev)
|
||||
{
|
||||
_guideCache[ev.ConstructionId] = ev.Guide;
|
||||
|
|
@ -88,7 +162,7 @@ namespace Content.Client.Construction
|
|||
|
||||
private void HandleConstructionGhostExamined(EntityUid uid, ConstructionGhostComponent component, ExaminedEvent args)
|
||||
{
|
||||
if (component.Prototype == null)
|
||||
if (component.Prototype?.Name is null)
|
||||
return;
|
||||
|
||||
using (args.PushGroup(nameof(ConstructionGhostComponent)))
|
||||
|
|
@ -97,7 +171,7 @@ namespace Content.Client.Construction
|
|||
"construction-ghost-examine-message",
|
||||
("name", component.Prototype.Name)));
|
||||
|
||||
if (!_prototypeManager.TryIndex(component.Prototype.Graph, out ConstructionGraphPrototype? graph))
|
||||
if (!PrototypeManager.TryIndex(component.Prototype.Graph, out var graph))
|
||||
return;
|
||||
|
||||
var startNode = graph.Nodes[component.Prototype.StartNode];
|
||||
|
|
@ -198,6 +272,9 @@ namespace Content.Client.Construction
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!TryGetRecipePrototype(prototype.ID, out var targetProtoId) || !PrototypeManager.TryIndex(targetProtoId, out EntityPrototype? targetProto))
|
||||
return false;
|
||||
|
||||
if (GhostPresent(loc))
|
||||
return false;
|
||||
|
||||
|
|
@ -214,16 +291,43 @@ namespace Content.Client.Construction
|
|||
comp.GhostId = ghost.GetHashCode();
|
||||
EntityManager.GetComponent<TransformComponent>(ghost.Value).LocalRotation = dir.ToAngle();
|
||||
_ghosts.Add(comp.GhostId, ghost.Value);
|
||||
|
||||
var sprite = EntityManager.GetComponent<SpriteComponent>(ghost.Value);
|
||||
sprite.Color = new Color(48, 255, 48, 128);
|
||||
|
||||
for (int i = 0; i < prototype.Layers.Count; i++)
|
||||
if (targetProto.TryGetComponent(out IconComponent? icon, EntityManager.ComponentFactory))
|
||||
{
|
||||
sprite.AddBlankLayer(i); // There is no way to actually check if this already exists, so we blindly insert a new one
|
||||
sprite.LayerSetSprite(i, prototype.Layers[i]);
|
||||
sprite.LayerSetShader(i, "unshaded");
|
||||
sprite.LayerSetVisible(i, true);
|
||||
sprite.AddBlankLayer(0);
|
||||
sprite.LayerSetSprite(0, icon.Icon);
|
||||
sprite.LayerSetShader(0, "unshaded");
|
||||
sprite.LayerSetVisible(0, true);
|
||||
}
|
||||
else if (targetProto.Components.TryGetValue("Sprite", out _))
|
||||
{
|
||||
var dummy = EntityManager.SpawnEntity(targetProtoId, MapCoordinates.Nullspace);
|
||||
var targetSprite = EntityManager.EnsureComponent<SpriteComponent>(dummy);
|
||||
EntityManager.System<AppearanceSystem>().OnChangeData(dummy, targetSprite);
|
||||
|
||||
for (var i = 0; i < targetSprite.AllLayers.Count(); i++)
|
||||
{
|
||||
if (!targetSprite[i].Visible || !targetSprite[i].RsiState.IsValid)
|
||||
continue;
|
||||
|
||||
var rsi = targetSprite[i].Rsi ?? targetSprite.BaseRSI;
|
||||
if (rsi is null || !rsi.TryGetState(targetSprite[i].RsiState, out var state) ||
|
||||
state.StateId.Name is null)
|
||||
continue;
|
||||
|
||||
sprite.AddBlankLayer(i);
|
||||
sprite.LayerSetSprite(i, new SpriteSpecifier.Rsi(rsi.Path, state.StateId.Name));
|
||||
sprite.LayerSetShader(i, "unshaded");
|
||||
sprite.LayerSetVisible(i, true);
|
||||
}
|
||||
|
||||
EntityManager.DeleteEntity(dummy);
|
||||
}
|
||||
else
|
||||
return false;
|
||||
|
||||
if (prototype.CanBuildInImpassable)
|
||||
EnsureComp<WallMountComponent>(ghost.Value).Arc = new(Math.Tau);
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
<DefaultWindow xmlns="https://spacestation14.io">
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<BoxContainer Orientation="Vertical" MinWidth="243" Margin="0 0 5 0">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="0 0 0 5">
|
||||
<LineEdit Name="SearchBar" PlaceHolder="{ Loc 'construction-menu-search'}" HorizontalExpand="True"/>
|
||||
<LineEdit Name="SearchBar" PlaceHolder="{Loc 'construction-menu-search'}" HorizontalExpand="True"/>
|
||||
<OptionButton Name="OptionCategories" Access="Public" MinSize="130 0"/>
|
||||
</BoxContainer>
|
||||
<ItemList Name="Recipes" Access="Public" SelectMode="Single" VerticalExpand="True"/>
|
||||
<controls:ListContainer Name="Recipes" Access="Public" Group="True" Toggle="True" VerticalExpand="True" />
|
||||
<ScrollContainer Name="RecipesGridScrollContainer" VerticalExpand="True" Access="Public" Visible="False">
|
||||
<GridContainer Name="RecipesGrid" Columns="5" Access="Public"/>
|
||||
</ScrollContainer>
|
||||
|
|
@ -18,7 +19,7 @@
|
|||
<Control>
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" Margin="0 0 0 5">
|
||||
<BoxContainer Orientation="Horizontal" Align="Center">
|
||||
<TextureRect Name="TargetTexture" HorizontalAlignment="Right" Stretch="Keep" Margin="0 0 10 0"/>
|
||||
<EntityPrototypeView Name="TargetTexture" HorizontalAlignment="Right" Stretch="Fill" Margin="0 0 10 0"/>
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<RichTextLabel Name="TargetName"/>
|
||||
<RichTextLabel Name="TargetDesc"/>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,11 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Construction.Prototypes;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Construction.UI
|
||||
{
|
||||
|
|
@ -28,7 +25,7 @@ namespace Content.Client.Construction.UI
|
|||
bool GridViewButtonPressed { get; set; }
|
||||
bool BuildButtonPressed { get; set; }
|
||||
|
||||
ItemList Recipes { get; }
|
||||
ListContainer Recipes { get; }
|
||||
ItemList RecipeStepList { get; }
|
||||
|
||||
|
||||
|
|
@ -36,14 +33,14 @@ namespace Content.Client.Construction.UI
|
|||
GridContainer RecipesGrid { get; }
|
||||
|
||||
event EventHandler<(string search, string catagory)> PopulateRecipes;
|
||||
event EventHandler<ItemList.Item?> RecipeSelected;
|
||||
event EventHandler<ConstructionMenu.ConstructionMenuListData?> RecipeSelected;
|
||||
event EventHandler RecipeFavorited;
|
||||
event EventHandler<bool> BuildButtonToggled;
|
||||
event EventHandler<bool> EraseButtonToggled;
|
||||
event EventHandler ClearAllGhosts;
|
||||
|
||||
void ClearRecipeInfo();
|
||||
void SetRecipeInfo(string Id, Texture iconTexture, bool isItem, bool isFavorite);
|
||||
void SetRecipeInfo(string name, string description, EntityPrototype? targetPrototype, bool isItem, bool isFavorite);
|
||||
void ResetPlacement();
|
||||
|
||||
#region Window Control
|
||||
|
|
@ -94,8 +91,36 @@ namespace Content.Client.Construction.UI
|
|||
Title = Loc.GetString("construction-menu-title");
|
||||
|
||||
BuildButton.Text = Loc.GetString("construction-menu-place-ghost");
|
||||
Recipes.OnItemSelected += obj => RecipeSelected?.Invoke(this, obj.ItemList[obj.ItemIndex]);
|
||||
Recipes.OnItemDeselected += _ => RecipeSelected?.Invoke(this, null);
|
||||
Recipes.ItemPressed += (_, data) => RecipeSelected?.Invoke(this, data as ConstructionMenuListData);
|
||||
Recipes.NoItemSelected += () => RecipeSelected?.Invoke(this, null);
|
||||
Recipes.GenerateItem += (data, button) =>
|
||||
{
|
||||
if (data is not ConstructionMenuListData (var prototype, var targetPrototype))
|
||||
return;
|
||||
|
||||
var entProtoView = new EntityPrototypeView()
|
||||
{
|
||||
SetSize = new(32f),
|
||||
Stretch = SpriteView.StretchMode.Fill,
|
||||
Scale = new(2),
|
||||
Margin = new(0, 2),
|
||||
};
|
||||
entProtoView.SetPrototype(targetPrototype);
|
||||
|
||||
var label = new Label()
|
||||
{
|
||||
Text = prototype.Name,
|
||||
Margin = new(5, 0),
|
||||
};
|
||||
|
||||
var box = new BoxContainer();
|
||||
box.AddChild(entProtoView);
|
||||
box.AddChild(label);
|
||||
|
||||
button.AddChild(box);
|
||||
button.ToolTip = prototype.Description;
|
||||
button.AddStyleClass(ListContainer.StyleClassListContainerButton);
|
||||
};
|
||||
|
||||
SearchBar.OnTextChanged += _ =>
|
||||
PopulateRecipes?.Invoke(this, (SearchBar.Text, Categories[OptionCategories.SelectedId]));
|
||||
|
|
@ -121,7 +146,7 @@ namespace Content.Client.Construction.UI
|
|||
|
||||
public event EventHandler? ClearAllGhosts;
|
||||
public event EventHandler<(string search, string catagory)>? PopulateRecipes;
|
||||
public event EventHandler<ItemList.Item?>? RecipeSelected;
|
||||
public event EventHandler<ConstructionMenuListData?>? RecipeSelected;
|
||||
public event EventHandler? RecipeFavorited;
|
||||
public event EventHandler<bool>? BuildButtonToggled;
|
||||
public event EventHandler<bool>? EraseButtonToggled;
|
||||
|
|
@ -133,13 +158,17 @@ namespace Content.Client.Construction.UI
|
|||
}
|
||||
|
||||
public void SetRecipeInfo(
|
||||
string Id, Texture iconTexture, bool isItem, bool isFavorite)
|
||||
string name,
|
||||
string description,
|
||||
EntityPrototype? targetPrototype,
|
||||
bool isItem,
|
||||
bool isFavorite)
|
||||
{
|
||||
BuildButton.Disabled = false;
|
||||
BuildButton.Text = Loc.GetString(isItem ? "construction-menu-place-ghost" : "construction-menu-craft");
|
||||
TargetName.SetMessage(Loc.GetString($"recipe-{Id}-name"));
|
||||
TargetDesc.SetMessage(Loc.GetString($"recipe-{Id}-description"));
|
||||
TargetTexture.Texture = iconTexture;
|
||||
TargetName.SetMessage(name);
|
||||
TargetDesc.SetMessage(description);
|
||||
TargetTexture.SetPrototype(targetPrototype?.ID);
|
||||
FavoriteButton.Visible = true;
|
||||
FavoriteButton.Text = Loc.GetString(
|
||||
isFavorite ? "construction-add-favorite-button" : "construction-remove-from-favorite-button");
|
||||
|
|
@ -150,9 +179,11 @@ namespace Content.Client.Construction.UI
|
|||
BuildButton.Disabled = true;
|
||||
TargetName.SetMessage(string.Empty);
|
||||
TargetDesc.SetMessage(string.Empty);
|
||||
TargetTexture.Texture = null;
|
||||
TargetTexture.SetPrototype(null);
|
||||
FavoriteButton.Visible = false;
|
||||
RecipeStepList.Clear();
|
||||
}
|
||||
|
||||
public sealed record ConstructionMenuListData(ConstructionPrototype Prototype, EntityPrototype TargetPrototype) : ListData;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,10 +10,8 @@ using Robust.Client.Placement;
|
|||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Prototypes;
|
||||
using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||
|
||||
namespace Content.Client.Construction.UI
|
||||
{
|
||||
|
|
@ -30,18 +28,20 @@ namespace Content.Client.Construction.UI
|
|||
[Dependency] private readonly IPlacementManager _placementManager = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
private readonly SpriteSystem _spriteSystem;
|
||||
|
||||
private readonly IConstructionMenuView _constructionView;
|
||||
private readonly EntityWhitelistSystem _whitelistSystem;
|
||||
private readonly SpriteSystem _spriteSystem;
|
||||
|
||||
private ConstructionSystem? _constructionSystem;
|
||||
private ConstructionPrototype? _selected;
|
||||
private List<ConstructionPrototype> _favoritedRecipes = [];
|
||||
private Dictionary<string, TextureButton> _recipeButtons = new();
|
||||
private Dictionary<string, ContainerButton> _recipeButtons = new();
|
||||
private string _selectedCategory = string.Empty;
|
||||
private string _favoriteCatName = "construction-category-favorites";
|
||||
private string _forAllCategoryName = "construction-category-all";
|
||||
|
||||
private const string FavoriteCatName = "construction-category-favorites";
|
||||
private const string ForAllCategoryName = "construction-category-all";
|
||||
|
||||
private bool CraftingAvailable
|
||||
{
|
||||
get => _uiManager.GetActiveUIWidget<GameTopMenuBar>().CraftingButton.Visible;
|
||||
|
|
@ -98,15 +98,18 @@ namespace Content.Client.Construction.UI
|
|||
|
||||
_placementManager.PlacementChanged += OnPlacementChanged;
|
||||
|
||||
_constructionView.OnClose += () => _uiManager.GetActiveUIWidget<GameTopMenuBar>().CraftingButton.Pressed = false;
|
||||
_constructionView.OnClose +=
|
||||
() => _uiManager.GetActiveUIWidget<GameTopMenuBar>().CraftingButton.Pressed = false;
|
||||
_constructionView.ClearAllGhosts += (_, _) => _constructionSystem?.ClearAllGhosts();
|
||||
_constructionView.PopulateRecipes += OnViewPopulateRecipes;
|
||||
_constructionView.RecipeSelected += OnViewRecipeSelected;
|
||||
_constructionView.BuildButtonToggled += (_, b) => BuildButtonToggled(b);
|
||||
_constructionView.EraseButtonToggled += (_, b) =>
|
||||
{
|
||||
if (_constructionSystem is null) return;
|
||||
if (b) _placementManager.Clear();
|
||||
if (_constructionSystem is null)
|
||||
return;
|
||||
if (b)
|
||||
_placementManager.Clear();
|
||||
_placementManager.ToggleEraserHijacked(new ConstructionPlacementHijack(_constructionSystem, null));
|
||||
_constructionView.EraseButtonPressed = b;
|
||||
};
|
||||
|
|
@ -117,7 +120,7 @@ namespace Content.Client.Construction.UI
|
|||
OnViewPopulateRecipes(_constructionView, (string.Empty, string.Empty));
|
||||
}
|
||||
|
||||
public void OnHudCraftingButtonToggled(ButtonToggledEventArgs args)
|
||||
public void OnHudCraftingButtonToggled(BaseButton.ButtonToggledEventArgs args)
|
||||
{
|
||||
WindowOpen = args.Pressed;
|
||||
}
|
||||
|
|
@ -139,7 +142,7 @@ namespace Content.Client.Construction.UI
|
|||
_constructionView.ResetPlacement();
|
||||
}
|
||||
|
||||
private void OnViewRecipeSelected(object? sender, ItemList.Item? item)
|
||||
private void OnViewRecipeSelected(object? sender, ConstructionMenu.ConstructionMenuListData? item)
|
||||
{
|
||||
if (item is null)
|
||||
{
|
||||
|
|
@ -148,12 +151,15 @@ namespace Content.Client.Construction.UI
|
|||
return;
|
||||
}
|
||||
|
||||
_selected = (ConstructionPrototype) item.Metadata!;
|
||||
if (_placementManager.IsActive && !_placementManager.Eraser) UpdateGhostPlacement();
|
||||
_selected = item.Prototype;
|
||||
|
||||
if (_placementManager is { IsActive: true, Eraser: false })
|
||||
UpdateGhostPlacement();
|
||||
|
||||
PopulateInfo(_selected);
|
||||
}
|
||||
|
||||
private void OnGridViewRecipeSelected(object? sender, ConstructionPrototype? recipe)
|
||||
private void OnGridViewRecipeSelected(object? _, ConstructionPrototype? recipe)
|
||||
{
|
||||
if (recipe is null)
|
||||
{
|
||||
|
|
@ -163,66 +169,21 @@ namespace Content.Client.Construction.UI
|
|||
}
|
||||
|
||||
_selected = recipe;
|
||||
if (_placementManager.IsActive && !_placementManager.Eraser) UpdateGhostPlacement();
|
||||
|
||||
if (_placementManager is { IsActive: true, Eraser: false })
|
||||
UpdateGhostPlacement();
|
||||
|
||||
PopulateInfo(_selected);
|
||||
}
|
||||
|
||||
private void OnViewPopulateRecipes(object? sender, (string search, string catagory) args)
|
||||
{
|
||||
var (search, category) = args;
|
||||
if (_constructionSystem is null)
|
||||
return;
|
||||
|
||||
var recipes = new List<ConstructionPrototype>();
|
||||
|
||||
var isEmptyCategory = string.IsNullOrEmpty(category) || category == _forAllCategoryName;
|
||||
|
||||
if (isEmptyCategory)
|
||||
_selectedCategory = string.Empty;
|
||||
else
|
||||
_selectedCategory = category;
|
||||
|
||||
foreach (var recipe in _prototypeManager.EnumeratePrototypes<ConstructionPrototype>())
|
||||
{
|
||||
if (recipe.Hide)
|
||||
continue;
|
||||
|
||||
if (_playerManager.LocalSession == null
|
||||
|| _playerManager.LocalEntity == null
|
||||
|| _whitelistSystem.IsWhitelistFail(recipe.EntityWhitelist, _playerManager.LocalEntity.Value))
|
||||
continue;
|
||||
|
||||
var recipeName = recipe.Name.ToLowerInvariant();
|
||||
var localizedRecipeName = Loc.GetString($"recipe-{recipe.ID}-name").ToLowerInvariant();
|
||||
|
||||
if (!string.IsNullOrEmpty(search))
|
||||
{
|
||||
var searchLower = search.Trim().ToLowerInvariant();
|
||||
if (!recipeName.Contains(searchLower) && !localizedRecipeName.Contains(searchLower))
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isEmptyCategory)
|
||||
{
|
||||
if (category == _favoriteCatName)
|
||||
{
|
||||
if (!_favoritedRecipes.Contains(recipe))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (recipe.Category != category)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
recipes.Add(recipe);
|
||||
}
|
||||
|
||||
recipes.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.InvariantCulture));
|
||||
var actualRecipes = GetAndSortRecipes(args);
|
||||
|
||||
var recipesList = _constructionView.Recipes;
|
||||
recipesList.Clear();
|
||||
|
||||
var recipesGrid = _constructionView.RecipesGrid;
|
||||
recipesGrid.RemoveAllChildren();
|
||||
|
||||
|
|
@ -231,60 +192,120 @@ namespace Content.Client.Construction.UI
|
|||
|
||||
if (_constructionView.GridViewButtonPressed)
|
||||
{
|
||||
foreach (var recipe in recipes)
|
||||
{
|
||||
var itemButton = new TextureButton
|
||||
{
|
||||
TextureNormal = _spriteSystem.Frame0(recipe.Icon),
|
||||
VerticalAlignment = Control.VAlignment.Center,
|
||||
Name = recipe.Name,
|
||||
ToolTip = recipe.Name,
|
||||
Scale = new Vector2(1.35f),
|
||||
ToggleMode = true,
|
||||
};
|
||||
var itemButtonPanelContainer = new PanelContainer
|
||||
{
|
||||
PanelOverride = new StyleBoxFlat { BackgroundColor = StyleNano.ButtonColorDefault },
|
||||
Children = { itemButton },
|
||||
};
|
||||
|
||||
itemButton.OnToggled += buttonToggledEventArgs =>
|
||||
{
|
||||
SelectGridButton(itemButton, buttonToggledEventArgs.Pressed);
|
||||
|
||||
if (buttonToggledEventArgs.Pressed &&
|
||||
_selected != null &&
|
||||
_recipeButtons.TryGetValue(_selected.Name, out var oldButton))
|
||||
{
|
||||
oldButton.Pressed = false;
|
||||
SelectGridButton(oldButton, false);
|
||||
}
|
||||
|
||||
OnGridViewRecipeSelected(this, buttonToggledEventArgs.Pressed ? recipe : null);
|
||||
};
|
||||
|
||||
recipesGrid.AddChild(itemButtonPanelContainer);
|
||||
_recipeButtons[recipe.Name] = itemButton;
|
||||
var isCurrentButtonSelected = _selected == recipe;
|
||||
itemButton.Pressed = isCurrentButtonSelected;
|
||||
SelectGridButton(itemButton, isCurrentButtonSelected);
|
||||
}
|
||||
recipesList.PopulateList([]);
|
||||
PopulateGrid(recipesGrid, actualRecipes);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var recipe in recipes)
|
||||
{
|
||||
recipesList.Add(GetItem(recipe, recipesList));
|
||||
}
|
||||
recipesList.PopulateList(actualRecipes);
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectGridButton(TextureButton button, bool select)
|
||||
private void PopulateGrid(GridContainer recipesGrid,
|
||||
IEnumerable<ConstructionMenu.ConstructionMenuListData> actualRecipes)
|
||||
{
|
||||
foreach (var recipe in actualRecipes)
|
||||
{
|
||||
var protoView = new EntityPrototypeView()
|
||||
{
|
||||
Scale = new Vector2(1.2f),
|
||||
};
|
||||
protoView.SetPrototype(recipe.TargetPrototype);
|
||||
|
||||
var itemButton = new ContainerButton()
|
||||
{
|
||||
VerticalAlignment = Control.VAlignment.Center,
|
||||
Name = recipe.TargetPrototype.Name,
|
||||
ToolTip = recipe.TargetPrototype.Name,
|
||||
ToggleMode = true,
|
||||
Children = { protoView },
|
||||
};
|
||||
|
||||
var itemButtonPanelContainer = new PanelContainer
|
||||
{
|
||||
PanelOverride = new StyleBoxFlat { BackgroundColor = StyleNano.ButtonColorDefault },
|
||||
Children = { itemButton },
|
||||
};
|
||||
|
||||
itemButton.OnToggled += buttonToggledEventArgs =>
|
||||
{
|
||||
SelectGridButton(itemButton, buttonToggledEventArgs.Pressed);
|
||||
|
||||
if (buttonToggledEventArgs.Pressed &&
|
||||
_selected != null &&
|
||||
_recipeButtons.TryGetValue(_selected.Name!, out var oldButton))
|
||||
{
|
||||
oldButton.Pressed = false;
|
||||
SelectGridButton(oldButton, false);
|
||||
}
|
||||
|
||||
OnGridViewRecipeSelected(this, buttonToggledEventArgs.Pressed ? recipe.Prototype : null);
|
||||
};
|
||||
|
||||
recipesGrid.AddChild(itemButtonPanelContainer);
|
||||
_recipeButtons[recipe.Prototype.Name!] = itemButton;
|
||||
var isCurrentButtonSelected = _selected == recipe.Prototype;
|
||||
itemButton.Pressed = isCurrentButtonSelected;
|
||||
SelectGridButton(itemButton, isCurrentButtonSelected);
|
||||
}
|
||||
}
|
||||
|
||||
private List<ConstructionMenu.ConstructionMenuListData> GetAndSortRecipes((string, string) args)
|
||||
{
|
||||
var recipes = new List<ConstructionMenu.ConstructionMenuListData>();
|
||||
|
||||
var (search, category) = args;
|
||||
var isEmptyCategory = string.IsNullOrEmpty(category) || category == ForAllCategoryName;
|
||||
_selectedCategory = isEmptyCategory ? string.Empty : category;
|
||||
|
||||
foreach (var recipe in _prototypeManager.EnumeratePrototypes<ConstructionPrototype>())
|
||||
{
|
||||
if (recipe.Hide)
|
||||
continue;
|
||||
|
||||
if (_playerManager.LocalSession == null
|
||||
|| _playerManager.LocalEntity == null
|
||||
|| _whitelistSystem.IsWhitelistFail(recipe.EntityWhitelist, _playerManager.LocalEntity.Value))
|
||||
continue;
|
||||
|
||||
if (!string.IsNullOrEmpty(search) && (recipe.Name is { } name &&
|
||||
!name.Contains(search.Trim(),
|
||||
StringComparison.InvariantCultureIgnoreCase)))
|
||||
continue;
|
||||
|
||||
if (!isEmptyCategory)
|
||||
{
|
||||
if ((category != FavoriteCatName || !_favoritedRecipes.Contains(recipe)) &&
|
||||
recipe.Category != category)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_constructionSystem!.TryGetRecipePrototype(recipe.ID, out var targetProtoId))
|
||||
{
|
||||
Logger.Error("Cannot find the target prototype in the recipe cache with the id \"{0}\" of {1}.",
|
||||
recipe.ID,
|
||||
nameof(ConstructionPrototype));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_prototypeManager.TryIndex(targetProtoId, out EntityPrototype? proto))
|
||||
continue;
|
||||
|
||||
recipes.Add(new(recipe, proto));
|
||||
}
|
||||
|
||||
recipes.Sort(
|
||||
(a, b) => string.Compare(a.Prototype.Name, b.Prototype.Name, StringComparison.InvariantCulture));
|
||||
|
||||
return recipes;
|
||||
}
|
||||
|
||||
private void SelectGridButton(BaseButton button, bool select)
|
||||
{
|
||||
if (button.Parent is not PanelContainer buttonPanel)
|
||||
return;
|
||||
|
||||
button.Modulate = select ? Color.Green : Color.White;
|
||||
button.Modulate = select ? Color.Green : Color.Transparent;
|
||||
var buttonColor = select ? StyleNano.ButtonColorDefault : Color.Transparent;
|
||||
buttonPanel.PanelOverride = new StyleBoxFlat { BackgroundColor = buttonColor };
|
||||
}
|
||||
|
|
@ -306,12 +327,12 @@ namespace Content.Client.Construction.UI
|
|||
|
||||
// hard-coded to show all recipes
|
||||
var idx = 0;
|
||||
categoriesArray[idx++] = _forAllCategoryName;
|
||||
categoriesArray[idx++] = ForAllCategoryName;
|
||||
|
||||
// hard-coded to show favorites if it need
|
||||
if (isFavorites)
|
||||
{
|
||||
categoriesArray[idx++] = _favoriteCatName;
|
||||
categoriesArray[idx++] = FavoriteCatName;
|
||||
}
|
||||
|
||||
var sortedProtoCategories = uniqueCategories.OrderBy(Loc.GetString);
|
||||
|
|
@ -329,19 +350,31 @@ namespace Content.Client.Construction.UI
|
|||
|
||||
if (!string.IsNullOrEmpty(selectCategory) && selectCategory == categoriesArray[i])
|
||||
_constructionView.OptionCategories.SelectId(i);
|
||||
|
||||
}
|
||||
|
||||
_constructionView.Categories = categoriesArray;
|
||||
}
|
||||
|
||||
private void PopulateInfo(ConstructionPrototype prototype)
|
||||
private void PopulateInfo(ConstructionPrototype? prototype)
|
||||
{
|
||||
if (_constructionSystem is null)
|
||||
return;
|
||||
|
||||
_constructionView.ClearRecipeInfo();
|
||||
|
||||
if (prototype is null)
|
||||
return;
|
||||
|
||||
if (!_constructionSystem.TryGetRecipePrototype(prototype.ID, out var targetProtoId))
|
||||
return;
|
||||
|
||||
if (!_prototypeManager.TryIndex(targetProtoId, out EntityPrototype? proto))
|
||||
return;
|
||||
|
||||
_constructionView.SetRecipeInfo(
|
||||
prototype.ID,
|
||||
_spriteSystem.Frame0(prototype.Icon),
|
||||
prototype.Name!,
|
||||
prototype.Description!,
|
||||
proto,
|
||||
prototype.Type != ConstructionType.Item,
|
||||
!_favoritedRecipes.Contains(prototype));
|
||||
|
||||
|
|
@ -354,16 +387,17 @@ namespace Content.Client.Construction.UI
|
|||
if (_constructionSystem?.GetGuide(prototype) is not { } guide)
|
||||
return;
|
||||
|
||||
|
||||
foreach (var entry in guide.Entries)
|
||||
{
|
||||
var text = entry.Arguments != null
|
||||
? Loc.GetString(entry.Localization, entry.Arguments) : Loc.GetString(entry.Localization);
|
||||
? Loc.GetString(entry.Localization, entry.Arguments)
|
||||
: Loc.GetString(entry.Localization);
|
||||
|
||||
if (entry.EntryNumber is { } number)
|
||||
{
|
||||
text = Loc.GetString("construction-presenter-step-wrapper",
|
||||
("step-number", number), ("text", text));
|
||||
("step-number", number),
|
||||
("text", text));
|
||||
}
|
||||
|
||||
// The padding needs to be applied regardless of text length... (See PadLeft documentation)
|
||||
|
|
@ -374,23 +408,12 @@ namespace Content.Client.Construction.UI
|
|||
}
|
||||
}
|
||||
|
||||
private ItemList.Item GetItem(ConstructionPrototype recipe, ItemList itemList)
|
||||
{
|
||||
return new(itemList)
|
||||
{
|
||||
Metadata = recipe,
|
||||
Text = Loc.GetString($"recipe-{recipe.ID}-name"),
|
||||
Icon = _spriteSystem.Frame0(recipe.Icon),
|
||||
TooltipEnabled = true,
|
||||
TooltipText = recipe.Description,
|
||||
};
|
||||
}
|
||||
|
||||
private void BuildButtonToggled(bool pressed)
|
||||
{
|
||||
if (pressed)
|
||||
{
|
||||
if (_selected == null) return;
|
||||
if (_selected == null)
|
||||
return;
|
||||
|
||||
// not bound to a construction system
|
||||
if (_constructionSystem is null)
|
||||
|
|
@ -407,10 +430,11 @@ namespace Content.Client.Construction.UI
|
|||
}
|
||||
|
||||
_placementManager.BeginPlacing(new PlacementInformation
|
||||
{
|
||||
IsTile = false,
|
||||
PlacementOption = _selected.PlacementMode
|
||||
}, new ConstructionPlacementHijack(_constructionSystem, _selected));
|
||||
{
|
||||
IsTile = false,
|
||||
PlacementOption = _selected.PlacementMode
|
||||
},
|
||||
new ConstructionPlacementHijack(_constructionSystem, _selected));
|
||||
|
||||
UpdateGhostPlacement();
|
||||
}
|
||||
|
|
@ -434,38 +458,39 @@ namespace Content.Client.Construction.UI
|
|||
var constructSystem = _systemManager.GetEntitySystem<ConstructionSystem>();
|
||||
|
||||
_placementManager.BeginPlacing(new PlacementInformation()
|
||||
{
|
||||
IsTile = false,
|
||||
PlacementOption = _selected.PlacementMode,
|
||||
}, new ConstructionPlacementHijack(constructSystem, _selected));
|
||||
{
|
||||
IsTile = false,
|
||||
PlacementOption = _selected.PlacementMode,
|
||||
},
|
||||
new ConstructionPlacementHijack(constructSystem, _selected));
|
||||
|
||||
_constructionView.BuildButtonPressed = true;
|
||||
}
|
||||
|
||||
private void OnSystemLoaded(object? sender, SystemChangedArgs args)
|
||||
{
|
||||
if (args.System is ConstructionSystem system) SystemBindingChanged(system);
|
||||
if (args.System is ConstructionSystem system)
|
||||
SystemBindingChanged(system);
|
||||
}
|
||||
|
||||
private void OnSystemUnloaded(object? sender, SystemChangedArgs args)
|
||||
{
|
||||
if (args.System is ConstructionSystem) SystemBindingChanged(null);
|
||||
if (args.System is ConstructionSystem)
|
||||
SystemBindingChanged(null);
|
||||
}
|
||||
|
||||
private void OnViewFavoriteRecipe()
|
||||
{
|
||||
if (_selected is not ConstructionPrototype recipe)
|
||||
if (_selected is null)
|
||||
return;
|
||||
|
||||
if (!_favoritedRecipes.Remove(_selected))
|
||||
_favoritedRecipes.Add(_selected);
|
||||
|
||||
if (_selectedCategory == _favoriteCatName)
|
||||
if (_selectedCategory == FavoriteCatName)
|
||||
{
|
||||
if (_favoritedRecipes.Count > 0)
|
||||
OnViewPopulateRecipes(_constructionView, (string.Empty, _favoriteCatName));
|
||||
else
|
||||
OnViewPopulateRecipes(_constructionView, (string.Empty, string.Empty));
|
||||
OnViewPopulateRecipes(_constructionView,
|
||||
_favoritedRecipes.Count > 0 ? (string.Empty, FavoriteCatName) : (string.Empty, string.Empty));
|
||||
}
|
||||
|
||||
PopulateInfo(_selected);
|
||||
|
|
@ -497,6 +522,9 @@ namespace Content.Client.Construction.UI
|
|||
private void BindToSystem(ConstructionSystem system)
|
||||
{
|
||||
_constructionSystem = system;
|
||||
|
||||
OnViewPopulateRecipes(_constructionView, (string.Empty, string.Empty));
|
||||
|
||||
system.ToggleCraftingWindow += SystemOnToggleMenu;
|
||||
system.FlipConstructionPrototype += SystemFlipConstructionPrototype;
|
||||
system.CraftingAvailabilityChanged += SystemCraftingAvailabilityChanged;
|
||||
|
|
@ -538,7 +566,8 @@ namespace Content.Client.Construction.UI
|
|||
if (IsAtFront)
|
||||
{
|
||||
WindowOpen = false;
|
||||
_uiManager.GetActiveUIWidget<GameTopMenuBar>().CraftingButton.SetClickPressed(false); // This does not call CraftingButtonToggled
|
||||
_uiManager.GetActiveUIWidget<GameTopMenuBar>()
|
||||
.CraftingButton.SetClickPressed(false); // This does not call CraftingButtonToggled
|
||||
}
|
||||
else
|
||||
_constructionView.MoveToFront();
|
||||
|
|
@ -546,7 +575,8 @@ namespace Content.Client.Construction.UI
|
|||
else
|
||||
{
|
||||
WindowOpen = true;
|
||||
_uiManager.GetActiveUIWidget<GameTopMenuBar>().CraftingButton.SetClickPressed(true); // This does not call CraftingButtonToggled
|
||||
_uiManager.GetActiveUIWidget<GameTopMenuBar>()
|
||||
.CraftingButton.SetClickPressed(true); // This does not call CraftingButtonToggled
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using Content.Shared.Disposal;
|
|||
using Content.Shared.Disposal.Components;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Client.Disposal.Mailing;
|
||||
|
||||
|
|
@ -70,10 +71,10 @@ public sealed class MailingUnitBoundUserInterface : BoundUserInterface
|
|||
//UnitTag.Text = state.Tag;
|
||||
MailingUnitWindow.Target.Text = entity.Comp.Target;
|
||||
|
||||
MailingUnitWindow.TargetListContainer.Clear();
|
||||
foreach (var target in entity.Comp.TargetList)
|
||||
{
|
||||
MailingUnitWindow.TargetListContainer.AddItem(target);
|
||||
}
|
||||
var entries = entity.Comp.TargetList.Select(target => new ItemList.Item(MailingUnitWindow.TargetListContainer) {
|
||||
Text = target,
|
||||
Selected = target == entity.Comp.Target
|
||||
}).ToList();
|
||||
MailingUnitWindow.TargetListContainer.SetItems(entries);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ namespace Content.Client.Examine
|
|||
public const string StyleClassEntityTooltip = "entity-tooltip";
|
||||
|
||||
private EntityUid _examinedEntity;
|
||||
private EntityUid _lastExaminedEntity;
|
||||
private Popup? _examineTooltipOpen;
|
||||
private ScreenCoordinates _popupPos;
|
||||
private CancellationTokenSource? _requestCancelTokenSource;
|
||||
|
|
@ -416,15 +415,14 @@ namespace Content.Client.Examine
|
|||
if (!IsClientSide(entity))
|
||||
{
|
||||
// Ask server for extra examine info.
|
||||
if (entity != _lastExaminedEntity)
|
||||
unchecked
|
||||
{
|
||||
_idCounter += 1;
|
||||
if (_idCounter == int.MaxValue)
|
||||
_idCounter = 0;
|
||||
}
|
||||
RaiseNetworkEvent(new ExamineSystemMessages.RequestExamineInfoMessage(GetNetEntity(entity), _idCounter, true));
|
||||
}
|
||||
|
||||
RaiseLocalEvent(entity, new ClientExaminedEvent(entity, playerEnt.Value));
|
||||
_lastExaminedEntity = entity;
|
||||
}
|
||||
|
||||
private void CloseTooltip()
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ public sealed partial class TriggerSystem
|
|||
break;
|
||||
case ProximityTriggerVisuals.Active:
|
||||
if (_player.HasRunningAnimation(uid, player, AnimKey)) return;
|
||||
_player.Play(uid, player, _flasherAnimation, AnimKey);
|
||||
_player.Play((uid, player), _flasherAnimation, AnimKey);
|
||||
break;
|
||||
case ProximityTriggerVisuals.Off:
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ public sealed class EyeLerpingSystem : EntitySystem
|
|||
SubscribeLocalEvent<LerpingEyeComponent, LocalPlayerDetachedEvent>(OnDetached);
|
||||
|
||||
UpdatesAfter.Add(typeof(TransformSystem));
|
||||
UpdatesAfter.Add(typeof(PhysicsSystem));
|
||||
UpdatesAfter.Add(typeof(Robust.Client.Physics.PhysicsSystem));
|
||||
UpdatesBefore.Add(typeof(SharedEyeSystem));
|
||||
UpdatesOutsidePrediction = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Numerics;
|
||||
using Content.Client.Physics.Controllers;
|
||||
using Content.Client.PhysicsSystem.Controllers;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.NPC;
|
||||
using Content.Shared.NPC.Events;
|
||||
|
|
|
|||
47
Content.Client/Overlays/BlackAndWhiteOverlay.cs
Normal file
47
Content.Client/Overlays/BlackAndWhiteOverlay.cs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Overlays;
|
||||
|
||||
public sealed partial class BlackAndWhiteOverlay : Overlay
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
public override bool RequestScreenTexture => true;
|
||||
private readonly ShaderInstance _greyscaleShader;
|
||||
|
||||
public BlackAndWhiteOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_greyscaleShader = _prototypeManager.Index<ShaderPrototype>("GreyscaleFullscreen").InstanceUnique();
|
||||
ZIndex = 10; // draw this over the DamageOverlay, RainbowOverlay etc.
|
||||
}
|
||||
|
||||
protected override bool BeforeDraw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (!_entityManager.TryGetComponent(_playerManager.LocalEntity, out EyeComponent? eyeComp))
|
||||
return false;
|
||||
|
||||
if (args.Viewport.Eye != eyeComp.Eye)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (ScreenTexture == null)
|
||||
return;
|
||||
|
||||
var handle = args.WorldHandle;
|
||||
_greyscaleShader.SetParameter("SCREEN_TEXTURE", ScreenTexture);
|
||||
handle.UseShader(_greyscaleShader);
|
||||
handle.DrawRect(args.WorldBounds, Color.White);
|
||||
handle.UseShader(null);
|
||||
}
|
||||
}
|
||||
34
Content.Client/Overlays/BlackAndWhiteOverlaySystem.cs
Normal file
34
Content.Client/Overlays/BlackAndWhiteOverlaySystem.cs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
using Content.Shared.Inventory.Events;
|
||||
using Content.Shared.Overlays;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
|
||||
namespace Content.Client.Overlays;
|
||||
|
||||
public sealed partial class BlackAndWhiteOverlaySystem : EquipmentHudSystem<BlackAndWhiteOverlayComponent>
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlayMan = default!;
|
||||
|
||||
private BlackAndWhiteOverlay _overlay = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_overlay = new();
|
||||
}
|
||||
|
||||
protected override void UpdateInternal(RefreshEquipmentHudEvent<BlackAndWhiteOverlayComponent> component)
|
||||
{
|
||||
base.UpdateInternal(component);
|
||||
|
||||
_overlayMan.AddOverlay(_overlay);
|
||||
}
|
||||
|
||||
protected override void DeactivateInternal()
|
||||
{
|
||||
base.DeactivateInternal();
|
||||
|
||||
_overlayMan.RemoveOverlay(_overlay);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Robust.Client.AutoGenerated;
|
||||
|
|
@ -29,48 +30,47 @@ namespace Content.Client.PDA.Ringer
|
|||
{
|
||||
var input = RingerNoteInputs[i];
|
||||
var index = i;
|
||||
var foo = () => // Prevents unauthorized characters from being entered into the LineEdit
|
||||
{
|
||||
input.Text = input.Text.ToUpper();
|
||||
|
||||
if (!IsNote(input.Text))
|
||||
{
|
||||
input.Text = PreviousNoteInputs[index];
|
||||
}
|
||||
else
|
||||
PreviousNoteInputs[index] = input.Text;
|
||||
|
||||
input.RemoveStyleClass("Caution");
|
||||
};
|
||||
|
||||
input.OnFocusExit += _ => foo();
|
||||
input.OnTextEntered += _ =>
|
||||
{
|
||||
foo();
|
||||
input.CursorPosition = input.Text.Length; // Resets caret position to the end of the typed input
|
||||
};
|
||||
|
||||
input.OnTextChanged += args =>
|
||||
{
|
||||
// Convert to uppercase
|
||||
var upperText = args.Text.ToUpper();
|
||||
if (input.Text.Length <= 0)
|
||||
return;
|
||||
|
||||
// Filter to only valid notes
|
||||
var newText = upperText;
|
||||
if (!IsNote(newText))
|
||||
input.Text = args.Text.ToUpper();
|
||||
|
||||
var isValid = IsNote(input.Text);
|
||||
|
||||
if (!isValid)
|
||||
{
|
||||
newText = PreviousNoteInputs[index];
|
||||
input.Text = PreviousNoteInputs[index];
|
||||
input.AddStyleClass("Caution");
|
||||
}
|
||||
else
|
||||
{
|
||||
PreviousNoteInputs[index] = newText;
|
||||
PreviousNoteInputs[index] = input.Text;
|
||||
input.RemoveStyleClass("Caution");
|
||||
}
|
||||
|
||||
// Only update if there's a change
|
||||
if (newText != input.Text)
|
||||
input.Text = newText;
|
||||
input.CursorPosition = input.Text.Length;
|
||||
};
|
||||
|
||||
input.OnFocusExit += _ =>
|
||||
{
|
||||
if (!IsNote(input.Text))
|
||||
{
|
||||
input.Text = PreviousNoteInputs[index];
|
||||
input.RemoveStyleClass("Caution");
|
||||
}
|
||||
};
|
||||
|
||||
input.OnTextEntered += _ =>
|
||||
{
|
||||
if (!IsNote(input.Text))
|
||||
{
|
||||
input.Text = PreviousNoteInputs[index];
|
||||
input.RemoveStyleClass("Caution");
|
||||
}
|
||||
input.CursorPosition = input.Text.Length;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -86,6 +86,9 @@ namespace Content.Client.PDA.Ringer
|
|||
/// </summary>
|
||||
public static bool IsNote(string input)
|
||||
{
|
||||
if (input.Any(char.IsDigit))
|
||||
return false;
|
||||
|
||||
input = input.Replace("#", "sharp");
|
||||
|
||||
return Enum.TryParse(input, true, out Note _);
|
||||
|
|
|
|||
|
|
@ -3,15 +3,13 @@ using Content.Shared.CCVar;
|
|||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Movement.Pulling.Components;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Physics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Physics.Controllers;
|
||||
namespace Content.Client.PhysicsSystem.Controllers;
|
||||
|
||||
public sealed class MoverController : SharedMoverController
|
||||
{
|
||||
|
|
@ -101,39 +99,13 @@ public sealed class MoverController : SharedMoverController
|
|||
|
||||
private void HandleClientsideMovement(EntityUid player, float frameTime)
|
||||
{
|
||||
if (!MoverQuery.TryGetComponent(player, out var mover) ||
|
||||
!XformQuery.TryGetComponent(player, out var xform))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var physicsUid = player;
|
||||
PhysicsComponent? body;
|
||||
var xformMover = xform;
|
||||
|
||||
if (mover.ToParent && RelayQuery.HasComponent(xform.ParentUid))
|
||||
{
|
||||
if (!PhysicsQuery.TryGetComponent(xform.ParentUid, out body) ||
|
||||
!XformQuery.TryGetComponent(xform.ParentUid, out xformMover))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
physicsUid = xform.ParentUid;
|
||||
}
|
||||
else if (!PhysicsQuery.TryGetComponent(player, out body))
|
||||
if (!MoverQuery.TryGetComponent(player, out var mover))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Server-side should just be handled on its own so we'll just do this shizznit
|
||||
HandleMobMovement(
|
||||
player,
|
||||
mover,
|
||||
physicsUid,
|
||||
body,
|
||||
xformMover,
|
||||
frameTime);
|
||||
HandleMobMovement((player, mover), frameTime);
|
||||
}
|
||||
|
||||
protected override bool CanSound()
|
||||
|
|
|
|||
|
|
@ -66,10 +66,60 @@ namespace Content.Client.Stack
|
|||
if (!_appearanceSystem.TryGetData<bool>(uid, StackVisuals.Hide, out var hidden, args.Component))
|
||||
hidden = false;
|
||||
|
||||
if (comp.LayerFunction != StackLayerFunction.None)
|
||||
ApplyLayerFunction((uid, comp), ref actual, ref maxCount);
|
||||
|
||||
if (comp.IsComposite)
|
||||
_counterSystem.ProcessCompositeSprite(uid, actual, maxCount, comp.LayerStates, hidden, sprite: args.Sprite);
|
||||
else
|
||||
_counterSystem.ProcessOpaqueSprite(uid, comp.BaseLayer, actual, maxCount, comp.LayerStates, hidden, sprite: args.Sprite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adjusts the actual and maxCount to change how stack amounts are displayed.
|
||||
/// </summary>
|
||||
/// <param name="ent">The entity considered.</param>
|
||||
/// <param name="actual">The actual number of items in the stack. Altered depending on the function to run.</param>
|
||||
/// <param name="maxCount">The maximum number of items in the stack. Altered depending on the function to run.</param>
|
||||
/// <returns>Whether or not a function was applied.</returns>
|
||||
private bool ApplyLayerFunction(Entity<StackComponent> ent, ref int actual, ref int maxCount)
|
||||
{
|
||||
switch (ent.Comp.LayerFunction)
|
||||
{
|
||||
case StackLayerFunction.Threshold:
|
||||
if (TryComp<StackLayerThresholdComponent>(ent, out var threshold))
|
||||
{
|
||||
ApplyThreshold(threshold, ref actual, ref maxCount);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// No function applied.
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selects which layer a stack applies based on a list of thresholds.
|
||||
/// Each threshold passed results in the next layer being selected.
|
||||
/// </summary>
|
||||
/// <param name="comp">The threshold parameters to apply.</param>
|
||||
/// <param name="actual">The number of items in the stack. Will be set to the index of the layer to use.</param>
|
||||
/// <param name="maxCount">The maximum possible number of items in the stack. Will be set to the number of selectable layers.</param>
|
||||
private static void ApplyThreshold(StackLayerThresholdComponent comp, ref int actual, ref int maxCount)
|
||||
{
|
||||
// We must stop before we run out of thresholds or layers, whichever's smaller.
|
||||
maxCount = Math.Min(comp.Thresholds.Count + 1, maxCount);
|
||||
var newActual = 0;
|
||||
foreach (var threshold in comp.Thresholds)
|
||||
{
|
||||
//If our value exceeds threshold, the next layer should be displayed.
|
||||
//Note: we must ensure actual <= MaxCount.
|
||||
if (actual >= threshold && newActual < maxCount)
|
||||
newActual++;
|
||||
else
|
||||
break;
|
||||
}
|
||||
actual = newActual;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ namespace Content.Client.Station;
|
|||
/// <summary>
|
||||
/// This handles letting the client know stations are a thing. Only really used by an admin menu.
|
||||
/// </summary>
|
||||
public sealed class StationSystem : EntitySystem
|
||||
public sealed partial class StationSystem : SharedStationSystem
|
||||
{
|
||||
private readonly List<(string Name, NetEntity Entity)> _stations = new();
|
||||
|
||||
|
|
@ -15,11 +15,14 @@ public sealed class StationSystem : EntitySystem
|
|||
/// <remarks>
|
||||
/// I'd have this just invoke an entity query, but we're on the client and the client barely knows about stations.
|
||||
/// </remarks>
|
||||
// TODO: Stations have a global PVS override now, this can probably be changed into a query.
|
||||
public IReadOnlyList<(string Name, NetEntity Entity)> Stations => _stations;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeNetworkEvent<StationsUpdatedEvent>(StationsUpdated);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -68,6 +68,10 @@ namespace Content.Client.Stylesheets
|
|||
public const string StyleClassStorageButton = "storageButton";
|
||||
public const string StyleClassInset = "Inset";
|
||||
|
||||
public const string StyleClassConsoleHeading = "ConsoleHeading";
|
||||
public const string StyleClassConsoleSubHeading = "ConsoleSubHeading";
|
||||
public const string StyleClassConsoleText = "ConsoleText";
|
||||
|
||||
public const string StyleClassSliderRed = "Red";
|
||||
public const string StyleClassSliderGreen = "Green";
|
||||
public const string StyleClassSliderBlue = "Blue";
|
||||
|
|
@ -194,6 +198,10 @@ namespace Content.Client.Stylesheets
|
|||
var notoSansBold18 = resCache.NotoStack(variation: "Bold", size: 18);
|
||||
var notoSansBold20 = resCache.NotoStack(variation: "Bold", size: 20);
|
||||
var notoSansMono = resCache.GetFont("/EngineFonts/NotoSans/NotoSansMono-Regular.ttf", size: 12);
|
||||
var robotoMonoBold11 = resCache.GetFont("/Fonts/RobotoMono/RobotoMono-Bold.ttf", size: 11);
|
||||
var robotoMonoBold12 = resCache.GetFont("/Fonts/RobotoMono/RobotoMono-Bold.ttf", size: 12);
|
||||
var robotoMonoBold14 = resCache.GetFont("/Fonts/RobotoMono/RobotoMono-Bold.ttf", size: 14);
|
||||
|
||||
var windowHeaderTex = resCache.GetTexture("/Textures/Interface/Nano/window_header.png");
|
||||
var windowHeader = new StyleBoxTexture
|
||||
{
|
||||
|
|
@ -428,9 +436,60 @@ namespace Content.Client.Stylesheets
|
|||
};
|
||||
progressBarForeground.SetContentMarginOverride(StyleBox.Margin.Vertical, 14.5f);
|
||||
|
||||
// Monotone (unfilled)
|
||||
var monotoneButton = new StyleBoxTexture
|
||||
{
|
||||
Texture = resCache.GetTexture("/Textures/Interface/Nano/Monotone/monotone_button.svg.96dpi.png"),
|
||||
};
|
||||
monotoneButton.SetPatchMargin(StyleBox.Margin.All, 11);
|
||||
monotoneButton.SetPadding(StyleBox.Margin.All, 1);
|
||||
monotoneButton.SetContentMarginOverride(StyleBox.Margin.Vertical, 2);
|
||||
monotoneButton.SetContentMarginOverride(StyleBox.Margin.Horizontal, 14);
|
||||
|
||||
var monotoneButtonOpenLeft = new StyleBoxTexture(monotoneButton)
|
||||
{
|
||||
Texture = resCache.GetTexture("/Textures/Interface/Nano/Monotone/monotone_button_open_left.svg.96dpi.png"),
|
||||
};
|
||||
|
||||
var monotoneButtonOpenRight = new StyleBoxTexture(monotoneButton)
|
||||
{
|
||||
Texture = resCache.GetTexture("/Textures/Interface/Nano/Monotone/monotone_button_open_right.svg.96dpi.png"),
|
||||
};
|
||||
|
||||
var monotoneButtonOpenBoth = new StyleBoxTexture(monotoneButton)
|
||||
{
|
||||
Texture = resCache.GetTexture("/Textures/Interface/Nano/Monotone/monotone_button_open_both.svg.96dpi.png"),
|
||||
};
|
||||
|
||||
// Monotone (filled)
|
||||
var monotoneFilledButton = new StyleBoxTexture(monotoneButton)
|
||||
{
|
||||
Texture = buttonTex,
|
||||
};
|
||||
|
||||
var monotoneFilledButtonOpenLeft = new StyleBoxTexture(monotoneButton)
|
||||
{
|
||||
Texture = new AtlasTexture(buttonTex, UIBox2.FromDimensions(new Vector2(10, 0), new Vector2(14, 24))),
|
||||
};
|
||||
monotoneFilledButtonOpenLeft.SetPatchMargin(StyleBox.Margin.Left, 0);
|
||||
|
||||
var monotoneFilledButtonOpenRight = new StyleBoxTexture(monotoneButton)
|
||||
{
|
||||
Texture = new AtlasTexture(buttonTex, UIBox2.FromDimensions(new Vector2(0, 0), new Vector2(14, 24))),
|
||||
};
|
||||
monotoneFilledButtonOpenRight.SetPatchMargin(StyleBox.Margin.Right, 0);
|
||||
|
||||
var monotoneFilledButtonOpenBoth = new StyleBoxTexture(monotoneButton)
|
||||
{
|
||||
Texture = new AtlasTexture(buttonTex, UIBox2.FromDimensions(new Vector2(10, 0), new Vector2(3, 24))),
|
||||
};
|
||||
monotoneFilledButtonOpenBoth.SetPatchMargin(StyleBox.Margin.Horizontal, 0);
|
||||
|
||||
// CheckBox
|
||||
var checkBoxTextureChecked = resCache.GetTexture("/Textures/Interface/Nano/checkbox_checked.svg.96dpi.png");
|
||||
var checkBoxTextureUnchecked = resCache.GetTexture("/Textures/Interface/Nano/checkbox_unchecked.svg.96dpi.png");
|
||||
var monotoneCheckBoxTextureChecked = resCache.GetTexture("/Textures/Interface/Nano/Monotone/monotone_checkbox_checked.svg.96dpi.png");
|
||||
var monotoneCheckBoxTextureUnchecked = resCache.GetTexture("/Textures/Interface/Nano/Monotone/monotone_checkbox_unchecked.svg.96dpi.png");
|
||||
|
||||
// Tooltip box
|
||||
var tooltipTexture = resCache.GetTexture("/Textures/Interface/Nano/tooltip.png");
|
||||
|
|
@ -960,6 +1019,17 @@ namespace Content.Client.Stylesheets
|
|||
new StyleProperty(BoxContainer.StylePropertySeparation, 10),
|
||||
}),
|
||||
|
||||
// MonotoneCheckBox
|
||||
new StyleRule(new SelectorElement(typeof(TextureRect), new [] { MonotoneCheckBox.StyleClassMonotoneCheckBox }, null, null), new[]
|
||||
{
|
||||
new StyleProperty(TextureRect.StylePropertyTexture, monotoneCheckBoxTextureUnchecked),
|
||||
}),
|
||||
|
||||
new StyleRule(new SelectorElement(typeof(TextureRect), new [] { MonotoneCheckBox.StyleClassMonotoneCheckBox, CheckBox.StyleClassCheckBoxChecked }, null, null), new[]
|
||||
{
|
||||
new StyleProperty(TextureRect.StylePropertyTexture, monotoneCheckBoxTextureChecked),
|
||||
}),
|
||||
|
||||
// Tooltip
|
||||
new StyleRule(new SelectorElement(typeof(Tooltip), null, null, null), new[]
|
||||
{
|
||||
|
|
@ -1158,6 +1228,22 @@ namespace Content.Client.Stylesheets
|
|||
new StyleProperty(Label.StylePropertyFontColor, Color.DarkGray),
|
||||
}),
|
||||
|
||||
// Console text
|
||||
new StyleRule(new SelectorElement(typeof(Label), new[] {StyleClassConsoleText}, null, null), new[]
|
||||
{
|
||||
new StyleProperty(Label.StylePropertyFont, robotoMonoBold11)
|
||||
}),
|
||||
|
||||
new StyleRule(new SelectorElement(typeof(Label), new[] {StyleClassConsoleSubHeading}, null, null), new[]
|
||||
{
|
||||
new StyleProperty(Label.StylePropertyFont, robotoMonoBold12)
|
||||
}),
|
||||
|
||||
new StyleRule(new SelectorElement(typeof(Label), new[] {StyleClassConsoleHeading}, null, null), new[]
|
||||
{
|
||||
new StyleProperty(Label.StylePropertyFont, robotoMonoBold14)
|
||||
}),
|
||||
|
||||
// Big Button
|
||||
new StyleRule(new SelectorChild(
|
||||
new SelectorElement(typeof(Button), new[] {StyleClassButtonBig}, null, null),
|
||||
|
|
@ -1257,6 +1343,64 @@ namespace Content.Client.Stylesheets
|
|||
new StyleProperty(Label.StylePropertyFont, notoSansDisplayBold14),
|
||||
}),
|
||||
|
||||
// MonotoneButton (unfilled)
|
||||
new StyleRule(
|
||||
new SelectorElement(typeof(MonotoneButton), null, null, null),
|
||||
new[]
|
||||
{
|
||||
new StyleProperty(Button.StylePropertyStyleBox, monotoneButton),
|
||||
}),
|
||||
|
||||
new StyleRule(
|
||||
new SelectorElement(typeof(MonotoneButton), new[] { ButtonOpenLeft }, null, null),
|
||||
new[]
|
||||
{
|
||||
new StyleProperty(Button.StylePropertyStyleBox, monotoneButtonOpenLeft),
|
||||
}),
|
||||
|
||||
new StyleRule(
|
||||
new SelectorElement(typeof(MonotoneButton), new[] { ButtonOpenRight }, null, null),
|
||||
new[]
|
||||
{
|
||||
new StyleProperty(Button.StylePropertyStyleBox, monotoneButtonOpenRight),
|
||||
}),
|
||||
|
||||
new StyleRule(
|
||||
new SelectorElement(typeof(MonotoneButton), new[] { ButtonOpenBoth }, null, null),
|
||||
new[]
|
||||
{
|
||||
new StyleProperty(Button.StylePropertyStyleBox, monotoneButtonOpenBoth),
|
||||
}),
|
||||
|
||||
// MonotoneButton (filled)
|
||||
new StyleRule(
|
||||
new SelectorElement(typeof(MonotoneButton), null, null, new[] { Button.StylePseudoClassPressed }),
|
||||
new[]
|
||||
{
|
||||
new StyleProperty(Button.StylePropertyStyleBox, monotoneFilledButton),
|
||||
}),
|
||||
|
||||
new StyleRule(
|
||||
new SelectorElement(typeof(MonotoneButton), new[] { ButtonOpenLeft }, null, new[] { Button.StylePseudoClassPressed }),
|
||||
new[]
|
||||
{
|
||||
new StyleProperty(Button.StylePropertyStyleBox, monotoneFilledButtonOpenLeft),
|
||||
}),
|
||||
|
||||
new StyleRule(
|
||||
new SelectorElement(typeof(MonotoneButton), new[] { ButtonOpenRight }, null, new[] { Button.StylePseudoClassPressed }),
|
||||
new[]
|
||||
{
|
||||
new StyleProperty(Button.StylePropertyStyleBox, monotoneFilledButtonOpenRight),
|
||||
}),
|
||||
|
||||
new StyleRule(
|
||||
new SelectorElement(typeof(MonotoneButton), new[] { ButtonOpenBoth }, null, new[] { Button.StylePseudoClassPressed }),
|
||||
new[]
|
||||
{
|
||||
new StyleProperty(Button.StylePropertyStyleBox, monotoneFilledButtonOpenBoth),
|
||||
}),
|
||||
|
||||
// NanoHeading
|
||||
|
||||
new StyleRule(
|
||||
|
|
|
|||
11
Content.Client/Teleportation/TeleportLocationsSystem.cs
Normal file
11
Content.Client/Teleportation/TeleportLocationsSystem.cs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
using Content.Shared.Teleportation.Systems;
|
||||
|
||||
namespace Content.Client.Teleportation;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="SharedTeleportLocationsSystem"/>
|
||||
/// </summary>
|
||||
public sealed partial class TeleportLocationsSystem : SharedTeleportLocationsSystem
|
||||
{
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
using Content.Shared.Teleportation;
|
||||
using Content.Shared.Teleportation.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.Teleportation.Ui;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class TeleportLocationsBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[ViewVariables]
|
||||
private TeleportMenu? _menu;
|
||||
|
||||
public TeleportLocationsBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = this.CreateWindow<TeleportMenu>();
|
||||
|
||||
if (!EntMan.TryGetComponent<TeleportLocationsComponent>(Owner, out var teleComp))
|
||||
return;
|
||||
|
||||
_menu.Title = Loc.GetString(teleComp.Name);
|
||||
_menu.Warps = teleComp.AvailableWarps;
|
||||
_menu.AddTeleportButtons();
|
||||
|
||||
_menu.TeleportClicked += (netEnt, pointName) =>
|
||||
{
|
||||
SendPredictedMessage(new TeleportLocationDestinationMessage(netEnt, pointName));
|
||||
};
|
||||
}
|
||||
}
|
||||
10
Content.Client/Teleportation/Ui/TeleportMenu.xaml
Normal file
10
Content.Client/Teleportation/Ui/TeleportMenu.xaml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<DefaultWindow xmlns="https://spacestation14.io" Title = "{Loc 'teleportation-scroll-window-title'}" MinSize="450 450" SetSize="450 450">
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" SizeFlagsStretchRatio="0.4">
|
||||
<LineEdit Name="SearchBar" PlaceHolder="Search" HorizontalExpand="True" Margin="0 4" />
|
||||
<ScrollContainer Name="TeleportScroll" VerticalExpand="True" HorizontalExpand="True" HScrollEnabled="False">
|
||||
<BoxContainer Name="TeleportButtonContainer" Orientation="Vertical" VerticalExpand="True" SeparationOverride="5">
|
||||
<!-- Teleport buttons get added here by code -->
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
74
Content.Client/Teleportation/Ui/TeleportMenu.xaml.cs
Normal file
74
Content.Client/Teleportation/Ui/TeleportMenu.xaml.cs
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
using System.Numerics;
|
||||
using Content.Shared.Teleportation.Components;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
|
||||
namespace Content.Client.Teleportation.Ui;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class TeleportMenu : DefaultWindow
|
||||
{
|
||||
public string SearchText = "";
|
||||
|
||||
public HashSet<TeleportPoint> Warps = new();
|
||||
|
||||
public event Action<NetEntity, string>? TeleportClicked;
|
||||
|
||||
public TeleportMenu()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
SearchBar.OnTextChanged += OnSearchTextChanged;
|
||||
}
|
||||
|
||||
public void AddTeleportButtons()
|
||||
{
|
||||
foreach (var points in Warps)
|
||||
{
|
||||
var name = points.Location;
|
||||
var teleportPoint = points.TelePoint;
|
||||
|
||||
var currentButtonRef = new Button
|
||||
{
|
||||
Text = name,
|
||||
TextAlign = Label.AlignMode.Right,
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
SizeFlagsStretchRatio = 1,
|
||||
MinSize = new Vector2(340, 20),
|
||||
ClipText = true,
|
||||
};
|
||||
|
||||
currentButtonRef.OnPressed += _ => TeleportClicked?.Invoke(teleportPoint, name);
|
||||
currentButtonRef.Visible = ButtonIsVisible(currentButtonRef);
|
||||
|
||||
TeleportButtonContainer.AddChild(currentButtonRef);
|
||||
}
|
||||
}
|
||||
|
||||
private bool ButtonIsVisible(Button button)
|
||||
{
|
||||
return string.IsNullOrEmpty(SearchText) || button.Text == null ||
|
||||
button.Text.Contains(SearchText, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private void UpdateVisibleButtons()
|
||||
{
|
||||
foreach (var child in TeleportButtonContainer.Children)
|
||||
{
|
||||
if (child is Button button)
|
||||
button.Visible = ButtonIsVisible(button);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnSearchTextChanged(LineEdit.LineEditEventArgs args)
|
||||
{
|
||||
SearchText = args.Text;
|
||||
UpdateVisibleButtons();
|
||||
// Very funny it's called TeleportScroll
|
||||
TeleportScroll.SetScrollValue(Vector2.Zero);
|
||||
}
|
||||
}
|
||||
|
|
@ -264,12 +264,6 @@ public class ListContainer : Control
|
|||
_updateChildren = false;
|
||||
|
||||
var toRemove = new Dictionary<ListData, ListContainerButton>(_buttons);
|
||||
foreach (var child in Children.ToArray())
|
||||
{
|
||||
if (child == _vScrollBar)
|
||||
continue;
|
||||
RemoveChild(child);
|
||||
}
|
||||
|
||||
if (_data.Count > 0)
|
||||
{
|
||||
|
|
@ -292,8 +286,9 @@ public class ListContainer : Control
|
|||
|
||||
if (Toggle && data == _selected)
|
||||
button.Pressed = true;
|
||||
AddChild(button);
|
||||
}
|
||||
AddChild(button);
|
||||
button.SetPositionInParent(i - _topIndex);
|
||||
button.Measure(finalSize);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
79
Content.Client/UserInterface/Controls/MonotoneButton.cs
Normal file
79
Content.Client/UserInterface/Controls/MonotoneButton.cs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
using JetBrains.Annotations;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using static Robust.Client.UserInterface.Controls.Label;
|
||||
|
||||
namespace Content.Client.UserInterface.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// A button intended for use with a monotone color palette
|
||||
/// </summary>
|
||||
public sealed class MonotoneButton : ContainerButton
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the color of the label text when the button is pressed.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Color AltTextColor { set; get; } = new Color(0.2f, 0.2f, 0.2f);
|
||||
|
||||
/// <summary>
|
||||
/// The label that holds the button text.
|
||||
/// </summary>
|
||||
public Label Label { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The text displayed by the button.
|
||||
/// </summary>
|
||||
[PublicAPI, ViewVariables]
|
||||
public string? Text { get => Label.Text; set => Label.Text = value; }
|
||||
|
||||
/// <summary>
|
||||
/// How to align the text inside the button.
|
||||
/// </summary>
|
||||
[PublicAPI, ViewVariables]
|
||||
public AlignMode TextAlign { get => Label.Align; set => Label.Align = value; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, the button will allow shrinking and clip text
|
||||
/// to prevent the text from going outside the bounds of the button.
|
||||
/// If false, the minimum size will always fit the contained text.
|
||||
/// </summary>
|
||||
[PublicAPI, ViewVariables]
|
||||
public bool ClipText
|
||||
{
|
||||
get => Label.ClipText;
|
||||
set => Label.ClipText = value;
|
||||
}
|
||||
|
||||
public MonotoneButton()
|
||||
{
|
||||
Label = new Label
|
||||
{
|
||||
StyleClasses = { StyleClassButton }
|
||||
};
|
||||
|
||||
AddChild(Label);
|
||||
UpdateAppearance();
|
||||
}
|
||||
|
||||
private void UpdateAppearance()
|
||||
{
|
||||
// Recolor the label
|
||||
if (Label != null)
|
||||
Label.ModulateSelfOverride = DrawMode == DrawModeEnum.Pressed ? AltTextColor : null;
|
||||
|
||||
// Modulate the button if disabled
|
||||
Modulate = Disabled ? Color.Gray : Color.White;
|
||||
}
|
||||
|
||||
protected override void StylePropertiesChanged()
|
||||
{
|
||||
base.StylePropertiesChanged();
|
||||
UpdateAppearance();
|
||||
}
|
||||
|
||||
protected override void DrawModeChanged()
|
||||
{
|
||||
base.DrawModeChanged();
|
||||
UpdateAppearance();
|
||||
}
|
||||
}
|
||||
24
Content.Client/UserInterface/Controls/MonotoneCheckBox.cs
Normal file
24
Content.Client/UserInterface/Controls/MonotoneCheckBox.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
using Robust.Client.UserInterface.Controls;
|
||||
|
||||
namespace Content.Client.UserInterface.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// A check box intended for use with a monotone color palette
|
||||
/// </summary>
|
||||
public sealed class MonotoneCheckBox : CheckBox
|
||||
{
|
||||
public const string StyleClassMonotoneCheckBox = "monotoneCheckBox";
|
||||
|
||||
public MonotoneCheckBox()
|
||||
{
|
||||
TextureRect.AddStyleClass(StyleClassMonotoneCheckBox);
|
||||
}
|
||||
|
||||
protected override void DrawModeChanged()
|
||||
{
|
||||
base.DrawModeChanged();
|
||||
|
||||
// Appearance modulations
|
||||
Modulate = Disabled ? Color.Gray : Color.White;
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,8 @@ namespace Content.Client.UserInterface.Systems.Alerts.Controls
|
|||
{
|
||||
public sealed class AlertControl : BaseButton
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
public AlertPrototype Alert { get; }
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -33,8 +35,7 @@ namespace Content.Client.UserInterface.Systems.Alerts.Controls
|
|||
private (TimeSpan Start, TimeSpan End)? _cooldown;
|
||||
|
||||
private short? _severity;
|
||||
private readonly IGameTiming _gameTiming;
|
||||
private readonly IEntityManager _entityManager;
|
||||
|
||||
private readonly SpriteView _icon;
|
||||
private readonly CooldownGraphic _cooldownGraphic;
|
||||
|
||||
|
|
@ -47,8 +48,10 @@ namespace Content.Client.UserInterface.Systems.Alerts.Controls
|
|||
/// <param name="severity">severity of alert, null if alert doesn't have severity levels</param>
|
||||
public AlertControl(AlertPrototype alert, short? severity)
|
||||
{
|
||||
_gameTiming = IoCManager.Resolve<IGameTiming>();
|
||||
_entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
// Alerts will handle this.
|
||||
MuteSounds = true;
|
||||
|
||||
IoCManager.InjectDependencies(this);
|
||||
TooltipSupplier = SupplyTooltip;
|
||||
Alert = alert;
|
||||
_severity = severity;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Atmos.EntitySystems;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
|
|
@ -17,7 +18,7 @@ namespace Content.Client.UserInterface.Systems.Atmos.GasTank
|
|||
|
||||
public void SetOutputPressure(float value)
|
||||
{
|
||||
SendMessage(new GasTankSetPressureMessage
|
||||
SendPredictedMessage(new GasTankSetPressureMessage
|
||||
{
|
||||
Pressure = value
|
||||
});
|
||||
|
|
@ -25,13 +26,14 @@ namespace Content.Client.UserInterface.Systems.Atmos.GasTank
|
|||
|
||||
public void ToggleInternals()
|
||||
{
|
||||
SendMessage(new GasTankToggleInternalsMessage());
|
||||
SendPredictedMessage(new GasTankToggleInternalsMessage());
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
_window = this.CreateWindow<GasTankWindow>();
|
||||
_window.Entity = Owner;
|
||||
_window.SetTitle(EntMan.GetComponent<MetaDataComponent>(Owner).EntityName);
|
||||
_window.OnOutputPressure += SetOutputPressure;
|
||||
_window.OnToggleInternals += ToggleInternals;
|
||||
|
|
@ -41,6 +43,12 @@ namespace Content.Client.UserInterface.Systems.Atmos.GasTank
|
|||
{
|
||||
base.UpdateState(state);
|
||||
|
||||
if (EntMan.TryGetComponent(Owner, out GasTankComponent? component))
|
||||
{
|
||||
var canConnect = EntMan.System<SharedGasTankSystem>().CanConnectToInternals((Owner, component));
|
||||
_window?.Update(canConnect, component.IsConnected, component.OutputPressure);
|
||||
}
|
||||
|
||||
if (state is GasTankBoundUserInterfaceState cast)
|
||||
_window?.UpdateState(cast);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,11 +3,14 @@ using Content.Client.Message;
|
|||
using Content.Client.Resources;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Atmos.EntitySystems;
|
||||
using Content.Shared.Timing;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Timing;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Content.Client.UserInterface.Systems.Atmos.GasTank;
|
||||
|
|
@ -15,6 +18,7 @@ namespace Content.Client.UserInterface.Systems.Atmos.GasTank;
|
|||
public sealed class GasTankWindow
|
||||
: BaseWindow
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IResourceCache _cache = default!;
|
||||
|
||||
private readonly RichTextLabel _lblPressure;
|
||||
|
|
@ -23,6 +27,8 @@ public sealed class GasTankWindow
|
|||
private readonly Button _btnInternals;
|
||||
private readonly Label _topLabel;
|
||||
|
||||
public EntityUid Entity;
|
||||
|
||||
public event Action<float>? OnOutputPressure;
|
||||
public event Action? OnToggleInternals;
|
||||
|
||||
|
|
@ -194,12 +200,30 @@ public sealed class GasTankWindow
|
|||
public void UpdateState(GasTankBoundUserInterfaceState state)
|
||||
{
|
||||
_lblPressure.SetMarkup(Loc.GetString("gas-tank-window-tank-pressure-text", ("tankPressure", $"{state.TankPressure:0.##}")));
|
||||
_btnInternals.Disabled = !state.CanConnectInternals;
|
||||
}
|
||||
|
||||
public void Update(bool canConnectInternals, bool internalsConnected, float outputPressure)
|
||||
{
|
||||
_btnInternals.Disabled = !canConnectInternals;
|
||||
_lblInternals.SetMarkup(Loc.GetString("gas-tank-window-internal-text",
|
||||
("status", Loc.GetString(state.InternalsConnected ? "gas-tank-window-internal-connected" : "gas-tank-window-internal-disconnected"))));
|
||||
if (state.OutputPressure.HasValue)
|
||||
("status", Loc.GetString(internalsConnected ? "gas-tank-window-internal-connected" : "gas-tank-window-internal-disconnected"))));
|
||||
_spbPressure.Value = outputPressure;
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
// Easier than managing state on any ent changes. Previously this was just ticked on server's GasTankSystem.
|
||||
if (_entManager.TryGetComponent(Entity, out GasTankComponent? tank))
|
||||
{
|
||||
_spbPressure.Value = state.OutputPressure.Value;
|
||||
var canConnectInternals = _entManager.System<SharedGasTankSystem>().CanConnectToInternals((Entity, tank));
|
||||
_btnInternals.Disabled = !canConnectInternals;
|
||||
}
|
||||
|
||||
if (!_btnInternals.Disabled)
|
||||
{
|
||||
_btnInternals.Disabled = _entManager.System<UseDelaySystem>().IsDelayed(Entity, id: SharedGasTankSystem.GasTankDelay);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ using Robust.Client.UserInterface;
|
|||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.VendingMachines.UI
|
||||
{
|
||||
|
|
@ -163,7 +164,9 @@ namespace Content.Client.VendingMachines.UI
|
|||
continue;
|
||||
|
||||
var dummy = _dummies[proto];
|
||||
var amount = cachedInventory.First(o => o.ID == proto).Amount;
|
||||
if (!cachedInventory.TryFirstOrDefault(o => o.ID == proto, out var entry))
|
||||
continue;
|
||||
var amount = entry.Amount;
|
||||
// Could be better? Problem is all inventory entries get squashed.
|
||||
var text = GetItemText(dummy, amount);
|
||||
|
||||
|
|
|
|||
|
|
@ -219,6 +219,7 @@ public sealed class CargoTest
|
|||
|
||||
- type: stack
|
||||
id: StackProto
|
||||
name: stack-steel
|
||||
spawn: A
|
||||
|
||||
- type: entity
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ public sealed class NukeOpsTest
|
|||
}
|
||||
|
||||
Assert.That(!entMan.EntityExists(nukieStationEnt)); // its not supposed to be a station!
|
||||
Assert.That(server.MapMan.MapExists(gridsRule.Map));
|
||||
Assert.That(mapSys.MapExists(gridsRule.Map));
|
||||
var nukieMap = mapSys.GetMap(gridsRule.Map!.Value);
|
||||
|
||||
var targetStation = entMan.GetComponent<StationDataComponent>(ruleComp.TargetStation!.Value);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using Content.Server.Power.Components;
|
|||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Server.Power.Nodes;
|
||||
using Content.Shared.Coordinates;
|
||||
using Content.Shared.NodeContainer;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
|
|
|||
|
|
@ -182,9 +182,9 @@ namespace Content.IntegrationTests.Tests
|
|||
var server = pair.Server;
|
||||
await server.WaitIdleAsync();
|
||||
|
||||
var mapManager = server.ResolveDependency<IMapManager>();
|
||||
var entityManager = server.ResolveDependency<IEntityManager>();
|
||||
var entitySystemManager = server.ResolveDependency<IEntitySystemManager>();
|
||||
var mapSystem = server.System<SharedMapSystem>();
|
||||
|
||||
EntityUid packageRight;
|
||||
EntityUid packageWrong;
|
||||
|
|
@ -255,7 +255,7 @@ namespace Content.IntegrationTests.Tests
|
|||
Assert.That(systemMachine.GetAvailableInventory(machine, machineComponent), Has.Count.GreaterThan(0),
|
||||
"Machine available inventory count is not greater than zero after restock.");
|
||||
|
||||
mapManager.DeleteMap(testMap.MapId);
|
||||
mapSystem.DeleteMap(testMap.MapId);
|
||||
});
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
|
|
@ -269,9 +269,9 @@ namespace Content.IntegrationTests.Tests
|
|||
await server.WaitIdleAsync();
|
||||
|
||||
var prototypeManager = server.ResolveDependency<IPrototypeManager>();
|
||||
var mapManager = server.ResolveDependency<IMapManager>();
|
||||
var entityManager = server.ResolveDependency<IEntityManager>();
|
||||
var entitySystemManager = server.ResolveDependency<IEntitySystemManager>();
|
||||
var mapSystem = server.System<SharedMapSystem>();
|
||||
|
||||
var damageableSystem = entitySystemManager.GetEntitySystem<DamageableSystem>();
|
||||
|
||||
|
|
@ -319,7 +319,7 @@ namespace Content.IntegrationTests.Tests
|
|||
Assert.That(totalRamen, Is.EqualTo(2),
|
||||
"Did not find enough ramen after destroying restock box.");
|
||||
|
||||
mapManager.DeleteMap(testMap.MapId);
|
||||
mapSystem.DeleteMap(testMap.MapId);
|
||||
});
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
|
|
|
|||
|
|
@ -197,7 +197,7 @@ public sealed class AccessOverriderSystem : SharedAccessOverriderSystem
|
|||
if (!PrivilegedIdIsAuthorized(uid, component))
|
||||
return;
|
||||
|
||||
if (!_interactionSystem.InRangeUnobstructed(uid, component.TargetAccessReaderId))
|
||||
if (!_interactionSystem.InRangeUnobstructed(player, component.TargetAccessReaderId))
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("access-overrider-out-of-range"), player, player);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using Content.Shared.Interaction;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
|
||||
namespace Content.Server.Actions;
|
||||
|
||||
|
|
@ -20,8 +19,10 @@ namespace Content.Server.Actions;
|
|||
[RegisterComponent]
|
||||
public sealed partial class ActionOnInteractComponent : Component
|
||||
{
|
||||
[DataField(required:true)]
|
||||
[DataField(required: true)]
|
||||
public List<EntProtoId>? Actions;
|
||||
|
||||
[DataField] public List<EntityUid>? ActionEntities;
|
||||
|
||||
[DataField] public bool RequiresCharge;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
using System.Linq;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Charges.Components;
|
||||
using Content.Shared.Charges.Systems;
|
||||
using Content.Shared.Interaction;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
|
@ -15,6 +17,7 @@ public sealed class ActionOnInteractSystem : EntitySystem
|
|||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly ActionContainerSystem _actionContainer = default!;
|
||||
[Dependency] private readonly SharedChargesSystem _charges = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
|
|
@ -54,6 +57,9 @@ public sealed class ActionOnInteractSystem : EntitySystem
|
|||
if (options.Count == 0)
|
||||
return;
|
||||
|
||||
if (!TryUseCharge((uid, component)))
|
||||
return;
|
||||
|
||||
var (actId, act) = _random.Pick(options);
|
||||
_actions.PerformAction(args.User, null, actId, act, act.Event, _timing.CurTime, false);
|
||||
args.Handled = true;
|
||||
|
|
@ -85,6 +91,9 @@ public sealed class ActionOnInteractSystem : EntitySystem
|
|||
|
||||
if (entOptions.Count > 0)
|
||||
{
|
||||
if (!TryUseCharge((uid, component)))
|
||||
return;
|
||||
|
||||
var (entActId, entAct) = _random.Pick(entOptions);
|
||||
if (entAct.Event != null)
|
||||
{
|
||||
|
|
@ -108,6 +117,9 @@ public sealed class ActionOnInteractSystem : EntitySystem
|
|||
|
||||
if (entWorldOptions.Count > 0)
|
||||
{
|
||||
if (!TryUseCharge((uid, component)))
|
||||
return;
|
||||
|
||||
var (entActId, entAct) = _random.Pick(entWorldOptions);
|
||||
if (entAct.Event != null)
|
||||
{
|
||||
|
|
@ -132,6 +144,9 @@ public sealed class ActionOnInteractSystem : EntitySystem
|
|||
if (options.Count == 0)
|
||||
return;
|
||||
|
||||
if (!TryUseCharge((uid, component)))
|
||||
return;
|
||||
|
||||
var (actId, act) = _random.Pick(options);
|
||||
if (act.Event != null)
|
||||
{
|
||||
|
|
@ -163,4 +178,17 @@ public sealed class ActionOnInteractSystem : EntitySystem
|
|||
|
||||
return valid;
|
||||
}
|
||||
|
||||
private bool TryUseCharge(Entity<ActionOnInteractComponent> ent)
|
||||
{
|
||||
if (!ent.Comp.RequiresCharge)
|
||||
return true;
|
||||
|
||||
Entity<LimitedChargesComponent?> charges = ent.Owner;
|
||||
if (_charges.IsEmpty(charges))
|
||||
return false;
|
||||
|
||||
_charges.TryUseCharge(charges);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using Content.Server.Warps;
|
|||
using Content.Shared.Administration;
|
||||
using Content.Shared.Follower;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Warps;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Map;
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ using Content.Shared.Access.Components;
|
|||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Construction.Components;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ using Content.Server.Chat.Managers;
|
|||
using Content.Server.Explosion.EntitySystems;
|
||||
using Content.Server.NodeContainer.NodeGroups;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Shared.NodeContainer;
|
||||
using Content.Shared.NodeContainer.NodeGroups;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Random;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ using Content.Shared.Ame.Components;
|
|||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.NodeContainer;
|
||||
using Content.Shared.Power;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
using Content.Shared.Inventory;
|
||||
|
||||
namespace Content.Server.Atmos.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Used in internals as breath tool.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[ComponentProtoName("BreathMask")]
|
||||
public sealed partial class BreathToolComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Tool is functional only in allowed slots
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SlotFlags AllowedSlots = SlotFlags.MASK | SlotFlags.HEAD;
|
||||
public bool IsFunctional;
|
||||
|
||||
public EntityUid? ConnectedInternalsEntity;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,121 +0,0 @@
|
|||
using Content.Shared.Atmos;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Atmos.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed partial class GasTankComponent : Component, IGasMixtureHolder
|
||||
{
|
||||
public const float MaxExplosionRange = 26f;
|
||||
private const float DefaultLowPressure = 0f;
|
||||
private const float DefaultOutputPressure = Atmospherics.OneAtmosphere;
|
||||
|
||||
public int Integrity = 3;
|
||||
public bool IsLowPressure => (Air?.Pressure ?? 0F) <= TankLowPressure;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("ruptureSound")]
|
||||
public SoundSpecifier RuptureSound = new SoundPathSpecifier("/Audio/Effects/spray.ogg");
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("connectSound")]
|
||||
public SoundSpecifier? ConnectSound =
|
||||
new SoundPathSpecifier("/Audio/Effects/internals.ogg")
|
||||
{
|
||||
Params = AudioParams.Default.WithVolume(5f),
|
||||
};
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("disconnectSound")]
|
||||
public SoundSpecifier? DisconnectSound;
|
||||
|
||||
// Cancel toggles sounds if we re-toggle again.
|
||||
|
||||
public EntityUid? ConnectStream;
|
||||
public EntityUid? DisconnectStream;
|
||||
|
||||
[DataField("air"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public GasMixture Air { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Pressure at which tank should be considered 'low' such as for internals.
|
||||
/// </summary>
|
||||
[DataField("tankLowPressure"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public float TankLowPressure = DefaultLowPressure;
|
||||
|
||||
/// <summary>
|
||||
/// Distributed pressure.
|
||||
/// </summary>
|
||||
[DataField("outputPressure"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public float OutputPressure = DefaultOutputPressure;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum allowed output pressure.
|
||||
/// </summary>
|
||||
[DataField("maxOutputPressure"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public float MaxOutputPressure = 3 * DefaultOutputPressure;
|
||||
|
||||
/// <summary>
|
||||
/// Tank is connected to internals.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public bool IsConnected => User != null;
|
||||
|
||||
[ViewVariables]
|
||||
public EntityUid? User;
|
||||
|
||||
/// <summary>
|
||||
/// True if this entity was recently moved out of a container. This might have been a hand -> inventory
|
||||
/// transfer, or it might have been the user dropping the tank. This indicates the tank needs to be checked.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public bool CheckUser;
|
||||
|
||||
/// <summary>
|
||||
/// Pressure at which tanks start leaking.
|
||||
/// </summary>
|
||||
[DataField("tankLeakPressure"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public float TankLeakPressure = 30 * Atmospherics.OneAtmosphere;
|
||||
|
||||
/// <summary>
|
||||
/// Pressure at which tank spills all contents into atmosphere.
|
||||
/// </summary>
|
||||
[DataField("tankRupturePressure"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public float TankRupturePressure = 40 * Atmospherics.OneAtmosphere;
|
||||
|
||||
/// <summary>
|
||||
/// Base 3x3 explosion.
|
||||
/// </summary>
|
||||
[DataField("tankFragmentPressure"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public float TankFragmentPressure = 50 * Atmospherics.OneAtmosphere;
|
||||
|
||||
/// <summary>
|
||||
/// Increases explosion for each scale kPa above threshold.
|
||||
/// </summary>
|
||||
[DataField("tankFragmentScale"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public float TankFragmentScale = 2 * Atmospherics.OneAtmosphere;
|
||||
|
||||
[DataField("toggleAction", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string ToggleAction = "ActionToggleInternals";
|
||||
|
||||
[DataField("toggleActionEntity")] public EntityUid? ToggleActionEntity;
|
||||
|
||||
/// <summary>
|
||||
/// Valve to release gas from tank
|
||||
/// </summary>
|
||||
[DataField("isValveOpen"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool IsValveOpen = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gas release rate in L/s
|
||||
/// </summary>
|
||||
[DataField("valveOutputRate"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public float ValveOutputRate = 100f;
|
||||
|
||||
[DataField("valveSound"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public SoundSpecifier ValveSound =
|
||||
new SoundCollectionSpecifier("valveSqueak")
|
||||
{
|
||||
Params = AudioParams.Default.WithVolume(-5f),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ using Robust.Shared.Timing;
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Shared.DeviceNetwork.Components;
|
||||
using Content.Shared.NodeContainer;
|
||||
|
||||
namespace Content.Server.Atmos.Consoles;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Body.Components;
|
||||
|
||||
namespace Content.Server.Atmos.EntitySystems;
|
||||
|
||||
public sealed partial class AtmosphereSystem
|
||||
{
|
||||
private void InitializeBreathTool()
|
||||
{
|
||||
SubscribeLocalEvent<BreathToolComponent, ComponentShutdown>(OnBreathToolShutdown);
|
||||
}
|
||||
|
||||
private void OnBreathToolShutdown(Entity<BreathToolComponent> entity, ref ComponentShutdown args)
|
||||
{
|
||||
DisconnectInternals(entity);
|
||||
}
|
||||
|
||||
public void DisconnectInternals(Entity<BreathToolComponent> entity)
|
||||
{
|
||||
var old = entity.Comp.ConnectedInternalsEntity;
|
||||
entity.Comp.ConnectedInternalsEntity = null;
|
||||
|
||||
if (TryComp<InternalsComponent>(old, out var internalsComponent))
|
||||
{
|
||||
_internals.DisconnectBreathTool((old.Value, internalsComponent), entity.Owner);
|
||||
}
|
||||
|
||||
entity.Comp.IsFunctional = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -36,7 +36,7 @@ public sealed partial class AtmosphereSystem
|
|||
return;
|
||||
}
|
||||
|
||||
var mixtures = new GasMixture[8];
|
||||
var mixtures = new GasMixture[9];
|
||||
for (var i = 0; i < mixtures.Length; i++)
|
||||
mixtures[i] = new GasMixture(Atmospherics.CellVolume) { Temperature = Atmospherics.T20C };
|
||||
|
||||
|
|
@ -68,6 +68,10 @@ public sealed partial class AtmosphereSystem
|
|||
// 7: Nitrogen (101kpa) for vox rooms
|
||||
mixtures[7].AdjustMoles(Gas.Nitrogen, Atmospherics.MolesCellStandard);
|
||||
|
||||
// 8: Air (GM)
|
||||
mixtures[8].AdjustMoles(Gas.Oxygen, Atmospherics.OxygenMolesGasMiner);
|
||||
mixtures[8].AdjustMoles(Gas.Nitrogen, Atmospherics.NitrogenMolesGasMiner);
|
||||
|
||||
foreach (var arg in args)
|
||||
{
|
||||
if (!NetEntity.TryParse(arg, out var netEntity) || !TryGetEntity(netEntity, out var euid))
|
||||
|
|
|
|||
|
|
@ -44,8 +44,6 @@ public sealed partial class AtmosphereSystem
|
|||
|
||||
private void OnGridAtmosphereInit(EntityUid uid, GridAtmosphereComponent component, ComponentInit args)
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
EnsureComp<GasTileOverlayComponent>(uid);
|
||||
foreach (var tile in component.Tiles.Values)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
|
|||
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLog = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly InternalsSystem _internals = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containers = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly GasTileOverlaySystem _gasTileOverlaySystem = default!;
|
||||
|
|
@ -56,7 +55,6 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
|
|||
|
||||
UpdatesAfter.Add(typeof(NodeGroupSystem));
|
||||
|
||||
InitializeBreathTool();
|
||||
InitializeGases();
|
||||
InitializeCommands();
|
||||
InitializeCVars();
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using Content.Shared.Atmos;
|
|||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.NodeContainer;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using static Content.Shared.Atmos.Components.GasAnalyzerComponent;
|
||||
|
|
|
|||
|
|
@ -1,21 +1,13 @@
|
|||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Server.Cargo.Systems;
|
||||
using Content.Server.Explosion.EntitySystems;
|
||||
using Content.Shared.UserInterface;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Atmos.EntitySystems;
|
||||
using Content.Shared.Throwing;
|
||||
using Content.Shared.Toggleable;
|
||||
using Content.Shared.Verbs;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Configuration;
|
||||
using Content.Shared.CCVar;
|
||||
|
|
@ -23,14 +15,11 @@ using Content.Shared.CCVar;
|
|||
namespace Content.Server.Atmos.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class GasTankSystem : EntitySystem
|
||||
public sealed class GasTankSystem : SharedGasTankSystem
|
||||
{
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
[Dependency] private readonly ExplosionSystem _explosions = default!;
|
||||
[Dependency] private readonly InternalsSystem _internals = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audioSys = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containers = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _ui = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly ThrowingSystem _throwing = default!;
|
||||
|
|
@ -44,17 +33,9 @@ namespace Content.Server.Atmos.EntitySystems
|
|||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<GasTankComponent, ComponentShutdown>(OnGasShutdown);
|
||||
SubscribeLocalEvent<GasTankComponent, BeforeActivatableUIOpenEvent>(BeforeUiOpen);
|
||||
SubscribeLocalEvent<GasTankComponent, GetItemActionsEvent>(OnGetActions);
|
||||
SubscribeLocalEvent<GasTankComponent, ExaminedEvent>(OnExamined);
|
||||
SubscribeLocalEvent<GasTankComponent, ToggleActionEvent>(OnActionToggle);
|
||||
SubscribeLocalEvent<GasTankComponent, EntParentChangedMessage>(OnParentChange);
|
||||
SubscribeLocalEvent<GasTankComponent, GasTankSetPressureMessage>(OnGasTankSetPressure);
|
||||
SubscribeLocalEvent<GasTankComponent, GasTankToggleInternalsMessage>(OnGasTankToggleInternals);
|
||||
SubscribeLocalEvent<GasTankComponent, GasAnalyzerScanEvent>(OnAnalyzed);
|
||||
SubscribeLocalEvent<GasTankComponent, PriceCalculationEvent>(OnGasTankPrice);
|
||||
SubscribeLocalEvent<GasTankComponent, GetVerbsEvent<AlternativeVerb>>(OnGetAlternativeVerb);
|
||||
Subs.CVar(_cfg, CCVars.AtmosTankFragment, UpdateMaxRange, true);
|
||||
}
|
||||
|
||||
|
|
@ -63,44 +44,16 @@ namespace Content.Server.Atmos.EntitySystems
|
|||
_maxExplosionRange = value;
|
||||
}
|
||||
|
||||
private void OnGasShutdown(Entity<GasTankComponent> gasTank, ref ComponentShutdown args)
|
||||
{
|
||||
DisconnectFromInternals(gasTank);
|
||||
}
|
||||
|
||||
private void OnGasTankToggleInternals(Entity<GasTankComponent> ent, ref GasTankToggleInternalsMessage args)
|
||||
{
|
||||
ToggleInternals(ent);
|
||||
}
|
||||
|
||||
private void OnGasTankSetPressure(Entity<GasTankComponent> ent, ref GasTankSetPressureMessage args)
|
||||
{
|
||||
var pressure = Math.Clamp(args.Pressure, 0f, ent.Comp.MaxOutputPressure);
|
||||
|
||||
ent.Comp.OutputPressure = pressure;
|
||||
|
||||
UpdateUserInterface(ent, true);
|
||||
}
|
||||
|
||||
public void UpdateUserInterface(Entity<GasTankComponent> ent, bool initialUpdate = false)
|
||||
public override void UpdateUserInterface(Entity<GasTankComponent> ent)
|
||||
{
|
||||
var (owner, component) = ent;
|
||||
_ui.SetUiState(owner, SharedGasTankUiKey.Key,
|
||||
new GasTankBoundUserInterfaceState
|
||||
{
|
||||
TankPressure = component.Air?.Pressure ?? 0,
|
||||
OutputPressure = initialUpdate ? component.OutputPressure : null,
|
||||
InternalsConnected = component.IsConnected,
|
||||
CanConnectInternals = CanConnectToInternals(ent)
|
||||
});
|
||||
}
|
||||
|
||||
private void BeforeUiOpen(Entity<GasTankComponent> ent, ref BeforeActivatableUIOpenEvent args)
|
||||
{
|
||||
// Only initial update includes output pressure information, to avoid overwriting client-input as the updates come in.
|
||||
UpdateUserInterface(ent, true);
|
||||
}
|
||||
|
||||
private void OnParentChange(EntityUid uid, GasTankComponent component, ref EntParentChangedMessage args)
|
||||
{
|
||||
// When an item is moved from hands -> pockets, the container removal briefly dumps the item on the floor.
|
||||
|
|
@ -109,30 +62,6 @@ namespace Content.Server.Atmos.EntitySystems
|
|||
component.CheckUser = true;
|
||||
}
|
||||
|
||||
private void OnGetActions(EntityUid uid, GasTankComponent component, GetItemActionsEvent args)
|
||||
{
|
||||
args.AddAction(ref component.ToggleActionEntity, component.ToggleAction);
|
||||
}
|
||||
|
||||
private void OnExamined(EntityUid uid, GasTankComponent component, ExaminedEvent args)
|
||||
{
|
||||
using var _ = args.PushGroup(nameof(GasTankComponent));
|
||||
if (args.IsInDetailsRange)
|
||||
args.PushMarkup(Loc.GetString("comp-gas-tank-examine", ("pressure", Math.Round(component.Air?.Pressure ?? 0))));
|
||||
if (component.IsConnected)
|
||||
args.PushMarkup(Loc.GetString("comp-gas-tank-connected"));
|
||||
args.PushMarkup(Loc.GetString(component.IsValveOpen ? "comp-gas-tank-examine-open-valve" : "comp-gas-tank-examine-closed-valve"));
|
||||
}
|
||||
|
||||
private void OnActionToggle(Entity<GasTankComponent> gasTank, ref ToggleActionEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
ToggleInternals(gasTank);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
|
@ -167,8 +96,10 @@ namespace Content.Server.Atmos.EntitySystems
|
|||
{
|
||||
_atmosphereSystem.React(comp.Air, comp);
|
||||
}
|
||||
|
||||
CheckStatus(gasTank);
|
||||
if (_ui.IsUiOpen(uid, SharedGasTankUiKey.Key))
|
||||
|
||||
if ((comp.IsConnected || comp.IsValveOpen) && _ui.IsUiOpen(uid, SharedGasTankUiKey.Key))
|
||||
{
|
||||
UpdateUserInterface(gasTank);
|
||||
}
|
||||
|
|
@ -190,18 +121,6 @@ namespace Content.Server.Atmos.EntitySystems
|
|||
_audioSys.PlayPvs(gasTank.Comp.RuptureSound, gasTank);
|
||||
}
|
||||
|
||||
private void ToggleInternals(Entity<GasTankComponent> ent)
|
||||
{
|
||||
if (ent.Comp.IsConnected)
|
||||
{
|
||||
DisconnectFromInternals(ent);
|
||||
}
|
||||
else
|
||||
{
|
||||
ConnectToInternals(ent);
|
||||
}
|
||||
}
|
||||
|
||||
public GasMixture? RemoveAir(Entity<GasTankComponent> gasTank, float amount)
|
||||
{
|
||||
var gas = gasTank.Comp.Air?.Remove(amount);
|
||||
|
|
@ -227,95 +146,6 @@ namespace Content.Server.Atmos.EntitySystems
|
|||
return air;
|
||||
}
|
||||
|
||||
public bool CanConnectToInternals(Entity<GasTankComponent> ent)
|
||||
{
|
||||
TryGetInternalsComp(ent, out _, out var internalsComp, ent.Comp.User);
|
||||
return internalsComp != null && internalsComp.BreathTools.Count != 0 && !ent.Comp.IsValveOpen;
|
||||
}
|
||||
|
||||
public void ConnectToInternals(Entity<GasTankComponent> ent)
|
||||
{
|
||||
var (owner, component) = ent;
|
||||
if (component.IsConnected || !CanConnectToInternals(ent))
|
||||
return;
|
||||
|
||||
TryGetInternalsComp(ent, out var internalsUid, out var internalsComp, ent.Comp.User);
|
||||
if (internalsUid == null || internalsComp == null)
|
||||
return;
|
||||
|
||||
if (_internals.TryConnectTank((internalsUid.Value, internalsComp), owner))
|
||||
component.User = internalsUid.Value;
|
||||
|
||||
_actions.SetToggled(component.ToggleActionEntity, component.IsConnected);
|
||||
|
||||
// Couldn't toggle!
|
||||
if (!component.IsConnected)
|
||||
return;
|
||||
|
||||
component.ConnectStream = _audioSys.Stop(component.ConnectStream);
|
||||
component.ConnectStream = _audioSys.PlayPvs(component.ConnectSound, owner)?.Entity;
|
||||
|
||||
UpdateUserInterface(ent);
|
||||
}
|
||||
|
||||
public void DisconnectFromInternals(Entity<GasTankComponent> ent)
|
||||
{
|
||||
var (owner, component) = ent;
|
||||
|
||||
if (component.User == null)
|
||||
return;
|
||||
|
||||
TryGetInternalsComp(ent, out var internalsUid, out var internalsComp, component.User);
|
||||
component.User = null;
|
||||
|
||||
_actions.SetToggled(component.ToggleActionEntity, false);
|
||||
|
||||
if (internalsUid != null && internalsComp != null)
|
||||
_internals.DisconnectTank((internalsUid.Value, internalsComp));
|
||||
component.DisconnectStream = _audioSys.Stop(component.DisconnectStream);
|
||||
component.DisconnectStream = _audioSys.PlayPvs(component.DisconnectSound, owner)?.Entity;
|
||||
|
||||
UpdateUserInterface(ent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to retrieve the internals component of either the gas tank's user,
|
||||
/// or the gas tank's... containing container
|
||||
/// </summary>
|
||||
/// <param name="user">The user of the gas tank</param>
|
||||
/// <returns>True if internals comp isn't null, false if it is null</returns>
|
||||
private bool TryGetInternalsComp(Entity<GasTankComponent> ent, out EntityUid? internalsUid, out InternalsComponent? internalsComp, EntityUid? user = null)
|
||||
{
|
||||
internalsUid = default;
|
||||
internalsComp = default;
|
||||
|
||||
// If the gas tank doesn't exist for whatever reason, don't even bother
|
||||
if (TerminatingOrDeleted(ent.Owner))
|
||||
return false;
|
||||
|
||||
user ??= ent.Comp.User;
|
||||
// Check if the gas tank's user actually has the component that allows them to use a gas tank and mask
|
||||
if (TryComp<InternalsComponent>(user, out var userInternalsComp) && userInternalsComp != null)
|
||||
{
|
||||
internalsUid = user;
|
||||
internalsComp = userInternalsComp;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Yeah I have no clue what this actually does, I appreciate the lack of comments on the original function
|
||||
if (_containers.TryGetContainingContainer((ent.Owner, Transform(ent.Owner)), out var container) && container != null)
|
||||
{
|
||||
if (TryComp<InternalsComponent>(container.Owner, out var containerInternalsComp) && containerInternalsComp != null)
|
||||
{
|
||||
internalsUid = container.Owner;
|
||||
internalsComp = containerInternalsComp;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void AssumeAir(Entity<GasTankComponent> ent, GasMixture giver)
|
||||
{
|
||||
_atmosphereSystem.Merge(ent.Comp.Air, giver);
|
||||
|
|
@ -404,21 +234,5 @@ namespace Content.Server.Atmos.EntitySystems
|
|||
{
|
||||
args.Price += _atmosphereSystem.GetPrice(component.Air);
|
||||
}
|
||||
|
||||
private void OnGetAlternativeVerb(EntityUid uid, GasTankComponent component, GetVerbsEvent<AlternativeVerb> args)
|
||||
{
|
||||
if (!args.CanAccess || !args.CanInteract || args.Hands == null)
|
||||
return;
|
||||
args.Verbs.Add(new AlternativeVerb()
|
||||
{
|
||||
Text = component.IsValveOpen ? Loc.GetString("comp-gas-tank-close-valve") : Loc.GetString("comp-gas-tank-open-valve"),
|
||||
Act = () =>
|
||||
{
|
||||
component.IsValveOpen = !component.IsValveOpen;
|
||||
_audioSys.PlayPvs(component.ValveSound, uid);
|
||||
},
|
||||
Disabled = component.IsConnected,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using Content.Server.NodeContainer.Nodes;
|
|||
using Content.Server.Popups;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Construction.Components;
|
||||
using Content.Shared.NodeContainer;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
using Content.Shared.Atmos;
|
||||
|
||||
namespace Content.Server.Atmos
|
||||
{
|
||||
public interface IGasMixtureHolder
|
||||
{
|
||||
public GasMixture Air { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ using Content.Server.NodeContainer.EntitySystems;
|
|||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.NodeContainer;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.EntitySystems;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using Content.Server.Popups;
|
|||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Construction.Components;
|
||||
using Content.Shared.Destructible;
|
||||
using Content.Shared.NodeContainer;
|
||||
using Content.Shared.Popups;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,73 +0,0 @@
|
|||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Guidebook;
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed partial class GasCanisterComponent : Component, IGasMixtureHolder
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("port")]
|
||||
public string PortName { get; set; } = "port";
|
||||
|
||||
/// <summary>
|
||||
/// Container name for the gas tank holder.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("container")]
|
||||
public string ContainerName { get; set; } = "tank_slot";
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public ItemSlot GasTankSlot = new();
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("gasMixture")]
|
||||
public GasMixture Air { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Last recorded pressure, for appearance-updating purposes.
|
||||
/// </summary>
|
||||
public float LastPressure { get; set; } = 0f;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum release pressure possible for the release valve.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("minReleasePressure")]
|
||||
public float MinReleasePressure { get; set; } = Atmospherics.OneAtmosphere / 10;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum release pressure possible for the release valve.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("maxReleasePressure")]
|
||||
public float MaxReleasePressure { get; set; } = Atmospherics.OneAtmosphere * 10;
|
||||
|
||||
/// <summary>
|
||||
/// Valve release pressure.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("releasePressure")]
|
||||
public float ReleasePressure { get; set; } = Atmospherics.OneAtmosphere;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the release valve is open on the canister.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("releaseValve")]
|
||||
public bool ReleaseValve { get; set; } = false;
|
||||
|
||||
[DataField("accessDeniedSound")]
|
||||
public SoundSpecifier AccessDeniedSound = new SoundPathSpecifier("/Audio/Machines/custom_deny.ogg");
|
||||
|
||||
#region GuidebookData
|
||||
|
||||
[GuidebookData]
|
||||
public float Volume => Air.Volume;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
@ -1,55 +1,32 @@
|
|||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Atmos.Piping.Components;
|
||||
using Content.Server.Atmos.Piping.Unary.Components;
|
||||
using Content.Server.Cargo.Systems;
|
||||
using Content.Server.NodeContainer;
|
||||
using Content.Server.NodeContainer.EntitySystems;
|
||||
using Content.Server.NodeContainer.NodeGroups;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Atmos.Piping.Binary.Components;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Atmos.Piping.Unary.Systems;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Lock;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Player;
|
||||
using Content.Shared.NodeContainer;
|
||||
using GasCanisterComponent = Content.Shared.Atmos.Piping.Unary.Components.GasCanisterComponent;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.Unary.EntitySystems;
|
||||
|
||||
public sealed class GasCanisterSystem : EntitySystem
|
||||
public sealed class GasCanisterSystem : SharedGasCanisterSystem
|
||||
{
|
||||
[Dependency] private readonly AtmosphereSystem _atmos = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _ui = default!;
|
||||
[Dependency] private readonly NodeContainerSystem _nodeContainer = default!;
|
||||
[Dependency] private readonly ItemSlotsSystem _slots = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<GasCanisterComponent, ComponentStartup>(OnCanisterStartup);
|
||||
SubscribeLocalEvent<GasCanisterComponent, AtmosDeviceUpdateEvent>(OnCanisterUpdated);
|
||||
SubscribeLocalEvent<GasCanisterComponent, ActivateInWorldEvent>(OnCanisterActivate, after: new[] { typeof(LockSystem) });
|
||||
SubscribeLocalEvent<GasCanisterComponent, InteractHandEvent>(OnCanisterInteractHand);
|
||||
SubscribeLocalEvent<GasCanisterComponent, ItemSlotInsertAttemptEvent>(OnCanisterInsertAttempt);
|
||||
SubscribeLocalEvent<GasCanisterComponent, EntInsertedIntoContainerMessage>(OnCanisterContainerInserted);
|
||||
SubscribeLocalEvent<GasCanisterComponent, EntRemovedFromContainerMessage>(OnCanisterContainerRemoved);
|
||||
SubscribeLocalEvent<GasCanisterComponent, PriceCalculationEvent>(CalculateCanisterPrice);
|
||||
SubscribeLocalEvent<GasCanisterComponent, GasAnalyzerScanEvent>(OnAnalyzed);
|
||||
// Bound UI subscriptions
|
||||
SubscribeLocalEvent<GasCanisterComponent, GasCanisterHoldingTankEjectMessage>(OnHoldingTankEjectMessage);
|
||||
SubscribeLocalEvent<GasCanisterComponent, GasCanisterChangeReleasePressureMessage>(OnCanisterChangeReleasePressure);
|
||||
SubscribeLocalEvent<GasCanisterComponent, GasCanisterChangeReleaseValveMessage>(OnCanisterChangeReleaseValve);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -65,24 +42,16 @@ public sealed class GasCanisterSystem : EntitySystem
|
|||
if (environment is not null)
|
||||
_atmos.Merge(environment, canister.Air);
|
||||
|
||||
_adminLogger.Add(LogType.CanisterPurged, LogImpact.Medium, $"Canister {ToPrettyString(uid):canister} purged its contents of {canister.Air:gas} into the environment.");
|
||||
AdminLogger.Add(LogType.CanisterPurged, LogImpact.Medium, $"Canister {ToPrettyString(uid):canister} purged its contents of {canister.Air:gas} into the environment.");
|
||||
canister.Air.Clear();
|
||||
}
|
||||
|
||||
private void OnCanisterStartup(EntityUid uid, GasCanisterComponent comp, ComponentStartup args)
|
||||
{
|
||||
// Ensure container
|
||||
_slots.AddItemSlot(uid, comp.ContainerName, comp.GasTankSlot);
|
||||
}
|
||||
|
||||
private void DirtyUI(EntityUid uid,
|
||||
GasCanisterComponent? canister = null, NodeContainerComponent? nodeContainer = null)
|
||||
protected override void DirtyUI(EntityUid uid, GasCanisterComponent? canister = null, NodeContainerComponent? nodeContainer = null)
|
||||
{
|
||||
if (!Resolve(uid, ref canister, ref nodeContainer))
|
||||
return;
|
||||
|
||||
var portStatus = false;
|
||||
string? tankLabel = null;
|
||||
var tankPressure = 0f;
|
||||
|
||||
if (_nodeContainer.TryGetNode(nodeContainer, canister.PortName, out PipeNode? portNode) && portNode.NodeGroup?.Nodes.Count > 1)
|
||||
|
|
@ -92,62 +61,11 @@ public sealed class GasCanisterSystem : EntitySystem
|
|||
{
|
||||
var tank = canister.GasTankSlot.Item.Value;
|
||||
var tankComponent = Comp<GasTankComponent>(tank);
|
||||
tankLabel = Name(tank);
|
||||
tankPressure = tankComponent.Air.Pressure;
|
||||
}
|
||||
|
||||
_ui.SetUiState(uid, GasCanisterUiKey.Key,
|
||||
new GasCanisterBoundUserInterfaceState(Name(uid),
|
||||
canister.Air.Pressure, portStatus, tankLabel, tankPressure, canister.ReleasePressure,
|
||||
canister.ReleaseValve, canister.MinReleasePressure, canister.MaxReleasePressure));
|
||||
}
|
||||
|
||||
private void OnHoldingTankEjectMessage(EntityUid uid, GasCanisterComponent canister, GasCanisterHoldingTankEjectMessage args)
|
||||
{
|
||||
if (canister.GasTankSlot.Item == null)
|
||||
return;
|
||||
|
||||
var item = canister.GasTankSlot.Item;
|
||||
_slots.TryEjectToHands(uid, canister.GasTankSlot, args.Actor);
|
||||
|
||||
if (canister.ReleaseValve)
|
||||
{
|
||||
_adminLogger.Add(LogType.CanisterTankEjected, LogImpact.High, $"Player {ToPrettyString(args.Actor):player} ejected tank {ToPrettyString(item):tank} from {ToPrettyString(uid):canister} while the valve was open, releasing [{GetContainedGasesString((uid, canister))}] to atmosphere");
|
||||
}
|
||||
else
|
||||
{
|
||||
_adminLogger.Add(LogType.CanisterTankEjected, LogImpact.Medium, $"Player {ToPrettyString(args.Actor):player} ejected tank {ToPrettyString(item):tank} from {ToPrettyString(uid):canister}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCanisterChangeReleasePressure(EntityUid uid, GasCanisterComponent canister, GasCanisterChangeReleasePressureMessage args)
|
||||
{
|
||||
var pressure = Math.Clamp(args.Pressure, canister.MinReleasePressure, canister.MaxReleasePressure);
|
||||
|
||||
_adminLogger.Add(LogType.CanisterPressure, LogImpact.Medium, $"{ToPrettyString(args.Actor):player} set the release pressure on {ToPrettyString(uid):canister} to {args.Pressure}");
|
||||
|
||||
canister.ReleasePressure = pressure;
|
||||
DirtyUI(uid, canister);
|
||||
}
|
||||
|
||||
private void OnCanisterChangeReleaseValve(EntityUid uid, GasCanisterComponent canister, GasCanisterChangeReleaseValveMessage args)
|
||||
{
|
||||
// filling a jetpack with plasma is less important than filling a room with it
|
||||
var hasItem = canister.GasTankSlot.HasItem;
|
||||
var impact = hasItem ? LogImpact.Medium : LogImpact.High;
|
||||
|
||||
_adminLogger.Add(
|
||||
LogType.CanisterValve,
|
||||
impact,
|
||||
$"{ToPrettyString(args.Actor):player} {(args.Valve ? "opened" : "closed")} the valve on {ToPrettyString(uid):canister} to {(hasItem ? "inserted tank" : "environment")} while it contained [{GetContainedGasesString((uid, canister))}]");
|
||||
|
||||
canister.ReleaseValve = args.Valve;
|
||||
DirtyUI(uid, canister);
|
||||
}
|
||||
|
||||
private static string GetContainedGasesString(Entity<GasCanisterComponent> canister)
|
||||
{
|
||||
return string.Join(", ", canister.Comp.Air);
|
||||
UI.SetUiState(uid, GasCanisterUiKey.Key,
|
||||
new GasCanisterBoundUserInterfaceState(canister.Air.Pressure, portStatus, tankPressure));
|
||||
}
|
||||
|
||||
private void OnCanisterUpdated(EntityUid uid, GasCanisterComponent canister, ref AtmosDeviceUpdateEvent args)
|
||||
|
|
@ -207,76 +125,6 @@ public sealed class GasCanisterSystem : EntitySystem
|
|||
}
|
||||
}
|
||||
|
||||
private void OnCanisterActivate(EntityUid uid, GasCanisterComponent component, ActivateInWorldEvent args)
|
||||
{
|
||||
if (!args.Complex)
|
||||
return;
|
||||
|
||||
if (!TryComp<ActorComponent>(args.User, out var actor))
|
||||
return;
|
||||
|
||||
if (CheckLocked(uid, component, args.User))
|
||||
return;
|
||||
|
||||
// Needs to be here so the locked check still happens if the canister
|
||||
// is locked and you don't have permissions
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
_ui.OpenUi(uid, GasCanisterUiKey.Key, actor.PlayerSession);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnCanisterInteractHand(EntityUid uid, GasCanisterComponent component, InteractHandEvent args)
|
||||
{
|
||||
if (!TryComp<ActorComponent>(args.User, out var actor))
|
||||
return;
|
||||
|
||||
if (CheckLocked(uid, component, args.User))
|
||||
return;
|
||||
|
||||
_ui.OpenUi(uid, GasCanisterUiKey.Key, actor.PlayerSession);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnCanisterInsertAttempt(EntityUid uid, GasCanisterComponent component, ref ItemSlotInsertAttemptEvent args)
|
||||
{
|
||||
if (args.Slot.ID != component.ContainerName || args.User == null)
|
||||
return;
|
||||
|
||||
if (!TryComp<GasTankComponent>(args.Item, out var gasTank) || gasTank.IsValveOpen)
|
||||
{
|
||||
args.Cancelled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Preventing inserting a tank since if its locked you cant remove it.
|
||||
if (!CheckLocked(uid, component, args.User.Value))
|
||||
return;
|
||||
|
||||
args.Cancelled = true;
|
||||
}
|
||||
|
||||
private void OnCanisterContainerInserted(EntityUid uid, GasCanisterComponent component, EntInsertedIntoContainerMessage args)
|
||||
{
|
||||
if (args.Container.ID != component.ContainerName)
|
||||
return;
|
||||
|
||||
DirtyUI(uid, component);
|
||||
|
||||
_appearance.SetData(uid, GasCanisterVisuals.TankInserted, true);
|
||||
}
|
||||
|
||||
private void OnCanisterContainerRemoved(EntityUid uid, GasCanisterComponent component, EntRemovedFromContainerMessage args)
|
||||
{
|
||||
if (args.Container.ID != component.ContainerName)
|
||||
return;
|
||||
|
||||
DirtyUI(uid, component);
|
||||
|
||||
_appearance.SetData(uid, GasCanisterVisuals.TankInserted, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mix air from a gas container into a pipe net.
|
||||
/// Useful for anything that uses connector ports.
|
||||
|
|
@ -317,23 +165,4 @@ public sealed class GasCanisterSystem : EntitySystem
|
|||
args.GasMixtures.Add((Name(tank), tankComponent.Air));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the canister is locked, playing its sound and popup if so.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// True if locked, false otherwise.
|
||||
/// </returns>
|
||||
private bool CheckLocked(EntityUid uid, GasCanisterComponent comp, EntityUid user)
|
||||
{
|
||||
if (TryComp<LockComponent>(uid, out var lockComp) && lockComp.Locked)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("gas-canister-popup-denied"), uid, user);
|
||||
_audio.PlayPvs(comp.AccessDeniedSound, uid);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
using Content.Shared.Alert;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Body.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles hooking up a mask (breathing tool) / gas tank together and allowing the Owner to breathe through it.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class InternalsComponent : Component
|
||||
{
|
||||
[ViewVariables]
|
||||
public EntityUid? GasTankEntity;
|
||||
|
||||
[ViewVariables]
|
||||
public HashSet<EntityUid> BreathTools { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Toggle Internals delay when the target is not you.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public TimeSpan Delay = TimeSpan.FromSeconds(3);
|
||||
|
||||
[DataField]
|
||||
public ProtoId<AlertPrototype> InternalsAlert = "Internals";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,28 +1,21 @@
|
|||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Systems;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Internals;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Body.Systems;
|
||||
|
||||
public sealed class InternalsSystem : EntitySystem
|
||||
public sealed class InternalsSystem : SharedInternalsSystem
|
||||
{
|
||||
[Dependency] private readonly AlertsSystem _alerts = default!;
|
||||
[Dependency] private readonly AtmosphereSystem _atmos = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
||||
[Dependency] private readonly GasTankSystem _gasTank = default!;
|
||||
[Dependency] private readonly InventorySystem _inventory = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly RespiratorSystem _respirator = default!;
|
||||
|
||||
private EntityQuery<InternalsComponent> _internalsQuery;
|
||||
|
|
@ -34,12 +27,6 @@ public sealed class InternalsSystem : EntitySystem
|
|||
_internalsQuery = GetEntityQuery<InternalsComponent>();
|
||||
|
||||
SubscribeLocalEvent<InternalsComponent, InhaleLocationEvent>(OnInhaleLocation);
|
||||
SubscribeLocalEvent<InternalsComponent, ComponentStartup>(OnInternalsStartup);
|
||||
SubscribeLocalEvent<InternalsComponent, ComponentShutdown>(OnInternalsShutdown);
|
||||
SubscribeLocalEvent<InternalsComponent, GetVerbsEvent<InteractionVerb>>(OnGetInteractionVerbs);
|
||||
SubscribeLocalEvent<InternalsComponent, InternalsDoAfterEvent>(OnDoAfter);
|
||||
SubscribeLocalEvent<InternalsComponent, ToggleInternalsAlertEvent>(OnToggleInternalsAlert);
|
||||
|
||||
SubscribeLocalEvent<InternalsComponent, StartingGearEquippedEvent>(OnStartingGear);
|
||||
}
|
||||
|
||||
|
|
@ -66,120 +53,6 @@ public sealed class InternalsSystem : EntitySystem
|
|||
ToggleInternals(uid, uid, force: false, component);
|
||||
}
|
||||
|
||||
private void OnGetInteractionVerbs(
|
||||
Entity<InternalsComponent> ent,
|
||||
ref GetVerbsEvent<InteractionVerb> args)
|
||||
{
|
||||
if (!args.CanAccess || !args.CanInteract || args.Hands is null)
|
||||
return;
|
||||
|
||||
if (!AreInternalsWorking(ent) && ent.Comp.BreathTools.Count == 0)
|
||||
return;
|
||||
|
||||
var user = args.User;
|
||||
|
||||
InteractionVerb verb = new()
|
||||
{
|
||||
Act = () =>
|
||||
{
|
||||
ToggleInternals(ent, user, force: false, ent);
|
||||
},
|
||||
Message = Loc.GetString("action-description-internals-toggle"),
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/dot.svg.192dpi.png")),
|
||||
Text = Loc.GetString("action-name-internals-toggle"),
|
||||
};
|
||||
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
public void ToggleInternals(
|
||||
EntityUid uid,
|
||||
EntityUid user,
|
||||
bool force,
|
||||
InternalsComponent? internals = null)
|
||||
{
|
||||
if (!Resolve(uid, ref internals, logMissing: false))
|
||||
return;
|
||||
|
||||
// Toggle off if they're on
|
||||
if (AreInternalsWorking(internals))
|
||||
{
|
||||
if (force)
|
||||
{
|
||||
DisconnectTank((uid, internals));
|
||||
return;
|
||||
}
|
||||
|
||||
StartToggleInternalsDoAfter(user, (uid, internals));
|
||||
return;
|
||||
}
|
||||
|
||||
// If they're not on then check if we have a mask to use
|
||||
if (internals.BreathTools.Count == 0)
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("internals-no-breath-tool"), uid, user);
|
||||
return;
|
||||
}
|
||||
|
||||
var tank = FindBestGasTank(uid);
|
||||
|
||||
if (tank is null)
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("internals-no-tank"), uid, user);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!force)
|
||||
{
|
||||
StartToggleInternalsDoAfter(user, (uid, internals));
|
||||
return;
|
||||
}
|
||||
|
||||
_gasTank.ConnectToInternals(tank.Value);
|
||||
}
|
||||
|
||||
private void StartToggleInternalsDoAfter(EntityUid user, Entity<InternalsComponent> targetEnt)
|
||||
{
|
||||
// Is the target not you? If yes, use a do-after to give them time to respond.
|
||||
var isUser = user == targetEnt.Owner;
|
||||
var delay = !isUser ? targetEnt.Comp.Delay : TimeSpan.Zero;
|
||||
|
||||
_doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, user, delay, new InternalsDoAfterEvent(), targetEnt, target: targetEnt)
|
||||
{
|
||||
BreakOnDamage = true,
|
||||
BreakOnMove = true,
|
||||
MovementThreshold = 0.1f,
|
||||
});
|
||||
}
|
||||
|
||||
private void OnDoAfter(Entity<InternalsComponent> ent, ref InternalsDoAfterEvent args)
|
||||
{
|
||||
if (args.Cancelled || args.Handled)
|
||||
return;
|
||||
|
||||
ToggleInternals(ent, args.User, force: true, ent);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnToggleInternalsAlert(Entity<InternalsComponent> ent, ref ToggleInternalsAlertEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
ToggleInternals(ent, ent, false, internals: ent.Comp);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnInternalsStartup(Entity<InternalsComponent> ent, ref ComponentStartup args)
|
||||
{
|
||||
_alerts.ShowAlert(ent, ent.Comp.InternalsAlert, GetSeverity(ent));
|
||||
}
|
||||
|
||||
private void OnInternalsShutdown(Entity<InternalsComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
_alerts.ClearAlert(ent, ent.Comp.InternalsAlert);
|
||||
}
|
||||
|
||||
private void OnInhaleLocation(Entity<InternalsComponent> ent, ref InhaleLocationEvent args)
|
||||
{
|
||||
if (AreInternalsWorking(ent))
|
||||
|
|
@ -190,110 +63,4 @@ public sealed class InternalsSystem : EntitySystem
|
|||
_alerts.ShowAlert(ent, ent.Comp.InternalsAlert, GetSeverity(ent));
|
||||
}
|
||||
}
|
||||
public void DisconnectBreathTool(Entity<InternalsComponent> ent, EntityUid toolEntity)
|
||||
{
|
||||
ent.Comp.BreathTools.Remove(toolEntity);
|
||||
|
||||
if (TryComp(toolEntity, out BreathToolComponent? breathTool))
|
||||
_atmos.DisconnectInternals((toolEntity, breathTool));
|
||||
|
||||
if (ent.Comp.BreathTools.Count == 0)
|
||||
DisconnectTank(ent);
|
||||
|
||||
_alerts.ShowAlert(ent, ent.Comp.InternalsAlert, GetSeverity(ent));
|
||||
}
|
||||
|
||||
public void ConnectBreathTool(Entity<InternalsComponent> ent, EntityUid toolEntity)
|
||||
{
|
||||
if (!ent.Comp.BreathTools.Add(toolEntity))
|
||||
return;
|
||||
|
||||
_alerts.ShowAlert(ent, ent.Comp.InternalsAlert, GetSeverity(ent));
|
||||
}
|
||||
|
||||
public void DisconnectTank(Entity<InternalsComponent> ent)
|
||||
{
|
||||
if (TryComp(ent.Comp.GasTankEntity, out GasTankComponent? tank))
|
||||
_gasTank.DisconnectFromInternals((ent.Comp.GasTankEntity.Value, tank));
|
||||
|
||||
ent.Comp.GasTankEntity = null;
|
||||
_alerts.ShowAlert(ent.Owner, ent.Comp.InternalsAlert, GetSeverity(ent.Comp));
|
||||
}
|
||||
|
||||
public bool TryConnectTank(Entity<InternalsComponent> ent, EntityUid tankEntity)
|
||||
{
|
||||
if (ent.Comp.BreathTools.Count == 0)
|
||||
return false;
|
||||
|
||||
if (TryComp(ent.Comp.GasTankEntity, out GasTankComponent? tank))
|
||||
_gasTank.DisconnectFromInternals((ent.Comp.GasTankEntity.Value, tank));
|
||||
|
||||
ent.Comp.GasTankEntity = tankEntity;
|
||||
_alerts.ShowAlert(ent, ent.Comp.InternalsAlert, GetSeverity(ent));
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool AreInternalsWorking(EntityUid uid, InternalsComponent? component = null)
|
||||
{
|
||||
return Resolve(uid, ref component, logMissing: false)
|
||||
&& AreInternalsWorking(component);
|
||||
}
|
||||
|
||||
public bool AreInternalsWorking(InternalsComponent component)
|
||||
{
|
||||
return TryComp(component.BreathTools.FirstOrNull(), out BreathToolComponent? breathTool)
|
||||
&& breathTool.IsFunctional
|
||||
&& HasComp<GasTankComponent>(component.GasTankEntity);
|
||||
}
|
||||
|
||||
private short GetSeverity(InternalsComponent component)
|
||||
{
|
||||
if (component.BreathTools.Count == 0 || !AreInternalsWorking(component))
|
||||
return 2;
|
||||
|
||||
// If pressure in the tank is below low pressure threshold, flash warning on internals UI
|
||||
if (TryComp<GasTankComponent>(component.GasTankEntity, out var gasTank)
|
||||
&& gasTank.IsLowPressure)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
public Entity<GasTankComponent>? FindBestGasTank(
|
||||
Entity<HandsComponent?, InventoryComponent?, ContainerManagerComponent?> user)
|
||||
{
|
||||
// TODO use _respirator.CanMetabolizeGas() to prioritize metabolizable gasses
|
||||
// Prioritise
|
||||
// 1. back equipped tanks
|
||||
// 2. exo-slot tanks
|
||||
// 3. in-hand tanks
|
||||
// 4. pocket/belt tanks
|
||||
|
||||
if (!Resolve(user, ref user.Comp2, ref user.Comp3))
|
||||
return null;
|
||||
|
||||
if (_inventory.TryGetSlotEntity(user, "back", out var backEntity, user.Comp2, user.Comp3) &&
|
||||
TryComp<GasTankComponent>(backEntity, out var backGasTank) &&
|
||||
_gasTank.CanConnectToInternals((backEntity.Value, backGasTank)))
|
||||
{
|
||||
return (backEntity.Value, backGasTank);
|
||||
}
|
||||
|
||||
if (_inventory.TryGetSlotEntity(user, "suitstorage", out var entity, user.Comp2, user.Comp3) &&
|
||||
TryComp<GasTankComponent>(entity, out var gasTank) &&
|
||||
_gasTank.CanConnectToInternals((entity.Value, gasTank)))
|
||||
{
|
||||
return (entity.Value, gasTank);
|
||||
}
|
||||
|
||||
foreach (var item in _inventory.GetHandOrInventoryEntities((user.Owner, user.Comp1, user.Comp2)))
|
||||
{
|
||||
if (TryComp(item, out gasTank) && _gasTank.CanConnectToInternals((item, gasTank)))
|
||||
return (item, gasTank);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
|
|
@ -6,6 +5,8 @@ using Content.Shared.Atmos;
|
|||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.Inventory.Events;
|
||||
using BreathToolComponent = Content.Shared.Atmos.Components.BreathToolComponent;
|
||||
using InternalsComponent = Content.Shared.Body.Components.InternalsComponent;
|
||||
|
||||
namespace Content.Server.Body.Systems;
|
||||
|
||||
|
|
@ -23,7 +24,6 @@ public sealed class LungSystem : EntitySystem
|
|||
SubscribeLocalEvent<LungComponent, ComponentInit>(OnComponentInit);
|
||||
SubscribeLocalEvent<BreathToolComponent, GotEquippedEvent>(OnGotEquipped);
|
||||
SubscribeLocalEvent<BreathToolComponent, GotUnequippedEvent>(OnGotUnequipped);
|
||||
SubscribeLocalEvent<BreathToolComponent, ItemMaskToggledEvent>(OnMaskToggled);
|
||||
}
|
||||
|
||||
private void OnGotUnequipped(Entity<BreathToolComponent> ent, ref GotUnequippedEvent args)
|
||||
|
|
@ -38,8 +38,6 @@ public sealed class LungSystem : EntitySystem
|
|||
return;
|
||||
}
|
||||
|
||||
ent.Comp.IsFunctional = true;
|
||||
|
||||
if (TryComp(args.Equipee, out InternalsComponent? internals))
|
||||
{
|
||||
ent.Comp.ConnectedInternalsEntity = args.Equipee;
|
||||
|
|
@ -56,24 +54,6 @@ public sealed class LungSystem : EntitySystem
|
|||
}
|
||||
}
|
||||
|
||||
private void OnMaskToggled(Entity<BreathToolComponent> ent, ref ItemMaskToggledEvent args)
|
||||
{
|
||||
if (args.Mask.Comp.IsToggled)
|
||||
{
|
||||
_atmos.DisconnectInternals(ent);
|
||||
}
|
||||
else
|
||||
{
|
||||
ent.Comp.IsFunctional = true;
|
||||
|
||||
if (TryComp(args.Wearer, out InternalsComponent? internals))
|
||||
{
|
||||
ent.Comp.ConnectedInternalsEntity = args.Wearer;
|
||||
_internals.ConnectBreathTool((args.Wearer.Value, internals), ent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void GasToReagent(EntityUid uid, LungComponent lung)
|
||||
{
|
||||
if (!_solutionContainerSystem.ResolveSolution(uid, lung.SolutionName, ref lung.Solution, out var solution))
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using Content.Server.Botany.Components;
|
||||
using Content.Shared.EntityEffects;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.FixedPoint;
|
||||
|
||||
namespace Content.Server.Botany.Systems;
|
||||
|
|
@ -37,4 +38,23 @@ public sealed partial class BotanySystem
|
|||
solutionContainer.AddReagent(chem, amount);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnProduceExamined(EntityUid uid, ProduceComponent comp, ExaminedEvent args)
|
||||
{
|
||||
if (comp.Seed == null)
|
||||
return;
|
||||
|
||||
using (args.PushGroup(nameof(ProduceComponent)))
|
||||
{
|
||||
foreach (var m in comp.Seed.Mutations)
|
||||
{
|
||||
// Don't show mutations that have no effect on produce (sentience)
|
||||
if (!m.AppliesToProduce)
|
||||
continue;
|
||||
|
||||
if (m.Description != null)
|
||||
args.PushMarkup(Loc.GetString(m.Description));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ public sealed partial class BotanySystem : EntitySystem
|
|||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<SeedComponent, ExaminedEvent>(OnExamined);
|
||||
SubscribeLocalEvent<ProduceComponent, ExaminedEvent>(OnProduceExamined);
|
||||
}
|
||||
|
||||
public bool TryGetSeed(SeedComponent comp, [NotNullWhen(true)] out SeedData? seed)
|
||||
|
|
|
|||
|
|
@ -116,6 +116,20 @@ public sealed class PlantHolderSystem : EntitySystem
|
|||
? "plant-holder-component-plant-old-adjective"
|
||||
: "plant-holder-component-plant-unhealthy-adjective"))));
|
||||
}
|
||||
|
||||
// For future reference, mutations should only appear on examine if they apply to a plant, not to produce.
|
||||
|
||||
if (component.Seed.Ligneous)
|
||||
args.PushMarkup(Loc.GetString("mutation-plant-ligneous"));
|
||||
|
||||
if (component.Seed.TurnIntoKudzu)
|
||||
args.PushMarkup(Loc.GetString("mutation-plant-kudzu"));
|
||||
|
||||
if (component.Seed.CanScream)
|
||||
args.PushMarkup(Loc.GetString("mutation-plant-scream"));
|
||||
|
||||
if (component.Seed.Viable == false)
|
||||
args.PushMarkup(Loc.GetString("mutation-plant-unviable"));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -389,7 +389,7 @@ public sealed class InjectorSystem : SharedInjectorSystem
|
|||
var removedSolution = SolutionContainers.Draw(target.Owner, targetSolution, realTransferAmount);
|
||||
|
||||
// Add back non-whitelisted reagents to the target solution
|
||||
applicableTargetSolution.AddSolution(temporarilyRemovedSolution, null);
|
||||
SolutionContainers.TryAddSolution(targetSolution, temporarilyRemovedSolution);
|
||||
|
||||
if (!SolutionContainers.TryAddSolution(soln.Value, removedSolution))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
using Content.Server.Fluids.EntitySystems;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.Reaction;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.FixedPoint;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Server.Chemistry.TileReactions
|
||||
{
|
||||
[UsedImplicitly]
|
||||
[DataDefinition]
|
||||
public sealed partial class SpillIfPuddlePresentTileReaction : ITileReaction
|
||||
{
|
||||
public FixedPoint2 TileReact(TileRef tile,
|
||||
ReagentPrototype reagent,
|
||||
FixedPoint2 reactVolume,
|
||||
IEntityManager entityManager,
|
||||
List<ReagentData>? data)
|
||||
{
|
||||
var spillSystem = entityManager.System<PuddleSystem>();
|
||||
if (!spillSystem.TryGetPuddle(tile, out _))
|
||||
return FixedPoint2.Zero;
|
||||
|
||||
return spillSystem.TrySpillAt(tile, new Solution(reagent.ID, reactVolume, data), out _, sound: false, tileReact: false)
|
||||
? reactVolume
|
||||
: FixedPoint2.Zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
28
Content.Server/Chemistry/TileReactions/SpillTileReaction.cs
Normal file
28
Content.Server/Chemistry/TileReactions/SpillTileReaction.cs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
using Content.Server.Fluids.EntitySystems;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.Reaction;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.FixedPoint;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Server.Chemistry.TileReactions
|
||||
{
|
||||
[UsedImplicitly]
|
||||
[DataDefinition]
|
||||
public sealed partial class SpillTileReaction : ITileReaction
|
||||
{
|
||||
public FixedPoint2 TileReact(TileRef tile,
|
||||
ReagentPrototype reagent,
|
||||
FixedPoint2 reactVolume,
|
||||
IEntityManager entityManager,
|
||||
List<ReagentData>? data)
|
||||
{
|
||||
var spillSystem = entityManager.System<PuddleSystem>();
|
||||
|
||||
return spillSystem.TrySpillAt(tile, new Solution(reagent.ID, reactVolume, data), out _, sound: false, tileReact: false)
|
||||
? reactVolume
|
||||
: FixedPoint2.Zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,6 @@ using Content.Shared.Construction;
|
|||
using Content.Shared.DoAfter;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Containers;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using SharedToolSystem = Content.Shared.Tools.Systems.SharedToolSystem;
|
||||
|
||||
|
|
|
|||
|
|
@ -20,23 +20,6 @@ public sealed class DeviceLinkSystem : SharedDeviceLinkSystem
|
|||
SubscribeLocalEvent<DeviceLinkSourceComponent, NewLinkEvent>(OnNewLink);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
var query = EntityQueryEnumerator<DeviceLinkSinkComponent>();
|
||||
|
||||
while (query.MoveNext(out var component))
|
||||
{
|
||||
if (component.InvokeLimit < 1)
|
||||
{
|
||||
component.InvokeCounter = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(component.InvokeCounter > 0)
|
||||
component.InvokeCounter--;
|
||||
}
|
||||
}
|
||||
|
||||
#region Sending & Receiving
|
||||
public override void InvokePort(EntityUid uid, string port, NetworkPayload? data = null, DeviceLinkSourceComponent? sourceComponent = null)
|
||||
{
|
||||
|
|
@ -67,16 +50,17 @@ public sealed class DeviceLinkSystem : SharedDeviceLinkSystem
|
|||
if (!Resolve(sink, ref sink.Comp))
|
||||
return;
|
||||
|
||||
if (sink.Comp.InvokeCounter > sink.Comp.InvokeLimit)
|
||||
var invokeCounter = GetEffectiveInvokeCounter(sink.Comp);
|
||||
if (invokeCounter > sink.Comp.InvokeLimit)
|
||||
{
|
||||
sink.Comp.InvokeCounter = 0;
|
||||
SetInvokeCounter(sink.Comp, 0);
|
||||
var args = new DeviceLinkOverloadedEvent();
|
||||
RaiseLocalEvent(sink, ref args);
|
||||
RemoveAllFromSink(sink, sink.Comp);
|
||||
return;
|
||||
}
|
||||
|
||||
sink.Comp.InvokeCounter++;
|
||||
SetInvokeCounter(sink.Comp, invokeCounter + 1);
|
||||
|
||||
//Just skip using device networking if the source or the sink doesn't support it
|
||||
if (!HasComp<DeviceNetworkComponent>(source) || !TryComp<DeviceNetworkComponent>(sink, out var sinkNetwork))
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using Content.Server.Power.Nodes;
|
|||
using Content.Server.Power.NodeGroups;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.NodeContainer;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Power.Generator;
|
||||
using Content.Shared.Timing;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Shared.NodeContainer;
|
||||
|
||||
namespace Content.Server.DeviceNetwork.Components
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using JetBrains.Annotations;
|
|||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Server.Power.Nodes;
|
||||
using Content.Shared.DeviceNetwork.Events;
|
||||
using Content.Shared.NodeContainer;
|
||||
|
||||
namespace Content.Server.DeviceNetwork.Systems
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using Content.Server.NodeContainer;
|
||||
using Content.Server.NodeContainer.EntitySystems;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Shared.NodeContainer;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Content.Server.Electrocution
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ using Content.Shared.Interaction;
|
|||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Jittering;
|
||||
using Content.Shared.Maps;
|
||||
using Content.Shared.NodeContainer;
|
||||
using Content.Shared.NodeContainer.NodeGroups;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Speech.EntitySystems;
|
||||
using Content.Shared.StatusEffect;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
using Content.Shared.Explosion.Components.OnTrigger;
|
||||
using Content.Shared.Explosion.EntitySystems;
|
||||
using Content.Shared.RepulseAttract;
|
||||
using Content.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Explosion.EntitySystems;
|
||||
|
||||
public sealed class RepulseAttractOnTriggerSystem : SharedRepulseAttractOnTriggerSystem
|
||||
{
|
||||
[Dependency] private readonly RepulseAttractSystem _repulse = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly UseDelaySystem _delay = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<SharedRepulseAttractOnTriggerComponent, TriggerEvent>(OnTrigger);
|
||||
}
|
||||
|
||||
private void OnTrigger(Entity<SharedRepulseAttractOnTriggerComponent> ent, ref TriggerEvent args)
|
||||
{
|
||||
if (_delay.IsDelayed(ent.Owner))
|
||||
return;
|
||||
|
||||
var position = _transform.GetMapCoordinates(ent);
|
||||
_repulse.TryRepulseAttract(position, args.User, ent.Comp.Speed, ent.Comp.Range, ent.Comp.Whitelist, ent.Comp.CollisionMask);
|
||||
}
|
||||
}
|
||||
|
|
@ -390,6 +390,9 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
|
|||
if (!TryComp<StepTriggerComponent>(entity, out var comp))
|
||||
return;
|
||||
|
||||
// Ensure we actually have the component
|
||||
EnsureComp<TileFrictionModifierComponent>(entity);
|
||||
|
||||
// This is the base amount of reagent needed before a puddle can be considered slippery. Is defined based on
|
||||
// the sprite threshold for a puddle larger than 5 pixels.
|
||||
var smallPuddleThreshold = FixedPoint2.New(entity.Comp.OverflowVolume.Float() * LowThreshold);
|
||||
|
|
@ -413,7 +416,7 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
|
|||
if (solution.Volume <= smallPuddleThreshold)
|
||||
{
|
||||
_stepTrigger.SetActive(entity, false, comp);
|
||||
_tile.SetModifier(entity, TileFrictionController.DefaultFriction);
|
||||
_tile.SetModifier(entity, 1f);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -465,7 +468,7 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
|
|||
|
||||
// Lower tile friction based on how slippery it is, lets items slide across a puddle of lube
|
||||
slipComp.SlipData.SlipFriction = (float)(puddleFriction/solution.Volume);
|
||||
_tile.SetModifier(entity, TileFrictionController.DefaultFriction * slipComp.SlipData.SlipFriction);
|
||||
_tile.SetModifier(entity, slipComp.SlipData.SlipFriction);
|
||||
|
||||
Dirty(entity, slipComp);
|
||||
}
|
||||
|
|
@ -483,7 +486,7 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
|
|||
{
|
||||
var comp = EnsureComp<SpeedModifierContactsComponent>(uid);
|
||||
var speed = 1 - maxViscosity;
|
||||
_speedModContacts.ChangeModifiers(uid, speed, comp);
|
||||
_speedModContacts.ChangeSpeedModifiers(uid, speed, comp);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ using Content.Shared.Weapons.Melee.Events;
|
|||
using Robust.Shared.Random;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Utility;
|
||||
using Content.Shared.Hands.Components;
|
||||
|
||||
namespace Content.Server.Forensics
|
||||
{
|
||||
|
|
@ -32,7 +33,7 @@ namespace Content.Server.Forensics
|
|||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<FingerprintComponent, ContactInteractionEvent>(OnInteract);
|
||||
SubscribeLocalEvent<HandsComponent, ContactInteractionEvent>(OnInteract);
|
||||
SubscribeLocalEvent<FingerprintComponent, MapInitEvent>(OnFingerprintInit, after: new[] { typeof(BloodstreamSystem) });
|
||||
// The solution entities are spawned on MapInit as well, so we have to wait for that to be able to set the DNA in the bloodstream correctly without ResolveSolution failing
|
||||
SubscribeLocalEvent<DnaComponent, MapInitEvent>(OnDNAInit, after: new[] { typeof(BloodstreamSystem) });
|
||||
|
|
@ -60,7 +61,7 @@ namespace Content.Server.Forensics
|
|||
}
|
||||
}
|
||||
|
||||
private void OnInteract(EntityUid uid, FingerprintComponent component, ContactInteractionEvent args)
|
||||
private void OnInteract(EntityUid uid, HandsComponent component, ContactInteractionEvent args)
|
||||
{
|
||||
ApplyEvidence(uid, args.Other);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ using Content.Shared.NameModifier.EntitySystems;
|
|||
using Content.Shared.Popups;
|
||||
using Content.Shared.Storage.Components;
|
||||
using Content.Shared.Tag;
|
||||
using Content.Shared.Warps;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ public sealed class IdentitySystem : SharedIdentitySystem
|
|||
/// <summary>
|
||||
/// Queues an identity update to the start of the next tick.
|
||||
/// </summary>
|
||||
public void QueueIdentityUpdate(EntityUid uid)
|
||||
public override void QueueIdentityUpdate(EntityUid uid)
|
||||
{
|
||||
_queuedIdentityUpdates.Add(uid);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,13 +21,6 @@ public sealed class MagicSystem : SharedMagicSystem
|
|||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<SpeakSpellEvent>(OnSpellSpoken);
|
||||
}
|
||||
|
||||
private void OnSpellSpoken(ref SpeakSpellEvent args)
|
||||
{
|
||||
_chat.TrySendInGameICMessage(args.Performer, Loc.GetString(args.Speech), InGameICChatType.Speak, false);
|
||||
}
|
||||
|
||||
public override void OnVoidApplause(VoidApplauseSpellEvent ev)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Robust.Shared.Collections;
|
||||
|
|
|
|||
|
|
@ -181,11 +181,24 @@ public sealed class HTNSystem : EntitySystem
|
|||
_planQueue.Process();
|
||||
var query = EntityQueryEnumerator<ActiveNPCComponent, HTNComponent>();
|
||||
|
||||
// Move ahead "count" entries in the query.
|
||||
// This is to ensure that if we didn't process all the npcs the first time,
|
||||
// we get to the remaining ones instead of iterating over the beginning again.
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
query.MoveNext(out _, out _);
|
||||
}
|
||||
|
||||
// the amount of updates we've processed during this iteration.
|
||||
var updates = 0;
|
||||
while (query.MoveNext(out var uid, out _, out var comp))
|
||||
{
|
||||
// If we're over our max count or it's not MapInit then ignore the NPC.
|
||||
if (count >= maxUpdates)
|
||||
break;
|
||||
if (updates >= maxUpdates)
|
||||
{
|
||||
// Intentional return. We don't want to go to the end logic and reset count.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!comp.Enabled)
|
||||
continue;
|
||||
|
|
@ -275,7 +288,12 @@ public sealed class HTNSystem : EntitySystem
|
|||
|
||||
Update(uid, comp, frameTime);
|
||||
count++;
|
||||
updates++;
|
||||
}
|
||||
|
||||
// only reset our counter back to 0 if we finish iterating.
|
||||
// otherwise it lets us know where we left off.
|
||||
count = 0;
|
||||
}
|
||||
|
||||
private void AppendDebugText(HTNTask task, StringBuilder text, List<int> planBtr, List<int> btr, ref int level)
|
||||
|
|
|
|||
|
|
@ -133,7 +133,6 @@ namespace Content.Server.NPC.Systems
|
|||
if (!Enabled)
|
||||
return;
|
||||
|
||||
_count = 0;
|
||||
// Add your system here.
|
||||
_htn.UpdateNPC(ref _count, _maxUpdates, frameTime);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -232,6 +232,9 @@ public sealed class NPCUtilitySystem : EntitySystem
|
|||
{
|
||||
if (_container.TryGetContainingContainer(targetUid, out var container))
|
||||
{
|
||||
if (container.Owner == owner)
|
||||
return 0f;
|
||||
|
||||
if (TryComp<EntityStorageComponent>(container.Owner, out var storageComponent))
|
||||
{
|
||||
if (storageComponent is { Open: false } && _weldable.IsWelded(container.Owner))
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ using System.Diagnostics.CodeAnalysis;
|
|||
using Content.Server.NodeContainer.NodeGroups;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.NodeContainer;
|
||||
using Content.Shared.NodeContainer.NodeGroups;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Server.NodeContainer.EntitySystems
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using Content.Server.NodeContainer.NodeGroups;
|
|||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.NodeContainer;
|
||||
using Content.Shared.NodeContainer.NodeGroups;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Enums;
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
|
||||
namespace Content.Server.NodeContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates and maintains a set of <see cref="Node"/>s.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class NodeContainerComponent : Component
|
||||
{
|
||||
//HACK: THIS BEING readOnly IS A FILTHY HACK AND I HATE IT --moony
|
||||
[DataField("nodes", readOnly: true)] public Dictionary<string, Node> Nodes { get; private set; } = new();
|
||||
|
||||
[DataField("examinable")] public bool Examinable = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,40 +1,12 @@
|
|||
using System.Linq;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Shared.NodeContainer;
|
||||
using Content.Shared.NodeContainer.NodeGroups;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.NodeContainer.NodeGroups
|
||||
{
|
||||
/// <summary>
|
||||
/// Maintains a collection of <see cref="Node"/>s, and performs operations requiring a list of
|
||||
/// all connected <see cref="Node"/>s.
|
||||
/// </summary>
|
||||
public interface INodeGroup
|
||||
{
|
||||
bool Remaking { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of nodes currently in this group.
|
||||
/// </summary>
|
||||
IReadOnlyList<Node> Nodes { get; }
|
||||
|
||||
void Create(NodeGroupID groupId);
|
||||
|
||||
void Initialize(Node sourceNode, IEntityManager entMan);
|
||||
|
||||
void RemoveNode(Node node);
|
||||
|
||||
void LoadNodes(List<Node> groupNodes);
|
||||
|
||||
// In theory, the SS13 curse ensures this method will never be called.
|
||||
void AfterRemake(IEnumerable<IGrouping<INodeGroup?, Node>> newGroups);
|
||||
|
||||
/// <summary>
|
||||
/// Return any additional data to display for the node-visualizer debug overlay.
|
||||
/// </summary>
|
||||
string? GetDebugData();
|
||||
}
|
||||
|
||||
[NodeGroup(NodeGroupID.Default, NodeGroupID.WireNet)]
|
||||
[Virtual]
|
||||
public class BaseNodeGroup : INodeGroup
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using Content.Shared.NodeContainer.NodeGroups;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Server.NodeContainer.NodeGroups
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
using System.Reflection;
|
||||
using Content.Server.Power.Generation.Teg;
|
||||
using Content.Shared.NodeContainer.NodeGroups;
|
||||
using Robust.Shared.Reflection;
|
||||
|
||||
namespace Content.Server.NodeContainer.NodeGroups
|
||||
|
|
@ -51,22 +51,4 @@ namespace Content.Server.NodeContainer.NodeGroups
|
|||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
public enum NodeGroupID : byte
|
||||
{
|
||||
Default,
|
||||
HVPower,
|
||||
MVPower,
|
||||
Apc,
|
||||
AMEngine,
|
||||
Pipe,
|
||||
WireNet,
|
||||
|
||||
/// <summary>
|
||||
/// Group used by the TEG.
|
||||
/// </summary>
|
||||
/// <seealso cref="TegSystem"/>
|
||||
/// <seealso cref="TegNodeGroup"/>
|
||||
Teg,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ using Content.Server.Atmos;
|
|||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.NodeContainer;
|
||||
using Content.Shared.NodeContainer.NodeGroups;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.NodeContainer.NodeGroups
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using Content.Shared.NodeContainer;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
using Content.Shared.NodeContainer;
|
||||
|
||||
namespace Content.Server.NodeContainer.Nodes
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue