From cad7a9ad5457879b3c8b376985ad24d08128a53c Mon Sep 17 00:00:00 2001 From: SethLafuente <84478872+SethLafuente@users.noreply.github.com> Date: Sun, 25 Jul 2021 22:36:27 -0700 Subject: [PATCH 01/76] Gave lab Coats Storage Room (#4370) Co-authored-by: SETh lafuente --- .../Entities/Clothing/OuterClothing/coats.yml | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/coats.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/coats.yml index b6654eec1b..e8662570c4 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/coats.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/coats.yml @@ -8,6 +8,8 @@ sprite: Clothing/OuterClothing/Coats/bomber.rsi - type: Clothing sprite: Clothing/OuterClothing/Coats/bomber.rsi + - type: Storage + capacity: 10 - type: entity parent: ClothingOuterBase @@ -19,6 +21,8 @@ sprite: Clothing/OuterClothing/Coats/detective.rsi - type: Clothing sprite: Clothing/OuterClothing/Coats/detective.rsi + - type: Storage + capacity: 10 - type: entity parent: ClothingOuterBase @@ -30,6 +34,8 @@ sprite: Clothing/OuterClothing/Coats/gentlecoat.rsi - type: Clothing sprite: Clothing/OuterClothing/Coats/gentlecoat.rsi + - type: Storage + capacity: 10 - type: entity parent: ClothingOuterBase @@ -41,6 +47,8 @@ sprite: Clothing/OuterClothing/Coats/hos_trenchcoat.rsi - type: Clothing sprite: Clothing/OuterClothing/Coats/hos_trenchcoat.rsi + - type: Storage + capacity: 10 - type: entity parent: ClothingOuterBase @@ -52,6 +60,8 @@ sprite: Clothing/OuterClothing/Coats/insp_coat.rsi - type: Clothing sprite: Clothing/OuterClothing/Coats/insp_coat.rsi + - type: Storage + capacity: 10 - type: entity parent: ClothingOuterBase @@ -63,6 +73,8 @@ sprite: Clothing/OuterClothing/Coats/jensencoat.rsi - type: Clothing sprite: Clothing/OuterClothing/Coats/jensencoat.rsi + - type: Storage + capacity: 10 - type: entity parent: ClothingOuterBase @@ -74,6 +86,8 @@ sprite: Clothing/OuterClothing/Coats/labcoat.rsi - type: Clothing sprite: Clothing/OuterClothing/Coats/labcoat.rsi + - type: Storage + capacity: 10 - type: entity parent: ClothingOuterBase @@ -85,6 +99,8 @@ sprite: Clothing/OuterClothing/Coats/labcoat_chem.rsi - type: Clothing sprite: Clothing/OuterClothing/Coats/labcoat_chem.rsi + - type: Storage + capacity: 10 - type: entity parent: ClothingOuterBase @@ -96,6 +112,8 @@ sprite: Clothing/OuterClothing/Coats/labcoat_cmo.rsi - type: Clothing sprite: Clothing/OuterClothing/Coats/labcoat_cmo.rsi + - type: Storage + capacity: 10 - type: entity parent: ClothingOuterBase @@ -107,3 +125,5 @@ sprite: Clothing/OuterClothing/Coats/pirate.rsi - type: Clothing sprite: Clothing/OuterClothing/Coats/pirate.rsi + - type: Storage + capacity: 10 From 4dcc384deda7d7772692804c7970b1427fcce0e3 Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 26 Jul 2021 01:37:30 -0400 Subject: [PATCH 02/76] Automatic changelog update --- Resources/Changelog/Changelog.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 866ea757d5..2987e1d464 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1598,3 +1598,8 @@ Entries: - {message: Shuttle-relative movement for when we get shuttle rotation., type: Add} id: 284 time: '2021-07-25T15:48:22.0000000+00:00' +- author: Seth + changes: + - {message: Added Storage Room to Coats, type: Add} + id: 285 + time: '2021-07-26T05:36:27.0000000+00:00' From 8661bdf3da6255fab45afe52a98d7fe7b837d702 Mon Sep 17 00:00:00 2001 From: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> Date: Mon, 26 Jul 2021 00:04:47 -0700 Subject: [PATCH 03/76] Fix typo in inventory component throw --- Content.Server/Inventory/Components/InventoryComponent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Server/Inventory/Components/InventoryComponent.cs b/Content.Server/Inventory/Components/InventoryComponent.cs index 6ba250d52d..8f1d0d7ad1 100644 --- a/Content.Server/Inventory/Components/InventoryComponent.cs +++ b/Content.Server/Inventory/Components/InventoryComponent.cs @@ -460,7 +460,7 @@ namespace Content.Server.Inventory.Components { if (!HasSlot(slot)) { - throw new InvalidOperationException($"Slow '{slot}' does not exist."); + throw new InvalidOperationException($"Slot '{slot}' does not exist."); } ForceUnequip(slot); From e826615f81ec514ff2232b87e121deeca7e3e630 Mon Sep 17 00:00:00 2001 From: Swept Date: Mon, 26 Jul 2021 07:10:00 +0000 Subject: [PATCH 04/76] Updates broken description in sprays.yml --- .../Prototypes/Entities/Objects/Specific/Hydroponics/sprays.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Prototypes/Entities/Objects/Specific/Hydroponics/sprays.yml b/Resources/Prototypes/Entities/Objects/Specific/Hydroponics/sprays.yml index a53113ae72..abc6ffceea 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Hydroponics/sprays.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Hydroponics/sprays.yml @@ -50,7 +50,7 @@ name: pest spray id: PestSpray parent: WeedSpray - description: Objects/Tools/Hydroponics/sprays.rsi + description: It's some pest eliminator spray! Do not inhale! suffix: "Filled" components: - type: Sprite From 7fa10bd17bbb9d063625eb65705fd4119c52829b Mon Sep 17 00:00:00 2001 From: Vera Aguilera Puerto Date: Mon, 26 Jul 2021 11:05:43 +0200 Subject: [PATCH 05/76] Remove atmos archiving. --- .../EntitySystems/AtmosphereSystem.Gases.cs | 25 +++++++------------ .../EntitySystems/AtmosphereSystem.LINDA.cs | 11 -------- .../AtmosphereSystem.Superconductivity.cs | 9 ++----- Content.Server/Atmos/GasMixture.cs | 21 ---------------- Content.Server/Atmos/TileAtmosphere.cs | 6 ----- 5 files changed, 11 insertions(+), 61 deletions(-) diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Gases.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Gases.cs index ba11dd0740..16469a0a58 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Gases.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Gases.cs @@ -42,13 +42,6 @@ namespace Content.Server.Atmos.EntitySystems return MathF.Max(NumericsHelpers.HorizontalAdd(tmp), Atmospherics.MinimumHeatCapacity); } - public float GetHeatCapacityArchived(GasMixture mixture) - { - Span tmp = stackalloc float[mixture.Moles.Length]; - NumericsHelpers.Multiply(mixture.MolesArchived, GasSpecificHeats, tmp); - return MathF.Max(NumericsHelpers.HorizontalAdd(tmp), Atmospherics.MinimumHeatCapacity); - } - public float GetThermalEnergy(GasMixture mixture) { return mixture.Temperature * GetHeatCapacity(mixture); @@ -79,7 +72,7 @@ namespace Content.Server.Atmos.EntitySystems public float Share(GasMixture receiver, GasMixture sharer, int atmosAdjacentTurfs) { - var temperatureDelta = receiver.TemperatureArchived - sharer.TemperatureArchived; + var temperatureDelta = receiver.Temperature - sharer.Temperature; var absTemperatureDelta = Math.Abs(temperatureDelta); var oldHeatCapacity = 0f; var oldSharerHeatCapacity = 0f; @@ -130,12 +123,12 @@ namespace Content.Server.Atmos.EntitySystems // Transfer of thermal energy (via changed heat capacity) between self and sharer. if (!receiver.Immutable && newHeatCapacity > Atmospherics.MinimumHeatCapacity) { - receiver.Temperature = ((oldHeatCapacity * receiver.Temperature) - (heatCapacityToSharer * receiver.TemperatureArchived) + (heatCapacitySharerToThis * sharer.TemperatureArchived)) / newHeatCapacity; + receiver.Temperature = ((oldHeatCapacity * receiver.Temperature) - (heatCapacityToSharer * receiver.Temperature) + (heatCapacitySharerToThis * sharer.Temperature)) / newHeatCapacity; } if (!sharer.Immutable && newSharerHeatCapacity > Atmospherics.MinimumHeatCapacity) { - sharer.Temperature = ((oldSharerHeatCapacity * sharer.Temperature) - (heatCapacitySharerToThis * sharer.TemperatureArchived) + (heatCapacityToSharer*receiver.TemperatureArchived)) / newSharerHeatCapacity; + sharer.Temperature = ((oldSharerHeatCapacity * sharer.Temperature) - (heatCapacitySharerToThis * sharer.Temperature) + (heatCapacityToSharer*receiver.Temperature)) / newSharerHeatCapacity; } // Thermal energy of the system (self and sharer) is unchanged. @@ -154,17 +147,17 @@ namespace Content.Server.Atmos.EntitySystems var moles = receiver.TotalMoles; var theirMoles = sharer.TotalMoles; - return (receiver.TemperatureArchived * (moles + movedMoles)) - (sharer.TemperatureArchived * (theirMoles - movedMoles)) * Atmospherics.R / receiver.Volume; + return (receiver.Temperature * (moles + movedMoles)) - (sharer.Temperature * (theirMoles - movedMoles)) * Atmospherics.R / receiver.Volume; } public float TemperatureShare(GasMixture receiver, GasMixture sharer, float conductionCoefficient) { - var temperatureDelta = receiver.TemperatureArchived - sharer.TemperatureArchived; + var temperatureDelta = receiver.Temperature - sharer.Temperature; if (MathF.Abs(temperatureDelta) > Atmospherics.MinimumTemperatureDeltaToConsider) { - var heatCapacity = GetHeatCapacityArchived(receiver); - var sharerHeatCapacity = GetHeatCapacityArchived(sharer); + var heatCapacity = GetHeatCapacity(receiver); + var sharerHeatCapacity = GetHeatCapacity(sharer); if (sharerHeatCapacity > Atmospherics.MinimumHeatCapacity && heatCapacity > Atmospherics.MinimumHeatCapacity) { @@ -183,10 +176,10 @@ namespace Content.Server.Atmos.EntitySystems public float TemperatureShare(GasMixture receiver, float conductionCoefficient, float sharerTemperature, float sharerHeatCapacity) { - var temperatureDelta = receiver.TemperatureArchived - sharerTemperature; + var temperatureDelta = receiver.Temperature - sharerTemperature; if (MathF.Abs(temperatureDelta) > Atmospherics.MinimumTemperatureDeltaToConsider) { - var heatCapacity = GetHeatCapacityArchived(receiver); + var heatCapacity = GetHeatCapacity(receiver); if (sharerHeatCapacity > Atmospherics.MinimumHeatCapacity && heatCapacity > Atmospherics.MinimumHeatCapacity) { diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs index 93b73458b1..12c7179a45 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs @@ -16,9 +16,6 @@ namespace Content.Server.Atmos.EntitySystems return; } - if (tile.ArchivedCycle < fireCount) - Archive(tile, fireCount); - tile.CurrentCycle = fireCount; var adjacentTileLength = 0; @@ -38,7 +35,6 @@ namespace Content.Server.Atmos.EntitySystems // If the tile is null or has no air, we don't do anything for it. if(enemyTile?.Air == null) continue; if (fireCount <= enemyTile.CurrentCycle) continue; - Archive(enemyTile, fireCount); var shouldShareAir = false; @@ -110,13 +106,6 @@ namespace Content.Server.Atmos.EntitySystems RemoveActiveTile(gridAtmosphere, tile); } - private void Archive(TileAtmosphere tile, int fireCount) - { - tile.Air?.Archive(); - tile.ArchivedCycle = fireCount; - tile.TemperatureArchived = tile.Temperature; - } - private void LastShareCheck(TileAtmosphere tile) { if (tile.Air == null || tile.ExcitedGroup == null) diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Superconductivity.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Superconductivity.cs index 2853a6934e..fe21db69d4 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Superconductivity.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Superconductivity.cs @@ -21,9 +21,6 @@ namespace Content.Server.Atmos.EntitySystems if (adjacent == null || adjacent.ThermalConductivity == 0f) continue; - if(adjacent.ArchivedCycle < gridAtmosphere.UpdateCounter) - Archive(adjacent, gridAtmosphere.UpdateCounter); - NeighborConductWithSource(gridAtmosphere, adjacent, tile); ConsiderSuperconductivity(gridAtmosphere, adjacent); @@ -37,8 +34,6 @@ namespace Content.Server.Atmos.EntitySystems { if(tile.Air == null) { - if(tile.ArchivedCycle < gridAtmosphere.UpdateCounter) - Archive(tile, gridAtmosphere.UpdateCounter); return AtmosDirection.All; } @@ -128,7 +123,7 @@ namespace Content.Server.Atmos.EntitySystems private void TemperatureShareMutualSolid(TileAtmosphere tile, TileAtmosphere other, float conductionCoefficient) { - var deltaTemperature = (tile.TemperatureArchived - other.TemperatureArchived); + var deltaTemperature = (tile.Temperature - other.Temperature); if (MathF.Abs(deltaTemperature) > Atmospherics.MinimumTemperatureDeltaToConsider && tile.HeatCapacity != 0f && other.HeatCapacity != 0f) { @@ -146,7 +141,7 @@ namespace Content.Server.Atmos.EntitySystems if (tile.Temperature > Atmospherics.T0C) { // Hardcoded space temperature. - var deltaTemperature = (tile.TemperatureArchived - Atmospherics.TCMB); + var deltaTemperature = (tile.Temperature - Atmospherics.TCMB); if ((tile.HeatCapacity > 0) && (MathF.Abs(deltaTemperature) > Atmospherics.MinimumTemperatureDeltaToConsider)) { var heat = tile.ThermalConductivity * deltaTemperature * (tile.HeatCapacity * diff --git a/Content.Server/Atmos/GasMixture.cs b/Content.Server/Atmos/GasMixture.cs index dddad7366f..f4d013b2ab 100644 --- a/Content.Server/Atmos/GasMixture.cs +++ b/Content.Server/Atmos/GasMixture.cs @@ -26,9 +26,6 @@ namespace Content.Server.Atmos [DataField("moles")] [ViewVariables] public float[] Moles = new float[Atmospherics.AdjustedNumberOfGases]; - [DataField("molesArchived")] [ViewVariables] - public float[] MolesArchived = new float[Atmospherics.AdjustedNumberOfGases]; - [DataField("temperature")] [ViewVariables] private float _temperature = Atmospherics.TCMB; @@ -73,9 +70,6 @@ namespace Content.Server.Atmos } } - [DataField("temperatureArchived")] [ViewVariables] - public float TemperatureArchived { get; private set; } - [DataField("volume")] [ViewVariables] public float Volume { get; set; } @@ -96,13 +90,6 @@ namespace Content.Server.Atmos Immutable = true; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Archive() - { - Moles.AsSpan().CopyTo(MolesArchived.AsSpan()); - TemperatureArchived = Temperature; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public float GetMoles(int gasId) { @@ -253,7 +240,6 @@ namespace Content.Server.Atmos { // The arrays MUST have a specific length. Array.Resize(ref Moles, Atmospherics.AdjustedNumberOfGases); - Array.Resize(ref MolesArchived, Atmospherics.AdjustedNumberOfGases); } public override bool Equals(object? obj) @@ -268,12 +254,10 @@ namespace Content.Server.Atmos if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; return Moles.SequenceEqual(other.Moles) - && MolesArchived.SequenceEqual(other.MolesArchived) && _temperature.Equals(other._temperature) && ReactionResults.SequenceEqual(other.ReactionResults) && Immutable == other.Immutable && LastShare.Equals(other.LastShare) - && TemperatureArchived.Equals(other.TemperatureArchived) && Volume.Equals(other.Volume); } @@ -284,13 +268,10 @@ namespace Content.Server.Atmos for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++) { var moles = Moles[i]; - var molesArchived = MolesArchived[i]; hashCode.Add(moles); - hashCode.Add(molesArchived); } hashCode.Add(_temperature); - hashCode.Add(TemperatureArchived); hashCode.Add(Immutable); hashCode.Add(LastShare); hashCode.Add(Volume); @@ -303,11 +284,9 @@ namespace Content.Server.Atmos var newMixture = new GasMixture() { Moles = (float[])Moles.Clone(), - MolesArchived = (float[])MolesArchived.Clone(), _temperature = _temperature, Immutable = Immutable, LastShare = LastShare, - TemperatureArchived = TemperatureArchived, Volume = Volume, }; return newMixture; diff --git a/Content.Server/Atmos/TileAtmosphere.cs b/Content.Server/Atmos/TileAtmosphere.cs index 5bcfc710b4..b90ced4c02 100644 --- a/Content.Server/Atmos/TileAtmosphere.cs +++ b/Content.Server/Atmos/TileAtmosphere.cs @@ -13,18 +13,12 @@ namespace Content.Server.Atmos /// public class TileAtmosphere : IGasMixtureHolder { - [ViewVariables] - public int ArchivedCycle; - [ViewVariables] public int CurrentCycle; [ViewVariables] public float Temperature { get; set; } = Atmospherics.T20C; - [ViewVariables] - public float TemperatureArchived { get; set; } = Atmospherics.T20C; - [ViewVariables] public TileAtmosphere? PressureSpecificTarget { get; set; } From 86cecd3b5e09668da97ed5ca46a5c148ac898199 Mon Sep 17 00:00:00 2001 From: Vera Aguilera Puerto Date: Mon, 26 Jul 2021 11:06:34 +0200 Subject: [PATCH 06/76] Add CVar for disabling/enabling excited groups. --- .../EntitySystems/AtmosphereSystem.CVars.cs | 2 ++ .../EntitySystems/AtmosphereSystem.LINDA.cs | 31 ++++++++++--------- .../AtmosphereSystem.Processing.cs | 3 +- Content.Shared/CCVar/CCVars.cs | 8 ++++- 4 files changed, 28 insertions(+), 16 deletions(-) diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.CVars.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.CVars.cs index 4693761b33..df9fbd7c1e 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.CVars.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.CVars.cs @@ -15,6 +15,7 @@ namespace Content.Server.Atmos.EntitySystems public bool MonstermosRipTiles { get; private set; } public bool GridImpulse { get; private set; } public bool Superconduction { get; private set; } + public bool ExcitedGroups { get; private set; } public bool ExcitedGroupsSpaceIsAllConsuming { get; private set; } public float AtmosMaxProcessTime { get; private set; } public float AtmosTickRate { get; private set; } @@ -31,6 +32,7 @@ namespace Content.Server.Atmos.EntitySystems _cfg.OnValueChanged(CCVars.Superconduction, value => Superconduction = value, true); _cfg.OnValueChanged(CCVars.AtmosMaxProcessTime, value => AtmosMaxProcessTime = value, true); _cfg.OnValueChanged(CCVars.AtmosTickRate, value => AtmosTickRate = value, true); + _cfg.OnValueChanged(CCVars.ExcitedGroups, value => ExcitedGroups = value, true); _cfg.OnValueChanged(CCVars.ExcitedGroupsSpaceIsAllConsuming, value => ExcitedGroupsSpaceIsAllConsuming = value, true); } } diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs index 12c7179a45..2629bb4d33 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs @@ -38,7 +38,7 @@ namespace Content.Server.Atmos.EntitySystems var shouldShareAir = false; - if (tile.ExcitedGroup != null && enemyTile.ExcitedGroup != null) + if (ExcitedGroups && tile.ExcitedGroup != null && enemyTile.ExcitedGroup != null) { if (tile.ExcitedGroup != enemyTile.ExcitedGroup) { @@ -53,21 +53,24 @@ namespace Content.Server.Atmos.EntitySystems AddActiveTile(gridAtmosphere, enemyTile); } - var excitedGroup = tile.ExcitedGroup; - excitedGroup ??= enemyTile.ExcitedGroup; - - if (excitedGroup == null) + if (ExcitedGroups) { - excitedGroup = new ExcitedGroup(); - gridAtmosphere.ExcitedGroups.Add(excitedGroup); + var excitedGroup = tile.ExcitedGroup; + excitedGroup ??= enemyTile.ExcitedGroup; + + if (excitedGroup == null) + { + excitedGroup = new ExcitedGroup(); + gridAtmosphere.ExcitedGroups.Add(excitedGroup); + } + + if (tile.ExcitedGroup == null) + ExcitedGroupAddTile(excitedGroup, tile); + + if(enemyTile.ExcitedGroup == null) + ExcitedGroupAddTile(excitedGroup, enemyTile); } - if (tile.ExcitedGroup == null) - ExcitedGroupAddTile(excitedGroup, tile); - - if(enemyTile.ExcitedGroup == null) - ExcitedGroupAddTile(excitedGroup, enemyTile); - shouldShareAir = true; } @@ -102,7 +105,7 @@ namespace Content.Server.Atmos.EntitySystems if (ConsiderSuperconductivity(gridAtmosphere, tile, true)) remove = false; - if(tile.ExcitedGroup == null && remove) + if(ExcitedGroups && tile.ExcitedGroup == null && remove) RemoveActiveTile(gridAtmosphere, tile); } diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs index 184e0bf9f5..db87ed7c10 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs @@ -281,7 +281,8 @@ namespace Content.Server.Atmos.EntitySystems } atmosphere.ProcessingPaused = false; - atmosphere.State = AtmosphereProcessingState.ExcitedGroups; + // Next state depends on whether excited groups are enabled or not. + atmosphere.State = ExcitedGroups ? AtmosphereProcessingState.ExcitedGroups : AtmosphereProcessingState.HighPressureDelta; continue; case AtmosphereProcessingState.ExcitedGroups: if (!ProcessExcitedGroups(atmosphere)) diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index 966377ae77..3b2091492c 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -291,7 +291,6 @@ namespace Content.Shared.CCVar public static readonly CVarDef AtmosGridImpulse = CVarDef.Create("atmos.grid_impulse", false, CVar.SERVERONLY); - /// /// Whether atmos superconduction is enabled. /// @@ -299,10 +298,17 @@ namespace Content.Shared.CCVar public static readonly CVarDef Superconduction = CVarDef.Create("atmos.superconduction", false, CVar.SERVERONLY); + /// + /// Whether excited groups will be processed and created. + /// + public static readonly CVarDef ExcitedGroups = + CVarDef.Create("atmos.excited_groups", true, CVar.SERVERONLY); + /// /// Whether all tiles in an excited group will clear themselves once being exposed to space. /// Similar to , without none of the tile ripping or /// things being thrown around very violently. + /// Needs to be enabled to work. /// public static readonly CVarDef ExcitedGroupsSpaceIsAllConsuming = CVarDef.Create("atmos.excited_groups_space_is_all_consuming", false, CVar.SERVERONLY); From 5d929485edfdbd26ae95bdecf0405149cc3a56c8 Mon Sep 17 00:00:00 2001 From: Vera Aguilera Puerto Date: Mon, 26 Jul 2021 11:28:43 +0200 Subject: [PATCH 07/76] Hotspot passes MapManager to GetTileRef. --- Content.Server/Atmos/EntitySystems/AtmosphereSystem.Hotspot.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Hotspot.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Hotspot.cs index 03052e17df..df78bc015d 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Hotspot.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Hotspot.cs @@ -140,7 +140,7 @@ namespace Content.Server.Atmos.EntitySystems Merge(tile.Air, affected); } - var tileRef = tile.GridIndices.GetTileRef(tile.GridIndex); + var tileRef = tile.GridIndices.GetTileRef(tile.GridIndex, _mapManager); foreach (var entity in tileRef.GetEntitiesInTileFast()) { From 995f1dbf1a8bd2dbe5b8fc16263f961945154ff8 Mon Sep 17 00:00:00 2001 From: Vera Aguilera Puerto Date: Mon, 26 Jul 2021 11:32:59 +0200 Subject: [PATCH 08/76] Remove archived moles from saltern. --- Resources/Maps/saltern.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/Resources/Maps/saltern.yml b/Resources/Maps/saltern.yml index 2c0fd2c5ac..8ca177e8e3 100644 --- a/Resources/Maps/saltern.yml +++ b/Resources/Maps/saltern.yml @@ -12318,15 +12318,6 @@ entities: uniqueMixes: - volume: 2500 temperature: 293.15 - molesArchived: - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 moles: - 21.824879 - 82.10312 From 4e93340fb0ffa78047df1263c733b4e836147934 Mon Sep 17 00:00:00 2001 From: Vera Aguilera Puerto Date: Mon, 26 Jul 2021 12:13:16 +0200 Subject: [PATCH 09/76] Update engine submodule to v0.5.0 --- RobustToolbox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RobustToolbox b/RobustToolbox index 3a86c827ea..8c1e075c91 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit 3a86c827ea02f46b213f473f6479fbfaed35095e +Subproject commit 8c1e075c919604fe2e27cd6a2bbe22fceeb89249 From 1033d8bbe5d3bbec4b12b15b7b2063411bc03e52 Mon Sep 17 00:00:00 2001 From: Vera Aguilera Puerto Date: Mon, 26 Jul 2021 12:58:17 +0200 Subject: [PATCH 10/76] Use EntitySystem dependencies in a bunch of systems. --- .../EntitySystems/GasTileOverlaySystem.cs | 5 +---- Content.Client/Audio/BackgroundAudioSystem.cs | 7 +++--- Content.Client/DragDrop/DragDropSystem.cs | 6 ++--- .../Weapons/Melee/MeleeWeaponSystem.cs | 5 +++-- .../Weapons/Ranged/RangedWeaponSystem.cs | 13 ++--------- .../Accessible/AiReachableSystem.cs | 4 +--- .../AI/Steering/AiSteeringSystem.cs | 4 +--- .../Atmos/EntitySystems/AirtightSystem.cs | 8 +++---- .../EntitySystems/AtmosDebugOverlaySystem.cs | 4 ++-- .../EntitySystems/AtmosphereSystem.Grid.cs | 5 +---- .../Atmos/EntitySystems/GasTankSystem.cs | 7 +++--- .../EntitySystems/GasTileOverlaySystem.cs | 4 +--- .../GasDualPortVentPumpSystem.cs | 10 +++++---- .../EntitySystems/GasVolumePumpSystem.cs | 8 +++---- .../Piping/EntitySystems/AtmosDeviceSystem.cs | 5 +++-- .../AtmosUnsafeUnanchorSystem.cs | 13 ++++++----- .../Other/EntitySystems/GasMinerSystem.cs | 15 +++++++------ .../Unary/EntitySystems/GasCanisterSystem.cs | 21 +++++++++--------- .../EntitySystems/GasOutletInjectorSystem.cs | 8 ++++--- .../EntitySystems/GasPassiveVentSystem.cs | 8 ++++--- .../EntitySystems/GasThermoMachineSystem.cs | 5 ++++- .../Unary/EntitySystems/GasVentPumpSystem.cs | 8 ++++--- .../EntitySystems/GasVentScrubberSystem.cs | 12 +++++----- .../Surgery/Components/SurgeryToolSystem.cs | 5 ++++- .../Construction/ConstructionSystem.cs | 8 +++---- .../Destructible/DestructibleSystem.cs | 14 ++---------- .../EntitySystems/SpawnAfterInteractSystem.cs | 8 ++++--- Content.Server/Hands/HandsSystem.cs | 8 +++++-- .../Interaction/InteractionSystem.cs | 22 +++++++++---------- .../Pointing/EntitySystems/PointingSystem.cs | 3 ++- .../Interaction/SharedInteractionSystem.cs | 7 ++++-- 31 files changed, 130 insertions(+), 130 deletions(-) diff --git a/Content.Client/Atmos/EntitySystems/GasTileOverlaySystem.cs b/Content.Client/Atmos/EntitySystems/GasTileOverlaySystem.cs index 9beb61a3c9..ee46b25881 100644 --- a/Content.Client/Atmos/EntitySystems/GasTileOverlaySystem.cs +++ b/Content.Client/Atmos/EntitySystems/GasTileOverlaySystem.cs @@ -19,6 +19,7 @@ namespace Content.Client.Atmos.EntitySystems { [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IResourceCache _resourceCache = default!; + [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; // Gas overlays private readonly float[] _timer = new float[Atmospherics.TotalNumberOfGases]; @@ -38,16 +39,12 @@ namespace Content.Client.Atmos.EntitySystems private readonly Dictionary> _tileData = new(); - private AtmosphereSystem _atmosphereSystem = default!; - public override void Initialize() { base.Initialize(); SubscribeNetworkEvent(HandleGasOverlayMessage); _mapManager.OnGridRemoved += OnGridRemoved; - _atmosphereSystem = Get(); - for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++) { var overlay = _atmosphereSystem.GetOverlay(i); diff --git a/Content.Client/Audio/BackgroundAudioSystem.cs b/Content.Client/Audio/BackgroundAudioSystem.cs index 4d79891649..8a6f2b2607 100644 --- a/Content.Client/Audio/BackgroundAudioSystem.cs +++ b/Content.Client/Audio/BackgroundAudioSystem.cs @@ -25,6 +25,7 @@ namespace Content.Client.Audio [Dependency] private readonly IConfigurationManager _configManager = default!; [Dependency] private readonly IStateManager _stateManager = default!; [Dependency] private readonly IBaseClient _client = default!; + [Dependency] private readonly ClientGameTicker _gameTicker = default!; private SoundCollectionPrototype _ambientCollection = default!; @@ -48,7 +49,7 @@ namespace Content.Client.Audio _client.PlayerJoinedServer += OnJoin; _client.PlayerLeaveServer += OnLeave; - Get().LobbyStatusUpdated += LobbySongReceived; + _gameTicker.LobbyStatusUpdated += LobbySongReceived; } public override void Shutdown() @@ -60,7 +61,7 @@ namespace Content.Client.Audio _client.PlayerJoinedServer -= OnJoin; _client.PlayerLeaveServer -= OnLeave; - Get().LobbyStatusUpdated -= LobbySongReceived; + _gameTicker.LobbyStatusUpdated -= LobbySongReceived; EndAmbience(); EndLobbyMusic(); @@ -165,7 +166,7 @@ namespace Content.Client.Audio private void StartLobbyMusic() { EndLobbyMusic(); - var file = Get().LobbySong; + var file = _gameTicker.LobbySong; if (file == null) // We have not received the lobby song yet. { return; diff --git a/Content.Client/DragDrop/DragDropSystem.cs b/Content.Client/DragDrop/DragDropSystem.cs index 7051978def..7163623a4b 100644 --- a/Content.Client/DragDrop/DragDropSystem.cs +++ b/Content.Client/DragDrop/DragDropSystem.cs @@ -36,6 +36,8 @@ namespace Content.Client.DragDrop [Dependency] private readonly IEyeManager _eyeManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; + [Dependency] private readonly InputSystem _inputSystem = default!; // how often to recheck possible targets (prevents calling expensive // check logic each update) @@ -69,8 +71,6 @@ namespace Content.Client.DragDrop private ShaderInstance? _dropTargetInRangeShader; private ShaderInstance? _dropTargetOutOfRangeShader; - private SharedInteractionSystem _interactionSystem = default!; - private InputSystem _inputSystem = default!; private readonly List _highlightedSprites = new(); @@ -80,8 +80,6 @@ namespace Content.Client.DragDrop _dropTargetInRangeShader = _prototypeManager.Index(ShaderDropTargetInRange).Instance(); _dropTargetOutOfRangeShader = _prototypeManager.Index(ShaderDropTargetOutOfRange).Instance(); - _interactionSystem = Get(); - _inputSystem = Get(); // needs to fire on mouseup and mousedown so we can detect a drag / drop CommandBinds.Builder .Bind(EngineKeyFunctions.Use, new PointerInputCmdHandler(OnUse, false)) diff --git a/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs b/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs index d793e3c501..0253c71f76 100644 --- a/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs +++ b/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs @@ -18,6 +18,7 @@ namespace Content.Client.Weapons.Melee { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly EffectSystem _effectSystem = default!; public override void Initialize() { @@ -67,7 +68,6 @@ namespace Content.Client.Weapons.Melee source.TryGetComponent(out ISpriteComponent? sourceSprite) && sourceSprite.BaseRSI?.Path != null) { - var sys = Get(); var curTime = _gameTiming.CurTime; var effect = new EffectSystemMessage { @@ -81,7 +81,8 @@ namespace Content.Client.Weapons.Melee Born = curTime, DeathTime = curTime.Add(TimeSpan.FromMilliseconds(300f)), }; - sys.CreateEffect(effect); + + _effectSystem.CreateEffect(effect); } } diff --git a/Content.Client/Weapons/Ranged/RangedWeaponSystem.cs b/Content.Client/Weapons/Ranged/RangedWeaponSystem.cs index 85de5e4d55..38ffb63802 100644 --- a/Content.Client/Weapons/Ranged/RangedWeaponSystem.cs +++ b/Content.Client/Weapons/Ranged/RangedWeaponSystem.cs @@ -24,21 +24,12 @@ namespace Content.Client.Weapons.Ranged [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IInputManager _inputManager = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly InputSystem _inputSystem = default!; + [Dependency] private readonly CombatModeSystem _combatModeSystem = default!; - private InputSystem _inputSystem = default!; - private CombatModeSystem _combatModeSystem = default!; private bool _blocked; private int _shotCounter; - public override void Initialize() - { - base.Initialize(); - - IoCManager.InjectDependencies(this); - _inputSystem = Get(); - _combatModeSystem = Get(); - } - public override void Update(float frameTime) { base.Update(frameTime); diff --git a/Content.Server/AI/Pathfinding/Accessible/AiReachableSystem.cs b/Content.Server/AI/Pathfinding/Accessible/AiReachableSystem.cs index 5f66ec5e2e..64791ce9ff 100644 --- a/Content.Server/AI/Pathfinding/Accessible/AiReachableSystem.cs +++ b/Content.Server/AI/Pathfinding/Accessible/AiReachableSystem.cs @@ -37,8 +37,7 @@ namespace Content.Server.AI.Pathfinding.Accessible */ [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; - - private PathfindingSystem _pathfindingSystem = default!; + [Dependency] private readonly PathfindingSystem _pathfindingSystem = default!; /// /// Queued region updates @@ -80,7 +79,6 @@ namespace Content.Server.AI.Pathfinding.Accessible public override void Initialize() { - _pathfindingSystem = Get(); SubscribeLocalEvent(Reset); SubscribeLocalEvent(RecalculateNodeRegions); #if DEBUG diff --git a/Content.Server/AI/Steering/AiSteeringSystem.cs b/Content.Server/AI/Steering/AiSteeringSystem.cs index 7db0a97349..b7e1e468c6 100644 --- a/Content.Server/AI/Steering/AiSteeringSystem.cs +++ b/Content.Server/AI/Steering/AiSteeringSystem.cs @@ -27,8 +27,7 @@ namespace Content.Server.AI.Steering // http://www.red3d.com/cwr/papers/1999/gdc99steer.html for a steering overview [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IPauseManager _pauseManager = default!; - - private PathfindingSystem _pathfindingSystem = default!; + [Dependency] private readonly PathfindingSystem _pathfindingSystem = default!; /// /// Whether we try to avoid non-blocking physics objects @@ -87,7 +86,6 @@ namespace Content.Server.AI.Steering public override void Initialize() { base.Initialize(); - _pathfindingSystem = Get(); for (var i = 0; i < AgentListCount; i++) { diff --git a/Content.Server/Atmos/EntitySystems/AirtightSystem.cs b/Content.Server/Atmos/EntitySystems/AirtightSystem.cs index a1fea661a1..b3b0d4dd5a 100644 --- a/Content.Server/Atmos/EntitySystems/AirtightSystem.cs +++ b/Content.Server/Atmos/EntitySystems/AirtightSystem.cs @@ -12,6 +12,7 @@ namespace Content.Server.Atmos.EntitySystems public class AirtightSystem : EntitySystem { [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; public override void Initialize() { @@ -42,7 +43,7 @@ namespace Content.Server.Atmos.EntitySystems if (airtight.FixVacuum) { - Get().FixVacuum(airtight.LastPosition.Item1, airtight.LastPosition.Item2); + _atmosphereSystem.FixVacuum(airtight.LastPosition.Item1, airtight.LastPosition.Item2); } } @@ -93,9 +94,8 @@ namespace Content.Server.Atmos.EntitySystems if (!gridId.IsValid()) return; - var atmosphereSystem = Get(); - atmosphereSystem.UpdateAdjacent(gridId, pos); - atmosphereSystem.InvalidateTile(gridId, pos); + _atmosphereSystem.UpdateAdjacent(gridId, pos); + _atmosphereSystem.InvalidateTile(gridId, pos); } private AtmosDirection Rotate(AtmosDirection myDirection, Angle myAngle) diff --git a/Content.Server/Atmos/EntitySystems/AtmosDebugOverlaySystem.cs b/Content.Server/Atmos/EntitySystems/AtmosDebugOverlaySystem.cs index 87a9bc0952..d28ac4324f 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosDebugOverlaySystem.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosDebugOverlaySystem.cs @@ -21,6 +21,7 @@ namespace Content.Server.Atmos.EntitySystems [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IConfigurationManager _configManager = default!; + [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; /// /// Players allowed to see the atmos debug overlay. @@ -127,7 +128,6 @@ namespace Content.Server.Atmos.EntitySystems AccumulatedFrameTime -= _updateCooldown; var currentTick = _gameTiming.CurTick; - var atmosphereSystem = Get(); // Now we'll go through each player, then through each chunk in range of that player checking if the player is still in range // If they are, check if they need the new data to send (i.e. if there's an overlay for the gas). @@ -157,7 +157,7 @@ namespace Content.Server.Atmos.EntitySystems for (var x = 0; x < LocalViewRange; x++) { var Vector2i = new Vector2i(baseTile.X + x, baseTile.Y + y); - debugOverlayContent[index++] = ConvertTileToData(atmosphereSystem.GetTileAtmosphereOrCreateSpace(grid, gam, Vector2i)); + debugOverlayContent[index++] = ConvertTileToData(_atmosphereSystem.GetTileAtmosphereOrCreateSpace(grid, gam, Vector2i)); } } diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Grid.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Grid.cs index 41424c990a..661b5b169f 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Grid.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Grid.cs @@ -23,13 +23,10 @@ namespace Content.Server.Atmos.EntitySystems public partial class AtmosphereSystem { [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!; - - private GasTileOverlaySystem _gasTileOverlaySystem = default!; + [Dependency] private readonly GasTileOverlaySystem _gasTileOverlaySystem = default!; private void InitializeGrid() { - _gasTileOverlaySystem = Get(); - SubscribeLocalEvent(OnGridAtmosphereInit); } diff --git a/Content.Server/Atmos/EntitySystems/GasTankSystem.cs b/Content.Server/Atmos/EntitySystems/GasTankSystem.cs index 2d3da529e9..0cebc10603 100644 --- a/Content.Server/Atmos/EntitySystems/GasTankSystem.cs +++ b/Content.Server/Atmos/EntitySystems/GasTankSystem.cs @@ -1,12 +1,15 @@ using Content.Server.Atmos.Components; using JetBrains.Annotations; using Robust.Shared.GameObjects; +using Robust.Shared.IoC; namespace Content.Server.Atmos.EntitySystems { [UsedImplicitly] public class GasTankSystem : EntitySystem { + [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; + private const float TimerDelay = 0.5f; private float _timer = 0f; @@ -19,11 +22,9 @@ namespace Content.Server.Atmos.EntitySystems if (_timer < TimerDelay) return; _timer -= TimerDelay; - var atmosphereSystem = Get(); - foreach (var gasTank in EntityManager.ComponentManager.EntityQuery()) { - atmosphereSystem.React(gasTank.Air, gasTank); + _atmosphereSystem.React(gasTank.Air, gasTank); gasTank.CheckStatus(); gasTank.UpdateUserInterface(); } diff --git a/Content.Server/Atmos/EntitySystems/GasTileOverlaySystem.cs b/Content.Server/Atmos/EntitySystems/GasTileOverlaySystem.cs index 3a24573cc3..ee9d880ca7 100644 --- a/Content.Server/Atmos/EntitySystems/GasTileOverlaySystem.cs +++ b/Content.Server/Atmos/EntitySystems/GasTileOverlaySystem.cs @@ -29,6 +29,7 @@ namespace Content.Server.Atmos.EntitySystems [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; /// /// The tiles that have had their atmos data updated since last tick @@ -58,8 +59,6 @@ namespace Content.Server.Atmos.EntitySystems /// private float _updateCooldown; - private AtmosphereSystem _atmosphereSystem = default!; - private int _thresholds; public override void Initialize() @@ -68,7 +67,6 @@ namespace Content.Server.Atmos.EntitySystems SubscribeLocalEvent(Reset); - _atmosphereSystem = Get(); _playerManager.PlayerStatusChanged += OnPlayerStatusChanged; _mapManager.OnGridRemoved += OnGridRemoved; var configManager = IoCManager.Resolve(); diff --git a/Content.Server/Atmos/Piping/Binary/EntitySystems/GasDualPortVentPumpSystem.cs b/Content.Server/Atmos/Piping/Binary/EntitySystems/GasDualPortVentPumpSystem.cs index 0782523559..24c03e399e 100644 --- a/Content.Server/Atmos/Piping/Binary/EntitySystems/GasDualPortVentPumpSystem.cs +++ b/Content.Server/Atmos/Piping/Binary/EntitySystems/GasDualPortVentPumpSystem.cs @@ -10,12 +10,15 @@ using Content.Shared.Atmos.Visuals; using JetBrains.Annotations; using Robust.Server.GameObjects; using Robust.Shared.GameObjects; +using Robust.Shared.IoC; namespace Content.Server.Atmos.Piping.Binary.EntitySystems { [UsedImplicitly] public class GasDualPortVentPumpSystem : EntitySystem { + [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; + public override void Initialize() { base.Initialize(); @@ -46,8 +49,7 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems || !nodeContainer.TryGetNode(vent.OutletName, out PipeNode? outlet)) return; - var atmosphereSystem = Get(); - var environment = atmosphereSystem.GetTileMixture(vent.Owner.Transform.Coordinates, true); + var environment = _atmosphereSystem.GetTileMixture(vent.Owner.Transform.Coordinates, true); // We're in an air-blocked tile... Do nothing. if (environment == null) @@ -68,7 +70,7 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems { var transferMoles = pressureDelta * environment.Volume / inlet.Air.Temperature * Atmospherics.R; var removed = inlet.Air.Remove(transferMoles); - atmosphereSystem.Merge(environment, removed); + _atmosphereSystem.Merge(environment, removed); } } else if (vent.PumpDirection == VentPumpDirection.Siphoning && environment.Pressure > 0f) @@ -89,7 +91,7 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems { var removed = environment.Remove(molesDelta); - Get().Merge(outlet.Air, removed); + _atmosphereSystem.Merge(outlet.Air, removed); } } } diff --git a/Content.Server/Atmos/Piping/Binary/EntitySystems/GasVolumePumpSystem.cs b/Content.Server/Atmos/Piping/Binary/EntitySystems/GasVolumePumpSystem.cs index ba4fd92866..555385d401 100644 --- a/Content.Server/Atmos/Piping/Binary/EntitySystems/GasVolumePumpSystem.cs +++ b/Content.Server/Atmos/Piping/Binary/EntitySystems/GasVolumePumpSystem.cs @@ -13,7 +13,8 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems [UsedImplicitly] public class GasVolumePumpSystem : EntitySystem { - [Dependency] private IGameTiming _gameTiming = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; public override void Initialize() { @@ -56,13 +57,12 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems // Some of the gas from the mixture leaks when overclocked. if (pump.Overclocked) { - var atmosphereSystem = Get(); - var tile = atmosphereSystem.GetTileMixture(pump.Owner.Transform.Coordinates, true); + var tile = _atmosphereSystem.GetTileMixture(pump.Owner.Transform.Coordinates, true); if (tile != null) { var leaked = removed.RemoveRatio(pump.LeakRatio); - atmosphereSystem.Merge(tile, leaked); + _atmosphereSystem.Merge(tile, leaked); } } diff --git a/Content.Server/Atmos/Piping/EntitySystems/AtmosDeviceSystem.cs b/Content.Server/Atmos/Piping/EntitySystems/AtmosDeviceSystem.cs index 5c0d7826c6..166a4d07b8 100644 --- a/Content.Server/Atmos/Piping/EntitySystems/AtmosDeviceSystem.cs +++ b/Content.Server/Atmos/Piping/EntitySystems/AtmosDeviceSystem.cs @@ -13,6 +13,7 @@ namespace Content.Server.Atmos.Piping.EntitySystems public class AtmosDeviceSystem : EntitySystem { [Dependency] private IGameTiming _gameTiming = default!; + [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; public override void Initialize() { @@ -35,7 +36,7 @@ namespace Content.Server.Atmos.Piping.EntitySystems return; // We try to add the device to a valid atmosphere. - if (!Get().AddAtmosDevice(component)) + if (!_atmosphereSystem.AddAtmosDevice(component)) return; component.LastProcess = _gameTiming.CurTime; @@ -45,7 +46,7 @@ namespace Content.Server.Atmos.Piping.EntitySystems public void LeaveAtmosphere(AtmosDeviceComponent component) { - if (!Get().RemoveAtmosDevice(component)) + if (!_atmosphereSystem.RemoveAtmosDevice(component)) return; component.LastProcess = TimeSpan.Zero; diff --git a/Content.Server/Atmos/Piping/EntitySystems/AtmosUnsafeUnanchorSystem.cs b/Content.Server/Atmos/Piping/EntitySystems/AtmosUnsafeUnanchorSystem.cs index c14fc7dafa..9e8935d0d3 100644 --- a/Content.Server/Atmos/Piping/EntitySystems/AtmosUnsafeUnanchorSystem.cs +++ b/Content.Server/Atmos/Piping/EntitySystems/AtmosUnsafeUnanchorSystem.cs @@ -8,6 +8,7 @@ using Content.Shared.Atmos; using Content.Shared.Notification.Managers; using JetBrains.Annotations; using Robust.Shared.GameObjects; +using Robust.Shared.IoC; using Robust.Shared.Localization; namespace Content.Server.Atmos.Piping.EntitySystems @@ -15,6 +16,8 @@ namespace Content.Server.Atmos.Piping.EntitySystems [UsedImplicitly] public class AtmosUnsafeUnanchorSystem : EntitySystem { + [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; + public override void Initialize() { SubscribeLocalEvent(OnBeforeUnanchored); @@ -26,7 +29,7 @@ namespace Content.Server.Atmos.Piping.EntitySystems if (!component.Enabled || !ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodes)) return; - if (Get().GetTileMixture(component.Owner.Transform.Coordinates) is not {} environment) + if (_atmosphereSystem.GetTileMixture(component.Owner.Transform.Coordinates) is not {} environment) return; foreach (var node in nodes.Nodes.Values) @@ -47,9 +50,7 @@ namespace Content.Server.Atmos.Piping.EntitySystems if (!component.Enabled || !ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodes)) return; - var atmosphereSystem = Get(); - - if (atmosphereSystem.GetTileMixture(component.Owner.Transform.Coordinates, true) is not {} environment) + if (_atmosphereSystem.GetTileMixture(component.Owner.Transform.Coordinates, true) is not {} environment) environment = GasMixture.SpaceGas; var lost = 0f; @@ -71,10 +72,10 @@ namespace Content.Server.Atmos.Piping.EntitySystems { if (node is not PipeNode pipe) continue; - atmosphereSystem.Merge(buffer, pipe.Air.Remove(sharedLoss)); + _atmosphereSystem.Merge(buffer, pipe.Air.Remove(sharedLoss)); } - atmosphereSystem.Merge(environment, buffer); + _atmosphereSystem.Merge(environment, buffer); } } } diff --git a/Content.Server/Atmos/Piping/Other/EntitySystems/GasMinerSystem.cs b/Content.Server/Atmos/Piping/Other/EntitySystems/GasMinerSystem.cs index d1e61c7b98..0a4379d7f6 100644 --- a/Content.Server/Atmos/Piping/Other/EntitySystems/GasMinerSystem.cs +++ b/Content.Server/Atmos/Piping/Other/EntitySystems/GasMinerSystem.cs @@ -6,12 +6,15 @@ using Content.Server.Atmos.Piping.Other.Components; using Content.Shared.Atmos; using JetBrains.Annotations; using Robust.Shared.GameObjects; +using Robust.Shared.IoC; namespace Content.Server.Atmos.Piping.Other.EntitySystems { [UsedImplicitly] public class GasMinerSystem : EntitySystem { + [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; + public override void Initialize() { base.Initialize(); @@ -21,9 +24,7 @@ namespace Content.Server.Atmos.Piping.Other.EntitySystems private void OnMinerUpdated(EntityUid uid, GasMinerComponent miner, AtmosDeviceUpdateEvent args) { - var atmosphereSystem = Get(); - - if (!CheckMinerOperation(atmosphereSystem, miner, out var environment) || !miner.Enabled || !miner.SpawnGas.HasValue || miner.SpawnAmount <= 0f) + if (!CheckMinerOperation(miner, out var environment) || !miner.Enabled || !miner.SpawnGas.HasValue || miner.SpawnAmount <= 0f) return; // Time to mine some gas. @@ -31,15 +32,15 @@ namespace Content.Server.Atmos.Piping.Other.EntitySystems var merger = new GasMixture(1) { Temperature = miner.SpawnTemperature }; merger.SetMoles(miner.SpawnGas.Value, miner.SpawnAmount); - atmosphereSystem.Merge(environment, merger); + _atmosphereSystem.Merge(environment, merger); } - private bool CheckMinerOperation(AtmosphereSystem atmosphereSystem, GasMinerComponent miner, [NotNullWhen(true)] out GasMixture? environment) + private bool CheckMinerOperation(GasMinerComponent miner, [NotNullWhen(true)] out GasMixture? environment) { - environment = atmosphereSystem.GetTileMixture(miner.Owner.Transform.Coordinates, true); + environment = _atmosphereSystem.GetTileMixture(miner.Owner.Transform.Coordinates, true); // Space. - if (atmosphereSystem.IsTileSpace(miner.Owner.Transform.Coordinates)) + if (_atmosphereSystem.IsTileSpace(miner.Owner.Transform.Coordinates)) { miner.Broken = true; return false; diff --git a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs index afc56ec96c..18b4c52433 100644 --- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs +++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs @@ -19,6 +19,7 @@ using JetBrains.Annotations; using Robust.Server.GameObjects; using Robust.Shared.Containers; using Robust.Shared.GameObjects; +using Robust.Shared.IoC; using Robust.Shared.Maths; namespace Content.Server.Atmos.Piping.Unary.EntitySystems @@ -26,6 +27,8 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems [UsedImplicitly] public class GasCanisterSystem : EntitySystem { + [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; + public override void Initialize() { base.Initialize(); @@ -131,23 +134,21 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems if (!nodeContainer.TryGetNode(canister.PortName, out PortablePipeNode? portNode)) return; - var atmosphereSystem = Get(); - - atmosphereSystem.React(canister.Air, portNode); + _atmosphereSystem.React(canister.Air, portNode); if (portNode.NodeGroup is PipeNet {NodeCount: > 1} net) { var buffer = new GasMixture(net.Air.Volume + canister.Air.Volume); - atmosphereSystem.Merge(buffer, net.Air); - atmosphereSystem.Merge(buffer, canister.Air); + _atmosphereSystem.Merge(buffer, net.Air); + _atmosphereSystem.Merge(buffer, canister.Air); net.Air.Clear(); - atmosphereSystem.Merge(net.Air, buffer); + _atmosphereSystem.Merge(net.Air, buffer); net.Air.Multiply(net.Air.Volume / buffer.Volume); canister.Air.Clear(); - atmosphereSystem.Merge(canister.Air, buffer); + _atmosphereSystem.Merge(canister.Air, buffer); canister.Air.Multiply(canister.Air.Volume / buffer.Volume); } @@ -161,12 +162,12 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems if (container.ContainedEntities.Count > 0) { var gasTank = container.ContainedEntities[0].GetComponent(); - atmosphereSystem.ReleaseGasTo(canister.Air, gasTank.Air, canister.ReleasePressure); + _atmosphereSystem.ReleaseGasTo(canister.Air, gasTank.Air, canister.ReleasePressure); } else { - var environment = atmosphereSystem.GetTileMixture(canister.Owner.Transform.Coordinates, true); - atmosphereSystem.ReleaseGasTo(canister.Air, environment, canister.ReleasePressure); + var environment = _atmosphereSystem.GetTileMixture(canister.Owner.Transform.Coordinates, true); + _atmosphereSystem.ReleaseGasTo(canister.Air, environment, canister.ReleasePressure); } } diff --git a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasOutletInjectorSystem.cs b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasOutletInjectorSystem.cs index 63f9665d44..c716f72c04 100644 --- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasOutletInjectorSystem.cs +++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasOutletInjectorSystem.cs @@ -6,12 +6,15 @@ using Content.Server.NodeContainer.Nodes; using Content.Shared.Atmos; using JetBrains.Annotations; using Robust.Shared.GameObjects; +using Robust.Shared.IoC; namespace Content.Server.Atmos.Piping.Unary.EntitySystems { [UsedImplicitly] public class GasOutletInjectorSystem : EntitySystem { + [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; + public override void Initialize() { base.Initialize(); @@ -32,8 +35,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems if (!nodeContainer.TryGetNode(injector.InletName, out PipeNode? inlet)) return; - var atmosphereSystem = Get(); - var environment = atmosphereSystem.GetTileMixture(injector.Owner.Transform.Coordinates, true); + var environment = _atmosphereSystem.GetTileMixture(injector.Owner.Transform.Coordinates, true); if (environment == null) return; @@ -44,7 +46,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems var removed = inlet.Air.Remove(transferMoles); - atmosphereSystem.Merge(environment, removed); + _atmosphereSystem.Merge(environment, removed); } } } diff --git a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasPassiveVentSystem.cs b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasPassiveVentSystem.cs index 5a61cbc350..e484e80da0 100644 --- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasPassiveVentSystem.cs +++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasPassiveVentSystem.cs @@ -7,12 +7,15 @@ using Content.Server.NodeContainer.Nodes; using Content.Shared.Atmos; using JetBrains.Annotations; using Robust.Shared.GameObjects; +using Robust.Shared.IoC; namespace Content.Server.Atmos.Piping.Unary.EntitySystems { [UsedImplicitly] public class GasPassiveVentSystem : EntitySystem { + [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; + public override void Initialize() { base.Initialize(); @@ -22,8 +25,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems private void OnPassiveVentUpdated(EntityUid uid, GasPassiveVentComponent vent, AtmosDeviceUpdateEvent args) { - var atmosphereSystem = Get(); - var environment = atmosphereSystem.GetTileMixture(vent.Owner.Transform.Coordinates, true); + var environment = _atmosphereSystem.GetTileMixture(vent.Owner.Transform.Coordinates, true); if (environment == null) return; @@ -44,7 +46,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems var airTemperature = environment.Temperature > 0 ? environment.Temperature : inlet.Air.Temperature; var transferMoles = pressureDelta * environment.Volume / (airTemperature * Atmospherics.R); var removed = inlet.Air.Remove(transferMoles); - atmosphereSystem.Merge(environment, removed); + _atmosphereSystem.Merge(environment, removed); } else { diff --git a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasThermoMachineSystem.cs b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasThermoMachineSystem.cs index 748700d13e..f48aa149f5 100644 --- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasThermoMachineSystem.cs +++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasThermoMachineSystem.cs @@ -7,12 +7,15 @@ using Content.Shared.Atmos.Piping; using JetBrains.Annotations; using Robust.Server.GameObjects; using Robust.Shared.GameObjects; +using Robust.Shared.IoC; namespace Content.Server.Atmos.Piping.Unary.EntitySystems { [UsedImplicitly] public class GasThermoMachineSystem : EntitySystem { + [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; + public override void Initialize() { base.Initialize(); @@ -35,7 +38,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems if (!nodeContainer.TryGetNode(thermoMachine.InletName, out PipeNode? inlet)) return; - var airHeatCapacity = Get().GetHeatCapacity(inlet.Air); + var airHeatCapacity = _atmosphereSystem.GetHeatCapacity(inlet.Air); var combinedHeatCapacity = airHeatCapacity + thermoMachine.HeatCapacity; var oldTemperature = inlet.Air.Temperature; diff --git a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentPumpSystem.cs b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentPumpSystem.cs index 5e69a4f3b4..f9af0231f6 100644 --- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentPumpSystem.cs +++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentPumpSystem.cs @@ -9,12 +9,15 @@ using Content.Shared.Atmos.Visuals; using JetBrains.Annotations; using Robust.Server.GameObjects; using Robust.Shared.GameObjects; +using Robust.Shared.IoC; namespace Content.Server.Atmos.Piping.Unary.EntitySystems { [UsedImplicitly] public class GasVentPumpSystem : EntitySystem { + [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; + public override void Initialize() { base.Initialize(); @@ -44,8 +47,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems if (!nodeContainer.TryGetNode(vent.InletName, out PipeNode? pipe)) return; - var atmosphereSystem = Get(); - var environment = atmosphereSystem.GetTileMixture(vent.Owner.Transform.Coordinates, true); + var environment = _atmosphereSystem.GetTileMixture(vent.Owner.Transform.Coordinates, true); // We're in an air-blocked tile... Do nothing. if (environment == null) @@ -66,7 +68,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems { var transferMoles = pressureDelta * environment.Volume / (pipe.Air.Temperature * Atmospherics.R); - atmosphereSystem.Merge(environment, pipe.Air.Remove(transferMoles)); + _atmosphereSystem.Merge(environment, pipe.Air.Remove(transferMoles)); } } else if (vent.PumpDirection == VentPumpDirection.Siphoning && environment.Pressure > 0) diff --git a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentScrubberSystem.cs b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentScrubberSystem.cs index 399ba29a38..190fd86228 100644 --- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentScrubberSystem.cs +++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentScrubberSystem.cs @@ -9,6 +9,7 @@ using Content.Shared.Atmos.Piping.Unary.Visuals; using JetBrains.Annotations; using Robust.Server.GameObjects; using Robust.Shared.GameObjects; +using Robust.Shared.IoC; using Robust.Shared.Maths; namespace Content.Server.Atmos.Piping.Unary.EntitySystems @@ -16,6 +17,8 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems [UsedImplicitly] public class GasVentScrubberSystem : EntitySystem { + [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; + public override void Initialize() { base.Initialize(); @@ -45,17 +48,16 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems if (!nodeContainer.TryGetNode(scrubber.OutletName, out PipeNode? outlet)) return; - var atmosphereSystem = Get(); - var environment = atmosphereSystem.GetTileMixture(scrubber.Owner.Transform.Coordinates, true); + var environment = _atmosphereSystem.GetTileMixture(scrubber.Owner.Transform.Coordinates, true); - Scrub(atmosphereSystem, scrubber, appearance, environment, outlet); + Scrub(_atmosphereSystem, scrubber, appearance, environment, outlet); if (!scrubber.WideNet) return; // Scrub adjacent tiles too. - foreach (var adjacent in atmosphereSystem.GetAdjacentTileMixtures(scrubber.Owner.Transform.Coordinates, false, true)) + foreach (var adjacent in _atmosphereSystem.GetAdjacentTileMixtures(scrubber.Owner.Transform.Coordinates, false, true)) { - Scrub(atmosphereSystem, scrubber, null, adjacent, outlet); + Scrub(_atmosphereSystem, scrubber, null, adjacent, outlet); } } diff --git a/Content.Server/Body/Surgery/Components/SurgeryToolSystem.cs b/Content.Server/Body/Surgery/Components/SurgeryToolSystem.cs index b628e7f89c..5e95ace8be 100644 --- a/Content.Server/Body/Surgery/Components/SurgeryToolSystem.cs +++ b/Content.Server/Body/Surgery/Components/SurgeryToolSystem.cs @@ -6,12 +6,15 @@ using Content.Shared.Interaction.Events; using Content.Shared.Interaction.Helpers; using JetBrains.Annotations; using Robust.Shared.GameObjects; +using Robust.Shared.IoC; namespace Content.Server.Body.Surgery.Components { [UsedImplicitly] public class SurgeryToolSystem : EntitySystem { + [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; + private readonly HashSet _openSurgeryUIs = new(); public override void Initialize() @@ -54,7 +57,7 @@ namespace Content.Server.Body.Surgery.Components continue; } - if (!Get().CanInteract(tool.PerformerCache) || + if (!_actionBlockerSystem.CanInteract(tool.PerformerCache) || !tool.PerformerCache.InRangeUnobstructed(tool.BodyCache)) { tool.CloseAllSurgeryUIs(); diff --git a/Content.Server/Construction/ConstructionSystem.cs b/Content.Server/Construction/ConstructionSystem.cs index 11e7c41f72..f69cffd012 100644 --- a/Content.Server/Construction/ConstructionSystem.cs +++ b/Content.Server/Construction/ConstructionSystem.cs @@ -39,6 +39,8 @@ namespace Content.Server.Construction { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IRobustRandom _robustRandom = default!; + [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly StackSystem _stackSystem = default!; private readonly Dictionary> _beingBuilt = new(); @@ -170,7 +172,7 @@ namespace Content.Server.Construction if (!materialStep.EntityValid(entity, out var stack)) continue; - var splitStack = Get().Split(entity.Uid, stack, materialStep.Amount, user.ToCoordinates()); + var splitStack = _stackSystem.Split(entity.Uid, stack, materialStep.Amount, user.ToCoordinates()); if (splitStack == null) continue; @@ -226,8 +228,6 @@ namespace Content.Server.Construction return null; } - var doAfterSystem = Get(); - var doAfterArgs = new DoAfterEventArgs(user, doAfterTime) { BreakOnDamage = true, @@ -237,7 +237,7 @@ namespace Content.Server.Construction NeedHand = false, }; - if (await doAfterSystem.WaitDoAfter(doAfterArgs) == DoAfterStatus.Cancelled) + if (await _doAfterSystem.WaitDoAfter(doAfterArgs) == DoAfterStatus.Cancelled) { FailCleanup(); return null; diff --git a/Content.Server/Destructible/DestructibleSystem.cs b/Content.Server/Destructible/DestructibleSystem.cs index f2ad5d4922..1be935c9f6 100644 --- a/Content.Server/Destructible/DestructibleSystem.cs +++ b/Content.Server/Destructible/DestructibleSystem.cs @@ -11,17 +11,7 @@ namespace Content.Server.Destructible public class DestructibleSystem : EntitySystem { [Dependency] public readonly IRobustRandom Random = default!; - - public AudioSystem AudioSystem { get; private set; } = default!; - - public ActSystem ActSystem { get; private set; } = default!; - - public override void Initialize() - { - base.Initialize(); - - AudioSystem = Get(); - ActSystem = Get(); - } + [Dependency] public readonly AudioSystem AudioSystem = default!; + [Dependency] public readonly ActSystem ActSystem = default!; } } diff --git a/Content.Server/Engineering/EntitySystems/SpawnAfterInteractSystem.cs b/Content.Server/Engineering/EntitySystems/SpawnAfterInteractSystem.cs index fcf4bb1d85..61bfad8791 100644 --- a/Content.Server/Engineering/EntitySystems/SpawnAfterInteractSystem.cs +++ b/Content.Server/Engineering/EntitySystems/SpawnAfterInteractSystem.cs @@ -16,6 +16,8 @@ namespace Content.Server.Engineering.EntitySystems public class SpawnAfterInteractSystem : EntitySystem { [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly StackSystem _stackSystem = default!; public override void Initialize() { @@ -41,7 +43,7 @@ namespace Content.Server.Engineering.EntitySystems if (!IsTileClear()) return; - if (component.DoAfterTime > 0 && TryGet(out var doAfterSystem)) + if (component.DoAfterTime > 0) { var doAfterArgs = new DoAfterEventArgs(args.User, component.DoAfterTime) { @@ -49,7 +51,7 @@ namespace Content.Server.Engineering.EntitySystems BreakOnStun = true, PostCheck = IsTileClear, }; - var result = await doAfterSystem.WaitDoAfter(doAfterArgs); + var result = await _doAfterSystem.WaitDoAfter(doAfterArgs); if (result != DoAfterStatus.Finished) return; @@ -59,7 +61,7 @@ namespace Content.Server.Engineering.EntitySystems return; if (component.Owner.TryGetComponent(out var stackComp) - && component.RemoveOnInteract && !Get().Use(uid, stackComp, 1)) + && component.RemoveOnInteract && !_stackSystem.Use(uid, stackComp, 1)) { return; } diff --git a/Content.Server/Hands/HandsSystem.cs b/Content.Server/Hands/HandsSystem.cs index 712c0867a6..4a76d07101 100644 --- a/Content.Server/Hands/HandsSystem.cs +++ b/Content.Server/Hands/HandsSystem.cs @@ -18,6 +18,7 @@ using Robust.Server.Player; using Robust.Shared.Containers; using Robust.Shared.GameObjects; using Robust.Shared.Input.Binding; +using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Map; using Robust.Shared.Maths; @@ -29,6 +30,9 @@ namespace Content.Server.Hands [UsedImplicitly] internal sealed class HandsSystem : SharedHandsSystem { + [Dependency] private readonly InteractionSystem _interactionSystem = default!; + [Dependency] private readonly StackSystem _stackSystem = default!; + public override void Initialize() { base.Initialize(); @@ -113,12 +117,12 @@ namespace Content.Server.Hands if (!hands.TryGetActiveHeldEntity(out var throwEnt)) return false; - if (!Get().TryThrowInteraction(hands.Owner, throwEnt)) + if (!_interactionSystem.TryThrowInteraction(hands.Owner, throwEnt)) return false; if (throwEnt.TryGetComponent(out StackComponent? stack) && stack.Count > 1 && stack.ThrowIndividually) { - var splitStack = Get().Split(throwEnt.Uid, stack, 1, playerEnt.Transform.Coordinates); + var splitStack = _stackSystem.Split(throwEnt.Uid, stack, 1, playerEnt.Transform.Coordinates); if (splitStack == null) return false; diff --git a/Content.Server/Interaction/InteractionSystem.cs b/Content.Server/Interaction/InteractionSystem.cs index efb01638c4..61a5ce724e 100644 --- a/Content.Server/Interaction/InteractionSystem.cs +++ b/Content.Server/Interaction/InteractionSystem.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading.Tasks; using Content.Server.Buckle.Components; using Content.Server.CombatMode; +using Content.Server.DoAfter; using Content.Server.Hands.Components; using Content.Server.Items; using Content.Server.Pulling; @@ -46,6 +47,7 @@ namespace Content.Server.Interaction { [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; public override void Initialize() { @@ -175,9 +177,7 @@ namespace Content.Server.Interaction private void InteractionActivate(IEntity user, IEntity used) { - var actionBlocker = Get(); - - if (!actionBlocker.CanInteract(user) || ! actionBlocker.CanUse(user)) + if (!_actionBlockerSystem.CanInteract(user) || ! _actionBlockerSystem.CanUse(user)) return; // all activates should only fire when in range / unobstructed @@ -275,7 +275,7 @@ namespace Content.Server.Interaction if (!ValidateInteractAndFace(user, coordinates)) return; - if (!Get().CanInteract(user)) + if (!_actionBlockerSystem.CanInteract(user)) return; // Get entity clicked upon from UID if valid UID, if not assume no entity clicked upon and null @@ -344,7 +344,7 @@ namespace Content.Server.Interaction if (diff.LengthSquared <= 0.01f) return; var diffAngle = Angle.FromWorldVec(diff); - if (Get().CanChangeDirection(user)) + if (_actionBlockerSystem.CanChangeDirection(user)) { user.Transform.WorldRotation = diffAngle; } @@ -394,7 +394,7 @@ namespace Content.Server.Interaction /// public async Task InteractUsing(IEntity user, IEntity used, IEntity target, EntityCoordinates clickLocation) { - if (!Get().CanInteract(user)) + if (!_actionBlockerSystem.CanInteract(user)) return; // all interactions should only happen when in range / unobstructed, so no range check is needed @@ -424,7 +424,7 @@ namespace Content.Server.Interaction /// public void InteractHand(IEntity user, IEntity target) { - if (!Get().CanInteract(user)) + if (!_actionBlockerSystem.CanInteract(user)) return; // all interactions should only happen when in range / unobstructed, so no range check is needed @@ -457,7 +457,7 @@ namespace Content.Server.Interaction /// public void TryUseInteraction(IEntity user, IEntity used) { - if (user != null && used != null && Get().CanUse(user)) + if (user != null && used != null && _actionBlockerSystem.CanUse(user)) { UseInteraction(user, used); } @@ -501,7 +501,7 @@ namespace Content.Server.Interaction /// public bool TryThrowInteraction(IEntity user, IEntity item) { - if (user == null || item == null || !Get().CanThrow(user)) return false; + if (user == null || item == null || !_actionBlockerSystem.CanThrow(user)) return false; ThrownInteraction(user, item); return true; @@ -618,7 +618,7 @@ namespace Content.Server.Interaction /// public bool TryDroppedInteraction(IEntity user, IEntity item, bool intentional) { - if (user == null || item == null || !Get().CanDrop(user)) return false; + if (user == null || item == null || !_actionBlockerSystem.CanDrop(user)) return false; DroppedInteraction(user, item, intentional); return true; @@ -726,7 +726,7 @@ namespace Content.Server.Interaction if (!ValidateInteractAndFace(user, coordinates)) return; - if (!Get().CanAttack(user)) + if (!_actionBlockerSystem.CanAttack(user)) return; IEntity? targetEnt = null; diff --git a/Content.Server/Pointing/EntitySystems/PointingSystem.cs b/Content.Server/Pointing/EntitySystems/PointingSystem.cs index f31155ff11..e855c9270c 100644 --- a/Content.Server/Pointing/EntitySystems/PointingSystem.cs +++ b/Content.Server/Pointing/EntitySystems/PointingSystem.cs @@ -31,6 +31,7 @@ namespace Content.Server.Pointing.EntitySystems [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; private static readonly TimeSpan PointDelay = TimeSpan.FromSeconds(0.5f); @@ -112,7 +113,7 @@ namespace Content.Server.Pointing.EntitySystems return false; } - if (EntitySystem.Get().CanChangeDirection(player)) + if (_actionBlockerSystem.CanChangeDirection(player)) { var diff = coords.ToMapPos(EntityManager) - player.Transform.MapPosition.Position; if (diff.LengthSquared > 0.01f) diff --git a/Content.Shared/Interaction/SharedInteractionSystem.cs b/Content.Shared/Interaction/SharedInteractionSystem.cs index 7f50cf0063..0d02792593 100644 --- a/Content.Shared/Interaction/SharedInteractionSystem.cs +++ b/Content.Shared/Interaction/SharedInteractionSystem.cs @@ -4,6 +4,7 @@ using Content.Shared.Notification.Managers; using Content.Shared.Physics; using JetBrains.Annotations; using Robust.Shared.GameObjects; +using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Map; using Robust.Shared.Physics; @@ -17,6 +18,8 @@ namespace Content.Shared.Interaction [UsedImplicitly] public class SharedInteractionSystem : EntitySystem { + [Dependency] private readonly SharedBroadphaseSystem _sharedBroadphaseSystem = default!; + public const float InteractionRange = 2; public const float InteractionRangeSquared = InteractionRange * InteractionRange; @@ -46,7 +49,7 @@ namespace Content.Shared.Interaction predicate ??= _ => false; var ray = new CollisionRay(origin.Position, dir.Normalized, collisionMask); - var rayResults = Get().IntersectRayWithPredicate(origin.MapId, ray, dir.Length, predicate.Invoke, false).ToList(); + var rayResults = _sharedBroadphaseSystem.IntersectRayWithPredicate(origin.MapId, ray, dir.Length, predicate.Invoke, false).ToList(); if (rayResults.Count == 0) return dir.Length; return (rayResults[0].HitPos - origin.Position).Length; @@ -122,7 +125,7 @@ namespace Content.Shared.Interaction predicate ??= _ => false; var ray = new CollisionRay(origin.Position, dir.Normalized, (int) collisionMask); - var rayResults = Get().IntersectRayWithPredicate(origin.MapId, ray, dir.Length, predicate.Invoke, false).ToList(); + var rayResults = _sharedBroadphaseSystem.IntersectRayWithPredicate(origin.MapId, ray, dir.Length, predicate.Invoke, false).ToList(); if (rayResults.Count == 0) return true; From 8357fcd7c2f21d39f089c0b9c531cc9e93f9a755 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 26 Jul 2021 18:17:42 +0200 Subject: [PATCH 11/76] fixes enpendablelight to actually be expendable --- Content.Server/Light/Components/ExpendableLightComponent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Server/Light/Components/ExpendableLightComponent.cs b/Content.Server/Light/Components/ExpendableLightComponent.cs index 1f1c4a9df5..ef74376c01 100644 --- a/Content.Server/Light/Components/ExpendableLightComponent.cs +++ b/Content.Server/Light/Components/ExpendableLightComponent.cs @@ -56,7 +56,7 @@ namespace Content.Server.Light.Components /// private bool TryActivate() { - if (!Activated) + if (!Activated && CurrentState == ExpendableLightState.BrandNew) { if (Owner.TryGetComponent(out var item)) { From 70a16427f471b65bd30cc08f2535713702b71f8c Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 26 Jul 2021 18:28:04 +0200 Subject: [PATCH 12/76] fixes #4372 --- Content.Server/Light/Components/EmergencyLightComponent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Server/Light/Components/EmergencyLightComponent.cs b/Content.Server/Light/Components/EmergencyLightComponent.cs index 5d7fd2a933..b5df55fb28 100644 --- a/Content.Server/Light/Components/EmergencyLightComponent.cs +++ b/Content.Server/Light/Components/EmergencyLightComponent.cs @@ -142,7 +142,7 @@ namespace Content.Server.Light.Components void IExamine.Examine(FormattedMessage message, bool inDetailsRange) { - message.AddMarkup(Loc.GetString("emergency-light-component-on-examine",("batteryStateText", BatteryStateText[State]))); + message.AddMarkup(Loc.GetString("emergency-light-component-on-examine",("batteryStateText", Loc.GetString(BatteryStateText[State])))); } public enum EmergencyLightState From aa4727d18506c1d7beffc7548ae3315319f00fcd Mon Sep 17 00:00:00 2001 From: Swept Date: Mon, 26 Jul 2021 09:39:12 -0700 Subject: [PATCH 13/76] Gas tanks have proper item sizes --- Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml b/Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml index 4c93eab341..48b667a26d 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml @@ -11,6 +11,7 @@ - key: enum.SharedGasTankUiKey.Key type: GasTankBoundUserInterface - type: Clothing + size: 15 sprite: Objects/Tanks/generic.rsi QuickEquip: false - type: GasTank @@ -80,6 +81,7 @@ volume: 2 temperature: 293.15 - type: Clothing + size: 10 sprite: Objects/Tanks/emergency.rsi Slots: - Pocket @@ -100,6 +102,7 @@ volume: 6 temperature: 293.15 - type: Clothing + size: 10 sprite: Objects/Tanks/emergency_yellow.rsi Slots: - Pocket @@ -118,6 +121,7 @@ volume: 10 temperature: 293.15 - type: Clothing + size: 10 sprite: Objects/Tanks/emergency_double.rsi Slots: - Pocket @@ -157,6 +161,7 @@ volume: 70 temperature: 293.15 - type: Clothing + size: 10 sprite: Objects/Tanks/plasma.rsi Slots: - Belt From 86aa1caddb7bd167445f5907703f054dcd2b44d5 Mon Sep 17 00:00:00 2001 From: Swept Date: Mon, 26 Jul 2021 09:49:43 -0700 Subject: [PATCH 14/76] Updated bucket's size to 100 --- Resources/Prototypes/Entities/Objects/Tools/bucket.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/Resources/Prototypes/Entities/Objects/Tools/bucket.yml b/Resources/Prototypes/Entities/Objects/Tools/bucket.yml index 9646bdce26..be03c43997 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/bucket.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/bucket.yml @@ -12,6 +12,7 @@ sprite: Objects/Tools/bucket.rsi state: icon - type: Clothing + size: 100 sprite: Objects/Tools/bucket.rsi Slots: - Helmet From 283dcb0802aae6a6d3a62dd3f2a4575b9fa742dc Mon Sep 17 00:00:00 2001 From: SethLafuente <84478872+SethLafuente@users.noreply.github.com> Date: Mon, 26 Jul 2021 15:14:10 -0700 Subject: [PATCH 15/76] Gives RD locker a hardsuit (#4376) Co-authored-by: SETh lafuente --- Resources/Prototypes/Catalog/Fills/Lockers/heads.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml b/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml index f8df3df691..415db3246d 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml @@ -147,6 +147,10 @@ prob: 1 - id: ClothingHeadsetMedicalScience prob: 1 + - id: ClothingHeadHelmetHardsuitRd + prob: 1 + - id: ClothingOuterHardsuitRd + prob: 1 - id: PlushieSlime prob: 0.1 From af4dec27ab2c6a96c6ac1a9fe47eb0306c9e4693 Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 26 Jul 2021 18:15:13 -0400 Subject: [PATCH 16/76] Automatic changelog update --- Resources/Changelog/Changelog.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 2987e1d464..ce4f52b54b 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1603,3 +1603,8 @@ Entries: - {message: Added Storage Room to Coats, type: Add} id: 285 time: '2021-07-26T05:36:27.0000000+00:00' +- author: Seth + changes: + - {message: Added a hardsuit into research directors locker, type: Add} + id: 286 + time: '2021-07-26T22:14:10.0000000+00:00' From 3e7b0fe7a211b57faad67d74e6fa56a880f7ba2d Mon Sep 17 00:00:00 2001 From: Kara Dinyes Date: Mon, 26 Jul 2021 17:38:27 -0700 Subject: [PATCH 17/76] Fix patron OOC message wrap --- Resources/Locale/en-US/chat/managers/chat-manager.ftl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Resources/Locale/en-US/chat/managers/chat-manager.ftl b/Resources/Locale/en-US/chat/managers/chat-manager.ftl index 02b71db263..01b43a67c6 100644 --- a/Resources/Locale/en-US/chat/managers/chat-manager.ftl +++ b/Resources/Locale/en-US/chat/managers/chat-manager.ftl @@ -13,7 +13,7 @@ chat-manager-sender-announcement-wrap-message = {$sender} Announcement: chat-manager-entity-say-wrap-message = {$entityName} says: "{"{0}"}" chat-manager-entity-me-wrap-message = {$entityName} {"{0}"} chat-manager-send-ooc-wrap-message = OOC: {$playerName}: {"{0}"} -chat-manager-send-ooc-patron-wrap-message = OOC: [color={$patronColor}]{$playerName}[/color]: {"{0}"}: +chat-manager-send-ooc-patron-wrap-message = OOC: [color={$patronColor}]{$playerName}[/color]: {"{0}"} chat-manager-send-dead-chat-wrap-message = {$deadChannelName}: {$playerName}: {"{0}"} chat-manager-send-admin-dead-chat-wrap-message = {$adminChannelName}:({$userName}): {"{0}"} chat-manager-send-admin-chat-wrap-message = {$adminChannelName}: {$playerName}: {"{0}"} @@ -21,4 +21,4 @@ chat-manager-send-admin-announcement-wrap-message = {$adminChannelName}: {"{0}"} chat-manager-send-hook-ooc-wrap-message = OOC: (D){$senderName}: {"{0}"} chat-manager-dead-channel-name = DEAD -chat-manager-admin-channel-name = ADMIN \ No newline at end of file +chat-manager-admin-channel-name = ADMIN From af948d00d80dc9e0fe25eb63a22188e1d9743bfb Mon Sep 17 00:00:00 2001 From: Paul Ritter Date: Tue, 27 Jul 2021 14:54:26 +0200 Subject: [PATCH 18/76] adds a integrationtest that tries out all reactions (#4374) Co-authored-by: Paul --- .../Tests/Chemistry/TryAllReactionsTest.cs | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 Content.IntegrationTests/Tests/Chemistry/TryAllReactionsTest.cs diff --git a/Content.IntegrationTests/Tests/Chemistry/TryAllReactionsTest.cs b/Content.IntegrationTests/Tests/Chemistry/TryAllReactionsTest.cs new file mode 100644 index 0000000000..9587897442 --- /dev/null +++ b/Content.IntegrationTests/Tests/Chemistry/TryAllReactionsTest.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Content.Server.Chemistry.Components; +using Content.Server.Fluids.Components; +using Content.Shared.Chemistry.Reaction; +using Content.Shared.Chemistry.Reagent; +using Content.Shared.Coordinates; +using NUnit.Framework; +using Robust.Shared.GameObjects; +using Robust.Shared.Map; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; +using Robust.Shared.Utility; + +namespace Content.IntegrationTests.Tests.Chemistry +{ + [TestFixture] + [TestOf(typeof(ReactionPrototype))] + public class TryAllReactionsTest : ContentIntegrationTest + { + [Test] + public async Task TryAllTest() + { + var server = StartServerDummyTicker(); + + await server.WaitIdleAsync(); + + var mapManager = server.ResolveDependency(); + var entityManager = server.ResolveDependency(); + var prototypeManager = server.ResolveDependency(); + + foreach (var reactionPrototype in prototypeManager.EnumeratePrototypes()) + { + //since i have no clue how to isolate each loop assert-wise im just gonna throw this one in for good measure + Console.WriteLine($"Testing {reactionPrototype.ID}"); + + IEntity beaker; + SolutionContainerComponent component = null; + + server.Assert(() => + { + mapManager.CreateNewMapEntity(MapId.Nullspace); + + beaker = entityManager.SpawnEntity("BluespaceBeaker", MapCoordinates.Nullspace); + Assert.That(beaker.TryGetComponent(out component)); + foreach (var (id, reactant) in reactionPrototype.Reactants) + { + Assert.That(component.TryAddReagent(id, reactant.Amount, out var quantity)); + Assert.That(reactant.Amount, Is.EqualTo(quantity)); + } + }); + + await server.WaitIdleAsync(); + + server.Assert(() => + { + //you just got linq'd fool + //(i'm sorry) + var foundProductsMap = reactionPrototype.Products + .Concat(reactionPrototype.Reactants.Where(x => x.Value.Catalyst).ToDictionary(x => x.Key, x => x.Value.Amount)) + .ToDictionary(x => x, x => false); + foreach (var reagent in component.Solution.Contents) + { + Assert.That(foundProductsMap.TryFirstOrNull(x => x.Key.Key == reagent.ReagentId && x.Key.Value == reagent.Quantity, out var foundProduct)); + foundProductsMap[foundProduct.Value.Key] = true; + } + + Assert.That(foundProductsMap.All(x => x.Value)); + }); + } + + } + } + +} From e38d7e0a2f76367aaf229b8d227d48bed9dda0d1 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Wed, 28 Jul 2021 00:30:39 +1000 Subject: [PATCH 19/76] Update submodule --- RobustToolbox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RobustToolbox b/RobustToolbox index 8c1e075c91..7bac32d18e 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit 8c1e075c919604fe2e27cd6a2bbe22fceeb89249 +Subproject commit 7bac32d18ebd71547c76565dc4cd86f8007c0e8c From 273eaa493fc2570c4a3811163cf41ff0eca8a360 Mon Sep 17 00:00:00 2001 From: Visne <39844191+Visne@users.noreply.github.com> Date: Tue, 27 Jul 2021 20:09:12 +0200 Subject: [PATCH 20/76] XAMLify ID card computer, Alerts and AME window (#4322) * Refactor ID card computer to XAML UI * Alerts * XAMLify AME window * Use Control.SetButtonDisabledRecursive() instead --- .../AME/UI/AMEControllerBoundUserInterface.cs | 9 +- Content.Client/AME/UI/AMEWindow.cs | 173 --------------- Content.Client/AME/UI/AMEWindow.xaml | 46 ++++ Content.Client/AME/UI/AMEWindow.xaml.cs | 73 ++++++ .../Access/UI/IdCardConsoleWindow.cs | 208 ------------------ .../Access/UI/IdCardConsoleWindow.xaml | 30 +++ .../Access/UI/IdCardConsoleWindow.xaml.cs | 121 ++++++++++ Content.Client/Alerts/UI/AlertsUI.xaml | 10 + .../UI/{AlertsUI.cs => AlertsUI.xaml.cs} | 38 ++-- .../Chemistry/UI/ChemMasterWindow.cs | 23 -- .../Chemistry/UI/ReagentDispenserWindow.cs | 23 -- 11 files changed, 295 insertions(+), 459 deletions(-) delete mode 100644 Content.Client/AME/UI/AMEWindow.cs create mode 100644 Content.Client/AME/UI/AMEWindow.xaml create mode 100644 Content.Client/AME/UI/AMEWindow.xaml.cs delete mode 100644 Content.Client/Access/UI/IdCardConsoleWindow.cs create mode 100644 Content.Client/Access/UI/IdCardConsoleWindow.xaml create mode 100644 Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs create mode 100644 Content.Client/Alerts/UI/AlertsUI.xaml rename Content.Client/Alerts/UI/{AlertsUI.cs => AlertsUI.xaml.cs} (71%) diff --git a/Content.Client/AME/UI/AMEControllerBoundUserInterface.cs b/Content.Client/AME/UI/AMEControllerBoundUserInterface.cs index 3765d45fa2..aaeaf538f3 100644 --- a/Content.Client/AME/UI/AMEControllerBoundUserInterface.cs +++ b/Content.Client/AME/UI/AMEControllerBoundUserInterface.cs @@ -19,14 +19,9 @@ namespace Content.Client.AME.UI { base.Open(); - _window = new AMEWindow(); + _window = new AMEWindow(this); _window.OnClose += Close; _window.OpenCentered(); - - _window.EjectButton.OnPressed += _ => ButtonPressed(UiButton.Eject); - _window.ToggleInjection.OnPressed += _ => ButtonPressed(UiButton.ToggleInjection); - _window.IncreaseFuelButton.OnPressed += _ => ButtonPressed(UiButton.IncreaseFuel); - _window.DecreaseFuelButton.OnPressed += _ => ButtonPressed(UiButton.DecreaseFuel); } /// @@ -44,7 +39,7 @@ namespace Content.Client.AME.UI _window?.UpdateState(castState); //Update window state } - private void ButtonPressed(UiButton button, int dispenseIndex = -1) + public void ButtonPressed(UiButton button, int dispenseIndex = -1) { SendMessage(new UiButtonPressedMessage(button)); } diff --git a/Content.Client/AME/UI/AMEWindow.cs b/Content.Client/AME/UI/AMEWindow.cs deleted file mode 100644 index e9104e6f67..0000000000 --- a/Content.Client/AME/UI/AMEWindow.cs +++ /dev/null @@ -1,173 +0,0 @@ -using Content.Client.Stylesheets; -using Robust.Client.UserInterface; -using Robust.Client.UserInterface.Controls; -using Robust.Client.UserInterface.CustomControls; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; -using static Content.Shared.AME.SharedAMEControllerComponent; -using static Robust.Client.UserInterface.Controls.BoxContainer; - -namespace Content.Client.AME.UI -{ - public class AMEWindow : SS14Window - { - public Label InjectionStatus { get; set; } - public Button EjectButton { get; set; } - public Button ToggleInjection { get; set; } - public Button IncreaseFuelButton { get; set; } - public Button DecreaseFuelButton { get; set; } - public ProgressBar? FuelMeter { get; set; } - public Label FuelAmount { get; set; } - public Label InjectionAmount { get; set; } - public Label CoreCount { get; set; } - - - public AMEWindow() - { - IoCManager.InjectDependencies(this); - - Title = Loc.GetString("ame-window-title"); - - MinSize = SetSize = (250, 250); - - Contents.AddChild(new BoxContainer - { - Orientation = LayoutOrientation.Vertical, - Children = - { - new BoxContainer - { - Orientation = LayoutOrientation.Horizontal, - Children = - { - new Label {Text = Loc.GetString("ame-window-engine-status-label") + " "}, - (InjectionStatus = new Label {Text = Loc.GetString("ame-window-engine-injection-status-not-injecting-label")}) - } - }, - new BoxContainer - { - Orientation = LayoutOrientation.Horizontal, - Children = - { - (ToggleInjection = new Button {Text = Loc.GetString("ame-window-toggle-injection-button"), StyleClasses = {StyleBase.ButtonOpenBoth}, Disabled = true}), - } - }, - new BoxContainer - { - Orientation = LayoutOrientation.Horizontal, - Children = - { - new Label {Text = Loc.GetString("ame-window-fuel-status-label") + " "}, - (FuelAmount = new Label {Text = Loc.GetString("ame-window-fuel-not-inserted-text")}) - } - }, - new BoxContainer - { - Orientation = LayoutOrientation.Horizontal, - Children = - { - (EjectButton = new Button {Text = Loc.GetString("ame-window-eject-button"), StyleClasses = {StyleBase.ButtonOpenBoth}, Disabled = true}), - } - }, - new BoxContainer - { - Orientation = LayoutOrientation.Horizontal, - Children = - { - new Label {Text = Loc.GetString("ame-window-injection-amount-label") + " "}, - (InjectionAmount = new Label {Text = "0"}) - } - }, - new BoxContainer - { - Orientation = LayoutOrientation.Horizontal, - Children = - { - (IncreaseFuelButton = new Button {Text = Loc.GetString("ame-window-increase-fuel-button"), StyleClasses = {StyleBase.ButtonOpenRight}}), - (DecreaseFuelButton = new Button {Text = Loc.GetString("ame-window-decrease-fuel-button"), StyleClasses = {StyleBase.ButtonOpenLeft}}), - } - }, - new BoxContainer - { - Orientation = LayoutOrientation.Horizontal, - Children = - { - new Label { Text = Loc.GetString("ame-window-core-count-label") + " "}, - (CoreCount = new Label { Text = "0"}), - } - } - } - }); - } - - /// - /// This searches recursively through all the children of "parent" - /// and sets the Disabled value of any buttons found to "val" - /// - /// The control which childrens get searched - /// The value to which disabled gets set - private void SetButtonDisabledRecursive(Control parent, bool val) - { - foreach (var child in parent.Children) - { - if (child is Button but) - { - but.Disabled = val; - continue; - } - - if (child.Children != null) - { - SetButtonDisabledRecursive(child, val); - } - } - } - - /// - /// Update the UI state when new state data is received from the server. - /// - /// State data sent by the server. - public void UpdateState(BoundUserInterfaceState state) - { - var castState = (AMEControllerBoundUserInterfaceState) state; - - // Disable all buttons if not powered - if (Contents.Children != null) - { - SetButtonDisabledRecursive(Contents, !castState.HasPower); - EjectButton.Disabled = false; - } - - if (!castState.HasFuelJar) - { - EjectButton.Disabled = true; - ToggleInjection.Disabled = true; - FuelAmount.Text = Loc.GetString("ame-window-fuel-not-inserted-text"); - } - else - { - EjectButton.Disabled = false; - ToggleInjection.Disabled = false; - FuelAmount.Text = $"{castState.FuelAmount}"; - } - - if (!castState.IsMaster) - { - ToggleInjection.Disabled = true; - } - - if (!castState.Injecting) - { - InjectionStatus.Text = Loc.GetString("ame-window-engine-injection-status-not-injecting-label") + " "; - } - else - { - InjectionStatus.Text = Loc.GetString("ame-window-engine-injection-status-injecting-label") + " "; - } - - CoreCount.Text = $"{castState.CoreCount}"; - InjectionAmount.Text = $"{castState.InjectionAmount}"; - } - } -} diff --git a/Content.Client/AME/UI/AMEWindow.xaml b/Content.Client/AME/UI/AMEWindow.xaml new file mode 100644 index 0000000000..bc1e7d9ded --- /dev/null +++ b/Content.Client/AME/UI/AMEWindow.xaml @@ -0,0 +1,46 @@ + + + + + + [RegisterComponent] - [ComponentReference(typeof(IActivate))] - public class ReagentGrinderComponent : SharedReagentGrinderComponent, IActivate, IInteractUsing + public class ReagentGrinderComponent : SharedReagentGrinderComponent { - private AudioSystem _audioSystem = default!; - [ViewVariables] private ContainerSlot _beakerContainer = default!; + [ViewVariables] public ContainerSlot BeakerContainer = default!; /// /// Can be null since we won't always have a beaker in the grinder. /// - [ViewVariables] private SolutionContainerComponent? _heldBeaker = default!; + [ViewVariables] public SolutionContainerComponent? HeldBeaker = default!; /// /// Contains the things that are going to be ground or juiced. /// - [ViewVariables] private Container _chamber = default!; + [ViewVariables] public Container Chamber = default!; - [ViewVariables] private bool ChamberEmpty => _chamber.ContainedEntities.Count <= 0; - [ViewVariables] private bool HasBeaker => _beakerContainer.ContainedEntity != null; - [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(ReagentGrinderUiKey.Key); - - private bool Powered => !Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver) || receiver.Powered; - - /// - /// Should the BoundUI be told to update? - /// - private bool _uiDirty = true; /// /// Is the machine actively doing something and can't be used right now? /// - private bool _busy = false; + public bool Busy; //YAML serialization vars - [ViewVariables(VVAccess.ReadWrite)] [DataField("chamberCapacity")] private int _storageCap = 16; - [ViewVariables(VVAccess.ReadWrite)] [DataField("workTime")] private int _workTime = 3500; //3.5 seconds, completely arbitrary for now. - - protected override void Initialize() - { - base.Initialize(); - //A slot for the beaker where the grounds/juices will go. - _beakerContainer = - ContainerHelpers.EnsureContainer(Owner, $"{Name}-reagentContainerContainer"); - - //A container for the things that WILL be ground/juiced. Useful for ejecting them instead of deleting them from the hands of the user. - _chamber = - ContainerHelpers.EnsureContainer(Owner, $"{Name}-entityContainerContainer"); - - if (UserInterface != null) - { - UserInterface.OnReceiveMessage += UserInterfaceOnReceiveMessage; - } - - _audioSystem = EntitySystem.Get(); - } - - public override void HandleMessage(ComponentMessage message, IComponent? component) - { - base.HandleMessage(message, component); - switch (message) - { - case PowerChangedMessage powerChanged: - OnPowerStateChanged(powerChanged); - break; - } - } - - protected override void OnRemove() - { - base.OnRemove(); - if (UserInterface != null) - { - UserInterface.OnReceiveMessage -= UserInterfaceOnReceiveMessage; - } - } - - private void UserInterfaceOnReceiveMessage(ServerBoundUserInterfaceMessage message) - { - if(_busy) - { - return; - } - - switch(message.Message) - { - case ReagentGrinderGrindStartMessage msg: - if (!Powered) break; - ClickSound(); - DoWork(message.Session.AttachedEntity!, GrinderProgram.Grind); - break; - - case ReagentGrinderJuiceStartMessage msg: - if (!Powered) break; - ClickSound(); - DoWork(message.Session.AttachedEntity!, GrinderProgram.Juice); - break; - - case ReagentGrinderEjectChamberAllMessage msg: - if(!ChamberEmpty) - { - ClickSound(); - for (var i = _chamber.ContainedEntities.Count - 1; i >= 0; i--) - { - EjectSolid(_chamber.ContainedEntities.ElementAt(i).Uid); - } - } - break; - - case ReagentGrinderEjectChamberContentMessage msg: - if (!ChamberEmpty) - { - EjectSolid(msg.EntityID); - ClickSound(); - _uiDirty = true; - } - break; - - case ReagentGrinderEjectBeakerMessage msg: - ClickSound(); - EjectBeaker(message.Session.AttachedEntity); - //EjectBeaker will dirty the UI for us, we don't have to do it explicitly here. - break; - } - } - - private void OnPowerStateChanged(PowerChangedMessage e) - { - _uiDirty = true; - } - - private void ClickSound() - { - SoundSystem.Play(Filter.Pvs(Owner), "/Audio/Machines/machine_switch.ogg", Owner, AudioParams.Default.WithVolume(-2f)); - } - - private void SetAppearance() - { - if (Owner.TryGetComponent(out AppearanceComponent? appearance)) - { - appearance.SetData(ReagentGrinderVisualState.BeakerAttached, HasBeaker); - } - } - - public void OnUpdate() - { - if(_uiDirty) - { - UpdateInterface(); - _uiDirty = false; - } - } - - // This doesn't check for UI dirtiness so handle that when calling this. - private void UpdateInterface() - { - bool canJuice = false; - bool canGrind = false; - if (HasBeaker) - { - foreach (var entity in _chamber.ContainedEntities) - { - if (!canJuice && entity.HasComponent()) canJuice = true; - if (!canGrind && entity.HasTag("Grindable")) canGrind = true; - if (canJuice && canGrind) break; - } - } - - UserInterface?.SetState(new ReagentGrinderInterfaceState - ( - _busy, - HasBeaker, - Powered, - canJuice, - canGrind, - _chamber.ContainedEntities.Select(item => item.Uid).ToArray(), - //Remember the beaker can be null! - _heldBeaker?.Solution.Contents.ToArray() - )); - _uiDirty = false; - } - - private void EjectSolid(EntityUid entityID) - { - if (_busy) - return; - - if (Owner.EntityManager.TryGetEntity(entityID, out var entity)) - { - _chamber.Remove(entity); - - //Give the ejected entity a tiny bit of offset so each one is apparent in case of a big stack, - //but (hopefully) not enough to clip it through a solid (wall). - entity.RandomOffset(0.4f); - } - _uiDirty = true; - } - - /// - /// Tries to eject whatever is in the beaker slot. Puts the item in the user's hands or failing that on top - /// of the grinder. - /// - private void EjectBeaker(IEntity? user) - { - if (!HasBeaker || _heldBeaker == null || _busy) - return; - - var beaker = _beakerContainer.ContainedEntity; - if(beaker is null) - return; - - _beakerContainer.Remove(beaker); - - if (user == null || !user.TryGetComponent(out var hands) || !_heldBeaker.Owner.TryGetComponent(out var item)) - return; - hands.PutInHandOrDrop(item); - - _heldBeaker = null; - _uiDirty = true; - SetAppearance(); - } - - void IActivate.Activate(ActivateEventArgs eventArgs) - { - if (!eventArgs.User.TryGetComponent(out ActorComponent? actor)) - { - return; - } - _uiDirty = true; - UserInterface?.Toggle(actor.PlayerSession); - } - - async Task IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) - { - if (!eventArgs.User.TryGetComponent(out IHandsComponent? hands)) - { - Owner.PopupMessage(eventArgs.User, Loc.GetString("reagent-grinder-component-interact-using-no-hands")); - return true; - } - - IEntity heldEnt = eventArgs.Using; - - //First, check if user is trying to insert a beaker. - //No promise it will be a beaker right now, but whatever. - //Maybe this should whitelist "beaker" in the prototype id of heldEnt? - if(heldEnt.TryGetComponent(out SolutionContainerComponent? beaker) && beaker.Capabilities.HasFlag(SolutionContainerCaps.FitsInDispenser)) - { - _beakerContainer.Insert(heldEnt); - _heldBeaker = beaker; - _uiDirty = true; - //We are done, return. Insert the beaker and exit! - SetAppearance(); - ClickSound(); - return true; - } - - //Next, see if the user is trying to insert something they want to be ground/juiced. - if(!heldEnt.HasTag("Grindable") && !heldEnt.TryGetComponent(out JuiceableComponent? juice)) - { - //Entity did NOT pass the whitelist for grind/juice. - //Wouldn't want the clown grinding up the Captain's ID card now would you? - //Why am I asking you? You're biased. - return false; - } - - //Cap the chamber. Don't want someone putting in 500 entities and ejecting them all at once. - //Maybe I should have done that for the microwave too? - if (_chamber.ContainedEntities.Count >= _storageCap) - { - return false; - } - - if (!_chamber.Insert(heldEnt)) - return false; - - _uiDirty = true; - return true; - } - - /// - /// The wzhzhzh of the grinder. Processes the contents of the grinder and puts the output in the beaker. - /// - /// true for wanting to juice, false for wanting to grind. - private async void DoWork(IEntity user, GrinderProgram program) - { - //Have power, are we busy, chamber has anything to grind, a beaker for the grounds to go? - if(!Powered || _busy || ChamberEmpty || !HasBeaker || _heldBeaker == null) - { - return; - } - - _busy = true; - - UserInterface?.SendMessage(new ReagentGrinderWorkStartedMessage(program)); - switch (program) - { - case GrinderProgram.Grind: - SoundSystem.Play(Filter.Pvs(Owner), "/Audio/Machines/blender.ogg", Owner, AudioParams.Default); - //Get each item inside the chamber and get the reagents it contains. Transfer those reagents to the beaker, given we have one in. - Owner.SpawnTimer(_workTime, (Action) (() => - { - foreach (var item in _chamber.ContainedEntities.ToList()) - { - if (!item.HasTag("Grindable")) continue; - if (!item.TryGetComponent(out var solution)) continue; - if (_heldBeaker.CurrentVolume + solution.CurrentVolume > _heldBeaker.MaxVolume) continue; - _heldBeaker.TryAddSolution(solution.Solution); - solution.RemoveAllSolution(); - item.Delete(); - } - - _busy = false; - _uiDirty = true; - UserInterface?.SendMessage(new ReagentGrinderWorkCompleteMessage()); - })); - break; - - case GrinderProgram.Juice: - SoundSystem.Play(Filter.Pvs(Owner), "/Audio/Machines/juicer.ogg", Owner, AudioParams.Default); - Owner.SpawnTimer(_workTime, (Action) (() => - { - foreach (var item in _chamber.ContainedEntities.ToList()) - { - if (!item.TryGetComponent(out var juiceMe)) continue; - if (_heldBeaker.CurrentVolume + juiceMe.JuiceResultSolution.TotalVolume > _heldBeaker.MaxVolume) continue; - _heldBeaker.TryAddSolution(juiceMe.JuiceResultSolution); - item.Delete(); - } - UserInterface?.SendMessage(new ReagentGrinderWorkCompleteMessage()); - _busy = false; - _uiDirty = true; - })); - break; - } - } + [ViewVariables(VVAccess.ReadWrite)] [DataField("chamberCapacity")] public int StorageCap = 16; + [ViewVariables(VVAccess.ReadWrite)] [DataField("workTime")] public int WorkTime = 3500; //3.5 seconds, completely arbitrary for now. } } diff --git a/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs b/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs index 95f3dbe5f8..c58c5b32ee 100644 --- a/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs +++ b/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs @@ -1,19 +1,314 @@ -using Content.Server.Kitchen.Components; +using System; +using System.Collections.Generic; +using System.Linq; +using Content.Server.Chemistry.Components; +using Content.Server.Hands.Components; +using Content.Server.Items; +using Content.Server.Kitchen.Components; +using Content.Server.Power.Components; +using Content.Server.UserInterface; +using Content.Shared.Chemistry.Solution; +using Content.Shared.Interaction; +using Content.Shared.Kitchen.Components; +using Content.Shared.Notification.Managers; +using Content.Shared.Random.Helpers; +using Content.Shared.Tag; using JetBrains.Annotations; +using Robust.Server.GameObjects; +using Robust.Shared.Audio; +using Robust.Shared.Containers; using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Robust.Shared.Player; +using Robust.Shared.Utility; namespace Content.Server.Kitchen.EntitySystems { [UsedImplicitly] internal sealed class ReagentGrinderSystem : EntitySystem { + [Dependency] private readonly IEntityManager _entityManager = default!; + + private Queue _uiUpdateQueue = new (); + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnComponentInit); + SubscribeLocalEvent((_, component, _) => EnqueueUiUpdate(component)); + SubscribeLocalEvent(OnInteractHand); + SubscribeLocalEvent(OnInteractUsing); + } + + private void OnInteractUsing(EntityUid uid, ReagentGrinderComponent component, InteractUsingEvent args) + { + if(args.Handled) return; + + if (!args.User.TryGetComponent(out IHandsComponent? hands)) + { + component.Owner.PopupMessage(args.User, Loc.GetString("reagent-grinder-component-interact-using-no-hands")); + args.Handled = true; + return; + } + + IEntity heldEnt = args.Used; + + //First, check if user is trying to insert a beaker. + //No promise it will be a beaker right now, but whatever. + //Maybe this should whitelist "beaker" in the prototype id of heldEnt? + if(heldEnt.TryGetComponent(out SolutionContainerComponent? beaker) && beaker.Capabilities.HasFlag(SolutionContainerCaps.FitsInDispenser)) + { + component.BeakerContainer.Insert(heldEnt); + component.HeldBeaker = beaker; + EnqueueUiUpdate(component); + //We are done, return. Insert the beaker and exit! + if (component.Owner.TryGetComponent(out AppearanceComponent? appearance)) + { + appearance.SetData(SharedReagentGrinderComponent.ReagentGrinderVisualState.BeakerAttached, component.BeakerContainer.ContainedEntity != null); + } + ClickSound(component); + args.Handled = true; + return; + } + + //Next, see if the user is trying to insert something they want to be ground/juiced. + if(!heldEnt.HasTag("Grindable") && !heldEnt.TryGetComponent(out JuiceableComponent? juice)) + { + //Entity did NOT pass the whitelist for grind/juice. + //Wouldn't want the clown grinding up the Captain's ID card now would you? + //Why am I asking you? You're biased. + return; + } + + //Cap the chamber. Don't want someone putting in 500 entities and ejecting them all at once. + //Maybe I should have done that for the microwave too? + if (component.Chamber.ContainedEntities.Count >= component.StorageCap) + { + return; + } + + if (!component.Chamber.Insert(heldEnt)) + { + return; + } + + EnqueueUiUpdate(component); + args.Handled = true; + } + + private void OnInteractHand(EntityUid uid, ReagentGrinderComponent component, InteractHandEvent args) + { + if (args.Handled) return; + + if (!args.User.TryGetComponent(out ActorComponent? actor)) + { + return; + } + EnqueueUiUpdate(component); + component.Owner.GetUIOrNull(SharedReagentGrinderComponent.ReagentGrinderUiKey.Key)?.Toggle(actor.PlayerSession); + args.Handled = true; + } + + private void EnqueueUiUpdate(ReagentGrinderComponent component) + { + if(!_uiUpdateQueue.Contains(component)) _uiUpdateQueue.Enqueue(component); + } + + private void OnComponentInit(EntityUid uid, ReagentGrinderComponent component, ComponentInit args) + { + EnqueueUiUpdate(component); + + //A slot for the beaker where the grounds/juices will go. + component.BeakerContainer = + ContainerHelpers.EnsureContainer(component.Owner, $"{component.Name}-reagentContainerContainer"); + + //A container for the things that WILL be ground/juiced. Useful for ejecting them instead of deleting them from the hands of the user. + component.Chamber = + ContainerHelpers.EnsureContainer(component.Owner, $"{component.Name}-entityContainerContainer"); + + var bui = component.Owner.GetUIOrNull(SharedReagentGrinderComponent.ReagentGrinderUiKey.Key); + if (bui != null) + { + bui.OnReceiveMessage += msg => OnUIMessageReceived(uid, component, msg); + } + } + + private void OnUIMessageReceived(EntityUid uid, ReagentGrinderComponent component, + ServerBoundUserInterfaceMessage message) + { + if(component.Busy) + { + return; + } + + switch(message.Message) + { + case SharedReagentGrinderComponent.ReagentGrinderGrindStartMessage msg: + if (!component.Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver) || !receiver.Powered) break; + ClickSound(component); + DoWork(component, message.Session.AttachedEntity!, SharedReagentGrinderComponent.GrinderProgram.Grind); + break; + + case SharedReagentGrinderComponent.ReagentGrinderJuiceStartMessage msg: + if (!component.Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver2) || !receiver2.Powered) break; + ClickSound(component); + DoWork(component, message.Session.AttachedEntity!, SharedReagentGrinderComponent.GrinderProgram.Juice); + break; + + case SharedReagentGrinderComponent.ReagentGrinderEjectChamberAllMessage msg: + if(component.Chamber.ContainedEntities.Count > 0) + { + ClickSound(component); + for (var i = component.Chamber.ContainedEntities.Count - 1; i >= 0; i--) + { + var entity = component.Chamber.ContainedEntities[i]; + component.Chamber.Remove(entity); + entity.RandomOffset(0.4f); + } + EnqueueUiUpdate(component); + } + break; + + case SharedReagentGrinderComponent.ReagentGrinderEjectChamberContentMessage msg: + if (component.Chamber.ContainedEntities.TryFirstOrDefault(x => x.Uid == msg.EntityID, out var ent)) + { + component.Chamber.Remove(ent); + ent.RandomOffset(0.4f); + EnqueueUiUpdate(component); + ClickSound(component); + } + break; + + case SharedReagentGrinderComponent.ReagentGrinderEjectBeakerMessage msg: + ClickSound(component); + EjectBeaker(component, message.Session.AttachedEntity); + EnqueueUiUpdate(component); + break; + } + } + public override void Update(float frameTime) { base.Update(frameTime); - foreach (var comp in ComponentManager.EntityQuery(true)) + + while (_uiUpdateQueue.TryDequeue(out var comp)) { - comp.OnUpdate(); + bool canJuice = false; + bool canGrind = false; + if (comp.BeakerContainer.ContainedEntity != null) + { + foreach (var entity in comp.Chamber.ContainedEntities) + { + if (!canJuice && entity.HasComponent()) canJuice = true; + if (!canGrind && entity.HasTag("Grindable")) canGrind = true; + if (canJuice && canGrind) break; + } + } + + comp.Owner.GetUIOrNull(SharedReagentGrinderComponent.ReagentGrinderUiKey.Key)?.SetState(new ReagentGrinderInterfaceState + ( + comp.Busy, + comp.BeakerContainer.ContainedEntity != null, + comp.Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver) && receiver.Powered, + canJuice, + canGrind, + comp.Chamber.ContainedEntities.Select(item => item.Uid).ToArray(), + //Remember the beaker can be null! + comp.HeldBeaker?.Solution.Contents.ToArray() + )); } } + + /// + /// Tries to eject whatever is in the beaker slot. Puts the item in the user's hands or failing that on top + /// of the grinder. + /// + private void EjectBeaker(ReagentGrinderComponent component, IEntity? user) + { + if (component.BeakerContainer.ContainedEntity == null || component.HeldBeaker == null || component.Busy) + return; + + var beaker = component.BeakerContainer.ContainedEntity; + if(beaker is null) + return; + + component.BeakerContainer.Remove(beaker); + + if (user == null || !user.TryGetComponent(out var hands) || !component.HeldBeaker.Owner.TryGetComponent(out var item)) + return; + hands.PutInHandOrDrop(item); + + component.HeldBeaker = null; + EnqueueUiUpdate(component); + if (component.Owner.TryGetComponent(out AppearanceComponent? appearance)) + { + appearance.SetData(SharedReagentGrinderComponent.ReagentGrinderVisualState.BeakerAttached, component.BeakerContainer.ContainedEntity != null); + } + } + + /// + /// The wzhzhzh of the grinder. Processes the contents of the grinder and puts the output in the beaker. + /// + /// true for wanting to juice, false for wanting to grind. + private void DoWork(ReagentGrinderComponent component, IEntity user, SharedReagentGrinderComponent.GrinderProgram program) + { + //Have power, are we busy, chamber has anything to grind, a beaker for the grounds to go? + if(!component.Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver) || !receiver.Powered || component.Busy || component.Chamber.ContainedEntities.Count <= 0 || component.BeakerContainer.ContainedEntity == null || component.HeldBeaker == null) + { + return; + } + + component.Busy = true; + + var bui = component.Owner.GetUIOrNull(SharedReagentGrinderComponent.ReagentGrinderUiKey.Key); + bui?.SendMessage(new SharedReagentGrinderComponent.ReagentGrinderWorkStartedMessage(program)); + switch (program) + { + case SharedReagentGrinderComponent.GrinderProgram.Grind: + SoundSystem.Play(Filter.Pvs(component.Owner), "/Audio/Machines/blender.ogg", component.Owner, AudioParams.Default); + //Get each item inside the chamber and get the reagents it contains. Transfer those reagents to the beaker, given we have one in. + component.Owner.SpawnTimer(component.WorkTime, (Action) (() => + { + foreach (var item in component.Chamber.ContainedEntities.ToList()) + { + if (!item.HasTag("Grindable")) continue; + if (!item.TryGetComponent(out var solution)) continue; + if (component.HeldBeaker.CurrentVolume + solution.CurrentVolume > component.HeldBeaker.MaxVolume) continue; + component.HeldBeaker.TryAddSolution(solution.Solution); + solution.RemoveAllSolution(); + item.Delete(); + } + + component.Busy = false; + EnqueueUiUpdate(component); + bui?.SendMessage(new SharedReagentGrinderComponent.ReagentGrinderWorkCompleteMessage()); + })); + break; + + case SharedReagentGrinderComponent.GrinderProgram.Juice: + SoundSystem.Play(Filter.Pvs(component.Owner), "/Audio/Machines/juicer.ogg", component.Owner, AudioParams.Default); + component.Owner.SpawnTimer(component.WorkTime, (Action) (() => + { + foreach (var item in component.Chamber.ContainedEntities.ToList()) + { + if (!item.TryGetComponent(out var juiceMe)) continue; + if (component.HeldBeaker.CurrentVolume + juiceMe.JuiceResultSolution.TotalVolume > component.HeldBeaker.MaxVolume) continue; + component.HeldBeaker.TryAddSolution(juiceMe.JuiceResultSolution); + item.Delete(); + } + bui?.SendMessage(new SharedReagentGrinderComponent.ReagentGrinderWorkCompleteMessage()); + component.Busy = false; + EnqueueUiUpdate(component); + })); + break; + } + } + + private void ClickSound(ReagentGrinderComponent component) + { + SoundSystem.Play(Filter.Pvs(component.Owner), "/Audio/Machines/machine_switch.ogg", component.Owner, AudioParams.Default.WithVolume(-2f)); + } } } diff --git a/Content.Shared/Kitchen/Components/SharedReagentGrinderComponent.cs b/Content.Shared/Kitchen/Components/SharedReagentGrinderComponent.cs index ca1eae6923..0effbe15db 100644 --- a/Content.Shared/Kitchen/Components/SharedReagentGrinderComponent.cs +++ b/Content.Shared/Kitchen/Components/SharedReagentGrinderComponent.cs @@ -51,16 +51,6 @@ namespace Content.Shared.Kitchen.Components } } - [Serializable, NetSerializable] - public class ReagentGrinderVaporizeReagentIndexedMessage : BoundUserInterfaceMessage - { - public Solution.ReagentQuantity ReagentQuantity; - public ReagentGrinderVaporizeReagentIndexedMessage(Solution.ReagentQuantity reagentQuantity) - { - ReagentQuantity = reagentQuantity; - } - } - [Serializable, NetSerializable] public class ReagentGrinderWorkStartedMessage : BoundUserInterfaceMessage { From 8ed1a9e19cc7badc262a3a5eb2620af05b1b2c45 Mon Sep 17 00:00:00 2001 From: Vera Aguilera Puerto Date: Thu, 29 Jul 2021 12:55:22 +0200 Subject: [PATCH 25/76] Add ButtonHelpers and the SetButtonDisabledRecursive helper. Fixes the mess from the other day. --- Content.Client/AME/UI/AMEWindow.xaml.cs | 3 +- .../Chemistry/UI/ChemMasterWindow.cs | 3 +- .../Chemistry/UI/ReagentDispenserWindow.cs | 3 +- Content.Client/UserInterface/ButtonHelpers.cs | 31 +++++++++++++++++++ 4 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 Content.Client/UserInterface/ButtonHelpers.cs diff --git a/Content.Client/AME/UI/AMEWindow.xaml.cs b/Content.Client/AME/UI/AMEWindow.xaml.cs index c55a18efcf..90c345faf7 100644 --- a/Content.Client/AME/UI/AMEWindow.xaml.cs +++ b/Content.Client/AME/UI/AMEWindow.xaml.cs @@ -1,3 +1,4 @@ +using Content.Client.UserInterface; using Robust.Client.AutoGenerated; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; @@ -35,7 +36,7 @@ namespace Content.Client.AME.UI // Disable all buttons if not powered if (Contents.Children != null) { - SetButtonDisabledRecursive(Contents, !castState.HasPower); + ButtonHelpers.SetButtonDisabledRecursive(Contents, !castState.HasPower); EjectButton.Disabled = false; } diff --git a/Content.Client/Chemistry/UI/ChemMasterWindow.cs b/Content.Client/Chemistry/UI/ChemMasterWindow.cs index 1bd7b63202..5f3a895e35 100644 --- a/Content.Client/Chemistry/UI/ChemMasterWindow.cs +++ b/Content.Client/Chemistry/UI/ChemMasterWindow.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using Content.Client.Stylesheets; +using Content.Client.UserInterface; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Reagent; using Robust.Client.Graphics; @@ -279,7 +280,7 @@ namespace Content.Client.Chemistry.UI UpdatePanelInfo(castState); if (Contents.Children != null) { - SetButtonDisabledRecursive(Contents, !castState.HasPower); + ButtonHelpers.SetButtonDisabledRecursive(Contents, !castState.HasPower); EjectButton.Disabled = !castState.HasBeaker; } } diff --git a/Content.Client/Chemistry/UI/ReagentDispenserWindow.cs b/Content.Client/Chemistry/UI/ReagentDispenserWindow.cs index 4dc65be1ad..34311fb74d 100644 --- a/Content.Client/Chemistry/UI/ReagentDispenserWindow.cs +++ b/Content.Client/Chemistry/UI/ReagentDispenserWindow.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Content.Client.Stylesheets; +using Content.Client.UserInterface; using Content.Shared.Chemistry.Dispenser; using Content.Shared.Chemistry.Reagent; using Robust.Client.Graphics; @@ -186,7 +187,7 @@ namespace Content.Client.Chemistry.UI // Disable all buttons if not powered if (Contents.Children != null) { - SetButtonDisabledRecursive(Contents, !castState.HasPower); + ButtonHelpers.SetButtonDisabledRecursive(Contents, !castState.HasPower); EjectButton.Disabled = false; } diff --git a/Content.Client/UserInterface/ButtonHelpers.cs b/Content.Client/UserInterface/ButtonHelpers.cs new file mode 100644 index 0000000000..38abc8684c --- /dev/null +++ b/Content.Client/UserInterface/ButtonHelpers.cs @@ -0,0 +1,31 @@ +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; + +namespace Content.Client.UserInterface +{ + public static class ButtonHelpers + { + /// + /// This searches recursively through all the children of "parent" + /// and sets the Disabled value of any buttons found to "val" + /// + /// The control which childrens get searched + /// The value to which disabled gets set + public static void SetButtonDisabledRecursive(Control parent, bool val) + { + foreach (var child in parent.Children) + { + if (child is Button but) + { + but.Disabled = val; + continue; + } + + if (child.ChildCount > 0) + { + SetButtonDisabledRecursive(child, val); + } + } + } + } +} From 9b42c1f69b1670537931a784f9a5201b557e20b8 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Thu, 29 Jul 2021 13:06:18 +0200 Subject: [PATCH 26/76] Fix bad localization string in secret stash. --- Content.Server/Storage/Components/SecretStashComponent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Server/Storage/Components/SecretStashComponent.cs b/Content.Server/Storage/Components/SecretStashComponent.cs index b202ab19db..30af3865fc 100644 --- a/Content.Server/Storage/Components/SecretStashComponent.cs +++ b/Content.Server/Storage/Components/SecretStashComponent.cs @@ -81,7 +81,7 @@ namespace Content.Server.Storage.Components if (_itemContainer.ContainedEntity == null) return false; - Owner.PopupMessage(user, Loc.GetString("There was something inside {0}!", ("stash", SecretPartName))); + Owner.PopupMessage(user, Loc.GetString("comp-secret-stash-action-get-item-found-something", ("stash", SecretPartName))); if (user.TryGetComponent(out HandsComponent? hands)) { From 8c362fcd9b8065aa92bb684f7610fe4d4c74e5e9 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Thu, 29 Jul 2021 16:55:09 +0200 Subject: [PATCH 27/76] Use non-directed event for powernet battery sync. Most of the CPU time in the power system was sending these events. --- .../Power/EntitySystems/BatterySystem.cs | 24 ++++++++++--------- .../Power/EntitySystems/PowerNetSystem.cs | 14 ++++------- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/Content.Server/Power/EntitySystems/BatterySystem.cs b/Content.Server/Power/EntitySystems/BatterySystem.cs index 44126d6810..e97b007861 100644 --- a/Content.Server/Power/EntitySystems/BatterySystem.cs +++ b/Content.Server/Power/EntitySystems/BatterySystem.cs @@ -11,23 +11,25 @@ namespace Content.Server.Power.EntitySystems { base.Initialize(); - SubscribeLocalEvent(PreSync); - SubscribeLocalEvent(PostSync); + SubscribeLocalEvent(PreSync); + SubscribeLocalEvent(PostSync); } - private void PreSync(EntityUid uid, BatteryComponent component, NetworkBatteryPreSync args) + private void PreSync(NetworkBatteryPreSync ev) { - var networkBattery = ComponentManager.GetComponent(uid); - - networkBattery.NetworkBattery.Capacity = component.MaxCharge; - networkBattery.NetworkBattery.CurrentStorage = component.CurrentCharge; + foreach (var (bat, netBat) in ComponentManager.EntityQuery()) + { + netBat.NetworkBattery.Capacity = bat.MaxCharge; + netBat.NetworkBattery.CurrentStorage = bat.CurrentCharge; + } } - private void PostSync(EntityUid uid, BatteryComponent component, NetworkBatteryPostSync args) + private void PostSync(NetworkBatteryPostSync ev) { - var networkBattery = ComponentManager.GetComponent(uid); - - component.CurrentCharge = networkBattery.NetworkBattery.CurrentStorage; + foreach (var (bat, netBat) in ComponentManager.EntityQuery()) + { + bat.CurrentCharge = netBat.NetworkBattery.CurrentStorage; + } } public override void Update(float frameTime) diff --git a/Content.Server/Power/EntitySystems/PowerNetSystem.cs b/Content.Server/Power/EntitySystems/PowerNetSystem.cs index 3b7f81fb25..84b65289b5 100644 --- a/Content.Server/Power/EntitySystems/PowerNetSystem.cs +++ b/Content.Server/Power/EntitySystems/PowerNetSystem.cs @@ -175,19 +175,13 @@ namespace Content.Server.Power.EntitySystems } // Synchronize batteries - foreach (var battery in ComponentManager.EntityQuery()) - { - RaiseLocalEvent(battery.Owner.Uid, new NetworkBatteryPreSync()); - } + RaiseLocalEvent(new NetworkBatteryPreSync()); // Run power solver. _solver.Tick(frameTime, _powerState); // Synchronize batteries, the other way around. - foreach (var battery in ComponentManager.EntityQuery()) - { - RaiseLocalEvent(battery.Owner.Uid, new NetworkBatteryPostSync()); - } + RaiseLocalEvent(new NetworkBatteryPostSync()); // Send events where necessary. { @@ -313,7 +307,7 @@ namespace Content.Server.Power.EntitySystems /// Raised before power network simulation happens, to synchronize battery state from /// components like into . /// - public sealed class NetworkBatteryPreSync : EntityEventArgs + public struct NetworkBatteryPreSync { } @@ -321,7 +315,7 @@ namespace Content.Server.Power.EntitySystems /// Raised after power network simulation happens, to synchronize battery charge changes from /// to components like . /// - public sealed class NetworkBatteryPostSync : EntityEventArgs + public struct NetworkBatteryPostSync { } From f4769c00d7891a2dd428bb6ee153f77b20ff9805 Mon Sep 17 00:00:00 2001 From: Vera Aguilera Puerto Date: Fri, 30 Jul 2021 12:25:58 +0200 Subject: [PATCH 28/76] SubFloorHide requires entities to be anchored by default. --- Content.Shared/SubFloor/SubFloorHideComponent.cs | 9 +++++++++ Content.Shared/SubFloor/SubFloorHideSystem.cs | 16 ++++++++++++++-- SpaceStation14.sln.DotSettings | 2 ++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/Content.Shared/SubFloor/SubFloorHideComponent.cs b/Content.Shared/SubFloor/SubFloorHideComponent.cs index a76c73473e..93587e6f8c 100644 --- a/Content.Shared/SubFloor/SubFloorHideComponent.cs +++ b/Content.Shared/SubFloor/SubFloorHideComponent.cs @@ -1,4 +1,6 @@ using Robust.Shared.GameObjects; +using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.ViewVariables; namespace Content.Shared.SubFloor { @@ -13,5 +15,12 @@ namespace Content.Shared.SubFloor { /// public override string Name => "SubFloorHide"; + + /// + /// This entity needs to be anchored to be hid in the subfloor. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("requireAnchored")] + public bool RequireAnchored { get; set; } = true; } } diff --git a/Content.Shared/SubFloor/SubFloorHideSystem.cs b/Content.Shared/SubFloor/SubFloorHideSystem.cs index 5dd30a86a3..6f3f5bd9f2 100644 --- a/Content.Shared/SubFloor/SubFloorHideSystem.cs +++ b/Content.Shared/SubFloor/SubFloorHideSystem.cs @@ -68,8 +68,7 @@ namespace Content.Shared.SubFloor var transform = ComponentManager.GetComponent(uid); // We do this directly instead of calling UpdateEntity. - if(_mapManager.TryGetGrid(transform.GridID, out var grid)) - UpdateTile(grid, grid.TileIndicesFor(transform.Coordinates)); + UpdateEntity(uid); } private void MapManagerOnTileChanged(object? sender, TileChangedEventArgs e) @@ -113,6 +112,7 @@ namespace Content.Shared.SubFloor private void UpdateEntity(EntityUid uid) { var transform = ComponentManager.GetComponent(uid); + if (!_mapManager.TryGetGrid(transform.GridID, out var grid)) { // Not being on a grid counts as no subfloor, unhide this. @@ -134,6 +134,18 @@ namespace Content.Shared.SubFloor if (subFloorHideEvent.Handled) return; + // This might look weird, but basically we only need to query the SubFloorHide and Transform components + // if we are gonna hide the entity and we require it to be anchored to be hidden. Because getting components + // is "expensive", we have a slow path where we query them, and a fast path where we don't. + if (!subFloor + && ComponentManager.TryGetComponent(uid, out SubFloorHideComponent? subFloorHideComponent) && + subFloorHideComponent.RequireAnchored + && ComponentManager.TryGetComponent(uid, out ITransformComponent? transformComponent)) + { + // If we require the entity to be anchored but it's not, this will set subfloor to true, unhiding it. + subFloor = !transformComponent.Anchored; + } + // Show sprite if (ComponentManager.TryGetComponent(uid, out SharedSpriteComponent? spriteComponent)) { diff --git a/SpaceStation14.sln.DotSettings b/SpaceStation14.sln.DotSettings index 5ff7f8d1e5..692912bc33 100644 --- a/SpaceStation14.sln.DotSettings +++ b/SpaceStation14.sln.DotSettings @@ -264,6 +264,7 @@ True True True + True True True True @@ -284,6 +285,7 @@ True True True + True True True True From e256cb7bb0d9b1e57c03eb63ea423e424eb46a8e Mon Sep 17 00:00:00 2001 From: Vera Aguilera Puerto Date: Fri, 30 Jul 2021 14:00:14 +0200 Subject: [PATCH 29/76] Optimize atmos devices' appearance updating. --- .../GasDualPortVentPumpSystem.cs | 18 ++++++------- .../EntitySystems/GasPressurePumpSystem.cs | 17 +++++++------ .../EntitySystems/GasThermoMachineSystem.cs | 14 +++++------ .../Unary/EntitySystems/GasVentPumpSystem.cs | 18 ++++++------- .../EntitySystems/GasVentScrubberSystem.cs | 25 ++++++++----------- 5 files changed, 44 insertions(+), 48 deletions(-) diff --git a/Content.Server/Atmos/Piping/Binary/EntitySystems/GasDualPortVentPumpSystem.cs b/Content.Server/Atmos/Piping/Binary/EntitySystems/GasDualPortVentPumpSystem.cs index 24c03e399e..78205ef201 100644 --- a/Content.Server/Atmos/Piping/Binary/EntitySystems/GasDualPortVentPumpSystem.cs +++ b/Content.Server/Atmos/Piping/Binary/EntitySystems/GasDualPortVentPumpSystem.cs @@ -37,23 +37,23 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems return; } - appearance?.SetData(VentPumpVisuals.State, VentPumpState.Off); - - if (!vent.Enabled) - return; - - if (!ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer)) - return; - - if (!nodeContainer.TryGetNode(vent.InletName, out PipeNode? inlet) + if (!vent.Enabled + || !ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer) + || !nodeContainer.TryGetNode(vent.InletName, out PipeNode? inlet) || !nodeContainer.TryGetNode(vent.OutletName, out PipeNode? outlet)) + { + appearance?.SetData(VentPumpVisuals.State, VentPumpState.Off); return; + } var environment = _atmosphereSystem.GetTileMixture(vent.Owner.Transform.Coordinates, true); // We're in an air-blocked tile... Do nothing. if (environment == null) + { + appearance?.SetData(VentPumpVisuals.State, VentPumpState.Off); return; + } if (vent.PumpDirection == VentPumpDirection.Releasing) { diff --git a/Content.Server/Atmos/Piping/Binary/EntitySystems/GasPressurePumpSystem.cs b/Content.Server/Atmos/Piping/Binary/EntitySystems/GasPressurePumpSystem.cs index dc38b4da76..3d2b9ca2ae 100644 --- a/Content.Server/Atmos/Piping/Binary/EntitySystems/GasPressurePumpSystem.cs +++ b/Content.Server/Atmos/Piping/Binary/EntitySystems/GasPressurePumpSystem.cs @@ -25,22 +25,23 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems private void OnPumpUpdated(EntityUid uid, GasPressurePumpComponent pump, AtmosDeviceUpdateEvent args) { var appearance = pump.Owner.GetComponentOrNull(); - appearance?.SetData(PressurePumpVisuals.Enabled, false); - if (!pump.Enabled) - return; - - if (!ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer)) - return; - - if (!nodeContainer.TryGetNode(pump.InletName, out PipeNode? inlet) + if (!pump.Enabled + || !ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer) + || !nodeContainer.TryGetNode(pump.InletName, out PipeNode? inlet) || !nodeContainer.TryGetNode(pump.OutletName, out PipeNode? outlet)) + { + appearance?.SetData(PressurePumpVisuals.Enabled, false); return; + } var outputStartingPressure = outlet.Air.Pressure; if (MathHelper.CloseTo(pump.TargetPressure, outputStartingPressure)) + { + appearance?.SetData(PressurePumpVisuals.Enabled, false); return; // No need to pump gas if target has been reached. + } if (inlet.Air.TotalMoles > 0 && inlet.Air.Temperature > 0) { diff --git a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasThermoMachineSystem.cs b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasThermoMachineSystem.cs index f48aa149f5..8706a22156 100644 --- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasThermoMachineSystem.cs +++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasThermoMachineSystem.cs @@ -27,16 +27,14 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems private void OnThermoMachineUpdated(EntityUid uid, GasThermoMachineComponent thermoMachine, AtmosDeviceUpdateEvent args) { var appearance = thermoMachine.Owner.GetComponentOrNull(); - appearance?.SetData(ThermoMachineVisuals.Enabled, false); - if (!thermoMachine.Enabled) - return; - - if (!ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer)) - return; - - if (!nodeContainer.TryGetNode(thermoMachine.InletName, out PipeNode? inlet)) + if (!thermoMachine.Enabled + || !ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer) + || !nodeContainer.TryGetNode(thermoMachine.InletName, out PipeNode? inlet)) + { + appearance?.SetData(ThermoMachineVisuals.Enabled, false); return; + } var airHeatCapacity = _atmosphereSystem.GetHeatCapacity(inlet.Air); var combinedHeatCapacity = airHeatCapacity + thermoMachine.HeatCapacity; diff --git a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentPumpSystem.cs b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentPumpSystem.cs index f9af0231f6..a008cde4ca 100644 --- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentPumpSystem.cs +++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentPumpSystem.cs @@ -36,22 +36,22 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems return; } - appearance?.SetData(VentPumpVisuals.State, VentPumpState.Off); - - if (!vent.Enabled) - return; - - if (!ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer)) - return; - - if (!nodeContainer.TryGetNode(vent.InletName, out PipeNode? pipe)) + if (!vent.Enabled + || !ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer) + || !nodeContainer.TryGetNode(vent.InletName, out PipeNode? pipe)) + { + appearance?.SetData(VentPumpVisuals.State, VentPumpState.Off); return; + } var environment = _atmosphereSystem.GetTileMixture(vent.Owner.Transform.Coordinates, true); // We're in an air-blocked tile... Do nothing. if (environment == null) + { + appearance?.SetData(VentPumpVisuals.State, VentPumpState.Off); return; + } if (vent.PumpDirection == VentPumpDirection.Releasing) { diff --git a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentScrubberSystem.cs b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentScrubberSystem.cs index 190fd86228..9b6722411d 100644 --- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentScrubberSystem.cs +++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentScrubberSystem.cs @@ -37,16 +37,13 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems return; } - appearance?.SetData(ScrubberVisuals.State, ScrubberState.Off); - - if (!scrubber.Enabled) - return; - - if (!ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer)) - return; - - if (!nodeContainer.TryGetNode(scrubber.OutletName, out PipeNode? outlet)) + if (!scrubber.Enabled + || !ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer) + || !nodeContainer.TryGetNode(scrubber.OutletName, out PipeNode? outlet)) + { + appearance?.SetData(ScrubberVisuals.State, ScrubberState.Off); return; + } var environment = _atmosphereSystem.GetTileMixture(scrubber.Owner.Transform.Coordinates, true); @@ -72,12 +69,12 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems private void Scrub(AtmosphereSystem atmosphereSystem, GasVentScrubberComponent scrubber, AppearanceComponent? appearance, GasMixture? tile, PipeNode outlet) { // Cannot scrub if tile is null or air-blocked. - if (tile == null) - return; - - // Cannot scrub if pressure too high. - if (outlet.Air.Pressure >= 50 * Atmospherics.OneAtmosphere) + if (tile == null + || outlet.Air.Pressure >= 50 * Atmospherics.OneAtmosphere) // Cannot scrub if pressure too high. + { + appearance?.SetData(ScrubberVisuals.State, ScrubberState.Off); return; + } if (scrubber.PumpDirection == ScrubberPumpDirection.Scrubbing) { From 898bdc949149554d5f9e7cd95bfd08a5744cb451 Mon Sep 17 00:00:00 2001 From: Galactic Chimp <63882831+GalacticChimp@users.noreply.github.com> Date: Fri, 30 Jul 2021 19:22:06 +0200 Subject: [PATCH 30/76] #4420 Replaced SecureEntityStorageComp with LockComponent (new ECS) (#4228) * #4420 moved sound components to a better spot in the project * MWF-4420 - ported SecureEntityStorageComponent to new ECS system, as a LockComponent * MWF-4420 - removed unused usings * #4420 removed dumb ToggleLockVerb override workaround * #4420 added SoundSpecifier to LockComponent --- Content.Client/Entry/IgnoredComponents.cs | 1 + .../Storage/Visualizers/StorageVisualizer.cs | 2 +- Content.Server/Lock/LockComponent.cs | 64 ++++++++ Content.Server/Lock/LockSystem.cs | 116 ++++++++++++++ .../Components/EntityStorageComponent.cs | 18 ++- .../SecureEntityStorageComponent.cs | 148 ------------------ .../Locale/en-US/lock/lock-component.ftl | 10 ++ .../secure-entity-storage-component.ftl | 6 - .../Storage/Closets/Lockers/base.yml | 2 +- .../Structures/Storage/Crates/crates.yml | 18 +-- 10 files changed, 215 insertions(+), 170 deletions(-) create mode 100644 Content.Server/Lock/LockComponent.cs create mode 100644 Content.Server/Lock/LockSystem.cs delete mode 100644 Content.Server/Storage/Components/SecureEntityStorageComponent.cs create mode 100644 Resources/Locale/en-US/lock/lock-component.ftl delete mode 100644 Resources/Locale/en-US/storage/components/secure-entity-storage-component.ftl diff --git a/Content.Client/Entry/IgnoredComponents.cs b/Content.Client/Entry/IgnoredComponents.cs index 805c620ea0..f5d976796d 100644 --- a/Content.Client/Entry/IgnoredComponents.cs +++ b/Content.Client/Entry/IgnoredComponents.cs @@ -92,6 +92,7 @@ namespace Content.Client.Entry "ExaminableBattery", "PottedPlantHide", "SecureEntityStorage", + "Lock", "PresetIdCard", "SolarControlConsole", "FlashOnTrigger", diff --git a/Content.Client/Storage/Visualizers/StorageVisualizer.cs b/Content.Client/Storage/Visualizers/StorageVisualizer.cs index 7b7aaf28c4..43a8df5684 100644 --- a/Content.Client/Storage/Visualizers/StorageVisualizer.cs +++ b/Content.Client/Storage/Visualizers/StorageVisualizer.cs @@ -1,4 +1,4 @@ -using Content.Shared.Storage; +using Content.Shared.Storage; using JetBrains.Annotations; using Robust.Client.GameObjects; using Robust.Shared.GameObjects; diff --git a/Content.Server/Lock/LockComponent.cs b/Content.Server/Lock/LockComponent.cs new file mode 100644 index 0000000000..966548e607 --- /dev/null +++ b/Content.Server/Lock/LockComponent.cs @@ -0,0 +1,64 @@ +using Content.Server.Lock; +using Content.Shared.ActionBlocker; +using Content.Shared.Interaction; +using Content.Shared.Interaction.Helpers; +using Content.Shared.Sound; +using Content.Shared.Verbs; +using Robust.Shared.GameObjects; +using Robust.Shared.Localization; +using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.ViewVariables; + +namespace Content.Server.Storage.Components +{ + /// + /// Allows locking/unlocking, with access determined by AccessReader + /// + [RegisterComponent] + public class LockComponent : Component + { + public override string Name => "Lock"; + + [ViewVariables(VVAccess.ReadWrite)] [DataField("locked")] public bool Locked { get; set; } = true; + [ViewVariables(VVAccess.ReadWrite)] [DataField("unlockingSound")] public SoundSpecifier? UnlockSound { get; set; } = new SoundPathSpecifier("/Audio/Machines/door_lock_off.ogg"); + [ViewVariables(VVAccess.ReadWrite)] [DataField("lockingSound")] public SoundSpecifier? LockSound { get; set; } = new SoundPathSpecifier("/Audio/Machines/door_lock_off.ogg"); + + [Verb] + private sealed class ToggleLockVerb : Verb + { + protected override void GetData(IEntity user, LockComponent component, VerbData data) + { + if (!EntitySystem.Get().CanInteract(user) || + component.Owner.TryGetComponent(out EntityStorageComponent? entityStorageComponent) && entityStorageComponent.Open) + { + data.Visibility = VerbVisibility.Invisible; + return; + } + + data.Text = Loc.GetString(component.Locked ? "toggle-lock-verb-unlock" : "toggle-lock-verb-lock"); + } + + protected override void Activate(IEntity user, LockComponent component) + { + // Do checks + if (!EntitySystem.Get().CanInteract(user) || + !user.InRangeUnobstructed(component)) + { + return; + } + + // Call relevant entity system + var lockSystem = user.EntityManager.EntitySysManager.GetEntitySystem(); + var eventData = new ActivateInWorldEvent(user, component.Owner); + if (component.Locked) + { + lockSystem.DoUnlock(component, eventData); + } + else + { + lockSystem.DoLock(component, eventData); + } + } + } + } +} diff --git a/Content.Server/Lock/LockSystem.cs b/Content.Server/Lock/LockSystem.cs new file mode 100644 index 0000000000..063ac1656a --- /dev/null +++ b/Content.Server/Lock/LockSystem.cs @@ -0,0 +1,116 @@ +using Content.Server.Access.Components; +using Content.Server.Storage.Components; +using Content.Shared.Examine; +using Content.Shared.Interaction; +using Content.Shared.Notification.Managers; +using Content.Shared.Storage; +using JetBrains.Annotations; +using Robust.Server.GameObjects; +using Robust.Shared.Audio; +using Robust.Shared.GameObjects; +using Robust.Shared.Localization; +using Robust.Shared.Player; + +namespace Content.Server.Lock +{ + /// + /// Handles (un)locking and examining of Lock components + /// + [UsedImplicitly] + public class LockSystem : EntitySystem + { + /// + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnActivated); + SubscribeLocalEvent(OnExamined); + } + + private void OnStartup(EntityUid eUI, LockComponent lockComp, ComponentStartup args) + { + if (lockComp.Owner.TryGetComponent(out AppearanceComponent? appearance)) + { + appearance.SetData(StorageVisuals.CanLock, true); + } + } + + private void OnActivated(EntityUid eUI, LockComponent lockComp, ActivateInWorldEvent args) + { + // Only attempt an unlock by default on Activate + if (lockComp.Locked) + { + DoUnlock(lockComp, args); + } + } + + private void OnExamined(EntityUid eUI, LockComponent lockComp, ExaminedEvent args) + { + args.Message.AddText("\n"); + args.Message.AddText(Loc.GetString(lockComp.Locked + ? "lock-comp-on-examined-is-locked" + : "lock-comp-on-examined-is-unlocked", + ("entityName", lockComp.Owner.Name))); + } + + public void DoLock(LockComponent lockComp, ActivateInWorldEvent args) + { + if (!HasUserAccess(lockComp, args.User)) + { + return; + } + + lockComp.Owner.PopupMessage(args.User, Loc.GetString("lock-comp-do-lock-success", ("entityName",lockComp.Owner.Name))); + lockComp.Locked = true; + if(lockComp.LockSound != null) + { + SoundSystem.Play(Filter.Pvs(lockComp.Owner), lockComp.LockSound.GetSound(), lockComp.Owner, AudioParams.Default.WithVolume(-5)); + } + + if (lockComp.Owner.TryGetComponent(out AppearanceComponent? appearanceComp)) + { + appearanceComp.SetData(StorageVisuals.Locked, true); + } + + args.Handled = true; + } + + public void DoUnlock(LockComponent lockComp, ActivateInWorldEvent args ) + { + if (!HasUserAccess(lockComp, args.User)) + { + return; + } + + lockComp.Owner.PopupMessage(args.User, Loc.GetString("lock-comp-do-unlock-success", ("entityName", lockComp.Owner.Name))); + lockComp.Locked = false; + if(lockComp.UnlockSound != null) + { + SoundSystem.Play(Filter.Pvs(lockComp.Owner), lockComp.UnlockSound.GetSound(), lockComp.Owner, AudioParams.Default.WithVolume(-5)); + } + + if (lockComp.Owner.TryGetComponent(out AppearanceComponent? appearanceComp)) + { + appearanceComp.SetData(StorageVisuals.Locked, false); + } + + // To stop EntityStorageComponent from opening right after the container gets unlocked + args.Handled = true; + } + + private static bool HasUserAccess(LockComponent lockComp, IEntity user) + { + if (lockComp.Owner.TryGetComponent(out AccessReader? reader)) + { + if (!reader.IsAllowed(user)) + { + lockComp.Owner.PopupMessage(user, Loc.GetString("lock-comp-has-user-access-fail")); + return false; + } + } + + return true; + } + } +} diff --git a/Content.Server/Storage/Components/EntityStorageComponent.cs b/Content.Server/Storage/Components/EntityStorageComponent.cs index 5face20f31..cbe6f9affd 100644 --- a/Content.Server/Storage/Components/EntityStorageComponent.cs +++ b/Content.Server/Storage/Components/EntityStorageComponent.cs @@ -24,7 +24,6 @@ using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Maths; using Robust.Shared.Physics; -using Robust.Shared.Physics.Broadphase; using Robust.Shared.Player; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Timing; @@ -131,7 +130,8 @@ namespace Content.Server.Storage.Components private bool _beingWelded; [ViewVariables(VVAccess.ReadWrite)] - public bool CanWeldShut { + public bool CanWeldShut + { get => _canWeldShut; set { @@ -160,6 +160,13 @@ namespace Content.Server.Storage.Components public virtual void Activate(ActivateEventArgs eventArgs) { + // HACK until EntityStorageComponent gets refactored to the new ECS system + if (Owner.TryGetComponent(out var @lock) && @lock.Locked) + { + // Do nothing, LockSystem is responsible for handling this case + return; + } + ToggleOpen(eventArgs.User); } @@ -167,7 +174,7 @@ namespace Content.Server.Storage.Components { if (IsWeldedShut) { - if(!silent) Owner.PopupMessage(user, Loc.GetString("entity-storage-component-welded-shut-message")); + if (!silent) Owner.PopupMessage(user, Loc.GetString("entity-storage-component-welded-shut-message")); return false; } return true; @@ -465,7 +472,8 @@ namespace Content.Server.Storage.Components protected virtual void OpenVerbGetData(IEntity user, EntityStorageComponent component, VerbData data) { - if (!EntitySystem.Get().CanInteract(user)) + if (!EntitySystem.Get().CanInteract(user) || + component.Owner.TryGetComponent(out LockComponent? lockComponent) && lockComponent.Locked) // HACK extra check, until EntityStorage gets refactored { data.Visibility = VerbVisibility.Invisible; return; @@ -475,7 +483,7 @@ namespace Content.Server.Storage.Components { data.Visibility = VerbVisibility.Disabled; var verb = Loc.GetString(component.Open ? "open-toggle-verb-close" : "open-toggle-verb-open"); - data.Text = Loc.GetString("open-toggle-verb-welded-shut-message",("verb", verb)); + data.Text = Loc.GetString("open-toggle-verb-welded-shut-message", ("verb", verb)); return; } diff --git a/Content.Server/Storage/Components/SecureEntityStorageComponent.cs b/Content.Server/Storage/Components/SecureEntityStorageComponent.cs deleted file mode 100644 index 5dcaf78cf4..0000000000 --- a/Content.Server/Storage/Components/SecureEntityStorageComponent.cs +++ /dev/null @@ -1,148 +0,0 @@ -using Content.Server.Access.Components; -using Content.Shared.ActionBlocker; -using Content.Shared.Interaction; -using Content.Shared.Interaction.Events; -using Content.Shared.Notification.Managers; -using Content.Shared.Storage; -using Content.Shared.Verbs; -using Robust.Server.GameObjects; -using Robust.Shared.Audio; -using Robust.Shared.GameObjects; -using Robust.Shared.Localization; -using Robust.Shared.Player; -using Robust.Shared.Serialization.Manager.Attributes; -using Robust.Shared.ViewVariables; - -namespace Content.Server.Storage.Components -{ - [RegisterComponent] - [ComponentReference(typeof(EntityStorageComponent))] - [ComponentReference(typeof(IActivate))] - [ComponentReference(typeof(IStorageComponent))] - public class SecureEntityStorageComponent : EntityStorageComponent - { - public override string Name => "SecureEntityStorage"; - [DataField("locked")] - private bool _locked = true; - - [ViewVariables(VVAccess.ReadWrite)] - public bool Locked - { - get => _locked; - set - { - _locked = value; - - if (Owner.TryGetComponent(out AppearanceComponent? appearance)) - { - appearance.SetData(StorageVisuals.Locked, _locked); - } - } - } - - protected override void Startup() - { - base.Startup(); - - if (Owner.TryGetComponent(out AppearanceComponent? appearance)) - { - appearance.SetData(StorageVisuals.CanLock, true); - } - } - - public override void Activate(ActivateEventArgs eventArgs) - { - if (Locked) - { - DoToggleLock(eventArgs.User); - return; - } - - base.Activate(eventArgs); - } - - public override bool CanOpen(IEntity user, bool silent = false) - { - if (Locked) - { - Owner.PopupMessage(user, "It's locked!"); - return false; - } - return base.CanOpen(user, silent); - } - - protected override void OpenVerbGetData(IEntity user, EntityStorageComponent component, VerbData data) - { - if (Locked) - { - data.Visibility = VerbVisibility.Invisible; - - return; - } - - base.OpenVerbGetData(user, component, data); - } - - private void DoToggleLock(IEntity user) - { - if (Locked) - { - DoUnlock(user); - } - else - { - DoLock(user); - } - } - - private void DoUnlock(IEntity user) - { - if (!CheckAccess(user)) return; - - Locked = false; - SoundSystem.Play(Filter.Pvs(Owner), "/Audio/Machines/door_lock_off.ogg", Owner, AudioParams.Default.WithVolume(-5)); - } - - private void DoLock(IEntity user) - { - if (!CheckAccess(user)) return; - - Locked = true; - SoundSystem.Play(Filter.Pvs(Owner), "/Audio/Machines/door_lock_on.ogg", Owner, AudioParams.Default.WithVolume(-5)); - } - - private bool CheckAccess(IEntity user) - { - if (Owner.TryGetComponent(out AccessReader? reader)) - { - if (!reader.IsAllowed(user)) - { - Owner.PopupMessage(user, Loc.GetString("secure-entity-storage-component-not-allowed-message")); - return false; - } - } - - return true; - } - - [Verb] - private sealed class ToggleLockVerb : Verb - { - protected override void GetData(IEntity user, SecureEntityStorageComponent component, VerbData data) - { - if (!EntitySystem.Get().CanInteract(user) || component.Open) - { - data.Visibility = VerbVisibility.Invisible; - return; - } - - data.Text = Loc.GetString(component.Locked ? "toggle-lock-verb-unlock" : "toggle-lock-verb-lock"); - } - - protected override void Activate(IEntity user, SecureEntityStorageComponent component) - { - component.DoToggleLock(user); - } - } - } -} diff --git a/Resources/Locale/en-US/lock/lock-component.ftl b/Resources/Locale/en-US/lock/lock-component.ftl new file mode 100644 index 0000000000..f9f975c96e --- /dev/null +++ b/Resources/Locale/en-US/lock/lock-component.ftl @@ -0,0 +1,10 @@ +lock-comp-on-examined-is-locked = The {$entityName} seems to be locked. +lock-comp-on-examined-is-unlocked = The {$entityName} seems to be unlocked. +lock-comp-do-lock-success = You lock the {$entityName}. +lock-comp-do-unlock-success = You unlock the {$entityName}. +lock-comp-has-user-access-fail = Access denied + +## ToggleLockVerb + +toggle-lock-verb-unlock = Unlock +toggle-lock-verb-lock = Lock \ No newline at end of file diff --git a/Resources/Locale/en-US/storage/components/secure-entity-storage-component.ftl b/Resources/Locale/en-US/storage/components/secure-entity-storage-component.ftl deleted file mode 100644 index 02082f2dcc..0000000000 --- a/Resources/Locale/en-US/storage/components/secure-entity-storage-component.ftl +++ /dev/null @@ -1,6 +0,0 @@ -secure-entity-storage-component-not-allowed-message = Access denied - -## ToggleLockVerb - -toggle-lock-verb-unlock = Unlock -toggle-lock-verb-lock = Lock \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/base.yml b/Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/base.yml index 1b33400a9f..a8fe9b1a7e 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/base.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/base.yml @@ -4,7 +4,7 @@ abstract: true components: - type: AccessReader - - type: SecureEntityStorage + - type: Lock - type: Sprite netsync: false sprite: Structures/Storage/closet.rsi diff --git a/Resources/Prototypes/Entities/Structures/Storage/Crates/crates.yml b/Resources/Prototypes/Entities/Structures/Storage/Crates/crates.yml index dae7bc4c1c..70e4f09b5d 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Crates/crates.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Crates/crates.yml @@ -261,7 +261,7 @@ components: - type: AccessReader access: [["Security"]] - - type: SecureEntityStorage + - type: Lock - type: Sprite sprite: Structures/Storage/Crates/sec_gear.rsi layers: @@ -290,7 +290,7 @@ components: - type: AccessReader access: [["Engineering"]] - - type: SecureEntityStorage + - type: Lock - type: Sprite sprite: Structures/Storage/Crates/engicrate_secure.rsi layers: @@ -319,7 +319,7 @@ components: - type: AccessReader access: [["Medical"]] - - type: SecureEntityStorage + - type: Lock - type: Sprite sprite: Structures/Storage/Crates/medicalcrate_secure.rsi layers: @@ -347,7 +347,7 @@ parent: CrateGeneric components: - type: AccessReader - - type: SecureEntityStorage + - type: Lock - type: Sprite sprite: Structures/Storage/Crates/privatecrate_secure.rsi layers: @@ -376,7 +376,7 @@ components: - type: AccessReader access: [["Research"]] - - type: SecureEntityStorage + - type: Lock - type: Sprite sprite: Structures/Storage/Crates/scicrate_secure.rsi layers: @@ -405,7 +405,7 @@ components: - type: AccessReader access: [["Engineering"]] - - type: SecureEntityStorage + - type: Lock - type: Sprite sprite: Structures/Storage/Crates/plasma.rsi layers: @@ -433,7 +433,7 @@ parent: CrateGeneric components: - type: AccessReader - - type: SecureEntityStorage + - type: Lock - type: Sprite sprite: Structures/Storage/Crates/secure.rsi layers: @@ -462,7 +462,7 @@ components: - type: AccessReader access: [["Service"]] - - type: SecureEntityStorage + - type: Lock - type: Sprite sprite: Structures/Storage/Crates/hydro_secure.rsi layers: @@ -491,7 +491,7 @@ components: - type: AccessReader access: [["Security"]] - - type: SecureEntityStorage + - type: Lock - type: Sprite sprite: Structures/Storage/Crates/weapon.rsi layers: From 6649310ccf489afd6e96d7b5106d433013b0482b Mon Sep 17 00:00:00 2001 From: clyf Date: Fri, 30 Jul 2021 16:05:07 -0700 Subject: [PATCH 31/76] Moved MagbootsComponent.cs from Cloning to Clothing (#4399) --- Content.Client/{Cloning => Clothing}/MagbootsComponent.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename Content.Client/{Cloning => Clothing}/MagbootsComponent.cs (87%) diff --git a/Content.Client/Cloning/MagbootsComponent.cs b/Content.Client/Clothing/MagbootsComponent.cs similarity index 87% rename from Content.Client/Cloning/MagbootsComponent.cs rename to Content.Client/Clothing/MagbootsComponent.cs index dce398cf52..fb03315318 100644 --- a/Content.Client/Cloning/MagbootsComponent.cs +++ b/Content.Client/Clothing/MagbootsComponent.cs @@ -1,7 +1,7 @@ -using Content.Shared.Clothing; +using Content.Shared.Clothing; using Robust.Shared.GameObjects; -namespace Content.Client.Cloning +namespace Content.Client.Clothing { [RegisterComponent] public sealed class MagbootsComponent : SharedMagbootsComponent From e93d174c1bce30368e3a132592310ff6eec3e403 Mon Sep 17 00:00:00 2001 From: Kara D Date: Fri, 30 Jul 2021 16:19:24 -0700 Subject: [PATCH 32/76] Update submodule. --- RobustToolbox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RobustToolbox b/RobustToolbox index e93c0f76a9..8fea42ff9a 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit e93c0f76a9ea5021754b6c9b5f1819e0a46b8f4b +Subproject commit 8fea42ff9ac05dfef40f9fec0dfd051ba054dbfa From b92a2ba115b90b47206a29404dea3249fe074b71 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sat, 31 Jul 2021 01:53:40 +0200 Subject: [PATCH 33/76] Remove GraphWalkSolver It's not worth maintaining and if anybody cares the code is in git history. --- Content.Server/Power/Pow3r/GraphWalkSolver.cs | 181 ------------------ Pow3r/Program.Simulation.cs | 2 - 2 files changed, 183 deletions(-) delete mode 100644 Content.Server/Power/Pow3r/GraphWalkSolver.cs diff --git a/Content.Server/Power/Pow3r/GraphWalkSolver.cs b/Content.Server/Power/Pow3r/GraphWalkSolver.cs deleted file mode 100644 index 981aad8120..0000000000 --- a/Content.Server/Power/Pow3r/GraphWalkSolver.cs +++ /dev/null @@ -1,181 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using static Content.Server.Power.Pow3r.PowerState; - -namespace Content.Server.Power.Pow3r -{ - /// - /// Partial implementation of full-graph-walking power solving under pow3r. - /// Concept described at https://hackmd.io/@ss14/lowpower - /// - /// - /// Many features like batteries, cycle detection, join handling, etc... are not implemented at all. - /// Seriously, this implementation barely works. Ah well. - /// is better. - /// - public class GraphWalkSolver : IPowerSolver - { - public void Tick(float frameTime, PowerState state) - { - foreach (var load in state.Loads.Values) - { - load.ReceivingPower = 0; - } - - foreach (var supply in state.Supplies.Values) - { - supply.CurrentSupply = 0; - } - - foreach (var network in state.Networks.Values) - { - // Clear some stuff. - network.LocalDemandMet = 0; - - // Add up demands in network. - network.LocalDemandTotal = network.Loads - .Select(l => state.Loads[l]) - .Where(c => c.Enabled) - .Sum(c => c.DesiredPower); - - // Add up supplies in network. - var availableSupplySum = 0f; - var maxSupplySum = 0f; - foreach (var supplyId in network.Supplies) - { - var supply = state.Supplies[supplyId]; - if (!supply.Enabled) - continue; - - var rampMax = supply.SupplyRampPosition + supply.SupplyRampTolerance; - var effectiveSupply = Math.Min(rampMax, supply.MaxSupply); - supply.EffectiveMaxSupply = effectiveSupply; - availableSupplySum += effectiveSupply; - maxSupplySum += supply.MaxSupply; - } - - network.AvailableSupplyTotal = availableSupplySum; - network.TheoreticalSupplyTotal = maxSupplySum; - } - - // Sort networks by tree height so that suppliers that have less possible loads go FIRST. - // Idea being that a backup generator on a small subnet should do more work - // so that a larger generator that covers more networks can put its power elsewhere. - var sortedByHeight = state.Networks.Values.OrderBy(v => TotalSubLoadCount(state, v)).ToArray(); - - // Go over every network with supply to send power. - foreach (var network in sortedByHeight) - { - // Find all loads recursively, and sum them up. - var subNets = new List(); - var totalDemand = 0f; - GetLoadingNetworksRecursively(state, network, subNets, ref totalDemand); - - if (totalDemand == 0) - continue; - - // Calculate power delivered. - var power = Math.Min(totalDemand, network.AvailableSupplyTotal); - - // Distribute load across supplies in network. - foreach (var supplyId in network.Supplies) - { - var supply = state.Supplies[supplyId]; - if (!supply.Enabled) - continue; - - if (supply.EffectiveMaxSupply != 0) - { - var ratio = supply.EffectiveMaxSupply / network.AvailableSupplyTotal; - - supply.CurrentSupply = ratio * power; - } - else - { - supply.CurrentSupply = 0; - } - - if (supply.MaxSupply != 0) - { - var ratio = supply.MaxSupply / network.TheoreticalSupplyTotal; - - supply.SupplyRampTarget = ratio * totalDemand; - } - else - { - supply.SupplyRampTarget = 0; - } - } - - // Distribute supply across subnet loads. - foreach (var subNet in subNets) - { - var rem = subNet.RemainingDemand; - var ratio = rem / totalDemand; - - subNet.LocalDemandMet += ratio * power; - } - } - - // Distribute power across loads in networks. - foreach (var network in state.Networks.Values) - { - if (network.LocalDemandMet == 0) - continue; - - foreach (var loadId in network.Loads) - { - var load = state.Loads[loadId]; - if (!load.Enabled) - continue; - - var ratio = load.DesiredPower / network.LocalDemandTotal; - load.ReceivingPower = ratio * network.LocalDemandMet; - } - } - - PowerSolverShared.UpdateRampPositions(frameTime, state); - } - - private int TotalSubLoadCount(PowerState state, Network network) - { - // TODO: Cycle detection. - var height = network.Loads.Count; - - foreach (var batteryId in network.BatteriesCharging) - { - var battery = state.Batteries[batteryId]; - if (battery.LinkedNetworkDischarging != default) - { - height += TotalSubLoadCount(state, state.Networks[battery.LinkedNetworkDischarging]); - } - } - - return height; - } - - private void GetLoadingNetworksRecursively( - PowerState state, - Network network, - List networks, - ref float totalDemand) - { - networks.Add(network); - totalDemand += network.LocalDemandTotal - network.LocalDemandMet; - - foreach (var batteryId in network.BatteriesCharging) - { - var battery = state.Batteries[batteryId]; - if (battery.LinkedNetworkDischarging != default) - { - GetLoadingNetworksRecursively( - state, - state.Networks[battery.LinkedNetworkDischarging], - networks, - ref totalDemand); - } - } - } - } -} diff --git a/Pow3r/Program.Simulation.cs b/Pow3r/Program.Simulation.cs index 440e262f83..2c8072e3e8 100644 --- a/Pow3r/Program.Simulation.cs +++ b/Pow3r/Program.Simulation.cs @@ -18,13 +18,11 @@ namespace Pow3r private readonly string[] _solverNames = { - nameof(GraphWalkSolver), nameof(BatteryRampPegSolver), nameof(NoOpSolver) }; private readonly IPowerSolver[] _solvers = { - new GraphWalkSolver(), new BatteryRampPegSolver(), new NoOpSolver() }; From 73e4946e27805b473e70d4949c03b1070e78b4b1 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sat, 31 Jul 2021 02:50:11 +0200 Subject: [PATCH 34/76] Pow3r goes brrr with generational IDs. --- .../Power/EntitySystems/PowerNetSystem.cs | 30 +- Content.Server/Power/Pow3r/PowerState.cs | 309 ++++++++++++++++-- Pow3r/Program.SaveLoad.cs | 11 +- Pow3r/Program.Simulation.cs | 6 - Pow3r/Program.UI.cs | 32 +- 5 files changed, 310 insertions(+), 78 deletions(-) diff --git a/Content.Server/Power/EntitySystems/PowerNetSystem.cs b/Content.Server/Power/EntitySystems/PowerNetSystem.cs index 84b65289b5..d8eee5d111 100644 --- a/Content.Server/Power/EntitySystems/PowerNetSystem.cs +++ b/Content.Server/Power/EntitySystems/PowerNetSystem.cs @@ -19,7 +19,6 @@ namespace Content.Server.Power.EntitySystems private readonly HashSet _powerNetReconnectQueue = new(); private readonly HashSet _apcNetReconnectQueue = new(); - private int _nextId = 1; private readonly BatteryRampPegSolver _solver = new(); public override void Initialize() @@ -50,7 +49,7 @@ namespace Content.Server.Power.EntitySystems private void ApcPowerReceiverShutdown(EntityUid uid, ApcPowerReceiverComponent component, ComponentShutdown args) { - _powerState.Loads.Remove(component.NetworkLoad.Id); + _powerState.Loads.Free(component.NetworkLoad.Id); } private static void ApcPowerReceiverPaused( @@ -68,7 +67,7 @@ namespace Content.Server.Power.EntitySystems private void BatteryShutdown(EntityUid uid, PowerNetworkBatteryComponent component, ComponentShutdown args) { - _powerState.Batteries.Remove(component.NetworkBattery.Id); + _powerState.Batteries.Free(component.NetworkBattery.Id); } private static void BatteryPaused(EntityUid uid, PowerNetworkBatteryComponent component, EntityPausedEvent args) @@ -83,7 +82,7 @@ namespace Content.Server.Power.EntitySystems private void PowerConsumerShutdown(EntityUid uid, PowerConsumerComponent component, ComponentShutdown args) { - _powerState.Loads.Remove(component.NetworkLoad.Id); + _powerState.Loads.Free(component.NetworkLoad.Id); } private static void PowerConsumerPaused(EntityUid uid, PowerConsumerComponent component, EntityPausedEvent args) @@ -98,7 +97,7 @@ namespace Content.Server.Power.EntitySystems private void PowerSupplierShutdown(EntityUid uid, PowerSupplierComponent component, ComponentShutdown args) { - _powerState.Supplies.Remove(component.NetworkSupply.Id); + _powerState.Supplies.Free(component.NetworkSupply.Id); } private static void PowerSupplierPaused(EntityUid uid, PowerSupplierComponent component, EntityPausedEvent args) @@ -113,7 +112,7 @@ namespace Content.Server.Power.EntitySystems public void DestroyPowerNet(PowerNet powerNet) { - _powerState.Networks.Remove(powerNet.NetworkNode.Id); + _powerState.Networks.Free(powerNet.NetworkNode.Id); } public void QueueReconnectPowerNet(PowerNet powerNet) @@ -128,7 +127,7 @@ namespace Content.Server.Power.EntitySystems public void DestroyApcNet(ApcNet apcNet) { - _powerState.Networks.Remove(apcNet.NetworkNode.Id); + _powerState.Networks.Free(apcNet.NetworkNode.Id); } public void QueueReconnectApcNet(ApcNet apcNet) @@ -213,26 +212,22 @@ namespace Content.Server.Power.EntitySystems private void AllocLoad(PowerState.Load load) { - load.Id = AllocId(); - _powerState.Loads.Add(load.Id, load); + _powerState.Loads.Allocate(out load.Id) = load; } private void AllocSupply(PowerState.Supply supply) { - supply.Id = AllocId(); - _powerState.Supplies.Add(supply.Id, supply); + _powerState.Supplies.Allocate(out supply.Id) = supply; } private void AllocBattery(PowerState.Battery battery) { - battery.Id = AllocId(); - _powerState.Batteries.Add(battery.Id, battery); + _powerState.Batteries.Allocate(out battery.Id) = battery; } private void AllocNetwork(PowerState.Network network) { - network.Id = AllocId(); - _powerState.Networks.Add(network.Id, network); + _powerState.Networks.Allocate(out network.Id) = network; } private static void DoReconnectApcNet(ApcNet net) @@ -296,11 +291,6 @@ namespace Content.Server.Power.EntitySystems battery.NetworkBattery.LinkedNetworkDischarging = netNode.Id; } } - - private PowerState.NodeId AllocId() - { - return new(_nextId++); - } } /// diff --git a/Content.Server/Power/Pow3r/PowerState.cs b/Content.Server/Power/Pow3r/PowerState.cs index 276fc785a9..0d220b9259 100644 --- a/Content.Server/Power/Pow3r/PowerState.cs +++ b/Content.Server/Power/Pow3r/PowerState.cs @@ -1,40 +1,51 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Numerics; +using System.Linq; +using System.Runtime.CompilerServices; using System.Text.Json; using System.Text.Json.Serialization; +using Robust.Shared.Utility; using Robust.Shared.ViewVariables; namespace Content.Server.Power.Pow3r { public sealed class PowerState { - public const int MaxTickData = 180; - public static readonly JsonSerializerOptions SerializerOptions = new() { IncludeFields = true, Converters = {new NodeIdJsonConverter()} }; - public Dictionary Supplies = new(); - public Dictionary Networks = new(); - public Dictionary Loads = new(); - public Dictionary Batteries = new(); + public GenIdStorage Supplies = new(); + public GenIdStorage Networks = new(); + public GenIdStorage Loads = new(); + public GenIdStorage Batteries = new(); public readonly struct NodeId : IEquatable { - public readonly int Id; + public readonly int Index; + public readonly int Generation; - public NodeId(int id) + public long Combined => (uint) Index | ((long) Generation << 32); + + public NodeId(int index, int generation) { - Id = id; + Index = index; + Generation = generation; + } + + public NodeId(long combined) + { + Index = (int) combined; + Generation = (int) (combined >> 32); } public bool Equals(NodeId other) { - return Id == other.Id; + return Index == other.Index && Generation == other.Generation; } public override bool Equals(object? obj) @@ -44,7 +55,7 @@ namespace Content.Server.Power.Pow3r public override int GetHashCode() { - return Id; + return HashCode.Combine(Index, Generation); } public static bool operator ==(NodeId left, NodeId right) @@ -59,7 +70,261 @@ namespace Content.Server.Power.Pow3r public override string ToString() { - return Id.ToString(); + return $"{Index} (G{Generation})"; + } + } + + public static class GenIdStorage + { + public static GenIdStorage FromEnumerable(IEnumerable<(NodeId, T)> enumerable) + { + return GenIdStorage.FromEnumerable(enumerable); + } + } + + public sealed class GenIdStorage + { + // This is an implementation of "generational index" storage. + // + // The advantage of this storage method is extremely fast, O(1) lookup (way faster than Dictionary). + // Resolving a value in the storage is a single array load and generation compare. Extremely fast. + // Indices can also be cached into temporary + // Disadvantages are that storage cannot be shrunk, and sparse storage is inefficient space wise. + // Also this implementation does not have optimizations necessary to make sparse iteration efficient. + // + // The idea here is that the index type (NodeId in this case) has both an index and a generation. + // The index is an integer index into the storage array, the generation is used to avoid use-after-free. + // + // Empty slots in the array form a linked list of free slots. + // When we allocate a new slot, we pop one link off this linked list and hand out its index + generation. + // + // When we free a node, we bump the generation of the slot and make it the head of the linked list. + // The generation being bumped means that any IDs to this slot will fail to resolve (generation mismatch). + // + + // Index of the next free slot to use when allocating a new one. + // If this is int.MaxValue, + // it basically means "no slot available" and the next allocation call should resize the array storage. + private int _nextFree = int.MaxValue; + private Slot[] _storage; + + public int Count { get; private set; } + + public ref T this[NodeId id] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + ref var slot = ref _storage[id.Index]; + if (slot.Generation != id.Generation) + ThrowKeyNotFound(); + + return ref slot.Value; + } + } + + public GenIdStorage() + { + _storage = Array.Empty(); + } + + public static GenIdStorage FromEnumerable(IEnumerable<(NodeId, T)> enumerable) + { + var storage = new GenIdStorage(); + + // Cache enumerable to array to do double enumeration. + var cache = enumerable.ToArray(); + + if (cache.Length == 0) + return storage; + + // Figure out max size necessary and set storage size to that. + var maxSize = cache.Max(tup => tup.Item1.Index) + 1; + storage._storage = new Slot[maxSize]; + + // Fill in slots. + foreach (var (id, value) in cache) + { + DebugTools.Assert(id.Generation != 0, "Generation cannot be 0"); + + ref var slot = ref storage._storage[id.Index]; + DebugTools.Assert(slot.Generation == 0, "Duplicate key index!"); + + slot.Generation = id.Generation; + slot.Value = value; + } + + // Go through empty slots and build the free chain. + var nextFree = int.MaxValue; + for (var i = 0; i < storage._storage.Length; i++) + { + ref var slot = ref storage._storage[i]; + + if (slot.Generation != 0) + // Slot in use. + continue; + + slot.NextSlot = nextFree; + nextFree = i; + } + + storage.Count = cache.Length; + storage._nextFree = nextFree; + + return storage; + } + + public ref T Allocate(out NodeId id) + { + if (_nextFree == int.MaxValue) + ReAllocate(); + + var idx = _nextFree; + ref var slot = ref _storage[idx]; + + Count += 1; + _nextFree = slot.NextSlot; + // NextSlot = -1 indicates filled. + slot.NextSlot = -1; + + id = new NodeId(idx, slot.Generation); + return ref slot.Value; + } + + public void Free(NodeId id) + { + var idx = id.Index; + ref var slot = ref _storage[idx]; + if (slot.Generation != id.Generation) + ThrowKeyNotFound(); + + if (RuntimeHelpers.IsReferenceOrContainsReferences()) + slot.Value = default!; + + Count -= 1; + slot.Generation += 1; + slot.NextSlot = _nextFree; + _nextFree = idx; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void ReAllocate() + { + var oldLength = _storage.Length; + var newLength = Math.Max(oldLength, 2) * 2; + + ReAllocateTo(newLength); + } + + private void ReAllocateTo(int newSize) + { + var oldLength = _storage.Length; + DebugTools.Assert(newSize >= oldLength, "Cannot shrink GenIdStorage"); + + Array.Resize(ref _storage, newSize); + + for (var i = oldLength; i < newSize - 1; i++) + { + // Build linked list chain for newly allocated segment. + ref var slot = ref _storage[i]; + slot.NextSlot = i + 1; + // Every slot starts at generation 1. + slot.Generation = 1; + } + + _storage[^1].NextSlot = _nextFree; + + _nextFree = oldLength; + } + + public ValuesCollection Values => new(this); + + private struct Slot + { + // Next link on the free list. if int.MaxValue then this is the tail. + // If negative, this slot is occupied. + public int NextSlot; + // Generation of this slot. + public int Generation; + public T Value; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowKeyNotFound() + { + throw new KeyNotFoundException(); + } + + public readonly struct ValuesCollection : IReadOnlyCollection + { + private readonly GenIdStorage _owner; + + public ValuesCollection(GenIdStorage owner) + { + _owner = owner; + } + + public Enumerator GetEnumerator() + { + return new Enumerator(_owner); + } + + public int Count => _owner.Count; + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public struct Enumerator : IEnumerator + { + // Save the array in the enumerator here to avoid a few pointer dereferences. + private readonly Slot[] _owner; + private int _index; + + public Enumerator(GenIdStorage owner) + { + _owner = owner._storage; + Current = default!; + _index = -1; + } + + public bool MoveNext() + { + while (true) + { + _index += 1; + if (_index >= _owner.Length) + return false; + + ref var slot = ref _owner[_index]; + + if (slot.NextSlot < 0) + { + Current = slot.Value; + return true; + } + } + } + + public void Reset() + { + _index = -1; + } + + object IEnumerator.Current => Current!; + + public T Current { get; private set; } + + public void Dispose() + { + } + } } } @@ -67,12 +332,12 @@ namespace Content.Server.Power.Pow3r { public override NodeId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new(reader.GetInt32()); + return new NodeId(reader.GetInt64()); } public override void Write(Utf8JsonWriter writer, NodeId value, JsonSerializerOptions options) { - writer.WriteNumberValue(value.Id); + writer.WriteNumberValue(value.Combined); } } @@ -186,22 +451,8 @@ namespace Content.Server.Power.Pow3r // "Supplying" means the network is connected to the OUTPUT port of the battery. [ViewVariables] public List BatteriesDischarging = new(); - // Calculation parameters used by GraphWalkSolver. - // Unused by BatteryRampPegSolver. - [JsonIgnore] public float LocalDemandTotal; - [JsonIgnore] public float LocalDemandMet; - [JsonIgnore] public float GroupDemandTotal; - [JsonIgnore] public float GroupDemandMet; - [ViewVariables] [JsonIgnore] public int Height; [JsonIgnore] public bool HeightTouched; - - // Supply available this tick. - [JsonIgnore] public float AvailableSupplyTotal; - - // Max theoretical supply assuming max ramp. - [JsonIgnore] public float TheoreticalSupplyTotal; - public float RemainingDemand => LocalDemandTotal - LocalDemandMet; } } } diff --git a/Pow3r/Program.SaveLoad.cs b/Pow3r/Program.SaveLoad.cs index 2ffdc8eb4e..5fff64f662 100644 --- a/Pow3r/Program.SaveLoad.cs +++ b/Pow3r/Program.SaveLoad.cs @@ -20,15 +20,14 @@ namespace Pow3r return; _paused = dat.Paused; - _nextId = dat.NextId; _currentSolver = dat.Solver; _state = new PowerState { - Networks = dat.Networks.ToDictionary(n => n.Id, n => n), - Supplies = dat.Supplies.ToDictionary(s => s.Id, s => s), - Loads = dat.Loads.ToDictionary(l => l.Id, l => l), - Batteries = dat.Batteries.ToDictionary(b => b.Id, b => b) + Networks = GenIdStorage.FromEnumerable(dat.Networks.Select(n => (n.Id, n))), + Supplies = GenIdStorage.FromEnumerable(dat.Supplies.Select(s => (s.Id, s))), + Loads = GenIdStorage.FromEnumerable(dat.Loads.Select(l => (l.Id, l))), + Batteries = GenIdStorage.FromEnumerable(dat.Batteries.Select(b => (b.Id, b))) }; _displayLoads = dat.Loads.ToDictionary(n => n.Id, _ => new DisplayLoad()); @@ -44,7 +43,6 @@ namespace Pow3r var data = new DiskDat { Paused = _paused, - NextId = _nextId, Solver = _currentSolver, Loads = _state.Loads.Values.ToList(), @@ -59,7 +57,6 @@ namespace Pow3r private sealed class DiskDat { public bool Paused; - public int NextId; public int Solver; public List Loads; diff --git a/Pow3r/Program.Simulation.cs b/Pow3r/Program.Simulation.cs index 2c8072e3e8..fac2dbc415 100644 --- a/Pow3r/Program.Simulation.cs +++ b/Pow3r/Program.Simulation.cs @@ -10,7 +10,6 @@ namespace Pow3r { private const int MaxTickData = 180; - private int _nextId = 1; private PowerState _state = new(); private Network _linking; private int _tickDataIdx; @@ -33,11 +32,6 @@ namespace Pow3r private readonly Queue _remQueue = new(); private readonly Stopwatch _simStopwatch = new Stopwatch(); - private NodeId AllocId() - { - return new(_nextId++); - } - private void Tick(float frameTime) { if (_paused) diff --git a/Pow3r/Program.UI.cs b/Pow3r/Program.UI.cs index 96f7b90cf8..9a99a52699 100644 --- a/Pow3r/Program.UI.cs +++ b/Pow3r/Program.UI.cs @@ -36,30 +36,30 @@ namespace Pow3r if (Button("Generator")) { - var id = AllocId(); - _state.Supplies.Add(id, new Supply { Id = id }); - _displaySupplies.Add(id, new DisplaySupply()); + var supply = new Supply(); + _state.Supplies.Allocate(out supply.Id) = supply; + _displaySupplies.Add(supply.Id, new DisplaySupply()); } if (Button("Load")) { - var id = AllocId(); - _state.Loads.Add(id, new Load { Id = id }); - _displayLoads.Add(id, new DisplayLoad()); + var load = new Load(); + _state.Loads.Allocate(out load.Id) = load; + _displayLoads.Add(load.Id, new DisplayLoad()); } if (Button("Network")) { - var id = AllocId(); - _state.Networks.Add(id, new Network { Id = id }); - _displayNetworks.Add(id, new DisplayNetwork()); + var network = new Network(); + _state.Networks.Allocate(out network.Id) = network; + _displayNetworks.Add(network.Id, new DisplayNetwork()); } if (Button("Battery")) { - var id = AllocId(); - _state.Batteries.Add(id, new Battery { Id = id }); - _displayBatteries.Add(id, new DisplayBattery()); + var battery = new Battery(); + _state.Batteries.Allocate(out battery.Id) = battery; + _displayBatteries.Add(battery.Id, new DisplayBattery()); } Checkbox("Paused", ref _paused); @@ -355,25 +355,25 @@ namespace Pow3r switch (item) { case Network n: - _state.Networks.Remove(n.Id); + _state.Networks.Free(n.Id); _displayNetworks.Remove(n.Id); reLink = true; break; case Supply s: - _state.Supplies.Remove(s.Id); + _state.Supplies.Free(s.Id); _state.Networks.Values.ForEach(n => n.Supplies.Remove(s.Id)); _displaySupplies.Remove(s.Id); break; case Load l: - _state.Loads.Remove(l.Id); + _state.Loads.Free(l.Id); _state.Networks.Values.ForEach(n => n.Loads.Remove(l.Id)); _displayLoads.Remove(l.Id); break; case Battery b: - _state.Batteries.Remove(b.Id); + _state.Batteries.Free(b.Id); _state.Networks.Values.ForEach(n => n.BatteriesCharging.Remove(b.Id)); _state.Networks.Values.ForEach(n => n.BatteriesDischarging.Remove(b.Id)); _displayBatteries.Remove(b.Id); From 632e72b817c110dcda6fa008ff55d26136753be9 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sat, 31 Jul 2021 03:14:00 +0200 Subject: [PATCH 35/76] Improve hands & pulling (#4389) --- .../Actions/ClientActionsComponent.cs | 38 +--- .../Hands/HandVirtualPullItemStatus.xaml | 3 + .../Hands/HandVirtualPullItemStatus.xaml.cs | 13 ++ Content.Client/Hands/HandsComponent.cs | 127 +----------- Content.Client/Hands/HandsGui.xaml | 6 + .../Hands/{HandsGui.cs => HandsGui.xaml.cs} | 142 ++++++------- Content.Client/Hands/HandsSystem.cs | 80 -------- .../Hands/Systems/HandVirtualPullSystem.cs | 18 ++ Content.Client/Hands/Systems/HandsSystem.cs | 149 ++++++++++++++ Content.Client/Items/ItemStatusMessages.cs | 32 +++ .../Items/Managers/IItemSlotManager.cs | 19 +- .../Items/Managers/ItemSlotManager.cs | 56 +++++- Content.Client/Items/UI/ItemSlotButton.cs | 35 +++- Content.Client/Items/UI/ItemStatusPanel.cs | 40 +++- .../Fluids/Components/SpillableComponent.cs | 1 + .../Hands/Components/HandsComponent.cs | 97 ++------- .../Hands/Systems/HandVirtualPullSystem.cs | 57 ++++++ .../Hands/{ => Systems}/HandsSystem.cs | 136 ++++++++++++- .../Interaction/InteractionSystem.cs | 23 ++- .../Components/HandVirtualPullComponent.cs | 50 +++++ .../Hands/Components/SharedHandsComponent.cs | 186 +++++++----------- Content.Shared/Hands/SharedHandsSystem.cs | 48 ++--- Content.Shared/Interaction/BeforeInteract.cs | 53 +++++ .../{DragDrop => Interaction}/IDropped.cs | 2 +- .../Components/SharedPullableComponent.cs | 40 +--- .../Components/SharedPullerComponent.cs | 29 +-- .../Events}/PullAttemptMessage.cs | 0 .../Pull => Pulling/Events}/PullMessage.cs | 2 +- .../Events}/PullStartedMessage.cs | 0 .../Events}/PullStoppedMessage.cs | 0 .../Pulling/Systems/SharedPullerSystem.cs | 44 +++++ .../{ => Systems}/SharedPullingSystem.cs | 23 +++ .../Entities/Virtual/virtual_pull_item.yml | 8 + 33 files changed, 945 insertions(+), 612 deletions(-) create mode 100644 Content.Client/Hands/HandVirtualPullItemStatus.xaml create mode 100644 Content.Client/Hands/HandVirtualPullItemStatus.xaml.cs create mode 100644 Content.Client/Hands/HandsGui.xaml rename Content.Client/Hands/{HandsGui.cs => HandsGui.xaml.cs} (68%) delete mode 100644 Content.Client/Hands/HandsSystem.cs create mode 100644 Content.Client/Hands/Systems/HandVirtualPullSystem.cs create mode 100644 Content.Client/Hands/Systems/HandsSystem.cs create mode 100644 Content.Client/Items/ItemStatusMessages.cs create mode 100644 Content.Server/Hands/Systems/HandVirtualPullSystem.cs rename Content.Server/Hands/{ => Systems}/HandsSystem.cs (58%) create mode 100644 Content.Shared/Hands/Components/HandVirtualPullComponent.cs create mode 100644 Content.Shared/Interaction/BeforeInteract.cs rename Content.Shared/{DragDrop => Interaction}/IDropped.cs (97%) rename Content.Shared/{Physics/Pull => Pulling/Events}/PullAttemptMessage.cs (100%) rename Content.Shared/{Physics/Pull => Pulling/Events}/PullMessage.cs (87%) rename Content.Shared/{Physics/Pull => Pulling/Events}/PullStartedMessage.cs (100%) rename Content.Shared/{Physics/Pull => Pulling/Events}/PullStoppedMessage.cs (100%) create mode 100644 Content.Shared/Pulling/Systems/SharedPullerSystem.cs rename Content.Shared/Pulling/{ => Systems}/SharedPullingSystem.cs (88%) create mode 100644 Resources/Prototypes/Entities/Virtual/virtual_pull_item.yml diff --git a/Content.Client/Actions/ClientActionsComponent.cs b/Content.Client/Actions/ClientActionsComponent.cs index 6df12b2581..3caab80cc6 100644 --- a/Content.Client/Actions/ClientActionsComponent.cs +++ b/Content.Client/Actions/ClientActionsComponent.cs @@ -1,9 +1,8 @@ -using System.Collections.Generic; using Content.Client.Actions.Assignments; using Content.Client.Actions.UI; using Content.Client.Hands; using Content.Client.Inventory; -using Content.Client.Items.UI; +using Content.Client.Items.Managers; using Content.Shared.Actions.Components; using Content.Shared.Actions.Prototypes; using Robust.Client.GameObjects; @@ -26,12 +25,13 @@ namespace Content.Client.Actions public const byte Slots = 10; [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IItemSlotManager _itemSlotManager = default!; [ComponentDependency] private readonly HandsComponent? _handsComponent = null; [ComponentDependency] private readonly ClientInventoryComponent? _inventoryComponent = null; private ActionsUI? _ui; - private readonly List _highlightingItemSlots = new(); + private EntityUid _highlightedEntity; /// /// Current assignments for all hotbars / slots for this entity. @@ -225,26 +225,8 @@ namespace Content.Client.Actions { StopHighlightingItemSlots(); - // figure out if it's in hand or inventory and highlight it - foreach (var hand in _handsComponent!.Gui!.Hands) - { - if (hand.HeldItem != item || hand.HandButton == null) continue; - _highlightingItemSlots.Add(hand.HandButton); - hand.HandButton.Highlight(true); - return; - } - - foreach (var (slot, slotItem) in _inventoryComponent!.AllSlots) - { - if (slotItem != item) continue; - foreach (var itemSlotButton in - _inventoryComponent.InterfaceController.GetItemSlotButtons(slot)) - { - _highlightingItemSlots.Add(itemSlotButton); - itemSlotButton.Highlight(true); - } - return; - } + _highlightedEntity = item.Uid; + _itemSlotManager.HighlightEntity(item.Uid); } /// @@ -252,11 +234,11 @@ namespace Content.Client.Actions /// public void StopHighlightingItemSlots() { - foreach (var itemSlot in _highlightingItemSlots) - { - itemSlot.Highlight(false); - } - _highlightingItemSlots.Clear(); + if (_highlightedEntity == default) + return; + + _itemSlotManager.UnHighlightEntity(_highlightedEntity); + _highlightedEntity = default; } public void ToggleActionsMenu() diff --git a/Content.Client/Hands/HandVirtualPullItemStatus.xaml b/Content.Client/Hands/HandVirtualPullItemStatus.xaml new file mode 100644 index 0000000000..9fac1e0d08 --- /dev/null +++ b/Content.Client/Hands/HandVirtualPullItemStatus.xaml @@ -0,0 +1,3 @@ + + diff --git a/Content.Client/Hands/HandVirtualPullItemStatus.xaml.cs b/Content.Client/Hands/HandVirtualPullItemStatus.xaml.cs new file mode 100644 index 0000000000..f4324f6e41 --- /dev/null +++ b/Content.Client/Hands/HandVirtualPullItemStatus.xaml.cs @@ -0,0 +1,13 @@ +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.XAML; + +namespace Content.Client.Hands +{ + public sealed class HandVirtualPullItemStatus : Control + { + public HandVirtualPullItemStatus() + { + RobustXamlLoader.Load(this); + } + } +} diff --git a/Content.Client/Hands/HandsComponent.cs b/Content.Client/Hands/HandsComponent.cs index 0299384e33..4276ca1e9c 100644 --- a/Content.Client/Hands/HandsComponent.cs +++ b/Content.Client/Hands/HandsComponent.cs @@ -1,15 +1,8 @@ using System.Collections.Generic; -using Content.Client.Animations; -using Content.Client.HUD; using Content.Shared.Hands.Components; using Content.Shared.Item; using Robust.Shared.Containers; using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Network; -using Robust.Shared.Players; -using Robust.Shared.Timing; -using Robust.Shared.ViewVariables; namespace Content.Client.Hands { @@ -18,16 +11,7 @@ namespace Content.Client.Hands [ComponentReference(typeof(SharedHandsComponent))] public class HandsComponent : SharedHandsComponent { - [Dependency] private readonly IGameHud _gameHud = default!; - - [ViewVariables] - public HandsGui? Gui { get; private set; } - - protected override void OnRemove() - { - ClearGui(); - base.OnRemove(); - } + public HandsGui? Gui { get; set; } public override void HandleComponentState(ComponentState? curState, ComponentState? nextState) { @@ -38,94 +22,23 @@ namespace Content.Client.Hands foreach (var handState in state.Hands) { - var newHand = new Hand(handState.Name, handState.Enabled, handState.Location); + var newHand = new Hand(handState.Name, handState.Location); Hands.Add(newHand); } + ActiveHand = state.ActiveHand; UpdateHandContainers(); UpdateHandVisualizer(); - UpdateHandsGuiState(); - } - - public void SettupGui() - { - if (Gui == null) - { - Gui = new HandsGui(); - _gameHud.HandsContainer.AddChild(Gui); - Gui.HandClick += args => OnHandClick(args.HandClicked); - Gui.HandActivate += args => OnActivateInHand(args.HandUsed); - UpdateHandsGuiState(); - } - } - - public void ClearGui() - { - Gui?.Dispose(); - Gui = null; - } - - public override void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel, ICommonSession? session = null) - { - base.HandleNetworkMessage(message, netChannel, session); - - switch (message) - { - case PickupAnimationMessage msg: - RunPickupAnimation(msg); - break; - } + Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new HandsModifiedMessage { Hands = this }); } public override void HandsModified() { - base.HandsModified(); - UpdateHandContainers(); UpdateHandVisualizer(); - UpdateHandsGuiState(); - } - private void OnHandClick(string handClicked) - { - if (!TryGetHand(handClicked, out var pressedHand)) - return; - - if (!TryGetActiveHand(out var activeHand)) - return; - - var pressedEntity = pressedHand.HeldEntity; - var activeEntity = activeHand.HeldEntity; - - if (pressedHand == activeHand && activeEntity != null) - { - SendNetworkMessage(new UseInHandMsg()); //use item in hand - return; - } - - if (pressedHand != activeHand && pressedEntity == null) - { - SendNetworkMessage(new ClientChangedHandMsg(pressedHand.Name)); //swap hand - return; - } - - if (pressedHand != activeHand && pressedEntity != null && activeEntity != null) - { - SendNetworkMessage(new ClientAttackByInHandMsg(pressedHand.Name)); //use active item on held item - return; - } - - if (pressedHand != activeHand && pressedEntity != null && activeEntity == null) - { - SendNetworkMessage(new MoveItemFromHandMsg(pressedHand.Name)); //move item in hand to active hand - return; - } - } - - private void OnActivateInHand(string handActivated) - { - SendNetworkMessage(new ActivateInHandMsg(handActivated)); + base.HandsModified(); } public void UpdateHandContainers() @@ -149,27 +62,10 @@ namespace Content.Client.Hands appearance.SetData(HandsVisuals.VisualState, GetHandsVisualState()); } - public void UpdateHandsGuiState() - { - Gui?.SetState(GetHandsGuiState()); - } - - private HandsGuiState GetHandsGuiState() - { - var handStates = new List(); - - foreach (var hand in ReadOnlyHands) - { - var handState = new GuiHand(hand.Name, hand.Location, hand.HeldEntity, hand.Enabled); - handStates.Add(handState); - } - return new HandsGuiState(handStates, ActiveHand); - } - private HandsVisualState GetHandsVisualState() { var hands = new List(); - foreach (var hand in ReadOnlyHands) + foreach (var hand in Hands) { if (hand.HeldEntity == null) continue; @@ -182,16 +78,5 @@ namespace Content.Client.Hands } return new(hands); } - - private void RunPickupAnimation(PickupAnimationMessage msg) - { - if (!Owner.EntityManager.TryGetEntity(msg.EntityUid, out var entity)) - return; - - if (!IoCManager.Resolve().IsFirstTimePredicted) - return; - - ReusableAnimations.AnimateEntityPickup(entity, msg.InitialPosition, msg.PickupDirection); - } } } diff --git a/Content.Client/Hands/HandsGui.xaml b/Content.Client/Hands/HandsGui.xaml new file mode 100644 index 0000000000..d39c044821 --- /dev/null +++ b/Content.Client/Hands/HandsGui.xaml @@ -0,0 +1,6 @@ + + + + + + diff --git a/Content.Client/Hands/HandsGui.cs b/Content.Client/Hands/HandsGui.xaml.cs similarity index 68% rename from Content.Client/Hands/HandsGui.cs rename to Content.Client/Hands/HandsGui.xaml.cs index ff0fa47b23..63590882bf 100644 --- a/Content.Client/Hands/HandsGui.cs +++ b/Content.Client/Hands/HandsGui.xaml.cs @@ -1,87 +1,84 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; using Content.Client.HUD; using Content.Client.Items.Managers; using Content.Client.Items.UI; using Content.Client.Resources; -using Content.Shared; using Content.Shared.CCVar; using Content.Shared.Hands.Components; -using Content.Shared.Input; +using Robust.Client.AutoGenerated; using Robust.Client.Graphics; -using Robust.Client.Player; using Robust.Client.ResourceManagement; using Robust.Client.UserInterface; -using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; using Robust.Shared.Configuration; using Robust.Shared.GameObjects; using Robust.Shared.Input; using Robust.Shared.IoC; using Robust.Shared.Timing; using Robust.Shared.ViewVariables; -using static Robust.Client.UserInterface.Controls.BoxContainer; namespace Content.Client.Hands { - public class HandsGui : Control + [GenerateTypedNameReferences] + public sealed partial class HandsGui : Control { [Dependency] private readonly IResourceCache _resourceCache = default!; [Dependency] private readonly IItemSlotManager _itemSlotManager = default!; [Dependency] private readonly IGameHud _gameHud = default!; [Dependency] private readonly INetConfigurationManager _configManager = default!; + private readonly HandsSystem _handsSystem; + private readonly HandsComponent _handsComponent; + private Texture StorageTexture => _gameHud.GetHudTexture("back.png"); private Texture BlockedTexture => _resourceCache.GetTexture("/Textures/Interface/Inventory/blocked.png"); private ItemStatusPanel StatusPanel { get; } - private BoxContainer HandsContainer { get; } - - [ViewVariables] - public IReadOnlyList Hands => _hands; - private List _hands = new(); + [ViewVariables] private GuiHand[] _hands = Array.Empty(); private string? ActiveHand { get; set; } - public Action? HandClick; //TODO: Move to Eventbus - - public Action? HandActivate; //TODO: Move to Eventbus - - public HandsGui() + public HandsGui(HandsComponent hands, HandsSystem handsSystem) { - IoCManager.InjectDependencies(this); - _configManager.OnValueChanged(CCVars.HudTheme, UpdateHudTheme); + _handsComponent = hands; + _handsSystem = handsSystem; - AddChild(new BoxContainer - { - Orientation = LayoutOrientation.Horizontal, - SeparationOverride = 0, - HorizontalAlignment = HAlignment.Center, - Children = - { - new BoxContainer - { - Orientation = LayoutOrientation.Vertical, - Children = - { - (StatusPanel = ItemStatusPanel.FromSide(HandLocation.Middle)), - (HandsContainer = new BoxContainer - { - Orientation = LayoutOrientation.Horizontal, - HorizontalAlignment = HAlignment.Center - }), - } - }, - } - }); + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + + StatusPanel = ItemStatusPanel.FromSide(HandLocation.Middle); + StatusContainer.AddChild(StatusPanel); + StatusPanel.SetPositionFirst(); } - public void SetState(HandsGuiState state) + protected override void EnteredTree() { + base.EnteredTree(); + + _handsSystem.GuiStateUpdated += HandsSystemOnGuiStateUpdated; + _configManager.OnValueChanged(CCVars.HudTheme, UpdateHudTheme); + + HandsSystemOnGuiStateUpdated(); + } + + protected override void ExitedTree() + { + base.ExitedTree(); + + _handsSystem.GuiStateUpdated -= HandsSystemOnGuiStateUpdated; + _configManager.UnsubValueChanged(CCVars.HudTheme, UpdateHudTheme); + } + + private void HandsSystemOnGuiStateUpdated() + { + var state = _handsSystem.GetGuiState(); + ActiveHand = state.ActiveHand; _hands = state.GuiHands; + Array.Sort(_hands, HandOrderComparer.Instance); UpdateGui(); } @@ -97,12 +94,15 @@ namespace Content.Client.Hands var handName = hand.Name; newButton.OnPressed += args => OnHandPressed(args, handName); - newButton.OnStoragePressed += args => OnStoragePressed(handName); - - newButton.Blocked.Visible = !hand.Enabled; + newButton.OnStoragePressed += _ => OnStoragePressed(handName); _itemSlotManager.SetItemSlot(newButton, hand.HeldItem); + + // Show blocked overlay if hand is pulling. + newButton.Blocked.Visible = + hand.HeldItem != null && hand.HeldItem.HasComponent(); } + if (TryGetActiveHand(out var activeHand)) { activeHand.HandButton.SetActiveHand(true); @@ -114,7 +114,7 @@ namespace Content.Client.Hands { if (args.Function == EngineKeyFunctions.UIClick) { - HandClick?.Invoke(new HandClickEventArgs(handName)); + _handsSystem.UIHandClick(_handsComponent, handName); } else if (TryGetHand(handName, out var hand)) { @@ -124,7 +124,7 @@ namespace Content.Client.Hands private void OnStoragePressed(string handName) { - HandActivate?.Invoke(new HandActivateEventArgs(handName)); + _handsSystem.UIHandActivate(handName); } private bool TryGetActiveHand([NotNullWhen(true)] out GuiHand? activeHand) @@ -145,6 +145,7 @@ namespace Content.Client.Hands if (hand.Name == handName) foundHand = hand; } + return foundHand != null; } @@ -153,7 +154,9 @@ namespace Content.Client.Hands base.FrameUpdate(args); foreach (var hand in _hands) + { _itemSlotManager.UpdateCooldown(hand.HandButton, hand.HeldItem); + } } private HandButton MakeHandButton(HandLocation buttonLocation) @@ -173,23 +176,31 @@ namespace Content.Client.Hands UpdateGui(); } - public class HandClickEventArgs + private sealed class HandOrderComparer : IComparer { - public string HandClicked { get; } + public static readonly HandOrderComparer Instance = new(); - public HandClickEventArgs(string handClicked) + public int Compare(GuiHand? x, GuiHand? y) { - HandClicked = handClicked; - } - } + if (ReferenceEquals(x, y)) return 0; + if (ReferenceEquals(null, y)) return 1; + if (ReferenceEquals(null, x)) return -1; - public class HandActivateEventArgs - { - public string HandUsed { get; } + var orderX = Map(x.HandLocation); + var orderY = Map(y.HandLocation); - public HandActivateEventArgs(string handUsed) - { - HandUsed = handUsed; + return orderX.CompareTo(orderY); + + static int Map(HandLocation loc) + { + return loc switch + { + HandLocation.Left => 3, + HandLocation.Middle => 2, + HandLocation.Right => 1, + _ => throw new ArgumentOutOfRangeException(nameof(loc), loc, null) + }; + } } } } @@ -203,7 +214,7 @@ namespace Content.Client.Hands /// The set of hands to be displayed. /// [ViewVariables] - public List GuiHands { get; } = new(); + public GuiHand[] GuiHands { get; } /// /// The name of the currently active hand. @@ -211,7 +222,7 @@ namespace Content.Client.Hands [ViewVariables] public string? ActiveHand { get; } - public HandsGuiState(List guiHands, string? activeHand = null) + public HandsGuiState(GuiHand[] guiHands, string? activeHand = null) { GuiHands = guiHands; ActiveHand = activeHand; @@ -247,18 +258,11 @@ namespace Content.Client.Hands [ViewVariables] public HandButton HandButton { get; set; } = default!; - /// - /// If this hand can be used by the player. - /// - [ViewVariables] - public bool Enabled { get; } - - public GuiHand(string name, HandLocation handLocation, IEntity? heldItem, bool enabled) + public GuiHand(string name, HandLocation handLocation, IEntity? heldItem) { Name = name; HandLocation = handLocation; HeldItem = heldItem; - Enabled = enabled; } } } diff --git a/Content.Client/Hands/HandsSystem.cs b/Content.Client/Hands/HandsSystem.cs deleted file mode 100644 index 4ba3a73c6f..0000000000 --- a/Content.Client/Hands/HandsSystem.cs +++ /dev/null @@ -1,80 +0,0 @@ -using Content.Shared.Hands; -using Content.Shared.Hands.Components; -using Content.Shared.Input; -using Robust.Client.GameObjects; -using Robust.Shared.Containers; -using Robust.Shared.GameObjects; -using Robust.Shared.Input.Binding; -using Robust.Shared.Map; -using Robust.Shared.Players; - -namespace Content.Client.Hands -{ - internal sealed class HandsSystem : SharedHandsSystem - { - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent((_, component, _) => component.SettupGui()); - SubscribeLocalEvent((_, component, _) => component.ClearGui()); - - CommandBinds.Builder - .Bind(ContentKeyFunctions.SwapHands, InputCmdHandler.FromDelegate(SwapHandsPressed)) - .Bind(ContentKeyFunctions.Drop, new PointerInputCmdHandler(DropPressed)) - .Register(); - } - - public override void Shutdown() - { - CommandBinds.Unregister(); - base.Shutdown(); - } - - private void SwapHandsPressed(ICommonSession? session) - { - if (session == null) - return; - - var player = session.AttachedEntity; - - if (player == null) - return; - - if (!player.TryGetComponent(out SharedHandsComponent? hands)) - return; - - if (!hands.TryGetSwapHandsResult(out var nextHand)) - return; - - EntityManager.RaisePredictiveEvent(new RequestSetHandEvent(nextHand)); - } - - private bool DropPressed(ICommonSession? session, EntityCoordinates coords, EntityUid uid) - { - if (session == null) - return false; - - var player = session.AttachedEntity; - - if (player == null) - return false; - - if (!player.TryGetComponent(out SharedHandsComponent? hands)) - return false; - - var activeHand = hands.ActiveHand; - - if (activeHand == null) - return false; - - EntityManager.RaisePredictiveEvent(new RequestDropHeldEntityEvent(activeHand, coords)); - return true; - } - - protected override void HandleContainerModified(EntityUid uid, SharedHandsComponent component, ContainerModifiedMessage args) - { - component.HandsModified(); - } - } -} diff --git a/Content.Client/Hands/Systems/HandVirtualPullSystem.cs b/Content.Client/Hands/Systems/HandVirtualPullSystem.cs new file mode 100644 index 0000000000..0f47efd9dc --- /dev/null +++ b/Content.Client/Hands/Systems/HandVirtualPullSystem.cs @@ -0,0 +1,18 @@ +using Content.Client.Items; +using Content.Shared.Hands.Components; +using JetBrains.Annotations; +using Robust.Shared.GameObjects; + +namespace Content.Client.Hands +{ + [UsedImplicitly] + public sealed class HandVirtualPullSystem : EntitySystem + { + public override void Initialize() + { + base.Initialize(); + + Subs.ItemStatus(_ => new HandVirtualPullItemStatus()); + } + } +} diff --git a/Content.Client/Hands/Systems/HandsSystem.cs b/Content.Client/Hands/Systems/HandsSystem.cs new file mode 100644 index 0000000000..5f2e40f2e5 --- /dev/null +++ b/Content.Client/Hands/Systems/HandsSystem.cs @@ -0,0 +1,149 @@ +using System; +using System.Linq; +using Content.Client.Animations; +using Content.Client.HUD; +using Content.Shared.Hands; +using Content.Shared.Hands.Components; +using JetBrains.Annotations; +using Robust.Client.GameObjects; +using Robust.Client.Player; +using Robust.Shared.Containers; +using Robust.Shared.GameObjects; +using Robust.Shared.Input.Binding; +using Robust.Shared.IoC; +using Robust.Shared.Timing; + +namespace Content.Client.Hands +{ + [UsedImplicitly] + public sealed class HandsSystem : SharedHandsSystem + { + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly IGameHud _gameHud = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + + public event Action? GuiStateUpdated; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(HandlePlayerAttached); + SubscribeLocalEvent(HandlePlayerDetached); + SubscribeLocalEvent(HandleCompRemove); + SubscribeLocalEvent(HandleHandsModified); + + SubscribeNetworkEvent(HandlePickupAnimation); + } + + public override void Shutdown() + { + CommandBinds.Unregister(); + base.Shutdown(); + } + + private void HandleHandsModified(HandsModifiedMessage ev) + { + if (ev.Hands.Owner == _playerManager.LocalPlayer?.ControlledEntity) + GuiStateUpdated?.Invoke(); + } + + protected override void HandleContainerModified(EntityUid uid, SharedHandsComponent component, ContainerModifiedMessage args) + { + if (uid == _playerManager.LocalPlayer?.ControlledEntity?.Uid) + GuiStateUpdated?.Invoke(); + } + + private void HandlePickupAnimation(PickupAnimationMessage msg) + { + if (!EntityManager.TryGetEntity(msg.EntityUid, out var entity)) + return; + + if (!_gameTiming.IsFirstTimePredicted) + return; + + ReusableAnimations.AnimateEntityPickup(entity, msg.InitialPosition, msg.PickupDirection); + } + + public HandsGuiState GetGuiState() + { + var player = _playerManager.LocalPlayer?.ControlledEntity; + + if (player == null || !player.TryGetComponent(out HandsComponent? hands)) + return new HandsGuiState(Array.Empty()); + + var states = hands.Hands + .Select(hand => new GuiHand(hand.Name, hand.Location, hand.HeldEntity)) + .ToArray(); + + return new HandsGuiState(states, hands.ActiveHand); + } + + public void UIHandClick(HandsComponent hands, string handName) + { + if (!hands.TryGetHand(handName, out var pressedHand)) + return; + + if (!hands.TryGetActiveHand(out var activeHand)) + return; + + var pressedEntity = pressedHand.HeldEntity; + var activeEntity = activeHand.HeldEntity; + + if (pressedHand == activeHand && activeEntity != null) + { + // use item in hand + // it will always be attack_self() in my heart. + RaiseNetworkEvent(new UseInHandMsg()); + return; + } + + if (pressedHand != activeHand && pressedEntity == null) + { + // change active hand + RaiseNetworkEvent(new RequestSetHandEvent(handName)); + return; + } + + if (pressedHand != activeHand && pressedEntity != null && activeEntity != null) + { + // use active item on held item + RaiseNetworkEvent(new ClientInteractUsingInHandMsg(pressedHand.Name)); + return; + } + + if (pressedHand != activeHand && pressedEntity != null && activeEntity == null) + { + // use active item on held item + RaiseNetworkEvent(new MoveItemFromHandMsg(pressedHand.Name)); + } + } + + public void UIHandActivate(string handName) + { + RaiseNetworkEvent (new ActivateInHandMsg(handName)); + } + + private void HandlePlayerAttached(EntityUid uid, HandsComponent component, PlayerAttachedEvent args) + { + component.Gui = new HandsGui(component, this); + _gameHud.HandsContainer.AddChild(component.Gui); + } + + private static void HandlePlayerDetached(EntityUid uid, HandsComponent component, PlayerDetachedEvent args) + { + ClearGui(component); + } + + private static void HandleCompRemove(EntityUid uid, HandsComponent component, ComponentRemove args) + { + ClearGui(component); + } + + private static void ClearGui(HandsComponent comp) + { + comp.Gui?.Orphan(); + comp.Gui = null; + } + } +} diff --git a/Content.Client/Items/ItemStatusMessages.cs b/Content.Client/Items/ItemStatusMessages.cs new file mode 100644 index 0000000000..93ebaeabe0 --- /dev/null +++ b/Content.Client/Items/ItemStatusMessages.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using Robust.Client.UserInterface; +using Robust.Shared.GameObjects; + +namespace Content.Client.Items +{ + public sealed class ItemStatusCollectMessage : EntityEventArgs + { + public List Controls = new(); + } + + public static class ItemStatusRegisterExt + { + /// + /// Register an item status control for a component. + /// + /// The handle from within entity system initialize. + /// A delegate to create the actual control. + /// The type of component for which this control should be made. + public static void ItemStatus( + this EntitySystem.Subscriptions subs, + Func createControl) + where TComp : IComponent + { + subs.SubscribeLocalEvent((uid, _, args) => + { + args.Controls.Add(createControl(uid)); + }); + } + } +} diff --git a/Content.Client/Items/Managers/IItemSlotManager.cs b/Content.Client/Items/Managers/IItemSlotManager.cs index 127eedc1b4..bbebaf55b4 100644 --- a/Content.Client/Items/Managers/IItemSlotManager.cs +++ b/Content.Client/Items/Managers/IItemSlotManager.cs @@ -1,4 +1,5 @@ -using Content.Client.Items.UI; +using System; +using Content.Client.Items.UI; using Robust.Client.UserInterface; using Robust.Shared.GameObjects; @@ -10,5 +11,21 @@ namespace Content.Client.Items.Managers void UpdateCooldown(ItemSlotButton? cooldownTexture, IEntity? entity); bool SetItemSlot(ItemSlotButton button, IEntity? entity); void HoverInSlot(ItemSlotButton button, IEntity? entity, bool fits); + event Action? EntityHighlightedUpdated; + bool IsHighlighted(EntityUid uid); + + /// + /// Highlight all slot controls that contain the specified entity. + /// + /// The UID of the entity to highlight. + /// + void HighlightEntity(EntityUid uid); + + /// + /// Remove highlighting for the specified entity. + /// + /// The UID of the entity to unhighlight. + /// + void UnHighlightEntity(EntityUid uid); } } diff --git a/Content.Client/Items/Managers/ItemSlotManager.cs b/Content.Client/Items/Managers/ItemSlotManager.cs index 5fd3f0fbe0..3435913914 100644 --- a/Content.Client/Items/Managers/ItemSlotManager.cs +++ b/Content.Client/Items/Managers/ItemSlotManager.cs @@ -1,8 +1,11 @@ +using System; +using System.Collections.Generic; using Content.Client.Examine; using Content.Client.Items.UI; using Content.Client.Storage; using Content.Client.Verbs; using Content.Shared.Cooldown; +using Content.Shared.Hands.Components; using Content.Shared.Input; using Robust.Client.GameObjects; using Robust.Client.Graphics; @@ -28,6 +31,11 @@ namespace Content.Client.Items.Managers [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IEyeManager _eyeManager = default!; [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly IComponentManager _componentManager = default!; + + private readonly HashSet _highlightEntities = new(); + + public event Action? EntityHighlightedUpdated; public bool SetItemSlot(ItemSlotButton button, IEntity? entity) { @@ -38,13 +46,26 @@ namespace Content.Client.Items.Managers } else { - if (!entity.TryGetComponent(out ISpriteComponent? sprite)) + ISpriteComponent? sprite; + if (entity.TryGetComponent(out HandVirtualPullComponent? virtPull) + && _componentManager.TryGetComponent(virtPull.PulledEntity, out ISpriteComponent pulledSprite)) + { + sprite = pulledSprite; + } + else if (!entity.TryGetComponent(out sprite)) + { return false; + } button.ClearHover(); button.SpriteView.Sprite = sprite; button.StorageButton.Visible = entity.HasComponent(); } + + button.Entity = entity?.Uid ?? default; + + // im lazy + button.UpdateSlotHighlighted(); return true; } @@ -145,5 +166,38 @@ namespace Content.Client.Items.Managers button.HoverSpriteView.Sprite = hoverSprite; } + + public bool IsHighlighted(EntityUid uid) + { + return _highlightEntities.Contains(uid); + } + + public void HighlightEntity(EntityUid uid) + { + if (!_highlightEntities.Add(uid)) + return; + + EntityHighlightedUpdated?.Invoke(new EntitySlotHighlightedEventArgs(uid, true)); + } + + public void UnHighlightEntity(EntityUid uid) + { + if (!_highlightEntities.Remove(uid)) + return; + + EntityHighlightedUpdated?.Invoke(new EntitySlotHighlightedEventArgs(uid, false)); + } + } + + public readonly struct EntitySlotHighlightedEventArgs + { + public EntitySlotHighlightedEventArgs(EntityUid entity, bool newHighlighted) + { + Entity = entity; + NewHighlighted = newHighlighted; + } + + public EntityUid Entity { get; } + public bool NewHighlighted { get; } } } diff --git a/Content.Client/Items/UI/ItemSlotButton.cs b/Content.Client/Items/UI/ItemSlotButton.cs index 8ffbdc5cf8..201fa28f5c 100644 --- a/Content.Client/Items/UI/ItemSlotButton.cs +++ b/Content.Client/Items/UI/ItemSlotButton.cs @@ -1,18 +1,24 @@ using System; using Content.Client.Cooldown; +using Content.Client.Items.Managers; using Content.Client.Stylesheets; using Robust.Client.Graphics; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; +using Robust.Shared.GameObjects; using Robust.Shared.Input; +using Robust.Shared.IoC; using Robust.Shared.Maths; namespace Content.Client.Items.UI { - public class ItemSlotButton : Control + public class ItemSlotButton : Control, IEntityEventSubscriber { private const string HighlightShader = "SelectionOutlineInrange"; + [Dependency] private readonly IItemSlotManager _itemSlotManager = default!; + + public EntityUid Entity { get; set; } public TextureRect Button { get; } public SpriteView SpriteView { get; } public SpriteView HoverSpriteView { get; } @@ -32,6 +38,8 @@ namespace Content.Client.Items.UI public ItemSlotButton(Texture texture, Texture storageTexture, string textureName) { + IoCManager.InjectDependencies(this); + MinSize = (64, 64); TextureName = textureName; @@ -101,6 +109,31 @@ namespace Content.Client.Items.UI }); } + protected override void EnteredTree() + { + base.EnteredTree(); + + _itemSlotManager.EntityHighlightedUpdated += HandleEntitySlotHighlighted; + UpdateSlotHighlighted(); + } + + protected override void ExitedTree() + { + base.ExitedTree(); + + _itemSlotManager.EntityHighlightedUpdated -= HandleEntitySlotHighlighted; + } + + private void HandleEntitySlotHighlighted(EntitySlotHighlightedEventArgs entitySlotHighlightedEventArgs) + { + UpdateSlotHighlighted(); + } + + public void UpdateSlotHighlighted() + { + Highlight(_itemSlotManager.IsHighlighted(Entity)); + } + public void ClearHover() { if (EntityHover) diff --git a/Content.Client/Items/UI/ItemStatusPanel.cs b/Content.Client/Items/UI/ItemStatusPanel.cs index d880d7ea49..d1dbd714c6 100644 --- a/Content.Client/Items/UI/ItemStatusPanel.cs +++ b/Content.Client/Items/UI/ItemStatusPanel.cs @@ -8,7 +8,9 @@ using Robust.Client.Graphics; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Shared.GameObjects; +using Robust.Shared.IoC; using Robust.Shared.Maths; +using Robust.Shared.Timing; using Robust.Shared.Utility; using Robust.Shared.ViewVariables; using static Content.Client.IoC.StaticIoC; @@ -18,6 +20,8 @@ namespace Content.Client.Items.UI { public class ItemStatusPanel : Control { + [Dependency] private readonly IEntityManager _entityManager = default!; + [ViewVariables] private readonly List<(IItemStatus, Control)> _activeStatusComponents = new(); @@ -33,6 +37,8 @@ namespace Content.Client.Items.UI public ItemStatusPanel(Texture texture, StyleBox.Margin cutout, StyleBox.Margin flat, Label.AlignMode textAlign) { + IoCManager.InjectDependencies(this); + var panel = new StyleBoxTexture { Texture = texture @@ -117,6 +123,13 @@ namespace Content.Client.Items.UI return new ItemStatusPanel(ResC.GetTexture(texture), cutOut, flat, textAlign); } + protected override void FrameUpdate(FrameEventArgs args) + { + base.FrameUpdate(args); + + UpdateItemName(); + } + public void Update(IEntity? entity) { if (entity == null) @@ -131,12 +144,29 @@ namespace Content.Client.Items.UI { _entity = entity; BuildNewEntityStatus(); - _itemNameLabel.Text = entity.Name; + + UpdateItemName(); } _panel.Visible = true; } + private void UpdateItemName() + { + if (_entity == null) + return; + + if (_entity.TryGetComponent(out HandVirtualPullComponent? virtualPull) + && _entityManager.TryGetEntity(virtualPull.PulledEntity, out var pulledEnt)) + { + _itemNameLabel.Text = pulledEnt.Name; + } + else + { + _itemNameLabel.Text = _entity.Name; + } + } + private void ClearOldStatus() { _statusContents.RemoveAllChildren(); @@ -162,6 +192,14 @@ namespace Content.Client.Items.UI _activeStatusComponents.Add((statusComponent, control)); } + + var collectMsg = new ItemStatusCollectMessage(); + _entity.EntityManager.EventBus.RaiseLocalEvent(_entity.Uid, collectMsg); + + foreach (var control in collectMsg.Controls) + { + _statusContents.AddChild(control); + } } } } diff --git a/Content.Server/Fluids/Components/SpillableComponent.cs b/Content.Server/Fluids/Components/SpillableComponent.cs index 79e1f267a7..bd5dca6d3d 100644 --- a/Content.Server/Fluids/Components/SpillableComponent.cs +++ b/Content.Server/Fluids/Components/SpillableComponent.cs @@ -2,6 +2,7 @@ using Content.Shared.ActionBlocker; using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Solution.Components; using Content.Shared.DragDrop; +using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.Notification.Managers; using Content.Shared.Verbs; diff --git a/Content.Server/Hands/Components/HandsComponent.cs b/Content.Server/Hands/Components/HandsComponent.cs index c3f65060b5..f8b84c9992 100644 --- a/Content.Server/Hands/Components/HandsComponent.cs +++ b/Content.Server/Hands/Components/HandsComponent.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -10,7 +11,6 @@ using Content.Shared.Audio; using Content.Shared.Body.Part; using Content.Shared.Hands.Components; using Content.Shared.Notification.Managers; -using Content.Shared.Physics.Pull; using Content.Shared.Pulling.Components; using Robust.Server.GameObjects; using Robust.Shared.Audio; @@ -19,9 +19,7 @@ using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Map; -using Robust.Shared.Network; using Robust.Shared.Player; -using Robust.Shared.Players; namespace Content.Server.Hands.Components { @@ -35,48 +33,6 @@ namespace Content.Server.Hands.Components int IDisarmedAct.Priority => int.MaxValue; // We want this to be the last disarm act to run. - public override void HandleMessage(ComponentMessage message, IComponent? component) - { - base.HandleMessage(message, component); - - switch (message) - { - case PullAttemptMessage msg: - AttemptPull(msg); - break; - case PullStartedMessage: - StartPulling(); - break; - case PullStoppedMessage: - StopPulling(); - break; - } - } - - public override void HandleNetworkMessage(ComponentMessage message, INetChannel channel, ICommonSession? session = null) - { - base.HandleNetworkMessage(message, channel, session); - - switch (message) - { - case ClientChangedHandMsg msg: - ActiveHand = msg.HandName; - break; - case ClientAttackByInHandMsg msg: - InteractHandWithActiveHand(msg.HandName); - break; - case UseInHandMsg: - UseActiveHeldEntity(); - break; - case ActivateInHandMsg msg: - ActivateHeldEntity(msg.HandName); - break; - case MoveItemFromHandMsg msg: - TryMoveHeldEntityToActiveHand(msg.HandName); - break; - } - } - protected override void OnHeldEntityRemovedFromHand(IEntity heldEntity, HandState handState) { if (heldEntity.TryGetComponent(out ItemComponent? item)) @@ -141,7 +97,8 @@ namespace Content.Server.Hands.Components if (pickupDirection == initialPosition.ToMapPos(Owner.EntityManager)) return; - SendNetworkMessage(new PickupAnimationMessage(entity.Uid, pickupDirection, initialPosition)); + Owner.EntityManager.EntityNetManager!.SendSystemNetworkMessage( + new PickupAnimationMessage(entity.Uid, pickupDirection, initialPosition)); } #region Pull/Disarm @@ -151,9 +108,17 @@ namespace Content.Server.Hands.Components if (args.Part.PartType != BodyPartType.Hand) return; - var handLocation = ReadOnlyHands.Count == 0 ? HandLocation.Right : HandLocation.Left; //TODO: make hand body part have a handlocation? + // If this annoys you, which it should. + // Ping Smugleaf. + var location = args.Part.Symmetry switch + { + BodyPartSymmetry.None => HandLocation.Middle, + BodyPartSymmetry.Left => HandLocation.Left, + BodyPartSymmetry.Right => HandLocation.Right, + _ => throw new ArgumentOutOfRangeException() + }; - AddHand(args.Slot, handLocation); + AddHand(args.Slot, location); } void IBodyPartRemoved.BodyPartRemoved(BodyPartRemovedEventArgs args) @@ -205,41 +170,13 @@ namespace Content.Server.Hands.Components return pullable.TryStopPull(); } - private void AttemptPull(PullAttemptMessage msg) - { - if (!ReadOnlyHands.Any(hand => hand.Enabled)) - { - msg.Cancelled = true; - } - } - - private void StartPulling() - { - var firstFreeHand = Hands.FirstOrDefault(hand => hand.Enabled); - - if (firstFreeHand == null) - return; - - DisableHand(firstFreeHand); - } - - private void StopPulling() - { - var firstOccupiedHand = Hands.FirstOrDefault(hand => !hand.Enabled); - - if (firstOccupiedHand == null) - return; - - EnableHand(firstOccupiedHand); - } - #endregion #region Old public methods - public IEnumerable HandNames => ReadOnlyHands.Select(h => h.Name); + public IEnumerable HandNames => Hands.Select(h => h.Name); - public int Count => ReadOnlyHands.Count; + public int Count => Hands.Count; /// /// Returns a list of all hand names, with the active hand being first. @@ -249,9 +186,9 @@ namespace Content.Server.Hands.Components if (ActiveHand != null) yield return ActiveHand; - foreach (var hand in ReadOnlyHands) + foreach (var hand in Hands) { - if (hand.Name == ActiveHand || !hand.Enabled) + if (hand.Name == ActiveHand) continue; yield return hand.Name; diff --git a/Content.Server/Hands/Systems/HandVirtualPullSystem.cs b/Content.Server/Hands/Systems/HandVirtualPullSystem.cs new file mode 100644 index 0000000000..f95a4747ca --- /dev/null +++ b/Content.Server/Hands/Systems/HandVirtualPullSystem.cs @@ -0,0 +1,57 @@ +using Content.Server.Pulling; +using Content.Shared.Hands; +using Content.Shared.Hands.Components; +using Content.Shared.Interaction; +using JetBrains.Annotations; +using Robust.Shared.GameObjects; + +namespace Content.Server.Hands +{ + [UsedImplicitly] + public sealed class HandVirtualPullSystem : EntitySystem + { + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(HandlePullerDropped); + SubscribeLocalEvent(HandlePullerUnequipped); + + SubscribeLocalEvent(HandleBeforeInteract); + } + + private static void HandleBeforeInteract( + EntityUid uid, + HandVirtualPullComponent component, + BeforeInteractEvent args) + { + // No interactions with a virtual pull, please. + args.Handled = true; + } + + // If the virtual pull gets removed from the hands for any reason, cancel the pull and delete it. + private void HandlePullerUnequipped(EntityUid uid, HandVirtualPullComponent component, UnequippedHandEvent args) + { + MaybeDelete(component, args.User); + } + + private void HandlePullerDropped(EntityUid uid, HandVirtualPullComponent component, DroppedEvent args) + { + MaybeDelete(component, args.User); + } + + private void MaybeDelete(HandVirtualPullComponent comp, IEntity? user) + { + var pulled = comp.PulledEntity; + + if (!ComponentManager.TryGetComponent(pulled, out PullableComponent? pullable)) + return; + + if (pullable.Puller != user) + return; + + pullable.TryStopPull(user); + comp.Owner.QueueDelete(); + } + } +} diff --git a/Content.Server/Hands/HandsSystem.cs b/Content.Server/Hands/Systems/HandsSystem.cs similarity index 58% rename from Content.Server/Hands/HandsSystem.cs rename to Content.Server/Hands/Systems/HandsSystem.cs index 4a76d07101..7e6adca66c 100644 --- a/Content.Server/Hands/HandsSystem.cs +++ b/Content.Server/Hands/Systems/HandsSystem.cs @@ -13,9 +13,9 @@ using Content.Shared.Hands; using Content.Shared.Hands.Components; using Content.Shared.Input; using Content.Shared.Notification.Managers; +using Content.Shared.Physics.Pull; using JetBrains.Annotations; using Robust.Server.Player; -using Robust.Shared.Containers; using Robust.Shared.GameObjects; using Robust.Shared.Input.Binding; using Robust.Shared.IoC; @@ -23,6 +23,7 @@ using Robust.Shared.Localization; using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Players; +using Robust.Shared.Utility; using static Content.Shared.Inventory.EquipmentSlotDefines; namespace Content.Server.Hands @@ -38,15 +39,126 @@ namespace Content.Server.Hands base.Initialize(); SubscribeLocalEvent(HandleExamined); + SubscribeNetworkEvent(HandleActivateInHand); + SubscribeNetworkEvent(HandleInteractUsingInHand); + SubscribeNetworkEvent(HandleUseInHand); + SubscribeNetworkEvent(HandleMoveItemFromHand); + + SubscribeLocalEvent(HandlePullAttempt); + SubscribeLocalEvent(HandlePullStarted); + SubscribeLocalEvent(HandlePullStopped); CommandBinds.Builder .Bind(ContentKeyFunctions.ActivateItemInHand, InputCmdHandler.FromDelegate(HandleActivateItem)) .Bind(ContentKeyFunctions.ThrowItemInHand, new PointerInputCmdHandler(HandleThrowItem)) .Bind(ContentKeyFunctions.SmartEquipBackpack, InputCmdHandler.FromDelegate(HandleSmartEquipBackpack)) .Bind(ContentKeyFunctions.SmartEquipBelt, InputCmdHandler.FromDelegate(HandleSmartEquipBelt)) + .Bind(ContentKeyFunctions.SwapHands, InputCmdHandler.FromDelegate(SwapHandsPressed, handle: false)) + .Bind(ContentKeyFunctions.Drop, new PointerInputCmdHandler(DropPressed)) .Register(); } + private static void HandlePullAttempt(EntityUid uid, HandsComponent component, PullAttemptMessage args) + { + // Cancel pull if all hands full. + if (component.Hands.All(hand => !hand.IsEmpty)) + args.Cancelled = true; + } + + private void HandlePullStarted(EntityUid uid, HandsComponent component, PullStartedMessage args) + { + foreach (var handName in component.ActivePriorityEnumerable()) + { + var hand = component.GetHand(handName); + if (!hand.IsEmpty) + continue; + + var pos = component.Owner.Transform.Coordinates; + var virtualPull = EntityManager.SpawnEntity("HandVirtualPull", pos); + var virtualPullComp = virtualPull.GetComponent(); + virtualPullComp.PulledEntity = args.Pulled.Owner.Uid; + component.PutEntityIntoHand(hand, virtualPull); + return; + } + + DebugTools.Assert("Unable to find available hand when starting pulling??"); + } + + private void HandlePullStopped(EntityUid uid, HandsComponent component, PullStoppedMessage args) + { + // Try find hand that is doing this pull. + // and clear it. + foreach (var hand in component.Hands) + { + if (hand.HeldEntity == null + || !hand.HeldEntity.TryGetComponent(out HandVirtualPullComponent? virtualPull) + || virtualPull.PulledEntity != args.Pulled.Owner.Uid) + continue; + + hand.HeldEntity.Delete(); + break; + } + } + + private void SwapHandsPressed(ICommonSession? session) + { + var player = session?.AttachedEntity; + + if (player == null) + return; + + if (!player.TryGetComponent(out SharedHandsComponent? hands)) + return; + + if (!hands.TryGetSwapHandsResult(out var nextHand)) + return; + + hands.ActiveHand = nextHand; + } + + private bool DropPressed(ICommonSession? session, EntityCoordinates coords, EntityUid uid) + { + var player = session?.AttachedEntity; + + if (player == null) + return false; + + if (!player.TryGetComponent(out SharedHandsComponent? hands)) + return false; + + var activeHand = hands.ActiveHand; + + if (activeHand == null) + return false; + + hands.TryDropHand(activeHand, coords); + return false; + } + + private void HandleMoveItemFromHand(MoveItemFromHandMsg msg, EntitySessionEventArgs args) + { + if (!TryGetHandsComp(args.SenderSession, out var hands)) + return; + + hands.TryMoveHeldEntityToActiveHand(msg.HandName); + } + + private void HandleUseInHand(UseInHandMsg msg, EntitySessionEventArgs args) + { + if (!TryGetHandsComp(args.SenderSession, out var hands)) + return; + + hands.UseActiveHeldEntity(); + } + + private void HandleInteractUsingInHand(ClientInteractUsingInHandMsg msg, EntitySessionEventArgs args) + { + if (!TryGetHandsComp(args.SenderSession, out var hands)) + return; + + hands.InteractHandWithActiveHand(msg.HandName); + } + public override void Shutdown() { base.Shutdown(); @@ -54,6 +166,14 @@ namespace Content.Server.Hands CommandBinds.Unregister(); } + private void HandleActivateInHand(ActivateInHandMsg msg, EntitySessionEventArgs args) + { + if (!TryGetHandsComp(args.SenderSession, out var hands)) + return; + + hands.ActivateHeldEntity(msg.HandName); + } + protected override void DropAllItemsInHands(IEntity entity, bool doMobChecks = true) { base.DropAllItemsInHands(entity, doMobChecks); @@ -75,25 +195,21 @@ namespace Content.Server.Hands } } - protected override void HandleContainerModified(EntityUid uid, SharedHandsComponent component, ContainerModifiedMessage args) - { - component.Dirty(); - } - - private bool TryGetHandsComp(ICommonSession? session, [NotNullWhen(true)] out SharedHandsComponent? hands) + private static bool TryGetHandsComp( + ICommonSession? session, + [NotNullWhen(true)] out SharedHandsComponent? hands) { hands = default; if (session is not IPlayerSession playerSession) return false; - var playerEnt = playerSession?.AttachedEntity; + var playerEnt = playerSession.AttachedEntity; if (playerEnt == null || !playerEnt.IsValid()) return false; - playerEnt.TryGetComponent(out hands); - return hands != null; + return playerEnt.TryGetComponent(out hands); } private void HandleActivateItem(ICommonSession? session) diff --git a/Content.Server/Interaction/InteractionSystem.cs b/Content.Server/Interaction/InteractionSystem.cs index 61a5ce724e..6908b146b5 100644 --- a/Content.Server/Interaction/InteractionSystem.cs +++ b/Content.Server/Interaction/InteractionSystem.cs @@ -387,6 +387,18 @@ namespace Content.Server.Interaction return false; } + private async Task InteractDoBefore( + IEntity user, + IEntity used, + IEntity? target, + EntityCoordinates clickLocation, + bool canReach) + { + var ev = new BeforeInteractEvent(user, used, target, clickLocation, canReach); + RaiseLocalEvent(used.Uid, ev, false); + return ev.Handled; + } + /// /// Uses a item/object on an entity /// Finds components with the InteractUsing interface and calls their function @@ -397,6 +409,9 @@ namespace Content.Server.Interaction if (!_actionBlockerSystem.CanInteract(user)) return; + if (await InteractDoBefore(user, used, target, clickLocation, true)) + return; + // all interactions should only happen when in range / unobstructed, so no range check is needed var interactUsingEvent = new InteractUsingEvent(user, used, target, clickLocation); RaiseLocalEvent(target.Uid, interactUsingEvent); @@ -696,6 +711,9 @@ namespace Content.Server.Interaction /// public async Task InteractUsingRanged(IEntity user, IEntity used, IEntity? target, EntityCoordinates clickLocation, bool inRangeUnobstructed) { + if (await InteractDoBefore(user, used, inRangeUnobstructed ? target : null, clickLocation, false)) + return true; + if (target != null) { var rangedMsg = new RangedInteractEvent(user, used, target, clickLocation); @@ -715,10 +733,7 @@ namespace Content.Server.Interaction } } - if (inRangeUnobstructed) - return await InteractDoAfter(user, used, target, clickLocation, false); - else - return await InteractDoAfter(user, used, null, clickLocation, false); + return await InteractDoAfter(user, used, inRangeUnobstructed ? target : null, clickLocation, false); } public void DoAttack(IEntity user, EntityCoordinates coordinates, bool wideAttack, EntityUid targetUid = default) diff --git a/Content.Shared/Hands/Components/HandVirtualPullComponent.cs b/Content.Shared/Hands/Components/HandVirtualPullComponent.cs new file mode 100644 index 0000000000..42f6bd85f3 --- /dev/null +++ b/Content.Shared/Hands/Components/HandVirtualPullComponent.cs @@ -0,0 +1,50 @@ +using System; +using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; +using Robust.Shared.Players; +using Robust.Shared.Serialization; + +namespace Content.Shared.Hands.Components +{ + [RegisterComponent] + [NetworkedComponent] + public sealed class HandVirtualPullComponent : Component + { + private EntityUid _pulledEntity; + public override string Name => "HandVirtualPull"; + + public EntityUid PulledEntity + { + get => _pulledEntity; + set + { + _pulledEntity = value; + Dirty(); + } + } + + public override ComponentState GetComponentState(ICommonSession player) + { + return new VirtualPullComponentState(_pulledEntity); + } + + public override void HandleComponentState(ComponentState? curState, ComponentState? nextState) + { + if (curState is not VirtualPullComponentState pullState) + return; + + _pulledEntity = pullState.PulledEntity; + } + + [Serializable, NetSerializable] + public sealed class VirtualPullComponentState : ComponentState + { + public readonly EntityUid PulledEntity; + + public VirtualPullComponentState(EntityUid pulledEntity) + { + PulledEntity = pulledEntity; + } + } + } +} diff --git a/Content.Shared/Hands/Components/SharedHandsComponent.cs b/Content.Shared/Hands/Components/SharedHandsComponent.cs index 15a2ca3d69..9e1978b06f 100644 --- a/Content.Shared/Hands/Components/SharedHandsComponent.cs +++ b/Content.Shared/Hands/Components/SharedHandsComponent.cs @@ -55,11 +55,11 @@ namespace Content.Shared.Hands.Components } } } + private string? _activeHand; [ViewVariables] - public IReadOnlyList ReadOnlyHands => Hands; - protected readonly List Hands = new(); + public readonly List Hands = new(); /// /// The amount of throw impulse per distance the player is from the throw target. @@ -89,7 +89,10 @@ namespace Content.Shared.Hands.Components public virtual void HandsModified() { + // todo axe all this for ECS. Dirty(); + + Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new HandsModifiedMessage { Hands = this }); } public void AddHand(string handName, HandLocation handLocation) @@ -100,7 +103,7 @@ namespace Content.Shared.Hands.Components var container = ContainerHelpers.CreateContainer(Owner, handName); container.OccludesLight = false; - Hands.Add(new Hand(handName, true, handLocation, container)); + Hands.Add(new Hand(handName, handLocation, container)); if (ActiveHand == null) ActiveHand = handName; @@ -125,48 +128,55 @@ namespace Content.Shared.Hands.Components Hands.Remove(hand); if (ActiveHand == hand.Name) - ActiveHand = ReadOnlyHands.FirstOrDefault()?.Name; + ActiveHand = Hands.FirstOrDefault()?.Name; HandCountChanged(); HandsModified(); } - public bool HasHand(string handName) - { - foreach (var hand in Hands) - { - if (hand.Name == handName) - return true; - } - return false; - } - - private Hand? GetHand(string handName) - { - foreach (var hand in Hands) - { - if (hand.Name == handName) - return hand; - } - return null; - } - private Hand? GetActiveHand() { if (ActiveHand == null) return null; - return GetHand(ActiveHand); + return GetHandOrNull(ActiveHand); } - protected bool TryGetHand(string handName, [NotNullWhen(true)] out Hand? foundHand) + public bool HasHand(string handName) { - foundHand = GetHand(handName); - return foundHand != null; + return TryGetHand(handName, out _); } - protected bool TryGetActiveHand([NotNullWhen(true)] out Hand? activeHand) + public Hand? GetHandOrNull(string handName) + { + return TryGetHand(handName, out var hand) ? hand : null; + } + + public Hand GetHand(string handName) + { + if (!TryGetHand(handName, out var hand)) + throw new KeyNotFoundException($"Unable to find hand with name {handName}"); + + return hand; + } + + public bool TryGetHand(string handName, [NotNullWhen(true)] out Hand? foundHand) + { + foreach (var hand in Hands) + { + if (hand.Name == handName) + { + foundHand = hand; + return true; + }; + } + + foundHand = null; + return false; + } + + public bool TryGetActiveHand([NotNullWhen(true)] out Hand? activeHand) { activeHand = GetActiveHand(); return activeHand != null; @@ -211,7 +221,7 @@ namespace Content.Shared.Hands.Components public IEnumerable GetAllHeldEntities() { - foreach (var hand in ReadOnlyHands) + foreach (var hand in Hands) { if (hand.HeldEntity != null) yield return hand.HeldEntity; @@ -416,7 +426,7 @@ namespace Content.Shared.Hands.Components /// /// Drops a hands contents to the target location. /// - private void DropHeldEntity(Hand hand, EntityCoordinates targetDropLocation, bool intentionalDrop = true) + public void DropHeldEntity(Hand hand, EntityCoordinates targetDropLocation, bool intentionalDrop = true) { var heldEntity = hand.HeldEntity; @@ -538,16 +548,7 @@ namespace Content.Shared.Hands.Components public bool CanPickupEntityToActiveHand(IEntity entity, bool checkActionBlocker = true) { - if (!TryGetActiveHand(out var hand)) - return false; - - if (checkActionBlocker && !PlayerCanPickup()) - return false; - - if (!CanInsertEntityIntoHand(hand, entity)) - return false; - - return true; + return ActiveHand != null && CanPickupEntity(ActiveHand, entity, checkActionBlocker); } /// @@ -563,10 +564,7 @@ namespace Content.Shared.Hands.Components public bool TryPickupEntityToActiveHand(IEntity entity, bool checkActionBlocker = true) { - if (!TryGetActiveHand(out var hand)) - return false; - - return TryPickupEntity(hand, entity, checkActionBlocker); + return ActiveHand != null && TryPickupEntity(ActiveHand, entity, checkActionBlocker); } /// @@ -574,9 +572,6 @@ namespace Content.Shared.Hands.Components /// protected bool CanInsertEntityIntoHand(Hand hand, IEntity entity) { - if (!hand.Enabled) - return false; - var handContainer = hand.Container; if (handContainer == null) return false; @@ -602,7 +597,7 @@ namespace Content.Shared.Hands.Components /// /// Puts an entity into the player's hand, assumes that the insertion is allowed. /// - private void PutEntityIntoHand(Hand hand, IEntity entity) + public void PutEntityIntoHand(Hand hand, IEntity entity) { var handContainer = hand.Container; if (handContainer == null) @@ -658,7 +653,7 @@ namespace Content.Shared.Hands.Components if (newActiveIndex > finalHandIndex) newActiveIndex = 0; - nextHand = ReadOnlyHands[newActiveIndex].Name; + nextHand = Hands[newActiveIndex].Name; return true; } @@ -752,7 +747,7 @@ namespace Content.Shared.Hands.Components Hand? priorityHand = null; if (priorityHandName != null) - priorityHand = GetHand(priorityHandName); + priorityHand = GetHandOrNull(priorityHandName); return TryPutInAnyHand(entity, priorityHand, checkActionBlocker); } @@ -793,43 +788,16 @@ namespace Content.Shared.Hands.Components protected virtual void DoActivate(IEntity heldEntity) { } protected virtual void HandlePickupAnimation(IEntity entity) { } - - protected void EnableHand(Hand hand) - { - hand.Enabled = true; - Dirty(); - } - - protected void DisableHand(Hand hand) - { - hand.Enabled = false; - DropHeldEntityToFloor(hand, intentionalDrop: false); - Dirty(); - } } - public interface IReadOnlyHand + public class Hand { + [ViewVariables] public string Name { get; } - public bool Enabled { get; } - + [ViewVariables] public HandLocation Location { get; } - public abstract IEntity? HeldEntity { get; } - } - - public class Hand : IReadOnlyHand - { - [ViewVariables] - public string Name { get; set; } - - [ViewVariables] - public bool Enabled { get; set; } - - [ViewVariables] - public HandLocation Location { get; set; } - /// /// The container used to hold the contents of this hand. Nullable because the client must get the containers via , /// which may not be synced with the server when the client hands are created. @@ -840,37 +808,36 @@ namespace Content.Shared.Hands.Components [ViewVariables] public IEntity? HeldEntity => Container?.ContainedEntities?.FirstOrDefault(); - public Hand(string name, bool enabled, HandLocation location, IContainer? container = null) + public bool IsEmpty => HeldEntity == null; + + public Hand(string name, HandLocation location, IContainer? container = null) { Name = name; - Enabled = enabled; Location = location; Container = container; } public HandState ToHandState() { - return new(Name, Location, Enabled); + return new(Name, Location); } } [Serializable, NetSerializable] - public sealed class HandState + public struct HandState { public string Name { get; } public HandLocation Location { get; } - public bool Enabled { get; } - public HandState(string name, HandLocation location, bool enabled) + public HandState(string name, HandLocation location) { Name = name; Location = location; - Enabled = enabled; } } [Serializable, NetSerializable] - public class HandsComponentState : ComponentState + public sealed class HandsComponentState : ComponentState { public HandState[] Hands { get; } public string? ActiveHand { get; } @@ -886,25 +853,20 @@ namespace Content.Shared.Hands.Components /// A message that calls the use interaction on an item in hand, presumed for now the interaction will occur only on the active hand. /// [Serializable, NetSerializable] - public class UseInHandMsg : ComponentMessage + public sealed class UseInHandMsg : EntityEventArgs { - public UseInHandMsg() - { - Directed = true; - } } /// /// A message that calls the activate interaction on the item in the specified hand. /// [Serializable, NetSerializable] - public class ActivateInHandMsg : ComponentMessage + public class ActivateInHandMsg : EntityEventArgs { public string HandName { get; } public ActivateInHandMsg(string handName) { - Directed = true; HandName = handName; } } @@ -913,13 +875,12 @@ namespace Content.Shared.Hands.Components /// Uses the item in the active hand on the item in the specified hand. /// [Serializable, NetSerializable] - public class ClientAttackByInHandMsg : ComponentMessage + public class ClientInteractUsingInHandMsg : EntityEventArgs { public string HandName { get; } - public ClientAttackByInHandMsg(string handName) + public ClientInteractUsingInHandMsg(string handName) { - Directed = true; HandName = handName; } } @@ -928,28 +889,12 @@ namespace Content.Shared.Hands.Components /// Moves an item from one hand to the active hand. /// [Serializable, NetSerializable] - public class MoveItemFromHandMsg : ComponentMessage + public class MoveItemFromHandMsg : EntityEventArgs { public string HandName { get; } public MoveItemFromHandMsg(string handName) { - Directed = true; - HandName = handName; - } - } - - /// - /// Sets the player's active hand to a specified hand. - /// - [Serializable, NetSerializable] - public class ClientChangedHandMsg : ComponentMessage - { - public string HandName { get; } - - public ClientChangedHandMsg(string handName) - { - Directed = true; HandName = handName; } } @@ -975,7 +920,7 @@ namespace Content.Shared.Hands.Components } [Serializable, NetSerializable] - public class PickupAnimationMessage : ComponentMessage + public class PickupAnimationMessage : EntityEventArgs { public EntityUid EntityUid { get; } public EntityCoordinates InitialPosition { get; } @@ -983,10 +928,15 @@ namespace Content.Shared.Hands.Components public PickupAnimationMessage(EntityUid entityUid, Vector2 pickupDirection, EntityCoordinates initialPosition) { - Directed = true; EntityUid = entityUid; PickupDirection = pickupDirection; InitialPosition = initialPosition; } } + + [Serializable, NetSerializable] + public struct HandsModifiedMessage + { + public SharedHandsComponent Hands; + } } diff --git a/Content.Shared/Hands/SharedHandsSystem.cs b/Content.Shared/Hands/SharedHandsSystem.cs index 9a9bdec3d8..96bd96d793 100644 --- a/Content.Shared/Hands/SharedHandsSystem.cs +++ b/Content.Shared/Hands/SharedHandsSystem.cs @@ -1,7 +1,6 @@ using Content.Shared.Hands.Components; using Robust.Shared.Containers; using Robust.Shared.GameObjects; -using Robust.Shared.Map; using Robust.Shared.Serialization; using System; @@ -16,11 +15,7 @@ namespace Content.Shared.Hands SubscribeLocalEvent(HandleContainerModified); SubscribeLocalEvent(HandleContainerModified); - SubscribeLocalEvent(HandleSetHand); - SubscribeNetworkEvent(HandleSetHand); - - SubscribeLocalEvent(HandleDrop); - SubscribeNetworkEvent(HandleDrop); + SubscribeAllEvent(HandleSetHand); } public void DropHandItems(IEntity entity, bool doMobChecks = true) @@ -38,14 +33,16 @@ namespace Content.Shared.Hands eventBus.RaiseLocalEvent(uid, msg); - if (msg.Cancelled) return; + if (msg.Cancelled) + return; if (entity.TryGetContainerMan(out var containerManager)) { var parentMsg = new ContainedEntityDropHandItemsAttemptEvent(uid); eventBus.RaiseLocalEvent(containerManager.Owner.Uid, parentMsg); - if (parentMsg.Cancelled) return; + if (parentMsg.Cancelled) + return; } DropAllItemsInHands(entity, doMobChecks); @@ -55,9 +52,9 @@ namespace Content.Shared.Hands { } - private void HandleSetHand(RequestSetHandEvent msg, EntitySessionEventArgs eventArgs) + private static void HandleSetHand(RequestSetHandEvent msg, EntitySessionEventArgs eventArgs) { - var entity = eventArgs.SenderSession?.AttachedEntity; + var entity = eventArgs.SenderSession.AttachedEntity; if (entity == null || !entity.TryGetComponent(out SharedHandsComponent? hands)) return; @@ -65,17 +62,13 @@ namespace Content.Shared.Hands hands.ActiveHand = msg.HandName; } - private void HandleDrop(RequestDropHeldEntityEvent msg, EntitySessionEventArgs eventArgs) + protected virtual void HandleContainerModified( + EntityUid uid, + SharedHandsComponent component, + ContainerModifiedMessage args) { - var entity = eventArgs.SenderSession?.AttachedEntity; - - if (entity == null || !entity.TryGetComponent(out SharedHandsComponent? hands)) - return; - - hands.TryDropHand(msg.HandName, msg.DropTarget); + component.Dirty(); } - - protected abstract void HandleContainerModified(EntityUid uid, SharedHandsComponent component, ContainerModifiedMessage args); } public sealed class ContainedEntityDropHandItemsAttemptEvent : CancellableEntityEventArgs @@ -103,21 +96,4 @@ namespace Content.Shared.Hands HandName = handName; } } - - [Serializable, NetSerializable] - public class RequestDropHeldEntityEvent : EntityEventArgs - { - /// - /// The hand to drop from. - /// - public string HandName { get; } - - public EntityCoordinates DropTarget { get; } - - public RequestDropHeldEntityEvent(string handName, EntityCoordinates dropTarget) - { - HandName = handName; - DropTarget = dropTarget; - } - } } diff --git a/Content.Shared/Interaction/BeforeInteract.cs b/Content.Shared/Interaction/BeforeInteract.cs new file mode 100644 index 0000000000..2c8bb5ca83 --- /dev/null +++ b/Content.Shared/Interaction/BeforeInteract.cs @@ -0,0 +1,53 @@ +using JetBrains.Annotations; +using Robust.Shared.GameObjects; +using Robust.Shared.Map; + +namespace Content.Shared.Interaction +{ + /// + /// Raised directed on the used object when clicking on another object before an interaction is handled. + /// + [PublicAPI] + public class BeforeInteractEvent : HandledEntityEventArgs + { + /// + /// Entity that triggered the interaction. + /// + public IEntity User { get; } + + /// + /// Entity that the user used to interact. + /// + public IEntity Used { get; } + + /// + /// Entity that was interacted on. This can be null if the attack did not click on an entity. + /// + public IEntity? Target { get; } + + /// + /// Location that the user clicked outside of their interaction range. + /// + public EntityCoordinates ClickLocation { get; } + + /// + /// Is the click location close enough to reach by the player? This does not check for obstructions, just that the target is within + /// reach radius around the user. + /// + public bool CanReach { get; } + + public BeforeInteractEvent( + IEntity user, + IEntity used, + IEntity? target, + EntityCoordinates clickLocation, + bool canReach) + { + User = user; + Used = used; + Target = target; + ClickLocation = clickLocation; + CanReach = canReach; + } + } +} diff --git a/Content.Shared/DragDrop/IDropped.cs b/Content.Shared/Interaction/IDropped.cs similarity index 97% rename from Content.Shared/DragDrop/IDropped.cs rename to Content.Shared/Interaction/IDropped.cs index c09f7fc7df..b1daed20fc 100644 --- a/Content.Shared/DragDrop/IDropped.cs +++ b/Content.Shared/Interaction/IDropped.cs @@ -3,7 +3,7 @@ using JetBrains.Annotations; using Robust.Shared.Analyzers; using Robust.Shared.GameObjects; -namespace Content.Shared.DragDrop +namespace Content.Shared.Interaction { /// /// This interface gives components behavior when they're dropped by a mob. diff --git a/Content.Shared/Pulling/Components/SharedPullableComponent.cs b/Content.Shared/Pulling/Components/SharedPullableComponent.cs index 34890165eb..a25f3915ff 100644 --- a/Content.Shared/Pulling/Components/SharedPullableComponent.cs +++ b/Content.Shared/Pulling/Components/SharedPullableComponent.cs @@ -50,6 +50,8 @@ namespace Content.Shared.Pulling.Components return; } + var eventBus = Owner.EntityManager.EventBus; + // New value. Abandon being pulled by any existing object. if (_puller != null) { @@ -64,10 +66,9 @@ namespace Content.Shared.Pulling.Components { var message = new PullStoppedMessage(oldPullerPhysics, _physics); - oldPuller.SendMessage(null, message); - Owner.SendMessage(null, message); + eventBus.RaiseLocalEvent(oldPuller.Uid, message, broadcast: false); + eventBus.RaiseLocalEvent(Owner.Uid, message); - oldPuller.EntityManager.EventBus.RaiseEvent(EventSource.Local, message); _physics.WakeBody(); } // else-branch warning is handled below @@ -125,14 +126,14 @@ namespace Content.Shared.Pulling.Components var pullAttempt = new PullAttemptMessage(pullerPhysics, _physics); - value.SendMessage(null, pullAttempt); + eventBus.RaiseLocalEvent(value.Uid, pullAttempt, broadcast: false); if (pullAttempt.Cancelled) { return; } - Owner.SendMessage(null, pullAttempt); + eventBus.RaiseLocalEvent(Owner.Uid, pullAttempt); if (pullAttempt.Cancelled) { @@ -147,10 +148,8 @@ namespace Content.Shared.Pulling.Components var message = new PullStartedMessage(PullerPhysics, _physics); - _puller.SendMessage(null, message); - Owner.SendMessage(null, message); - - _puller.EntityManager.EventBus.RaiseEvent(EventSource.Local, message); + eventBus.RaiseLocalEvent(_puller.Uid, message, broadcast: false); + eventBus.RaiseLocalEvent(Owner.Uid, message); var union = PullerPhysics.GetWorldAABB().Union(_physics.GetWorldAABB()); var length = Math.Max(union.Size.X, union.Size.Y) * 0.75f; @@ -335,29 +334,6 @@ namespace Content.Shared.Pulling.Components Puller = entity; } - public override void HandleMessage(ComponentMessage message, IComponent? component) - { - base.HandleMessage(message, component); - - if (message is not PullMessage pullMessage || - pullMessage.Pulled.Owner != Owner) - { - return; - } - - var pulledStatus = Owner.GetComponentOrNull(); - - switch (message) - { - case PullStartedMessage: - pulledStatus?.ShowAlert(AlertType.Pulled); - break; - case PullStoppedMessage: - pulledStatus?.ClearAlert(AlertType.Pulled); - break; - } - } - protected override void OnRemove() { TryStopPull(); diff --git a/Content.Shared/Pulling/Components/SharedPullerComponent.cs b/Content.Shared/Pulling/Components/SharedPullerComponent.cs index 12a539ab12..1579339958 100644 --- a/Content.Shared/Pulling/Components/SharedPullerComponent.cs +++ b/Content.Shared/Pulling/Components/SharedPullerComponent.cs @@ -1,6 +1,4 @@ -using Content.Shared.Alert; -using Content.Shared.Movement.Components; -using Content.Shared.Physics.Pull; +using Content.Shared.Movement.Components; using Robust.Shared.GameObjects; using Component = Robust.Shared.GameObjects.Component; @@ -46,30 +44,5 @@ namespace Content.Shared.Pulling.Components base.OnRemove(); } - - public override void HandleMessage(ComponentMessage message, IComponent? component) - { - base.HandleMessage(message, component); - - if (message is not PullMessage pullMessage || - pullMessage.Puller.Owner != Owner) - { - return; - } - - SharedAlertsComponent? ownerStatus = Owner.GetComponentOrNull(); - - switch (message) - { - case PullStartedMessage msg: - Pulling = msg.Pulled.Owner; - ownerStatus?.ShowAlert(AlertType.Pulling); - break; - case PullStoppedMessage _: - Pulling = null; - ownerStatus?.ClearAlert(AlertType.Pulling); - break; - } - } } } diff --git a/Content.Shared/Physics/Pull/PullAttemptMessage.cs b/Content.Shared/Pulling/Events/PullAttemptMessage.cs similarity index 100% rename from Content.Shared/Physics/Pull/PullAttemptMessage.cs rename to Content.Shared/Pulling/Events/PullAttemptMessage.cs diff --git a/Content.Shared/Physics/Pull/PullMessage.cs b/Content.Shared/Pulling/Events/PullMessage.cs similarity index 87% rename from Content.Shared/Physics/Pull/PullMessage.cs rename to Content.Shared/Pulling/Events/PullMessage.cs index cbdb5af3a1..4376720a03 100644 --- a/Content.Shared/Physics/Pull/PullMessage.cs +++ b/Content.Shared/Pulling/Events/PullMessage.cs @@ -3,7 +3,7 @@ using Robust.Shared.Physics; namespace Content.Shared.Physics.Pull { - public class PullMessage : ComponentMessage + public class PullMessage : EntityEventArgs { public readonly IPhysBody Puller; public readonly IPhysBody Pulled; diff --git a/Content.Shared/Physics/Pull/PullStartedMessage.cs b/Content.Shared/Pulling/Events/PullStartedMessage.cs similarity index 100% rename from Content.Shared/Physics/Pull/PullStartedMessage.cs rename to Content.Shared/Pulling/Events/PullStartedMessage.cs diff --git a/Content.Shared/Physics/Pull/PullStoppedMessage.cs b/Content.Shared/Pulling/Events/PullStoppedMessage.cs similarity index 100% rename from Content.Shared/Physics/Pull/PullStoppedMessage.cs rename to Content.Shared/Pulling/Events/PullStoppedMessage.cs diff --git a/Content.Shared/Pulling/Systems/SharedPullerSystem.cs b/Content.Shared/Pulling/Systems/SharedPullerSystem.cs new file mode 100644 index 0000000000..d05754c851 --- /dev/null +++ b/Content.Shared/Pulling/Systems/SharedPullerSystem.cs @@ -0,0 +1,44 @@ +using Content.Shared.Alert; +using Content.Shared.Physics.Pull; +using Content.Shared.Pulling.Components; +using JetBrains.Annotations; +using Robust.Shared.GameObjects; + +namespace Content.Shared.Pulling +{ + [UsedImplicitly] + public sealed class SharedPullerSystem : EntitySystem + { + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(PullerHandlePullStarted); + SubscribeLocalEvent(PullerHandlePullStopped); + } + + private static void PullerHandlePullStarted( + EntityUid uid, + SharedPullerComponent component, + PullStartedMessage args) + { + if (args.Puller.Owner.Uid != uid) + return; + + if (component.Owner.TryGetComponent(out SharedAlertsComponent? alerts)) + alerts.ShowAlert(AlertType.Pulling); + } + + private static void PullerHandlePullStopped( + EntityUid uid, + SharedPullerComponent component, + PullStoppedMessage args) + { + if (args.Puller.Owner.Uid != uid) + return; + + if (component.Owner.TryGetComponent(out SharedAlertsComponent? alerts)) + alerts.ClearAlert(AlertType.Pulling); + } + } +} diff --git a/Content.Shared/Pulling/SharedPullingSystem.cs b/Content.Shared/Pulling/Systems/SharedPullingSystem.cs similarity index 88% rename from Content.Shared/Pulling/SharedPullingSystem.cs rename to Content.Shared/Pulling/Systems/SharedPullingSystem.cs index 0bb04d19b4..5783ee332d 100644 --- a/Content.Shared/Pulling/SharedPullingSystem.cs +++ b/Content.Shared/Pulling/Systems/SharedPullingSystem.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using Content.Shared.Alert; using Content.Shared.GameTicking; using Content.Shared.Input; using Content.Shared.Physics.Pull; @@ -56,12 +57,34 @@ namespace Content.Shared.Pulling SubscribeLocalEvent(PullerMoved); SubscribeLocalEvent(HandleContainerInsert); + SubscribeLocalEvent(PullableHandlePullStarted); + SubscribeLocalEvent(PullableHandlePullStopped); + CommandBinds.Builder .Bind(ContentKeyFunctions.MovePulledObject, new PointerInputCmdHandler(HandleMovePulledObject)) .Bind(ContentKeyFunctions.ReleasePulledObject, InputCmdHandler.FromDelegate(HandleReleasePulledObject)) .Register(); } + // Raise a "you are being pulled" alert if the pulled entity has alerts. + private static void PullableHandlePullStarted(EntityUid uid, SharedPullableComponent component, PullStartedMessage args) + { + if (args.Pulled.Owner.Uid != uid) + return; + + if (component.Owner.TryGetComponent(out SharedAlertsComponent? alerts)) + alerts.ShowAlert(AlertType.Pulled); + } + + private static void PullableHandlePullStopped(EntityUid uid, SharedPullableComponent component, PullStoppedMessage args) + { + if (args.Pulled.Owner.Uid != uid) + return; + + if (component.Owner.TryGetComponent(out SharedAlertsComponent? alerts)) + alerts.ClearAlert(AlertType.Pulled); + } + public override void Update(float frameTime) { base.Update(frameTime); diff --git a/Resources/Prototypes/Entities/Virtual/virtual_pull_item.yml b/Resources/Prototypes/Entities/Virtual/virtual_pull_item.yml new file mode 100644 index 0000000000..aa5b7bdb22 --- /dev/null +++ b/Resources/Prototypes/Entities/Virtual/virtual_pull_item.yml @@ -0,0 +1,8 @@ +# This item is stored in the hand slot while you are pulling something, to represent that and what you are pulling. +- type: entity + id: HandVirtualPull + name: VIRTUAL PULL YOU SHOULD NOT SEE THIS + abstract: true + components: + - type: Item + - type: HandVirtualPull From 881c9c9856ef312d5b8011cfc8d77d82cdc672d3 Mon Sep 17 00:00:00 2001 From: PJBot Date: Fri, 30 Jul 2021 21:15:03 -0400 Subject: [PATCH 36/76] Automatic changelog update --- Resources/Changelog/Changelog.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 62bb69b3ae..085c58872c 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1613,3 +1613,10 @@ Entries: - {message: Added bio suits into bio lockers, type: Add} id: 287 time: '2021-07-28T17:36:06.0000000+00:00' +- author: PJB3005 + changes: + - {message: Pulling is better integrated with hands now. It properly picks an empty + hand and dropping on the pulling hand stops the pull. You also see what you're + pulling in your hand!, type: Tweak} + id: 288 + time: '2021-07-31T01:14:00.0000000+00:00' From dbc90f202a1ff0a06301ea0fbf44ace31d67bc29 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+leonsfriedrich@users.noreply.github.com> Date: Sat, 31 Jul 2021 14:11:02 +1000 Subject: [PATCH 37/76] Bug fixes for metabolisable reagents (#4385) * HealthChangeMetabolism now scales with ticktime and metabolism rate Both Food and Drink metabolisms scale with ticktime, Now HealthChangeMetabolism also does so. Additionally, 'healthChange' now correctly scales with the metabolism rate, so it's description is now correct. * LiverBehaviour now uses correct frameTime Previously, the liver only metabolised reagants once every second, but incorrectly passes the current frameTime to the metabilism function, not 1 second. * Stomach now only transfers non-empty solutions. Makes debugging bloodstream bugs easier if the stomach is not constantly adding empty solution to it. * Fixed StomachBehaviour using wrong SolutionContainerComponent Stomach was using the first SolutionContainerComponent in the owner of the body, instead of the container in the owner of the mechanism (stomach). As a result, it used to use the BloodStreamComponent.Solution as a "Stomach". * Update StomachBehavior.cs Somach now checks if it still contains a reagant, before transferring it. * Added argument to IMetabolizable.Metabolize() Added availableReagent argument to IMetabolizable.Metabolize(), This ensures that this function does not over-metabolize a reagant, which can happen if tickTime*metabolismRate is larger than the available reagant * Revert "Stomach now only transfers non-empty solutions." This reverts commit 2a51e2d87e6e17ab76b48e5316ce501ec05ac061. * Renamed _updateInterval to _updateIntervalSeconds Also modified doc comment specifying units * Fix spelling of healthChangeAmount Changed from healthChangeAmmount to healthChangeAmount * Fixed comment comment used to mention _updateInterval, which has been renamed _updateIntervalSeconds * Fixed typo in comment * fixed typos: reagant -> reagent Most typos were just in comments. * Make metabolizable classes inherit from DefaultMetabolizable Also involved changing around IMetabolizable * Renamed variables metabolismAmount -> amountMetabolized * Updated Comments in DefaultMetabolizable Makes it clearer why DefaultMetabolizable works as it does, and that other classes depend on it. --- Content.Server/Body/Behavior/LiverBehavior.cs | 18 ++++++++--- .../Body/Behavior/StomachBehavior.cs | 25 ++++++++++++---- .../Chemistry/Metabolism/DefaultDrink.cs | 21 +++++++------ .../Chemistry/Metabolism/DefaultFood.cs | 30 +++++++++---------- .../Metabolism/HealthChangeMetabolism.cs | 28 +++++++++-------- .../Metabolizable/DefaultMetabolizable.cs | 24 +++++++++++---- .../Chemistry/Metabolizable/IMetabolizable.cs | 5 ++-- 7 files changed, 96 insertions(+), 55 deletions(-) diff --git a/Content.Server/Body/Behavior/LiverBehavior.cs b/Content.Server/Body/Behavior/LiverBehavior.cs index 82e12b29ce..95897b320a 100644 --- a/Content.Server/Body/Behavior/LiverBehavior.cs +++ b/Content.Server/Body/Behavior/LiverBehavior.cs @@ -17,6 +17,11 @@ namespace Content.Server.Body.Behavior private float _accumulatedFrameTime; + /// + /// Delay time that determines how often to metabolise blood contents (in seconds). + /// + private float _updateIntervalSeconds = 1.0f; + /// /// Whether the liver is functional. /// @@ -63,13 +68,13 @@ namespace Content.Server.Body.Behavior _accumulatedFrameTime += frameTime; - // Update at most once per second - if (_accumulatedFrameTime < 1) + // Update at most once every _updateIntervalSeconds + if (_accumulatedFrameTime < _updateIntervalSeconds) { return; } - _accumulatedFrameTime -= 1; + _accumulatedFrameTime -= _updateIntervalSeconds; if (!Body.Owner.TryGetComponent(out BloodstreamComponent? bloodstream)) { @@ -90,6 +95,10 @@ namespace Content.Server.Body.Behavior continue; } + // How much reagent is available to metabolise? + // This needs to be passed to other functions that have metabolism rate information, such that they don't "overmetabolise" a reagent. + var availableReagent = bloodstream.Solution.Solution.GetReagentQuantity(reagent.ReagentId); + //TODO BODY Check if it's a Toxin. If volume < _toxinTolerance, just remove it. If greater, add damage = volume * _toxinLethality //TODO BODY Check if it has BoozePower > 0. Affect drunkenness, apply damage. Proposed formula (SS13-derived): damage = sqrt(volume) * BoozePower^_alcoholExponent * _alcoholLethality / 10 //TODO BODY Liver failure. @@ -99,8 +108,9 @@ namespace Content.Server.Body.Behavior // Run metabolism code for each reagent foreach (var metabolizable in prototype.Metabolism) { - var reagentDelta = metabolizable.Metabolize(Body.Owner, reagent.ReagentId, frameTime); + var reagentDelta = metabolizable.Metabolize(Body.Owner, reagent.ReagentId, _updateIntervalSeconds, availableReagent); bloodstream.Solution.TryRemoveReagent(reagent.ReagentId, reagentDelta); + availableReagent -= reagentDelta; } } } diff --git a/Content.Server/Body/Behavior/StomachBehavior.cs b/Content.Server/Body/Behavior/StomachBehavior.cs index a6c903efe9..813b8a3a5e 100644 --- a/Content.Server/Body/Behavior/StomachBehavior.cs +++ b/Content.Server/Body/Behavior/StomachBehavior.cs @@ -30,6 +30,8 @@ namespace Content.Server.Body.Behavior /// public override void Update(float frameTime) { + + // Do not metabolise if the organ does not have a body. if (Body == null) { return; @@ -45,7 +47,9 @@ namespace Content.Server.Body.Behavior _accumulatedFrameTime -= 1; - if (!Body.Owner.TryGetComponent(out SolutionContainerComponent? solution) || + // Note that "Owner" should be the organ that has this behaviour/mechanism, and it should have a dedicated + // solution container. "Body.Owner" is something else, and may have more than one solution container. + if (!Owner.TryGetComponent(out SolutionContainerComponent? solution) || !Body.Owner.TryGetComponent(out BloodstreamComponent? bloodstream)) { return; @@ -61,8 +65,19 @@ namespace Content.Server.Body.Behavior delta.Increment(1); if (delta.Lifetime > _digestionDelay) { - solution.TryRemoveReagent(delta.ReagentId, delta.Quantity); - transferSolution.AddReagent(delta.ReagentId, delta.Quantity); + // This reagent has been in the somach long enough, TRY to transfer it. + // But first, check if the reagent still exists, and how much is left. + // Some poor spessman may have washed down a potassium snack with some water. + if (solution.Solution.ContainsReagent(delta.ReagentId, out ReagentUnit quantity)){ + + if (quantity > delta.Quantity) { + quantity = delta.Quantity; + } + + solution.TryRemoveReagent(delta.ReagentId, quantity); + transferSolution.AddReagent(delta.ReagentId, quantity); + } + _reagentDeltas.Remove(delta); } } @@ -133,10 +148,10 @@ namespace Content.Server.Body.Behavior public bool TryTransferSolution(Solution solution) { - if (Body == null || !CanTransferSolution(solution)) + if (Owner == null || !CanTransferSolution(solution)) return false; - if (!Body.Owner.TryGetComponent(out SolutionContainerComponent? solutionComponent)) + if (!Owner.TryGetComponent(out SolutionContainerComponent? solutionComponent)) { return false; } diff --git a/Content.Server/Chemistry/Metabolism/DefaultDrink.cs b/Content.Server/Chemistry/Metabolism/DefaultDrink.cs index 3b32776c85..255200f103 100644 --- a/Content.Server/Chemistry/Metabolism/DefaultDrink.cs +++ b/Content.Server/Chemistry/Metabolism/DefaultDrink.cs @@ -1,4 +1,4 @@ -using Content.Server.Nutrition.Components; +using Content.Server.Nutrition.Components; using Content.Shared.Chemistry; using Content.Shared.Chemistry.Metabolizable; using Content.Shared.Chemistry.Reagent; @@ -9,28 +9,27 @@ namespace Content.Server.Chemistry.Metabolism { /// /// Default metabolism for drink reagents. Attempts to find a ThirstComponent on the target, - /// and to update it's thirst values. + /// and to update it's thirst values. Inherits metabolisation rate logic from DefaultMetabolizable. /// [DataDefinition] - public class DefaultDrink : IMetabolizable + public class DefaultDrink : DefaultMetabolizable { - //Rate of metabolism in units / second - [DataField("rate")] - public ReagentUnit MetabolismRate { get; set; } = ReagentUnit.New(1); - //How much thirst is satiated when 1u of the reagent is metabolized [DataField("hydrationFactor")] public float HydrationFactor { get; set; } = 30.0f; //Remove reagent at set rate, satiate thirst if a ThirstComponent can be found - ReagentUnit IMetabolizable.Metabolize(IEntity solutionEntity, string reagentId, float tickTime) + public override ReagentUnit Metabolize(IEntity solutionEntity, string reagentId, float tickTime, ReagentUnit availableReagent) { - var metabolismAmount = MetabolismRate * tickTime; + // use DefaultMetabolism to determine how much reagent we should metabolize + var amountMetabolized = base.Metabolize(solutionEntity, reagentId, tickTime, availableReagent); + + // If metabolizing entity has a ThirstComponent, hydrate them. if (solutionEntity.TryGetComponent(out ThirstComponent? thirst)) - thirst.UpdateThirst(metabolismAmount.Float() * HydrationFactor); + thirst.UpdateThirst(amountMetabolized.Float() * HydrationFactor); //Return amount of reagent to be removed, remove reagent regardless of ThirstComponent presence - return metabolismAmount; + return amountMetabolized; } } } diff --git a/Content.Server/Chemistry/Metabolism/DefaultFood.cs b/Content.Server/Chemistry/Metabolism/DefaultFood.cs index aeba8ed95d..fafcf58dd1 100644 --- a/Content.Server/Chemistry/Metabolism/DefaultFood.cs +++ b/Content.Server/Chemistry/Metabolism/DefaultFood.cs @@ -1,4 +1,4 @@ -using Content.Server.Nutrition.Components; +using Content.Server.Nutrition.Components; using Content.Shared.Chemistry; using Content.Shared.Chemistry.Metabolizable; using Content.Shared.Chemistry.Reagent; @@ -9,30 +9,30 @@ namespace Content.Server.Chemistry.Metabolism { /// /// Default metabolism for food reagents. Attempts to find a HungerComponent on the target, - /// and to update it's hunger values. + /// and to update it's hunger values. Inherits metabolisation rate logic from DefaultMetabolizable. /// [DataDefinition] - public class DefaultFood : IMetabolizable + public class DefaultFood : DefaultMetabolizable { - /// - /// Rate of metabolism in units / second - /// - [DataField("rate")] public ReagentUnit MetabolismRate { get; private set; } = ReagentUnit.New(1.0); /// /// How much hunger is satiated when 1u of the reagent is metabolized /// [DataField("nutritionFactor")] public float NutritionFactor { get; set; } = 30.0f; - //Remove reagent at set rate, satiate hunger if a HungerComponent can be found - ReagentUnit IMetabolizable.Metabolize(IEntity solutionEntity, string reagentId, float tickTime) - { - var metabolismAmount = MetabolismRate * tickTime; - if (solutionEntity.TryGetComponent(out HungerComponent? hunger)) - hunger.UpdateFood(metabolismAmount.Float() * NutritionFactor); - //Return amount of reagent to be removed, remove reagent regardless of HungerComponent presence - return metabolismAmount; + //Remove reagent at set rate, satiate hunger if a HungerComponent can be found + public override ReagentUnit Metabolize(IEntity solutionEntity, string reagentId, float tickTime, ReagentUnit availableReagent) + { + // use DefaultMetabolism to determine how much reagent we should metabolize + var amountMetabolized = base.Metabolize(solutionEntity, reagentId, tickTime, availableReagent); + + // If metabolizing entity has a HungerComponent, feed them. + if (solutionEntity.TryGetComponent(out HungerComponent? hunger)) + hunger.UpdateFood(amountMetabolized.Float() * NutritionFactor); + + //Return amount of reagent to be removed. Reagent is removed regardless of HungerComponent presence + return amountMetabolized; } } } diff --git a/Content.Server/Chemistry/Metabolism/HealthChangeMetabolism.cs b/Content.Server/Chemistry/Metabolism/HealthChangeMetabolism.cs index 45bbff68d7..bbf2534505 100644 --- a/Content.Server/Chemistry/Metabolism/HealthChangeMetabolism.cs +++ b/Content.Server/Chemistry/Metabolism/HealthChangeMetabolism.cs @@ -10,16 +10,11 @@ namespace Content.Server.Chemistry.Metabolism { /// /// Default metabolism for medicine reagents. Attempts to find a DamageableComponent on the target, - /// and to update its damage values. + /// and to update its damage values. Inherits metabolisation rate logic from DefaultMetabolizable. /// [DataDefinition] - public class HealthChangeMetabolism : IMetabolizable + public class HealthChangeMetabolism : DefaultMetabolizable { - /// - /// How much of the reagent should be metabolized each sec. - /// - [DataField("rate")] - public ReagentUnit MetabolismRate { get; set; } = ReagentUnit.New(1); /// /// How much damage is changed when 1u of the reagent is metabolized. @@ -41,14 +36,23 @@ namespace Content.Server.Chemistry.Metabolism /// /// /// + /// Reagent available to be metabolized. /// - ReagentUnit IMetabolizable.Metabolize(IEntity solutionEntity, string reagentId, float tickTime) + public override ReagentUnit Metabolize(IEntity solutionEntity, string reagentId, float tickTime, ReagentUnit availableReagent) { + // use DefaultMetabolism to determine how much reagent we should metabolize + var amountMetabolized = base.Metabolize(solutionEntity, reagentId, tickTime, availableReagent); + + // how much does this much reagent heal for + var healthChangeAmount = HealthChange * amountMetabolized.Float(); + if (solutionEntity.TryGetComponent(out IDamageableComponent? health)) { - health.ChangeDamage(DamageType, (int)HealthChange, true); - float decHealthChange = (float) (HealthChange - (int) HealthChange); - _accumulatedHealth += decHealthChange; + // Heal damage by healthChangeAmmount, rounding down to nearest integer + health.ChangeDamage(DamageType, (int) healthChangeAmount, true); + + // Store decimal remainder of healthChangeAmmount in _accumulatedHealth + _accumulatedHealth += (healthChangeAmount - (int) healthChangeAmount); if (_accumulatedHealth >= 1) { @@ -62,7 +66,7 @@ namespace Content.Server.Chemistry.Metabolism _accumulatedHealth += 1; } } - return MetabolismRate; + return amountMetabolized; } } } diff --git a/Content.Shared/Chemistry/Metabolizable/DefaultMetabolizable.cs b/Content.Shared/Chemistry/Metabolizable/DefaultMetabolizable.cs index cbd10016a5..939acdd21a 100644 --- a/Content.Shared/Chemistry/Metabolizable/DefaultMetabolizable.cs +++ b/Content.Shared/Chemistry/Metabolizable/DefaultMetabolizable.cs @@ -1,11 +1,13 @@ -using Content.Shared.Chemistry.Reagent; +using Content.Shared.Chemistry.Reagent; using Robust.Shared.GameObjects; using Robust.Shared.Serialization.Manager.Attributes; namespace Content.Shared.Chemistry.Metabolizable { /// - /// Default metabolism for reagents. Metabolizes the reagent with no effects + /// Default metabolization for reagents. Returns the amount of reagents metabolized without applying effects. + /// Metabolizes reagents at a constant rate, limited by how much is available. Other classes are derived from + /// this class, so that they do not need their own metabolization quantity calculation. /// [DataDefinition] public class DefaultMetabolizable : IMetabolizable @@ -13,12 +15,22 @@ namespace Content.Shared.Chemistry.Metabolizable /// /// Rate of metabolism in units / second /// - [DataField("rate")] - public double MetabolismRate { get; set; } = 1; + [DataField("rate")] public ReagentUnit MetabolismRate { get; set; } = ReagentUnit.New(1); - ReagentUnit IMetabolizable.Metabolize(IEntity solutionEntity, string reagentId, float tickTime) + public virtual ReagentUnit Metabolize(IEntity solutionEntity, string reagentId, float tickTime, ReagentUnit availableReagent) { - return ReagentUnit.New(MetabolismRate * tickTime); + + // How much reagent should we metabolize + // The default behaviour is to metabolize at a constant rate, independent of the quantity of reagents. + var amountMetabolized = MetabolismRate * tickTime; + + // is that much reagent actually available? + if (availableReagent < amountMetabolized) + { + return availableReagent; + } + + return amountMetabolized; } } } diff --git a/Content.Shared/Chemistry/Metabolizable/IMetabolizable.cs b/Content.Shared/Chemistry/Metabolizable/IMetabolizable.cs index e08e209a0c..f8b2a888cc 100644 --- a/Content.Shared/Chemistry/Metabolizable/IMetabolizable.cs +++ b/Content.Shared/Chemistry/Metabolizable/IMetabolizable.cs @@ -1,4 +1,4 @@ -using Content.Shared.Chemistry.Reagent; +using Content.Shared.Chemistry.Reagent; using Robust.Shared.GameObjects; namespace Content.Shared.Chemistry.Metabolizable @@ -16,7 +16,8 @@ namespace Content.Shared.Chemistry.Metabolizable /// The entity containing the solution. /// The reagent id /// The time since the last metabolism tick in seconds. + /// Reagent available to be metabolized. /// The amount of reagent to be removed. The metabolizing organ should handle removing the reagent. - ReagentUnit Metabolize(IEntity solutionEntity, string reagentId, float tickTime); + ReagentUnit Metabolize(IEntity solutionEntity, string reagentId, float tickTime, ReagentUnit availableReagent); } } From f93bdcd226e06daa3ae14d15c4cbb0c5b98316fb Mon Sep 17 00:00:00 2001 From: mirrorcult Date: Sat, 31 Jul 2021 01:59:18 -0700 Subject: [PATCH 38/76] Fix serv warnings (#4400) --- .../Chemistry/ReagentEntityReactions/AddToSolutionReaction.cs | 1 - .../Chemistry/ReagentEntityReactions/ExtinguishReaction.cs | 1 - .../Chemistry/ReagentEntityReactions/FlammableReaction.cs | 1 - .../Chemistry/ReagentEntityReactions/WashCreamPieReaction.cs | 1 - Content.Shared/Chemistry/Reagent/ReagentEntityReaction.cs | 2 +- Content.Shared/Sound/SoundSpecifier.cs | 4 +--- 6 files changed, 2 insertions(+), 8 deletions(-) diff --git a/Content.Server/Chemistry/ReagentEntityReactions/AddToSolutionReaction.cs b/Content.Server/Chemistry/ReagentEntityReactions/AddToSolutionReaction.cs index 3218e4f983..f509fa0d56 100644 --- a/Content.Server/Chemistry/ReagentEntityReactions/AddToSolutionReaction.cs +++ b/Content.Server/Chemistry/ReagentEntityReactions/AddToSolutionReaction.cs @@ -10,7 +10,6 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototy namespace Content.Server.Chemistry.ReagentEntityReactions { [UsedImplicitly] - [DataDefinition] public class AddToSolutionReaction : ReagentEntityReaction { [DataField("reagents", true, customTypeSerializer:typeof(PrototypeIdHashSetSerializer))] diff --git a/Content.Server/Chemistry/ReagentEntityReactions/ExtinguishReaction.cs b/Content.Server/Chemistry/ReagentEntityReactions/ExtinguishReaction.cs index 9078eef2d1..860df2c423 100644 --- a/Content.Server/Chemistry/ReagentEntityReactions/ExtinguishReaction.cs +++ b/Content.Server/Chemistry/ReagentEntityReactions/ExtinguishReaction.cs @@ -10,7 +10,6 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototy namespace Content.Server.Chemistry.ReagentEntityReactions { [UsedImplicitly] - [DataDefinition] public class ExtinguishReaction : ReagentEntityReaction { [DataField("reagents", true, customTypeSerializer:typeof(PrototypeIdHashSetSerializer))] diff --git a/Content.Server/Chemistry/ReagentEntityReactions/FlammableReaction.cs b/Content.Server/Chemistry/ReagentEntityReactions/FlammableReaction.cs index 5242565e0e..8c71a8904d 100644 --- a/Content.Server/Chemistry/ReagentEntityReactions/FlammableReaction.cs +++ b/Content.Server/Chemistry/ReagentEntityReactions/FlammableReaction.cs @@ -10,7 +10,6 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototy namespace Content.Server.Chemistry.ReagentEntityReactions { [UsedImplicitly] - [DataDefinition] public class FlammableReaction : ReagentEntityReaction { [DataField("reagents", true, customTypeSerializer:typeof(PrototypeIdHashSetSerializer))] diff --git a/Content.Server/Chemistry/ReagentEntityReactions/WashCreamPieReaction.cs b/Content.Server/Chemistry/ReagentEntityReactions/WashCreamPieReaction.cs index 44774ffcc9..8005384562 100644 --- a/Content.Server/Chemistry/ReagentEntityReactions/WashCreamPieReaction.cs +++ b/Content.Server/Chemistry/ReagentEntityReactions/WashCreamPieReaction.cs @@ -10,7 +10,6 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototy namespace Content.Server.Chemistry.ReagentEntityReactions { [UsedImplicitly] - [DataDefinition] public class WashCreamPieReaction : ReagentEntityReaction { [DataField("reagents", true, customTypeSerializer:typeof(PrototypeIdHashSetSerializer))] diff --git a/Content.Shared/Chemistry/Reagent/ReagentEntityReaction.cs b/Content.Shared/Chemistry/Reagent/ReagentEntityReaction.cs index 59f931e871..fe8e081e25 100644 --- a/Content.Shared/Chemistry/Reagent/ReagentEntityReaction.cs +++ b/Content.Shared/Chemistry/Reagent/ReagentEntityReaction.cs @@ -12,7 +12,7 @@ namespace Content.Shared.Chemistry.Reagent Ingestion, } - [DataDefinition] + [ImplicitDataDefinitionForInheritors] public abstract class ReagentEntityReaction { [ViewVariables] diff --git a/Content.Shared/Sound/SoundSpecifier.cs b/Content.Shared/Sound/SoundSpecifier.cs index 616ccf9551..bb74337e8a 100644 --- a/Content.Shared/Sound/SoundSpecifier.cs +++ b/Content.Shared/Sound/SoundSpecifier.cs @@ -8,13 +8,12 @@ using Robust.Shared.Utility; namespace Content.Shared.Sound { - [DataDefinition] + [ImplicitDataDefinitionForInheritors] public abstract class SoundSpecifier { public abstract string GetSound(); } - [DataDefinition] public sealed class SoundPathSpecifier : SoundSpecifier { public const string Node = "path"; @@ -42,7 +41,6 @@ namespace Content.Shared.Sound } } - [DataDefinition] public sealed class SoundCollectionSpecifier : SoundSpecifier { public const string Node = "collection"; From dc18997bf8c859ce3cd59ffde718a768f5c8b967 Mon Sep 17 00:00:00 2001 From: Vera Aguilera Puerto <6766154+Zumorica@users.noreply.github.com> Date: Sat, 31 Jul 2021 12:41:59 +0200 Subject: [PATCH 39/76] Removes LoopingSoundComponent. (#4396) I'm sorry. --- Content.Client/Entry/EntryPoint.cs | 1 - .../{ => Components}/KitchenSpikeComponent.cs | 2 +- .../Kitchen/Components/MicrowaveComponent.cs | 17 +++ .../Kitchen/EntitySystems/MicrowaveSystem.cs | 31 +++++ .../Visualizers/MicrowaveVisualizer.cs | 24 ++-- .../Components/ExpendableLightComponent.cs | 3 +- .../Visualizers/ExpendableLightVisualizer.cs | 36 +++++- Content.Client/Sound/LoopingSoundComponent.cs | 105 ---------------- .../Components/Mobs/ActionsComponentTests.cs | 1 - .../EntitySystems/ReagentGrinderSystem.cs | 3 + .../Components/ExpendableLightComponent.cs | 21 +--- .../LoopingLoopingSoundComponent.cs | 67 ---------- .../SharedExpendableLightComponent.cs | 12 +- .../Sound/SharedLoopingSoundComponent.cs | 116 ------------------ .../Entities/Clothing/Head/base.yml | 1 - .../Entities/Clothing/Head/hardhats.yml | 1 - .../Entities/Clothing/Shoes/specific.yml | 1 - .../Prototypes/Entities/Effects/puddle.yml | 1 - .../Prototypes/Entities/Mobs/NPCs/animals.yml | 1 - .../Objects/Consumable/Food/Baked/bread.yml | 1 - .../Objects/Consumable/Food/Baked/cake.yml | 1 - .../Objects/Consumable/Food/Baked/donut.yml | 1 - .../Objects/Consumable/Food/Baked/misc.yml | 1 - .../Objects/Consumable/Food/Baked/pizza.yml | 1 - .../Consumable/Food/Containers/box.yml | 1 - .../Consumable/Food/Containers/tin.yml | 4 - .../Objects/Consumable/Food/burger.yml | 1 - .../Entities/Objects/Consumable/Food/egg.yml | 1 - .../Objects/Consumable/Food/frozen.yml | 1 - .../Entities/Objects/Consumable/Food/meat.yml | 1 - .../Objects/Consumable/Food/noodles.yml | 1 - .../Objects/Consumable/Food/skewer.yml | 1 - .../Objects/Consumable/Food/snacks.yml | 1 - .../Entities/Objects/Consumable/Food/soup.yml | 1 - .../Objects/Consumable/trash_drinks.yml | 1 - .../Entities/Objects/Devices/pda.yml | 1 - .../Prototypes/Entities/Objects/Fun/skub.yml | 1 - .../Prototypes/Entities/Objects/Fun/toys.yml | 22 +--- .../Entities/Objects/Misc/torch.yml | 1 - .../Entities/Objects/Tools/flare.yml | 1 - .../Entities/Objects/Tools/flashlight.yml | 1 - .../Entities/Objects/Tools/lantern.yml | 2 - .../Objects/Weapons/Melee/pickaxe.yml | 1 - .../Entities/Structures/Dispensers/base.yml | 1 - .../Structures/Machines/microwave.yml | 1 - .../Structures/Machines/reagent_grinder.yml | 1 - .../Structures/Storage/Closets/base.yml | 1 - .../Structures/Storage/Crates/base.yml | 1 - .../Entities/Structures/Storage/morgue.yml | 1 - .../Structures/Wallmounts/lighting.yml | 1 - 50 files changed, 120 insertions(+), 380 deletions(-) rename Content.Client/Kitchen/{ => Components}/KitchenSpikeComponent.cs (88%) create mode 100644 Content.Client/Kitchen/Components/MicrowaveComponent.cs create mode 100644 Content.Client/Kitchen/EntitySystems/MicrowaveSystem.cs delete mode 100644 Content.Client/Sound/LoopingSoundComponent.cs delete mode 100644 Content.Server/Sound/Components/LoopingLoopingSoundComponent.cs delete mode 100644 Content.Shared/Sound/SharedLoopingSoundComponent.cs diff --git a/Content.Client/Entry/EntryPoint.cs b/Content.Client/Entry/EntryPoint.cs index b975c28673..95a7ec0596 100644 --- a/Content.Client/Entry/EntryPoint.cs +++ b/Content.Client/Entry/EntryPoint.cs @@ -81,7 +81,6 @@ namespace Content.Client.Entry factory.RegisterClass(); factory.RegisterClass(); factory.RegisterClass(); - factory.RegisterClass(); factory.RegisterClass(); factory.RegisterClass(); diff --git a/Content.Client/Kitchen/KitchenSpikeComponent.cs b/Content.Client/Kitchen/Components/KitchenSpikeComponent.cs similarity index 88% rename from Content.Client/Kitchen/KitchenSpikeComponent.cs rename to Content.Client/Kitchen/Components/KitchenSpikeComponent.cs index efae8b89e6..58f271d63f 100644 --- a/Content.Client/Kitchen/KitchenSpikeComponent.cs +++ b/Content.Client/Kitchen/Components/KitchenSpikeComponent.cs @@ -2,7 +2,7 @@ using Content.Shared.DragDrop; using Content.Shared.Kitchen.Components; using Robust.Shared.GameObjects; -namespace Content.Client.Kitchen +namespace Content.Client.Kitchen.Components { [RegisterComponent] internal sealed class KitchenSpikeComponent : SharedKitchenSpikeComponent diff --git a/Content.Client/Kitchen/Components/MicrowaveComponent.cs b/Content.Client/Kitchen/Components/MicrowaveComponent.cs new file mode 100644 index 0000000000..862a8b6de0 --- /dev/null +++ b/Content.Client/Kitchen/Components/MicrowaveComponent.cs @@ -0,0 +1,17 @@ +using Content.Shared.Kitchen.Components; +using Content.Shared.Sound; +using Robust.Shared.Audio; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization.Manager.Attributes; + +namespace Content.Client.Kitchen.Components +{ + [RegisterComponent] + public class MicrowaveComponent : SharedMicrowaveComponent + { + public IPlayingAudioStream? PlayingStream { get; set; } + + [DataField("loopingSound")] + public SoundSpecifier LoopingSound = new SoundPathSpecifier("/Audio/Machines/microwave_loop.ogg"); + } +} diff --git a/Content.Client/Kitchen/EntitySystems/MicrowaveSystem.cs b/Content.Client/Kitchen/EntitySystems/MicrowaveSystem.cs new file mode 100644 index 0000000000..6a18a0feac --- /dev/null +++ b/Content.Client/Kitchen/EntitySystems/MicrowaveSystem.cs @@ -0,0 +1,31 @@ +using System; +using Content.Client.Kitchen.Components; +using Robust.Shared.Audio; +using Robust.Shared.GameObjects; +using Robust.Shared.Player; + +namespace Content.Client.Kitchen.EntitySystems +{ + public class MicrowaveSystem : EntitySystem + { + public void StartSoundLoop(MicrowaveComponent microwave) + { + StopSoundLoop(microwave); + + microwave.PlayingStream = SoundSystem.Play(Filter.Local(), microwave.LoopingSound.GetSound(), microwave.Owner, + AudioParams.Default.WithAttenuation(1).WithMaxDistance(5).WithLoop(true)); + } + + public void StopSoundLoop(MicrowaveComponent microwave) + { + try + { + microwave.PlayingStream?.Stop(); + } + catch (Exception _) + { + // TODO: HOLY SHIT EXPOSE SOME DISPOSED PROPERTY ON PLAYING STREAM OR SOMETHING. + } + } + } +} diff --git a/Content.Client/Kitchen/Visualizers/MicrowaveVisualizer.cs b/Content.Client/Kitchen/Visualizers/MicrowaveVisualizer.cs index 97d2bf0e7c..c0a3a68e8c 100644 --- a/Content.Client/Kitchen/Visualizers/MicrowaveVisualizer.cs +++ b/Content.Client/Kitchen/Visualizers/MicrowaveVisualizer.cs @@ -1,10 +1,10 @@ -using Content.Client.Sound; +using Content.Client.Kitchen.Components; +using Content.Client.Kitchen.EntitySystems; using Content.Shared.Kitchen.Components; using Content.Shared.Power; -using Content.Shared.Sound; using JetBrains.Annotations; using Robust.Client.GameObjects; -using Robust.Shared.Audio; +using Robust.Shared.GameObjects; using Robust.Shared.Log; namespace Content.Client.Kitchen.Visualizers @@ -17,35 +17,33 @@ namespace Content.Client.Kitchen.Visualizers base.OnChangeData(component); var sprite = component.Owner.GetComponent(); - var loopingSoundComponent = component.Owner.GetComponentOrNull(); + var microwaveComponent = component.Owner.GetComponentOrNull(); if (!component.TryGetData(PowerDeviceVisuals.VisualState, out MicrowaveVisualState state)) { state = MicrowaveVisualState.Idle; } + // The only reason we get the entity system so late is so that tests don't fail... Amazing, huh? switch (state) { case MicrowaveVisualState.Broken: sprite.LayerSetState(MicrowaveVisualizerLayers.BaseUnlit, "mwb"); - loopingSoundComponent?.StopAllSounds(); + if(microwaveComponent != null) + EntitySystem.Get().StopSoundLoop(microwaveComponent); break; case MicrowaveVisualState.Idle: sprite.LayerSetState(MicrowaveVisualizerLayers.Base, "mw"); sprite.LayerSetState(MicrowaveVisualizerLayers.BaseUnlit, "mw_unlit"); - loopingSoundComponent?.StopAllSounds(); + if(microwaveComponent != null) + EntitySystem.Get().StopSoundLoop(microwaveComponent); break; case MicrowaveVisualState.Cooking: sprite.LayerSetState(MicrowaveVisualizerLayers.Base, "mw"); sprite.LayerSetState(MicrowaveVisualizerLayers.BaseUnlit, "mw_running_unlit"); - var audioParams = AudioParams.Default; - audioParams.Loop = true; - var scheduledSound = new ScheduledSound(); - scheduledSound.Filename = "/Audio/Machines/microwave_loop.ogg"; - scheduledSound.AudioParams = audioParams; - loopingSoundComponent?.StopAllSounds(); - loopingSoundComponent?.AddScheduledSound(scheduledSound); + if(microwaveComponent != null) + EntitySystem.Get().StartSoundLoop(microwaveComponent); break; default: diff --git a/Content.Client/Light/Components/ExpendableLightComponent.cs b/Content.Client/Light/Components/ExpendableLightComponent.cs index 5531379084..37eb37ee13 100644 --- a/Content.Client/Light/Components/ExpendableLightComponent.cs +++ b/Content.Client/Light/Components/ExpendableLightComponent.cs @@ -1,4 +1,5 @@ using Content.Shared.Light.Component; +using Robust.Shared.Audio; using Robust.Shared.GameObjects; namespace Content.Client.Light.Components @@ -9,6 +10,6 @@ namespace Content.Client.Light.Components [RegisterComponent] public class ExpendableLightComponent : SharedExpendableLightComponent { - + public IPlayingAudioStream? PlayingStream { get; set; } } } diff --git a/Content.Client/Light/Visualizers/ExpendableLightVisualizer.cs b/Content.Client/Light/Visualizers/ExpendableLightVisualizer.cs index 3a21d11b1a..59e11983e8 100644 --- a/Content.Client/Light/Visualizers/ExpendableLightVisualizer.cs +++ b/Content.Client/Light/Visualizers/ExpendableLightVisualizer.cs @@ -1,7 +1,10 @@ -using Content.Client.Light.Components; +using System; +using Content.Client.Light.Components; using Content.Shared.Light.Component; using JetBrains.Annotations; using Robust.Client.GameObjects; +using Robust.Shared.Audio; +using Robust.Shared.Player; namespace Content.Client.Light.Visualizers { @@ -17,7 +20,7 @@ namespace Content.Client.Light.Visualizers return; } - if (component.TryGetData(ExpendableLightVisuals.State, out string lightBehaviourID)) + if (component.TryGetData(ExpendableLightVisuals.Behavior, out string lightBehaviourID)) { if (component.Owner.TryGetComponent(out var lightBehaviour)) { @@ -33,6 +36,35 @@ namespace Content.Client.Light.Visualizers } } } + + void TryStopStream(IPlayingAudioStream? stream) + { + try + { + stream?.Stop(); + } + catch (Exception _) + { + // TODO: HOLY SHIT EXPOSE SOME DISPOSED PROPERTY ON PLAYING STREAM OR SOMETHING. + } + } + + if (component.TryGetData(ExpendableLightVisuals.State, out ExpendableLightState state) + && component.Owner.TryGetComponent(out var expendableLight)) + { + switch (state) + { + case ExpendableLightState.Lit: + TryStopStream(expendableLight.PlayingStream); + expendableLight.PlayingStream = SoundSystem.Play(Filter.Local(), expendableLight.LoopedSound, + expendableLight.Owner, SharedExpendableLightComponent.LoopedSoundParams.WithLoop(true)); + break; + + case ExpendableLightState.Dead: + TryStopStream(expendableLight.PlayingStream); + break; + } + } } } } diff --git a/Content.Client/Sound/LoopingSoundComponent.cs b/Content.Client/Sound/LoopingSoundComponent.cs deleted file mode 100644 index cd2d2e2812..0000000000 --- a/Content.Client/Sound/LoopingSoundComponent.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System.Collections.Generic; -using Content.Shared.Physics; -using Content.Shared.Sound; -using Robust.Shared.Audio; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Network; -using Robust.Shared.Player; -using Robust.Shared.Players; -using Robust.Shared.Random; -using Robust.Shared.Serialization.Manager.Attributes; - -namespace Content.Client.Sound -{ - [RegisterComponent] - public class LoopingSoundComponent : SharedLoopingSoundComponent - { - [Dependency] private readonly IRobustRandom _random = default!; - - private readonly Dictionary _audioStreams = new(); - - [DataField("schedules", true)] - private List _scheduledSounds - { - set => value.ForEach(AddScheduledSound); - get => new(); - } - - public override void StopAllSounds() - { - foreach (var kvp in _audioStreams) - { - kvp.Key.Play = false; - kvp.Value.Stop(); - } - _audioStreams.Clear(); - } - - public override void StopScheduledSound(string filename) - { - foreach (var kvp in _audioStreams) - { - if (kvp.Key.Filename != filename) continue; - kvp.Key.Play = false; - kvp.Value.Stop(); - _audioStreams.Remove(kvp.Key); - } - } - - public override void AddScheduledSound(ScheduledSound schedule) - { - Play(schedule); - } - - public void Play(ScheduledSound schedule) - { - if (!schedule.Play) return; - - Owner.SpawnTimer((int) schedule.Delay + (_random.Next((int) schedule.RandomDelay)),() => - { - if (!schedule.Play) return; // We make sure this hasn't changed. - - if (!_audioStreams.ContainsKey(schedule)) - { - _audioStreams.Add(schedule, SoundSystem.Play(Filter.Local(), schedule.Filename, Owner, schedule.AudioParams)!); - } - else - { - _audioStreams[schedule] = SoundSystem.Play(Filter.Local(), schedule.Filename, Owner, schedule.AudioParams)!; - } - - if (schedule.Times == 0) return; - - if (schedule.Times > 0) schedule.Times--; - - Play(schedule); - }); - } - - public override void HandleNetworkMessage(ComponentMessage message, INetChannel channel, ICommonSession? session = null) - { - base.HandleNetworkMessage(message, channel, session); - switch (message) - { - case ScheduledSoundMessage msg: - AddScheduledSound(msg.Schedule); - break; - - case StopSoundScheduleMessage msg: - StopScheduledSound(msg.Filename); - break; - - case StopAllSoundsMessage _: - StopAllSounds(); - break; - } - } - - protected override void Initialize() - { - base.Initialize(); - SoundSystem.OcclusionCollisionMask = (int) CollisionGroup.Impassable; - } - } -} diff --git a/Content.IntegrationTests/Tests/GameObjects/Components/Mobs/ActionsComponentTests.cs b/Content.IntegrationTests/Tests/GameObjects/Components/Mobs/ActionsComponentTests.cs index 497e735a82..82bdf16910 100644 --- a/Content.IntegrationTests/Tests/GameObjects/Components/Mobs/ActionsComponentTests.cs +++ b/Content.IntegrationTests/Tests/GameObjects/Components/Mobs/ActionsComponentTests.cs @@ -52,7 +52,6 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.Mobs - type: PointLight enabled: false radius: 3 - - type: LoopingSound - type: Appearance visuals: - type: FlashLightVisualizer diff --git a/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs b/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs index c58c5b32ee..2b110578c1 100644 --- a/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs +++ b/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs @@ -195,6 +195,9 @@ namespace Content.Server.Kitchen.EntitySystems while (_uiUpdateQueue.TryDequeue(out var comp)) { + if(comp.Deleted) + continue; + bool canJuice = false; bool canGrind = false; if (comp.BeakerContainer.ContainedEntity != null) diff --git a/Content.Server/Light/Components/ExpendableLightComponent.cs b/Content.Server/Light/Components/ExpendableLightComponent.cs index ef74376c01..180e791009 100644 --- a/Content.Server/Light/Components/ExpendableLightComponent.cs +++ b/Content.Server/Light/Components/ExpendableLightComponent.cs @@ -20,8 +20,6 @@ namespace Content.Server.Light.Components [RegisterComponent] public class ExpendableLightComponent : SharedExpendableLightComponent, IUse { - private static readonly AudioParams LoopedSoundParams = new(0, 1, "Master", 62.5f, 1, true, 0.3f); - /// /// Status of light, whether or not it is emitting light. /// @@ -77,18 +75,20 @@ namespace Content.Server.Light.Components private void UpdateVisualizer() { + _appearance?.SetData(ExpendableLightVisuals.State, CurrentState); + switch (CurrentState) { case ExpendableLightState.Lit: - _appearance?.SetData(ExpendableLightVisuals.State, TurnOnBehaviourID); + _appearance?.SetData(ExpendableLightVisuals.Behavior, TurnOnBehaviourID); break; case ExpendableLightState.Fading: - _appearance?.SetData(ExpendableLightVisuals.State, FadeOutBehaviourID); + _appearance?.SetData(ExpendableLightVisuals.Behavior, FadeOutBehaviourID); break; case ExpendableLightState.Dead: - _appearance?.SetData(ExpendableLightVisuals.State, string.Empty); + _appearance?.SetData(ExpendableLightVisuals.Behavior, string.Empty); break; } } @@ -100,12 +100,6 @@ namespace Content.Server.Light.Components switch (CurrentState) { case ExpendableLightState.Lit: - - if (LoopedSound != string.Empty && Owner.TryGetComponent(out var loopingSound)) - { - loopingSound.Play(LoopedSound, LoopedSoundParams); - } - if (LitSound != string.Empty) { SoundSystem.Play(Filter.Pvs(Owner), LitSound, Owner); @@ -131,11 +125,6 @@ namespace Content.Server.Light.Components SoundSystem.Play(Filter.Pvs(Owner), DieSound, Owner); } - if (LoopedSound != string.Empty && Owner.TryGetComponent(out var loopSound)) - { - loopSound.StopAllSounds(); - } - sprite.LayerSetState(0, IconStateSpent); sprite.LayerSetShader(0, "shaded"); sprite.LayerSetVisible(1, false); diff --git a/Content.Server/Sound/Components/LoopingLoopingSoundComponent.cs b/Content.Server/Sound/Components/LoopingLoopingSoundComponent.cs deleted file mode 100644 index 4230e39d8e..0000000000 --- a/Content.Server/Sound/Components/LoopingLoopingSoundComponent.cs +++ /dev/null @@ -1,67 +0,0 @@ -using Content.Shared.Sound; -using Robust.Shared.Audio; -using Robust.Shared.GameObjects; -using Robust.Shared.Network; - -namespace Content.Server.Sound.Components -{ - [RegisterComponent] - public class LoopingLoopingSoundComponent : SharedLoopingSoundComponent - { - /// - /// Stops all sounds. - /// - /// User that will be affected. - public void StopAllSounds(INetChannel? channel) - { - SendNetworkMessage(new StopAllSoundsMessage(), channel); - } - - /// - /// Stops a certain scheduled sound from playing. - /// - /// User that will be affected. - public void StopScheduledSound(string filename, INetChannel? channel) - { - SendNetworkMessage(new StopSoundScheduleMessage() {Filename = filename}, channel); - } - - /// - /// Adds an scheduled sound to be played. - /// - /// User that will be affected. - public void AddScheduledSound(ScheduledSound schedule, INetChannel? channel) - { - SendNetworkMessage(new ScheduledSoundMessage() {Schedule = schedule}, channel); - } - - public override void StopAllSounds() - { - StopAllSounds(null); - } - - public override void StopScheduledSound(string filename) - { - StopScheduledSound(filename, null); - } - - public override void AddScheduledSound(ScheduledSound schedule) - { - AddScheduledSound(schedule, null); - } - - /// - /// Play an audio file following the entity. - /// - /// The resource path to the OGG Vorbis file to play. - /// User that will be affected. - public void Play(string filename, AudioParams? audioParams = null, INetChannel? channel = null) - { - AddScheduledSound(new ScheduledSound() - { - Filename = filename, - AudioParams = audioParams, - }, channel); - } - } -} diff --git a/Content.Shared/Light/Component/SharedExpendableLightComponent.cs b/Content.Shared/Light/Component/SharedExpendableLightComponent.cs index f6f057ea23..9c0e2960b2 100644 --- a/Content.Shared/Light/Component/SharedExpendableLightComponent.cs +++ b/Content.Shared/Light/Component/SharedExpendableLightComponent.cs @@ -1,4 +1,8 @@ using System; +using Robust.Shared.Audio; +using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; +using Robust.Shared.Players; using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.ViewVariables; @@ -8,7 +12,8 @@ namespace Content.Shared.Light.Component [Serializable, NetSerializable] public enum ExpendableLightVisuals { - State + State, + Behavior } [Serializable, NetSerializable] @@ -20,8 +25,11 @@ namespace Content.Shared.Light.Component Dead } + [NetworkedComponent] public abstract class SharedExpendableLightComponent: Robust.Shared.GameObjects.Component { + public static readonly AudioParams LoopedSoundParams = new(0, 1, "Master", 62.5f, 1, true, 0.3f); + public sealed override string Name => "ExpendableLight"; [ViewVariables(VVAccess.ReadOnly)] @@ -65,7 +73,7 @@ namespace Content.Shared.Light.Component [ViewVariables] [DataField("loopedSound")] - protected string LoopedSound { get; set; } = string.Empty; + public string LoopedSound { get; set; } = string.Empty; [ViewVariables] [DataField("dieSound")] diff --git a/Content.Shared/Sound/SharedLoopingSoundComponent.cs b/Content.Shared/Sound/SharedLoopingSoundComponent.cs deleted file mode 100644 index b31234d03e..0000000000 --- a/Content.Shared/Sound/SharedLoopingSoundComponent.cs +++ /dev/null @@ -1,116 +0,0 @@ -using System; -using Robust.Shared.Audio; -using Robust.Shared.GameObjects; -using Robust.Shared.GameStates; -using Robust.Shared.Serialization; -using Robust.Shared.Serialization.Manager.Attributes; - -namespace Content.Shared.Sound -{ - [NetworkedComponent()] - public class SharedLoopingSoundComponent : Component - { - public override string Name => "LoopingSound"; - - /// - /// Stops all sounds. - /// - public virtual void StopAllSounds() - {} - - /// - /// Stops a certain scheduled sound from playing. - /// - public virtual void StopScheduledSound(string filename) - {} - - /// - /// Adds an scheduled sound to be played. - /// - public virtual void AddScheduledSound(ScheduledSound scheduledSound) - {} - - /// - /// Play an audio file following the entity. - /// - /// The resource path to the OGG Vorbis file to play. - public void Play(string filename, AudioParams? audioParams = null) - { - AddScheduledSound(new ScheduledSound() - { - Filename = filename, - AudioParams = audioParams, - }); - } - } - - [NetSerializable, Serializable] - public class ScheduledSoundMessage : ComponentMessage - { - public ScheduledSound Schedule = new(); - public ScheduledSoundMessage() - { - Directed = true; - } - } - - [NetSerializable, Serializable] - public class StopSoundScheduleMessage : ComponentMessage - { - public string Filename = string.Empty; - public StopSoundScheduleMessage() - { - Directed = true; - } - } - - [NetSerializable, Serializable] - public class StopAllSoundsMessage : ComponentMessage - { - public StopAllSoundsMessage() - { - Directed = true; - } - } - - [Serializable, NetSerializable] - [DataDefinition] - public class ScheduledSound - { - [DataField("fileName")] - public string Filename = string.Empty; - - /// - /// The parameters to play the sound with. - /// - [DataField("audioparams")] - public AudioParams? AudioParams; - - /// - /// Delay in milliseconds before playing the sound, - /// and delay between repetitions if Times is not 0. - /// - [DataField("delay")] - public uint Delay; - - /// - /// Maximum number of milliseconds to add to the delay randomly. - /// Useful for random ambience noises. Generated value differs from client to client. - /// - [DataField("randomdelay")] - public uint RandomDelay; - - /// - /// How many times to repeat the sound. If it's 0, it will play the sound once. - /// If it's less than 0, it will repeat the sound indefinitely. - /// If it's greater than 0, it will play the sound n+1 times. - /// - [DataField("times")] - public int Times; - - /// - /// Whether the sound will play or not. - /// - public bool Play = true; - } -} diff --git a/Resources/Prototypes/Entities/Clothing/Head/base.yml b/Resources/Prototypes/Entities/Clothing/Head/base.yml index c81acedf5c..ea2aa62164 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/base.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/base.yml @@ -47,7 +47,6 @@ - type: PointLight enabled: false radius: 3 - - type: LoopingSound - type: Appearance visuals: - type: FlashLightVisualizer diff --git a/Resources/Prototypes/Entities/Clothing/Head/hardhats.yml b/Resources/Prototypes/Entities/Clothing/Head/hardhats.yml index e8e403ae9b..ab1d35e31f 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/hardhats.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/hardhats.yml @@ -15,7 +15,6 @@ - type: PointLight enabled: false radius: 3 - - type: LoopingSound - type: Appearance visuals: - type: FlashLightVisualizer diff --git a/Resources/Prototypes/Entities/Clothing/Shoes/specific.yml b/Resources/Prototypes/Entities/Clothing/Shoes/specific.yml index d9481657b5..8863546e1c 100644 --- a/Resources/Prototypes/Entities/Clothing/Shoes/specific.yml +++ b/Resources/Prototypes/Entities/Clothing/Shoes/specific.yml @@ -19,7 +19,6 @@ sprite: Clothing/Shoes/Specific/clown.rsi - type: Clothing sprite: Clothing/Shoes/Specific/clown.rsi - - type: LoopingSound - type: FootstepModifier footstepSoundCollection: footstep_clown diff --git a/Resources/Prototypes/Entities/Effects/puddle.yml b/Resources/Prototypes/Entities/Effects/puddle.yml index 300ccbcf6e..6e018f8f0b 100644 --- a/Resources/Prototypes/Entities/Effects/puddle.yml +++ b/Resources/Prototypes/Entities/Effects/puddle.yml @@ -10,7 +10,6 @@ - type: Puddle spill_sound: /Audio/Effects/Fluids/splat.ogg recolor: true - - type: LoopingSound - type: Clickable - type: Slippery - type: Physics diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index 4ef1d7ba87..45b1b8e193 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -387,7 +387,6 @@ # Eek! You can eat them alive for now until someone makes something that detects when # a mob is dead or something idk - type: Food - - type: LoopingSound - type: SolutionContainer contents: reagents: diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/bread.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/bread.yml index 5154e389c5..5e08078666 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/bread.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/bread.yml @@ -9,7 +9,6 @@ - type: Sprite sprite: Objects/Consumable/Food/Baked/bread.rsi netsync: false - - type: LoopingSound - type: SolutionContainer maxVol: 20 contents: diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/cake.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/cake.yml index 5f792c0c8e..0ecb6b5fa6 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/cake.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/cake.yml @@ -10,7 +10,6 @@ - type: Sprite sprite: Objects/Consumable/Food/Baked/cake.rsi netsync: false - - type: LoopingSound - type: SolutionContainer maxVol: 20 contents: diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/donut.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/donut.yml index 25b22164db..5f71cd9431 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/donut.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/donut.yml @@ -13,7 +13,6 @@ - type: Sprite sprite: Objects/Consumable/Food/Baked/donut.rsi netsync: false - - type: LoopingSound - type: SolutionContainer maxVol: 5 contents: diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/misc.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/misc.yml index d697dd849f..416fcd82ff 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/misc.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/misc.yml @@ -9,7 +9,6 @@ - type: Sprite sprite: Objects/Consumable/Food/Baked/misc.rsi netsync: false - - type: LoopingSound - type: SolutionContainer maxVol: 5 contents: diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pizza.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pizza.yml index c5d94a8ded..2a5c2fa711 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pizza.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pizza.yml @@ -10,7 +10,6 @@ - type: Sprite sprite: Objects/Consumable/Food/Baked/pizza.rsi netsync: false - - type: LoopingSound - type: SolutionContainer maxVol: 15 contents: diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/box.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/box.yml index da91d4b718..b80ef6f762 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/box.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/box.yml @@ -92,7 +92,6 @@ - box11 - box12 # Someday... - # - type: LoopingSound # - type: DamageOnLand # amount: 5 # - type: DamageOtherOnHit diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/tin.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/tin.yml index f37b3c2670..fa7bc163fa 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/tin.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/tin.yml @@ -78,7 +78,6 @@ reagents: - ReagentId: Nutriment Quantity: 15 - - type: LoopingSound - type: Food trash: FoodTinPeachesTrash @@ -127,7 +126,6 @@ reagents: - ReagentId: Nutriment Quantity: 15 - - type: LoopingSound - type: Food trash: FoodTinPeachesMaintTrash @@ -176,7 +174,6 @@ reagents: - ReagentId: Nutriment Quantity: 15 - - type: LoopingSound - type: Food trash: FoodTinBeansTrash @@ -227,7 +224,6 @@ reagents: - ReagentId: Nutriment Quantity: 15 - - type: LoopingSound - type: Food trash: FoodTinMRETrash diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/burger.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/burger.yml index 956cbabd68..7f4dfafec8 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/burger.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/burger.yml @@ -10,7 +10,6 @@ - type: Sprite sprite: Objects/Consumable/Food/burger.rsi netsync: false - - type: LoopingSound - type: SolutionContainer maxVol: 15 contents: diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/egg.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/egg.yml index 886827dcd8..06c818d1b9 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/egg.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/egg.yml @@ -23,7 +23,6 @@ reagents: - ReagentId: Egg Quantity: 5 - - type: LoopingSound - type: DamageOnLand amount: 1 - type: DamageOtherOnHit diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/frozen.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/frozen.yml index 86d65c8845..745d0ec100 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/frozen.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/frozen.yml @@ -9,7 +9,6 @@ - type: Sprite sprite: Objects/Consumable/Food/frozen.rsi netsync: false - - type: LoopingSound - type: SolutionContainer maxVol: 20 # For sprinkles or something? Idk. contents: diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/meat.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/meat.yml index c04c0e016d..fef5800303 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/meat.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/meat.yml @@ -9,7 +9,6 @@ - type: Sprite sprite: Objects/Consumable/Food/meat.rsi netsync: false - - type: LoopingSound - type: SolutionContainer maxVol: 20 contents: diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/noodles.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/noodles.yml index 5c0f7ebce4..02ee16cf0c 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/noodles.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/noodles.yml @@ -11,7 +11,6 @@ - type: Sprite sprite: Objects/Consumable/Food/noodles.rsi netsync: false - - type: LoopingSound - type: SolutionContainer maxVol: 20 contents: diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/skewer.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/skewer.yml index 704e8a7376..3de439686d 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/skewer.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/skewer.yml @@ -10,7 +10,6 @@ - type: Sprite sprite: Objects/Consumable/Food/skewer.rsi netsync: false - - type: LoopingSound - type: SolutionContainer maxVol: 20 contents: diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/snacks.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/snacks.yml index b3fe15a37c..2fcb644269 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/snacks.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/snacks.yml @@ -9,7 +9,6 @@ - type: Sprite sprite: Objects/Consumable/Food/snacks.rsi netsync: false - - type: LoopingSound - type: SolutionContainer maxVol: 30 # Room for extra condiments contents: diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/soup.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/soup.yml index 95d8810b6b..d7a7ab1de6 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/soup.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/soup.yml @@ -5,7 +5,6 @@ components: - type: Food trash: FoodBowlBig - - type: LoopingSound - type: SolutionContainer maxVol: 20 contents: diff --git a/Resources/Prototypes/Entities/Objects/Consumable/trash_drinks.yml b/Resources/Prototypes/Entities/Objects/Consumable/trash_drinks.yml index 25c6f54d7c..eb10d855f8 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/trash_drinks.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/trash_drinks.yml @@ -6,7 +6,6 @@ id: DrinkBottleBaseEmpty description: That's an empty bottle. components: - - type: LoopingSound - type: Sprite state: icon - type: SolutionContainer diff --git a/Resources/Prototypes/Entities/Objects/Devices/pda.yml b/Resources/Prototypes/Entities/Objects/Devices/pda.yml index f52578dce9..cf36a7dfa6 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/pda.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/pda.yml @@ -28,7 +28,6 @@ interfaces: - key: enum.PDAUiKey.Key type: PDABoundUserInterface - - type: LoopingSound - type: entity parent: BasePDA diff --git a/Resources/Prototypes/Entities/Objects/Fun/skub.yml b/Resources/Prototypes/Entities/Objects/Fun/skub.yml index e4695f5062..6f1e2e6842 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/skub.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/skub.yml @@ -10,7 +10,6 @@ - type: Item sprite: Objects/Misc/skub.rsi - type: ItemCooldown - - type: LoopingSound - type: EmitSoundOnUse sound: path: /Audio/Items/skub.ogg diff --git a/Resources/Prototypes/Entities/Objects/Fun/toys.yml b/Resources/Prototypes/Entities/Objects/Fun/toys.yml index 0d8babe815..3ab1f035c5 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/toys.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/toys.yml @@ -5,7 +5,7 @@ id: BasePlushie components: - type: EmitSoundOnUse - sound: + sound: collection: ToySqueak - type: EmitSoundOnLand sound: @@ -13,7 +13,6 @@ - type: EmitSoundOnActivate sound: collection: ToySqueak - - type: LoopingSound - type: ItemCooldown - type: UseDelay delay: 1.0 @@ -91,7 +90,6 @@ sprite: Objects/Fun/toys.rsi state: plushie_snake - type: ItemCooldown - - type: LoopingSound - type: EmitSoundOnUse sound: path: /Audio/Items/Toys/rattle.ogg @@ -108,7 +106,6 @@ sprite: Objects/Fun/toys.rsi state: toy_mouse - type: ItemCooldown - - type: LoopingSound - type: EmitSoundOnUse sound: path: /Audio/Items/Toys/mousesqueek.ogg @@ -125,7 +122,6 @@ sprite: Objects/Fun/toys.rsi state: plushie_vox - type: ItemCooldown - - type: LoopingSound - type: EmitSoundOnUse sound: path: /Audio/Voice/Vox/shriek1.ogg @@ -151,7 +147,6 @@ sound: path: /Audio/Items/Toys/helpme.ogg - type: ItemCooldown - - type: LoopingSound - type: EmitSoundOnUse sound: path: /Audio/Items/Toys/helpme.ogg @@ -170,12 +165,11 @@ - type: Item sprite: Objects/Misc/carvings.rsi - type: EmitSoundOnThrow - sound: + sound: path: /Audio/Items/Toys/hellothere.ogg - type: ItemCooldown - - type: LoopingSound - type: EmitSoundOnUse - sound: + sound: path: /Audio/Items/Toys/hellothere.ogg - type: UseDelay delay: 1.0 @@ -195,9 +189,8 @@ sound: path: /Audio/Items/Toys/thankyou.ogg - type: ItemCooldown - - type: LoopingSound - type: EmitSoundOnUse - sound: + sound: path: /Audio/Items/Toys/thankyou.ogg - type: UseDelay delay: 1.0 @@ -214,12 +207,11 @@ - type: Item sprite: Objects/Misc/carvings.rsi - type: EmitSoundOnThrow - sound: + sound: path: /Audio/Items/Toys/verygood.ogg - type: ItemCooldown - - type: LoopingSound - type: EmitSoundOnUse - sound: + sound: path: /Audio/Items/Toys/verygood.ogg - type: UseDelay delay: 1.0 @@ -239,7 +231,6 @@ sound: path: /Audio/Items/Toys/imsorry.ogg - type: ItemCooldown - - type: LoopingSound - type: EmitSoundOnUse sound: path: /Audio/Items/Toys/imsorry.ogg @@ -311,7 +302,6 @@ sprite: Objects/Fun/toys.rsi state: ian - type: ItemCooldown - - type: LoopingSound - type: EmitSoundOnUse sound: path: /Audio/Items/Toys/ian.ogg diff --git a/Resources/Prototypes/Entities/Objects/Misc/torch.yml b/Resources/Prototypes/Entities/Objects/Misc/torch.yml index e2ca2f54ec..605ac31e3b 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/torch.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/torch.yml @@ -29,7 +29,6 @@ - type: Item sprite: Objects/Misc/torch.rsi HeldPrefix: unlit - - type: LoopingSound - type: Construction graph: lightTorch node: torch diff --git a/Resources/Prototypes/Entities/Objects/Tools/flare.yml b/Resources/Prototypes/Entities/Objects/Tools/flare.yml index 88f24071e3..cf2d18ee78 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/flare.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/flare.yml @@ -32,7 +32,6 @@ sprite: Objects/Misc/flare.rsi color: "#FF0000" HeldPrefix: unlit - - type: LoopingSound - type: Appearance visuals: - type: ExpendableLightVisualizer diff --git a/Resources/Prototypes/Entities/Objects/Tools/flashlight.yml b/Resources/Prototypes/Entities/Objects/Tools/flashlight.yml index cf5bff920b..80ffa05614 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/flashlight.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/flashlight.yml @@ -23,7 +23,6 @@ - type: PointLight enabled: false radius: 3 - - type: LoopingSound - type: Appearance visuals: - type: FlashLightVisualizer diff --git a/Resources/Prototypes/Entities/Objects/Tools/lantern.yml b/Resources/Prototypes/Entities/Objects/Tools/lantern.yml index e50fc08d74..11fc7a70db 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/lantern.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/lantern.yml @@ -23,7 +23,6 @@ radius: 3 energy: 2.5 color: "#FFC458" - - type: LoopingSound - type: Appearance visuals: - type: LanternVisualizer @@ -39,4 +38,3 @@ radius: 5 energy: 10 color: "#FFC458" - - type: LoopingSound diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/pickaxe.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/pickaxe.yml index ec7cf4e96a..39f1b8bf96 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/pickaxe.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/pickaxe.yml @@ -17,4 +17,3 @@ size: 24 sprite: Objects/Weapons/Melee/pickaxe.rsi prefix: inhand - - type: LoopingSound diff --git a/Resources/Prototypes/Entities/Structures/Dispensers/base.yml b/Resources/Prototypes/Entities/Structures/Dispensers/base.yml index 412c455d39..2f33762b74 100644 --- a/Resources/Prototypes/Entities/Structures/Dispensers/base.yml +++ b/Resources/Prototypes/Entities/Structures/Dispensers/base.yml @@ -26,7 +26,6 @@ interfaces: - key: enum.ReagentDispenserUiKey.Key type: ReagentDispenserBoundUserInterface - - type: LoopingSound - type: Anchorable - type: Pullable - type: Damageable diff --git a/Resources/Prototypes/Entities/Structures/Machines/microwave.yml b/Resources/Prototypes/Entities/Structures/Machines/microwave.yml index aa9093711a..4210bdcac9 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/microwave.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/microwave.yml @@ -11,7 +11,6 @@ - type: Appearance visuals: - type: MicrowaveVisualizer - - type: LoopingSound - type: UserInterface interfaces: - key: enum.MicrowaveUiKey.Key diff --git a/Resources/Prototypes/Entities/Structures/Machines/reagent_grinder.yml b/Resources/Prototypes/Entities/Structures/Machines/reagent_grinder.yml index 4a9a89d5a2..1ab00ae0d5 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/reagent_grinder.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/reagent_grinder.yml @@ -14,7 +14,6 @@ - type: Appearance visuals: - type: ReagentGrinderVisualizer - - type: LoopingSound - type: Physics fixtures: - shape: diff --git a/Resources/Prototypes/Entities/Structures/Storage/Closets/base.yml b/Resources/Prototypes/Entities/Structures/Storage/Closets/base.yml index 7c387a93d4..b64910f093 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Closets/base.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Closets/base.yml @@ -57,4 +57,3 @@ - type: StorageVisualizer state_open: generic_open state_closed: generic_door - - type: LoopingSound diff --git a/Resources/Prototypes/Entities/Structures/Storage/Crates/base.yml b/Resources/Prototypes/Entities/Structures/Storage/Crates/base.yml index eec102958c..2177b328c2 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Crates/base.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Crates/base.yml @@ -49,4 +49,3 @@ - type: StorageVisualizer state_open: crate_open state_closed: crate_door - - type: LoopingSound diff --git a/Resources/Prototypes/Entities/Structures/Storage/morgue.yml b/Resources/Prototypes/Entities/Structures/Storage/morgue.yml index a4dab64e39..92e4e35a4e 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/morgue.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/morgue.yml @@ -105,7 +105,6 @@ openSound: /Audio/Items/deconstruct.ogg trayPrototype: CrematoriumTray doSoulBeep: false - - type: LoopingSound - type: Appearance visuals: - type: CrematoriumVisualizer diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/lighting.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/lighting.yml index 620f13a43d..de20894f6d 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/lighting.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/lighting.yml @@ -16,7 +16,6 @@ !type:PhysShapeAabb bounds: "-0.45, -0.15, 0.45, 0.35" layer: [ Passable ] - - type: LoopingSound - type: Sprite sprite: Structures/Wallmounts/Lighting/light_tube.rsi layers: From 8aa4f998de198886796af54cc414a9d2b4257bf9 Mon Sep 17 00:00:00 2001 From: mirrorcult Date: Sat, 31 Jul 2021 04:50:32 -0700 Subject: [PATCH 40/76] Metabolism refactor (#4395) * metabolism -> respirator, add reageanteffect and reagenteffectcondition, start on metabolizercomp/system * move LiverBehavior metabolism logic to Metabolizer * minor tweaks and update all YAML * how about actually taking conditions into account * off by one * removals * reviews --- Content.Client/Entry/IgnoredComponents.cs | 3 +- .../Tests/Body/LungTest.cs | 12 +- Content.Server/Body/Behavior/LiverBehavior.cs | 89 +------ .../Body/Circulatory/BloodstreamComponent.cs | 4 +- .../Body/Metabolism/MetabolizerComponent.cs | 57 +++++ .../Body/Metabolism/MetabolizerSystem.cs | 107 ++++++++ .../Respiratory/RespiratorComponent.cs} | 7 +- .../Respiratory/RespiratorSystem.cs} | 8 +- .../Chemistry/Metabolism/DefaultDrink.cs | 35 --- .../Chemistry/Metabolism/DefaultFood.cs | 38 --- .../Metabolism/HealthChangeMetabolism.cs | 72 ------ .../ReagentThreshold.cs | 26 ++ .../Chemistry/ReagentEffects/HealthChange.cs | 56 +++++ .../Chemistry/ReagentEffects/SatiateHunger.cs | 28 +++ .../Chemistry/ReagentEffects/SatiateThirst.cs | 28 +++ .../ActionBlocker/ActionBlockerSystem.cs | 2 +- .../Metabolism}/ShiverAttemptEvent.cs | 2 +- .../Metabolism}/SweatAttemptEvent.cs | 2 +- .../Metabolizable/DefaultMetabolizable.cs | 36 --- .../Chemistry/Metabolizable/IMetabolizable.cs | 23 -- .../Chemistry/Reagent/ReagentEffect.cs | 23 ++ .../Reagent/ReagentEffectCondition.cs | 13 + .../Chemistry/Reagent/ReagentPrototype.cs | 5 - .../Prototypes/Body/Mechanisms/human.yml | 238 +++++++++++++++--- .../Entities/Mobs/NPCs/simplemob.yml | 2 +- .../Prototypes/Entities/Mobs/NPCs/xeno.yml | 2 +- .../Entities/Mobs/Species/human.yml | 2 +- .../Prototypes/Entities/Mobs/Species/vox.yml | 2 +- .../Reagents/Consumable/Drink/alcohol.yml | 11 - .../Reagents/Consumable/Drink/drinks.yml | 21 -- .../Reagents/Consumable/Drink/juice.yml | 39 --- .../Reagents/Consumable/Drink/soda.yml | 9 - .../Reagents/Consumable/Food/condiments.yml | 24 -- .../Reagents/Consumable/Food/ingredients.yml | 30 --- Resources/Prototypes/Reagents/chemicals.yml | 10 +- Resources/Prototypes/Reagents/medicine.yml | 64 +---- 36 files changed, 572 insertions(+), 558 deletions(-) create mode 100644 Content.Server/Body/Metabolism/MetabolizerComponent.cs create mode 100644 Content.Server/Body/Metabolism/MetabolizerSystem.cs rename Content.Server/{Metabolism/MetabolismComponent.cs => Body/Respiratory/RespiratorComponent.cs} (98%) rename Content.Server/{Metabolism/MetabolismSystem.cs => Body/Respiratory/RespiratorSystem.cs} (50%) delete mode 100644 Content.Server/Chemistry/Metabolism/DefaultDrink.cs delete mode 100644 Content.Server/Chemistry/Metabolism/DefaultFood.cs delete mode 100644 Content.Server/Chemistry/Metabolism/HealthChangeMetabolism.cs create mode 100644 Content.Server/Chemistry/ReagentEffectConditions/ReagentThreshold.cs create mode 100644 Content.Server/Chemistry/ReagentEffects/HealthChange.cs create mode 100644 Content.Server/Chemistry/ReagentEffects/SatiateHunger.cs create mode 100644 Content.Server/Chemistry/ReagentEffects/SatiateThirst.cs rename Content.Shared/{Metabolism/Events => Body/Metabolism}/ShiverAttemptEvent.cs (85%) rename Content.Shared/{Metabolism/Events => Body/Metabolism}/SweatAttemptEvent.cs (85%) delete mode 100644 Content.Shared/Chemistry/Metabolizable/DefaultMetabolizable.cs delete mode 100644 Content.Shared/Chemistry/Metabolizable/IMetabolizable.cs create mode 100644 Content.Shared/Chemistry/Reagent/ReagentEffect.cs create mode 100644 Content.Shared/Chemistry/Reagent/ReagentEffectCondition.cs diff --git a/Content.Client/Entry/IgnoredComponents.cs b/Content.Client/Entry/IgnoredComponents.cs index f5d976796d..388c56ae43 100644 --- a/Content.Client/Entry/IgnoredComponents.cs +++ b/Content.Client/Entry/IgnoredComponents.cs @@ -154,7 +154,8 @@ namespace Content.Client.Entry "GasPassiveGate", "GasValve", "GasThermoMachine", - "Metabolism", + "Respirator", + "Metabolizer", "AiFactionTag", "PressureProtection", "AMEPart", diff --git a/Content.IntegrationTests/Tests/Body/LungTest.cs b/Content.IntegrationTests/Tests/Body/LungTest.cs index 580984374b..d1ac590adf 100644 --- a/Content.IntegrationTests/Tests/Body/LungTest.cs +++ b/Content.IntegrationTests/Tests/Body/LungTest.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using Content.Server.Atmos; using Content.Server.Body.Behavior; using Content.Server.Body.Circulatory; -using Content.Server.Metabolism; +using Content.Server.Body.Respiratory; using Content.Shared.Atmos; using Content.Shared.Body.Components; using NUnit.Framework; @@ -32,7 +32,7 @@ namespace Content.IntegrationTests.Tests.Body template: HumanoidTemplate preset: HumanPreset centerSlot: torso - - type: Metabolism + - type: Respirator metabolismHeat: 5000 radiatedHeat: 400 implicitHeatRegulation: 5000 @@ -148,7 +148,7 @@ namespace Content.IntegrationTests.Tests.Body MapId mapId; IMapGrid grid = null; - MetabolismComponent metabolism = null; + RespiratorComponent respirator = null; IEntity human = null; var testMapName = "Maps/Test/Breathing/3by3-20oxy-80nit.yml"; @@ -169,8 +169,8 @@ namespace Content.IntegrationTests.Tests.Body Assert.True(human.TryGetComponent(out SharedBodyComponent body)); Assert.True(body.HasMechanismBehavior()); - Assert.True(human.TryGetComponent(out metabolism)); - Assert.False(metabolism.Suffocating); + Assert.True(human.TryGetComponent(out respirator)); + Assert.False(respirator.Suffocating); }); var increment = 10; @@ -178,7 +178,7 @@ namespace Content.IntegrationTests.Tests.Body for (var tick = 0; tick < 600; tick += increment) { await server.WaitRunTicks(increment); - Assert.False(metabolism.Suffocating, $"Entity {human.Name} is suffocating on tick {tick}"); + Assert.False(respirator.Suffocating, $"Entity {human.Name} is suffocating on tick {tick}"); } await server.WaitIdleAsync(); diff --git a/Content.Server/Body/Behavior/LiverBehavior.cs b/Content.Server/Body/Behavior/LiverBehavior.cs index 95897b320a..aa40d1b142 100644 --- a/Content.Server/Body/Behavior/LiverBehavior.cs +++ b/Content.Server/Body/Behavior/LiverBehavior.cs @@ -1,7 +1,6 @@ using System.Linq; using Content.Server.Body.Circulatory; using Content.Shared.Body.Networks; -using Content.Shared.Chemistry.Metabolizable; using Content.Shared.Chemistry.Reagent; using Robust.Shared.IoC; using Robust.Shared.Prototypes; @@ -13,52 +12,8 @@ namespace Content.Server.Body.Behavior /// public class LiverBehavior : MechanismBehavior { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - private float _accumulatedFrameTime; - /// - /// Delay time that determines how often to metabolise blood contents (in seconds). - /// - private float _updateIntervalSeconds = 1.0f; - - /// - /// Whether the liver is functional. - /// - //[ViewVariables] private bool _liverFailing = false; - - /// - /// Modifier for alcohol damage. - /// - //[DataField("alcoholLethality")] - //[ViewVariables] private float _alcoholLethality = 0.005f; - - /// - /// Modifier for alcohol damage. - /// - //[DataField("alcoholExponent")] - //[ViewVariables] private float _alcoholExponent = 1.6f; - - /// - /// Toxin volume that can be purged without damage. - /// - //[DataField("toxinTolerance")] - //[ViewVariables] private float _toxinTolerance = 3f; - - /// - /// Toxin damage modifier. - /// - //[DataField("toxinLethality")] - //[ViewVariables] private float _toxinLethality = 0.01f; - - /// - /// Loops through each reagent in _internalSolution, - /// and calls for each of them. - /// Also handles toxins and alcohol. - /// - /// - /// The time since the last update in seconds. - /// public override void Update(float frameTime) { if (Body == null) @@ -68,51 +23,13 @@ namespace Content.Server.Body.Behavior _accumulatedFrameTime += frameTime; - // Update at most once every _updateIntervalSeconds - if (_accumulatedFrameTime < _updateIntervalSeconds) + // Update at most once per second + if (_accumulatedFrameTime < 1) { return; } - _accumulatedFrameTime -= _updateIntervalSeconds; - - if (!Body.Owner.TryGetComponent(out BloodstreamComponent? bloodstream)) - { - return; - } - - if (bloodstream.Solution.CurrentVolume <= ReagentUnit.Zero) - { - return; - } - - // Run metabolism for each reagent, remove metabolized reagents - // Using ToList here lets us edit reagents while iterating - foreach (var reagent in bloodstream.Solution.ReagentList.ToList()) - { - if (!_prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype? prototype)) - { - continue; - } - - // How much reagent is available to metabolise? - // This needs to be passed to other functions that have metabolism rate information, such that they don't "overmetabolise" a reagent. - var availableReagent = bloodstream.Solution.Solution.GetReagentQuantity(reagent.ReagentId); - - //TODO BODY Check if it's a Toxin. If volume < _toxinTolerance, just remove it. If greater, add damage = volume * _toxinLethality - //TODO BODY Check if it has BoozePower > 0. Affect drunkenness, apply damage. Proposed formula (SS13-derived): damage = sqrt(volume) * BoozePower^_alcoholExponent * _alcoholLethality / 10 - //TODO BODY Liver failure. - - //TODO Make sure reagent prototypes actually have the toxin and boozepower vars set. - - // Run metabolism code for each reagent - foreach (var metabolizable in prototype.Metabolism) - { - var reagentDelta = metabolizable.Metabolize(Body.Owner, reagent.ReagentId, _updateIntervalSeconds, availableReagent); - bloodstream.Solution.TryRemoveReagent(reagent.ReagentId, reagentDelta); - availableReagent -= reagentDelta; - } - } + _accumulatedFrameTime -= 1; } } } diff --git a/Content.Server/Body/Circulatory/BloodstreamComponent.cs b/Content.Server/Body/Circulatory/BloodstreamComponent.cs index df90cd6fc6..998e3d97bb 100644 --- a/Content.Server/Body/Circulatory/BloodstreamComponent.cs +++ b/Content.Server/Body/Circulatory/BloodstreamComponent.cs @@ -2,8 +2,8 @@ using System; using System.Linq; using Content.Server.Atmos; using Content.Server.Atmos.EntitySystems; +using Content.Server.Body.Respiratory; using Content.Server.Chemistry.Components; -using Content.Server.Metabolism; using Content.Shared.Atmos; using Content.Shared.Body.Networks; using Content.Shared.Chemistry.Reagent; @@ -72,7 +72,7 @@ namespace Content.Server.Body.Circulatory { var atmosphereSystem = EntitySystem.Get(); - if (!Owner.TryGetComponent(out MetabolismComponent? metabolism)) + if (!Owner.TryGetComponent(out RespiratorComponent? metabolism)) { atmosphereSystem.Merge(to, Air); Air.Clear(); diff --git a/Content.Server/Body/Metabolism/MetabolizerComponent.cs b/Content.Server/Body/Metabolism/MetabolizerComponent.cs new file mode 100644 index 0000000000..b74926ff57 --- /dev/null +++ b/Content.Server/Body/Metabolism/MetabolizerComponent.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using Content.Shared.Chemistry.Reagent; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary; + +namespace Content.Server.Body.Metabolism +{ + /// + /// Handles metabolizing various reagents with given effects. + /// + [RegisterComponent] + public class MetabolizerComponent : Component + { + public override string Name => "Metabolizer"; + + public float AccumulatedFrametime = 0.0f; + + /// + /// How often to metabolize reagents, in seconds. + /// + /// + [DataField("updateFrequency")] + public float UpdateFrequency = 1.0f; + + /// + /// Whether this metabolizer should attempt to metabolize chemicals in its parent bodies' bloodstream, + /// as opposed to a solution container on the metabolizing entity itself. + /// + [DataField("takeFromBloodstream")] + public bool TakeFromBloodstream = true; + + /// + /// A dictionary mapping reagent string IDs to a list of effects & associated metabolism rate. + /// + /// + [DataField("metabolisms", required: true, customTypeSerializer:typeof(PrototypeIdDictionarySerializer))] + public Dictionary Metabolisms = default!; + } + + [DataDefinition] + public class ReagentEffectsEntry + { + /// + /// Amount of reagent to metabolize, per metabolism cycle. + /// + [DataField("metabolismRate")] + public ReagentUnit MetabolismRate = ReagentUnit.New(1.0f); + + /// + /// A list of effects to apply when these reagents are metabolized. + /// + [DataField("effects", required: true)] + public ReagentEffect[] Effects = default!; + } +} diff --git a/Content.Server/Body/Metabolism/MetabolizerSystem.cs b/Content.Server/Body/Metabolism/MetabolizerSystem.cs new file mode 100644 index 0000000000..249ca13391 --- /dev/null +++ b/Content.Server/Body/Metabolism/MetabolizerSystem.cs @@ -0,0 +1,107 @@ +using System.Collections.Generic; +using System.Linq; +using Content.Server.Body.Circulatory; +using Content.Server.Chemistry.Components; +using Content.Shared.Body.Components; +using Content.Shared.Body.Mechanism; +using Content.Shared.Chemistry.Reagent; +using Content.Shared.Chemistry.Solution; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Prototypes; + +namespace Content.Server.Body.Metabolism +{ + // TODO mirror in the future working on mechanisms move updating here to BodySystem so it can be ordered? + public class MetabolizerSystem : EntitySystem + { + public override void Update(float frameTime) + { + base.Update(frameTime); + + foreach (var metab in ComponentManager.EntityQuery(false)) + { + metab.AccumulatedFrametime += frameTime; + + // Only update as frequently as it should + if (metab.AccumulatedFrametime >= metab.UpdateFrequency) + { + metab.AccumulatedFrametime = 0.0f; + TryMetabolize(metab); + } + } + } + + private void TryMetabolize(MetabolizerComponent comp) + { + var owner = comp.Owner; + var reagentList = new List(); + SolutionContainerComponent? solution = null; + SharedBodyComponent? body = null; + + // if this field is passed we should try and take from the bloodstream over anything else + if (comp.TakeFromBloodstream && owner.TryGetComponent(out var mech)) + { + body = mech.Body; + if (body != null) + { + if (body.Owner.TryGetComponent(out var bloodstream) + && bloodstream.Solution.CurrentVolume >= ReagentUnit.Zero) + { + solution = bloodstream.Solution; + reagentList = bloodstream.Solution.ReagentList.ToList(); + } + } + } + else if (owner.TryGetComponent(out var sol)) + { + // if we have no mechanism/body but a solution container instead, + // we'll just use that to metabolize from + solution = sol; + reagentList = sol.ReagentList.ToList(); + } + if (solution == null || reagentList.Count == 0) + { + // We're all outta ideas on where to metabolize from + return; + } + + // Run metabolism for each reagent, remove metabolized reagents + foreach (var reagent in reagentList) + { + if (!comp.Metabolisms.ContainsKey(reagent.ReagentId)) + continue; + + var metabolism = comp.Metabolisms[reagent.ReagentId]; + // Run metabolism code for each reagent + foreach (var effect in metabolism.Effects) + { + var ent = body != null ? body.Owner : owner; + var conditionsMet = true; + if (effect.Conditions != null) + { + // yes this is 3 nested for loops, but all of these lists are + // basically guaranteed to be small or empty + foreach (var condition in effect.Conditions) + { + if (!condition.Condition(ent, reagent)) + { + conditionsMet = false; + break; + } + } + } + + if (!conditionsMet) + return; + + // If we're part of a body, pass that entity to Metabolize + // Otherwise, just pass our owner entity, maybe we're a plant or something + effect.Metabolize(ent, reagent); + } + + solution.TryRemoveReagent(reagent.ReagentId, metabolism.MetabolismRate); + } + } + } +} diff --git a/Content.Server/Metabolism/MetabolismComponent.cs b/Content.Server/Body/Respiratory/RespiratorComponent.cs similarity index 98% rename from Content.Server/Metabolism/MetabolismComponent.cs rename to Content.Server/Body/Respiratory/RespiratorComponent.cs index 4898692ab9..2aec76e351 100644 --- a/Content.Server/Metabolism/MetabolismComponent.cs +++ b/Content.Server/Body/Respiratory/RespiratorComponent.cs @@ -13,7 +13,6 @@ using Content.Shared.Atmos; using Content.Shared.Body.Components; using Content.Shared.Damage; using Content.Shared.Damage.Components; -using Content.Shared.Metabolism.Events; using Content.Shared.MobState; using Content.Shared.Notification.Managers; using Robust.Shared.GameObjects; @@ -21,14 +20,14 @@ using Robust.Shared.Localization; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.ViewVariables; -namespace Content.Server.Metabolism +namespace Content.Server.Body.Respiratory { [RegisterComponent] - public class MetabolismComponent : Component + public class RespiratorComponent : Component { [ComponentDependency] private readonly SharedBodyComponent? _body = default!; - public override string Name => "Metabolism"; + public override string Name => "Respirator"; private float _accumulatedFrameTime; diff --git a/Content.Server/Metabolism/MetabolismSystem.cs b/Content.Server/Body/Respiratory/RespiratorSystem.cs similarity index 50% rename from Content.Server/Metabolism/MetabolismSystem.cs rename to Content.Server/Body/Respiratory/RespiratorSystem.cs index 6616d013d5..424b1e4dce 100644 --- a/Content.Server/Metabolism/MetabolismSystem.cs +++ b/Content.Server/Body/Respiratory/RespiratorSystem.cs @@ -1,18 +1,18 @@ using JetBrains.Annotations; using Robust.Shared.GameObjects; -namespace Content.Server.Metabolism +namespace Content.Server.Body.Respiratory { [UsedImplicitly] - public class MetabolismSystem : EntitySystem + public class RespiratorSystem : EntitySystem { public override void Update(float frameTime) { base.Update(frameTime); - foreach (var metabolism in ComponentManager.EntityQuery(true)) + foreach (var respirator in ComponentManager.EntityQuery(false)) { - metabolism.Update(frameTime); + respirator.Update(frameTime); } } } diff --git a/Content.Server/Chemistry/Metabolism/DefaultDrink.cs b/Content.Server/Chemistry/Metabolism/DefaultDrink.cs deleted file mode 100644 index 255200f103..0000000000 --- a/Content.Server/Chemistry/Metabolism/DefaultDrink.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Content.Server.Nutrition.Components; -using Content.Shared.Chemistry; -using Content.Shared.Chemistry.Metabolizable; -using Content.Shared.Chemistry.Reagent; -using Robust.Shared.GameObjects; -using Robust.Shared.Serialization.Manager.Attributes; - -namespace Content.Server.Chemistry.Metabolism -{ - /// - /// Default metabolism for drink reagents. Attempts to find a ThirstComponent on the target, - /// and to update it's thirst values. Inherits metabolisation rate logic from DefaultMetabolizable. - /// - [DataDefinition] - public class DefaultDrink : DefaultMetabolizable - { - //How much thirst is satiated when 1u of the reagent is metabolized - [DataField("hydrationFactor")] - public float HydrationFactor { get; set; } = 30.0f; - - //Remove reagent at set rate, satiate thirst if a ThirstComponent can be found - public override ReagentUnit Metabolize(IEntity solutionEntity, string reagentId, float tickTime, ReagentUnit availableReagent) - { - // use DefaultMetabolism to determine how much reagent we should metabolize - var amountMetabolized = base.Metabolize(solutionEntity, reagentId, tickTime, availableReagent); - - // If metabolizing entity has a ThirstComponent, hydrate them. - if (solutionEntity.TryGetComponent(out ThirstComponent? thirst)) - thirst.UpdateThirst(amountMetabolized.Float() * HydrationFactor); - - //Return amount of reagent to be removed, remove reagent regardless of ThirstComponent presence - return amountMetabolized; - } - } -} diff --git a/Content.Server/Chemistry/Metabolism/DefaultFood.cs b/Content.Server/Chemistry/Metabolism/DefaultFood.cs deleted file mode 100644 index fafcf58dd1..0000000000 --- a/Content.Server/Chemistry/Metabolism/DefaultFood.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Content.Server.Nutrition.Components; -using Content.Shared.Chemistry; -using Content.Shared.Chemistry.Metabolizable; -using Content.Shared.Chemistry.Reagent; -using Robust.Shared.GameObjects; -using Robust.Shared.Serialization.Manager.Attributes; - -namespace Content.Server.Chemistry.Metabolism -{ - /// - /// Default metabolism for food reagents. Attempts to find a HungerComponent on the target, - /// and to update it's hunger values. Inherits metabolisation rate logic from DefaultMetabolizable. - /// - [DataDefinition] - public class DefaultFood : DefaultMetabolizable - { - - /// - /// How much hunger is satiated when 1u of the reagent is metabolized - /// - [DataField("nutritionFactor")] public float NutritionFactor { get; set; } = 30.0f; - - - //Remove reagent at set rate, satiate hunger if a HungerComponent can be found - public override ReagentUnit Metabolize(IEntity solutionEntity, string reagentId, float tickTime, ReagentUnit availableReagent) - { - // use DefaultMetabolism to determine how much reagent we should metabolize - var amountMetabolized = base.Metabolize(solutionEntity, reagentId, tickTime, availableReagent); - - // If metabolizing entity has a HungerComponent, feed them. - if (solutionEntity.TryGetComponent(out HungerComponent? hunger)) - hunger.UpdateFood(amountMetabolized.Float() * NutritionFactor); - - //Return amount of reagent to be removed. Reagent is removed regardless of HungerComponent presence - return amountMetabolized; - } - } -} diff --git a/Content.Server/Chemistry/Metabolism/HealthChangeMetabolism.cs b/Content.Server/Chemistry/Metabolism/HealthChangeMetabolism.cs deleted file mode 100644 index bbf2534505..0000000000 --- a/Content.Server/Chemistry/Metabolism/HealthChangeMetabolism.cs +++ /dev/null @@ -1,72 +0,0 @@ -using Content.Shared.Chemistry; -using Content.Shared.Chemistry.Metabolizable; -using Content.Shared.Chemistry.Reagent; -using Robust.Shared.GameObjects; -using Robust.Shared.Serialization.Manager.Attributes; -using Content.Shared.Damage; -using Content.Shared.Damage.Components; - -namespace Content.Server.Chemistry.Metabolism -{ - /// - /// Default metabolism for medicine reagents. Attempts to find a DamageableComponent on the target, - /// and to update its damage values. Inherits metabolisation rate logic from DefaultMetabolizable. - /// - [DataDefinition] - public class HealthChangeMetabolism : DefaultMetabolizable - { - - /// - /// How much damage is changed when 1u of the reagent is metabolized. - /// - [DataField("healthChange")] - public float HealthChange { get; set; } = 1.0f; - - /// - /// Class of damage changed, Brute, Burn, Toxin, Airloss. - /// - [DataField("damageClass")] - public DamageClass DamageType { get; set; } = DamageClass.Brute; - - private float _accumulatedHealth; - - /// - /// Remove reagent at set rate, changes damage if a DamageableComponent can be found. - /// - /// - /// - /// - /// Reagent available to be metabolized. - /// - public override ReagentUnit Metabolize(IEntity solutionEntity, string reagentId, float tickTime, ReagentUnit availableReagent) - { - // use DefaultMetabolism to determine how much reagent we should metabolize - var amountMetabolized = base.Metabolize(solutionEntity, reagentId, tickTime, availableReagent); - - // how much does this much reagent heal for - var healthChangeAmount = HealthChange * amountMetabolized.Float(); - - if (solutionEntity.TryGetComponent(out IDamageableComponent? health)) - { - // Heal damage by healthChangeAmmount, rounding down to nearest integer - health.ChangeDamage(DamageType, (int) healthChangeAmount, true); - - // Store decimal remainder of healthChangeAmmount in _accumulatedHealth - _accumulatedHealth += (healthChangeAmount - (int) healthChangeAmount); - - if (_accumulatedHealth >= 1) - { - health.ChangeDamage(DamageType, 1, true); - _accumulatedHealth -= 1; - } - - else if(_accumulatedHealth <= -1) - { - health.ChangeDamage(DamageType, -1, true); - _accumulatedHealth += 1; - } - } - return amountMetabolized; - } - } -} diff --git a/Content.Server/Chemistry/ReagentEffectConditions/ReagentThreshold.cs b/Content.Server/Chemistry/ReagentEffectConditions/ReagentThreshold.cs new file mode 100644 index 0000000000..cfc0177d41 --- /dev/null +++ b/Content.Server/Chemistry/ReagentEffectConditions/ReagentThreshold.cs @@ -0,0 +1,26 @@ +using Content.Shared.Body.Components; +using Content.Shared.Chemistry.Reagent; +using Content.Shared.Chemistry.Solution; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization.Manager.Attributes; + +namespace Content.Server.Chemistry.ReagentEffectConditions +{ + /// + /// Used for implementing reagent effects that require a certain amount of reagent before it should be applied. + /// For instance, overdoses. + /// + public class ReagentThreshold : ReagentEffectCondition + { + [DataField("min")] + public ReagentUnit Min = ReagentUnit.Zero; + + [DataField("max")] + public ReagentUnit Max = ReagentUnit.MaxValue; + + public override bool Condition(IEntity solutionEntity, Solution.ReagentQuantity reagent) + { + return reagent.Quantity >= Min && reagent.Quantity < Max; + } + } +} diff --git a/Content.Server/Chemistry/ReagentEffects/HealthChange.cs b/Content.Server/Chemistry/ReagentEffects/HealthChange.cs new file mode 100644 index 0000000000..47b640cdfc --- /dev/null +++ b/Content.Server/Chemistry/ReagentEffects/HealthChange.cs @@ -0,0 +1,56 @@ +using Content.Shared.Chemistry; +using Content.Shared.Chemistry.Reagent; +using Content.Shared.Chemistry.Solution; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization.Manager.Attributes; +using Content.Shared.Damage; +using Content.Shared.Damage.Components; + +namespace Content.Server.Chemistry.ReagentEffects +{ + /// + /// Default metabolism for medicine reagents. Attempts to find a DamageableComponent on the target, + /// and to update its damage values. + /// + public class HealthChange : ReagentEffect + { + /// + /// How much damage is changed when 1u of the reagent is metabolized. + /// + [DataField("healthChange")] + public float AmountToChange { get; set; } = 1.0f; + + /// + /// Class of damage changed, Brute, Burn, Toxin, Airloss. + /// + [DataField("damageClass")] + public DamageClass DamageType { get; set; } = DamageClass.Brute; + + private float _accumulatedHealth; + + /// + /// Changes damage if a DamageableComponent can be found. + /// + public override void Metabolize(IEntity solutionEntity, Solution.ReagentQuantity amount) + { + if (solutionEntity.TryGetComponent(out IDamageableComponent? health)) + { + health.ChangeDamage(DamageType, (int)AmountToChange, true); + float decHealthChange = (float) (AmountToChange - (int) AmountToChange); + _accumulatedHealth += decHealthChange; + + if (_accumulatedHealth >= 1) + { + health.ChangeDamage(DamageType, 1, true); + _accumulatedHealth -= 1; + } + + else if(_accumulatedHealth <= -1) + { + health.ChangeDamage(DamageType, -1, true); + _accumulatedHealth += 1; + } + } + } + } +} diff --git a/Content.Server/Chemistry/ReagentEffects/SatiateHunger.cs b/Content.Server/Chemistry/ReagentEffects/SatiateHunger.cs new file mode 100644 index 0000000000..983d30b565 --- /dev/null +++ b/Content.Server/Chemistry/ReagentEffects/SatiateHunger.cs @@ -0,0 +1,28 @@ +using Content.Server.Nutrition.Components; +using Content.Shared.Chemistry; +using Content.Shared.Chemistry.Reagent; +using Content.Shared.Chemistry.Solution; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization.Manager.Attributes; + +namespace Content.Server.Chemistry.ReagentEffects +{ + /// + /// Attempts to find a HungerComponent on the target, + /// and to update it's hunger values. + /// + public class SatiateHunger : ReagentEffect + { + /// + /// How much hunger is satiated when 1u of the reagent is metabolized + /// + [DataField("nutritionFactor")] public float NutritionFactor { get; set; } = 3.0f; + + //Remove reagent at set rate, satiate hunger if a HungerComponent can be found + public override void Metabolize(IEntity solutionEntity, Solution.ReagentQuantity amount) + { + if (solutionEntity.TryGetComponent(out HungerComponent? hunger)) + hunger.UpdateFood(NutritionFactor); + } + } +} diff --git a/Content.Server/Chemistry/ReagentEffects/SatiateThirst.cs b/Content.Server/Chemistry/ReagentEffects/SatiateThirst.cs new file mode 100644 index 0000000000..637c56f235 --- /dev/null +++ b/Content.Server/Chemistry/ReagentEffects/SatiateThirst.cs @@ -0,0 +1,28 @@ +using Content.Server.Nutrition.Components; +using Content.Shared.Chemistry; +using Content.Shared.Chemistry.Reagent; +using Content.Shared.Chemistry.Solution; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization.Manager.Attributes; + +namespace Content.Server.Chemistry.ReagentEffects +{ + /// + /// Default metabolism for drink reagents. Attempts to find a ThirstComponent on the target, + /// and to update it's thirst values. + /// + public class SatiateThirst : ReagentEffect + { + /// How much thirst is satiated each metabolism tick. Not currently tied to + /// rate or anything. + [DataField("hydrationFactor")] + public float HydrationFactor { get; set; } = 3.0f; + + /// Satiate thirst if a ThirstComponent can be found + public override void Metabolize(IEntity solutionEntity, Solution.ReagentQuantity amount) + { + if (solutionEntity.TryGetComponent(out ThirstComponent? thirst)) + thirst.UpdateThirst(HydrationFactor); + } + } +} diff --git a/Content.Shared/ActionBlocker/ActionBlockerSystem.cs b/Content.Shared/ActionBlocker/ActionBlockerSystem.cs index a4f3080583..253475235b 100644 --- a/Content.Shared/ActionBlocker/ActionBlockerSystem.cs +++ b/Content.Shared/ActionBlocker/ActionBlockerSystem.cs @@ -4,7 +4,7 @@ using Content.Shared.Emoting; using Content.Shared.Interaction.Events; using Content.Shared.Inventory.Events; using Content.Shared.Item; -using Content.Shared.Metabolism.Events; +using Content.Shared.Body.Metabolism; using Content.Shared.Movement; using Content.Shared.Speech; using Content.Shared.Throwing; diff --git a/Content.Shared/Metabolism/Events/ShiverAttemptEvent.cs b/Content.Shared/Body/Metabolism/ShiverAttemptEvent.cs similarity index 85% rename from Content.Shared/Metabolism/Events/ShiverAttemptEvent.cs rename to Content.Shared/Body/Metabolism/ShiverAttemptEvent.cs index da05d74ffd..d84630fa3c 100644 --- a/Content.Shared/Metabolism/Events/ShiverAttemptEvent.cs +++ b/Content.Shared/Body/Metabolism/ShiverAttemptEvent.cs @@ -1,6 +1,6 @@ using Robust.Shared.GameObjects; -namespace Content.Shared.Metabolism.Events +namespace Content.Shared.Body.Metabolism { public class ShiverAttemptEvent : CancellableEntityEventArgs { diff --git a/Content.Shared/Metabolism/Events/SweatAttemptEvent.cs b/Content.Shared/Body/Metabolism/SweatAttemptEvent.cs similarity index 85% rename from Content.Shared/Metabolism/Events/SweatAttemptEvent.cs rename to Content.Shared/Body/Metabolism/SweatAttemptEvent.cs index b920639aff..6c6180eb00 100644 --- a/Content.Shared/Metabolism/Events/SweatAttemptEvent.cs +++ b/Content.Shared/Body/Metabolism/SweatAttemptEvent.cs @@ -1,6 +1,6 @@ using Robust.Shared.GameObjects; -namespace Content.Shared.Metabolism.Events +namespace Content.Shared.Body.Metabolism { public class SweatAttemptEvent : CancellableEntityEventArgs { diff --git a/Content.Shared/Chemistry/Metabolizable/DefaultMetabolizable.cs b/Content.Shared/Chemistry/Metabolizable/DefaultMetabolizable.cs deleted file mode 100644 index 939acdd21a..0000000000 --- a/Content.Shared/Chemistry/Metabolizable/DefaultMetabolizable.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Content.Shared.Chemistry.Reagent; -using Robust.Shared.GameObjects; -using Robust.Shared.Serialization.Manager.Attributes; - -namespace Content.Shared.Chemistry.Metabolizable -{ - /// - /// Default metabolization for reagents. Returns the amount of reagents metabolized without applying effects. - /// Metabolizes reagents at a constant rate, limited by how much is available. Other classes are derived from - /// this class, so that they do not need their own metabolization quantity calculation. - /// - [DataDefinition] - public class DefaultMetabolizable : IMetabolizable - { - /// - /// Rate of metabolism in units / second - /// - [DataField("rate")] public ReagentUnit MetabolismRate { get; set; } = ReagentUnit.New(1); - - public virtual ReagentUnit Metabolize(IEntity solutionEntity, string reagentId, float tickTime, ReagentUnit availableReagent) - { - - // How much reagent should we metabolize - // The default behaviour is to metabolize at a constant rate, independent of the quantity of reagents. - var amountMetabolized = MetabolismRate * tickTime; - - // is that much reagent actually available? - if (availableReagent < amountMetabolized) - { - return availableReagent; - } - - return amountMetabolized; - } - } -} diff --git a/Content.Shared/Chemistry/Metabolizable/IMetabolizable.cs b/Content.Shared/Chemistry/Metabolizable/IMetabolizable.cs deleted file mode 100644 index f8b2a888cc..0000000000 --- a/Content.Shared/Chemistry/Metabolizable/IMetabolizable.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Content.Shared.Chemistry.Reagent; -using Robust.Shared.GameObjects; - -namespace Content.Shared.Chemistry.Metabolizable -{ - /// - /// Metabolism behavior for a reagent. - /// - public interface IMetabolizable - { - /// - /// Metabolize the attached reagent. Return the amount of reagent to be removed from the solution. - /// You shouldn't remove the reagent yourself to avoid invalidating the iterator of the metabolism - /// organ that is processing it's reagents. - /// - /// The entity containing the solution. - /// The reagent id - /// The time since the last metabolism tick in seconds. - /// Reagent available to be metabolized. - /// The amount of reagent to be removed. The metabolizing organ should handle removing the reagent. - ReagentUnit Metabolize(IEntity solutionEntity, string reagentId, float tickTime, ReagentUnit availableReagent); - } -} diff --git a/Content.Shared/Chemistry/Reagent/ReagentEffect.cs b/Content.Shared/Chemistry/Reagent/ReagentEffect.cs new file mode 100644 index 0000000000..78ca370df0 --- /dev/null +++ b/Content.Shared/Chemistry/Reagent/ReagentEffect.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using Content.Shared.Chemistry.Reagent; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization.Manager.Attributes; + +namespace Content.Shared.Chemistry.Reagent +{ + /// + /// Reagent effects describe behavior that occurs when a reagent is ingested and metabolized by some + /// organ. They only trigger when their conditions ( + /// + [ImplicitDataDefinitionForInheritors] + public abstract class ReagentEffect + { + /// + /// The list of conditions required for the effect to activate. Not required. + /// + [DataField("conditions")] + public ReagentEffectCondition[]? Conditions; + + public abstract void Metabolize(IEntity solutionEntity, Solution.Solution.ReagentQuantity amount); + } +} diff --git a/Content.Shared/Chemistry/Reagent/ReagentEffectCondition.cs b/Content.Shared/Chemistry/Reagent/ReagentEffectCondition.cs new file mode 100644 index 0000000000..54253d1c6d --- /dev/null +++ b/Content.Shared/Chemistry/Reagent/ReagentEffectCondition.cs @@ -0,0 +1,13 @@ +using Content.Shared.Body.Components; +using Content.Shared.Chemistry.Solution; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization.Manager.Attributes; + +namespace Content.Shared.Chemistry.Reagent +{ + [ImplicitDataDefinitionForInheritors] + public abstract class ReagentEffectCondition + { + public abstract bool Condition(IEntity solutionEntity, Solution.Solution.ReagentQuantity reagent); + } +} diff --git a/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs b/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs index 30de264604..321dacd497 100644 --- a/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs +++ b/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using Content.Shared.Botany; -using Content.Shared.Chemistry.Metabolizable; using Content.Shared.Chemistry.Reaction; using Robust.Shared.GameObjects; using Robust.Shared.Map; @@ -16,9 +15,6 @@ namespace Content.Shared.Chemistry.Reagent [DataDefinition] public class ReagentPrototype : IPrototype { - [DataField("metabolism", serverOnly: true)] - private readonly List _metabolism = new() {new DefaultMetabolizable()}; - [DataField("tileReactions", serverOnly: true)] private readonly List _tileReactions = new(0); @@ -60,7 +56,6 @@ namespace Content.Shared.Chemistry.Reagent public string SpriteReplacementPath { get; } = string.Empty; //List of metabolism effects this reagent has, should really only be used server-side. - public IReadOnlyList Metabolism => _metabolism; public IReadOnlyList TileReactions => _tileReactions; public IReadOnlyList PlantMetabolism => _plantMetabolism; diff --git a/Resources/Prototypes/Body/Mechanisms/human.yml b/Resources/Prototypes/Body/Mechanisms/human.yml index aec9769c51..8de6180529 100644 --- a/Resources/Prototypes/Body/Mechanisms/human.yml +++ b/Resources/Prototypes/Body/Mechanisms/human.yml @@ -50,18 +50,30 @@ compatibility: Biological - type: entity - id: OrganHumanHeart + id: OrganHumanTongue parent: BaseHumanOrgan - name: heart - description: "I feel bad for the heartless bastard who lost this." + name: tongue + description: "A fleshy muscle mostly used for lying." components: - type: Sprite - state: heart-on + state: tongue + - type: Mechanism + size: 1 + compatibility: Biological + +- type: entity + id: OrganHumanAppendix + parent: BaseHumanOrgan + name: appendix + components: + - type: Sprite + layers: + - state: appendix + - state: appendix-inflamed + visible: false - type: Mechanism size: 1 compatibility: Biological - behaviors: - - !type:HeartBehavior {} - type: entity id: OrganHumanEars @@ -91,6 +103,92 @@ behaviors: - !type:LungBehavior {} +- type: entity + id: OrganHumanHeart + parent: BaseHumanOrgan + name: heart + description: "I feel bad for the heartless bastard who lost this." + components: + - type: Sprite + state: heart-on + - type: Mechanism + size: 1 + compatibility: Biological + behaviors: + - !type:HeartBehavior {} + # The heart 'metabolizes' medicines and poisons that aren't filtered out by other organs. + # This is done because these chemicals need to have some effect even if they aren't being filtered out of your body. + # You're technically 'immune to poison' without a heart, but.. uhh, you'll have bigger problems on your hands. + - type: Metabolizer + metabolisms: + Dylovene: + effects: + - !type:HealthChange + damageClass: Toxin + healthChange: -1 + Arithrazine: + effects: + - !type:HealthChange + damageClass: Toxin + healthChange: -1 + - !type:HealthChange + damageClass: Brute + healthChange: 0.5 + Bicaridine: + effects: + - !type:HealthChange + damageClass: Brute + healthChange: -2 + Dermaline: + effects: + - !type:HealthChange + damageClass: Burn + healthChange: -3 + Dexalin: + effects: + - !type:HealthChange + damageClass: Airloss + healthChange: -1 + DexalinPlus: + effects: + - !type:HealthChange + damageClass: Airloss + healthChange: -3 + Kelotane: + effects: + - !type:HealthChange + damageClass: Burn + healthChange: -1 + Synaptizine: + effects: + - !type:HealthChange + damageClass: Toxin + healthChange: 0.5 + HeartbreakerToxin: + effects: + - !type:HealthChange + damageClass: Airloss + healthChange: 4 + Lexorin: + effects: + - !type:HealthChange + damageClass: Airloss + healthChange: 7 + Omnizine: + effects: + - !type:HealthChange + healthChange: -2 + damageClass: Burn + - !type:HealthChange + healthChange: -2 + damageClass: Toxin + - !type:HealthChange + healthChange: -2 + damageClass: Airloss + - !type:HealthChange + healthChange: -2 + damageClass: Brute + - type: entity id: OrganHumanStomach parent: BaseHumanOrgan @@ -108,6 +206,93 @@ digestionDelay: 20 - type: SolutionContainer maxVol: 250 + # The stomach metabolizes stuff like foods and drinks. + # TODO: Have it work off of the ent's solution container, and move this + # to intestines instead. + - type: Metabolizer # Release me from this hell called 1 reagent for every drink + # TODO please make every drink their own base thing + metabolisms: + Flour: + effects: + - !type:SatiateHunger + JuiceApple: + effects: + - !type:SatiateThirst + JuiceBerry: + effects: + - !type:SatiateThirst + JuiceBanana: + effects: + - !type:SatiateThirst + JuiceCarrot: + effects: + - !type:SatiateThirst + JuiceLime: + effects: + - !type:SatiateThirst + JuiceLemon: + effects: + - !type:SatiateThirst + JuiceGrape: + effects: + - !type:SatiateThirst + JuiceOrange: + effects: + - !type:SatiateThirst + JuiceTomato: + effects: + - !type:SatiateThirst + JuiceBerryPoison: + effects: + - !type:SatiateThirst + - !type:HealthChange + damageClass: Toxin + healthChange: 1 + JuiceWatermelon: + effects: + - !type:SatiateThirst + JuicePineapple: + effects: + - !type:SatiateThirst + Nutriment: + effects: + - !type:SatiateHunger + Water: + effects: + - !type:SatiateThirst + hydrationFactor: 2 + Coffee: + effects: + - !type:SatiateThirst + Tea: + effects: + - !type:SatiateThirst + Milk: + effects: + - !type:SatiateThirst + SpoiledMilk: + effects: + - !type:SatiateThirst + hydrationFactor: -2 + MilkSoy: + effects: + - !type:SatiateThirst + hydrationFactor: 2 # soyboys stay winning + MilkOat: + effects: + - !type:SatiateThirst + hydrationFactor: 2 # oatboys stay winning + Cola: + effects: + - !type:SatiateThirst + hydrationFactor: 0.5 # sodaboys stay losing + ThirteenLoko: + effects: + - !type:SatiateThirst + hydrationFactor: 2 + - !type:HealthChange + damageClass: Toxin + healthChange: 1 - type: entity id: OrganHumanLiver @@ -120,12 +305,17 @@ - type: Mechanism size: 1 compatibility: Biological - behaviors: - - !type:LiverBehavior - alcoholLethality: 0.005 - alcoholExponent: 1.6 - toxinTolerance: 3 - toxinLethality: 0.01 + - type: Metabolizer # The liver metabolizes certain chemicals only, like alcohol. + metabolisms: # TODO add the rest of alcohol + CreamyDelight: + effects: + - !type:SatiateThirst + Lean: + effects: + - !type:SatiateThirst + LeanShine: # who added this? + effects: + - !type:SatiateThirst - type: entity id: OrganHumanKidneys @@ -141,28 +331,4 @@ size: 1 compatibility: Biological -- type: entity - id: OrganHumanTongue - parent: BaseHumanOrgan - name: tongue - description: "A fleshy muscle mostly used for lying." - components: - - type: Sprite - state: tongue - - type: Mechanism - size: 1 - compatibility: Biological -- type: entity - id: OrganHumanAppendix - parent: BaseHumanOrgan - name: appendix - components: - - type: Sprite - layers: - - state: appendix - - state: appendix-inflamed - visible: false - - type: Mechanism - size: 1 - compatibility: Biological diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml index cef117e5da..8ca13d7725 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml @@ -67,7 +67,7 @@ currentTemperature: 310.15 specificHeat: 42 tempDamageCoefficient: 0.1 - - type: Metabolism + - type: Respirator metabolismHeat: 5000 radiatedHeat: 400 implicitHeatRegulation: 5000 diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml index 7b5b7dfcab..cbbabdf716 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml @@ -38,7 +38,7 @@ 0: !type:NormalMobState {} 150: !type:CriticalMobState {} 200: !type:DeadMobState {} - - type: Metabolism + - type: Respirator - type: UnarmedCombat range: 1.5 arcwidth: 0 diff --git a/Resources/Prototypes/Entities/Mobs/Species/human.yml b/Resources/Prototypes/Entities/Mobs/Species/human.yml index 915d97340a..a3b82f223e 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/human.yml @@ -167,7 +167,7 @@ preset: HumanPreset - type: Damageable damageContainer: biologicalDamageContainer - - type: Metabolism + - type: Respirator metabolismHeat: 5000 radiatedHeat: 400 implicitHeatRegulation: 5000 diff --git a/Resources/Prototypes/Entities/Mobs/Species/vox.yml b/Resources/Prototypes/Entities/Mobs/Species/vox.yml index dd9f5119c7..1f097c19a2 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/vox.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/vox.yml @@ -84,7 +84,7 @@ - type: Body template: HumanoidTemplate preset: VoxPreset - - type: Metabolism + - type: Respirator needsGases: Nitrogen: 0.00060763888 producesGases: diff --git a/Resources/Prototypes/Reagents/Consumable/Drink/alcohol.yml b/Resources/Prototypes/Reagents/Consumable/Drink/alcohol.yml index b31d59a19d..bff9a06605 100644 --- a/Resources/Prototypes/Reagents/Consumable/Drink/alcohol.yml +++ b/Resources/Prototypes/Reagents/Consumable/Drink/alcohol.yml @@ -183,9 +183,6 @@ desc: A combination of cream, wine and moonshine. Why would you do this? physicalDesc: foul color: "#a6969a" - metabolism: - - !type:DefaultDrink - rate: 1 - type: reagent id: Lean @@ -193,9 +190,6 @@ desc: Turn up for days. physicalDesc: bubbly color: "#9400D3" - metabolism: - - !type:DefaultDrink - rate: 1 - type: reagent id: LeanShine @@ -203,8 +197,3 @@ desc: Lean mixed with moonshine. Turn up for months. physicalDesc: bubbly color: "#9d5fb8" - metabolism: - - !type:DefaultDrink - rate: 1 - - diff --git a/Resources/Prototypes/Reagents/Consumable/Drink/drinks.yml b/Resources/Prototypes/Reagents/Consumable/Drink/drinks.yml index b4c5009d35..baa8792dd2 100644 --- a/Resources/Prototypes/Reagents/Consumable/Drink/drinks.yml +++ b/Resources/Prototypes/Reagents/Consumable/Drink/drinks.yml @@ -4,9 +4,6 @@ desc: A drink made from brewed coffee beans. Contains a moderate amount of caffeine. physicalDesc: aromatic color: "#664300" - metabolism: - - !type:DefaultDrink - rate: 1 - type: reagent id: Tea @@ -14,9 +11,6 @@ desc: A drink made by boiling leaves of the tea tree, Camellia sinensis. physicalDesc: aromatic color: "#8a5a3a" - metabolism: - - !type:DefaultDrink - rate: 1 - type: reagent id: Cream @@ -24,9 +18,6 @@ desc: The fatty, still liquid part of milk. Why don't you mix this with sum scotch, eh? physicalDesc: creamy color: "#DFD7AF" - metabolism: - - !type:DefaultDrink - rate: 1 - type: reagent id: Milk @@ -34,9 +25,6 @@ desc: An opaque white liquid produced by the mammary glands of mammals. physicalDesc: opaque color: "#DFDFDF" - metabolism: - - !type:DefaultDrink - rate: 1 plantMetabolism: - !type:AdjustNutrition amount: 0.1 @@ -49,9 +37,6 @@ desc: This milk has gone rancid. physicalDesc: putrid color: "#faffba" - metabolism: - - !type:DefaultDrink - rate: 1 - type: reagent id: MilkSoy @@ -59,9 +44,6 @@ desc: Surprisingly tasty. physicalDesc: refreshing color: "#302000" - metabolism: - - !type:DefaultDrink - rate: 1 - type: reagent id: MilkOat @@ -69,6 +51,3 @@ desc: Surprisingly tasty. physicalDesc: refreshing color: "#302000" - metabolism: - - !type:DefaultDrink - rate: 1 diff --git a/Resources/Prototypes/Reagents/Consumable/Drink/juice.yml b/Resources/Prototypes/Reagents/Consumable/Drink/juice.yml index 44a75bbd1d..ac4a65a2f5 100644 --- a/Resources/Prototypes/Reagents/Consumable/Drink/juice.yml +++ b/Resources/Prototypes/Reagents/Consumable/Drink/juice.yml @@ -4,9 +4,6 @@ desc: It's a little piece of Eden. physicalDesc: crisp color: "#FDAD01" - metabolism: - - !type:DefaultDrink - rate: 1 - type: reagent id: JuiceBerry @@ -14,9 +11,6 @@ desc: A delicious blend of several different kinds of berries. physicalDesc: sweet color: "#660099" - metabolism: - - !type:DefaultDrink - rate: 1 - type: reagent id: JuiceBanana @@ -24,9 +18,6 @@ desc: The raw essence of a banana. HONK. physicalDesc: crisp color: "#FFE777" - metabolism: - - !type:DefaultDrink - rate: 1 #TODO: port on_mob_life: restore eyesight #if(..()) @@ -45,9 +36,6 @@ desc: It's like a carrot, but less crunchy. physicalDesc: crisp color: "#FF8820" - metabolism: - - !type:DefaultDrink - rate: 1 - type: reagent id: JuiceLime @@ -55,9 +43,6 @@ desc: The sweet-sour juice of limes. physicalDesc: citric color: "#99bb43" - metabolism: - - !type:DefaultDrink - rate: 1 - type: reagent id: JuiceLemon @@ -65,9 +50,6 @@ desc: This juice is VERY sour. physicalDesc: citric color: "#fff690" - metabolism: - - !type:DefaultDrink - rate: 1 - type: reagent id: JuiceGrape @@ -75,9 +57,6 @@ desc: Freshly squeezed juice from red grapes. Quite sweet. physicalDesc: crisp color: "#512284" - metabolism: - - !type:DefaultDrink - rate: 1 # /datum/reagent/drink/orangejuice/on_mob_life(var/mob/living/M) @@ -93,9 +72,6 @@ desc: Both delicious AND rich in Vitamin C. What more do you need? physicalDesc: citric color: "#E78108" - metabolism: - - !type:DefaultDrink - rate: 1 - type: reagent id: JuiceTomato @@ -103,9 +79,6 @@ desc: Tomatoes made into juice. What a waste of good tomatoes, huh? physicalDesc: saucey color: "#731008" - metabolism: - - !type:DefaultDrink - rate: 1 # /datum/reagent/drink/poisonberryjuice/on_mob_life(var/mob/living/M) @@ -120,9 +93,6 @@ desc: A surprisingly tasty juice blended from various kinds of very deadly and toxic berries. physicalDesc: aromatic #maybe should be 'sickly'? color: "#6600CC" - metabolism: - - !type:DefaultDrink - rate: 1 - type: reagent id: JuiceWatermelon @@ -130,9 +100,6 @@ desc: The delicious juice of a watermelon. physicalDesc: sweet color: "#EF3520" - metabolism: - - !type:DefaultDrink - rate: 1 - type: reagent id: JuicePineapple @@ -140,9 +107,6 @@ desc: The delicious juice of a pineapple. physicalDesc: tropical color: yellow - metabolism: - - !type:DefaultDrink - rate: 1 # - type: reagent # id: PotatoJuice @@ -150,6 +114,3 @@ # desc: Juice of the potato. Bleh. # physicalDesc: starchy # color: "#302000" -# metabolism: -# - !type:DefaultDrink -# rate: 1 diff --git a/Resources/Prototypes/Reagents/Consumable/Drink/soda.yml b/Resources/Prototypes/Reagents/Consumable/Drink/soda.yml index eb2b34041b..b28baa70f1 100644 --- a/Resources/Prototypes/Reagents/Consumable/Drink/soda.yml +++ b/Resources/Prototypes/Reagents/Consumable/Drink/soda.yml @@ -4,9 +4,6 @@ desc: A sweet, carbonated soft drink. Caffeine free. physicalDesc: fizzy color: "#422912" - metabolism: - - !type:DefaultDrink - rate: 1 plantMetabolism: - !type:AdjustNutrition amount: 0.1 @@ -21,12 +18,6 @@ desc: A highly processed liquid substance barely-passing intergalatic health standarts for a soft drink. physicalDesc: fizzy color: "#deb928" - metabolism: - - !type:DefaultDrink - rate: 1 - - !type:HealthChangeMetabolism - healthChange: 2 - damageClass: Toxin plantMetabolism: - !type:AdjustNutrition amount: 0.1 diff --git a/Resources/Prototypes/Reagents/Consumable/Food/condiments.yml b/Resources/Prototypes/Reagents/Consumable/Food/condiments.yml index 88183dbfb6..8cdcb5903a 100644 --- a/Resources/Prototypes/Reagents/Consumable/Food/condiments.yml +++ b/Resources/Prototypes/Reagents/Consumable/Food/condiments.yml @@ -4,9 +4,6 @@ desc: The sweetness of a thousand sugars but none of the calories. # physicalDesc: color: aquamarine - metabolism: - - !type:DefaultFood - rate: 1 - type: reagent id: BbqSauce @@ -14,9 +11,6 @@ desc: Hand wipes not included. physicalDesc: Gloopy. color: darkred - metabolism: - - !type:DefaultFood - rate: 1 - type: reagent id: Cornoil @@ -24,9 +18,6 @@ desc: Corn oil, A delicious oil used in cooking. Made from corn. # physicalDesc: color: yellow - metabolism: - - !type:DefaultFood - rate: 1 - type: reagent id: Frostoil @@ -34,9 +25,6 @@ desc: Leaves the tongue numb in its passage. # physicalDesc: color: skyblue - metabolism: - - !type:DefaultFood - rate: 1 - type: reagent id: HorseradishSauce @@ -44,9 +32,6 @@ desc: Smelly horseradish sauce. physicalDesc: Overpowering. color: gray - metabolism: - - !type:DefaultFood - rate: 1 - type: reagent id: Hotsauce @@ -54,9 +39,6 @@ desc: Burns so good. # physicalDesc: color: red - metabolism: - - !type:DefaultFood - rate: 1 - type: reagent id: Ketchup @@ -64,9 +46,6 @@ desc: Made from pureed tomatoes and flavored with spices. # physicalDesc: color: red - metabolism: - - !type:DefaultFood - rate: 1 - type: reagent id: Soysauce @@ -74,6 +53,3 @@ desc: A salty soy-based flavoring. # physicalDesc: color: saddlebrown - metabolism: - - !type:DefaultFood - rate: 1 diff --git a/Resources/Prototypes/Reagents/Consumable/Food/ingredients.yml b/Resources/Prototypes/Reagents/Consumable/Food/ingredients.yml index ee13427ec1..bfeed21bb5 100644 --- a/Resources/Prototypes/Reagents/Consumable/Food/ingredients.yml +++ b/Resources/Prototypes/Reagents/Consumable/Food/ingredients.yml @@ -4,9 +4,6 @@ desc: Used for baking. physicalDesc: powdery color: white - metabolism: - - !type:DefaultFood - rate: 1 - type: reagent id: Oats @@ -14,18 +11,12 @@ desc: Used for a variety of tasty purposes. physicalDesc: coarse color: tan - metabolism: - - !type:DefaultFood - rate: 1 - type: reagent id: Enzyme name: universal enzyme desc: Used in cooking various dishes. color: "#009900" - metabolism: - - !type:DefaultFood - rate: 1 - type: reagent id: Egg @@ -33,9 +24,6 @@ desc: Used for baking. physicalDesc: mucus-like color: white - metabolism: - - !type:DefaultFood - rate: 1 - type: reagent id: Sugar @@ -43,9 +31,6 @@ desc: Tasty spacey sugar! physicalDesc: color: white - metabolism: - - !type:DefaultFood - rate: 1 - type: reagent id: Salt @@ -53,9 +38,6 @@ desc: Salt. From space oceans, presumably. physicalDesc: Coarse. color: white - metabolism: - - !type:DefaultFood - rate: 1 - type: reagent id: Blackpepper @@ -63,33 +45,21 @@ desc: Often used to flavor food or make people sneeze. physicalDesc: Grainy. color: black - metabolism: - - !type:DefaultFood - rate: 1 - type: reagent id: Vinegar name: vinegar desc: Often used to flavor food. color: tan - metabolism: - - !type:DefaultFood - rate: 1 - type: reagent id: Rice name: rice desc: Hard, small white grains. color: white - metabolism: - - !type:DefaultFood - rate: 1 - type: reagent id: OilOlive name: olive oil desc: Viscous and fragrant. color: olive - metabolism: - - !type:DefaultFood - rate: 1 diff --git a/Resources/Prototypes/Reagents/chemicals.yml b/Resources/Prototypes/Reagents/chemicals.yml index 3d6d49be64..ff882e463c 100644 --- a/Resources/Prototypes/Reagents/chemicals.yml +++ b/Resources/Prototypes/Reagents/chemicals.yml @@ -89,9 +89,6 @@ desc: All the vitamins, minerals, and carbohydrates the body needs in pure form. physicalDesc: opaque color: "#664330" - metabolism: - - !type:DefaultFood - rate: 1 plantMetabolism: - !type:AdjustNutrition amount: 1 @@ -180,7 +177,7 @@ amount: 10 - !type:AdjustHealth amount: -5 - + - type: reagent id: SulfuricAcid name: sulfuric acid @@ -218,9 +215,6 @@ color: "#c0e0ff20" boilingPoint: 100.0 meltingPoint: 0.0 - metabolism: - - !type:DefaultDrink - rate: 1 tileReactions: - !type:ExtinguishTileReaction {} - !type:SpillIfPuddlePresentTileReaction {} @@ -234,7 +228,7 @@ desc: Used by chefs to cook. physicalDesc: oily color: "#b67823" - boilingPoint: 300.0 + boilingPoint: 300.0 meltingPoint: -16.0 tileReactions: - !type:FlammableTileReaction {} diff --git a/Resources/Prototypes/Reagents/medicine.yml b/Resources/Prototypes/Reagents/medicine.yml index 16dba85352..c49cc13300 100644 --- a/Resources/Prototypes/Reagents/medicine.yml +++ b/Resources/Prototypes/Reagents/medicine.yml @@ -18,10 +18,6 @@ desc: A broad-spectrum anti-toxin, which treats toxin damage in the blood stream. Overdosing will cause vomiting, dizzyness and pain. physicalDesc: translucent color: "#3a1d8a" - metabolism: - - !type:HealthChangeMetabolism - healthChange: -1 - damageClass: Toxin plantMetabolism: - !type:AdjustToxins amount: -10 @@ -34,13 +30,6 @@ desc: A slightly unstable medication used for the most extreme any serious case of radiation poisoning. Lowers radiation level at over twice the rate Hyronalin does and will heal toxin damage at the same time. Deals very minor brute damage to the patient over time, but the patient's body will typically out-regenerate it easily. physicalDesc: cloudy color: "#bd5902" - metabolism: - - !type:HealthChangeMetabolism - healthChange: -1 - damageClass: Toxin #I think you need multiple of these components for different types of damage so I'll maybe change it to work better in the future - - !type:HealthChangeMetabolism - healthChange: 0.5 - damageClass: Brute - type: reagent id: Bicaridine @@ -48,10 +37,6 @@ desc: An analgesic which is highly effective at treating brute damage. It is useful for stabilizing people who have been severely beaten, as well as treating less life-threatening injuries. In the case of bleeding (internal or external), bicaridine will slow down the bleeding heavily. If the dosage exceeds the overdose limit, it'll stop it outright. physicalDesc: opaque color: "#ffaa00" - metabolism: - - !type:HealthChangeMetabolism - healthChange: -2 - damageClass: Brute - type: reagent id: Cryoxadone @@ -90,10 +75,6 @@ desc: An advanced chemical that is more effective at treating burn damage than Kelotane. physicalDesc: translucent color: "#215263" - metabolism: - - !type:HealthChangeMetabolism - healthChange: -3 - damageClass: Burn - type: reagent id: Dexalin @@ -101,21 +82,13 @@ desc: Used for treating oxygen deprivation. In most cases where it is likely to be needed, the strength of Dexalin Plus will probably be more useful (Results in 1 unit instead of 2). physicalDesc: opaque color: "#0041a8" - metabolism: - - !type:HealthChangeMetabolism - healthChange: -1 - damageClass: Airloss #this may be asphyxiation - + - type: reagent id: DexalinPlus name: dexalin plus desc: Used in treatment of extreme cases of oxygen deprivation. Even a single unit immediately counters all oxygen loss, which is hugely useful in many circumstances. Any dose beyond this will continue to counter oxygen loss until it is metabolized, essentially removing the need to breathe. physicalDesc: cloudy color: "#4da0bd" - metabolism: - - !type:HealthChangeMetabolism - healthChange: -3 - damageClass: Airloss - type: reagent id: Ethylredoxrazine @@ -165,10 +138,6 @@ desc: Treats burn damage and prevents infection. physicalDesc: strong-smelling color: "#bf3d19" - metabolism: - - !type:HealthChangeMetabolism - healthChange: -1 - damageClass: Burn - type: reagent id: Leporazine @@ -201,7 +170,7 @@ - type: reagent id: Paroxetine name: paroxetine - desc: Prevents hallucination, but has a 10% chance of causing intense hallucinations. + desc: Prevents hallucination, but has a 10% chance of causing intense hallucinations. physicalDesc: acrid color: "#fffbad" @@ -225,11 +194,6 @@ desc: Toxic, but treats hallucinations, drowsiness & halves the duration of paralysis, stuns and knockdowns. It is metabolized very slowly. One unit is enough to treat hallucinations; two units is deadly. physicalDesc: pungent color: "#d49a2f" - metabolism: - - !type:HealthChangeMetabolism - healthChange: 0.67 - damageClass: Toxin - rate: 0.2 - type: reagent id: Tramadol @@ -289,10 +253,6 @@ plantMetabolism: - !type:AdjustToxins amount: 10 - metabolism: - - !type:HealthChangeMetabolism - healthChange: 1 - damageClass: Airloss - type: reagent id: Impedrezene @@ -307,10 +267,6 @@ desc: Temporarily stops respiration and causes tissue damage. Large doses are fatal, and will cause people to pass out very quickly. Dexalin and Dexalin Plus will both remove it, however. physicalDesc: pungent color: "#6b0007" - metabolism: - - !type:HealthChangeMetabolism - healthChange: 7 - damageClass: Airloss - type: reagent id: Lipozine @@ -356,24 +312,10 @@ desc: Pure THC oil, extracted from the leaves of the cannabis plant. Much stronger than in it's natural form and can be used to numb chronic pain in patients. physicalDesc: skunky color: "#DAA520" - + - type: reagent id: Omnizine name: Omnizine desc: A soothing milky liquid with an iridescent gleam. A well known conspiracy theory says that it's origins remain a mystery because knowing the secrets of its production would render most commercial pharmaceuticals obsolete. physicalDesc: soothing color: "#fcf7f9" - metabolism: - - !type:HealthChangeMetabolism - healthChange: -2 - damageClass: Burn - - !type:HealthChangeMetabolism - healthChange: -2 - damageClass: Toxin - - !type:HealthChangeMetabolism - healthChange: -2 - damageClass: Airloss - - !type:HealthChangeMetabolism - healthChange: -2 - damageClass: Brute - From e5956566f5faa20953522d0eeecf5c3b46c614eb Mon Sep 17 00:00:00 2001 From: plinyvic Date: Sat, 31 Jul 2021 08:06:24 -0400 Subject: [PATCH 41/76] grindable/juiceable stacks (#4391) * grindable/juiceable stacks reagent grinder now takes stacks into account added ScaleSolution that allows easy scaling of solution contents * grindable/juiceable stacks revision grinder/juicer stack fix now ECS added JuiceableScalingEvent * Update Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs Co-authored-by: mirrorcult * Update Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs Co-authored-by: mirrorcult Co-authored-by: Vera Aguilera Puerto <6766154+Zumorica@users.noreply.github.com> --- .../EntitySystems/ReagentGrinderSystem.cs | 44 +++++++++++++------ .../Kitchen/Events/JuiceableScalingEvent.cs | 23 ++++++++++ Content.Shared/Chemistry/Solution/Solution.cs | 21 +++++++++ 3 files changed, 74 insertions(+), 14 deletions(-) create mode 100644 Content.Server/Kitchen/Events/JuiceableScalingEvent.cs diff --git a/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs b/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs index 2b110578c1..e5e45b5967 100644 --- a/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs +++ b/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Content.Server.Chemistry.Components; @@ -6,7 +6,9 @@ using Content.Server.Hands.Components; using Content.Server.Items; using Content.Server.Kitchen.Components; using Content.Server.Power.Components; +using Content.Server.Stack; using Content.Server.UserInterface; +using Content.Server.Kitchen.Events; using Content.Shared.Chemistry.Solution; using Content.Shared.Interaction; using Content.Shared.Kitchen.Components; @@ -30,7 +32,7 @@ namespace Content.Server.Kitchen.EntitySystems { [Dependency] private readonly IEntityManager _entityManager = default!; - private Queue _uiUpdateQueue = new (); + private Queue _uiUpdateQueue = new(); public override void Initialize() { @@ -40,11 +42,17 @@ namespace Content.Server.Kitchen.EntitySystems SubscribeLocalEvent((_, component, _) => EnqueueUiUpdate(component)); SubscribeLocalEvent(OnInteractHand); SubscribeLocalEvent(OnInteractUsing); + SubscribeLocalEvent(JuiceableScaling); + } + + private void JuiceableScaling(EntityUid uid, StackComponent component, JuiceableScalingEvent args) + { + args.Scalar *= component.Count; // multiply scalar by amount of items in stack } private void OnInteractUsing(EntityUid uid, ReagentGrinderComponent component, InteractUsingEvent args) { - if(args.Handled) return; + if (args.Handled) return; if (!args.User.TryGetComponent(out IHandsComponent? hands)) { @@ -58,7 +66,7 @@ namespace Content.Server.Kitchen.EntitySystems //First, check if user is trying to insert a beaker. //No promise it will be a beaker right now, but whatever. //Maybe this should whitelist "beaker" in the prototype id of heldEnt? - if(heldEnt.TryGetComponent(out SolutionContainerComponent? beaker) && beaker.Capabilities.HasFlag(SolutionContainerCaps.FitsInDispenser)) + if (heldEnt.TryGetComponent(out SolutionContainerComponent? beaker) && beaker.Capabilities.HasFlag(SolutionContainerCaps.FitsInDispenser)) { component.BeakerContainer.Insert(heldEnt); component.HeldBeaker = beaker; @@ -74,7 +82,7 @@ namespace Content.Server.Kitchen.EntitySystems } //Next, see if the user is trying to insert something they want to be ground/juiced. - if(!heldEnt.HasTag("Grindable") && !heldEnt.TryGetComponent(out JuiceableComponent? juice)) + if (!heldEnt.HasTag("Grindable") && !heldEnt.TryGetComponent(out JuiceableComponent? juice)) { //Entity did NOT pass the whitelist for grind/juice. //Wouldn't want the clown grinding up the Captain's ID card now would you? @@ -113,7 +121,7 @@ namespace Content.Server.Kitchen.EntitySystems private void EnqueueUiUpdate(ReagentGrinderComponent component) { - if(!_uiUpdateQueue.Contains(component)) _uiUpdateQueue.Enqueue(component); + if (!_uiUpdateQueue.Contains(component)) _uiUpdateQueue.Enqueue(component); } private void OnComponentInit(EntityUid uid, ReagentGrinderComponent component, ComponentInit args) @@ -138,12 +146,12 @@ namespace Content.Server.Kitchen.EntitySystems private void OnUIMessageReceived(EntityUid uid, ReagentGrinderComponent component, ServerBoundUserInterfaceMessage message) { - if(component.Busy) + if (component.Busy) { return; } - switch(message.Message) + switch (message.Message) { case SharedReagentGrinderComponent.ReagentGrinderGrindStartMessage msg: if (!component.Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver) || !receiver.Powered) break; @@ -158,7 +166,7 @@ namespace Content.Server.Kitchen.EntitySystems break; case SharedReagentGrinderComponent.ReagentGrinderEjectChamberAllMessage msg: - if(component.Chamber.ContainedEntities.Count > 0) + if (component.Chamber.ContainedEntities.Count > 0) { ClickSound(component); for (var i = component.Chamber.ContainedEntities.Count - 1; i >= 0; i--) @@ -234,7 +242,7 @@ namespace Content.Server.Kitchen.EntitySystems return; var beaker = component.BeakerContainer.ContainedEntity; - if(beaker is null) + if (beaker is null) return; component.BeakerContainer.Remove(beaker); @@ -258,7 +266,7 @@ namespace Content.Server.Kitchen.EntitySystems private void DoWork(ReagentGrinderComponent component, IEntity user, SharedReagentGrinderComponent.GrinderProgram program) { //Have power, are we busy, chamber has anything to grind, a beaker for the grounds to go? - if(!component.Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver) || !receiver.Powered || component.Busy || component.Chamber.ContainedEntities.Count <= 0 || component.BeakerContainer.ContainedEntity == null || component.HeldBeaker == null) + if (!component.Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver) || !receiver.Powered || component.Busy || component.Chamber.ContainedEntities.Count <= 0 || component.BeakerContainer.ContainedEntity == null || component.HeldBeaker == null) { return; } @@ -278,12 +286,14 @@ namespace Content.Server.Kitchen.EntitySystems { if (!item.HasTag("Grindable")) continue; if (!item.TryGetComponent(out var solution)) continue; - if (component.HeldBeaker.CurrentVolume + solution.CurrentVolume > component.HeldBeaker.MaxVolume) continue; + var juiceEvent = new JuiceableScalingEvent(); // default of scalar is always 1.0 + RaiseLocalEvent(item.Uid, juiceEvent, false); + if (component.HeldBeaker.CurrentVolume + solution.CurrentVolume * juiceEvent.Scalar > component.HeldBeaker.MaxVolume) continue; + solution.Solution.ScaleSolution(juiceEvent.Scalar); component.HeldBeaker.TryAddSolution(solution.Solution); solution.RemoveAllSolution(); item.Delete(); } - component.Busy = false; EnqueueUiUpdate(component); bui?.SendMessage(new SharedReagentGrinderComponent.ReagentGrinderWorkCompleteMessage()); @@ -297,7 +307,13 @@ namespace Content.Server.Kitchen.EntitySystems foreach (var item in component.Chamber.ContainedEntities.ToList()) { if (!item.TryGetComponent(out var juiceMe)) continue; - if (component.HeldBeaker.CurrentVolume + juiceMe.JuiceResultSolution.TotalVolume > component.HeldBeaker.MaxVolume) continue; + var juiceEvent = new JuiceableScalingEvent(); // default of scalar is always 1.0 + if (item.HasComponent()) + { + RaiseLocalEvent(item.Uid, juiceEvent); + } + if (component.HeldBeaker.CurrentVolume + juiceMe.JuiceResultSolution.TotalVolume * juiceEvent.Scalar > component.HeldBeaker.MaxVolume) continue; + juiceMe.JuiceResultSolution.ScaleSolution(juiceEvent.Scalar); component.HeldBeaker.TryAddSolution(juiceMe.JuiceResultSolution); item.Delete(); } diff --git a/Content.Server/Kitchen/Events/JuiceableScalingEvent.cs b/Content.Server/Kitchen/Events/JuiceableScalingEvent.cs new file mode 100644 index 0000000000..f904097e0a --- /dev/null +++ b/Content.Server/Kitchen/Events/JuiceableScalingEvent.cs @@ -0,0 +1,23 @@ +using Robust.Shared.GameObjects; + +namespace Content.Server.Kitchen.Events +{ + /// + /// Used in scaling amount of solution to extract in juicing + /// + public class JuiceableScalingEvent : EntityEventArgs + { + + public JuiceableScalingEvent() + { + Scalar = 1f; + } + + public float Scalar + { + get; + set; + } + + } +} diff --git a/Content.Shared/Chemistry/Solution/Solution.cs b/Content.Shared/Chemistry/Solution/Solution.cs index ceae2a17f7..8b89f0631a 100644 --- a/Content.Shared/Chemistry/Solution/Solution.cs +++ b/Content.Shared/Chemistry/Solution/Solution.cs @@ -115,6 +115,27 @@ namespace Content.Shared.Chemistry.Solution TotalVolume += quantity; } + /// + /// Scales the amount of solution. + /// + /// The scalar to modify the solution by. + public void ScaleSolution(float scale) + { + if (scale == 1) return; + var tempContents = new List(_contents); + foreach(ReagentQuantity current in tempContents) + { + if(scale > 1) + { + AddReagent(current.ReagentId, current.Quantity * scale - current.Quantity); + } + else + { + RemoveReagent(current.ReagentId, current.Quantity - current.Quantity * scale); + } + } + } + /// /// Returns the amount of a single reagent inside the solution. /// From 58b86addeeb116159c129eff349ec64d730b3d22 Mon Sep 17 00:00:00 2001 From: PJBot Date: Sat, 31 Jul 2021 08:07:27 -0400 Subject: [PATCH 42/76] Automatic changelog update --- Resources/Changelog/Changelog.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 085c58872c..a6500d72d2 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1620,3 +1620,7 @@ Entries: pulling in your hand!, type: Tweak} id: 288 time: '2021-07-31T01:14:00.0000000+00:00' +- author: plinyvic + changes: [] + id: 289 + time: '2021-07-31T12:06:25.0000000+00:00' From c0c18b555be1f57704887b654cc3dfa75b40f1ff Mon Sep 17 00:00:00 2001 From: Vera Aguilera Puerto Date: Sat, 31 Jul 2021 14:09:51 +0200 Subject: [PATCH 43/76] Fix incorrect changelog. --- Resources/Changelog/Changelog.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index a6500d72d2..fb85f4f200 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1621,6 +1621,7 @@ Entries: id: 288 time: '2021-07-31T01:14:00.0000000+00:00' - author: plinyvic - changes: [] + changes: + - {message: Fixed reagent grinder not giving correct output with stacked items, type: Fix} id: 289 time: '2021-07-31T12:06:25.0000000+00:00' From 3cf08af9ddeba876b99e615514d62b6dc9c56d83 Mon Sep 17 00:00:00 2001 From: Michael Cooke Date: Sat, 31 Jul 2021 08:53:18 -0400 Subject: [PATCH 44/76] Fix opposite force when throwing objects (#4398) * Fix opposite force when throwing objects * DRY the throw impulse vector calculation --- Content.Server/Throwing/ThrowHelper.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Content.Server/Throwing/ThrowHelper.cs b/Content.Server/Throwing/ThrowHelper.cs index 7230c6b534..ae4ec3d96d 100644 --- a/Content.Server/Throwing/ThrowHelper.cs +++ b/Content.Server/Throwing/ThrowHelper.cs @@ -69,7 +69,9 @@ namespace Content.Server.Throwing EntitySystem.Get().ThrownInteraction(user, entity); } - physicsComponent.ApplyLinearImpulse(direction.Normalized * strength * physicsComponent.Mass); + var impulseVector = direction.Normalized * strength * physicsComponent.Mass; + physicsComponent.ApplyLinearImpulse(impulseVector); + // Estimate time to arrival so we can apply OnGround status and slow it much faster. var time = (direction / strength).Length; @@ -96,7 +98,7 @@ namespace Content.Server.Throwing if (!msg.Cancelled) { - body.ApplyLinearImpulse(-direction * pushbackRatio); + body.ApplyLinearImpulse(-impulseVector * pushbackRatio); } } } From eee3c940fb833cdde87d82667cf13975ebaa1da4 Mon Sep 17 00:00:00 2001 From: PJBot Date: Sat, 31 Jul 2021 08:54:21 -0400 Subject: [PATCH 45/76] Automatic changelog update --- Resources/Changelog/Changelog.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index fb85f4f200..7a905c6cf4 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1622,6 +1622,13 @@ Entries: time: '2021-07-31T01:14:00.0000000+00:00' - author: plinyvic changes: - - {message: Fixed reagent grinder not giving correct output with stacked items, type: Fix} + - {message: Fixed reagent grinder not giving correct output with stacked items, + type: Fix} id: 289 time: '2021-07-31T12:06:25.0000000+00:00' +- author: michaelcooke + changes: + - {message: Throwing items in space will now push you in the opposite direction + to the thrown object, type: Fix} + id: 290 + time: '2021-07-31T12:53:18.0000000+00:00' From f2a398734337bb70e72e4c6c19972ebec4c79ec1 Mon Sep 17 00:00:00 2001 From: Vera Aguilera Puerto <6766154+Zumorica@users.noreply.github.com> Date: Sun, 1 Aug 2021 04:50:36 +0200 Subject: [PATCH 46/76] Add subfloor appearance data, pipes correctly render subfloor. (#4393) --- .../Visualizers/PipeConnectorVisualizer.cs | 6 ++- .../SubFloor/SubFloorShowLayerVisualizer.cs | 40 +++++++++++++++++++ Content.Shared/SubFloor/SubFloorHideSystem.cs | 19 ++++++++- .../Structures/Piping/Atmospherics/binary.yml | 29 +++++++++++++- .../Structures/Piping/Atmospherics/pipes.yml | 1 + .../Piping/Atmospherics/trinary.yml | 12 ++++++ .../Structures/Piping/Atmospherics/unary.yml | 39 ++++++++++++++---- 7 files changed, 135 insertions(+), 11 deletions(-) create mode 100644 Content.Client/SubFloor/SubFloorShowLayerVisualizer.cs diff --git a/Content.Client/Atmos/Visualizers/PipeConnectorVisualizer.cs b/Content.Client/Atmos/Visualizers/PipeConnectorVisualizer.cs index 9f7e58d31b..36244dc23d 100644 --- a/Content.Client/Atmos/Visualizers/PipeConnectorVisualizer.cs +++ b/Content.Client/Atmos/Visualizers/PipeConnectorVisualizer.cs @@ -1,6 +1,7 @@ using System; using Content.Shared.Atmos; using Content.Shared.Atmos.Piping; +using Content.Shared.SubFloor; using JetBrains.Annotations; using Robust.Client.GameObjects; using Robust.Client.Graphics; @@ -69,13 +70,16 @@ namespace Content.Client.Atmos.Visualizers if (!component.TryGetData(PipeVisuals.VisualState, out PipeVisualState state)) return; + if(!component.TryGetData(SubFloorVisuals.SubFloor, out bool subfloor)) + subfloor = true; + foreach (Layer layerKey in Enum.GetValues(typeof(Layer))) { var dir = (PipeDirection) layerKey; var layerVisible = state.ConnectedDirections.HasDirection(dir); var layer = sprite.LayerMapGet(layerKey); - sprite.LayerSetVisible(layer, layerVisible); + sprite.LayerSetVisible(layer, layerVisible && subfloor); sprite.LayerSetColor(layer, color); } } diff --git a/Content.Client/SubFloor/SubFloorShowLayerVisualizer.cs b/Content.Client/SubFloor/SubFloorShowLayerVisualizer.cs new file mode 100644 index 0000000000..e16e7b22e8 --- /dev/null +++ b/Content.Client/SubFloor/SubFloorShowLayerVisualizer.cs @@ -0,0 +1,40 @@ +using Content.Shared.SubFloor; +using JetBrains.Annotations; +using Robust.Client.GameObjects; + +namespace Content.Client.SubFloor +{ + [UsedImplicitly] + public class SubFloorShowLayerVisualizer : AppearanceVisualizer + { + public override void OnChangeData(AppearanceComponent component) + { + base.OnChangeData(component); + + if (!component.Owner.TryGetComponent(out SpriteComponent? sprite)) + return; + + if (component.TryGetData(SubFloorVisuals.SubFloor, out bool subfloor)) + { + sprite.Visible = true; + + // Due to the way this visualizer works, you might want to specify it before any other + // visualizer that hides/shows layers depending on certain conditions, such as PipeConnectorVisualizer. + foreach (var layer in sprite.AllLayers) + { + layer.Visible = subfloor; + } + + if (sprite.LayerMapTryGet(Layers.FirstLayer, out var firstLayer)) + { + sprite.LayerSetVisible(firstLayer, true); + } + } + } + + public enum Layers : byte + { + FirstLayer, + } + } +} diff --git a/Content.Shared/SubFloor/SubFloorHideSystem.cs b/Content.Shared/SubFloor/SubFloorHideSystem.cs index 6f3f5bd9f2..503a153ea4 100644 --- a/Content.Shared/SubFloor/SubFloorHideSystem.cs +++ b/Content.Shared/SubFloor/SubFloorHideSystem.cs @@ -1,9 +1,11 @@ +using System; using Content.Shared.Maps; using JetBrains.Annotations; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Map; using Robust.Shared.Maths; +using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; namespace Content.Shared.SubFloor @@ -146,10 +148,19 @@ namespace Content.Shared.SubFloor subFloor = !transformComponent.Anchored; } + // Whether to show this entity as visible, visually. + var subFloorVisible = ShowAll || subFloor; + // Show sprite if (ComponentManager.TryGetComponent(uid, out SharedSpriteComponent? spriteComponent)) { - spriteComponent.Visible = ShowAll || subFloor; + spriteComponent.Visible = subFloorVisible; + } + + // Set an appearance data value so visualizers can use this as needed. + if (ComponentManager.TryGetComponent(uid, out SharedAppearanceComponent? appearanceComponent)) + { + appearanceComponent.SetData(SubFloorVisuals.SubFloor, subFloorVisible); } // So for collision all we care about is that the component is running. @@ -169,4 +180,10 @@ namespace Content.Shared.SubFloor SubFloor = subFloor; } } + + [Serializable, NetSerializable] + public enum SubFloorVisuals : byte + { + SubFloor, + } } diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml index f7d23a40a4..d52e24f2fa 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml @@ -33,8 +33,10 @@ state: pipeStraight map: [ "enum.PipeColorVisualizer+Layers.Pipe" ] - state: pumpPressure + map: [ "enum.SubFloorShowLayerVisualizer+Layers.FirstLayer" ] - type: Appearance visuals: + - type: SubFloorShowLayerVisualizer - type: PipeConnectorVisualizer - type: PipeColorVisualizer - type: PressurePumpVisualizer @@ -57,6 +59,12 @@ state: pipeStraight map: [ "enum.PipeColorVisualizer+Layers.Pipe" ] - state: pumpVolume + map: [ "enum.SubFloorShowLayerVisualizer+Layers.FirstLayer" ] + - type: Appearance + visuals: + - type: SubFloorShowLayerVisualizer + - type: PipeConnectorVisualizer + - type: PipeColorVisualizer - type: GasVolumePump - type: entity @@ -75,6 +83,12 @@ state: pipeStraight map: [ "enum.PipeColorVisualizer+Layers.Pipe" ] - state: pumpPassiveGate + map: [ "enum.SubFloorShowLayerVisualizer+Layers.FirstLayer" ] + - type: Appearance + visuals: + - type: SubFloorShowLayerVisualizer + - type: PipeConnectorVisualizer + - type: PipeColorVisualizer - type: GasPassiveGate - type: entity @@ -94,6 +108,12 @@ state: pipeStraight map: [ "enum.PipeColorVisualizer+Layers.Pipe" ] - state: pumpPassiveGate + map: [ "enum.SubFloorShowLayerVisualizer+Layers.FirstLayer" ] + - type: Appearance + visuals: + - type: SubFloorShowLayerVisualizer + - type: PipeConnectorVisualizer + - type: PipeColorVisualizer - type: GasValve - type: NodeContainer nodes: @@ -118,6 +138,12 @@ state: pipeHalf map: [ "enum.PipeColorVisualizer+Layers.Pipe" ] - state: gasCanisterPort + map: [ "enum.SubFloorShowLayerVisualizer+Layers.FirstLayer" ] + - type: Appearance + visuals: + - type: SubFloorShowLayerVisualizer + - type: PipeConnectorVisualizer + - type: PipeColorVisualizer - type: GasPort - type: NodeContainer nodes: @@ -142,9 +168,10 @@ state: pipeStraight map: [ "enum.PipeColorVisualizer+Layers.Pipe" ] - state: vent_off - map: ["enum.VentVisualLayers.Vent"] + map: [ "enum.VentVisualLayers.Vent", "enum.SubFloorShowLayerVisualizer+Layers.FirstLayer" ] - type: Appearance visuals: + - type: SubFloorShowLayerVisualizer - type: PipeConnectorVisualizer - type: PipeColorVisualizer - type: VentPumpVisualizer diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/pipes.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/pipes.yml index df3534b3d3..ed58aecde5 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/pipes.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/pipes.yml @@ -33,6 +33,7 @@ - type: NodeContainer - type: AtmosUnsafeUnanchor - type: AtmosPipeColor + - type: SubFloorHide #Note: The PipeDirection of the PipeNode should be the south-facing version, because the entity starts at an angle of 0 (south) diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/trinary.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/trinary.yml index 916ba7302e..f8b5414d4f 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/trinary.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/trinary.yml @@ -38,6 +38,12 @@ state: pipeTJunction map: [ "enum.PipeColorVisualizer+Layers.Pipe" ] - state: gasFilter + map: [ "enum.SubFloorShowLayerVisualizer+Layers.FirstLayer" ] + - type: Appearance + visuals: + - type: SubFloorShowLayerVisualizer + - type: PipeConnectorVisualizer + - type: PipeColorVisualizer - type: GasFilter - type: entity @@ -57,6 +63,12 @@ state: pipeTJunction map: [ "enum.PipeColorVisualizer+Layers.Pipe" ] - state: gasFilter + map: [ "enum.SubFloorShowLayerVisualizer+Layers.FirstLayer" ] + - type: Appearance + visuals: + - type: SubFloorShowLayerVisualizer + - type: PipeConnectorVisualizer + - type: PipeColorVisualizer - type: GasMixer inletOne: inlet inletTwo: filter diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml index 14be2b9c3a..b85e71e3af 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml @@ -29,9 +29,10 @@ state: pipeHalf map: [ "enum.PipeColorVisualizer+Layers.Pipe" ] - state: vent_off - map: ["enum.VentVisualLayers.Vent"] + map: [ "enum.VentVisualLayers.Vent", "enum.SubFloorShowLayerVisualizer+Layers.FirstLayer" ] - type: Appearance visuals: + - type: SubFloorShowLayerVisualizer - type: PipeConnectorVisualizer - type: PipeColorVisualizer - type: VentPumpVisualizer @@ -54,8 +55,10 @@ state: pipeHalf map: [ "enum.PipeColorVisualizer+Layers.Pipe" ] - state: vent_off + map: [ "enum.SubFloorShowLayerVisualizer+Layers.FirstLayer" ] - type: Appearance visuals: + - type: SubFloorShowLayerVisualizer - type: PipeConnectorVisualizer - type: PipeColorVisualizer - type: GasPassiveVent @@ -76,9 +79,10 @@ state: pipeHalf map: [ "enum.PipeColorVisualizer+Layers.Pipe" ] - state: scrub_off - map: ["enum.ScrubberVisualLayers.Scrubber"] + map: [ "enum.ScrubberVisualLayers.Scrubber", "enum.SubFloorShowLayerVisualizer+Layers.FirstLayer" ] - type: Appearance visuals: + - type: SubFloorShowLayerVisualizer - type: PipeConnectorVisualizer - type: PipeColorVisualizer - type: ScrubberVisualizer @@ -93,18 +97,24 @@ placement: mode: SnapgridCenter components: - - type: GasOutletInjector + # TODO ATMOS: Actual sprite for this. - type: Sprite netsync: false layers: - state: pipeHalf sprite: Structures/Piping/Atmospherics/pipe.rsi - map: [ "enum.PipeColorVisualizer+Layers.Pipe" ] + map: [ "enum.PipeColorVisualizer+Layers.Pipe", "enum.SubFloorShowLayerVisualizer+Layers.FirstLayer" ] + - type: Appearance + visuals: + - type: SubFloorShowLayerVisualizer + - type: PipeConnectorVisualizer + - type: PipeColorVisualizer + - type: GasOutletInjector - type: entity - parent: GasUnaryBase - id: GasThermoMachineBase + parent: BaseMachinePowered + id: BaseGasThermoMachine name: thermomachine description: Heats or cools gas in connected pipes. abstract: true @@ -117,10 +127,19 @@ - type: Appearance visuals: - type: PipeConnectorVisualizer + - type: PipeColorVisualizer - type: GasThermoMachine + - type: AtmosPipeColor + - type: AtmosDevice + - type: NodeContainer + nodes: + pipe: + !type:PipeNode + nodeGroupID: Pipe + pipeDirection: South - type: entity - parent: GasThermoMachineBase + parent: BaseGasThermoMachine id: GasThermoMachineFreezer name: freezer placement: @@ -130,9 +149,11 @@ layers: - state: freezer_off - state: pipe + map: [ "enum.PipeColorVisualizer+Layers.Pipe" ] - type: Appearance visuals: - type: PipeConnectorVisualizer + - type: PipeColorVisualizer - type: ThermoMachineVisualizer enabledState: freezer_on - type: GasThermoMachine @@ -140,7 +161,7 @@ minTemperature: 73.15 - type: entity - parent: GasThermoMachineBase + parent: BaseGasThermoMachine id: GasThermoMachineHeater name: heater placement: @@ -150,9 +171,11 @@ layers: - state: heater_off - state: pipe + map: [ "enum.PipeColorVisualizer+Layers.Pipe" ] - type: Appearance visuals: - type: PipeConnectorVisualizer + - type: PipeColorVisualizer - type: ThermoMachineVisualizer enabledState: heater_on - type: GasThermoMachine From d14b82a1e6d98d5fa4b62f08192bec3dec887d4f Mon Sep 17 00:00:00 2001 From: Paul Ritter Date: Sun, 1 Aug 2021 06:13:46 +0200 Subject: [PATCH 47/76] fixes morgue (almost) (#4375) * fixes morgue (almost) * yes Co-authored-by: Paul --- .../Components/MorgueEntityStorageComponent.cs | 16 ++++++++++++++++ .../Storage/Components/EntityStorageComponent.cs | 2 +- .../Entities/Structures/Storage/morgue.yml | 12 ++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/Content.Server/Morgue/Components/MorgueEntityStorageComponent.cs b/Content.Server/Morgue/Components/MorgueEntityStorageComponent.cs index 0699c6e19f..7f38b3807d 100644 --- a/Content.Server/Morgue/Components/MorgueEntityStorageComponent.cs +++ b/Content.Server/Morgue/Components/MorgueEntityStorageComponent.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Content.Server.Storage.Components; using Content.Shared.Body.Components; using Content.Shared.Directions; @@ -12,6 +13,7 @@ using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.Containers; using Robust.Shared.GameObjects; +using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Maths; using Robust.Shared.Player; @@ -132,6 +134,20 @@ namespace Content.Server.Morgue.Components } } + protected override IEnumerable DetermineCollidingEntities() + { + if (_tray == null) + { + yield break; + } + + var entityLookup = IoCManager.Resolve(); + foreach (var entity in entityLookup.GetEntitiesIntersecting(_tray)) + { + yield return entity; + } + } + //Called every 10 seconds public void Update() { diff --git a/Content.Server/Storage/Components/EntityStorageComponent.cs b/Content.Server/Storage/Components/EntityStorageComponent.cs index cbe6f9affd..9fb9e995e8 100644 --- a/Content.Server/Storage/Components/EntityStorageComponent.cs +++ b/Content.Server/Storage/Components/EntityStorageComponent.cs @@ -443,7 +443,7 @@ namespace Content.Server.Storage.Components EmptyContents(); } - protected IEnumerable DetermineCollidingEntities() + protected virtual IEnumerable DetermineCollidingEntities() { var entityLookup = IoCManager.Resolve(); return entityLookup.GetEntitiesIntersecting(Owner); diff --git a/Resources/Prototypes/Entities/Structures/Storage/morgue.yml b/Resources/Prototypes/Entities/Structures/Storage/morgue.yml index 92e4e35a4e..2415993a28 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/morgue.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/morgue.yml @@ -54,6 +54,18 @@ name: morgue tray description: If you lay down to have a rest on this, you'll soon have a problem. components: + - type: Physics + bodyType: Static + fixtures: + - shape: + !type:PhysShapeAabb + bounds: "-0.5, -0.5, 0.5, 0.5" + mass: 25 + mask: + - Impassable + - MobImpassable + - VaultImpassable + - SmallImpassable - type: Sprite netsync: false sprite: Structures/Storage/morgue.rsi From 746eba632540e96a365a413679a318fcd7d33f92 Mon Sep 17 00:00:00 2001 From: SethLafuente <84478872+SethLafuente@users.noreply.github.com> Date: Sun, 1 Aug 2021 12:05:02 -0700 Subject: [PATCH 48/76] Made plasma grindable (#4334) Co-authored-by: SETh lafuente Co-authored-by: Swept --- .../Entities/Objects/Materials/Sheets/other.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Resources/Prototypes/Entities/Objects/Materials/Sheets/other.yml b/Resources/Prototypes/Entities/Objects/Materials/Sheets/other.yml index 71c81c55d0..6a4d77d8aa 100644 --- a/Resources/Prototypes/Entities/Objects/Materials/Sheets/other.yml +++ b/Resources/Prototypes/Entities/Objects/Materials/Sheets/other.yml @@ -92,6 +92,16 @@ - plasma - plasma_2 - plasma_3 + - type: SolutionContainer + caps: 1 + contents: + reagents: + - ReagentId: Plasma + Quantity: 10 + - type: Tag + tags: + - Grindable + - Sheet - type: entity parent: SheetPlasma From 01b0006735bef848289cbfa32f92ad7bb5930cca Mon Sep 17 00:00:00 2001 From: PJBot Date: Sun, 1 Aug 2021 15:06:05 -0400 Subject: [PATCH 49/76] Automatic changelog update --- Resources/Changelog/Changelog.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 7a905c6cf4..b9fed37da4 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1632,3 +1632,8 @@ Entries: to the thrown object, type: Fix} id: 290 time: '2021-07-31T12:53:18.0000000+00:00' +- author: Seth + changes: + - {message: Made plasma Grindable, type: Tweak} + id: 291 + time: '2021-08-01T19:05:02.0000000+00:00' From ff320b4edc203fafe1d2becac1441d6a0f916345 Mon Sep 17 00:00:00 2001 From: Swept Date: Sun, 1 Aug 2021 15:13:28 -0700 Subject: [PATCH 50/76] Fixes invalid yaml line (#4408) --- Resources/Prototypes/Entities/Objects/Consumable/Food/soup.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/soup.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/soup.yml index d7a7ab1de6..73eb82633c 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/soup.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/soup.yml @@ -11,7 +11,6 @@ reagents: - ReagentId: Nutriment Quantity: 20 - trash: BowlBigTrash - type: Sprite sprite: Objects/Consumable/Food/bowl.rsi netsync: false From 5c5cd601e67a19e9548d5382244d60eae19af509 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Mon, 2 Aug 2021 10:43:42 +1000 Subject: [PATCH 51/76] Fix potential timer exception --- Content.Server/Explosion/TriggerSystem.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Content.Server/Explosion/TriggerSystem.cs b/Content.Server/Explosion/TriggerSystem.cs index 8f1a03fef9..bc598de1e2 100644 --- a/Content.Server/Explosion/TriggerSystem.cs +++ b/Content.Server/Explosion/TriggerSystem.cs @@ -112,6 +112,7 @@ namespace Content.Server.Explosion Timer.Spawn(delay, () => { + if (triggered.Deleted) return; Trigger(triggered, user); }); } From 208f7a0c0d1603e35141cf0530521bcc0c31714f Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Mon, 2 Aug 2021 15:03:34 +1000 Subject: [PATCH 52/76] SetTile during extensions test (#4409) Mainly so it can actually account for accurate grid bounds when doing the test Co-authored-by: metalgearsloth --- .../Tests/Utility/EntitySystemExtensionsTest.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Content.IntegrationTests/Tests/Utility/EntitySystemExtensionsTest.cs b/Content.IntegrationTests/Tests/Utility/EntitySystemExtensionsTest.cs index 08f8a411f5..b33ccc3a57 100644 --- a/Content.IntegrationTests/Tests/Utility/EntitySystemExtensionsTest.cs +++ b/Content.IntegrationTests/Tests/Utility/EntitySystemExtensionsTest.cs @@ -5,6 +5,7 @@ using Content.Shared.Spawning; using NUnit.Framework; using Robust.Shared.GameObjects; using Robust.Shared.Map; +using Robust.Shared.Maths; using Robust.Shared.Physics; using Robust.Shared.Physics.Broadphase; @@ -44,7 +45,9 @@ namespace Content.IntegrationTests.Tests.Utility await server.WaitAssertion(() => { + var mapId = new MapId(1); var grid = sMapManager.GetGrid(new GridId(1)); + grid.SetTile(new Vector2i(0, 0), new Tile(1)); var entityCoordinates = new EntityCoordinates(grid.GridEntityId, 0, 0); // Nothing blocking it, only entity is the grid @@ -52,7 +55,6 @@ namespace Content.IntegrationTests.Tests.Utility Assert.True(sEntityManager.TrySpawnIfUnobstructed(null, entityCoordinates, CollisionGroup.Impassable, out var entity)); Assert.NotNull(entity); - var mapId = new MapId(1); var mapCoordinates = new MapCoordinates(0, 0, mapId); // Nothing blocking it, only entity is the grid From 54227240407c83454b880416af91b18d1843b153 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Mon, 2 Aug 2021 16:21:05 +1000 Subject: [PATCH 53/76] Update submodule --- RobustToolbox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RobustToolbox b/RobustToolbox index 8fea42ff9a..2239c30924 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit 8fea42ff9ac05dfef40f9fec0dfd051ba054dbfa +Subproject commit 2239c309240a20d00c14a400fd2aa7f424b65413 From 331604cc66fd6a58f583fb375d88d1dab41e0809 Mon Sep 17 00:00:00 2001 From: Vera Aguilera Puerto Date: Mon, 2 Aug 2021 13:20:31 +0200 Subject: [PATCH 54/76] GasFilter appearance, EnabledAtmosDeviceVisualizer improvements. --- .../EnabledAtmosDeviceVisualizer.cs | 17 +++------- .../Atmos/Visualizers/GasFilterVisualizer.cs | 18 ++++++++++ .../Visualizers/GasPortableVisualizer.cs | 2 +- .../Visualizers/OutletInjectorVisualizer.cs | 2 +- .../Visualizers/PassiveVentVisualizer.cs | 2 +- .../Visualizers/PressurePumpVisualizer.cs | 2 +- .../Atmos/Visualizers/ScrubberVisualizer.cs | 2 +- .../Visualizers/ThermoMachineVisualizer.cs | 2 +- .../Atmos/Visualizers/VentPumpVisualizer.cs | 2 +- .../Trinary/EntitySystems/GasFilterSystem.cs | 32 ++++++++++-------- .../Atmos/Piping/EnabledAtmosDeviceVisuals.cs | 16 ++++++--- .../Structures/Piping/Atmospherics/binary.yml | 3 +- .../Piping/Atmospherics/trinary.yml | 5 ++- .../Structures/Piping/Atmospherics/unary.yml | 4 +++ .../gasfilter.rsi/gasFilterOn.png | Bin 16173 -> 19883 bytes 15 files changed, 68 insertions(+), 41 deletions(-) create mode 100644 Content.Client/Atmos/Visualizers/GasFilterVisualizer.cs diff --git a/Content.Client/Atmos/Visualizers/EnabledAtmosDeviceVisualizer.cs b/Content.Client/Atmos/Visualizers/EnabledAtmosDeviceVisualizer.cs index b39fb9eed8..24af950e1e 100644 --- a/Content.Client/Atmos/Visualizers/EnabledAtmosDeviceVisualizer.cs +++ b/Content.Client/Atmos/Visualizers/EnabledAtmosDeviceVisualizer.cs @@ -9,22 +9,13 @@ namespace Content.Client.Atmos.Visualizers [UsedImplicitly] public abstract class EnabledAtmosDeviceVisualizer : AppearanceVisualizer { + [DataField("disabledState")] + private string _disabledState = string.Empty; [DataField("enabledState")] private string _enabledState = string.Empty; protected abstract object LayerMap { get; } protected abstract Enum DataKey { get; } - public override void InitializeEntity(IEntity entity) - { - base.InitializeEntity(entity); - - if (!entity.TryGetComponent(out ISpriteComponent? sprite)) - return; - - sprite.LayerMapSet(LayerMap, sprite.AddLayerState(_enabledState)); - sprite.LayerSetVisible(LayerMap, false); - } - public override void OnChangeData(AppearanceComponent component) { base.OnChangeData(component); @@ -32,8 +23,8 @@ namespace Content.Client.Atmos.Visualizers if (!component.Owner.TryGetComponent(out ISpriteComponent? sprite)) return; - if(component.TryGetData(DataKey, out bool enabled)) - sprite.LayerSetVisible(LayerMap, enabled); + if(component.TryGetData(DataKey, out bool enabled) && sprite.LayerMapTryGet(LayerMap, out var layer)) + sprite.LayerSetState(layer, enabled ? _enabledState : _disabledState); } } } diff --git a/Content.Client/Atmos/Visualizers/GasFilterVisualizer.cs b/Content.Client/Atmos/Visualizers/GasFilterVisualizer.cs new file mode 100644 index 0000000000..5ad5468c42 --- /dev/null +++ b/Content.Client/Atmos/Visualizers/GasFilterVisualizer.cs @@ -0,0 +1,18 @@ +using System; +using Content.Shared.Atmos.Piping; +using JetBrains.Annotations; + +namespace Content.Client.Atmos.Visualizers +{ + [UsedImplicitly] + public class GasFilterVisualizer : EnabledAtmosDeviceVisualizer + { + protected override object LayerMap => Layers.Enabled; + protected override Enum DataKey => FilterVisuals.Enabled; + + enum Layers : byte + { + Enabled, + } + } +} diff --git a/Content.Client/Atmos/Visualizers/GasPortableVisualizer.cs b/Content.Client/Atmos/Visualizers/GasPortableVisualizer.cs index 07e62b5d59..77804494ce 100644 --- a/Content.Client/Atmos/Visualizers/GasPortableVisualizer.cs +++ b/Content.Client/Atmos/Visualizers/GasPortableVisualizer.cs @@ -46,7 +46,7 @@ namespace Content.Client.Atmos.Visualizers } } - private enum Layers + private enum Layers : byte { ConnectedToPort, } diff --git a/Content.Client/Atmos/Visualizers/OutletInjectorVisualizer.cs b/Content.Client/Atmos/Visualizers/OutletInjectorVisualizer.cs index dc1681c226..ec23d7b503 100644 --- a/Content.Client/Atmos/Visualizers/OutletInjectorVisualizer.cs +++ b/Content.Client/Atmos/Visualizers/OutletInjectorVisualizer.cs @@ -10,7 +10,7 @@ namespace Content.Client.Atmos.Visualizers protected override object LayerMap => Layers.Enabled; protected override Enum DataKey => OutletInjectorVisuals.Enabled; - enum Layers + enum Layers : byte { Enabled, } diff --git a/Content.Client/Atmos/Visualizers/PassiveVentVisualizer.cs b/Content.Client/Atmos/Visualizers/PassiveVentVisualizer.cs index 01991d837b..d2d08244bc 100644 --- a/Content.Client/Atmos/Visualizers/PassiveVentVisualizer.cs +++ b/Content.Client/Atmos/Visualizers/PassiveVentVisualizer.cs @@ -10,7 +10,7 @@ namespace Content.Client.Atmos.Visualizers protected override object LayerMap => Layers.Enabled; protected override Enum DataKey => PassiveVentVisuals.Enabled; - enum Layers + enum Layers : byte { Enabled, } diff --git a/Content.Client/Atmos/Visualizers/PressurePumpVisualizer.cs b/Content.Client/Atmos/Visualizers/PressurePumpVisualizer.cs index c1441da37b..e0b5db06d3 100644 --- a/Content.Client/Atmos/Visualizers/PressurePumpVisualizer.cs +++ b/Content.Client/Atmos/Visualizers/PressurePumpVisualizer.cs @@ -10,7 +10,7 @@ namespace Content.Client.Atmos.Visualizers protected override object LayerMap => Layers.Enabled; protected override Enum DataKey => PressurePumpVisuals.Enabled; - enum Layers + enum Layers : byte { Enabled, } diff --git a/Content.Client/Atmos/Visualizers/ScrubberVisualizer.cs b/Content.Client/Atmos/Visualizers/ScrubberVisualizer.cs index 6568fab771..e58b0bf8e7 100644 --- a/Content.Client/Atmos/Visualizers/ScrubberVisualizer.cs +++ b/Content.Client/Atmos/Visualizers/ScrubberVisualizer.cs @@ -44,7 +44,7 @@ namespace Content.Client.Atmos.Visualizers } } - public enum ScrubberVisualLayers + public enum ScrubberVisualLayers : byte { Scrubber, } diff --git a/Content.Client/Atmos/Visualizers/ThermoMachineVisualizer.cs b/Content.Client/Atmos/Visualizers/ThermoMachineVisualizer.cs index e9d77226cc..ab7db6ae92 100644 --- a/Content.Client/Atmos/Visualizers/ThermoMachineVisualizer.cs +++ b/Content.Client/Atmos/Visualizers/ThermoMachineVisualizer.cs @@ -10,7 +10,7 @@ namespace Content.Client.Atmos.Visualizers protected override object LayerMap => Layers.Enabled; protected override Enum DataKey => ThermoMachineVisuals.Enabled; - enum Layers + enum Layers : byte { Enabled, } diff --git a/Content.Client/Atmos/Visualizers/VentPumpVisualizer.cs b/Content.Client/Atmos/Visualizers/VentPumpVisualizer.cs index 7db4bc7d5f..cecc544b57 100644 --- a/Content.Client/Atmos/Visualizers/VentPumpVisualizer.cs +++ b/Content.Client/Atmos/Visualizers/VentPumpVisualizer.cs @@ -40,7 +40,7 @@ namespace Content.Client.Atmos.Visualizers } } - public enum VentVisualLayers + public enum VentVisualLayers : byte { Vent, } diff --git a/Content.Server/Atmos/Piping/Trinary/EntitySystems/GasFilterSystem.cs b/Content.Server/Atmos/Piping/Trinary/EntitySystems/GasFilterSystem.cs index a2db87f7bc..2d24c80fdf 100644 --- a/Content.Server/Atmos/Piping/Trinary/EntitySystems/GasFilterSystem.cs +++ b/Content.Server/Atmos/Piping/Trinary/EntitySystems/GasFilterSystem.cs @@ -3,7 +3,9 @@ using Content.Server.Atmos.Piping.Trinary.Components; using Content.Server.NodeContainer; using Content.Server.NodeContainer.Nodes; using Content.Shared.Atmos; +using Content.Shared.Atmos.Piping; using JetBrains.Annotations; +using Robust.Server.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Timing; @@ -24,33 +26,35 @@ namespace Content.Server.Atmos.Piping.Trinary.EntitySystems private void OnFilterUpdated(EntityUid uid, GasFilterComponent filter, AtmosDeviceUpdateEvent args) { - if (!filter.Enabled) - return; + var appearance = filter.Owner.GetComponentOrNull(); - if (!ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer)) + if (!filter.Enabled + || !ComponentManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer) + || !ComponentManager.TryGetComponent(uid, out AtmosDeviceComponent? device) + || !nodeContainer.TryGetNode(filter.InletName, out PipeNode? inletNode) + || !nodeContainer.TryGetNode(filter.FilterName, out PipeNode? filterNode) + || !nodeContainer.TryGetNode(filter.OutletName, out PipeNode? outletNode) + || outletNode.Air.Pressure >= Atmospherics.MaxOutputPressure) // No need to transfer if target is full. + { + appearance?.SetData(FilterVisuals.Enabled, false); return; - - if (!ComponentManager.TryGetComponent(uid, out AtmosDeviceComponent? device)) - return; - - if (!nodeContainer.TryGetNode(filter.InletName, out PipeNode? inletNode) - || !nodeContainer.TryGetNode(filter.FilterName, out PipeNode? filterNode) - || !nodeContainer.TryGetNode(filter.OutletName, out PipeNode? outletNode)) - return; - - if (outletNode.Air.Pressure >= Atmospherics.MaxOutputPressure) - return; // No need to transfer if target is full. + } // We multiply the transfer rate in L/s by the seconds passed since the last process to get the liters. var transferRatio = (float)(filter.TransferRate * (_gameTiming.CurTime - device.LastProcess).TotalSeconds) / inletNode.Air.Volume; if (transferRatio <= 0) + { + appearance?.SetData(FilterVisuals.Enabled, false); return; + } var removed = inletNode.Air.RemoveRatio(transferRatio); if (filter.FilteredGas.HasValue) { + appearance?.SetData(FilterVisuals.Enabled, true); + var filteredOut = new GasMixture() {Temperature = removed.Temperature}; filteredOut.SetMoles(filter.FilteredGas.Value, removed.GetMoles(filter.FilteredGas.Value)); diff --git a/Content.Shared/Atmos/Piping/EnabledAtmosDeviceVisuals.cs b/Content.Shared/Atmos/Piping/EnabledAtmosDeviceVisuals.cs index e95188cc0b..5f3883a25c 100644 --- a/Content.Shared/Atmos/Piping/EnabledAtmosDeviceVisuals.cs +++ b/Content.Shared/Atmos/Piping/EnabledAtmosDeviceVisuals.cs @@ -4,31 +4,37 @@ using Robust.Shared.Serialization; namespace Content.Shared.Atmos.Piping { [Serializable, NetSerializable] - public enum OutletInjectorVisuals + public enum OutletInjectorVisuals : byte { Enabled, } [Serializable, NetSerializable] - public enum PassiveVentVisuals + public enum PassiveVentVisuals : byte { Enabled, } [Serializable, NetSerializable] - public enum VentScrubberVisuals + public enum VentScrubberVisuals : byte { Enabled, } [Serializable, NetSerializable] - public enum ThermoMachineVisuals + public enum ThermoMachineVisuals : byte { Enabled, } [Serializable, NetSerializable] - public enum PressurePumpVisuals + public enum PressurePumpVisuals : byte + { + Enabled, + } + + [Serializable, NetSerializable] + public enum FilterVisuals : byte { Enabled, } diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml index d52e24f2fa..c62a227933 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml @@ -33,13 +33,14 @@ state: pipeStraight map: [ "enum.PipeColorVisualizer+Layers.Pipe" ] - state: pumpPressure - map: [ "enum.SubFloorShowLayerVisualizer+Layers.FirstLayer" ] + map: [ "enum.SubFloorShowLayerVisualizer+Layers.FirstLayer", "enum.PressurePumpVisualizer+Layers.Enabled" ] - type: Appearance visuals: - type: SubFloorShowLayerVisualizer - type: PipeConnectorVisualizer - type: PipeColorVisualizer - type: PressurePumpVisualizer + disabledState: pumpPressure enabledState: pumpPressureOn - type: GasPressurePump diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/trinary.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/trinary.yml index f8b5414d4f..55b541dd2a 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/trinary.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/trinary.yml @@ -38,12 +38,15 @@ state: pipeTJunction map: [ "enum.PipeColorVisualizer+Layers.Pipe" ] - state: gasFilter - map: [ "enum.SubFloorShowLayerVisualizer+Layers.FirstLayer" ] + map: [ "enum.SubFloorShowLayerVisualizer+Layers.FirstLayer", "enum.GasFilterVisualizer+Layers.Enabled" ] - type: Appearance visuals: - type: SubFloorShowLayerVisualizer - type: PipeConnectorVisualizer - type: PipeColorVisualizer + - type: GasFilterVisualizer + disabledState: gasFilter + enabledState: gasFilterOn - type: GasFilter - type: entity diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml index b85e71e3af..f628e58595 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml @@ -148,6 +148,7 @@ - type: Sprite layers: - state: freezer_off + map: [ "enum.ThermoMachineVisualizer+Layers.Enabled" ] - state: pipe map: [ "enum.PipeColorVisualizer+Layers.Pipe" ] - type: Appearance @@ -155,6 +156,7 @@ - type: PipeConnectorVisualizer - type: PipeColorVisualizer - type: ThermoMachineVisualizer + disabledState: freezer_off enabledState: freezer_on - type: GasThermoMachine mode: Freezer @@ -170,6 +172,7 @@ - type: Sprite layers: - state: heater_off + map: [ "enum.ThermoMachineVisualizer+Layers.Enabled" ] - state: pipe map: [ "enum.PipeColorVisualizer+Layers.Pipe" ] - type: Appearance @@ -177,6 +180,7 @@ - type: PipeConnectorVisualizer - type: PipeColorVisualizer - type: ThermoMachineVisualizer + disabledState: heater_off enabledState: heater_on - type: GasThermoMachine mode: Heater diff --git a/Resources/Textures/Structures/Piping/Atmospherics/gasfilter.rsi/gasFilterOn.png b/Resources/Textures/Structures/Piping/Atmospherics/gasfilter.rsi/gasFilterOn.png index 891eface878691c8b320b85da128b36823b53629..06cdacf03e2675e7345ab72531ad0fd38353faec 100644 GIT binary patch literal 19883 zcmeFXRd8HO(k|L!mc?LM%-mvTW?9VKVrFJ$gTP{DW@ctt%nTMYOTO}+J@e1RJuiRU zd)}raR#mUctgkY^%vBIk5sLB>Nbn!w0RRA!l%%NAd+q<{frWlAd-)AV0RRd)PZbSk zB||qNdq+D{3u_ROvxhy12;^>I3IMn-m#1ld#p9|i+o+E8gNwk`Ko+m94QhRP=K5OJ zTq0ARD6149OM%Yh?vCiM`TP8L!{f&9H#1$D{@`Sp5^CKScs7-VhiskuO4qjrbxyxb z{mI6M->nPdqj<4NyuY6YVSesB9UR~7LEj6+WNxqE#SG%TzT&a*p6TC9PGZ=CzQ_zg+giFXY0G`8Jh z`|%K-!CX8U$Jo6nPHe;?l;1N}UL0SN!S2#>>DpzCc+ZZED%_#G6tA4w-)b?RC zt1xTBwe_Sl^KkSrz>}}CCCRM%!0Y(2#SlTCmtLKFbet~JUfk~$yR)->dY#$Bs`842 zHe+;{+8xnC-p>gWzWUih;u7}Z@p*^w*IaJ5d!LSmLF5lIKn6>vT`fL4>xrbgYBK|AUb!jr{n(I#s}E;wFpDsMi*I^}oew2stC6R!%F z={3e6O)8S7U6zvKj_guEm7xF^hINq`p-41S|0kKUYVu6sNb3AFNMHKrPmW{M2Re@9 zKqOR3h}V(=5_D+ZWfg%+i#Xvy1d149P++M zFy+Rwuic=hC0p!knx@+YtYgQt8Q2@2j$c(2Zs$E4geqlS zC_L_=Ce&iQwj}LO>0S5TTOP!5c^=trTl3hRQv7}QnQ+vH(Q2meywVDjIvMz@cXc#O zHrTN?zwf|wtNZ{jN%xR+-xSCas7K$HCo@p*p7nIL^SQAvtoF^N_>NBrZ1pmxL7cF- zp-FbloP0F@^Un(P$d%_YV=WV@p^NgflP~)!7_saO6|G9u^jAg^!>!ZW>#IX?%}VgC zvD4j?Ye{zT{yHnW#y&?}6+fp7e0$iLj+TF}H#};8T-VMS`gkyXet(wLlFp|@+uDaO z_$s4bE>p9wc5WLe87DYSS)D)R=Pqz!%&u;;2+a=uG{4N$%S<6n=jteCGfG-n%B8%F zEGOMju%l|IBj@nM=c)_m(aej*!ALr3Q~2#WrP8{?sjhIJ;Js}Ui^Nj!XF5dpRS6t# zi*>0FukIpft+9Po`i*#!Ug@+`V(Xk(J-)A;HN0P6r1jQ_F&Qj8w#90LBPyQZ8}^wf zY?)=HOZ)8U8E{#&qj9s6Ld1%3GB8;lP(}~su@VRGa25JGP_-m|ZQ5YKQWIME8~m90 zU%mU7#IIV9$3M6>=XNu}@eyqDBxNCX7Czet?J!8*)XtiYgPx`eFRkjTLuIIVY~5mF zUBH*%o{{bMKD}-iN434U?*eqaYfDL1SSQsXlP=hTJ$Cg+G;|0mu1^tyY#)$DwR6kr zNePy`q*_C2bjFJVm@n$L%_2~Pt!=>RTMKsJb%I>7;K7sEn-6@+a-%*APiEt zxI5U?vE9WrEm^?z&h4eNV5c#7=bkvzpOg@DNo@&L0m*k>pSq6dJipSFGYMUUYad z5VpE$4-Dj9A$m9j5c2m{w(hk$*>OtI;sW(*3w3N_X_F^r40CN{Rj>v%^Td#sELCw? zVIVJ@^GHckUkxoV<+js$mP*v4TbBa@mu$nyB@r)9xE1S891=X&mP1KpHh$iHZa9mm zj4^TonwM>SsLfs8wuH*Bl)Bb z*i(Fnwvx}$OmkXDLv=W~o*;=|UC?~tcCU-WH;QFK!K?=FYv{U4I5fFMvs|CHTt0e={DBV5hsSx=dopvBhfYgHKM4epNZYjPN5Bw2m{v1tfRwO zCy@tygk$ZC3_m{eF>V}w4XA0w|`A$yjx05RT zM?vI9(Q9Xo7Z==Bpdg+GCOd?tRvB?lo8%P>oT4DXjTw6-O~3d8>hhNhFOr>b`#^kJ zhbQK5Kt2~kqTOuC8|BYzk{wgXUr1;_(F?SKTVk^6=xZhr%U;^uIc+0ey>|5n69o?S zkH;3GsVIV}1b1cTA`YRZISLCK&x8G(x7^JIHh1WlprV|e_7+`iLhUI9=e=z|grMesP0XrIP0B;Jq5^6T8$-SOeI!J&u^4 zgQDRNx=qf*!V7N)IM}1TtS(HN&d}knhxOX~6E4=wL>4&vN0Q)K?Wmp|!f2M%PZzpm zou%Ib>G}x01`WbJmJE=?A=gu#JzBZ|;iTp<2h#28=~JekU}~VbZl*q@!ueZ?S0E99 z1VbH}ffdBi^9;x2ekU!6enK<`Mu!po{q!p%RM)Yk=ufNRaL*s(=mNX}1LCZKkzc?r zu{=J?JG$fzFxI+>0~Gl3&0m6R&_dFxJ;WR({EYYm2&{w$l96@^ z3xFA!bzPXQilpACRbUVaB}?}i!;=P{E?kIC=#yDe>_c4|pTI2K`Ps5%=w=Sg5u~crLUwsJ zjuQ ziA-X?pcH^g6*%07nhJMO9~#v=Zb{hyA$q2Tq9}N0$<`t)UZ;B@ND2rC+Xq_F5XSU) z+hA_sLh)|()2zclUn`rqW8>st-hGjg-+s0pIf_+HFRP(|sN_$QSmND!EDyn0#1MsF zO(z4CY+UlkM;Nw1YUAVUWk_||+vUPZJavwKF1X>BT~`#AE_guQH7aICA*vri8}^qTJZI)M`6t#`DnS`XIKsaI2n* zO-74v5ZDM(LK+rpb58=ECbZNbsf#&L>E>pY433Od!zRrlYMgAEo}xrWKZzA$RgS|Y z4x=kk2_sKeOxENjw_r6snSUBe3LDn&B@|S7YCVR7)W%d@lS#1mB$x<+D{{iY5Xohx z5<4S6m^OW^G#_l}Kz=<=?E6>4J^-wasLBpBTzH=1s;3b(q>u1Q$|=y4KxLG~A;<~a z4@-^){sTENMQ0ll_&_m6jcN0NAgG$6O)n{c&{&lU+PgC^*DI0u&^`w6>;A&zBqFRI zbF+X)ov}l&(Q2ewlgR&DCKCiT_Tp(&^QNXf^Qf;dB)SDPMI+C&~_1#BkXn;M@gs0*qLDBOPIhIF;mCZhcUnVF@0Oek# z@%@70Ne=l_As31{>l~Y3&fHDl%A=tg*JnE=ON*`x*4xvAE?v?gxvi!%0K-obY9W@%_ zAj|8&S+Y0q^fxl)Sh9B^8$}>jLRAkIFEu$P*~q`Lje1`fcYail`>>4^nOQ?3OwP+! z4Dc$clzgn{{Mwpkl#N{-A4BIQkY5)pq?f6Fas~wvAzed=Fky$S^E#T|zvRtpz3cGo z4*$jbh(Syo=i|CG3YA42xm^|fbX7Skx4cG#O!JCzr1e1Yr$=n@SC!z$Jh*3_Tbt#` z9DUq-PbS+M{#kEZjCuOzC+v9}lXU^Dd&a1*`?{)EPM@zBq&l$31iq8%TP5u0lRP>J zUCDXe+qm44?SF`L$;DRwN zlSO@qGTe!?|85ekz#{E9r$h_LdNjJ%-ocHU5&rSgbDYn)?Rx%NyILlurRXLhspNB z^Ov~EJ)ZdAVjT?~w7akT`d;co$C)(ep<^D8Wi8Q@ECgM;2qIbzH6;}pkgE-Y2)S#x z&J^mTq5fG(i12;nJl%`YIBjxY`E@>ak?RWm_*VAX0AQv~l4=hdITBQ{EkUjG5(+B% zAZdiq>wGL!UEYRM*i{5K#fyR+ZkG$?ZG81fMm4-8+_xRZQ52QtY|JX+d6OjEMt5ZJ zTMh7&zqMX7tQ3w3T!C<3DM#a5ft}Mx+jM+iB~-Wcg-!Tp;s7Ezf+{-1J`#x@mg4?l z>T*l6(Fl35jk)S`5vbAK1K?#OvW^B7-|q^_YZsXTooa6gM8+mT9LHE^3jbmUf#amfSB9F7%f6Sfmt&I#Y!%C zz{xPhIQeIN>$%F zXyYR&7gZ`1hb>qt#e1T;Vfq~kJ*j_!ei$RF@--ublk4_gqwUuL6{F94LZ^8UhzqHs zB(*2y6LhR>!_ea9oufBcyifu7AeUs?!pg@Qv?N0SIN%_5vDr-e($5GA zDZP<#@e9tB)g0jcrRF#TCo<}*ap1Hk8{k?2V2Lz<3rz9v{&oAlmfN_u(6ddduYGrA z9R#MFyOLN|9V9Oh@MLX^C1((Vg6B9>gtDbH%9AL(c7U1UO^tAuEJccsSH(6_*puOw zXHSGZ-7_>Lk2QtrCmiwi3s&o2Lc)fVIds5``Jz!6nxXQux6hP8istgked1j@ZR{Sf z8%Ch3%{>0^P>V`oOH>L0cl(N$_*7Y>@nl~XD!!M?%w!}ZnU)G8vJ4NR8d6xo-CMQ_ z;k4`IP{`o-V4TTMSR4cP9^*}6_5=G?q4~2H^W?L3x>c^f`kfnM4Xd`R@PLNP=-CzF zV$6mpus#sgPB>@Vm}}7dj2#2tn4M4}^BSx#`!VSJ&u{CW{RtCNW#D^G=PFWbOFr!AB(u)5F6i}EiWHgv- z6br7PeP~Erd`a z%jkMz8=5lo8c9!eBL6rN0dVcxm)&4*rjtDOT20M9-e($IH&qvYAqDirf;R}XPmnL9 zl&pT<^qu^&&I`HRR_n7X{NK&mk7_ybxos_9XvD-_sX$!{qA{K6&li zUd+Mtd1dXOpY~FzOep#z+y)YcnM(BrMM4GMpb*cyeSr!CNK|qKTIX$-!u|{_{-v}j zT!}i(mil5b6Ks|`&}ok0+tdQ{1{bPY~O{D;BN;n~1Qb4`N=am8=U{_A?=^9W?n zBdBkp1nK@o%L`h<`_v{Gl|wIZ$@*mGdS8~jsGADob#13$0$9E#5lWs2#Sb8Qz&w_nInOi z<|mVONc%Uh%c6LRK!P7S-jEMj@?>Bc7n@O@ztsmbJ1v`1HV!P`2AOjoZPG6@N22#z zxp!7bo|?r$H;t+{$C?(i)}A8baY{RtBZmUBxZ{Kg)19R`le9k;N-n9^u_Z-Mgh?Vs{zz9HCf>%Hj@a*;O$Ph9bha%^3EDO)Ftqw*P>ZhGq;? zOuB`^rd4CGx%#w~L|m}VEj{dTl=q^5Ibi(lx^}5uV=Z_0d~1;dk69nHQz7O{nxDB+ z_-NLsTg2s>+`1dtnEoB&YVC;m+biVt^AGLof(&zg@fLmQm`s_?jk%BGVcOmmt8b36 zOWw-XFFRbO*KYJd3$X!@t%==MgIzDWqnq`{2ENnGM0tP_<2FrO1gzY&^`5>2K{LcF z-m`{F0wry|tD~t~H1>kEF4C1$alx7L{+nCLrsjUI^QY6QCk}3QKi;ypnAmZrx#VqB z7Y5#~BCtF)s69ZNYwcd0Dg8-_@O;e<2Z98_E$h z9~|cpI=g)PbnyjUO3)%-ZH z^SV!3)+4Lvv1pmmxmk2?hCyNxFPOK7fBcJMF@vQlJOsDzdY9k;8=GOSg_q(iIuR4w z<#iEPw@c_DZ=Qc%d1b~qF*h5a=E0MmuoDG8Y(CV=bWzZ^L0*p47BQ8)H1FPQM1X8N z{VJeIfIf1qIoZS9@7rf~gzlh8n(Nm*AC1m&e#!$Kosu86m|>UB!m^}DY$@O!wWt;t zJ=Iva3PH+cESIa6s*dn#3vj=U40&%z=sXBaN@}Ye8kff8519t?rW^AgS5fv21NxW} zOzh@jDpl24zp;M)>@tu&0GVA0P=;O-V<;R5gRbDN?oY>#`>I5$CTLQ6Vb{CjbaBWW zBlPsMk>iCBAv8(82>k+&k=eUYcw=n>V92%^GHx09``JS6nxgqgUtPpk^;~{GHNXwC zZJIbr=kzo**zNW#P%Of_#K!nLa`2f3H&-+_9%Su&7IrK4IJ#D;<$eCRP|DXB%APuV z7Co;OY%;cQLPi714jl}MKhYhf?T47_jHQlfuYzzy@QcDB9ZrZn1&{K1mt|hQ>l?n6NjV*1@;B56eZW(VsR~=jOUcqMkTpWGeSC+!H zj6|~NysM77rFpM@l)_bC%9!UaWmBpFyHrlsjHRMh9G8JJR1>?A4QkC~P5%R7Nh&nU zr8{(*DH4)V77;-yI|RcA?a|Yvyu9VzRCF5Msv)o7TX5MY62>1TDRsI-^e{~V?ylW{ zjkqdOU*D>H3zNl6U8AEYlJ0n+Y9aUPVik~nHU(5_MItN~0{PeoZOG)B30oV(7|ss0 z4E|+3X5eN}T=CXs{#)^_PcR`8|mB}uvWD7lBhdJy?3T|^6+=h_scNK0J8 zwuA+zi&fk4p8{6ld-kcj4QaKIM|GI`N(itjigJl2W{;y^XQjrUg@3hjTR34eqL%n7 z)3R-xDS4GAywxF{K6ivi5^Rt-5w)P!f|`teJ^5-@+!^8y)qCa`9Gb>Rsm7FXnS{h2 zd~$qpM8;(${AfnpSW}E!Dt^TVgVlAVga{-YGbhL4&bA-THNh&pt%!Lf?J9uQXZCjf zr~`cmn;KeF@ftXK{0W(#$Sf|e&zIDqEv{2FQEYe-!#&4G@<)IC&zRO-rPeUV6tJGC zbk=dQ#ezEmOPldg#-;>r!6DZ4;|Cgn%z%6G$Ih!6#D+1!Us)+5 zWU_GZ1>)IgVJ(+zw1{D%>Tjh;PL3;)=tHlcb3{evnd%kPHjCtNseesl9At(9rNr2} z_~)bUOB=5jd!6Bbl53V~Ept{)CyDdcAq1X$T$Hu{US3eul5@I7f%O{=cfz)X)<_xh ztKW^^buYu5*T`1Vp4zp8R`c%9mcvq4(YA74wQZ0@Z~Wtpk|osWBWxv<+&olZ0S~pU zDs$<$V&Mf&#;ugKjphRXxUc|19HukyoVKEqzRA~?EZL8x>ZSP0<{4UPRCWFpT}#Iu zpdxG$mh9;ML%G(Tnn!`(>Q??3UCymx*=y9tYeqF|UsdC`Ftl3uvHeoiBli|S2`@Y; ze`5c}Wd1WtR(ezbMH~L)TZQlIPqxcKs~n_q9{yH}j0fuutJGP6Rc`}d+tim1Dc5kG~yS)Yd=MxXxczYEGMa0*ovQqK;tTq| zNV`T)qOn!zD|czex#`uh8wba;LTun;c97Bg9;s#CHgY0E7$&~}b;*cAm#3%XZKe2eb)7&#(?28Oy1v6SK-~SBW@cf>Sz8siM zE(b)DM!lNzNfT`ftSqYu${{8z$HkcvuP|U&D1dX#5c?{YyVMK+f@?JO=E4c@KwQU^ zN)T-#;V)GHGty1mK2qsGbRwpuD|l+v^VI<+eg2|AMZ)w-$pXg4OLqIV6|Ao)3&))J zWa?L0gfqp8>Np>zX1vvL`LV15$og4AQtBPBY#rtd zk=C~T&r=8nWn1jpT#&+6!0a8ngQ*Q>B^W?P>V&t~^pY-+n5p_jYhPbB)xO!y!X`12K@SO#H;@ox`wE|Gh# z1QCO^yphrC&-smwDA3RIMpgf!Z6qwrW)3{6Twcz0F7J5?5+A5*&jI9&Zp*K2avcic zVt5N7xuj$DF_Te&cbezHuMO*8E7HUu2GI27EY~lPZuJdSePnwc`Re{DI8SIN-l*m_ z8m^d`T@2m|_cU#u%Zl9TTm~v17oNE5n#>jhe~8V*>Tzt?m#UIv$Lr;#qQr+lfH8Qf zV5}CeoJ$1;U*79bR9;R<=VJyq7$Duj%O}mQEkly&WH#4|>TNJL{mo*kZT==Z$nwqp z=4J)C#A8Od-9Nc_*e2f{f5l#AClGFw3-oQwNEK~{pD95w0zwn-x7252c4?S$hIu}^ zidZ|-pL*(DL4D}k-z3>C?6jt>a5~bS2om?$j-gW_=~%N;)#TcWW7od_WW>J%;|gLa zZp?=F3I2-qoC`AiNJXJa1h=D!A{|+?Wuw72S?6M6I}>_6150im?8i-al-4-ij9r&~ z0-A?6L+4^}4_#{PhgFGOv>FDekcI320DQP1Ec$&d+5AY~w6SPg9bXW0;vNEjs9TMB zmV}OLGSkg>#ER^OD#KQpj#@8&8V<$TKlF9N0351pR`BY3odBZxG#Bgx`$=k>-&VFV zU!3w14WvOG_SNw()QRI8*tp-uA+Cw52_f_gDjTRIN1}3OLsy$o<|=E0iFDmtpNLSM zt?|pstqPZ22yyL~D$qp7C&cB5&?U@x`8Q!DUemmsA@OrVmZL71hny}vdkvF=+4GMF8)m<8I zn_WAziB>BFqqi|G+m#V}UO00ydfBngcASV%I>ZMZyMcnb2ch;JWr z3Kyd~nx@|hjyEX6fEy8f0o6uo<_jq zk1a~MXDNwXxzta`0iDg@^MAd4kucHKjE(r{LAm(_v1f@$oVznbqDN%NsP#?&-AZ*_ zbQr!A=d35V>gG^|1@>O0V){p@jF@{=L6|Gn2(`-X%cA9|9ZVb(jS35xaZF!V{HBE1 zY+I(T>dyD@2YbNkw|$zrImJR&G0Y_)L&;UeW)$>t5f=L3=d51xao-CnI5wFg69Q4= zuj`76XW?^}WBswXr*1Y(O58TsJ>IB%YHsOvpY!6O2%MCDczc({_(^q*xgjD7UrMZf zzK;D$J1(hA4Xs$nGVIz?!anB7$P)9$X-ygB>^bJ3(%wnjy*vLv!9~=D8$PCsYjAWWM=)nt^PRcY+(e$h56wq+paKt+!F4008ZScEzJfy8 zgYDNc> zkuD1ysb&Y}chJU~*5fc72lXeI7Cq}{V@TLD@bMNKKf)E)Ysd=$2zAdDeG33fX5PYz z)(!083N%B-N=glk&KUqgA=G*#cDImv_qyvD*GC!auoJ3fVRc>WnbQaGb5`{Vnv)Y! z9N~NW+2xs{nceRUi`#n$a7Rba7>-|G-!A}uw0OVhry(cHZER=5U}$1z1Y&Tvv46h^ z2mtU3xZ4{VTY;R3j6h}TGgAIeQV1xrL;sBS_g(Ud7nc%9zW9 zM1UWj*PZ(vzy{=ONaSu~ZR^DC&PVbWF8BNYKh2CJM1QF`Tk(--$SD$u*g1lTSQ%Iu zfb?SS7Ou=B{P0A)jwYtuN}}R_L%hH7k(fI>+jBEAy1BVAxUn$UIhrvtadB}m0+|_^ znd#p(=$$-loekaTZJkK}K>Pzk6y#*=XkqVcVP{M92h-5V&c&IJgydaL^iTV2?B(SC z1#j#0Hw*84FuEJsGcqv%8EtGB|GkHkvzY5U$lnJ2AA2~dyx*5(R028Kxi}hw#9TqP z&ZPeiAt5EF_%DP%E}2=_*#G6!J9+;xn2GVf^z2<6t^d+7F=hl=gKXYGo!;$Z`VW0) z3)BB0>pyJ!Bl*jne>dcPxPRgQhxR{x|4aE@OHPhk)Xv!D&!AGGd?bJD#q?j$kc?DgPq=xi=B<06=cjzZv^CEp=V~|WH&VA3?*F5zVqpVPb~gM&8q>Q$TtH?vCKjgmhneMnl+-|u zPVWi#2a^fNz|8uW#Kf3e{2kHoJ#j2-49!4{_O@n!3I2qI``wv$Vh#V~={xdY?eD&D zi#UP|o$VY|?Ch-hNd8zx^at{<^d{o{M^Gd!oZdA&{$%{$Ij;=?h=d1pw$GnXHFHU&>Quw!N;Jx2J z+TN#^_t}c^U(?m!oc*Ek|KjiOSo~ic;T`(FgZ!`f{V!errR#si!2e45zuEO)y8c%T z{I8V%n_d6E(FOlM2Of~^`!2}s{Wufrxfu6;6oNLAkq`wC0Z0Jkk_ta--do`8B{iJ@ z00z=O514ztfa`lBjI)%S7|b3lI07tja`(J70Dwa+B`T!izI>YQ75{0@Wx#KZr8Xs@ zd_cM!9g<~u&6yllK=TI6Vnbe0+{DGU%60-TP_Qym-+ z)V@~_OSx+XPdC3*gWk_G3pxQ90-7(R=z=qXL1$-tVfs#MeeeWfB!igxGojs?>?ikw z+OM?|mEWOTuzX#0Cm)tSOQCFNfr?0UaVQV%OUjVqPLt%W@uJnH-M2})7e@}03bDXV zSciVa&k<)TlL4rB)<;$SzL5n~gfNajm;E~OSqgE){lvi)@&q085tnZhtm#NgJSZBu zh2V%fH9$xF7f>Ee^n9;{csf)sD)Cv8hCuCC7R|TuB6T0+of%~VBm7+_9OaLgOgFtk z(Za;*BbL(5;>M)AHSv>1-uucx4`Fg9)kNx;ktR)DT!GX8xMpd- zJ?Em)5~~U?4X!~N@oW?v?Sy{Pj7rKlZ}HshAq_ULoNX3&BmDjRFyI8QNV|SJVlObX z{QJ`Cr%cqB#LQ`6=90fdDA=t_a?-uw%i|AwM*<2H+7VR34(;ifR6H_qW3U?T{kTR0 zStbl(C(WW!L4NIG%HPk)WJO5@hPImM#3p%eV~5Fc>p_O@Cev2rf@+ld`f=0d2JI?c zY4wD?R7AgRxJ*}0F(cXGp`wMsN^>D{F-Xs$P&z%k@Bpf<8W{L)%-^NVdNWeit8e*L6_Rbc;C4MIss~1Rf^F33eQc{t{ zm%mIsiX@5Zf=Pw^DhIkT< z*IilW8_#>w7#znfKT@YwT-t9s4Sl8J_=NcR3@A5Q+za!k}*YU*j zUPK@-WcMz=dW{a*D5HvN{!CJ-75-Alm7I<>zkp>#_=MtgCzMm$i>vRA)GWcXEp5ok zY&;Mhd6i5*F1d8}#Q;g|+VRB4i^;7VW)LnCX#B{#3$>ss_TJ(&LQWKI_9L zm`8W~{j^t&Np=$Tbtpn4!D1b}4(-Pdr`I6o-%sjU7U2>c#Ef>KUlE#y81+LRjS-Hm zqT*XW6mbd3OL(`79#paoX>O?!s3XCM7)*))%Y)Lt<7F&dBUOH0-2Zp)Ky zgudo=H_Hk6y$#oIBa8Ws0p>{tsG+o=gWW8xR_9ABdP*-~iMRkY_<11HfgF*0N+3(? zxto7?0&V{UqT7;K&ASsNHp<rkZ*Dua*=^1!Y#icC`Po8E3g5KvL6UCH{PBPW4m50ie&tc&y2x$mCGpep+1 zcxg&KFpk9YBVD&37rQFtz&%C2xCofxTJ90ASBHc&HrF-xoJi|gQR2{&VFL0|Xnu|7 zEi&>BjQ!VphzI$?8s)99&l}BEo$A!u3o6a{lLZKgCX6- zd<$8*6-grro-yMRLY(`p@{1bYUXaMGmR|lfr8J+FL;&sUrJcbV>mSj&jx*3u2a)vm z?9YgU6%+7OATebUX~YJLO*nEy7oa$t%Ofbch4Gsiwx{JR%};2B?4*7bs~~$#C8sO* zw5a>oW5D(8d-XUzJ$)nxtC6tw`N9O`=BZ(~?qf~eA-s;$!usGhvv07@#Q!i&Ak=HA z7u#H{yLG-Coi?K*HSf<#_ESWw+zPmoMDr4RD|tOH-2)9-5K2IFC?47XiM_&Ly$`Idd zLB+%!Q8ZO?lKz~HTzzI;38!D#p!g=g8EG(Dn0t#R;4L}#d#m-^ zAYu0dV83NP<>J;d=G<n zUWbu7P^X=n3{cc=p~=`RnkIdLh_-p)6hDoL8+tZangFZGC?Gr_6Q7L=88H-9q0WTb zQWKuWHYqa?jGo>;^l;XXqhX0k^OFfL%pTf|B?SW?K0H!A0QC-;ww>V17j>C~EY*Us zjKF0}5F+-^gWTFVxaY`07o(b)2*;}W2V#U=G(hL7va`%sdu6Y&Ux z2Wn4d&+uH;Mp_L*nMQ$|_BJcbyEh*iba5YKiaBDeS=ta#`!1S8j7O07URA5)2VC{f;42v8qf5*EL5*?d_6y+?u@VYG- zH5z9{T~mM#zPaqXd;Y?)`f+1*khI#}6YuKhBUmh(AN6)|x%6+XT{jyWW908#M<`RX zv(6eHi1PcER|7l-jZC~+EfdsfjH6^eRjSyL_$hk@1{V}i*v)qe22v?OC_aO-*E}+w;xjwiR1p@M){q`Sr<8^}+;5r^X=zcfn!Z*K!Y5 zR)3s*Vt{>b#P>O_$v3dYcUpMLrEC<-Shpd$0+d>sii0E-KYcr}WZ3TOOfz5mjrWCQ zz6`66(Y5s0O>&MqsiIq}A*CWLGm=~~?>NIx{XWyL&{MZjG$ibl-iTf!)ZLNR+`wja zcgcc9O+q58F$HdFVF7!0tvafI>euFAQT;|tlNnNjjWM4e^Zw&=m6!FC=X1}Lxu2d& z$j$Aop^aL1R)cO|gAHc24h`)KI9%Z|AA;h`Pik669(qackz+^9g zPTahz2ugzjm|Yb<%%6zq#hb=Bc=~$(HsC5-$o9nq=T}rK4X7!V1eQX5Vl;P_@*-2~ z9GFu;L9{d;*UnK>k5xtuv0h=H56sWbV7HSu#pO(E{fyh!?b|r0osm%OwV^t4uT`8W zk~Mz@?7Htg)n(KKi4Xc#QijJgZX91?h`Kz2zwAck*D&80;CjOJcqXg?aYJ%_tycQ8 zj;o?vampgJwcCA2xJnw!o09P4wN8zgx>P{8i~|10!npP$VP=<99>e5DAGSOgiKNB zABIe8?xap@#7k14>0>RVlY~a)=Qy2lsQ?H5r4sR2uD3di%*CgFpON=rIJ+-rY__O< zX^Qi{O8-aU7$|uJ%l$5X0Z?-KrVtv?J@^(aO0KLvL5Hn{eBXz`k?184Jx@n1d;B8l zTbAYbU2eFWo@*;WPHI-blc=819W5sOBZaO{3Lz1)x4;zzkv|r6u=_I!bu%$oTQ8nb zrQhtu+cW%*IvUaR3qU;Ee`%H1=tAh{<>gLn<*~Vki+B|~Q`oe5mBBvPytf@nV`a3L zIm(lClMl%LQ-MMhuKu+ViVHq{t|1PT?V^vs>482{Y?SsLM+prYSqfp7IzO4a(V{~L zA%Ri(o^$j7ID#nwz+h>Qs}2sgE{%X-S5{a-j}1wV638!j92MtZ(NSrN(4tpyC%xB2)i;pn#gdGGPV`r=sbtF~ru1ejM4*l(GERt&*q4OgGhCe}$yG>q>Zf z0dW%3$0ivcd2;)U4~?>-J%Z?syIHqM4$pJVU1wQ4r;-aKrBFRXYn-E-v zLG2h6>GH>R8PE&;E;@ZqJX1M*f@yd6Il-$nG71V`?);h~g+;)sy_9isI(7o4N^<=P zdB8|UQF?(u?0TXV5P;p**zx@Ub*;=~cB)?J;x)|^vH2`S1%h$w`g|Gi3WBH1j9pp^ zcv-bx4PZ_={^@&pTlZ$*)udv4Gr;M|MM7*BE0e9*)#gWDx?krmu^iOq%VhKn5w!I*abvPv1>(bc4gyau%TThIX1vt$Q)WMvj8%MT{agdV|)9rp7wYVlALL1p+;cv zbohm{B3D!xuxiF+ z))D6|9F*}orLR;9K8b3?&OsUUA%2=^A}#O{9f9i<&)yuM1M|L@2!eL&ZE$h+c_!9- z>@OWI6P+;c)7c1Z+ZV^|Drwg914~jk$bOySgmaeS0U2I>=!kW9-NI%-eV5{twwI1LNk4=jS0ccw}+W_#5gS zhEp%wYP-!zo*jM?VzB3`s4d$|ryAFZpE5Fu3Yvs^sod#*4@7<&J~jHBAF=cXm`5wV zVwrXJez7lSEHx&4@mF<&jaEqSzOuS9|K@0ZtH+#Q7TYF+OejqN1L2JH+)l7ecrr+J zVD5A4FmzT;1{?ugV@!(+M)_n3`=BcqznTVg~llbM-IV7-dLMW#_(|qgR4*k!|38y-IU@Y z5F9V&2-*xP!{3&FUowjYIz)OgeS}ZOBh$83qK&zu)i(FBcEXW9Q-DN20z+4l9fv`vECr#O0nF7qP-WeCY@h)t|EvPhp9l6~uY7fr2nSHFvJb*>x3A+YTgfbbSGxJ7+K#8 zio!K^cIv~Y`M_EES~Ds}6HD={_F!>&N&5&h^(|fsLKdd#cf8h-H+$jYoqhl8_F7t6 z#8<)a^?m*M)}$}Lu_hy?<#mg0o`Bn(`XICx!ouiq!g}Yq9>@w%4m>>Yt%ad0iUnTI z6Rs<@8ib($-yU9w$!;Vs=6|>S!rv3`PprzjCVjY?PqjmCMc{sW0r-xN1~%0J&|k;{ zo~&6hdMdbzZzIhBm#s;dSS7OCZj8nl@DX9AFoTWfi#XfvnGi1y@!8pS!muIwA<^qP zUl`>1wMAc?AOQOU{O#LSNRkJ$QRFL$7j--HT=a-EP~8;AEj3tGq?uOxj9 zG4OtC(q-hd=#e2fdO4SIZ^&Dgm$yu~osfutcA`vW|9OSaB3=G&d~j$F_t%tOkLoswIO+95s$e(u4qoL>Q5HFPKU_p0e(1}c!ikMQA` z!l!Si_XCd~g!uLPd<(;yBn)KfijLt>x~MQvY)HtuvS`9ydL9?iUHa1CaAGp0>2P}A zC5&ffX@^`y*sH;7UMSTEQqjW3MtoVHsfY-c+&STvSE}#NMXZ~#dy8ygIgiMInUls1 ztj3!7kqnwuA5K)iG2r+4*k~BB+_kzcLiABRdh5A3sYw7~sx}yfeelyX1=AMdZly`@ zez)sZzeHpDEwpQFw6PsxzHd5*egUcD9wyf`YO#!dZ>5WBdGcotw%L)-)6)i>zi%Q> zqbGiaj~`d}X}Ne>q5VGro&sV01eh^nh8k^I(q1O17OwVlg1(6r`8~9@QLRXx0tUDzG+msL7v0;**~W{7DlK7kS2iJz3)i z_~*?(6Ruy?oS<+}4Nv`rC+G?ie3@4dnTAA*Vs6j6C;J>_dM-T-AW_ zXNGc+O+Z~w$QKdx0Jz8nw_)$I5ijh-Xf#w*c9lt~K7Luk4)&|pc#3b-43KpoGYEO_ zTD|x3pNS+v04-V$7hI>p5r#fw`JH5*z_>k8$1T2loO(hv5YO+d^h7?tvuaOhRmF|d z6ZNsdKOQ`x-o52Ta}ht-sqlB)i*?kc R+l&AJ002ovPDHLkV1n8em?i)K literal 16173 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 Date: Mon, 2 Aug 2021 13:53:33 +0200 Subject: [PATCH 55/76] Add Enabled property to SubFloorHideComponent, make it networked. (#4404) --- .../SubFloor/SubFloorHideComponent.cs | 32 +++++++++++- Content.Shared/SubFloor/SubFloorHideSystem.cs | 49 +++++++++++++++---- 2 files changed, 71 insertions(+), 10 deletions(-) diff --git a/Content.Shared/SubFloor/SubFloorHideComponent.cs b/Content.Shared/SubFloor/SubFloorHideComponent.cs index 93587e6f8c..100903c855 100644 --- a/Content.Shared/SubFloor/SubFloorHideComponent.cs +++ b/Content.Shared/SubFloor/SubFloorHideComponent.cs @@ -1,4 +1,8 @@ +using System; using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; +using Robust.Shared.Players; +using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.ViewVariables; @@ -10,6 +14,7 @@ namespace Content.Shared.SubFloor /// (plating). /// /// + [NetworkedComponent] [RegisterComponent] public sealed class SubFloorHideComponent : Component { @@ -17,10 +22,35 @@ namespace Content.Shared.SubFloor public override string Name => "SubFloorHide"; /// - /// This entity needs to be anchored to be hid in the subfloor. + /// Whether the entity will be hid when not in subfloor. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("enabled")] + public bool Enabled { get; set; } = true; + + /// + /// This entity needs to be anchored to be hid when not in subfloor. /// [ViewVariables(VVAccess.ReadWrite)] [DataField("requireAnchored")] public bool RequireAnchored { get; set; } = true; + + public override ComponentState GetComponentState(ICommonSession player) + { + return new SubFloorHideComponentState(Enabled, RequireAnchored); + } + } + + [Serializable, NetSerializable] + public class SubFloorHideComponentState : ComponentState + { + public bool Enabled { get; } + public bool RequireAnchored { get; } + + public SubFloorHideComponentState(bool enabled, bool requireAnchored) + { + Enabled = enabled; + RequireAnchored = requireAnchored; + } } } diff --git a/Content.Shared/SubFloor/SubFloorHideSystem.cs b/Content.Shared/SubFloor/SubFloorHideSystem.cs index 503a153ea4..d729e2b31a 100644 --- a/Content.Shared/SubFloor/SubFloorHideSystem.cs +++ b/Content.Shared/SubFloor/SubFloorHideSystem.cs @@ -2,6 +2,7 @@ using System; using Content.Shared.Maps; using JetBrains.Annotations; using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; using Robust.Shared.IoC; using Robust.Shared.Map; using Robust.Shared.Maths; @@ -44,6 +45,7 @@ namespace Content.Shared.SubFloor SubscribeLocalEvent(OnSubFloorStarted); SubscribeLocalEvent(OnSubFloorTerminating); SubscribeLocalEvent(HandleAnchorChanged); + SubscribeLocalEvent(HandleComponentState); } public override void Shutdown() @@ -54,6 +56,20 @@ namespace Content.Shared.SubFloor _mapManager.TileChanged -= MapManagerOnTileChanged; } + public void SetEnabled(SubFloorHideComponent subFloor, bool enabled) + { + subFloor.Enabled = enabled; + subFloor.Dirty(); + UpdateEntity(subFloor.Owner.Uid); + } + + public void SetRequireAnchoring(SubFloorHideComponent subFloor, bool requireAnchored) + { + subFloor.RequireAnchored = requireAnchored; + subFloor.Dirty(); + UpdateEntity(subFloor.Owner.Uid); + } + private void OnSubFloorStarted(EntityUid uid, SubFloorHideComponent component, ComponentStartup _) { UpdateEntity(uid); @@ -73,6 +89,16 @@ namespace Content.Shared.SubFloor UpdateEntity(uid); } + private void HandleComponentState(EntityUid uid, SubFloorHideComponent component, ComponentHandleState args) + { + if (args.Current is not SubFloorHideComponentState state) + return; + + component.Enabled = state.Enabled; + component.RequireAnchored = state.RequireAnchored; + UpdateEntity(uid); + } + private void MapManagerOnTileChanged(object? sender, TileChangedEventArgs e) { UpdateTile(_mapManager.GetGrid(e.NewTile.GridIndex), e.NewTile.GridIndices); @@ -136,16 +162,21 @@ namespace Content.Shared.SubFloor if (subFloorHideEvent.Handled) return; - // This might look weird, but basically we only need to query the SubFloorHide and Transform components - // if we are gonna hide the entity and we require it to be anchored to be hidden. Because getting components - // is "expensive", we have a slow path where we query them, and a fast path where we don't. - if (!subFloor - && ComponentManager.TryGetComponent(uid, out SubFloorHideComponent? subFloorHideComponent) && - subFloorHideComponent.RequireAnchored - && ComponentManager.TryGetComponent(uid, out ITransformComponent? transformComponent)) + // We only need to query the subfloor component to check if it's enabled or not when we're not on subfloor. + // Getting components is expensive, after all. + if (!subFloor && ComponentManager.TryGetComponent(uid, out SubFloorHideComponent? subFloorHideComponent)) { - // If we require the entity to be anchored but it's not, this will set subfloor to true, unhiding it. - subFloor = !transformComponent.Anchored; + // If the component isn't enabled, then subfloor will always be true, and the entity will be shown. + if (!subFloorHideComponent.Enabled) + { + subFloor = true; + } + // We only need to query the TransformComp if the SubfloorHide is enabled and requires anchoring. + else if (subFloorHideComponent.RequireAnchored && ComponentManager.TryGetComponent(uid, out ITransformComponent? transformComponent)) + { + // If we require the entity to be anchored but it's not, this will set subfloor to true, unhiding it. + subFloor = !transformComponent.Anchored; + } } // Whether to show this entity as visible, visually. From af2e21c3559f01c88574b2a58398e074c895c80f Mon Sep 17 00:00:00 2001 From: mirrorcult Date: Mon, 2 Aug 2021 04:57:06 -0700 Subject: [PATCH 56/76] Refactor IDoorCheck into entity events (#4366) * IDoorCheck refactored to events # Conflicts: # Content.Server/Atmos/TileAtmosphere.cs # Content.Server/Doors/Components/AirlockComponent.cs # Content.Server/Doors/Components/FirelockComponent.cs # Content.Server/Doors/Components/ServerDoorComponent.cs # Content.Server/Doors/IDoorCheck.cs * namespaces * Fix mapinit bug with refreshautoclose * ok i guess these just didnt feel like staging today --- Content.Client/Doors/AirlockVisualizer.cs | 27 --- .../AtmosphereSystem.Monstermos.cs | 1 + .../Doors/Components/AirlockComponent.cs | 216 ++++++------------ .../Components/FirelockComponent.cs | 58 ++--- .../Doors/Components/ServerDoorComponent.cs | 156 +++++++++---- Content.Server/Doors/DoorEvents.cs | 141 ++++++++++++ Content.Server/Doors/IDoorCheck.cs | 76 ------ Content.Server/Doors/Systems/AirlockSystem.cs | 109 +++++++++ .../Doors/{ => Systems}/DoorSystem.cs | 0 .../Doors/Systems/FirelockSystem.cs | 69 ++++++ Content.Shared/Doors/SharedDoorComponent.cs | 3 + .../Structures/Doors/Airlocks/base.yml | 9 +- .../Structures/Doors/Airlocks/external.yml | 9 +- .../Structures/Doors/Firelocks/firelock.yml | 9 +- 14 files changed, 545 insertions(+), 338 deletions(-) rename Content.Server/{Atmos => Doors}/Components/FirelockComponent.cs (59%) create mode 100644 Content.Server/Doors/DoorEvents.cs delete mode 100644 Content.Server/Doors/IDoorCheck.cs create mode 100644 Content.Server/Doors/Systems/AirlockSystem.cs rename Content.Server/Doors/{ => Systems}/DoorSystem.cs (100%) create mode 100644 Content.Server/Doors/Systems/FirelockSystem.cs diff --git a/Content.Client/Doors/AirlockVisualizer.cs b/Content.Client/Doors/AirlockVisualizer.cs index 8f6c0651b1..a428a5bc2e 100644 --- a/Content.Client/Doors/AirlockVisualizer.cs +++ b/Content.Client/Doors/AirlockVisualizer.cs @@ -17,15 +17,6 @@ namespace Content.Client.Doors { private const string AnimationKey = "airlock_animation"; - [DataField("open_sound", required: true)] - private string _openSound = default!; - - [DataField("close_sound", required: true)] - private string _closeSound = default!; - - [DataField("deny_sound", required: true)] - private string _denySound = default!; - [DataField("animation_time")] private float _delay = 0.8f; @@ -51,14 +42,6 @@ namespace Content.Client.Doors CloseAnimation.AnimationTracks.Add(flickMaintenancePanel); flickMaintenancePanel.LayerKey = WiresVisualizer.WiresVisualLayers.MaintenancePanel; flickMaintenancePanel.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("panel_closing", 0f)); - - var sound = new AnimationTrackPlaySound(); - CloseAnimation.AnimationTracks.Add(sound); - - if (_closeSound != null) - { - sound.KeyFrames.Add(new AnimationTrackPlaySound.KeyFrame(_closeSound, 0)); - } } OpenAnimation = new Animation {Length = TimeSpan.FromSeconds(_delay)}; @@ -80,11 +63,6 @@ namespace Content.Client.Doors var sound = new AnimationTrackPlaySound(); OpenAnimation.AnimationTracks.Add(sound); - - if (_openSound != null) - { - sound.KeyFrames.Add(new AnimationTrackPlaySound.KeyFrame(_openSound, 0)); - } } DenyAnimation = new Animation {Length = TimeSpan.FromSeconds(0.3f)}; @@ -96,11 +74,6 @@ namespace Content.Client.Doors var sound = new AnimationTrackPlaySound(); DenyAnimation.AnimationTracks.Add(sound); - - if (_denySound != null) - { - sound.KeyFrames.Add(new AnimationTrackPlaySound.KeyFrame(_denySound, 0, () => AudioHelpers.WithVariation(0.05f))); - } } } diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Monstermos.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Monstermos.cs index 30b4df6473..87a4f8b2e1 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Monstermos.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Monstermos.cs @@ -5,6 +5,7 @@ using System.Buffers; using System.Collections.Generic; using Content.Server.Atmos.Components; using Content.Server.Coordinates.Helpers; +using Content.Server.Doors.Components; using Content.Shared.Atmos; using Robust.Shared.GameObjects; using Robust.Shared.IoC; diff --git a/Content.Server/Doors/Components/AirlockComponent.cs b/Content.Server/Doors/Components/AirlockComponent.cs index 2e47a1b6d7..dda64b4cda 100644 --- a/Content.Server/Doors/Components/AirlockComponent.cs +++ b/Content.Server/Doors/Components/AirlockComponent.cs @@ -7,12 +7,14 @@ using Content.Shared.Doors; using Content.Shared.Interaction; using Content.Shared.Notification; using Content.Shared.Notification.Managers; +using Content.Shared.Sound; using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.GameObjects; using Robust.Shared.Localization; using Robust.Shared.Maths; using Robust.Shared.Player; +using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.ViewVariables; using static Content.Shared.Wires.SharedWiresComponent; using static Content.Shared.Wires.SharedWiresComponent.WiresAction; @@ -23,30 +25,41 @@ namespace Content.Server.Doors.Components /// Companion component to ServerDoorComponent that handles airlock-specific behavior -- wires, requiring power to operate, bolts, and allowing automatic closing. /// [RegisterComponent] - [ComponentReference(typeof(IDoorCheck))] - public class AirlockComponent : Component, IWires, IDoorCheck + public class AirlockComponent : Component, IWires { public override string Name => "Airlock"; [ComponentDependency] - private readonly ServerDoorComponent? _doorComponent = null; + public readonly ServerDoorComponent? DoorComponent = null; [ComponentDependency] - private readonly SharedAppearanceComponent? _appearanceComponent = null; + public readonly SharedAppearanceComponent? AppearanceComponent = null; [ComponentDependency] - private readonly ApcPowerReceiverComponent? _receiverComponent = null; + public readonly ApcPowerReceiverComponent? ReceiverComponent = null; [ComponentDependency] - private readonly WiresComponent? _wiresComponent = null; + public readonly WiresComponent? WiresComponent = null; + + /// + /// Sound to play when the bolts on the airlock go up. + /// + [DataField("boltUpSound")] + public SoundSpecifier BoltUpSound = new SoundPathSpecifier("/Audio/Machines/boltsup.ogg"); + + /// + /// Sound to play when the bolts on the airlock go down. + /// + [DataField("boltDownSound")] + public SoundSpecifier BoltDownSound = new SoundPathSpecifier("/Audio/Machines/boltsdown.ogg"); /// /// Duration for which power will be disabled after pulsing either power wire. /// - private static readonly TimeSpan PowerWiresTimeout = TimeSpan.FromSeconds(5.0); + [DataField("powerWiresTimeout")] + public float PowerWiresTimeout = 5.0f; private CancellationTokenSource _powerWiresPulsedTimerCancel = new(); - private bool _powerWiresPulsed; /// @@ -83,7 +96,7 @@ namespace Content.Server.Doors.Components private bool BoltLightsVisible { get => _boltLightsWirePulsed && BoltsDown && IsPowered() - && _doorComponent != null && _doorComponent.State == SharedDoorComponent.DoorState.Closed; + && DoorComponent != null && DoorComponent.State == SharedDoorComponent.DoorState.Closed; set { _boltLightsWirePulsed = value; @@ -91,120 +104,53 @@ namespace Content.Server.Doors.Components } } - private static readonly TimeSpan AutoCloseDelayFast = TimeSpan.FromSeconds(1); + [ViewVariables(VVAccess.ReadWrite)] + [DataField("autoClose")] + public bool AutoClose = true; [ViewVariables(VVAccess.ReadWrite)] - private bool _autoClose = true; + [DataField("autoCloseDelayModifier")] + public float AutoCloseDelayModifier = 1.0f; [ViewVariables(VVAccess.ReadWrite)] - private bool _normalCloseSpeed = true; - - [ViewVariables(VVAccess.ReadWrite)] - private bool _safety = true; + public bool Safety = true; protected override void Initialize() { base.Initialize(); - if (_receiverComponent != null && _appearanceComponent != null) + if (ReceiverComponent != null && AppearanceComponent != null) { - _appearanceComponent.SetData(DoorVisuals.Powered, _receiverComponent.Powered); + AppearanceComponent.SetData(DoorVisuals.Powered, ReceiverComponent.Powered); } } - public override void HandleMessage(ComponentMessage message, IComponent? component) - { - base.HandleMessage(message, component); - switch (message) - { - case PowerChangedMessage powerChanged: - PowerDeviceOnOnPowerStateChanged(powerChanged); - break; - } - } - - void IDoorCheck.OnStateChange(SharedDoorComponent.DoorState doorState) - { - // Only show the maintenance panel if the airlock is closed - if (_wiresComponent != null) - { - _wiresComponent.IsPanelVisible = doorState != SharedDoorComponent.DoorState.Open; - } - // If the door is closed, we should look if the bolt was locked while closing - UpdateBoltLightStatus(); - } - - bool IDoorCheck.OpenCheck() => CanChangeState(); - - bool IDoorCheck.CloseCheck() => CanChangeState(); - - bool IDoorCheck.DenyCheck() => CanChangeState(); - - bool IDoorCheck.SafetyCheck() => _safety; - - bool IDoorCheck.AutoCloseCheck() => _autoClose; - - TimeSpan? IDoorCheck.GetCloseSpeed() - { - if (_normalCloseSpeed) - { - return null; - } - return AutoCloseDelayFast; - } - - bool IDoorCheck.BlockActivate(ActivateEventArgs eventArgs) - { - if (_wiresComponent != null && _wiresComponent.IsPanelOpen && - eventArgs.User.TryGetComponent(out ActorComponent? actor)) - { - _wiresComponent.OpenInterface(actor.PlayerSession); - return true; - } - return false; - } - - bool IDoorCheck.CanPryCheck(InteractUsingEventArgs eventArgs) - { - if (IsBolted()) - { - Owner.PopupMessage(eventArgs.User, Loc.GetString("airlock-component-cannot-pry-is-bolted-message ")); - return false; - } - if (IsPowered()) - { - Owner.PopupMessage(eventArgs.User, Loc.GetString("airlock-component-cannot-pry-is-powered-message")); - return false; - } - return true; - } - - private bool CanChangeState() + public bool CanChangeState() { return IsPowered() && !IsBolted(); } - private bool IsBolted() + public bool IsBolted() { return _boltsDown; } - private bool IsPowered() + public bool IsPowered() { - return _receiverComponent == null || _receiverComponent.Powered; + return ReceiverComponent == null || ReceiverComponent.Powered; } - private void UpdateBoltLightStatus() + public void UpdateBoltLightStatus() { - if (_appearanceComponent != null) + if (AppearanceComponent != null) { - _appearanceComponent.SetData(DoorVisuals.BoltLights, BoltLightsVisible); + AppearanceComponent.SetData(DoorVisuals.BoltLights, BoltLightsVisible); } } - private void UpdateWiresStatus() + public void UpdateWiresStatus() { - if (_doorComponent == null) + if (DoorComponent == null) { return; } @@ -214,9 +160,9 @@ namespace Content.Server.Doors.Components { powerLight = new StatusLightData(Color.Yellow, StatusLightState.BlinkingFast, "POWR"); } - else if (_wiresComponent != null && - _wiresComponent.IsWireCut(Wires.MainPower) && - _wiresComponent.IsWireCut(Wires.BackupPower)) + else if (WiresComponent != null && + WiresComponent.IsWireCut(Wires.MainPower) && + WiresComponent.IsWireCut(Wires.BackupPower)) { powerLight = new StatusLightData(Color.Red, StatusLightState.On, "POWR"); } @@ -226,63 +172,59 @@ namespace Content.Server.Doors.Components var boltLightsStatus = new StatusLightData(Color.Lime, _boltLightsWirePulsed ? StatusLightState.On : StatusLightState.Off, "BLTL"); + var ev = new DoorGetCloseTimeModifierEvent(); + Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, ev, false); + var timingStatus = - new StatusLightData(Color.Orange, !_autoClose ? StatusLightState.Off : - !_normalCloseSpeed ? StatusLightState.BlinkingSlow : + new StatusLightData(Color.Orange, !AutoClose ? StatusLightState.Off : + !MathHelper.CloseTo(ev.CloseTimeModifier, 1.0f) ? StatusLightState.BlinkingSlow : StatusLightState.On, "TIME"); var safetyStatus = - new StatusLightData(Color.Red, _safety ? StatusLightState.On : StatusLightState.Off, "SAFE"); + new StatusLightData(Color.Red, Safety ? StatusLightState.On : StatusLightState.Off, "SAFE"); - if (_wiresComponent == null) + if (WiresComponent == null) { return; } - _wiresComponent.SetStatus(AirlockWireStatus.PowerIndicator, powerLight); - _wiresComponent.SetStatus(AirlockWireStatus.BoltIndicator, boltStatus); - _wiresComponent.SetStatus(AirlockWireStatus.BoltLightIndicator, boltLightsStatus); - _wiresComponent.SetStatus(AirlockWireStatus.AIControlIndicator, new StatusLightData(Color.Purple, StatusLightState.BlinkingSlow, "AICT")); - _wiresComponent.SetStatus(AirlockWireStatus.TimingIndicator, timingStatus); - _wiresComponent.SetStatus(AirlockWireStatus.SafetyIndicator, safetyStatus); - /* - _wires.SetStatus(6, powerLight); - _wires.SetStatus(7, powerLight); - _wires.SetStatus(8, powerLight); - _wires.SetStatus(9, powerLight); - _wires.SetStatus(10, powerLight); - _wires.SetStatus(11, powerLight);*/ + WiresComponent.SetStatus(AirlockWireStatus.PowerIndicator, powerLight); + WiresComponent.SetStatus(AirlockWireStatus.BoltIndicator, boltStatus); + WiresComponent.SetStatus(AirlockWireStatus.BoltLightIndicator, boltLightsStatus); + WiresComponent.SetStatus(AirlockWireStatus.AIControlIndicator, new StatusLightData(Color.Purple, StatusLightState.BlinkingSlow, "AICT")); + WiresComponent.SetStatus(AirlockWireStatus.TimingIndicator, timingStatus); + WiresComponent.SetStatus(AirlockWireStatus.SafetyIndicator, safetyStatus); } private void UpdatePowerCutStatus() { - if (_receiverComponent == null) + if (ReceiverComponent == null) { return; } if (PowerWiresPulsed) { - _receiverComponent.PowerDisabled = true; + ReceiverComponent.PowerDisabled = true; return; } - if (_wiresComponent == null) + if (WiresComponent == null) { return; } - _receiverComponent.PowerDisabled = - _wiresComponent.IsWireCut(Wires.MainPower) || - _wiresComponent.IsWireCut(Wires.BackupPower); + ReceiverComponent.PowerDisabled = + WiresComponent.IsWireCut(Wires.MainPower) || + WiresComponent.IsWireCut(Wires.BackupPower); } private void PowerDeviceOnOnPowerStateChanged(PowerChangedMessage e) { - if (_appearanceComponent != null) + if (AppearanceComponent != null) { - _appearanceComponent.SetData(DoorVisuals.Powered, e.Powered); + AppearanceComponent.SetData(DoorVisuals.Powered, e.Powered); } // BoltLights also got out @@ -341,19 +283,13 @@ namespace Content.Server.Doors.Components builder.CreateWire(Wires.BoltLight); builder.CreateWire(Wires.Timing); builder.CreateWire(Wires.Safety); - /* - builder.CreateWire(6); - builder.CreateWire(7); - builder.CreateWire(8); - builder.CreateWire(9); - builder.CreateWire(10); - builder.CreateWire(11);*/ + UpdateWiresStatus(); } public void WiresUpdate(WiresUpdateEventArgs args) { - if(_doorComponent == null) + if(DoorComponent == null) { return; } @@ -367,7 +303,7 @@ namespace Content.Server.Doors.Components PowerWiresPulsed = true; _powerWiresPulsedTimerCancel.Cancel(); _powerWiresPulsedTimerCancel = new CancellationTokenSource(); - Owner.SpawnTimer(PowerWiresTimeout, + Owner.SpawnTimer(TimeSpan.FromSeconds(PowerWiresTimeout), () => PowerWiresPulsed = false, _powerWiresPulsedTimerCancel.Token); break; @@ -390,11 +326,11 @@ namespace Content.Server.Doors.Components BoltLightsVisible = !_boltLightsWirePulsed; break; case Wires.Timing: - _normalCloseSpeed = !_normalCloseSpeed; - _doorComponent.RefreshAutoClose(); + AutoCloseDelayModifier = 0.5f; + DoorComponent.RefreshAutoClose(); break; case Wires.Safety: - _safety = !_safety; + Safety = !Safety; break; } } @@ -413,11 +349,11 @@ namespace Content.Server.Doors.Components BoltLightsVisible = true; break; case Wires.Timing: - _autoClose = true; - _doorComponent.RefreshAutoClose(); + AutoClose = true; + DoorComponent.RefreshAutoClose(); break; case Wires.Safety: - _safety = true; + Safety = true; break; } } @@ -433,11 +369,11 @@ namespace Content.Server.Doors.Components BoltLightsVisible = false; break; case Wires.Timing: - _autoClose = false; - _doorComponent.RefreshAutoClose(); + AutoClose = false; + DoorComponent.RefreshAutoClose(); break; case Wires.Safety: - _safety = false; + Safety = false; break; } } @@ -455,7 +391,7 @@ namespace Content.Server.Doors.Components BoltsDown = newBolts; - SoundSystem.Play(Filter.Broadcast(), newBolts ? "/Audio/Machines/boltsdown.ogg" : "/Audio/Machines/boltsup.ogg", Owner); + SoundSystem.Play(Filter.Broadcast(), newBolts ? BoltDownSound.GetSound() : BoltUpSound.GetSound(), Owner); } } } diff --git a/Content.Server/Atmos/Components/FirelockComponent.cs b/Content.Server/Doors/Components/FirelockComponent.cs similarity index 59% rename from Content.Server/Atmos/Components/FirelockComponent.cs rename to Content.Server/Doors/Components/FirelockComponent.cs index b1849cd757..ee7154bb28 100644 --- a/Content.Server/Atmos/Components/FirelockComponent.cs +++ b/Content.Server/Doors/Components/FirelockComponent.cs @@ -1,3 +1,4 @@ +using Content.Server.Atmos.Components; using Content.Server.Atmos.EntitySystems; using Content.Server.Doors; using Content.Server.Doors.Components; @@ -6,26 +7,34 @@ using Content.Shared.Interaction; using Content.Shared.Notification.Managers; using Robust.Shared.GameObjects; using Robust.Shared.Localization; +using Robust.Shared.Serialization.Manager.Attributes; -namespace Content.Server.Atmos.Components +namespace Content.Server.Doors.Components { /// - /// Companion component to ServerDoorComponent that handles firelock-specific behavior -- primarily prying, and not being openable on open-hand click. + /// Companion component to ServerDoorComponent that handles firelock-specific behavior -- primarily prying, + /// and not being openable on open-hand click. /// [RegisterComponent] - [ComponentReference(typeof(IDoorCheck))] - public class FirelockComponent : Component, IDoorCheck + public class FirelockComponent : Component { public override string Name => "Firelock"; [ComponentDependency] - private readonly ServerDoorComponent? _doorComponent = null; + public readonly ServerDoorComponent? DoorComponent = null; + + /// + /// Pry time modifier to be used when the firelock is currently closed due to fire or pressure. + /// + /// + [DataField("lockedPryTimeModifier")] + public float LockedPryTimeModifier = 1.5f; public bool EmergencyPressureStop() { - if (_doorComponent != null && _doorComponent.State == SharedDoorComponent.DoorState.Open && _doorComponent.CanCloseGeneric()) + if (DoorComponent != null && DoorComponent.State == SharedDoorComponent.DoorState.Open && DoorComponent.CanCloseGeneric()) { - _doorComponent.Close(); + DoorComponent.Close(); if (Owner.TryGetComponent(out AirtightComponent? airtight)) { EntitySystem.Get().SetAirblocked(airtight, true); @@ -35,41 +44,6 @@ namespace Content.Server.Atmos.Components return false; } - bool IDoorCheck.OpenCheck() - { - return !IsHoldingFire() && !IsHoldingPressure(); - } - - bool IDoorCheck.DenyCheck() => false; - - float? IDoorCheck.GetPryTime() - { - if (IsHoldingFire() || IsHoldingPressure()) - { - return 1.5f; - } - return null; - } - - bool IDoorCheck.BlockActivate(ActivateEventArgs eventArgs) => true; - - void IDoorCheck.OnStartPry(InteractUsingEventArgs eventArgs) - { - if (_doorComponent == null || _doorComponent.State != SharedDoorComponent.DoorState.Closed) - { - return; - } - - if (IsHoldingPressure()) - { - Owner.PopupMessage(eventArgs.User, Loc.GetString("firelock-component-is-holding-pressure-message")); - } - else if (IsHoldingFire()) - { - Owner.PopupMessage(eventArgs.User, Loc.GetString("firelock-component-is-holding-fire-message")); - } - } - public bool IsHoldingPressure(float threshold = 20) { var atmosphereSystem = EntitySystem.Get(); diff --git a/Content.Server/Doors/Components/ServerDoorComponent.cs b/Content.Server/Doors/Components/ServerDoorComponent.cs index 8449074d65..1feb49a3b6 100644 --- a/Content.Server/Doors/Components/ServerDoorComponent.cs +++ b/Content.Server/Doors/Components/ServerDoorComponent.cs @@ -14,6 +14,7 @@ using Content.Shared.Damage; using Content.Shared.Damage.Components; using Content.Shared.Doors; using Content.Shared.Interaction; +using Content.Shared.Sound; using Content.Shared.Tool; using Robust.Shared.Audio; using Robust.Shared.Containers; @@ -37,9 +38,6 @@ namespace Content.Server.Doors.Components [ComponentReference(typeof(SharedDoorComponent))] public class ServerDoorComponent : SharedDoorComponent, IActivate, IInteractUsing, IMapInit { - [ComponentDependency] - private readonly IDoorCheck? _doorCheck = null; - [ViewVariables] [DataField("board")] private string? _boardPrototype; @@ -63,11 +61,8 @@ namespace Content.Server.Doors.Components _ => throw new ArgumentOutOfRangeException(), }; - if (_doorCheck != null) - { - _doorCheck.OnStateChange(State); - RefreshAutoClose(); - } + Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new DoorStateChangedEvent(State), false); + _autoCloseCancelTokenSource?.Cancel(); Dirty(); } @@ -105,7 +100,7 @@ namespace Content.Server.Doors.Components /// Handled in Startup(). /// [ViewVariables(VVAccess.ReadWrite)] [DataField("startOpen")] - private bool _startOpen; + private bool _startOpen = false; /// /// Whether the airlock is welded shut. Can be set by the prototype, although this will fail if the door isn't weldable. @@ -139,6 +134,41 @@ namespace Content.Server.Doors.Components [DataField("weldable")] private bool _weldable = true; + /// + /// Sound to play when the door opens. + /// + [DataField("openSound")] + public SoundSpecifier? OpenSound; + + /// + /// Sound to play when the door closes. + /// + [DataField("closeSound")] + public SoundSpecifier? CloseSound; + + /// + /// Sound to play if the door is denied. + /// + [DataField("denySound")] + public SoundSpecifier? DenySound; + + /// + /// Default time that the door should take to pry open. + /// + [DataField("pryTime")] + public float PryTime = 0.5f; + + /// + /// Minimum interval allowed between deny sounds in milliseconds. + /// + [DataField("denySoundMinimumInterval")] + public float DenySoundMinimumInterval = 250.0f; + + /// + /// Used to stop people from spamming the deny sound. + /// + private TimeSpan LastDenySoundTime = TimeSpan.Zero; + /// /// Whether the door can currently be welded. /// @@ -149,6 +179,7 @@ namespace Content.Server.Doors.Components /// private bool _beingWelded; + //[ViewVariables(VVAccess.ReadWrite)] //[DataField("canCrush")] //private bool _canCrush = true; // TODO implement door crushing @@ -187,7 +218,7 @@ namespace Content.Server.Doors.Components Logger.Warning("{0} prototype loaded with incompatible flags: 'welded' and 'startOpen' are both true.", Owner.Name); return; } - QuickOpen(); + QuickOpen(false); } CreateDoorElectronicsBoard(); @@ -195,10 +226,10 @@ namespace Content.Server.Doors.Components void IActivate.Activate(ActivateEventArgs eventArgs) { - if (_doorCheck != null && _doorCheck.BlockActivate(eventArgs)) - { + DoorClickShouldActivateEvent ev = new DoorClickShouldActivateEvent(eventArgs); + Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, ev, false); + if (ev.Handled) return; - } if (State == DoorState.Open) { @@ -279,12 +310,10 @@ namespace Content.Server.Doors.Components { return false; } - if(_doorCheck != null) - { - return _doorCheck.OpenCheck(); - } - return true; + var ev = new BeforeDoorOpenedEvent(); + Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, ev, false); + return !ev.Cancelled; } /// @@ -301,12 +330,19 @@ namespace Content.Server.Doors.Components _stateChangeCancelTokenSource?.Cancel(); _stateChangeCancelTokenSource = new(); + if (OpenSound != null) + { + SoundSystem.Play(Filter.Pvs(Owner), OpenSound.GetSound(), + AudioParams.Default.WithVolume(-5)); + } + Owner.SpawnTimer(OpenTimeOne, async () => { OnPartialOpen(); await Timer.Delay(OpenTimeTwo, _stateChangeCancelTokenSource.Token); State = DoorState.Open; + RefreshAutoClose(); }, _stateChangeCancelTokenSource.Token); } @@ -320,7 +356,7 @@ namespace Content.Server.Doors.Components Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new AccessReaderChangeMessage(Owner, false)); } - private void QuickOpen() + private void QuickOpen(bool refresh) { if (Occludes && Owner.TryGetComponent(out OccluderComponent? occluder)) { @@ -328,6 +364,8 @@ namespace Content.Server.Doors.Components } OnPartialOpen(); State = DoorState.Open; + if(refresh) + RefreshAutoClose(); } #endregion @@ -366,17 +404,19 @@ namespace Content.Server.Doors.Components /// Boolean describing whether this door can close. public bool CanCloseGeneric() { - if (_doorCheck != null && !_doorCheck.CloseCheck()) - { + var ev = new BeforeDoorClosedEvent(); + Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, ev, false); + if (ev.Cancelled) return false; - } return !IsSafetyColliding(); } private bool SafetyCheck() { - return (_doorCheck != null && _doorCheck.SafetyCheck()) || _inhibitCrush; + var ev = new DoorSafetyEnabledEvent(); + Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, ev, false); + return ev.Safety || _inhibitCrush; } /// @@ -412,6 +452,13 @@ namespace Content.Server.Doors.Components _stateChangeCancelTokenSource?.Cancel(); _stateChangeCancelTokenSource = new(); + + if (CloseSound != null) + { + SoundSystem.Play(Filter.Pvs(Owner), CloseSound.GetSound(), + AudioParams.Default.WithVolume(-10)); + } + Owner.SpawnTimer(CloseTimeOne, async () => { // if somebody walked into the door as it was closing, and we don't crush things @@ -504,10 +551,10 @@ namespace Content.Server.Doors.Components public void Deny() { - if (_doorCheck != null && !_doorCheck.DenyCheck()) - { + var ev = new BeforeDoorDeniedEvent(); + Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, ev, false); + if (ev.Cancelled) return; - } if (State == DoorState.Open || IsWeldedShut) return; @@ -515,6 +562,25 @@ namespace Content.Server.Doors.Components _stateChangeCancelTokenSource?.Cancel(); _stateChangeCancelTokenSource = new(); SetAppearance(DoorVisualState.Deny); + + if (DenySound != null) + { + if (LastDenySoundTime == TimeSpan.Zero) + { + LastDenySoundTime = _gameTiming.CurTime; + } + else + { + var difference = _gameTiming.CurTime - LastDenySoundTime; + if (difference < TimeSpan.FromMilliseconds(DenySoundMinimumInterval)) + return; + } + + LastDenySoundTime = _gameTiming.CurTime; + SoundSystem.Play(Filter.Pvs(Owner), DenySound.GetSound(), + AudioParams.Default.WithVolume(-3)); + } + Owner.SpawnTimer(DenyTime, () => { SetAppearance(DoorVisualState.Closed); @@ -522,19 +588,24 @@ namespace Content.Server.Doors.Components } /// - /// Stops the current auto-close timer if there is one. Starts a new one if this is appropriate (i.e. entity has an IDoorCheck component that allows auto-closing). + /// Starts a new auto close timer if this is appropriate + /// (i.e. event raised is not cancelled). /// public void RefreshAutoClose() { - _autoCloseCancelTokenSource?.Cancel(); - - if (State != DoorState.Open || _doorCheck == null || !_doorCheck.AutoCloseCheck()) - { + if (State != DoorState.Open) return; - } + + var autoev = new BeforeDoorAutoCloseEvent(); + Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, autoev, false); + if (autoev.Cancelled) + return; + _autoCloseCancelTokenSource = new(); - var realCloseTime = _doorCheck.GetCloseSpeed() ?? AutoCloseDelay; + var ev = new DoorGetCloseTimeModifierEvent(); + Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, ev, false); + var realCloseTime = AutoCloseDelay * ev.CloseTimeModifier; Owner.SpawnRepeatingTimer(realCloseTime, async () => { @@ -556,21 +627,18 @@ namespace Content.Server.Doors.Components // for prying doors if (tool.HasQuality(ToolQuality.Prying) && !IsWeldedShut) { - var successfulPry = false; + var ev = new DoorGetPryTimeModifierEvent(); + Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, ev, false); - if (_doorCheck != null) - { - _doorCheck.OnStartPry(eventArgs); - successfulPry = await tool.UseTool(eventArgs.User, Owner, - _doorCheck.GetPryTime() ?? 0.5f, ToolQuality.Prying, () => _doorCheck.CanPryCheck(eventArgs)); - } - else - { - successfulPry = await tool.UseTool(eventArgs.User, Owner, 0.5f, ToolQuality.Prying); - } + var canEv = new BeforeDoorPryEvent(eventArgs); + Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, canEv, false); + + var successfulPry = await tool.UseTool(eventArgs.User, Owner, + ev.PryTimeModifier * PryTime, ToolQuality.Prying, () => !canEv.Cancelled); if (successfulPry && !IsWeldedShut) { + Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new OnDoorPryEvent(eventArgs), false); if (State == DoorState.Closed) { Open(); diff --git a/Content.Server/Doors/DoorEvents.cs b/Content.Server/Doors/DoorEvents.cs new file mode 100644 index 0000000000..73a0986664 --- /dev/null +++ b/Content.Server/Doors/DoorEvents.cs @@ -0,0 +1,141 @@ +#nullable enable +using System; +using Content.Shared.Doors; +using Content.Shared.Interaction; +using Robust.Shared.GameObjects; + +namespace Content.Server.Doors +{ + /// + /// Raised when the door's State variable is changed to a new variable that it was not equal to before. + /// + public class DoorStateChangedEvent : EntityEventArgs + { + public SharedDoorComponent.DoorState State; + + public DoorStateChangedEvent(SharedDoorComponent.DoorState state) + { + State = state; + } + } + + /// + /// Raised when the door is determining whether it is able to open. + /// Cancel to stop the door from being opened. + /// + public class BeforeDoorOpenedEvent : CancellableEntityEventArgs + { + } + + /// + /// Raised when the door is successfully opened. + /// + public class OnDoorOpenedEvent : HandledEntityEventArgs + { + } + + /// + /// Raised when the door is determining whether it is able to close. + /// Cancel to stop the door from being closed. + /// + public class BeforeDoorClosedEvent : CancellableEntityEventArgs + { + } + + /// + /// Raised when the door is successfully closed. + /// + public class OnDoorClosedEvent : HandledEntityEventArgs + { + } + + /// + /// Called when the door is determining whether it is able to deny. + /// Cancel to stop the door from being able to deny. + /// + public class BeforeDoorDeniedEvent : CancellableEntityEventArgs + { + } + + /// + /// Raised when access to the door is denied. + /// + public class OnDoorDeniedEvent : HandledEntityEventArgs + { + } + + /// + /// Raised to determine whether the door's safety is on. + /// Modify Safety to set the door's safety. + /// + public class DoorSafetyEnabledEvent : HandledEntityEventArgs + { + public bool Safety = false; + } + + /// + /// Raised to determine whether the door should automatically close. + /// Cancel to stop it from automatically closing. + /// + public class BeforeDoorAutoCloseEvent : CancellableEntityEventArgs + { + } + + /// + /// Raised to determine how long the door's pry time should be modified by. + /// Multiply PryTimeModifier by the desired amount. + /// + public class DoorGetPryTimeModifierEvent : EntityEventArgs + { + public float PryTimeModifier = 1.0f; + } + + /// + /// Raised to determine how long the door's close time should be modified by. + /// Multiply CloseTimeModifier by the desired amount. + /// + public class DoorGetCloseTimeModifierEvent : EntityEventArgs + { + public float CloseTimeModifier = 1.0f; + } + + /// + /// Raised to determine whether clicking the door should open/close it. + /// + public class DoorClickShouldActivateEvent : HandledEntityEventArgs + { + public ActivateEventArgs Args; + + public DoorClickShouldActivateEvent(ActivateEventArgs args) + { + Args = args; + } + } + + /// + /// Raised when an attempt to pry open the door is made. + /// Cancel to stop the door from being pried open. + /// + public class BeforeDoorPryEvent : CancellableEntityEventArgs + { + public InteractUsingEventArgs Args; + + public BeforeDoorPryEvent(InteractUsingEventArgs args) + { + Args = args; + } + } + + /// + /// Raised when a door is successfully pried open. + /// + public class OnDoorPryEvent : EntityEventArgs + { + public InteractUsingEventArgs Args; + + public OnDoorPryEvent(InteractUsingEventArgs args) + { + Args = args; + } + } +} diff --git a/Content.Server/Doors/IDoorCheck.cs b/Content.Server/Doors/IDoorCheck.cs deleted file mode 100644 index 3c5989a5d8..0000000000 --- a/Content.Server/Doors/IDoorCheck.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using Content.Shared.Doors; -using Content.Shared.Interaction; - -namespace Content.Server.Doors -{ - public interface IDoorCheck - { - /// - /// Called when the door's State variable is changed to a new variable that it was not equal to before. - /// - void OnStateChange(SharedDoorComponent.DoorState doorState) { } - - /// - /// Called when the door is determining whether it is able to open. - /// - /// True if the door should open, false if it should not. - bool OpenCheck() => true; - - /// - /// Called when the door is determining whether it is able to close. - /// - /// True if the door should close, false if it should not. - bool CloseCheck() => true; - - /// - /// Called when the door is determining whether it is able to deny. - /// - /// True if the door should deny, false if it should not. - bool DenyCheck() => true; - - /// - /// Whether the door's safety is on. - /// - /// True if safety is on, false if it is not. - bool SafetyCheck() => false; - - /// - /// Whether the door should close automatically. - /// - /// True if the door should close automatically, false if it should not. - bool AutoCloseCheck() => false; - - /// - /// Gets an override for the amount of time to pry open the door, or null if there is no override. - /// - /// Float if there is an override, null otherwise. - float? GetPryTime() => null; - - /// - /// Gets an override for the amount of time before the door automatically closes, or null if there is no override. - /// - /// TimeSpan if there is an override, null otherwise. - TimeSpan? GetCloseSpeed() => null; - - /// - /// A check to determine whether or not a click on the door should interact with it with the intent to open/close. - /// - /// True if the door's IActivate should not run, false otherwise. - bool BlockActivate(ActivateEventArgs eventArgs) => false; - - /// - /// Called when somebody begins to pry open the door. - /// - /// The eventArgs of the InteractUsing method that called this function. - void OnStartPry(InteractUsingEventArgs eventArgs) { } - - /// - /// Check representing whether or not the door can be pried open. - /// - /// The eventArgs of the InteractUsing method that called this function. - /// True if the door can be pried open, false if it cannot. - bool CanPryCheck(InteractUsingEventArgs eventArgs) => true; - - } -} diff --git a/Content.Server/Doors/Systems/AirlockSystem.cs b/Content.Server/Doors/Systems/AirlockSystem.cs new file mode 100644 index 0000000000..73d6508272 --- /dev/null +++ b/Content.Server/Doors/Systems/AirlockSystem.cs @@ -0,0 +1,109 @@ +using Content.Server.Doors.Components; +using Content.Server.Power.Components; +using Content.Shared.Doors; +using Content.Shared.Notification.Managers; +using Robust.Server.GameObjects; +using Robust.Shared.GameObjects; +using Robust.Shared.Localization; + +namespace Content.Server.Doors.Systems +{ + public class AirlockSystem : EntitySystem + { + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnPowerChanged); + SubscribeLocalEvent(OnStateChanged); + SubscribeLocalEvent(OnBeforeDoorOpened); + SubscribeLocalEvent(OnBeforeDoorClosed); + SubscribeLocalEvent(OnBeforeDoorDenied); + SubscribeLocalEvent(OnDoorSafetyCheck); + SubscribeLocalEvent(OnDoorAutoCloseCheck); + SubscribeLocalEvent(OnDoorCloseTimeModifier); + SubscribeLocalEvent(OnDoorClickShouldActivate); + SubscribeLocalEvent(OnDoorPry); + } + + private void OnPowerChanged(EntityUid uid, AirlockComponent component, PowerChangedEvent args) + { + if (component.AppearanceComponent != null) + { + component.AppearanceComponent.SetData(DoorVisuals.Powered, args.Powered); + } + + // BoltLights also got out + component.UpdateBoltLightStatus(); + } + + private void OnStateChanged(EntityUid uid, AirlockComponent component, DoorStateChangedEvent args) + { + // Only show the maintenance panel if the airlock is closed + if (component.WiresComponent != null) + { + component.WiresComponent.IsPanelVisible = args.State != SharedDoorComponent.DoorState.Open; + } + // If the door is closed, we should look if the bolt was locked while closing + component.UpdateBoltLightStatus(); + } + + private void OnBeforeDoorOpened(EntityUid uid, AirlockComponent component, BeforeDoorOpenedEvent args) + { + if (!component.CanChangeState()) + args.Cancel(); + } + + private void OnBeforeDoorClosed(EntityUid uid, AirlockComponent component, BeforeDoorClosedEvent args) + { + if (!component.CanChangeState()) + args.Cancel(); + } + + private void OnBeforeDoorDenied(EntityUid uid, AirlockComponent component, BeforeDoorDeniedEvent args) + { + if (!component.CanChangeState()) + args.Cancel(); + } + + private void OnDoorSafetyCheck(EntityUid uid, AirlockComponent component, DoorSafetyEnabledEvent args) + { + args.Safety = component.Safety; + } + + private void OnDoorAutoCloseCheck(EntityUid uid, AirlockComponent component, BeforeDoorAutoCloseEvent args) + { + if (!component.AutoClose) + args.Cancel(); + } + + private void OnDoorCloseTimeModifier(EntityUid uid, AirlockComponent component, DoorGetCloseTimeModifierEvent args) + { + args.CloseTimeModifier *= component.AutoCloseDelayModifier; + } + + private void OnDoorClickShouldActivate(EntityUid uid, AirlockComponent component, DoorClickShouldActivateEvent args) + { + if (component.WiresComponent != null && component.WiresComponent.IsPanelOpen && + args.Args.User.TryGetComponent(out ActorComponent? actor)) + { + component.WiresComponent.OpenInterface(actor.PlayerSession); + args.Handled = true; + } + } + + private void OnDoorPry(EntityUid uid, AirlockComponent component, BeforeDoorPryEvent args) + { + if (component.IsBolted()) + { + component.Owner.PopupMessage(args.Args.User, Loc.GetString("airlock-component-cannot-pry-is-bolted-message")); + args.Cancel(); + } + if (component.IsPowered()) + { + component.Owner.PopupMessage(args.Args.User, Loc.GetString("airlock-component-cannot-pry-is-powered-message")); + args.Cancel(); + } + } + } +} diff --git a/Content.Server/Doors/DoorSystem.cs b/Content.Server/Doors/Systems/DoorSystem.cs similarity index 100% rename from Content.Server/Doors/DoorSystem.cs rename to Content.Server/Doors/Systems/DoorSystem.cs diff --git a/Content.Server/Doors/Systems/FirelockSystem.cs b/Content.Server/Doors/Systems/FirelockSystem.cs new file mode 100644 index 0000000000..49ab584471 --- /dev/null +++ b/Content.Server/Doors/Systems/FirelockSystem.cs @@ -0,0 +1,69 @@ +using Content.Server.Doors.Components; +using Content.Shared.Doors; +using Content.Shared.Notification.Managers; +using Robust.Shared.GameObjects; +using Robust.Shared.Localization; + +namespace Content.Server.Doors.Systems +{ + public class FirelockSystem : EntitySystem + { + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnBeforeDoorOpened); + SubscribeLocalEvent(OnBeforeDoorDenied); + SubscribeLocalEvent(OnDoorGetPryTimeModifier); + SubscribeLocalEvent(OnDoorClickShouldActivate); + SubscribeLocalEvent(OnBeforeDoorPry); + SubscribeLocalEvent(OnBeforeDoorAutoclose); + } + + private void OnBeforeDoorOpened(EntityUid uid, FirelockComponent component, BeforeDoorOpenedEvent args) + { + if (component.IsHoldingFire() || component.IsHoldingPressure()) + args.Cancel(); + } + + private void OnBeforeDoorDenied(EntityUid uid, FirelockComponent component, BeforeDoorDeniedEvent args) + { + args.Cancel(); + } + + private void OnDoorGetPryTimeModifier(EntityUid uid, FirelockComponent component, DoorGetPryTimeModifierEvent args) + { + if (component.IsHoldingFire() || component.IsHoldingPressure()) + args.PryTimeModifier *= component.LockedPryTimeModifier; + } + + private void OnDoorClickShouldActivate(EntityUid uid, FirelockComponent component, DoorClickShouldActivateEvent args) + { + // We're a firelock, you can't click to open it + args.Handled = true; + } + + private void OnBeforeDoorPry(EntityUid uid, FirelockComponent component, BeforeDoorPryEvent args) + { + if (component.DoorComponent == null || component.DoorComponent.State != SharedDoorComponent.DoorState.Closed) + { + return; + } + + if (component.IsHoldingPressure()) + { + component.Owner.PopupMessage(args.Args.User, Loc.GetString("firelock-component-is-holding-pressure-message")); + } + else if (component.IsHoldingFire()) + { + component.Owner.PopupMessage(args.Args.User, Loc.GetString("firelock-component-is-holding-fire-message")); + } + } + + private void OnBeforeDoorAutoclose(EntityUid uid, FirelockComponent component, BeforeDoorAutoCloseEvent args) + { + // Firelocks can't autoclose, they must be manually closed + args.Cancel(); + } + } +} diff --git a/Content.Shared/Doors/SharedDoorComponent.cs b/Content.Shared/Doors/SharedDoorComponent.cs index db6902c5c6..e85da53ff9 100644 --- a/Content.Shared/Doors/SharedDoorComponent.cs +++ b/Content.Shared/Doors/SharedDoorComponent.cs @@ -22,6 +22,9 @@ namespace Content.Shared.Doors [ComponentDependency] protected readonly IPhysBody? PhysicsComponent = null; + [Dependency] + protected readonly IGameTiming _gameTiming = default!; + [ViewVariables] private DoorState _state = DoorState.Closed; /// diff --git a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base.yml b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base.yml index 7b88ff7002..c570660647 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base.yml @@ -38,13 +38,16 @@ - SmallImpassable - type: Door board: DoorElectronics + openSound: + path: /Audio/Machines/airlock_open.ogg + closeSound: + path: /Audio/Machines/airlock_close.ogg + denySound: + path: /Audio/Machines/airlock_deny.ogg - type: Airlock - type: Appearance visuals: - type: AirlockVisualizer - open_sound: /Audio/Machines/airlock_open.ogg - close_sound: /Audio/Machines/airlock_close.ogg - deny_sound: /Audio/Machines/airlock_deny.ogg - type: WiresVisualizer - type: ApcPowerReceiver - type: Wires diff --git a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/external.yml b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/external.yml index b33fa434c7..931987b355 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/external.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/external.yml @@ -6,12 +6,15 @@ components: - type: Door bumpOpen: false + openSound: + path: /Audio/Machines/airlock_ext_open.ogg + closeSound: + path: /Audio/Machines/airlock_ext_close.ogg + denySound: + path: /Audio/Machines/airlock_deny.ogg - type: Sprite sprite: Structures/Doors/Airlocks/Standard/external.rsi - type: Appearance visuals: - type: AirlockVisualizer - open_sound: /Audio/Machines/airlock_ext_open.ogg - close_sound: /Audio/Machines/airlock_ext_close.ogg - deny_sound: /Audio/Machines/airlock_deny.ogg - type: WiresVisualizer diff --git a/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml b/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml index 40fad456b0..74cc60c768 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml @@ -54,13 +54,16 @@ startOpen: true bumpOpen: false inhibitCrush: false + openSound: + path: /Audio/Machines/airlock_open.ogg + closeSound: + path: /Audio/Machines/airlock_close.ogg + denySound: + path: /Audio/Machines/airlock_deny.ogg - type: Firelock - type: Appearance visuals: - type: AirlockVisualizer - open_sound: /Audio/Machines/airlock_open.ogg - close_sound: /Audio/Machines/airlock_close.ogg - deny_sound: /Audio/Machines/airlock_deny.ogg animation_time: 0.6 - type: WiresVisualizer - type: Wires From e42acf2401574a0994e834aeee0058c70713a4e1 Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 2 Aug 2021 07:58:08 -0400 Subject: [PATCH 57/76] Automatic changelog update --- Resources/Changelog/Changelog.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index b9fed37da4..fdcf11f2a8 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1637,3 +1637,8 @@ Entries: - {message: Made plasma Grindable, type: Tweak} id: 291 time: '2021-08-01T19:05:02.0000000+00:00' +- author: mirrorcult + changes: + - {message: Door sounds no longer play twice sometimes., type: Fix} + id: 292 + time: '2021-08-02T11:57:06.0000000+00:00' From 009087863f4a418e032178d96981460d95906e41 Mon Sep 17 00:00:00 2001 From: Vera Aguilera Puerto <6766154+Zumorica@users.noreply.github.com> Date: Mon, 2 Aug 2021 13:59:41 +0200 Subject: [PATCH 58/76] AtmosDevices can optionally process in space. (#4405) Refactors some misc atmos things, too. --- .../Atmos/Commands/AddAtmosCommand.cs | 2 +- .../Commands/AddUnsimulatedAtmosCommand.cs | 2 +- .../Components/GridAtmosphereComponent.cs | 4 +- ...reComponent.cs => IAtmosphereComponent.cs} | 2 +- .../Components/SpaceAtmosphereComponent.cs | 13 ++++ .../SpaceGridAtmosphereComponent.cs | 16 ---- .../UnsimulatedGridAtmosphereComponent.cs | 5 +- .../EntitySystems/AtmosphereSystem.Grid.cs | 16 +++- .../AtmosphereSystem.Processing.cs | 6 +- .../Atmos/EntitySystems/AtmosphereSystem.cs | 13 ---- .../Piping/Components/AtmosDeviceComponent.cs | 15 +++- .../Piping/EntitySystems/AtmosDeviceSystem.cs | 56 ++++++++++++-- .../Structures/Storage/Canisters/base.yml | 72 ------------------ .../Storage/Canisters/gas_canisters.yml | 74 +++++++++++++++++++ 14 files changed, 176 insertions(+), 120 deletions(-) rename Content.Server/Atmos/Components/{IGridAtmosphereComponent.cs => IAtmosphereComponent.cs} (79%) create mode 100644 Content.Server/Atmos/Components/SpaceAtmosphereComponent.cs delete mode 100644 Content.Server/Atmos/Components/SpaceGridAtmosphereComponent.cs delete mode 100644 Resources/Prototypes/Entities/Structures/Storage/Canisters/base.yml diff --git a/Content.Server/Atmos/Commands/AddAtmosCommand.cs b/Content.Server/Atmos/Commands/AddAtmosCommand.cs index 4759998b1a..69c85997f5 100644 --- a/Content.Server/Atmos/Commands/AddAtmosCommand.cs +++ b/Content.Server/Atmos/Commands/AddAtmosCommand.cs @@ -47,7 +47,7 @@ namespace Content.Server.Atmos.Commands return; } - if (grid.HasComponent()) + if (grid.HasComponent()) { shell.WriteLine("Grid already has an atmosphere."); return; diff --git a/Content.Server/Atmos/Commands/AddUnsimulatedAtmosCommand.cs b/Content.Server/Atmos/Commands/AddUnsimulatedAtmosCommand.cs index 67541a5361..5fb659cc59 100644 --- a/Content.Server/Atmos/Commands/AddUnsimulatedAtmosCommand.cs +++ b/Content.Server/Atmos/Commands/AddUnsimulatedAtmosCommand.cs @@ -47,7 +47,7 @@ namespace Content.Server.Atmos.Commands return; } - if (grid.HasComponent()) + if (grid.HasComponent()) { shell.WriteLine("Grid already has an atmosphere."); return; diff --git a/Content.Server/Atmos/Components/GridAtmosphereComponent.cs b/Content.Server/Atmos/Components/GridAtmosphereComponent.cs index 53670f8d05..fedac5c2dd 100644 --- a/Content.Server/Atmos/Components/GridAtmosphereComponent.cs +++ b/Content.Server/Atmos/Components/GridAtmosphereComponent.cs @@ -16,9 +16,9 @@ namespace Content.Server.Atmos.Components /// /// This is our SSAir equivalent. /// - [ComponentReference(typeof(IGridAtmosphereComponent))] + [ComponentReference(typeof(IAtmosphereComponent))] [RegisterComponent, Serializable] - public class GridAtmosphereComponent : Component, IGridAtmosphereComponent, ISerializationHooks + public class GridAtmosphereComponent : Component, IAtmosphereComponent, ISerializationHooks { public override string Name => "GridAtmosphere"; public virtual bool Simulated => true; diff --git a/Content.Server/Atmos/Components/IGridAtmosphereComponent.cs b/Content.Server/Atmos/Components/IAtmosphereComponent.cs similarity index 79% rename from Content.Server/Atmos/Components/IGridAtmosphereComponent.cs rename to Content.Server/Atmos/Components/IAtmosphereComponent.cs index 11b953b2fa..8e9bfb8abb 100644 --- a/Content.Server/Atmos/Components/IGridAtmosphereComponent.cs +++ b/Content.Server/Atmos/Components/IAtmosphereComponent.cs @@ -2,7 +2,7 @@ namespace Content.Server.Atmos.Components { - public interface IGridAtmosphereComponent : IComponent + public interface IAtmosphereComponent : IComponent { /// /// Whether this atmosphere is simulated or not. diff --git a/Content.Server/Atmos/Components/SpaceAtmosphereComponent.cs b/Content.Server/Atmos/Components/SpaceAtmosphereComponent.cs new file mode 100644 index 0000000000..3d42d24762 --- /dev/null +++ b/Content.Server/Atmos/Components/SpaceAtmosphereComponent.cs @@ -0,0 +1,13 @@ +using Robust.Shared.GameObjects; + +namespace Content.Server.Atmos.Components +{ + [RegisterComponent] + [ComponentReference(typeof(IAtmosphereComponent))] + public class SpaceAtmosphereComponent : Component, IAtmosphereComponent + { + public override string Name => "SpaceAtmosphere"; + + public bool Simulated => false; + } +} diff --git a/Content.Server/Atmos/Components/SpaceGridAtmosphereComponent.cs b/Content.Server/Atmos/Components/SpaceGridAtmosphereComponent.cs deleted file mode 100644 index 6fa75f774d..0000000000 --- a/Content.Server/Atmos/Components/SpaceGridAtmosphereComponent.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Content.Shared.Atmos; -using Robust.Shared.GameObjects; -using Robust.Shared.Map; -using Robust.Shared.Maths; - -namespace Content.Server.Atmos.Components -{ - [RegisterComponent] - [ComponentReference(typeof(IGridAtmosphereComponent))] - public class SpaceGridAtmosphereComponent : UnsimulatedGridAtmosphereComponent - { - public override string Name => "SpaceGridAtmosphere"; - } -} diff --git a/Content.Server/Atmos/Components/UnsimulatedGridAtmosphereComponent.cs b/Content.Server/Atmos/Components/UnsimulatedGridAtmosphereComponent.cs index 806164e6c1..a01d98c1da 100644 --- a/Content.Server/Atmos/Components/UnsimulatedGridAtmosphereComponent.cs +++ b/Content.Server/Atmos/Components/UnsimulatedGridAtmosphereComponent.cs @@ -8,10 +8,9 @@ using Robust.Shared.Maths; namespace Content.Server.Atmos.Components { [RegisterComponent] - [ComponentReference(typeof(IGridAtmosphereComponent))] - [ComponentReference(typeof(GridAtmosphereComponent))] + [ComponentReference(typeof(IAtmosphereComponent))] [Serializable] - public class UnsimulatedGridAtmosphereComponent : GridAtmosphereComponent, IGridAtmosphereComponent + public class UnsimulatedGridAtmosphereComponent : GridAtmosphereComponent { public override string Name => "UnsimulatedGridAtmosphere"; diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Grid.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Grid.cs index 661b5b169f..9f09ce4e41 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Grid.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Grid.cs @@ -117,6 +117,10 @@ namespace Content.Server.Atmos.EntitySystems /// All tile mixtures in a grid. public IEnumerable GetAllTileMixtures(GridId grid, bool invalidate = false) { + // Return an array with a single space gas mixture for invalid grids. + if (!grid.IsValid()) + return new []{ GasMixture.SpaceGas }; + if (!_mapManager.TryGetGrid(grid, out var mapGrid)) return Enumerable.Empty(); @@ -678,6 +682,10 @@ namespace Content.Server.Atmos.EntitySystems /// The tile mixture, or null public GasMixture? GetTileMixture(GridId grid, Vector2i tile, bool invalidate = false) { + // Always return space gas mixtures for invalid grids (grid 0) + if (!grid.IsValid()) + return GasMixture.SpaceGas; + if (!_mapManager.TryGetGrid(grid, out var mapGrid)) return null; @@ -686,7 +694,7 @@ namespace Content.Server.Atmos.EntitySystems return GetTileMixture(gridAtmosphere, tile, invalidate); } - if (ComponentManager.TryGetComponent(mapGrid.GridEntityId, out SpaceGridAtmosphereComponent? spaceAtmosphere)) + if (ComponentManager.TryGetComponent(mapGrid.GridEntityId, out SpaceAtmosphereComponent? _)) { // Always return a new space gas mixture in this case. return GasMixture.SpaceGas; @@ -967,6 +975,10 @@ namespace Content.Server.Atmos.EntitySystems /// All adjacent tile gas mixtures to the tile in question public IEnumerable GetAdjacentTileMixtures(GridId grid, Vector2i tile, bool includeBlocked = false, bool invalidate = false) { + // For invalid grids, return an array with a single space gas mixture in it. + if (!grid.IsValid()) + return new []{ GasMixture.SpaceGas }; + if (!_mapManager.TryGetGrid(grid, out var mapGrid)) return Enumerable.Empty(); @@ -1374,7 +1386,7 @@ namespace Content.Server.Atmos.EntitySystems return false; if (ComponentManager.TryGetComponent(mapGrid.GridEntityId, out GridAtmosphereComponent? gridAtmosphere) - && gridAtmosphere.AtmosDevices.Contains(atmosDevice)) + && gridAtmosphere.AtmosDevices.Contains(atmosDevice)) { atmosDevice.JoinedGrid = null; gridAtmosphere.AtmosDevices.Remove(atmosDevice); diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs index db87ed7c10..e02f81e0ca 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs @@ -14,6 +14,7 @@ namespace Content.Server.Atmos.EntitySystems { [Dependency] private readonly IGameTiming _gameTiming = default!; + private readonly AtmosDeviceUpdateEvent _updateEvent = new(); private readonly Stopwatch _simulationStopwatch = new(); /// @@ -204,11 +205,10 @@ namespace Content.Server.Atmos.EntitySystems atmosphere.CurrentRunAtmosDevices = new Queue(atmosphere.AtmosDevices); var time = _gameTiming.CurTime; - var updateEvent = new AtmosDeviceUpdateEvent(); var number = 0; while (atmosphere.CurrentRunAtmosDevices.TryDequeue(out var device)) { - EntityManager.EventBus.RaiseLocalEvent(device.Owner.Uid, updateEvent, false); + RaiseLocalEvent(device.Owner.Uid, _updateEvent, false); device.LastProcess = time; if (number++ < LagCheckIterations) continue; @@ -241,7 +241,7 @@ namespace Content.Server.Atmos.EntitySystems { var atmosphere = _currentRunAtmosphere[_currentRunAtmosphereIndex]; - if (atmosphere.Paused || atmosphere.LifeStage >= ComponentLifeStage.Stopping) + if (atmosphere.Paused || !atmosphere.Simulated || atmosphere.LifeStage >= ComponentLifeStage.Stopping) continue; atmosphere.Timer += frameTime; diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs index 910ae05a51..8588417ad2 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs @@ -29,7 +29,6 @@ namespace Content.Server.Atmos.EntitySystems #region Events // Map events. - _mapManager.MapCreated += OnMapCreated; _mapManager.TileChanged += OnTileChanged; #endregion @@ -39,7 +38,6 @@ namespace Content.Server.Atmos.EntitySystems { base.Shutdown(); - _mapManager.MapCreated -= OnMapCreated; _mapManager.TileChanged -= OnTileChanged; } @@ -57,17 +55,6 @@ namespace Content.Server.Atmos.EntitySystems InvalidateTile(eventArgs.NewTile.GridIndex, eventArgs.NewTile.GridIndices); } - private void OnMapCreated(object? sender, MapEventArgs e) - { - if (e.Map == MapId.Nullspace) - return; - - var map = _mapManager.GetMapEntity(e.Map); - - if (!map.HasComponent()) - map.AddComponent(); - } - public override void Update(float frameTime) { base.Update(frameTime); diff --git a/Content.Server/Atmos/Piping/Components/AtmosDeviceComponent.cs b/Content.Server/Atmos/Piping/Components/AtmosDeviceComponent.cs index 9b535e7e01..00c36df4e0 100644 --- a/Content.Server/Atmos/Piping/Components/AtmosDeviceComponent.cs +++ b/Content.Server/Atmos/Piping/Components/AtmosDeviceComponent.cs @@ -8,7 +8,7 @@ using Robust.Shared.ViewVariables; namespace Content.Server.Atmos.Piping.Components { /// - /// Adds itself to a to be updated by. + /// Adds itself to a to be updated by. /// [RegisterComponent] public class AtmosDeviceComponent : Component @@ -22,6 +22,19 @@ namespace Content.Server.Atmos.Piping.Components [DataField("requireAnchored")] public bool RequireAnchored { get; private set; } = true; + /// + /// Whether this device will join an entity system to process when not in a grid. + /// + [ViewVariables] + [DataField("joinSystem")] + public bool JoinSystem { get; } = false; + + /// + /// Whether we have joined an entity system to process. + /// + [ViewVariables] + public bool JoinedSystem { get; set; } = false; + [ViewVariables] public TimeSpan LastProcess { get; set; } = TimeSpan.Zero; diff --git a/Content.Server/Atmos/Piping/EntitySystems/AtmosDeviceSystem.cs b/Content.Server/Atmos/Piping/EntitySystems/AtmosDeviceSystem.cs index 166a4d07b8..12d98e8ff2 100644 --- a/Content.Server/Atmos/Piping/EntitySystems/AtmosDeviceSystem.cs +++ b/Content.Server/Atmos/Piping/EntitySystems/AtmosDeviceSystem.cs @@ -1,10 +1,10 @@ using System; +using System.Collections.Generic; using Content.Server.Atmos.EntitySystems; using Content.Server.Atmos.Piping.Components; using JetBrains.Annotations; using Robust.Shared.GameObjects; using Robust.Shared.IoC; -using Robust.Shared.Physics; using Robust.Shared.Timing; namespace Content.Server.Atmos.Piping.EntitySystems @@ -12,9 +12,14 @@ namespace Content.Server.Atmos.Piping.EntitySystems [UsedImplicitly] public class AtmosDeviceSystem : EntitySystem { - [Dependency] private IGameTiming _gameTiming = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; + private readonly AtmosDeviceUpdateEvent _updateEvent = new(); + + private float _timer = 0f; + private readonly HashSet _joinedDevices = new(); + public override void Initialize() { base.Initialize(); @@ -33,11 +38,24 @@ namespace Content.Server.Atmos.Piping.EntitySystems public void JoinAtmosphere(AtmosDeviceComponent component) { if (!CanJoinAtmosphere(component)) + { return; + } - // We try to add the device to a valid atmosphere. + // We try to add the device to a valid atmosphere, and if we can't, try to add it to the entity system. if (!_atmosphereSystem.AddAtmosDevice(component)) - return; + { + if (component.JoinSystem) + { + _joinedDevices.Add(component); + component.JoinedSystem = true; + } + else + { + return; + } + } + component.LastProcess = _gameTiming.CurTime; @@ -46,8 +64,19 @@ namespace Content.Server.Atmos.Piping.EntitySystems public void LeaveAtmosphere(AtmosDeviceComponent component) { - if (!_atmosphereSystem.RemoveAtmosDevice(component)) + // Try to remove the component from an atmosphere, and if not + if (component.JoinedGrid != null && !_atmosphereSystem.RemoveAtmosDevice(component)) + { + // The grid might have been removed but not us... This usually shouldn't happen. + component.JoinedGrid = null; return; + } + + if (component.JoinedSystem) + { + _joinedDevices.Remove(component); + component.JoinedSystem = false; + } component.LastProcess = TimeSpan.Zero; RaiseLocalEvent(component.Owner.Uid, new AtmosDeviceDisabledEvent(), false); @@ -85,5 +114,22 @@ namespace Content.Server.Atmos.Piping.EntitySystems { RejoinAtmosphere(component); } + + public override void Update(float frameTime) + { + _timer += frameTime; + + if (_timer < _atmosphereSystem.AtmosTime) + return; + + _timer -= _atmosphereSystem.AtmosTime; + + var time = _gameTiming.CurTime; + foreach (var device in _joinedDevices) + { + RaiseLocalEvent(device.Owner.Uid, _updateEvent, false); + device.LastProcess = time; + } + } } } diff --git a/Resources/Prototypes/Entities/Structures/Storage/Canisters/base.yml b/Resources/Prototypes/Entities/Structures/Storage/Canisters/base.yml deleted file mode 100644 index 2be102cc95..0000000000 --- a/Resources/Prototypes/Entities/Structures/Storage/Canisters/base.yml +++ /dev/null @@ -1,72 +0,0 @@ -- type: entity - abstract: true - id: GasCanister - name: gas canister - description: A canister that can contain any type of gas. It can be attached to connector ports using a wrench. - parent: BaseStructureDynamic - components: - - type: InteractionOutline - - type: Sprite - netsync: false - sprite: Structures/Storage/canister.rsi - state: grey - - type: Appearance - visuals: - - type: GasPortableVisualizer - stateConnected: can-connector - - type: GasCanisterVisualizer - insertedTankState: can-open - pressureStates: - - can-o0 - - can-o1 - - can-o2 - - can-o3 - - type: UserInterface - interfaces: - - key: enum.GasCanisterUiKey.Key - type: GasCanisterBoundUserInterface - - type: Destructible - thresholds: - - trigger: - !type:DamageTrigger - damage: 300 - behaviors: - - !type:PlaySoundBehavior - sound: /Audio/Effects/metalbreak.ogg - - !type:SpawnEntitiesBehavior - spawn: - GasCanisterBrokenBase: - min: 1 - max: 1 - - !type:DoActsBehavior - acts: [ "Destruction" ] - - type: Damageable - resistances: metallicResistances - - type: Physics - bodyType: Dynamic - fixtures: - - shape: - !type:PhysShapeAabb - bounds: "-0.25,-0.25,0.25,0.25" - mass: 25 - mask: - - MobImpassable - layer: - - Opaque - - MobImpassable - - SmallImpassable - - VaultImpassable - - type: AtmosDevice - requireAnchored: false - - type: ContainerContainer - containers: - GasCanisterTankHolder: !type:ContainerSlot {} - - type: NodeContainer - nodes: - port: - !type:PortablePipeNode - nodeGroupID: Pipe - rotationsEnabled: false - volume: 1 - - type: GasPortable - - type: GasCanister diff --git a/Resources/Prototypes/Entities/Structures/Storage/Canisters/gas_canisters.yml b/Resources/Prototypes/Entities/Structures/Storage/Canisters/gas_canisters.yml index 7144edd6f1..45a3e65145 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Canisters/gas_canisters.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Canisters/gas_canisters.yml @@ -1,3 +1,77 @@ +- type: entity + abstract: true + id: GasCanister + name: gas canister + description: A canister that can contain any type of gas. It can be attached to connector ports using a wrench. + parent: BaseStructureDynamic + components: + - type: InteractionOutline + - type: Sprite + netsync: false + sprite: Structures/Storage/canister.rsi + state: grey + - type: Appearance + visuals: + - type: GasPortableVisualizer + stateConnected: can-connector + - type: GasCanisterVisualizer + insertedTankState: can-open + pressureStates: + - can-o0 + - can-o1 + - can-o2 + - can-o3 + - type: UserInterface + interfaces: + - key: enum.GasCanisterUiKey.Key + type: GasCanisterBoundUserInterface + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 300 + behaviors: + - !type:PlaySoundBehavior + sound: /Audio/Effects/metalbreak.ogg + - !type:SpawnEntitiesBehavior + spawn: + GasCanisterBrokenBase: + min: 1 + max: 1 + - !type:DoActsBehavior + acts: [ "Destruction" ] + - type: Damageable + resistances: metallicResistances + - type: Physics + bodyType: Dynamic + fixtures: + - shape: + !type:PhysShapeAabb + bounds: "-0.25,-0.25,0.25,0.25" + mass: 25 + mask: + - MobImpassable + layer: + - Opaque + - MobImpassable + - SmallImpassable + - VaultImpassable + - type: AtmosDevice + requireAnchored: false + joinSystem: true + - type: ContainerContainer + containers: + GasCanisterTankHolder: !type:ContainerSlot {} + - type: NodeContainer + nodes: + port: + !type:PortablePipeNode + nodeGroupID: Pipe + rotationsEnabled: false + volume: 1 + - type: GasPortable + - type: GasCanister + - type: entity parent: GasCanister id: StorageCanister From 34defef80f51fa96366fa8567ac70bbec1b57524 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Mon, 2 Aug 2021 23:50:48 +1000 Subject: [PATCH 59/76] Fix gravity generator yeeting --- .../Entities/Structures/Machines/gravity_generator.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Resources/Prototypes/Entities/Structures/Machines/gravity_generator.yml b/Resources/Prototypes/Entities/Structures/Machines/gravity_generator.yml index bfbeb05fb5..f06d0cdae4 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/gravity_generator.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/gravity_generator.yml @@ -25,6 +25,9 @@ - shape: !type:PhysShapeAabb bounds: "-1.5,-1.5,1.5,1.5" + mass: 500 + mask: + - Impassable layer: - Opaque - Impassable From 611340e454fe6946aa87cd69f87aa138ec716a71 Mon Sep 17 00:00:00 2001 From: Vera Aguilera Puerto Date: Mon, 2 Aug 2021 17:03:13 +0200 Subject: [PATCH 60/76] Enables nullables for atmos internals --- .../EntitySystems/AtmosphereSystem.Hotspot.cs | 3 - .../EntitySystems/AtmosphereSystem.LINDA.cs | 2 - .../AtmosphereSystem.Monstermos.cs | 68 ++++++++++--------- Content.Server/Atmos/IGasMixtureHolder.cs | 15 ---- Content.Server/Atmos/TileAtmosphere.cs | 8 ++- 5 files changed, 42 insertions(+), 54 deletions(-) diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Hotspot.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Hotspot.cs index df78bc015d..445984d021 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Hotspot.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Hotspot.cs @@ -1,11 +1,8 @@ -#nullable disable warnings -#nullable enable annotations using Content.Server.Atmos.Components; using Content.Server.Atmos.Reactions; using Content.Server.Coordinates.Helpers; using Content.Shared.Atmos; using Content.Shared.Maps; -using Robust.Shared.Map; namespace Content.Server.Atmos.EntitySystems { diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs index 2629bb4d33..52f4e926f4 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs @@ -1,5 +1,3 @@ -#nullable disable warnings -#nullable enable annotations using Content.Server.Atmos.Components; using Content.Shared.Atmos; diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Monstermos.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Monstermos.cs index 87a4f8b2e1..6f4ed1bd48 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Monstermos.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Monstermos.cs @@ -1,10 +1,6 @@ -#nullable disable warnings -#nullable enable annotations using System; -using System.Buffers; using System.Collections.Generic; using Content.Server.Atmos.Components; -using Content.Server.Coordinates.Helpers; using Content.Server.Doors.Components; using Content.Shared.Atmos; using Robust.Shared.GameObjects; @@ -12,6 +8,7 @@ using Robust.Shared.IoC; using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Random; +using Robust.Shared.Utility; namespace Content.Server.Atmos.EntitySystems { @@ -21,7 +18,7 @@ namespace Content.Server.Atmos.EntitySystems private readonly TileAtmosphereComparer _monstermosComparer = new(); - private readonly TileAtmosphere[] _equalizeTiles = new TileAtmosphere[Atmospherics.MonstermosHardTileLimit]; + private readonly TileAtmosphere?[] _equalizeTiles = new TileAtmosphere[Atmospherics.MonstermosHardTileLimit]; private readonly TileAtmosphere[] _equalizeGiverTiles = new TileAtmosphere[Atmospherics.MonstermosTileLimit]; private readonly TileAtmosphere[] _equalizeTakerTiles = new TileAtmosphere[Atmospherics.MonstermosTileLimit]; private readonly TileAtmosphere[] _equalizeQueue = new TileAtmosphere[Atmospherics.MonstermosTileLimit]; @@ -29,7 +26,7 @@ namespace Content.Server.Atmos.EntitySystems private readonly TileAtmosphere[] _depressurizeSpaceTiles = new TileAtmosphere[Atmospherics.MonstermosHardTileLimit]; private readonly TileAtmosphere[] _depressurizeProgressionOrder = new TileAtmosphere[Atmospherics.MonstermosHardTileLimit * 2]; - public void EqualizePressureInZone(IMapGrid mapGrid, GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, int cycleNum) + private void EqualizePressureInZone(IMapGrid mapGrid, GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, int cycleNum) { if (tile.Air == null || (tile.MonstermosInfo.LastCycle >= cycleNum)) return; // Already done. @@ -66,11 +63,12 @@ namespace Content.Server.Atmos.EntitySystems for (var i = 0; i < tileCount; i++) { if (i > Atmospherics.MonstermosHardTileLimit) break; - var exploring = _equalizeTiles[i]; + var exploring = _equalizeTiles[i]!; if (i < Atmospherics.MonstermosTileLimit) { - var tileMoles = exploring.Air.TotalMoles; + // Tiles in the _equalizeTiles array cannot have null air. + var tileMoles = exploring.Air!.TotalMoles; exploring.MonstermosInfo.MoleDelta = tileMoles; totalMoles += tileMoles; } @@ -107,7 +105,7 @@ namespace Content.Server.Atmos.EntitySystems if (otherTile == null) continue; - _equalizeTiles[i].MonstermosInfo.LastQueueCycle = 0; + otherTile.MonstermosInfo.LastQueueCycle = 0; } tileCount = Atmospherics.MonstermosTileLimit; @@ -119,7 +117,7 @@ namespace Content.Server.Atmos.EntitySystems for (var i = 0; i < tileCount; i++) { - var otherTile = _equalizeTiles[i]; + var otherTile = _equalizeTiles[i]!; otherTile.MonstermosInfo.LastCycle = cycleNum; otherTile.MonstermosInfo.MoleDelta -= averageMoles; if (otherTile.MonstermosInfo.MoleDelta > 0) @@ -134,7 +132,7 @@ namespace Content.Server.Atmos.EntitySystems var logN = MathF.Log2(tileCount); - // Optimization - try to spread gases using an O(nlogn) algorithm that has a chance of not working first to avoid O(n^2) + // Optimization - try to spread gases using an O(n log n) algorithm that has a chance of not working first to avoid O(n^2) if (giverTilesLength > logN && takerTilesLength > logN) { // Even if it fails, it will speed up the next part. @@ -142,7 +140,7 @@ namespace Content.Server.Atmos.EntitySystems for (var i = 0; i < tileCount; i++) { - var otherTile = _equalizeTiles[i]; + var otherTile = _equalizeTiles[i]!; otherTile.MonstermosInfo.FastDone = true; if (!(otherTile.MonstermosInfo.MoleDelta > 0)) continue; var eligibleDirections = AtmosDirection.Invalid; @@ -151,7 +149,7 @@ namespace Content.Server.Atmos.EntitySystems { var direction = (AtmosDirection) (1 << j); if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue; - var tile2 = otherTile.AdjacentTiles[j]; + var tile2 = otherTile.AdjacentTiles[j]!; // skip anything that isn't part of our current processing block. if (tile2.MonstermosInfo.FastDone || tile2.MonstermosInfo.LastQueueCycle != queueCycle) @@ -172,7 +170,7 @@ namespace Content.Server.Atmos.EntitySystems AdjustEqMovement(otherTile, direction, molesToMove); otherTile.MonstermosInfo.MoleDelta -= molesToMove; - otherTile.AdjacentTiles[j].MonstermosInfo.MoleDelta += molesToMove; + otherTile.AdjacentTiles[j]!.MonstermosInfo.MoleDelta += molesToMove; } } @@ -181,7 +179,7 @@ namespace Content.Server.Atmos.EntitySystems for (var i = 0; i < tileCount; i++) { - var otherTile = _equalizeTiles[i]; + var otherTile = _equalizeTiles[i]!; if (otherTile.MonstermosInfo.MoleDelta > 0) { _equalizeGiverTiles[giverTilesLength++] = otherTile; @@ -253,7 +251,7 @@ namespace Content.Server.Atmos.EntitySystems if (otherTile.MonstermosInfo.CurrentTransferAmount != 0 && otherTile.MonstermosInfo.CurrentTransferDirection != AtmosDirection.Invalid) { AdjustEqMovement(otherTile, otherTile.MonstermosInfo.CurrentTransferDirection, otherTile.MonstermosInfo.CurrentTransferAmount); - otherTile.AdjacentTiles[otherTile.MonstermosInfo.CurrentTransferDirection.ToIndex()] + otherTile.AdjacentTiles[otherTile.MonstermosInfo.CurrentTransferDirection.ToIndex()]! .MonstermosInfo.CurrentTransferAmount += otherTile.MonstermosInfo.CurrentTransferAmount; otherTile.MonstermosInfo.CurrentTransferAmount = 0; } @@ -320,7 +318,7 @@ namespace Content.Server.Atmos.EntitySystems AdjustEqMovement(otherTile, otherTile.MonstermosInfo.CurrentTransferDirection, otherTile.MonstermosInfo.CurrentTransferAmount); - otherTile.AdjacentTiles[otherTile.MonstermosInfo.CurrentTransferDirection.ToIndex()] + otherTile.AdjacentTiles[otherTile.MonstermosInfo.CurrentTransferDirection.ToIndex()]! .MonstermosInfo.CurrentTransferAmount += otherTile.MonstermosInfo.CurrentTransferAmount; otherTile.MonstermosInfo.CurrentTransferAmount = 0; } @@ -329,19 +327,19 @@ namespace Content.Server.Atmos.EntitySystems for (var i = 0; i < tileCount; i++) { - var otherTile = _equalizeTiles[i]; + var otherTile = _equalizeTiles[i]!; FinalizeEq(gridAtmosphere, otherTile); } for (var i = 0; i < tileCount; i++) { - var otherTile = _equalizeTiles[i]; + var otherTile = _equalizeTiles[i]!; for (var j = 0; j < Atmospherics.Directions; j++) { var direction = (AtmosDirection) (1 << j); if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue; - var otherTile2 = otherTile.AdjacentTiles[j]; - if (otherTile2?.Air?.Compare(tile.Air) == GasMixture.GasCompareResult.NoExchange) continue; + var otherTile2 = otherTile.AdjacentTiles[j]!; + if (otherTile2.Air?.Compare(tile.Air) == GasMixture.GasCompareResult.NoExchange) continue; AddActiveTile(gridAtmosphere, otherTile2); break; } @@ -354,7 +352,7 @@ namespace Content.Server.Atmos.EntitySystems Array.Clear(_equalizeQueue, 0, Atmospherics.MonstermosTileLimit); } - public void ExplosivelyDepressurize(IMapGrid mapGrid, GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, int cycleNum) + private void ExplosivelyDepressurize(IMapGrid mapGrid, GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, int cycleNum) { // Check if explosive depressurization is enabled and if the tile is valid. if (!MonstermosDepressurization || tile.Air == null) @@ -377,7 +375,8 @@ namespace Content.Server.Atmos.EntitySystems var otherTile = _depressurizeTiles[i]; otherTile.MonstermosInfo.LastCycle = cycleNum; otherTile.MonstermosInfo.CurrentTransferDirection = AtmosDirection.Invalid; - if (otherTile.Air.Immutable) + // Tiles in the _depressurizeTiles array cannot have null air. + if (otherTile.Air!.Immutable) { _depressurizeSpaceTiles[spaceTileCount++] = otherTile; otherTile.PressureSpecificTarget = otherTile; @@ -389,7 +388,7 @@ namespace Content.Server.Atmos.EntitySystems var direction = (AtmosDirection) (1 << j); if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue; var otherTile2 = otherTile.AdjacentTiles[j]; - if (otherTile2.Air == null) continue; + if (otherTile2?.Air == null) continue; if (otherTile2.MonstermosInfo.LastQueueCycle == queueCycle) continue; ConsiderFirelocks(gridAtmosphere, otherTile, otherTile2); @@ -422,8 +421,8 @@ namespace Content.Server.Atmos.EntitySystems for (var j = 0; j < Atmospherics.Directions; j++) { var direction = (AtmosDirection) (1 << j); - // TODO ATMOS This is a terrible hack that accounts for the mess that are space TileAtmospheres. - if (!otherTile.AdjacentBits.IsFlagSet(direction) && !otherTile.Air.Immutable) continue; + // Tiles in _depressurizeProgressionOrder cannot have null air. + if (!otherTile.AdjacentBits.IsFlagSet(direction) && !otherTile.Air!.Immutable) continue; var tile2 = otherTile.AdjacentTiles[j]; if (tile2?.MonstermosInfo.LastQueueCycle != queueCycle) continue; if (tile2.MonstermosInfo.LastSlowQueueCycle == queueCycleSlow) continue; @@ -510,7 +509,7 @@ namespace Content.Server.Atmos.EntitySystems InvalidateVisuals(other.GridIndex, other.GridIndices); } - public void FinalizeEq(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile) + private void FinalizeEq(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile) { Span transferDirections = stackalloc float[Atmospherics.Directions]; var hasTransferDirs = false; @@ -534,7 +533,8 @@ namespace Content.Server.Atmos.EntitySystems if (otherTile?.Air == null) continue; if (amount > 0) { - if (tile.Air.TotalMoles < amount) + // Everything that calls this method already ensures that Air will not be null. + if (tile.Air!.TotalMoles < amount) FinalizeEqNeighbors(gridAtmosphere, tile, transferDirections); otherTile.MonstermosInfo[direction.GetOpposite()] = 0; @@ -552,15 +552,19 @@ namespace Content.Server.Atmos.EntitySystems { var direction = (AtmosDirection) (1 << i); var amount = transferDirs[i]; + // Since AdjacentBits is set, AdjacentTiles[i] wouldn't be null, and neither would its air. if(amount < 0 && tile.AdjacentBits.IsFlagSet(direction)) - FinalizeEq(gridAtmosphere, tile.AdjacentTiles[i]); // A bit of recursion if needed. + FinalizeEq(gridAtmosphere, tile.AdjacentTiles[i]!); // A bit of recursion if needed. } } private void AdjustEqMovement(TileAtmosphere tile, AtmosDirection direction, float amount) { + DebugTools.Assert(tile.AdjacentBits.HasFlag(direction)); + DebugTools.Assert(tile.AdjacentTiles[direction.ToIndex()] != null); tile.MonstermosInfo[direction] += amount; - tile.AdjacentTiles[direction.ToIndex()].MonstermosInfo[direction.GetOpposite()] -= amount; + // Every call to this method already ensures that the adjacent tile won't be null. + tile.AdjacentTiles[direction.ToIndex()]!.MonstermosInfo[direction.GetOpposite()] -= amount; } private void HandleDecompressionFloorRip(IMapGrid mapGrid, TileAtmosphere tile, float sum) @@ -574,9 +578,9 @@ namespace Content.Server.Atmos.EntitySystems PryTile(mapGrid, tile.GridIndices); } - private class TileAtmosphereComparer : IComparer + private class TileAtmosphereComparer : IComparer { - public int Compare(TileAtmosphere a, TileAtmosphere b) + public int Compare(TileAtmosphere? a, TileAtmosphere? b) { if (a == null && b == null) return 0; diff --git a/Content.Server/Atmos/IGasMixtureHolder.cs b/Content.Server/Atmos/IGasMixtureHolder.cs index c8bcb60391..e7a55e8048 100644 --- a/Content.Server/Atmos/IGasMixtureHolder.cs +++ b/Content.Server/Atmos/IGasMixtureHolder.cs @@ -6,20 +6,5 @@ namespace Content.Server.Atmos public interface IGasMixtureHolder { public GasMixture Air { get; set; } - - public virtual void AssumeAir(GasMixture giver) - { - EntitySystem.Get().Merge(Air, giver); - } - - public GasMixture RemoveAir(float amount) - { - return Air.Remove(amount); - } - - public GasMixture RemoveAirVolume(float ratio) - { - return Air.RemoveRatio(ratio); - } } } diff --git a/Content.Server/Atmos/TileAtmosphere.cs b/Content.Server/Atmos/TileAtmosphere.cs index b90ced4c02..d64a9bdd35 100644 --- a/Content.Server/Atmos/TileAtmosphere.cs +++ b/Content.Server/Atmos/TileAtmosphere.cs @@ -1,5 +1,3 @@ -#nullable disable warnings -#nullable enable annotations using Content.Shared.Atmos; using Content.Shared.Maps; using Robust.Shared.Map; @@ -71,6 +69,12 @@ namespace Content.Server.Atmos [ViewVariables] public GasMixture? Air { get; set; } + GasMixture IGasMixtureHolder.Air + { + get => Air ?? new GasMixture(Atmospherics.CellVolume){ Temperature = Temperature }; + set => Air = value; + } + [ViewVariables] public float MaxFireTemperatureSustained { get; set; } From bf43141c9632a1c60f5da1e101225112f8c3cf1d Mon Sep 17 00:00:00 2001 From: ScalyChimp <72841710+scaly-chimp@users.noreply.github.com> Date: Tue, 3 Aug 2021 11:32:08 +0800 Subject: [PATCH 61/76] Meth (#4186) * adds an overdose metabolism with sensible default settings * adds the compoenents for the run fast part of meth * not sure what I changed here but I trust my past self to not fuck up for once * adds basic meth recipe * correctly names comething * I really should've checked my spelling before making this pr Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> * adds suggestion from the maintaner, who managed to get to this pr suprisingly fast * Revert "adds suggestion from the maintaner, who managed to get to this pr suprisingly fast" This reverts commit 9b0f07402e35ed5890b3af805691b690671b950c. * tweak * tweak * git's gitting on my nerves * some small tweaks * don't need these anymore * makes some stuff required * changes the meth recipe to arbitary bullshit to get the yaml thingy to leave me alone goddamnit * extremely minor change * removes overdose, because however it's gonna be done, it's definitely not my way * i should really double check every key I press * hm * sigh, I should be more thorough with looking at error messages. * beenus * gay sex is gay * this one goes out to bingo * reviews * not sure why status lifetime description wasn't being commited * Update MovespeedModifierMetabolism.cs Co-authored-by: mirrorcult * adds VV to component variables * rebalances meth, and makes the completely unrealistic recipe properly work now * meth effects should go away now or something do you think I test these changes before pushing?? * ah yes, orginization * adds proper recipe and prequisite chemicals * fixes linter hopefully * Update chemicals.yml * a * starts working on prediction * thing * predmiction?? * changes thing * does it properly * uses timespan instead of timer * uses dif timers and adds a system * updates robust and tweaks a small thing * Fixes * "Fix" prediction * starts changing the timer to timespans to avoid icky on timer end * okay fixes the check thing but now meth is broken and I don't know why * fixes predicition (partially) * Delete ContentNetIDs.cs whoops * some changes advised by sloth * certified scalycode fix right here * moves resettimer to the metabolism to make it less oop stinky * moves resettimer to the metabolism to make it less oop stinky * gamin * when the is * updates mth colour to be accurate because I forgot * abc hard ok * everything should be up to date now * makes MovespeedModifierMetabolism ECS and cleans up some other stuff * does a fixy wixy * fix thing * Revert "Merge branch 'master' of https://github.com/space-wizards/space-station-14 into meth" This reverts commit 62886561098be02f9adb6352f4e858a8269d5bd5, reversing changes made to ca34fffb5b7a40f19aec7b9e4bc37bdeab914bc1. * Revert "Revert "Merge branch 'master' of https://github.com/space-wizards/space-station-14 into meth"" This reverts commit 4f550da19656abfd0be05f818fc6b7100252d5b8. * fix hopefully * updates metabolism to works with mirror's fancy new system * updates yaml + tweaks * bruh * yaml moment * :yaml moment * Revert " :yaml moment" This reverts commit 8cb51573c64db76d989de22acdbb9c50b2c6d052. * 99th commit yay, also I need to not do this * removes something that I don't need * makes system work with this and gets rid of unnesescary check * make the update only work on active components * oops * Cleanup * alphabetise this shit * Touchup * Woops stupid alloc by me * Nerf nyoom for now Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Co-authored-by: mirrorcult Co-authored-by: metalgearsloth --- .../ReagentEffects/MovespeedModifier.cs | 69 +++++++++++++++++ .../MovespeedModifierMetabolismComponent.cs | 54 +++++++++++++ .../MetabolismMovespeedModifierSystem.cs | 77 +++++++++++++++++++ .../Prototypes/Body/Mechanisms/human.yml | 39 ++++++---- .../Catalog/ReagentDispensers/chemical.yml | 2 + Resources/Prototypes/Reagents/chemicals.yml | 27 +++++++ .../Recipes/Reactions/chemicals.yml | 28 +++++++ 7 files changed, 283 insertions(+), 13 deletions(-) create mode 100644 Content.Server/Chemistry/ReagentEffects/MovespeedModifier.cs create mode 100644 Content.Shared/Chemistry/Components/MovespeedModifierMetabolismComponent.cs create mode 100644 Content.Shared/Chemistry/MetabolismMovespeedModifierSystem.cs diff --git a/Content.Server/Chemistry/ReagentEffects/MovespeedModifier.cs b/Content.Server/Chemistry/ReagentEffects/MovespeedModifier.cs new file mode 100644 index 0000000000..9031737443 --- /dev/null +++ b/Content.Server/Chemistry/ReagentEffects/MovespeedModifier.cs @@ -0,0 +1,69 @@ +using Content.Shared.Chemistry.Reagent; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization.Manager.Attributes; +using Content.Shared.Movement.Components; +using Content.Shared.Chemistry.Components; +using Robust.Shared.Timing; +using Robust.Shared.IoC; +using System; +using Content.Shared.Chemistry.Solution; + +namespace Content.Server.Chemistry.ReagentEffects +{ + /// + /// Default metabolism for stimulants and tranqs. Attempts to find a MovementSpeedModifier on the target, + /// adding one if not there and to change the movespeed + /// + public class MovespeedModifier : ReagentEffect + { + /// + /// How much the entities' walk speed is multiplied by. + /// + [DataField("walkSpeedModifier")] + public float WalkSpeedModifier { get; set; } = 1; + + /// + /// How much the entities' run speed is multiplied by. + /// + [DataField("sprintSpeedModifier")] + public float SprintSpeedModifier { get; set; } = 1; + + /// + /// How long the modifier applies (in seconds) when metabolized. + /// + [DataField("statusLifetime")] + public float StatusLifetime = 2f; + + /// + /// Remove reagent at set rate, changes the movespeed modifiers and adds a MovespeedModifierMetabolismComponent if not already there. + /// + public override void Metabolize(IEntity solutionEntity, Solution.ReagentQuantity amount) + { + if (!solutionEntity.TryGetComponent(out MovementSpeedModifierComponent? movement)) return; + + solutionEntity.EnsureComponent(out MovespeedModifierMetabolismComponent status); + + // Only refresh movement if we need to. + var modified = !status.WalkSpeedModifier.Equals(WalkSpeedModifier) || + !status.SprintSpeedModifier.Equals(SprintSpeedModifier); + + status.WalkSpeedModifier = WalkSpeedModifier; + status.SprintSpeedModifier = SprintSpeedModifier; + + IncreaseTimer(status, StatusLifetime * amount.Quantity.Float()); + + if (modified) + movement.RefreshMovementSpeedModifiers(); + + } + public void IncreaseTimer(MovespeedModifierMetabolismComponent status, float time) + { + var gameTiming = IoCManager.Resolve(); + + var offsetTime = Math.Max(status.ModifierTimer.TotalSeconds, gameTiming.CurTime.TotalSeconds); + + status.ModifierTimer = TimeSpan.FromSeconds(offsetTime + time); + status.Dirty(); + } + } +} diff --git a/Content.Shared/Chemistry/Components/MovespeedModifierMetabolismComponent.cs b/Content.Shared/Chemistry/Components/MovespeedModifierMetabolismComponent.cs new file mode 100644 index 0000000000..afcd0f3f42 --- /dev/null +++ b/Content.Shared/Chemistry/Components/MovespeedModifierMetabolismComponent.cs @@ -0,0 +1,54 @@ +using Content.Shared.Movement.Components; +using Robust.Shared.GameObjects; +using Robust.Shared.Players; +using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; +using System; +using Robust.Shared.GameStates; +using Robust.Shared.Timing; +using Robust.Shared.IoC; + +namespace Content.Shared.Chemistry.Components +{ + //TODO: refactor movement modifier component because this is a pretty poor solution + [RegisterComponent] + [NetworkedComponent] + public sealed class MovespeedModifierMetabolismComponent : Component, IMoveSpeedModifier + { + [ViewVariables] + public override string Name => "MovespeedModifierMetabolism"; + + [ViewVariables] + public float WalkSpeedModifier { get; set; } + + [ViewVariables] + public float SprintSpeedModifier { get; set; } + + /// + /// When the current modifier is expected to end. + /// + [ViewVariables] + public TimeSpan ModifierTimer { get; set; } = TimeSpan.Zero; + + public override ComponentState GetComponentState(ICommonSession player) + { + return new MovespeedModifierMetabolismComponentState(WalkSpeedModifier, SprintSpeedModifier, ModifierTimer); + } + + [Serializable, NetSerializable] + public class MovespeedModifierMetabolismComponentState : ComponentState + { + public float WalkSpeedModifier { get; } + public float SprintSpeedModifier { get; } + public TimeSpan ModifierTimer { get; } + + public MovespeedModifierMetabolismComponentState(float walkSpeedModifier, float sprintSpeedModifier, TimeSpan modifierTimer) + { + WalkSpeedModifier = walkSpeedModifier; + SprintSpeedModifier = sprintSpeedModifier; + ModifierTimer = modifierTimer; + } + } + } +} + diff --git a/Content.Shared/Chemistry/MetabolismMovespeedModifierSystem.cs b/Content.Shared/Chemistry/MetabolismMovespeedModifierSystem.cs new file mode 100644 index 0000000000..e67f5ae7da --- /dev/null +++ b/Content.Shared/Chemistry/MetabolismMovespeedModifierSystem.cs @@ -0,0 +1,77 @@ +using Content.Shared.Chemistry.Components; +using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; +using Robust.Shared.IoC; +using Robust.Shared.Timing; +using System.Collections.Generic; +using System.Linq; +using Content.Shared.Movement.Components; +using static Content.Shared.Chemistry.Components.MovespeedModifierMetabolismComponent; + +namespace Content.Shared.Chemistry +{ + public class MetabolismMovespeedModifierSystem : EntitySystem + { + [Dependency] private readonly IGameTiming _gameTiming = default!; + + private readonly List _components = new(); + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnMovespeedHandleState); + SubscribeLocalEvent(AddComponent); + } + + private void OnMovespeedHandleState(EntityUid uid, MovespeedModifierMetabolismComponent component, ComponentHandleState args) + { + if (args.Current is not MovespeedModifierMetabolismComponentState cast) + return; + + if (ComponentManager.TryGetComponent(uid, out var modifier) && + (!component.WalkSpeedModifier.Equals(cast.WalkSpeedModifier) || + !component.SprintSpeedModifier.Equals(cast.SprintSpeedModifier))) + { + modifier.RefreshMovementSpeedModifiers(); + } + + component.WalkSpeedModifier = cast.WalkSpeedModifier; + component.SprintSpeedModifier = cast.SprintSpeedModifier; + component.ModifierTimer = cast.ModifierTimer; + + } + private void AddComponent(EntityUid uid, MovespeedModifierMetabolismComponent component, ComponentStartup args) + { + _components.Add(component); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var currentTime = _gameTiming.CurTime; + + for (var i = _components.Count - 1; i >= 0; i--) + { + var component = _components[i]; + + if (component.Deleted) + { + _components.RemoveAt(i); + continue; + } + + if (component.ModifierTimer > currentTime) continue; + + _components.RemoveAt(i); + ComponentManager.RemoveComponent(component.Owner.Uid); + + if (component.Owner.TryGetComponent(out MovementSpeedModifierComponent? modifier)) + { + modifier.RefreshMovementSpeedModifiers(); + } + } + } + } +} diff --git a/Resources/Prototypes/Body/Mechanisms/human.yml b/Resources/Prototypes/Body/Mechanisms/human.yml index 8de6180529..44652e4e65 100644 --- a/Resources/Prototypes/Body/Mechanisms/human.yml +++ b/Resources/Prototypes/Body/Mechanisms/human.yml @@ -121,11 +121,6 @@ # You're technically 'immune to poison' without a heart, but.. uhh, you'll have bigger problems on your hands. - type: Metabolizer metabolisms: - Dylovene: - effects: - - !type:HealthChange - damageClass: Toxin - healthChange: -1 Arithrazine: effects: - !type:HealthChange @@ -154,26 +149,39 @@ - !type:HealthChange damageClass: Airloss healthChange: -3 - Kelotane: + Dylovene: effects: - - !type:HealthChange - damageClass: Burn - healthChange: -1 - Synaptizine: + - !type:HealthChange + damageClass: Toxin + healthChange: -1 + Ephedrine: effects: - - !type:HealthChange - damageClass: Toxin - healthChange: 0.5 + - !type:MovespeedModifier + walkSpeedModifier: 1.2 + sprintSpeedModifier: 1.2 HeartbreakerToxin: effects: - !type:HealthChange damageClass: Airloss healthChange: 4 + Kelotane: + effects: + - !type:HealthChange + damageClass: Burn + healthChange: -1 Lexorin: effects: - !type:HealthChange damageClass: Airloss healthChange: 7 + Meth: + effects: + - !type:HealthChange + healthChange: 2.5 + damageClass: Toxin + - !type:MovespeedModifier + walkSpeedModifier: 1.3 + sprintSpeedModifier: 1.3 Omnizine: effects: - !type:HealthChange @@ -188,6 +196,11 @@ - !type:HealthChange healthChange: -2 damageClass: Brute + Synaptizine: + effects: + - !type:HealthChange + damageClass: Toxin + healthChange: 0.5 - type: entity id: OrganHumanStomach diff --git a/Resources/Prototypes/Catalog/ReagentDispensers/chemical.yml b/Resources/Prototypes/Catalog/ReagentDispensers/chemical.yml index 70073d0dde..a13d46df4b 100644 --- a/Resources/Prototypes/Catalog/ReagentDispensers/chemical.yml +++ b/Resources/Prototypes/Catalog/ReagentDispensers/chemical.yml @@ -9,6 +9,7 @@ - Fluorine - Glucose - Hydrogen + - Iodine - Iron - Lithium - Mercury @@ -25,3 +26,4 @@ - SulfuricAcid - Uranium - Water + diff --git a/Resources/Prototypes/Reagents/chemicals.yml b/Resources/Prototypes/Reagents/chemicals.yml index ff882e463c..fb5be33970 100644 --- a/Resources/Prototypes/Reagents/chemicals.yml +++ b/Resources/Prototypes/Reagents/chemicals.yml @@ -222,6 +222,33 @@ - !type:AdjustWater amount: 1 +- type: reagent + id: Meth + name: meth + desc: Methamphetamine, more commonly know as meth, is a potent stimulant, with dangerous side-effects if too much is consumed. + physicalDesc: translucent + color: "#FAFAFA" + boilingPoint: 212.0 #Meth vape when? + meltingPoint: 170.0 + +- type: reagent + id: Iodine + name: iodine + desc: Commonly added to table salt as a nutrient. On its own it tastes far less pleasing. + physicalDesc: Dark Brown + color: "#BC8A00" + boilingPoint: 184.3 + meltingPoint: 113.7 + +- type: reagent + id: Ephedrine + name: ephedrine + desc: Increases stun resistance and movement speed, giving you hand cramps. Overdose deals toxin damage and inhibits breathing + physicalDesc: Bone white + color: "#D2FFFA" + boilingPoint: 255.0 + meltingPoint: 36.0 + - type: reagent id: Oil name: oil diff --git a/Resources/Prototypes/Recipes/Reactions/chemicals.yml b/Resources/Prototypes/Recipes/Reactions/chemicals.yml index e719e598fe..511edf22e3 100644 --- a/Resources/Prototypes/Recipes/Reactions/chemicals.yml +++ b/Resources/Prototypes/Recipes/Reactions/chemicals.yml @@ -226,3 +226,31 @@ amount: 1 products: Fluorosurfactant: 5 + +- type: reaction + id: Meth + reactants: + Ephedrine: + amount: 1 + Carbon: + amount: 1 + Iodine: + amount: 1 + Phosphorus: + amount: 1 + products: + Meth: 4 #I kinda remember having to heat this up, and if you heated it up too much, it went boom, I can't remember the specific values tho. + +- type: reaction + id: Ephedrine + reactants: + Oil: + amount: 1 + Hydrogen: + amount: 1 + Sugar: + amount: 1 + Diethylamine: + amount: 1 + products: + Ephedrine: 4 From 3d2f05e3e478fba59b314e13c7a1d26ebd0071f6 Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 2 Aug 2021 23:33:10 -0400 Subject: [PATCH 62/76] Automatic changelog update --- Resources/Changelog/Changelog.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index fdcf11f2a8..4d825205a7 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1642,3 +1642,8 @@ Entries: - {message: Door sounds no longer play twice sometimes., type: Fix} id: 292 time: '2021-08-02T11:57:06.0000000+00:00' +- author: scaly-chimp + changes: + - {message: Added everyone's favorite stimulant meth!, type: Add} + id: 293 + time: '2021-08-03T03:32:08.0000000+00:00' From af05332b36a0b05314ac57fd32b88cec7fdcc14a Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Tue, 3 Aug 2021 18:49:25 +1000 Subject: [PATCH 63/76] Random offset for DefaultGrid every round (#4411) * Random offset for DefaultGrid every round This is useful to make coders aware of entitycoordinates and mapcoordinates being different and to help spot problems early. It also puts the onus of fixing positioning bugs back onto the original coder rather than someone else if they happen to spot it. * Fix clickable test * Fix entitysystemextensions --- .../Tests/ClickableTest.cs | 22 +++++++++++++------ .../Utility/EntitySystemExtensionsTest.cs | 4 +++- .../GameTicking/GameTicker.RoundFlow.cs | 6 +++++ Content.Shared/CCVar/CCVars.cs | 6 +++++ .../Spawning/EntitySystemExtensions.cs | 2 +- 5 files changed, 31 insertions(+), 9 deletions(-) diff --git a/Content.IntegrationTests/Tests/ClickableTest.cs b/Content.IntegrationTests/Tests/ClickableTest.cs index ed29ec9d5b..7ac1f71eef 100644 --- a/Content.IntegrationTests/Tests/ClickableTest.cs +++ b/Content.IntegrationTests/Tests/ClickableTest.cs @@ -1,11 +1,14 @@ using System; using System.Threading.Tasks; using Content.Client.Clickable; +using Content.Server.GameTicking; using NUnit.Framework; using Robust.Server.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Map; +using Robust.Shared.Maths; +using Robust.Shared.Timing; namespace Content.IntegrationTests.Tests { @@ -57,12 +60,19 @@ namespace Content.IntegrationTests.Tests [TestCase("ClickTestRotatingCornerInvisibleNoRot", 0.25f, 0.25f, DirSouthEastJustShy, 1, ExpectedResult = true)] public async Task Test(string prototype, float clickPosX, float clickPosY, double angle, float scale) { + Vector2? worldPos = null; EntityUid entity = default; + var clientEntManager = _client.ResolveDependency(); + var serverEntManager = _server.ResolveDependency(); + var mapManager = _server.ResolveDependency(); + var gameTicker = _server.ResolveDependency().GetEntitySystem(); await _server.WaitPost(() => { - var entMgr = IoCManager.Resolve(); - var ent = entMgr.SpawnEntity(prototype, new MapCoordinates(0, 0, new MapId(1))); + var gridEnt = mapManager.GetGrid(gameTicker.DefaultGridId).GridEntityId; + worldPos = serverEntManager.GetEntity(gridEnt).Transform.WorldPosition; + + var ent = serverEntManager.SpawnEntity(prototype, new EntityCoordinates(gridEnt, 0f, 0f)); ent.Transform.LocalRotation = angle; ent.GetComponent().Scale = (scale, scale); entity = ent.Uid; @@ -75,17 +85,15 @@ namespace Content.IntegrationTests.Tests await _client.WaitPost(() => { - var entMgr = IoCManager.Resolve(); - var ent = entMgr.GetEntity(entity); + var ent = clientEntManager.GetEntity(entity); var clickable = ent.GetComponent(); - hit = clickable.CheckClick((clickPosX, clickPosY), out _, out _); + hit = clickable.CheckClick((clickPosX, clickPosY) + worldPos!.Value, out _, out _); }); await _server.WaitPost(() => { - var entMgr = IoCManager.Resolve(); - entMgr.DeleteEntity(entity); + serverEntManager.DeleteEntity(entity); }); return hit; diff --git a/Content.IntegrationTests/Tests/Utility/EntitySystemExtensionsTest.cs b/Content.IntegrationTests/Tests/Utility/EntitySystemExtensionsTest.cs index b33ccc3a57..b810b42e5d 100644 --- a/Content.IntegrationTests/Tests/Utility/EntitySystemExtensionsTest.cs +++ b/Content.IntegrationTests/Tests/Utility/EntitySystemExtensionsTest.cs @@ -48,6 +48,8 @@ namespace Content.IntegrationTests.Tests.Utility var mapId = new MapId(1); var grid = sMapManager.GetGrid(new GridId(1)); grid.SetTile(new Vector2i(0, 0), new Tile(1)); + var gridEnt = sEntityManager.GetEntity(grid.GridEntityId); + var gridPos = gridEnt.Transform.WorldPosition; var entityCoordinates = new EntityCoordinates(grid.GridEntityId, 0, 0); // Nothing blocking it, only entity is the grid @@ -55,7 +57,7 @@ namespace Content.IntegrationTests.Tests.Utility Assert.True(sEntityManager.TrySpawnIfUnobstructed(null, entityCoordinates, CollisionGroup.Impassable, out var entity)); Assert.NotNull(entity); - var mapCoordinates = new MapCoordinates(0, 0, mapId); + var mapCoordinates = new MapCoordinates(gridPos.X, gridPos.Y, mapId); // Nothing blocking it, only entity is the grid Assert.NotNull(sEntityManager.SpawnIfUnobstructed(null, mapCoordinates, CollisionGroup.Impassable)); diff --git a/Content.Server/GameTicking/GameTicker.RoundFlow.cs b/Content.Server/GameTicking/GameTicker.RoundFlow.cs index e47c36c58a..a7e8223bb9 100644 --- a/Content.Server/GameTicking/GameTicker.RoundFlow.cs +++ b/Content.Server/GameTicking/GameTicker.RoundFlow.cs @@ -11,6 +11,7 @@ using Robust.Server.Player; using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Log; +using Robust.Shared.Maths; using Robust.Shared.Player; using Robust.Shared.Random; using Robust.Shared.Timing; @@ -62,6 +63,11 @@ namespace Content.Server.GameTicking throw new InvalidOperationException($"No grid found for map {map}"); } + var maxStationOffset = _configurationManager.GetCVar(CCVars.MaxStationOffset); + var x = _robustRandom.NextFloat() * maxStationOffset * 2 - maxStationOffset; + var y = _robustRandom.NextFloat() * maxStationOffset * 2 - maxStationOffset; + _entityManager.GetEntity(grid.GridEntityId).Transform.LocalPosition = new Vector2(x, y); + DefaultGridId = grid.Index; _spawnPoint = grid.ToCoordinates(); diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index 3b2091492c..c4b1cd403c 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -55,6 +55,12 @@ namespace Content.Shared.CCVar public static readonly CVarDef GameMap = CVarDef.Create("game.map", "Maps/saltern.yml", CVar.SERVERONLY); + /// + /// When the default blueprint is loaded what is the maximum amount it can be offset from 0,0. + /// + public static readonly CVarDef MaxStationOffset = + CVarDef.Create("game.maxstationoffset", 1000.0f); + /// /// When enabled, guests will be assigned permanent UIDs and will have their preferences stored. /// diff --git a/Content.Shared/Spawning/EntitySystemExtensions.cs b/Content.Shared/Spawning/EntitySystemExtensions.cs index 6ec0226358..a096d0f4d9 100644 --- a/Content.Shared/Spawning/EntitySystemExtensions.cs +++ b/Content.Shared/Spawning/EntitySystemExtensions.cs @@ -32,7 +32,7 @@ namespace Content.Shared.Spawning in Box2? box = null, SharedBroadphaseSystem? collision = null) { - var boxOrDefault = box.GetValueOrDefault(Box2.UnitCentered); + var boxOrDefault = box.GetValueOrDefault(Box2.UnitCentered).Translated(coordinates.Position); collision ??= EntitySystem.Get(); foreach (var body in collision.GetCollidingEntities(coordinates.MapId, in boxOrDefault)) From ed8dab297445568b21a822a6b66c4be1e53a634c Mon Sep 17 00:00:00 2001 From: PJBot Date: Tue, 3 Aug 2021 04:50:28 -0400 Subject: [PATCH 64/76] Automatic changelog update --- Resources/Changelog/Changelog.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 4d825205a7..c3618cb6e9 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1647,3 +1647,8 @@ Entries: - {message: Added everyone's favorite stimulant meth!, type: Add} id: 293 time: '2021-08-03T03:32:08.0000000+00:00' +- author: metalgearsloth + changes: + - {message: 'Station will spawn at a random position rather than 0,0.', type: Tweak} + id: 294 + time: '2021-08-03T08:49:25.0000000+00:00' From 2205fef06a36193a990b67e2908124f831099209 Mon Sep 17 00:00:00 2001 From: Vera Aguilera Puerto Date: Tue, 3 Aug 2021 14:26:34 +0200 Subject: [PATCH 65/76] Document a few atmos classes. --- Content.Server/Atmos/Components/GasTankComponent.cs | 1 - Content.Server/Atmos/Components/GridAtmosphereComponent.cs | 3 ++- Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs | 3 +++ Content.Server/Atmos/TileAtmosphere.cs | 2 ++ 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Content.Server/Atmos/Components/GasTankComponent.cs b/Content.Server/Atmos/Components/GasTankComponent.cs index 2d0f7105b8..c8fdcb815e 100644 --- a/Content.Server/Atmos/Components/GasTankComponent.cs +++ b/Content.Server/Atmos/Components/GasTankComponent.cs @@ -1,4 +1,3 @@ -#nullable disable warnings using System; using Content.Server.Atmos.EntitySystems; using Content.Server.Body.Respiratory; diff --git a/Content.Server/Atmos/Components/GridAtmosphereComponent.cs b/Content.Server/Atmos/Components/GridAtmosphereComponent.cs index fedac5c2dd..76840c37c2 100644 --- a/Content.Server/Atmos/Components/GridAtmosphereComponent.cs +++ b/Content.Server/Atmos/Components/GridAtmosphereComponent.cs @@ -14,13 +14,14 @@ using Dependency = Robust.Shared.IoC.DependencyAttribute; namespace Content.Server.Atmos.Components { /// - /// This is our SSAir equivalent. + /// Internal Atmos class. Use to interact with atmos instead. /// [ComponentReference(typeof(IAtmosphereComponent))] [RegisterComponent, Serializable] public class GridAtmosphereComponent : Component, IAtmosphereComponent, ISerializationHooks { public override string Name => "GridAtmosphere"; + public virtual bool Simulated => true; [ViewVariables] diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs index 8588417ad2..90d482e39a 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs @@ -8,6 +8,9 @@ using Robust.Shared.Map; namespace Content.Server.Atmos.EntitySystems { + /// + /// This is our SSAir equivalent, if you need to interact with or query atmos in any way, go through this. + /// [UsedImplicitly] public partial class AtmosphereSystem : SharedAtmosphereSystem { diff --git a/Content.Server/Atmos/TileAtmosphere.cs b/Content.Server/Atmos/TileAtmosphere.cs index d64a9bdd35..49447e012a 100644 --- a/Content.Server/Atmos/TileAtmosphere.cs +++ b/Content.Server/Atmos/TileAtmosphere.cs @@ -1,3 +1,4 @@ +using Content.Server.Atmos.EntitySystems; using Content.Shared.Atmos; using Content.Shared.Maps; using Robust.Shared.Map; @@ -8,6 +9,7 @@ namespace Content.Server.Atmos { /// /// Internal Atmos class that stores data about the atmosphere in a grid. + /// You shouldn't use this directly, use instead. /// public class TileAtmosphere : IGasMixtureHolder { From 503677992937b350169371c01971ac3dd71c534c Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Wed, 4 Aug 2021 00:11:45 +1000 Subject: [PATCH 66/76] Update submodule We do a little bit of debugging --- RobustToolbox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RobustToolbox b/RobustToolbox index 2239c30924..04d94f87fc 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit 2239c309240a20d00c14a400fd2aa7f424b65413 +Subproject commit 04d94f87fc7205443aa8d9ea2b6fa178d4ba825d From c453bb07c55b95d01d8c04a5fd0f45a8e0ab7aa2 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Wed, 4 Aug 2021 01:02:43 +1000 Subject: [PATCH 67/76] Make pulling feel less crap (#4414) Something something pulling refactor someday --- Content.Client/Physics/Controllers/MoverController.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Content.Client/Physics/Controllers/MoverController.cs b/Content.Client/Physics/Controllers/MoverController.cs index de4e4eb7b3..0304888e34 100644 --- a/Content.Client/Physics/Controllers/MoverController.cs +++ b/Content.Client/Physics/Controllers/MoverController.cs @@ -21,6 +21,12 @@ namespace Content.Client.Physics.Controllers body.Predict = true; // TODO: equal prediction instead of true? + foreach (var joint in body.Joints) + { + joint.BodyA.Predict = true; + joint.BodyB.Predict = true; + } + // Server-side should just be handled on its own so we'll just do this shizznit if (player.TryGetComponent(out IMobMoverComponent? mobMover)) { From d5d1818b04616bc838cf69e7c02629fa233f99bd Mon Sep 17 00:00:00 2001 From: PJBot Date: Tue, 3 Aug 2021 11:03:46 -0400 Subject: [PATCH 68/76] Automatic changelog update --- Resources/Changelog/Changelog.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index c3618cb6e9..fef67ea5f5 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1652,3 +1652,9 @@ Entries: - {message: 'Station will spawn at a random position rather than 0,0.', type: Tweak} id: 294 time: '2021-08-03T08:49:25.0000000+00:00' +- author: metalgearsloth + changes: + - {message: Fix lerping for other physics bodies., type: Fix} + - {message: Fix joint prediction., type: Fix} + id: 295 + time: '2021-08-03T15:02:44.0000000+00:00' From 28a7d5041a98a9ab7c47b64f469f1f38f533d47e Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Wed, 4 Aug 2021 01:16:42 +1000 Subject: [PATCH 69/76] Comment joint prediction --- Content.Client/Physics/Controllers/MoverController.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Content.Client/Physics/Controllers/MoverController.cs b/Content.Client/Physics/Controllers/MoverController.cs index 0304888e34..9b53311e91 100644 --- a/Content.Client/Physics/Controllers/MoverController.cs +++ b/Content.Client/Physics/Controllers/MoverController.cs @@ -19,8 +19,13 @@ namespace Content.Client.Physics.Controllers !player.TryGetComponent(out IMoverComponent? mover) || !player.TryGetComponent(out PhysicsComponent? body)) return; - body.Predict = true; // TODO: equal prediction instead of true? + // Essentially we only want to set our mob to predicted so every other entity we just interpolate + // (i.e. only see what the server has sent us). + // The exception to this is joints. + body.Predict = true; + // We set joints to predicted given these can affect how our mob moves. + // I would only recommend disabling this if you make pulling not use joints anymore (someday maybe?) foreach (var joint in body.Joints) { joint.BodyA.Predict = true; From b7d049a52c767a767eefe7c4be1ee813b364da8c Mon Sep 17 00:00:00 2001 From: Vera Aguilera Puerto Date: Wed, 4 Aug 2021 09:25:30 +0200 Subject: [PATCH 70/76] Add CVar for random grid offset, disable it by default. --- Content.Server/GameTicking/GameTicker.CVars.cs | 12 ++++++++++-- Content.Server/GameTicking/GameTicker.RoundFlow.cs | 13 ++++++++----- Content.Server/GameTicking/GameTicker.Spawning.cs | 8 ++++---- Content.Server/GameTicking/GameTicker.cs | 1 - Content.Shared/CCVar/CCVars.cs | 6 ++++++ 5 files changed, 28 insertions(+), 12 deletions(-) diff --git a/Content.Server/GameTicking/GameTicker.CVars.cs b/Content.Server/GameTicking/GameTicker.CVars.cs index f8cd9faea6..a00299df4b 100644 --- a/Content.Server/GameTicking/GameTicker.CVars.cs +++ b/Content.Server/GameTicking/GameTicker.CVars.cs @@ -21,14 +21,22 @@ namespace Content.Server.GameTicking [ViewVariables] public bool DisallowLateJoin { get; private set; } = false; + [ViewVariables] + public bool StationOffset { get; private set; } = false; + + [ViewVariables] + public float MaxStationOffset { get; private set; } = 0f; + private void InitializeCVars() { _configurationManager.OnValueChanged(CCVars.GameLobbyEnabled, value => LobbyEnabled = value, true); _configurationManager.OnValueChanged(CCVars.GameDummyTicker, value => DummyTicker = value, true); _configurationManager.OnValueChanged(CCVars.GameMap, value => ChosenMap = value, true); _configurationManager.OnValueChanged(CCVars.GameLobbyDuration, value => LobbyDuration = TimeSpan.FromSeconds(value), true); - _configurationManager.OnValueChanged(CCVars.GameDisallowLateJoins, invokeImmediately:true, - onValueChanged:value => { DisallowLateJoin = value; UpdateLateJoinStatus(); UpdateJobsAvailable(); }); + _configurationManager.OnValueChanged(CCVars.GameDisallowLateJoins, + value => { DisallowLateJoin = value; UpdateLateJoinStatus(); UpdateJobsAvailable(); }, true); + _configurationManager.OnValueChanged(CCVars.StationOffset, value => StationOffset = value, true); + _configurationManager.OnValueChanged(CCVars.MaxStationOffset, value => MaxStationOffset = value, true); } } } diff --git a/Content.Server/GameTicking/GameTicker.RoundFlow.cs b/Content.Server/GameTicking/GameTicker.RoundFlow.cs index a7e8223bb9..e65c191f1c 100644 --- a/Content.Server/GameTicking/GameTicker.RoundFlow.cs +++ b/Content.Server/GameTicking/GameTicker.RoundFlow.cs @@ -63,10 +63,13 @@ namespace Content.Server.GameTicking throw new InvalidOperationException($"No grid found for map {map}"); } - var maxStationOffset = _configurationManager.GetCVar(CCVars.MaxStationOffset); - var x = _robustRandom.NextFloat() * maxStationOffset * 2 - maxStationOffset; - var y = _robustRandom.NextFloat() * maxStationOffset * 2 - maxStationOffset; - _entityManager.GetEntity(grid.GridEntityId).Transform.LocalPosition = new Vector2(x, y); + if (StationOffset) + { + // Apply a random offset to the station grid entity. + var x = _robustRandom.NextFloat() * MaxStationOffset * 2 - MaxStationOffset; + var y = _robustRandom.NextFloat() * MaxStationOffset * 2 - MaxStationOffset; + EntityManager.GetEntity(grid.GridEntityId).Transform.LocalPosition = new Vector2(x, y); + } DefaultGridId = grid.Index; _spawnPoint = grid.ToCoordinates(); @@ -286,7 +289,7 @@ namespace Content.Server.GameTicking } // Delete all entities. - foreach (var entity in _entityManager.GetEntities().ToList()) + foreach (var entity in EntityManager.GetEntities().ToList()) { // TODO: Maybe something less naive here? // FIXME: Actually, definitely. diff --git a/Content.Server/GameTicking/GameTicker.Spawning.cs b/Content.Server/GameTicking/GameTicker.Spawning.cs index 840af56302..cab3c3c1d1 100644 --- a/Content.Server/GameTicking/GameTicker.Spawning.cs +++ b/Content.Server/GameTicking/GameTicker.Spawning.cs @@ -155,7 +155,7 @@ namespace Content.Server.GameTicking private IEntity SpawnPlayerMob(Job job, HumanoidCharacterProfile? profile, bool lateJoin = true) { var coordinates = lateJoin ? GetLateJoinSpawnPoint() : GetJobSpawnPoint(job.Prototype.ID); - var entity = _entityManager.SpawnEntity(PlayerPrototypeName, coordinates); + var entity = EntityManager.SpawnEntity(PlayerPrototypeName, coordinates); if (job.StartingGear != null) { @@ -175,7 +175,7 @@ namespace Content.Server.GameTicking private IEntity SpawnObserverMob() { var coordinates = GetObserverSpawnPoint(); - return _entityManager.SpawnEntity(ObserverPrototypeName, coordinates); + return EntityManager.SpawnEntity(ObserverPrototypeName, coordinates); } #endregion @@ -189,7 +189,7 @@ namespace Content.Server.GameTicking var equipmentStr = startingGear.GetGear(slot, profile); if (equipmentStr != string.Empty) { - var equipmentEntity = _entityManager.SpawnEntity(equipmentStr, entity.Transform.Coordinates); + var equipmentEntity = EntityManager.SpawnEntity(equipmentStr, entity.Transform.Coordinates); inventory.Equip(slot, equipmentEntity.GetComponent()); } } @@ -200,7 +200,7 @@ namespace Content.Server.GameTicking var inhand = startingGear.Inhand; foreach (var (hand, prototype) in inhand) { - var inhandEntity = _entityManager.SpawnEntity(prototype, entity.Transform.Coordinates); + var inhandEntity = EntityManager.SpawnEntity(prototype, entity.Transform.Coordinates); handsComponent.TryPickupEntity(hand, inhandEntity, checkActionBlocker: false); } } diff --git a/Content.Server/GameTicking/GameTicker.cs b/Content.Server/GameTicking/GameTicker.cs index 5400e0348a..9ed3eda9da 100644 --- a/Content.Server/GameTicking/GameTicker.cs +++ b/Content.Server/GameTicking/GameTicker.cs @@ -72,7 +72,6 @@ namespace Content.Server.GameTicking UpdateRoundFlow(frameTime); } - [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IMapLoader _mapLoader = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index c4b1cd403c..450cc14727 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -55,6 +55,12 @@ namespace Content.Shared.CCVar public static readonly CVarDef GameMap = CVarDef.Create("game.map", "Maps/saltern.yml", CVar.SERVERONLY); + /// + /// Whether a random position offset will be applied to the station on roundstart. + /// + public static readonly CVarDef StationOffset = + CVarDef.Create("game.station_offset", false); + /// /// When the default blueprint is loaded what is the maximum amount it can be offset from 0,0. /// From 13ceae872bd2df4a792666d4057531a77e283086 Mon Sep 17 00:00:00 2001 From: Vera Aguilera Puerto Date: Wed, 4 Aug 2021 09:48:49 +0200 Subject: [PATCH 71/76] Fixes not taking pressure damage in space. Fixes #4415 --- .../Atmos/Components/AtmosExposedComponent.cs | 17 +++++++---------- .../Atmos/Components/FlammableComponent.cs | 6 +++--- .../EntitySystems/AtmosphereSystem.Grid.cs | 2 +- .../Atmos/EntitySystems/AtmosphereSystem.cs | 2 +- Resources/Changelog/Parts/funny_gas.yml | 4 ++++ 5 files changed, 16 insertions(+), 15 deletions(-) create mode 100644 Resources/Changelog/Parts/funny_gas.yml diff --git a/Content.Server/Atmos/Components/AtmosExposedComponent.cs b/Content.Server/Atmos/Components/AtmosExposedComponent.cs index e742458f10..9be43d8067 100644 --- a/Content.Server/Atmos/Components/AtmosExposedComponent.cs +++ b/Content.Server/Atmos/Components/AtmosExposedComponent.cs @@ -23,23 +23,20 @@ namespace Content.Server.Atmos.Components [ViewVariables] [ComponentDependency] private readonly FlammableComponent? _flammableComponent = null; - public void Update(TileAtmosphere tile, float frameDelta, AtmosphereSystem atmosphereSystem) + public void Update(GasMixture air, float frameDelta, AtmosphereSystem atmosphereSystem) { if (_temperatureComponent != null) { - if (tile.Air != null) - { - var temperatureDelta = tile.Air.Temperature - _temperatureComponent.CurrentTemperature; - var tileHeatCapacity = atmosphereSystem.GetHeatCapacity(tile.Air); - var heat = temperatureDelta * (tileHeatCapacity * _temperatureComponent.HeatCapacity / (tileHeatCapacity + _temperatureComponent.HeatCapacity)); - _temperatureComponent.ReceiveHeat(heat); - } + var temperatureDelta = air.Temperature - _temperatureComponent.CurrentTemperature; + var tileHeatCapacity = atmosphereSystem.GetHeatCapacity(air); + var heat = temperatureDelta * (tileHeatCapacity * _temperatureComponent.HeatCapacity / (tileHeatCapacity + _temperatureComponent.HeatCapacity)); + _temperatureComponent.ReceiveHeat(heat); _temperatureComponent.Update(); } - _barotraumaComponent?.Update(tile.Air?.Pressure ?? 0); + _barotraumaComponent?.Update(air.Pressure); - _flammableComponent?.Update(tile); + _flammableComponent?.Update(air); } } } diff --git a/Content.Server/Atmos/Components/FlammableComponent.cs b/Content.Server/Atmos/Components/FlammableComponent.cs index 99aa2402f8..3cccaba238 100644 --- a/Content.Server/Atmos/Components/FlammableComponent.cs +++ b/Content.Server/Atmos/Components/FlammableComponent.cs @@ -63,7 +63,7 @@ namespace Content.Server.Atmos.Components UpdateAppearance(); } - public void Update(TileAtmosphere tile) + public void Update(GasMixture air) { // Slowly dry ourselves off if wet. if (FireStacks < 0) @@ -104,13 +104,13 @@ namespace Content.Server.Atmos.Components } // If we're in an oxygenless environment, put the fire out. - if (tile.Air?.GetMoles(Gas.Oxygen) < 1f) + if (air.GetMoles(Gas.Oxygen) < 1f) { Extinguish(); return; } - EntitySystem.Get().HotspotExpose(tile.GridIndex, tile.GridIndices, 700f, 50f, true); + EntitySystem.Get().HotspotExpose(Owner.Transform.Coordinates, 700f, 50f, true); var physics = Owner.GetComponent(); diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Grid.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Grid.cs index 9f09ce4e41..35e06f437d 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Grid.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Grid.cs @@ -670,7 +670,7 @@ namespace Content.Server.Atmos.EntitySystems public GasMixture? GetTileMixture(EntityCoordinates coordinates, bool invalidate = false) { return TryGetGridAndTile(coordinates, out var tuple) - ? GetTileMixture(tuple.Value.Grid, tuple.Value.Tile, invalidate) : null; + ? GetTileMixture(tuple.Value.Grid, tuple.Value.Tile, invalidate) : GasMixture.SpaceGas; } /// diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs index 90d482e39a..966a3192f5 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs @@ -71,7 +71,7 @@ namespace Content.Server.Atmos.EntitySystems foreach (var exposed in EntityManager.ComponentManager.EntityQuery()) { // TODO ATMOS: Kill this with fire. - var tile = GetTileAtmosphereOrCreateSpace(exposed.Owner.Transform.Coordinates); + var tile = GetTileMixture(exposed.Owner.Transform.Coordinates); if (tile == null) continue; exposed.Update(tile, _exposedTimer, this); } diff --git a/Resources/Changelog/Parts/funny_gas.yml b/Resources/Changelog/Parts/funny_gas.yml new file mode 100644 index 0000000000..a2e7209f07 --- /dev/null +++ b/Resources/Changelog/Parts/funny_gas.yml @@ -0,0 +1,4 @@ +author: Zumorica +changes: + - type: Fix + message: Fixes mobs not taking pressure damage on space. From 5d60af2656dab27c8a009dd42108344bace69fde Mon Sep 17 00:00:00 2001 From: PJBot Date: Wed, 4 Aug 2021 03:49:53 -0400 Subject: [PATCH 72/76] Automatic changelog update --- Resources/Changelog/Changelog.yml | 5 +++++ Resources/Changelog/Parts/funny_gas.yml | 4 ---- 2 files changed, 5 insertions(+), 4 deletions(-) delete mode 100644 Resources/Changelog/Parts/funny_gas.yml diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index fef67ea5f5..b6509f5e2f 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1658,3 +1658,8 @@ Entries: - {message: Fix joint prediction., type: Fix} id: 295 time: '2021-08-03T15:02:44.0000000+00:00' +- author: Zumorica + changes: + - {message: Fixes mobs not taking pressure damage on space., type: Fix} + id: 296 + time: '2021-08-04T07:49:53.509340+00:00' diff --git a/Resources/Changelog/Parts/funny_gas.yml b/Resources/Changelog/Parts/funny_gas.yml deleted file mode 100644 index a2e7209f07..0000000000 --- a/Resources/Changelog/Parts/funny_gas.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: Zumorica -changes: - - type: Fix - message: Fixes mobs not taking pressure damage on space. From 532c9a6431df1ce7fef356b06aa3ba8a8b6fd242 Mon Sep 17 00:00:00 2001 From: Swept Date: Thu, 5 Aug 2021 03:18:05 -0700 Subject: [PATCH 73/76] Adds an alternate jumpsuit for botany (#4419) * Adds an alternate botanists jumpsuit * Updated license information --- .../Catalog/Fills/Lockers/service.yml | 4 +++ .../Entities/Clothing/Head/hats.yml | 13 ++++++++- .../Entities/Clothing/Uniforms/jumpsuits.yml | 11 ++++++++ .../Hats/truckershat.rsi/equipped-HELMET.png | Bin 0 -> 448 bytes .../Head/Hats/truckershat.rsi/icon.png | Bin 0 -> 306 bytes .../Head/Hats/truckershat.rsi/inhand-left.png | Bin 0 -> 417 bytes .../Hats/truckershat.rsi/inhand-right.png | Bin 0 -> 416 bytes .../Head/Hats/truckershat.rsi/meta.json | 26 ++++++++++++++++++ .../overalls.rsi/equipped-INNERCLOTHING.png | Bin 0 -> 1307 bytes .../Uniforms/Jumpsuit/overalls.rsi/icon.png | Bin 0 -> 503 bytes .../Jumpsuit/overalls.rsi/inhand-left.png | Bin 0 -> 339 bytes .../Jumpsuit/overalls.rsi/inhand-right.png | Bin 0 -> 416 bytes .../Uniforms/Jumpsuit/overalls.rsi/meta.json | 26 ++++++++++++++++++ 13 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 Resources/Textures/Clothing/Head/Hats/truckershat.rsi/equipped-HELMET.png create mode 100644 Resources/Textures/Clothing/Head/Hats/truckershat.rsi/icon.png create mode 100644 Resources/Textures/Clothing/Head/Hats/truckershat.rsi/inhand-left.png create mode 100644 Resources/Textures/Clothing/Head/Hats/truckershat.rsi/inhand-right.png create mode 100644 Resources/Textures/Clothing/Head/Hats/truckershat.rsi/meta.json create mode 100644 Resources/Textures/Clothing/Uniforms/Jumpsuit/overalls.rsi/equipped-INNERCLOTHING.png create mode 100644 Resources/Textures/Clothing/Uniforms/Jumpsuit/overalls.rsi/icon.png create mode 100644 Resources/Textures/Clothing/Uniforms/Jumpsuit/overalls.rsi/inhand-left.png create mode 100644 Resources/Textures/Clothing/Uniforms/Jumpsuit/overalls.rsi/inhand-right.png create mode 100644 Resources/Textures/Clothing/Uniforms/Jumpsuit/overalls.rsi/meta.json diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/service.yml b/Resources/Prototypes/Catalog/Fills/Lockers/service.yml index d2e13f35e9..8585bae168 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/service.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/service.yml @@ -84,3 +84,7 @@ prob: 0.8 - id: TomatoSeeds prob: 1 + - id: ClothingUniformOveralls + prob: 1 + - id: ClothingHeadHatTrucker + prob: 0.1 diff --git a/Resources/Prototypes/Entities/Clothing/Head/hats.yml b/Resources/Prototypes/Entities/Clothing/Head/hats.yml index 783c4e9186..fb84c0f136 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/hats.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/hats.yml @@ -23,7 +23,7 @@ - type: entity parent: ClothingHeadBase id: ClothingHeadHatBeretEngineering - name: engineering beret + name: engineering beret description: A beret with the engineering insignia emblazoned on it. For engineers that are more inclined towards style than safety. components: - type: Sprite @@ -360,3 +360,14 @@ sprite: Clothing/Head/Hats/xmascrown.rsi - type: Clothing sprite: Clothing/Head/Hats/xmascrown.rsi + +- type: entity + parent: ClothingHeadBase + id: ClothingHeadHatTrucker + name: truckers hat + description: Formerly Chucks, this hat is yours now. + components: + - type: Sprite + sprite: Clothing/Head/Hats/truckershat.rsi + - type: Clothing + sprite: Clothing/Head/Hats/truckershat.rsi diff --git a/Resources/Prototypes/Entities/Clothing/Uniforms/jumpsuits.yml b/Resources/Prototypes/Entities/Clothing/Uniforms/jumpsuits.yml index 77f7eb264e..f71364f361 100644 --- a/Resources/Prototypes/Entities/Clothing/Uniforms/jumpsuits.yml +++ b/Resources/Prototypes/Entities/Clothing/Uniforms/jumpsuits.yml @@ -571,3 +571,14 @@ sprite: Clothing/Uniforms/Jumpsuit/rainbow.rsi - type: Clothing sprite: Clothing/Uniforms/Jumpsuit/rainbow.rsi + +- type: entity + parent: ClothingUniformBase + id: ClothingUniformOveralls + name: overalls + description: Great for working outdoors. + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpsuit/overalls.rsi + - type: Clothing + sprite: Clothing/Uniforms/Jumpsuit/overalls.rsi diff --git a/Resources/Textures/Clothing/Head/Hats/truckershat.rsi/equipped-HELMET.png b/Resources/Textures/Clothing/Head/Hats/truckershat.rsi/equipped-HELMET.png new file mode 100644 index 0000000000000000000000000000000000000000..8e0362d8dd654e1dc88129baa39f92b9fe77217b GIT binary patch literal 448 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7T#lEU~KnvaSW-L^Y+$8ze52cY!4P| zdL~Qo2(pW(#~*Axcy{@On`ak(mpt|08IOMBZ07Fu2Od3oq|jzzV6xHktftAvErvVU zZx#3a-)^#()Asp4k!jT&|8_Fpf2)5yxofe;?YQ#A6Tfd>Qn&iPG~eENukLPn{e5${ z!IIgl{TH2{@WOK8$;*akC$~Jhz2&389p?$WyY8O)m9zDf^9Npq2_^?;pWRzC^?mfA z*-!NwUyFrn1~SB4@$KGy?wqg3tNi$-UtRw*uK8Z2uC+t|^ylBV)PMeC;R`KKWMDG5 z(5%Ihuz)?3o8M3PRO&2qF3u{~UhoA#U)YH|^Wt~$(697yK!5aVo literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/Head/Hats/truckershat.rsi/icon.png b/Resources/Textures/Clothing/Head/Hats/truckershat.rsi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..95a2ca01f5f0bf824cfa1c35b8f07c80c6b578cc GIT binary patch literal 306 zcmV-20nPr2P)W?3I9l=Bk%OCvuMXa%;g#gH41vWOaEu+{0q*(au_iqM4W@hR- zpuzt;1A`qj8V*5Hcf&?btdM!_f;1*2eu0|o{L0M^837AXz!VgLXD07*qoM6N<$ Ef)=-Ung9R* literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/Head/Hats/truckershat.rsi/inhand-left.png b/Resources/Textures/Clothing/Head/Hats/truckershat.rsi/inhand-left.png new file mode 100644 index 0000000000000000000000000000000000000000..7e2ef801dce07b30007fd83f9dd11364fedb6e8f GIT binary patch literal 417 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7T#lEV9fAzaSW-L^Y)gZAG4tV>jn8E zj4LuM|~m zWEpJPCeDZpc=@B6VRqBUr9R9OE|1<;?33?*{qY2+qrROi{}$eZIlLDheP_7GP~&U& zeb!IYdmTs4+uz$S`MhrCnPRi-1AOh)dyl?X@;+nx{=Cx0w0o9m{RgaCu8O@CTO%gU zFR`9cH~e+jnR?c$!wh@4KOBP-+kP-W-J4N)Zr{Yf-Txm%yx*|8zGchdHM-v?ZTmeZ zkLkj@;$8I$+u#11pT`t%tLge)=Bj1 z{1$NKLkw6#Mnzum4LrI3|7g zasIEy5{~DZ5B%jA-Z0j06sz2)oVzLNKDS)2{? zzt<0f*VVB`9cEa=z2O*~xK_j90s&X%9d+|~`-x%;;8;uw||?! zCT1Yxz{<@wXJl&*Z(^#L9ofOaAb(W#@7j~5PTq0_wcEcx&d$x|-o$icRoU!}b=S=b v^tBll++#dGE5FBp;Wg75LpWh7f53clW9~=KrUqwV$TN7l`njxgN@xNAq;a(e literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/Head/Hats/truckershat.rsi/meta.json b/Resources/Textures/Clothing/Head/Hats/truckershat.rsi/meta.json new file mode 100644 index 0000000000..48deea96a8 --- /dev/null +++ b/Resources/Textures/Clothing/Head/Hats/truckershat.rsi/meta.json @@ -0,0 +1,26 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from baystation12 and modified by Swept at commit https://github.com/Baystation12/Baystation12/commit/80b2ded014aa871e6bceb6913b2961a23109aa23", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-HELMET", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/overalls.rsi/equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/overalls.rsi/equipped-INNERCLOTHING.png new file mode 100644 index 0000000000000000000000000000000000000000..42ea220dbadf69966b381a1f6f80bb6b9698de34 GIT binary patch literal 1307 zcmV+$1?2jPP)ML*f0|lmBdVfh!K=X%>Lx!dflK0^Zyr`}dfQzPPiA3d z*-K=>cvy)S1wHs5F&g|wBok-F8J%%wGh@rEo>$iN^z?L9x_id8K4^OCb$5O5eO*;u z%zL1rp`oFnp`oGinIhsA_IIcDRkUxNpEl<@4ncSJI9&hz1b=pD�MdKLpD&$Kl55 z6Rv$O1YhqS<^1PPPI8_t2Zt;8_l^C`S$^F==E@lJ>JJ9L_M1MKn`wnL8~OnDjGA-} z+o_pW!0WJk)C@ud44(Mcf7{Q3rfb+v-T254o7Q>e_ap!XKXc_V7(D~b#zYWc7z6Bj z5F$|UANj{!`O)7mJ&Mp$?*D1&s!1-sy0WRG=?0Da$=^bny4i5Lr#wo>5Hb3}XM*ymbo!z-`kF^W4)9jpkIazgx z*YSRQZlR+9pLgQN7l48w@Juhn&hKRu)SUv@nP~#p=TxF=m>(i=?&JOB^g==Ww5AmD zH(8IFp3^LFJd1W&e%C3G&%CR${hPbyc&})8dQJsS{g`Hn1#85=;x=rfU4`FOfQ>tb z99Pn4dCEsM=o zdu*$|0A&IGFVF(WJ|J#Dyx9GQ-~3XGVDW7liVrTD-B{oz}jbl#UUMmnL zs4fA0K&bd3Ex?da7XVSKR$$F9$C&NU?|%$Mh@E9VDAy&3zR8LpH3cYm7W9TbmXjCJ zbKoyuQsQo~bD$6857=Lj2S~30L`3Onu})zAmC}ro0&<9)qA&KM2`^Cgm}Os+_F949 z^8x_X^$F;UO7IA20Z{EiuE5)3k~A!coML+3sc6D?0Z?r$N)o}H=mJPXHc%PbSMTD# zMIkVFm6`;leE<>Fsuc(m|<7Z)Z@ekZ4)(Tk3$zW4smA0<<_4wD;C%LlqrG)mG4Qsl(^pOm+3 zc#lk&iNRH@9nEdz*R0f3Zv-INZ1Mc7Sj0&7nqpl-DRG&db5lAQ5Sr7SuF_^aqi5lAv_Cb2T40Uhciez3;ud3p_kL{yh>;1D5v}nU#n9_KUiW zm~x1nrCm3Ed~<}lwTJBZ3EHg=Tl4DRmaDe|yeA+lemj**0zMfZuLcT*f;(tV{`Ax+ z%M&?OZUIDQcnpe?LnIT%!saaVk)=8L$0Avg<@g2At`fO8YG#$qquGcnf-KF6nyG>E zM7rP?QF~olhY|o(^%U>{1LJ@Jz)-*A<%=TbSIl}MGt_B~=7C3`S2iC*iyPk=8FMO%A9{k$j{(hT3i zqDSCB#eVLZ=91+5%ToLT(p%5A1+d*yK^lZpxNA1tJl@U{-2VZ(U>W_AeUk_W+wQr= zDK2^ug?>c(^bYKv#YWfAm=Lm#kK&>T-+@qajajSp;QEw6zR$;t@GBSzH<_;)oI~gH t4f1dbA@h#;8b(O?6?k}fc>Hq!d;tn~syP%t9D)D<002ovPDHLkV1g5`=Y#+N literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/overalls.rsi/inhand-left.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/overalls.rsi/inhand-left.png new file mode 100644 index 0000000000000000000000000000000000000000..ef474f95ad43040a2bccaa97c076fd9b0328d360 GIT binary patch literal 339 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0J3?w7mbKU|e{s5m4S0K$`6vg1y!(dy_U{T!4 zBC!`J!dMdI7tG-B>_!@plT#HEQR0$XoLrPyP?DLS$G}iACpfI2sPy}n;DV1|pJ;jO zYMncCK6pc@!A0W-k95xaXr5#!>gnBKVH{+9+1N{A&Z9|3CWR;jYs#-sG*n+6(b;XR zx_O1flKyVvwHk)zqDRc01ceoREoPj}!!U2NbXorMwPrwjt2|vCLp(a)PC3ZipupqW zE&QRCkwx7hreVi_zhezwFU{MNaj86O*7Rk|-BTpuj|%>@`TLbj( hYz?S?5cQ`~xbP7BWN)=^XMnC?@O1TaS?83{1ON_ffMoyx literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/overalls.rsi/inhand-right.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/overalls.rsi/inhand-right.png new file mode 100644 index 0000000000000000000000000000000000000000..79468f3b600d602e6dd8e1b50f2af05ad1de4ab7 GIT binary patch literal 416 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=g;gODB`&GO$wiq3C7Jno3=9=> zRF7M8H5dr69+*C6wsSR4C;C*`GF_wdYZt%Fo9an$Ac~Zn`-6<|KR9gBdH1?1;E=^CJ7a&C+G^ zjNu^Hyz+E$45^s&_SRnBW&?q?hu$I=FDy%B5LR-j!TLehZWl-Do1QhQ`o`}N zsC&Ke#WLXMGd`?o=stSJ;lMv< z=kA$}vX`bc0C_IA7$SgbmiXS@Z(qOv&LwkShJZJ$T!#}^R{XcO0U6}!>gTe~DWM4f D#uBir literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/overalls.rsi/meta.json b/Resources/Textures/Clothing/Uniforms/Jumpsuit/overalls.rsi/meta.json new file mode 100644 index 0000000000..c35ec06fb5 --- /dev/null +++ b/Resources/Textures/Clothing/Uniforms/Jumpsuit/overalls.rsi/meta.json @@ -0,0 +1,26 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation and modified by Swept at commit https://github.com/tgstation/tgstation/commit/ac792e3226d48e5b3aaccff165ce100f7636a040", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-INNERCLOTHING", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} From 21fca6c1fb473ccbc46187f4bf35cd0b4caf5e22 Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 5 Aug 2021 06:19:07 -0400 Subject: [PATCH 74/76] Automatic changelog update --- Resources/Changelog/Changelog.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index b6509f5e2f..0eccdb74b9 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1663,3 +1663,8 @@ Entries: - {message: Fixes mobs not taking pressure damage on space., type: Fix} id: 296 time: '2021-08-04T07:49:53.509340+00:00' +- author: SweptWasTaken + changes: + - {message: Added an alternate jumpsuit for Botany, type: Add} + id: 297 + time: '2021-08-05T10:18:05.0000000+00:00' From 96354923abb9d03cece37943dc4dbedf17f519d9 Mon Sep 17 00:00:00 2001 From: SethLafuente <84478872+SethLafuente@users.noreply.github.com> Date: Thu, 5 Aug 2021 15:20:58 -0700 Subject: [PATCH 75/76] Added two new small areas to maintenence (#4359) Co-authored-by: SETh lafuente --- Resources/Maps/saltern.yml | 1166 ++++++++++++++++- .../Prototypes/Catalog/Fills/Lockers/misc.yml | 34 + .../Structures/Storage/Closets/closets.yml | 13 + 3 files changed, 1181 insertions(+), 32 deletions(-) diff --git a/Resources/Maps/saltern.yml b/Resources/Maps/saltern.yml index 8ca177e8e3..3e767d8f62 100644 --- a/Resources/Maps/saltern.yml +++ b/Resources/Maps/saltern.yml @@ -56,17 +56,17 @@ grids: - ind: "0,1" tiles: IQAAABYAAAAWAAAAFgAAABYAAAAWAAAAIQAAABYAAAAWAAAAFgAAABYAAAAhAAAAIAAAABgAAAAhAAAAGAAAABgAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAIQAAACAAAAAYAAAAIQAAAAAAAAAhAAAAFgAAABYAAAAWAAAAFgAAABYAAAAhAAAAFgAAABYAAAAWAAAAFgAAACEAAAAgAAAAGAAAACEAAAAAAAAAIQAAABYAAAAWAAAAFgAAABYAAAAWAAAAIQAAABYAAAAWAAAAFgAAABYAAAAhAAAAIAAAABgAAAAhAAAAAAAAABgAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAGAAAACAAAAAYAAAAIQAAAAAAAAAhAAAAFgAAABYAAAAWAAAAFgAAABYAAAAhAAAAFgAAABYAAAAWAAAAFgAAACEAAAAYAAAAGAAAACEAAAAAAAAAIQAAACEAAAAWAAAAIQAAABYAAAAhAAAAIQAAACEAAAAhAAAAIQAAACEAAAAhAAAAGAAAACEAAAAhAAAAAAAAAA8AAAAhAAAAFgAAABYAAAAWAAAAIQAAAB4AAAAeAAAAHgAAAB4AAAAhAAAAGAAAABgAAAAYAAAAIQAAAAAAAAAPAAAADwAAABYAAAAWAAAAFgAAACEAAAAeAAAAHgAAAB4AAAAeAAAAIQAAACAAAAAgAAAAIAAAACEAAAAAAAAADwAAAA8AAAAWAAAAFgAAABYAAAAWAAAAHgAAAB4AAAAeAAAAHgAAACEAAAAhAAAAIQAAACEAAAAhAAAAAAAAAA8AAAAhAAAAFgAAABYAAAAWAAAAIQAAAB4AAAAeAAAAHgAAAB4AAAAhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhAAAAIQAAABYAAAAWAAAAFgAAACEAAAAhAAAAIQAAACEAAAAhAAAAIQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAACEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAIQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAIQAAACEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== - ind: "-2,0" - tiles: IQAAAB4AAAAeAAAAHgAAAB4AAAAeAAAAFgAAABYAAAAWAAAAFgAAACEAAAAOAAAAFgAAABYAAAAWAAAAFgAAACEAAAAeAAAAHgAAAB4AAAAeAAAAIQAAACEAAAAWAAAAFgAAACEAAAAhAAAADgAAAA4AAAAOAAAADgAAABYAAAAhAAAAIQAAACEAAAAhAAAAIQAAACEAAAAWAAAAFgAAABYAAAAWAAAAIQAAACEAAAAhAAAAIQAAACEAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAIQAAACEAAAAhAAAAIQAAACEAAAAhAAAAFgAAABYAAAAWAAAAFgAAACEAAAAhAAAAIQAAACEAAAAhAAAAGAAAACAAAAAhAAAAFgAAABYAAAAWAAAAIQAAACEAAAAWAAAAFgAAACEAAAAhAAAAFgAAABYAAAAhAAAAIAAAACAAAAAYAAAAIQAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAhAAAAFgAAABYAAAAWAAAAIQAAACAAAAAYAAAAGAAAACEAAAAWAAAAFgAAABYAAAAWAAAAIQAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAACEAAAAgAAAAGAAAABgAAAAhAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAACEAAAAWAAAAFgAAABYAAAAhAAAAIAAAABgAAAAYAAAAIQAAABYAAAAWAAAAFgAAABYAAAAhAAAAFgAAABYAAAAhAAAAIQAAABgAAAAhAAAAIQAAACAAAAAgAAAAGAAAACEAAAAhAAAAIQAAACEAAAAhAAAAIQAAACEAAAAhAAAAIQAAABgAAAAgAAAAGAAAABgAAAAgAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAIAAAACAAAAAgAAAAIAAAABgAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAhAAAAIQAAACAAAAAYAAAAIQAAACEAAAAhAAAAIQAAACEAAAAhAAAAIQAAACEAAAAhAAAAIQAAACEAAAAhAAAAIQAAACAAAAAgAAAAGAAAAA== + tiles: IQAAAB4AAAAeAAAAHgAAAB4AAAAeAAAAFgAAABYAAAAWAAAAFgAAACEAAAAOAAAAFgAAABYAAAAWAAAAFgAAACEAAAAeAAAAHgAAAB4AAAAeAAAAIQAAACEAAAAWAAAAFgAAACEAAAAhAAAADgAAAA4AAAAOAAAADgAAABYAAAAhAAAAIQAAACEAAAAhAAAAIQAAACEAAAAWAAAAFgAAABYAAAAWAAAAIQAAACEAAAAhAAAAIQAAACEAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAIQAAACEAAAAhAAAAIQAAACEAAAAhAAAAFgAAABYAAAAWAAAAFgAAACEAAAAhAAAAIQAAACEAAAAhAAAAGAAAACAAAAAhAAAAFgAAABYAAAAWAAAAIQAAACEAAAAWAAAAFgAAACEAAAAhAAAAFgAAABYAAAAhAAAAIAAAACAAAAAYAAAAIQAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAhAAAAFgAAABYAAAAWAAAAIQAAACAAAAAYAAAAGAAAACEAAAAWAAAAFgAAABYAAAAWAAAAIQAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAACEAAAAgAAAAGAAAABgAAAAhAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAACEAAAAWAAAAFgAAABYAAAAhAAAAIAAAABgAAAAYAAAAIQAAABYAAAAWAAAAFgAAABYAAAAhAAAAFgAAABYAAAAhAAAAIQAAABgAAAAhAAAAIQAAACAAAAAgAAAAGAAAACEAAAAhAAAAIQAAACEAAAAhAAAAIQAAACEAAAAhAAAAIQAAABgAAAAgAAAAGAAAABgAAAAgAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAIAAAACAAAAAgAAAAIAAAABgAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAhAAAAIQAAACAAAAAYAAAAIQAAACEAAAAhAAAAIQAAACEAAAAhAAAAIAAAACEAAAAhAAAAIQAAACEAAAAhAAAAIQAAACAAAAAgAAAAGAAAAA== - ind: "1,-1" tiles: GwAAABsAAAAbAAAAGwAAACEAAAAbAAAAGwAAABsAAAAbAAAAIQAAACAAAAAYAAAAIQAAAAAAAAAAAAAAAAAAABsAAAAbAAAAGwAAABsAAAAhAAAAGwAAABsAAAAbAAAAGwAAACEAAAAgAAAAGAAAACEAAAAAAAAAAAAAAAAAAAAbAAAAGwAAABsAAAAbAAAAGwAAABsAAAAbAAAAGwAAABsAAAAhAAAAIAAAABgAAAAhAAAAAAAAAAAAAAAAAAAAIQAAACEAAAAbAAAAGwAAACEAAAAbAAAAGwAAABsAAAAbAAAAIQAAACAAAAAYAAAAIQAAAAAAAAAAAAAAAAAAABsAAAAbAAAAGwAAABsAAAAhAAAAIQAAACEAAAAhAAAAIQAAACEAAAAgAAAAGAAAACEAAAAAAAAAAAAAAAAAAAAbAAAAGwAAABsAAAAbAAAAGAAAACAAAAAYAAAAGAAAABgAAAAYAAAAIAAAABgAAAAhAAAAAAAAAAAAAAAAAAAAGwAAABsAAAAbAAAAGwAAACEAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAYAAAAIQAAAAAAAAAAAAAAAAAAABsAAAAbAAAAGwAAABsAAAAhAAAAIQAAACEAAAAhAAAAIQAAACEAAAAgAAAAGAAAACEAAAAhAAAAIQAAACEAAAAbAAAAGwAAABsAAAAbAAAAIQAAABsAAAAbAAAAGwAAABsAAAAhAAAAIAAAABgAAAAhAAAAIAAAACAAAAAgAAAAGwAAABsAAAAbAAAAGwAAABsAAAAbAAAAGwAAABsAAAAbAAAAIQAAACAAAAAgAAAAIAAAACAAAAAYAAAAGAAAABsAAAAbAAAAGwAAABsAAAAhAAAAGwAAABsAAAAbAAAAGwAAACEAAAAgAAAAGAAAACEAAAAhAAAAIQAAACEAAAAbAAAAGwAAABsAAAAbAAAAIQAAABsAAAAbAAAAGwAAABsAAAAhAAAAIAAAABgAAAAhAAAAFgAAABYAAAAWAAAAIQAAABsAAAAhAAAAIQAAACEAAAAhAAAAIQAAACEAAAAhAAAAIQAAACAAAAAYAAAAIQAAABYAAAAWAAAAFgAAABsAAAAbAAAAGwAAACEAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAGAAAACEAAAAWAAAAFgAAABYAAAAbAAAAGwAAABsAAAAhAAAAIAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAhAAAAFgAAABYAAAAWAAAAGwAAABsAAAAbAAAAIQAAACAAAAAYAAAAGAAAACAAAAAhAAAAIQAAACEAAAAhAAAAIQAAACEAAAAhAAAAFgAAAA== - ind: "0,-2" tiles: AAAAACEAAAAhAAAAIQAAACEAAAAhAAAAIQAAACEAAAAhAAAAIQAAACEAAAAhAAAAIQAAACEAAAAAAAAAAAAAAAAAAAAhAAAAFgAAABYAAAAWAAAAIQAAAB4AAAAeAAAAHgAAAB4AAAAeAAAAHgAAAB4AAAAhAAAAAAAAAAAAAAAAAAAAIQAAABYAAAAWAAAAFgAAACEAAAAeAAAAHgAAAB4AAAAeAAAAIQAAAB4AAAAeAAAAIQAAAAAAAAAAAAAAIQAAACEAAAAWAAAAFgAAABYAAAAhAAAAHgAAAB4AAAAeAAAAHgAAACEAAAAhAAAAIQAAACEAAAAAAAAAAAAAABgAAAAhAAAAFgAAABYAAAAWAAAAIQAAAB4AAAAeAAAAHgAAAB4AAAAeAAAAHgAAAB4AAAAhAAAAAAAAAAAAAAAYAAAAIQAAABYAAAAWAAAAFgAAACEAAAAeAAAAHgAAAB4AAAAeAAAAHgAAAB4AAAAeAAAAIQAAACEAAAAhAAAAGAAAACEAAAAWAAAAFgAAABYAAAAhAAAAHgAAAB4AAAAeAAAAHgAAAB4AAAAeAAAAHgAAACEAAAAgAAAAIAAAABgAAAAhAAAAFgAAABYAAAAWAAAAHgAAAB4AAAAeAAAAHgAAAB4AAAAeAAAAHgAAAB4AAAAhAAAAIAAAABgAAAAgAAAAGAAAABYAAAAWAAAAFgAAAB4AAAAeAAAAHgAAAB4AAAAeAAAAHgAAAB4AAAAeAAAAIQAAACAAAAAYAAAAIQAAACEAAAAWAAAAFgAAABYAAAAhAAAAIQAAAB4AAAAeAAAAHgAAAB4AAAAeAAAAHgAAACEAAAAgAAAAGAAAACEAAAAWAAAAFgAAABYAAAAWAAAAFgAAACEAAAAhAAAAGAAAACEAAAAhAAAAIQAAACEAAAAhAAAAIAAAABgAAAAbAAAAFgAAABYAAAAWAAAAFgAAABYAAAAhAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAYAAAAGwAAABYAAAAWAAAAFgAAABYAAAAWAAAAGAAAACAAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAgAAAAGAAAACEAAAAWAAAAFgAAABYAAAAWAAAAFgAAACEAAAAYAAAAGAAAABgAAAAYAAAAIQAAACEAAAAhAAAAGAAAACEAAAAhAAAAFgAAABYAAAAWAAAAFgAAABYAAAAhAAAAIQAAACEAAAAhAAAAIQAAACEAAAAbAAAAGwAAABsAAAAbAAAAIQAAABYAAAAWAAAAFgAAABYAAAAWAAAAIQAAABsAAAAbAAAAGwAAABsAAAAhAAAAGwAAABsAAAAbAAAAGwAAAA== - ind: "1,-2" - tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhAAAAIQAAACEAAAAhAAAAIQAAACEAAAAhAAAAIQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAACAAAAAhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhAAAAIQAAACEAAAAhAAAAIQAAABgAAAAgAAAAIQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIQAAABsAAAAbAAAAGwAAACEAAAAYAAAAIAAAACEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACEAAAAbAAAAGwAAABsAAAAhAAAAGAAAACAAAAAhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAGwAAABsAAAAbAAAAIQAAABgAAAAgAAAAIQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAABsAAAAbAAAAGwAAACEAAAAYAAAAIAAAACEAAAAhAAAAIQAAACEAAAAhAAAAIQAAAAAAAAAAAAAAAAAAACEAAAAhAAAAIQAAABsAAAAhAAAAGAAAACAAAAAYAAAAGAAAABgAAAAYAAAAGAAAACEAAAAAAAAAAAAAAAAAAAAbAAAAGwAAABsAAAAbAAAAIQAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAABgAAAAhAAAAAAAAAAAAAAAAAAAAGwAAABsAAAAbAAAAGwAAACEAAAAhAAAAIQAAACEAAAAhAAAAIQAAACAAAAAYAAAAIQAAAAAAAAAAAAAAAAAAAA== + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhAAAAIQAAACEAAAAhAAAAIQAAACEAAAAhAAAAIQAAABgAAAAYAAAAGAAAABgAAAAYAAAAAAAAAAAAAAAAAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACEAAAAYAAAAGAAAABgAAAAYAAAAGAAAAAAAAAAAAAAAAAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAACAAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAAAAAAAAAAAAAAAAAhAAAAIQAAACEAAAAhAAAAIQAAABgAAAAgAAAAIQAAABgAAAAYAAAAGAAAABgAAAAYAAAAAAAAAAAAAAAAAAAAIQAAABsAAAAbAAAAGwAAACEAAAAYAAAAIAAAACEAAAAYAAAAGAAAABgAAAAYAAAAGAAAAAAAAAAAAAAAAAAAACEAAAAbAAAAGwAAABsAAAAhAAAAGAAAACAAAAAhAAAAGAAAABgAAAAYAAAAGAAAABgAAAAAAAAAAAAAAAAAAAAgAAAAGwAAABsAAAAbAAAAIQAAABgAAAAgAAAAIQAAABgAAAAYAAAAGAAAABgAAAAYAAAAAAAAAAAAAAAAAAAAGAAAABsAAAAbAAAAGwAAACEAAAAYAAAAIAAAACEAAAAhAAAAIQAAABgAAAAhAAAAIQAAAAAAAAAAAAAAAAAAACEAAAAhAAAAIQAAABsAAAAhAAAAGAAAACAAAAAYAAAAGAAAABgAAAAYAAAAGAAAACEAAAAAAAAAAAAAAAAAAAAbAAAAGwAAABsAAAAbAAAAIQAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAABgAAAAhAAAAAAAAAAAAAAAAAAAAGwAAABsAAAAbAAAAGwAAACEAAAAhAAAAIQAAACEAAAAhAAAAIQAAACAAAAAYAAAAIQAAAAAAAAAAAAAAAAAAAA== - ind: "-1,1" tiles: IQAAABYAAAAWAAAAFgAAABYAAAAhAAAAFgAAABYAAAAWAAAAFgAAABYAAAAhAAAAGAAAABgAAAAYAAAAGAAAACEAAAAhAAAAIQAAABYAAAAhAAAAIQAAABYAAAAWAAAAFgAAABYAAAAWAAAAIQAAABgAAAAYAAAAGAAAABgAAAAhAAAAEQAAABEAAAARAAAAEQAAACEAAAAhAAAAFgAAACEAAAAhAAAAIQAAACEAAAAYAAAAGAAAABgAAAAYAAAAIQAAABEAAAARAAAAEQAAABEAAAAhAAAACAAAAAgAAAAIAAAACAAAACEAAAAhAAAAIQAAACEAAAAhAAAAIQAAACEAAAARAAAAEQAAABEAAAARAAAAFgAAAAgAAAAeAAAAHgAAAB4AAAAhAAAAGAAAABgAAAAYAAAAGAAAACAAAAAhAAAAEQAAABEAAAARAAAAEQAAACEAAAAIAAAAHgAAAB4AAAAeAAAAIQAAACAAAAAgAAAAIAAAACAAAAAgAAAAIQAAACEAAAAhAAAAIQAAACEAAAAhAAAACAAAAB4AAAAeAAAAHgAAACEAAAAgAAAAIQAAACEAAAAPAAAAIQAAACEAAAAhAAAAIQAAACEAAAAhAAAAIQAAABgAAAAYAAAAGAAAABgAAAAYAAAAIAAAACEAAAAPAAAADwAAAA8AAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAACAAAAAgAAAAIAAAACAAAAAhAAAADwAAAA8AAAAPAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIQAAACEAAAAhAAAAIQAAAA8AAAAPAAAADwAAACEAAAAhAAAAIQAAACEAAAAhAAAAIQAAACEAAAAhAAAAIQAAACEAAAAAAAAAAAAAACEAAAAPAAAADwAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhAAAAIQAAACEAAAAhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIQAAABYAAAAWAAAAFgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACEAAAAWAAAAFgAAABYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhAAAAFgAAABYAAAAWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIQAAACEAAAAWAAAAFgAAAA== - ind: "-2,1" - tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIQAAACAAAAAYAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACEAAAAgAAAAGAAAACEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhAAAAIAAAABgAAAAhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIQAAACAAAAAYAAAAIQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACEAAAAgAAAAGAAAACEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhAAAAIAAAABgAAAAhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIQAAACAAAAAYAAAAIQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACEAAAAgAAAAGAAAACEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhAAAAIAAAABgAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIQAAACAAAAAgAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACEAAAAhAAAAIQAAACEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== + tiles: AAAAAAAAAAAAAAAAAAAAACEAAAAYAAAAIAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAACAAAAAYAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAhAAAAGAAAACAAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAgAAAAGAAAACEAAAAAAAAAAAAAAAAAAAAAAAAAIQAAABgAAAAgAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAIAAAABgAAAAhAAAAAAAAAAAAAAAAAAAAAAAAACEAAAAYAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAYAAAAIQAAAAAAAAAAAAAAAAAAAAAAAAAhAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAACEAAAAgAAAAGAAAACEAAAAAAAAAAAAAAAAAAAAAAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAhAAAAIAAAABgAAAAhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIQAAACAAAAAYAAAAIQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACEAAAAgAAAAGAAAACEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhAAAAIAAAABgAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIQAAACAAAAAgAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACEAAAAhAAAAIQAAACEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== - ind: "-1,2" tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACEAAAAhAAAAFgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIQAAACEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== - ind: "1,1" @@ -7968,10 +7968,9 @@ entities: stash: !type:ContainerSlot {} type: ContainerContainer - uid: 823 - type: ClosetBase + type: ClosetMaintenanceFilledRandom components: - - rot: 4.371139006309477E-08 rad - pos: -11.5,-12.5 + - pos: -11.5,-12.5 parent: 853 type: Transform - IsPlaceable: False @@ -12315,6 +12314,89 @@ entities: 45,-33: 0 46,-34: 0 47,-34: 0 + 24,-27: 0 + 24,-26: 0 + 24,-25: 0 + 24,-24: 0 + 24,-23: 0 + 24,-22: 0 + 24,-21: 0 + 25,-27: 0 + 25,-26: 0 + 25,-25: 0 + 25,-24: 0 + 25,-23: 0 + 25,-22: 0 + 25,-21: 0 + 26,-27: 0 + 26,-26: 0 + 26,-25: 0 + 26,-24: 0 + 26,-23: 0 + 26,-22: 0 + 26,-21: 0 + 27,-27: 0 + 27,-26: 0 + 27,-25: 0 + 27,-24: 0 + 27,-23: 0 + 27,-22: 0 + 27,-21: 0 + 28,-27: 0 + 28,-26: 0 + 28,-25: 0 + 28,-24: 0 + 28,-23: 0 + 28,-22: 0 + 28,-21: 0 + -28,16: 0 + -28,17: 0 + -28,18: 0 + -28,19: 0 + -28,20: 0 + -28,21: 0 + -27,16: 0 + -27,17: 0 + -27,18: 0 + -27,19: 0 + -27,20: 0 + -27,21: 0 + -26,16: 0 + -26,17: 0 + -26,18: 0 + -26,19: 0 + -26,20: 0 + -26,21: 0 + -25,16: 0 + -25,17: 0 + -25,18: 0 + -25,19: 0 + -25,20: 0 + -25,21: 0 + -24,16: 0 + -24,17: 0 + -24,18: 0 + -24,19: 0 + -24,20: 0 + -24,21: 0 + -23,16: 0 + -23,17: 0 + -23,18: 0 + -23,19: 0 + -23,20: 0 + -23,21: 0 + -22,16: 0 + -22,17: 0 + -22,18: 0 + -22,19: 0 + -22,20: 0 + -22,21: 0 + -21,16: 0 + -21,17: 0 + -21,18: 0 + -21,19: 0 + -21,20: 0 + -21,21: 0 uniqueMixes: - volume: 2500 temperature: 293.15 @@ -12440,16 +12522,6 @@ entities: mask: [] layer: [] mass: 256 - - shape: !type:PolygonShape - vertices: - - -16,16 - - -16,27 - - -20,27 - - -20,16 - id: grid_chunk--2-1 - mask: [] - layer: [] - mass: 44 - shape: !type:PolygonShape vertices: - 0,32 @@ -12610,6 +12682,16 @@ entities: mask: [] layer: [] mass: 16 + - shape: !type:PolygonShape + vertices: + - -16,16 + - -16,27 + - -28,27 + - -28,16 + id: grid_chunk--2-1 + mask: [] + layer: [] + mass: 132 bodyType: Dynamic type: Physics - uid: 854 @@ -15839,8 +15921,7 @@ entities: - uid: 1248 type: WallSolid components: - - rot: 4.371139006309477E-08 rad - pos: -33.5,9.5 + - pos: -33.5,9.5 parent: 853 type: Transform - uid: 1249 @@ -19315,9 +19396,9 @@ entities: parent: 853 type: Transform - uid: 1657 - type: WallReinforced + type: RandomSpawner components: - - pos: 23.5,-24.5 + - pos: 26.5,-25.5 parent: 853 type: Transform - uid: 1658 @@ -19399,11 +19480,16 @@ entities: parent: 853 type: Transform - uid: 1671 - type: WallReinforced + type: CableApcExtension components: - - pos: 26.5,-19.5 + - anchored: True + pos: 28.5,-24.5 parent: 853 type: Transform + - visible: False + type: Sprite + - canCollide: False + type: Physics - uid: 1672 type: WallReinforced components: @@ -19526,11 +19612,15 @@ entities: parent: 853 type: Transform - uid: 1692 - type: WallReinforced + type: AirlockMaint components: - - pos: -25.5,15.5 + - pos: 23.5,-24.5 parent: 853 type: Transform + - containers: + board: !type:Container + ents: [] + type: ContainerContainer - uid: 1693 type: WallReinforced components: @@ -19634,11 +19724,15 @@ entities: parent: 853 type: Transform - uid: 1709 - type: WallReinforced + type: AirlockMaint components: - - pos: -19.5,19.5 + - pos: -25.5,15.5 parent: 853 type: Transform + - containers: + board: !type:Container + ents: [] + type: ContainerContainer - uid: 1710 type: WallReinforced components: @@ -23154,12 +23248,15 @@ entities: parent: 853 type: Transform - uid: 2210 - type: WallSolid + type: AirlockMaint components: - - rot: 4.371139006309477E-08 rad - pos: 25.5,-15.5 + - pos: -23.5,18.5 parent: 853 type: Transform + - containers: + board: !type:Container + ents: [] + type: ContainerContainer - uid: 2211 type: WallSolid components: @@ -23219,8 +23316,7 @@ entities: - uid: 2219 type: WallSolid components: - - rot: 4.371139006309477E-08 rad - pos: 20.5,-17.5 + - pos: 20.5,-17.5 parent: 853 type: Transform - uid: 2220 @@ -24589,7 +24685,7 @@ entities: parent: 853 type: Transform - uid: 2414 - type: ClosetBase + type: ClosetMaintenanceFilledRandom components: - pos: -8.5,-25.5 parent: 853 @@ -49218,4 +49314,1010 @@ entities: pos: 2.5,31.5 parent: 853 type: Transform +- uid: 4961 + type: WallReinforced + components: + - pos: 28.5,-22.5 + parent: 853 + type: Transform +- uid: 4962 + type: WallReinforced + components: + - pos: 24.5,-26.5 + parent: 853 + type: Transform +- uid: 4963 + type: WallReinforced + components: + - pos: 25.5,-26.5 + parent: 853 + type: Transform +- uid: 4964 + type: WallReinforced + components: + - pos: 26.5,-26.5 + parent: 853 + type: Transform +- uid: 4965 + type: WallReinforced + components: + - pos: 28.5,-20.5 + parent: 853 + type: Transform +- uid: 4966 + type: WallReinforced + components: + - pos: 28.5,-23.5 + parent: 853 + type: Transform +- uid: 4967 + type: WallReinforced + components: + - pos: 28.5,-21.5 + parent: 853 + type: Transform +- uid: 4968 + type: WallReinforced + components: + - pos: 28.5,-24.5 + parent: 853 + type: Transform +- uid: 4969 + type: WallReinforced + components: + - pos: 28.5,-25.5 + parent: 853 + type: Transform +- uid: 4970 + type: WallReinforced + components: + - pos: 28.5,-26.5 + parent: 853 + type: Transform +- uid: 4971 + type: WallReinforced + components: + - pos: 27.5,-26.5 + parent: 853 + type: Transform +- uid: 4972 + type: WallSolid + components: + - pos: 25.5,-24.5 + parent: 853 + type: Transform +- uid: 4973 + type: WallSolid + components: + - pos: 25.5,-25.5 + parent: 853 + type: Transform +- uid: 4974 + type: AirlockMaint + components: + - pos: 25.5,-23.5 + parent: 853 + type: Transform + - containers: + board: !type:Container + ents: [] + type: ContainerContainer +- uid: 4975 + type: WallSolid + components: + - pos: 25.5,-21.5 + parent: 853 + type: Transform +- uid: 4976 + type: WallSolid + components: + - pos: 27.5,-21.5 + parent: 853 + type: Transform +- uid: 4977 + type: WallSolid + components: + - pos: 26.5,-21.5 + parent: 853 + type: Transform +- uid: 4978 + type: WallSolid + components: + - pos: 25.5,-15.5 + parent: 853 + type: Transform +- uid: 4979 + type: AcousticGuitarInstrument + components: + - pos: 27.500875,-23.377102 + parent: 853 + type: Transform + - canCollide: False + type: Physics +- uid: 4980 + type: WallSolid + components: + - pos: 25.5,-22.5 + parent: 853 + type: Transform +- uid: 4981 + type: Table + components: + - pos: 27.5,-25.5 + parent: 853 + type: Transform +- uid: 4982 + type: Table + components: + - pos: 26.5,-25.5 + parent: 853 + type: Transform +- uid: 4983 + type: PoweredSmallLight + components: + - rot: -1.5707963267948966 rad + pos: 28,-24.5 + parent: 853 + type: Transform + - powerLoad: 0 + type: ApcPowerReceiver + - containers: + light_bulb: !type:ContainerSlot {} + type: ContainerContainer +- uid: 4984 + type: PoweredSmallLight + components: + - pos: 25.5,-20 + parent: 853 + type: Transform + - powerLoad: 0 + type: ApcPowerReceiver + - containers: + light_bulb: !type:ContainerSlot {} + type: ContainerContainer +- uid: 4985 + type: PoweredSmallLight + components: + - rot: 1.5707963267948966 rad + pos: 24,-23.5 + parent: 853 + type: Transform + - powerLoad: 0 + type: ApcPowerReceiver + - containers: + light_bulb: !type:ContainerSlot {} + type: ContainerContainer +- uid: 4986 + type: ComfyChair + components: + - pos: 26.5,-24.5 + parent: 853 + type: Transform +- uid: 4987 + type: CableApcExtension + components: + - anchored: True + pos: 26.5,-19.5 + parent: 853 + type: Transform + - visible: False + type: Sprite + - canCollide: False + type: Physics +- uid: 4988 + type: CableApcExtension + components: + - anchored: True + pos: 26.5,-20.5 + parent: 853 + type: Transform + - visible: False + type: Sprite + - canCollide: False + type: Physics +- uid: 4989 + type: CableApcExtension + components: + - anchored: True + pos: 25.5,-19.5 + parent: 853 + type: Transform +- uid: 4990 + type: CableApcExtension + components: + - anchored: True + pos: 23.5,-24.5 + parent: 853 + type: Transform + - visible: False + type: Sprite + - canCollide: False + type: Physics +- uid: 4991 + type: CableApcExtension + components: + - anchored: True + pos: 23.5,-23.5 + parent: 853 + type: Transform +- uid: 4992 + type: CableApcExtension + components: + - anchored: True + pos: 23.5,-22.5 + parent: 853 + type: Transform +- uid: 4993 + type: CableApcExtension + components: + - anchored: True + pos: 23.5,-21.5 + parent: 853 + type: Transform +- uid: 4994 + type: CableApcExtension + components: + - anchored: True + pos: 23.5,-20.5 + parent: 853 + type: Transform +- uid: 4995 + type: CableApcExtension + components: + - anchored: True + pos: 23.5,-19.5 + parent: 853 + type: Transform +- uid: 4996 + type: CableApcExtension + components: + - anchored: True + pos: 24.5,-24.5 + parent: 853 + type: Transform + - visible: False + type: Sprite + - canCollide: False + type: Physics +- uid: 4997 + type: CableApcExtension + components: + - anchored: True + pos: 25.5,-24.5 + parent: 853 + type: Transform + - visible: False + type: Sprite + - canCollide: False + type: Physics +- uid: 4998 + type: CableApcExtension + components: + - anchored: True + pos: 26.5,-24.5 + parent: 853 + type: Transform + - visible: False + type: Sprite + - canCollide: False + type: Physics +- uid: 4999 + type: CableApcExtension + components: + - anchored: True + pos: 27.5,-24.5 + parent: 853 + type: Transform + - visible: False + type: Sprite + - canCollide: False + type: Physics +- uid: 5000 + type: AirlockMaint + components: + - pos: 26.5,-19.5 + parent: 853 + type: Transform + - containers: + board: !type:Container + ents: [] + type: ContainerContainer +- uid: 5001 + type: AirlockMaint + components: + - pos: -19.5,19.5 + parent: 853 + type: Transform + - containers: + board: !type:Container + ents: [] + type: ContainerContainer +- uid: 5002 + type: WallReinforced + components: + - pos: -27.5,16.5 + parent: 853 + type: Transform +- uid: 5003 + type: WallReinforced + components: + - pos: -27.5,17.5 + parent: 853 + type: Transform +- uid: 5004 + type: WallReinforced + components: + - pos: -27.5,18.5 + parent: 853 + type: Transform +- uid: 5005 + type: WallReinforced + components: + - pos: -27.5,19.5 + parent: 853 + type: Transform +- uid: 5006 + type: WallReinforced + components: + - pos: -27.5,20.5 + parent: 853 + type: Transform +- uid: 5007 + type: CrateEngineeringCableMV + components: + - pos: -20.5,16.5 + parent: 853 + type: Transform + - IsPlaceable: False + type: PlaceableSurface + - containers: + EntityStorageComponent: !type:Container + ents: [] + type: ContainerContainer +- uid: 5008 + type: WallSolid + components: + - pos: -20.5,18.5 + parent: 853 + type: Transform +- uid: 5009 + type: WallSolid + components: + - pos: -22.5,18.5 + parent: 853 + type: Transform +- uid: 5010 + type: WallSolid + components: + - pos: -21.5,18.5 + parent: 853 + type: Transform +- uid: 5011 + type: WallSolid + components: + - pos: -24.5,18.5 + parent: 853 + type: Transform +- uid: 5012 + type: WallSolid + components: + - pos: -24.5,17.5 + parent: 853 + type: Transform +- uid: 5013 + type: WallSolid + components: + - pos: -24.5,16.5 + parent: 853 + type: Transform +- uid: 5014 + type: WallReinforced + components: + - pos: -27.5,21.5 + parent: 853 + type: Transform +- uid: 5015 + type: WallReinforced + components: + - pos: -26.5,21.5 + parent: 853 + type: Transform +- uid: 5016 + type: WallReinforced + components: + - pos: -25.5,21.5 + parent: 853 + type: Transform +- uid: 5017 + type: WallReinforced + components: + - pos: -24.5,21.5 + parent: 853 + type: Transform +- uid: 5018 + type: WallReinforced + components: + - pos: -23.5,21.5 + parent: 853 + type: Transform +- uid: 5019 + type: WallReinforced + components: + - pos: -22.5,21.5 + parent: 853 + type: Transform +- uid: 5020 + type: WallReinforced + components: + - pos: -21.5,21.5 + parent: 853 + type: Transform +- uid: 5021 + type: WallReinforced + components: + - pos: -20.5,21.5 + parent: 853 + type: Transform +- uid: 5022 + type: CableApcExtension + components: + - anchored: True + pos: -27.5,14.5 + parent: 853 + type: Transform +- uid: 5023 + type: CableApcExtension + components: + - anchored: True + pos: -26.5,14.5 + parent: 853 + type: Transform +- uid: 5024 + type: CableApcExtension + components: + - anchored: True + pos: -25.5,14.5 + parent: 853 + type: Transform +- uid: 5025 + type: CableApcExtension + components: + - anchored: True + pos: -25.5,15.5 + parent: 853 + type: Transform +- uid: 5026 + type: CableApcExtension + components: + - anchored: True + pos: -19.5,19.5 + parent: 853 + type: Transform +- uid: 5027 + type: CableApcExtension + components: + - anchored: True + pos: -18.5,19.5 + parent: 853 + type: Transform +- uid: 5028 + type: CableApcExtension + components: + - anchored: True + pos: -19.5,18.5 + parent: 853 + type: Transform + - visible: False + type: Sprite + - canCollide: False + type: Physics +- uid: 5029 + type: CableApcExtension + components: + - anchored: True + pos: -20.5,18.5 + parent: 853 + type: Transform + - visible: False + type: Sprite + - canCollide: False + type: Physics +- uid: 5030 + type: CableApcExtension + components: + - anchored: True + pos: -21.5,18.5 + parent: 853 + type: Transform + - visible: False + type: Sprite + - canCollide: False + type: Physics +- uid: 5031 + type: CableApcExtension + components: + - anchored: True + pos: -22.5,18.5 + parent: 853 + type: Transform + - visible: False + type: Sprite + - canCollide: False + type: Physics +- uid: 5032 + type: CableApcExtension + components: + - anchored: True + pos: -23.5,18.5 + parent: 853 + type: Transform + - visible: False + type: Sprite + - canCollide: False + type: Physics +- uid: 5033 + type: CableApcExtension + components: + - anchored: True + pos: -23.5,18.5 + parent: 853 + type: Transform + - visible: False + type: Sprite + - canCollide: False + type: Physics +- uid: 5034 + type: PoweredSmallLight + components: + - pos: -21.5,18 + parent: 853 + type: Transform + - powerLoad: 0 + type: ApcPowerReceiver + - containers: + light_bulb: !type:ContainerSlot {} + type: ContainerContainer +- uid: 5035 + type: PoweredSmallLight + components: + - pos: -23.5,21 + parent: 853 + type: Transform + - powerLoad: 0 + type: ApcPowerReceiver + - containers: + light_bulb: !type:ContainerSlot {} + type: ContainerContainer +- uid: 5036 + type: PoweredSmallLight + components: + - rot: 1.5707963267948966 rad + pos: -27,17.5 + parent: 853 + type: Transform + - powerLoad: 0 + type: ApcPowerReceiver + - containers: + light_bulb: !type:ContainerSlot {} + type: ContainerContainer +- uid: 5037 + type: CrateEngineeringCableLV + components: + - pos: -20.5,17.5 + parent: 853 + type: Transform + - IsPlaceable: False + type: PlaceableSurface + - containers: + EntityStorageComponent: !type:Container + ents: [] + type: ContainerContainer +- uid: 5038 + type: CableHVStack + components: + - pos: -22.628485,16.665777 + parent: 853 + type: Transform + - canCollide: False + type: Physics +- uid: 5039 + type: Rack + components: + - pos: -23.5,16.5 + parent: 853 + type: Transform +- uid: 5040 + type: RandomSpawner + components: + - pos: -20.5,17.5 + parent: 853 + type: Transform +- uid: 5041 + type: Rack + components: + - pos: -22.5,16.5 + parent: 853 + type: Transform +- uid: 5042 + type: ClothingMaskGas + components: + - pos: -23.68732,16.378263 + parent: 853 + type: Transform + - canCollide: False + type: Physics +- uid: 5043 + type: CableApcExtension + components: + - anchored: True + pos: 25.5,-23.5 + parent: 853 + type: Transform + - visible: False + type: Sprite + - canCollide: False + type: Physics +- uid: 5044 + type: CableApcExtension + components: + - anchored: True + pos: 25.5,-22.5 + parent: 853 + type: Transform + - visible: False + type: Sprite + - canCollide: False + type: Physics +- uid: 5045 + type: CableApcExtension + components: + - anchored: True + pos: 25.5,-23.5 + parent: 853 + type: Transform + - visible: False + type: Sprite + - canCollide: False + type: Physics +- uid: 5046 + type: CableApcExtension + components: + - anchored: True + pos: 25.5,-22.5 + parent: 853 + type: Transform + - visible: False + type: Sprite + - canCollide: False + type: Physics +- uid: 5047 + type: CableApcExtension + components: + - anchored: True + pos: 25.5,-23.5 + parent: 853 + type: Transform + - visible: False + type: Sprite + - canCollide: False + type: Physics +- uid: 5048 + type: CableApcExtension + components: + - anchored: True + pos: 25.5,-22.5 + parent: 853 + type: Transform + - visible: False + type: Sprite + - canCollide: False + type: Physics +- uid: 5049 + type: CableApcExtension + components: + - anchored: True + pos: 25.5,-24.5 + parent: 853 + type: Transform + - visible: False + type: Sprite + - canCollide: False + type: Physics +- uid: 5050 + type: CableApcExtension + components: + - anchored: True + pos: 25.5,-22.5 + parent: 853 + type: Transform + - visible: False + type: Sprite + - canCollide: False + type: Physics +- uid: 5051 + type: CableApcExtension + components: + - anchored: True + pos: -25.5,16.5 + parent: 853 + type: Transform +- uid: 5052 + type: CableApcExtension + components: + - anchored: True + pos: -25.5,17.5 + parent: 853 + type: Transform +- uid: 5053 + type: CableApcExtension + components: + - anchored: True + pos: -25.5,18.5 + parent: 853 + type: Transform +- uid: 5054 + type: CableApcExtension + components: + - anchored: True + pos: -24.5,18.5 + parent: 853 + type: Transform + - visible: False + type: Sprite + - canCollide: False + type: Physics +- uid: 5055 + type: CableApcExtension + components: + - anchored: True + pos: -23.5,18.5 + parent: 853 + type: Transform + - visible: False + type: Sprite + - canCollide: False + type: Physics +- uid: 5056 + type: CableApcExtension + components: + - anchored: True + pos: -22.5,18.5 + parent: 853 + type: Transform + - visible: False + type: Sprite + - canCollide: False + type: Physics +- uid: 5057 + type: CableApcExtension + components: + - anchored: True + pos: -21.5,18.5 + parent: 853 + type: Transform + - visible: False + type: Sprite + - canCollide: False + type: Physics +- uid: 5058 + type: CableApcExtension + components: + - anchored: True + pos: -23.5,18.5 + parent: 853 + type: Transform + - visible: False + type: Sprite + - canCollide: False + type: Physics +- uid: 5059 + type: CableApcExtension + components: + - anchored: True + pos: -24.5,18.5 + parent: 853 + type: Transform + - visible: False + type: Sprite + - canCollide: False + type: Physics +- uid: 5060 + type: Firelock + components: + - pos: 26.5,-19.5 + parent: 853 + type: Transform +- uid: 5061 + type: Firelock + components: + - pos: 23.5,-24.5 + parent: 853 + type: Transform +- uid: 5062 + type: Firelock + components: + - pos: -25.5,15.5 + parent: 853 + type: Transform +- uid: 5063 + type: Firelock + components: + - pos: -19.5,19.5 + parent: 853 + type: Transform +- uid: 5064 + type: ClosetMaintenanceFilledRandom + components: + - pos: 26.5,-22.5 + parent: 853 + type: Transform + - IsPlaceable: False + type: PlaceableSurface + - containers: + EntityStorageComponent: !type:Container + ents: [] + type: ContainerContainer +- uid: 5065 + type: SignToolStorage + components: + - pos: 26.514614,-21.5 + parent: 853 + type: Transform +- uid: 5066 + type: Rack + components: + - pos: 27.5,-22.5 + parent: 853 + type: Transform +- uid: 5067 + type: CableMVStack + components: + - rot: 0.0004529799334704876 rad + pos: 27.736885,-22.289246 + parent: 853 + type: Transform + - canCollide: False + type: Physics +- uid: 5068 + type: Wirecutter + components: + - pos: 27.503157,-22.435783 + parent: 853 + type: Transform + - canCollide: False + type: Physics +- uid: 5069 + type: CableApcStack + components: + - pos: 27.36774,-22.300274 + parent: 853 + type: Transform + - canCollide: False + type: Physics +- uid: 5070 + type: SignEngineering + components: + - pos: -22.503485,18.5 + parent: 853 + type: Transform +- uid: 5071 + type: ClothingHandsGlovesCombat + components: + - pos: -23.447739,16.722254 + parent: 853 + type: Transform + - canCollide: False + type: Physics +- uid: 5072 + type: HarmonicaInstrument + components: + - pos: -22.46857,16.513775 + parent: 853 + type: Transform + - canCollide: False + type: Physics +- uid: 5073 + type: ClothingHeadHatOrangesoft + components: + - pos: -23.316175,16.5 + parent: 853 + type: Transform + - canCollide: False + type: Physics +- uid: 5074 + type: ClosetMaintenanceFilledRandom + components: + - pos: -26.5,19.5 + parent: 853 + type: Transform + - IsPlaceable: False + type: PlaceableSurface + - containers: + EntityStorageComponent: !type:Container + ents: [] + type: ContainerContainer +- uid: 5075 + type: ClosetEmergencyFilledRandom + components: + - pos: -26.5,18.5 + parent: 853 + type: Transform + - IsPlaceable: False + type: PlaceableSurface + - containers: + EntityStorageComponent: !type:Container + ents: [] + type: ContainerContainer +- uid: 5076 + type: ClosetFireFilled + components: + - pos: -26.5,17.5 + parent: 853 + type: Transform + - IsPlaceable: False + type: PlaceableSurface + - containers: + EntityStorageComponent: !type:Container + ents: [] + type: ContainerContainer +- uid: 5077 + type: ClothingHandsGlovesColorYellow + components: + - pos: 27.39341,-25.414946 + parent: 853 + type: Transform + - canCollide: False + type: Physics +- uid: 5078 + type: DrinkBottleBeer + components: + - pos: 26.341326,-25.258587 + parent: 853 + type: Transform + - caps: Refillable, Drainable + type: SolutionContainer + - canCollide: False + type: Physics +- uid: 5079 + type: DrinkBeerglass + components: + - pos: 26.73716,-25.331554 + parent: 853 + type: Transform + - caps: Refillable, Drainable + type: SolutionContainer + - canCollide: False + type: Physics +- uid: 5080 + type: ClosetFireFilled + components: + - pos: 24.5,-25.5 + parent: 853 + type: Transform + - IsPlaceable: False + type: PlaceableSurface + - containers: + EntityStorageComponent: !type:Container + ents: [] + type: ContainerContainer +- uid: 5081 + type: ClosetEmergencyFilledRandom + components: + - pos: 27.5,-20.5 + parent: 853 + type: Transform + - IsPlaceable: False + type: PlaceableSurface + - containers: + EntityStorageComponent: !type:Container + ents: [] + type: ContainerContainer +- uid: 5082 + type: CableHVStack + components: + - pos: -22.638397,16.589691 + parent: 853 + type: Transform + - canCollide: False + type: Physics ... diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/misc.yml b/Resources/Prototypes/Catalog/Fills/Lockers/misc.yml index 78cb14b62b..0a789fc616 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/misc.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/misc.yml @@ -32,3 +32,37 @@ prob: 0.6 - id: ClothingOuterSuitFire prob: 0.75 + +- type: entity + id: ClosetMaintenanceFilledRandom + suffix: Filled, Random + parent: ClosetMaintenance + components: + - type: StorageFill + contents: + - id: lantern + prob: 0.66 + - id: Wirecutter + prob: 0.44 + - id: Screwdriver + prob: 0.44 + - id: Wrench + prob: 0.44 + - id: Crowbar + prob: 0.44 + - id: Welder + prob: 0.44 + - id: Multitool + prob: 0.44 + - id: Soap + prob: 0.44 + - id: PlushieCarp + prob: 0.2 + - id: PlushieSlime + prob: 0.2 + - id: PlushieSnake + prob: 0.2 + - id: ClothingHandsGlovesColorYellow + prob: 0.33 + - id: ClothingBeltUtility + prob: 0.33 diff --git a/Resources/Prototypes/Entities/Structures/Storage/Closets/closets.yml b/Resources/Prototypes/Entities/Structures/Storage/Closets/closets.yml index ad7a284db0..41c491a005 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Closets/closets.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Closets/closets.yml @@ -103,3 +103,16 @@ visuals: - type: StorageVisualizer state: bio_jan + +# Maintenance closet +- type: entity + id: ClosetMaintenance + name: maintenance closet + parent: ClosetBase + description: It's a storage unit. + components: + - type: Appearance + visuals: + - type: StorageVisualizer + state_open: generic_open + state_closed: generic_door From 8637b01277ec13c7752888a5e442455c9125358a Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 5 Aug 2021 18:22:00 -0400 Subject: [PATCH 76/76] Automatic changelog update --- Resources/Changelog/Changelog.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 0eccdb74b9..e900669612 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1668,3 +1668,8 @@ Entries: - {message: Added an alternate jumpsuit for Botany, type: Add} id: 297 time: '2021-08-05T10:18:05.0000000+00:00' +- author: Seth + changes: + - {message: Added two new maintenance areas, type: Add} + id: 298 + time: '2021-08-05T22:20:58.0000000+00:00'