From 1eb3a3e6d162942beabb012b75e23fbd869029cd Mon Sep 17 00:00:00 2001 From: Plykiya <58439124+Plykiya@users.noreply.github.com> Date: Sun, 30 Jun 2024 04:45:43 -0700 Subject: [PATCH 01/86] Fixes lathe usage of EntityPrototypeView (#29608) Co-authored-by: plykiya --- Content.Client/Lathe/UI/LatheMenu.xaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Client/Lathe/UI/LatheMenu.xaml.cs b/Content.Client/Lathe/UI/LatheMenu.xaml.cs index d4bbe89c51..265130d15d 100644 --- a/Content.Client/Lathe/UI/LatheMenu.xaml.cs +++ b/Content.Client/Lathe/UI/LatheMenu.xaml.cs @@ -217,12 +217,12 @@ public sealed partial class LatheMenu : DefaultWindow queuedRecipeBox.Orientation = BoxContainer.LayoutOrientation.Horizontal; var queuedRecipeProto = new EntityPrototypeView(); + queuedRecipeBox.AddChild(queuedRecipeProto); if (_prototypeManager.TryIndex(recipe.Result, out EntityPrototype? entityProto) && entityProto != null) queuedRecipeProto.SetPrototype(entityProto); var queuedRecipeLabel = new Label(); queuedRecipeLabel.Text = $"{idx}. {recipe.Name}"; - queuedRecipeBox.AddChild(queuedRecipeProto); queuedRecipeBox.AddChild(queuedRecipeLabel); QueueList.AddChild(queuedRecipeBox); idx++; From 0c9a0707ce516e802a9a277c40e8369ee85ce4fc Mon Sep 17 00:00:00 2001 From: Chief-Engineer <119664036+Chief-Engineer@users.noreply.github.com> Date: Sun, 30 Jun 2024 07:26:41 -0500 Subject: [PATCH 02/86] Disable ame unsafe admin alert (#29582) disable ame unsafe admin alert --- Content.Server/Ame/EntitySystems/AmeControllerSystem.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Content.Server/Ame/EntitySystems/AmeControllerSystem.cs b/Content.Server/Ame/EntitySystems/AmeControllerSystem.cs index 1b323d6643..e6abe98b95 100644 --- a/Content.Server/Ame/EntitySystems/AmeControllerSystem.cs +++ b/Content.Server/Ame/EntitySystems/AmeControllerSystem.cs @@ -270,6 +270,9 @@ public sealed class AmeControllerSystem : EntitySystem var humanReadableState = controller.Injecting ? "Inject" : "Not inject"; _adminLogger.Add(LogType.Action, LogImpact.Extreme, $"{EntityManager.ToPrettyString(user.Value):player} has set the AME to inject {controller.InjectionAmount} while set to {humanReadableState}"); + /* This needs to be information which an admin is very likely to want to be informed about in order to be an admin alert or have a sound notification. + At the time of editing, players regularly "overclock" the AME and those cases require no admin attention. + // Admin alert var safeLimit = 0; if (TryGetAMENodeGroup(uid, out var group)) @@ -285,6 +288,7 @@ public sealed class AmeControllerSystem : EntitySystem controller.EffectCooldown = _gameTiming.CurTime + controller.CooldownDuration; } } + */ } public void AdjustInjectionAmount(EntityUid uid, int delta, int min = 0, int max = int.MaxValue, EntityUid? user = null, AmeControllerComponent? controller = null) From 6c7f6a425eebcea973dad7a573ed28dd69d8321c Mon Sep 17 00:00:00 2001 From: PJBot Date: Sun, 30 Jun 2024 12:27:47 +0000 Subject: [PATCH 03/86] Automatic changelog update --- Resources/Changelog/Admin.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Resources/Changelog/Admin.yml b/Resources/Changelog/Admin.yml index 8ba91e2e02..1d270f3c0e 100644 --- a/Resources/Changelog/Admin.yml +++ b/Resources/Changelog/Admin.yml @@ -352,5 +352,12 @@ Entries: id: 43 time: '2024-06-28T03:32:57.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28681 +- author: Chief-Engineer + changes: + - message: Admin alert for unsafe AME injection rate was disabled. + type: Remove + id: 44 + time: '2024-06-30T12:26:41.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29582 Name: Admin Order: 1 From c8bb4e961cedec83e4c750513e6654636215226b Mon Sep 17 00:00:00 2001 From: Plykiya <58439124+Plykiya@users.noreply.github.com> Date: Sun, 30 Jun 2024 05:28:00 -0700 Subject: [PATCH 04/86] Change the price of traitor reinforcements to 14, nukie reinforcements to 35 (#29557) Prices traitor reinforcements to 14, nukie reinforcements to 35 Co-authored-by: plykiya --- Resources/Prototypes/Catalog/uplink_catalog.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Resources/Prototypes/Catalog/uplink_catalog.yml b/Resources/Prototypes/Catalog/uplink_catalog.yml index 0ba83dfe73..ace354f43c 100644 --- a/Resources/Prototypes/Catalog/uplink_catalog.yml +++ b/Resources/Prototypes/Catalog/uplink_catalog.yml @@ -906,7 +906,7 @@ productEntity: ReinforcementRadioSyndicate icon: { sprite: Objects/Devices/communication.rsi, state: old-radio-urist } cost: - Telecrystal: 16 + Telecrystal: 14 categories: - UplinkAllies conditions: @@ -922,7 +922,7 @@ productEntity: ReinforcementRadioSyndicateNukeops icon: { sprite: Objects/Devices/communication.rsi, state: old-radio-urist } cost: - Telecrystal: 16 + Telecrystal: 35 categories: - UplinkAllies conditions: From b426d87d82e6268b40039b3da502770c26e805ba Mon Sep 17 00:00:00 2001 From: PJBot Date: Sun, 30 Jun 2024 12:29:06 +0000 Subject: [PATCH 05/86] Automatic changelog update --- Resources/Changelog/Changelog.yml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index c274bcf2d1..371dab6b54 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +1,4 @@ Entries: -- author: Boaz1111 - changes: - - message: The doors on the ice expedition map can now be accessed by anyone instead - of... botanists. - type: Fix - id: 6343 - time: '2024-04-13T20:49:02.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26928 - author: Tayrtahn changes: - message: Placing a player with no entry in the manifest into cryostorage no longer @@ -3830,3 +3822,13 @@ id: 6842 time: '2024-06-30T04:34:06.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29461 +- author: Plykiya + changes: + - message: Syndicate traitor reinforcements price has been changed from 16 TC to + 14 TC. + type: Tweak + - message: Nuclear ops reinforcements price has been changed from 16 TC to 35 TC. + type: Tweak + id: 6843 + time: '2024-06-30T12:28:01.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29557 From fb1740f20866df69a5d877b614b6c40329fa3cce Mon Sep 17 00:00:00 2001 From: Plykiya <58439124+Plykiya@users.noreply.github.com> Date: Sun, 30 Jun 2024 06:42:40 -0700 Subject: [PATCH 06/86] Give skeletons flash immunity (#29604) Co-authored-by: plykiya --- Resources/Prototypes/Entities/Mobs/Species/skeleton.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/Resources/Prototypes/Entities/Mobs/Species/skeleton.yml b/Resources/Prototypes/Entities/Mobs/Species/skeleton.yml index c714f8d6f4..41d81a0e9d 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/skeleton.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/skeleton.yml @@ -101,6 +101,7 @@ probability: 0.5 - type: FireVisuals alternateState: Standing + - type: FlashImmunity - type: entity parent: BaseSpeciesDummy From de50a985b4594d669bd83c0ff3cceaa118115174 Mon Sep 17 00:00:00 2001 From: PJBot Date: Sun, 30 Jun 2024 13:43:46 +0000 Subject: [PATCH 07/86] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 371dab6b54..a75401e853 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +1,4 @@ Entries: -- author: Tayrtahn - changes: - - message: Placing a player with no entry in the manifest into cryostorage no longer - removes the captain from the crew manifest. - type: Fix - id: 6344 - time: '2024-04-14T02:19:42.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26927 - author: superjj18 changes: - message: Honkbot's basic AI has been restored and honks have returned to being @@ -3832,3 +3824,10 @@ id: 6843 time: '2024-06-30T12:28:01.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29557 +- author: Plykiya + changes: + - message: Skeletons are now immune to flashes. + type: Add + id: 6844 + time: '2024-06-30T13:42:40.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29604 From 7e2157fbed07a5cfda2e02cb5e96809788677a81 Mon Sep 17 00:00:00 2001 From: Plykiya <58439124+Plykiya@users.noreply.github.com> Date: Sun, 30 Jun 2024 06:46:45 -0700 Subject: [PATCH 08/86] Borgs can no longer be freely locked/unlocked when emagged (#29605) Co-authored-by: plykiya --- Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml index 4e935f4db5..3b8e5bde6a 100644 --- a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml +++ b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml @@ -144,6 +144,7 @@ - cell_slot - type: Lock locked: true + breakOnEmag: false - type: ActivatableUIRequiresLock - type: LockedWiresPanel - type: Damageable From ab268b6e7793d881ffb089f4aafe5cff81fd4220 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sun, 30 Jun 2024 23:47:22 +1000 Subject: [PATCH 09/86] Make accentless cost 2 points (#29603) Not an easy way to do this so if someone wants the default to be better be my guest. --- Content.Shared/Preferences/HumanoidCharacterProfile.cs | 4 ++-- Resources/Prototypes/Traits/speech.yml | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Content.Shared/Preferences/HumanoidCharacterProfile.cs b/Content.Shared/Preferences/HumanoidCharacterProfile.cs index a823035cd3..c3ebe0d1a3 100644 --- a/Content.Shared/Preferences/HumanoidCharacterProfile.cs +++ b/Content.Shared/Preferences/HumanoidCharacterProfile.cs @@ -587,11 +587,11 @@ namespace Content.Shared.Preferences } var antags = AntagPreferences - .Where(id => prototypeManager.TryIndex(id, out var antag) && antag.SetPreference) + .Where(id => prototypeManager.TryIndex(id, out var antag) && antag.SetPreference) .ToList(); var traits = TraitPreferences - .Where(prototypeManager.HasIndex) + .Where(prototypeManager.HasIndex) .ToList(); Name = name; diff --git a/Resources/Prototypes/Traits/speech.yml b/Resources/Prototypes/Traits/speech.yml index 9448e160b5..961a53023b 100644 --- a/Resources/Prototypes/Traits/speech.yml +++ b/Resources/Prototypes/Traits/speech.yml @@ -5,6 +5,7 @@ name: trait-accentless-name description: trait-accentless-desc category: SpeechTraits + cost: 2 components: - type: Accentless removes: From f4c73398705a1dfe94790c6908b6819fee20b930 Mon Sep 17 00:00:00 2001 From: Partmedia Date: Sun, 30 Jun 2024 05:48:20 -0800 Subject: [PATCH 10/86] Auto-scale sensor monitoring graph (#29559) --- .../SensorMonitoring/SensorMonitoringWindow.xaml.cs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/Content.Client/SensorMonitoring/SensorMonitoringWindow.xaml.cs b/Content.Client/SensorMonitoring/SensorMonitoringWindow.xaml.cs index 9fc132c747..307307c687 100644 --- a/Content.Client/SensorMonitoring/SensorMonitoringWindow.xaml.cs +++ b/Content.Client/SensorMonitoring/SensorMonitoringWindow.xaml.cs @@ -129,14 +129,7 @@ public sealed partial class SensorMonitoringWindow : FancyWindow, IComputerWindo foreach (var stream in sensor.Streams.Values) { - var maxValue = stream.Unit switch - { - SensorUnit.PressureKpa => 5000, // 5 MPa - SensorUnit.Ratio => 1, - SensorUnit.PowerW => 1_000_000, // 1 MW - SensorUnit.EnergyJ => 2_000_000, // 2 MJ - _ => 1000 - }; + var maxValue = stream.Samples.Max(x => x.Value); // TODO: Better way to do this? var lastSample = stream.Samples.Last(); @@ -151,7 +144,7 @@ public sealed partial class SensorMonitoringWindow : FancyWindow, IComputerWindo } }); - Asdf.AddChild(new GraphView(stream.Samples, startTime, curTime, maxValue) { MinHeight = 150 }); + Asdf.AddChild(new GraphView(stream.Samples, startTime, curTime, maxValue * 1.1f) { MinHeight = 150 }); Asdf.AddChild(new PanelContainer { StyleClasses = { StyleBase.ClassLowDivider } }); } } From 715e027183bf8e003f5db1126e403fb2c2787df2 Mon Sep 17 00:00:00 2001 From: PJBot Date: Sun, 30 Jun 2024 13:48:27 +0000 Subject: [PATCH 11/86] Automatic changelog update --- Resources/Changelog/Changelog.yml | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index a75401e853..2b4b325ddd 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,19 +1,4 @@ Entries: -- author: superjj18 - changes: - - message: Honkbot's basic AI has been restored and honks have returned to being - at random intervals! - type: Fix - id: 6345 - time: '2024-04-14T02:51:24.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26939 -- author: GreaseMonk - changes: - - message: Added StopsWhenEntityDead property to audio components - type: Add - id: 6346 - time: '2024-04-14T03:12:38.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26905 - author: DrSmugleaf changes: - message: Fixed rocket launchers and laser guns looking like they have nothing @@ -3831,3 +3816,18 @@ id: 6844 time: '2024-06-30T13:42:40.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29604 +- author: Plykiya + changes: + - message: The lock on emagged borgs no longer breaks, preventing unauthorized access + and emag checking. + type: Tweak + id: 6845 + time: '2024-06-30T13:46:45.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29605 +- author: metalgearsloth + changes: + - message: Accentless speech now costs 2 trait points. + type: Tweak + id: 6846 + time: '2024-06-30T13:47:22.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29603 From 881f3e42557e31f97df16fd840363c2ef5f06e3f Mon Sep 17 00:00:00 2001 From: ~DreamlyJack~ <148849095+DreamlyJack@users.noreply.github.com> Date: Sun, 30 Jun 2024 18:49:35 +0500 Subject: [PATCH 12/86] New Color Dress (#29473) * add dress * add dress * add dress * add dress * add dress * add dress * add dress * add dress * add dress yml * add dress in dresser --- .../Clothing/Uniforms/color_dress.yml | 455 ++++++++++++++++++ .../Entities/Structures/Furniture/dresser.yml | 48 ++ .../equipped-INNERCLOTHING.png | Bin 0 -> 1605 bytes .../Jumpskirt/dresswestern.rsi/icon.png | Bin 0 -> 724 bytes .../dresswestern.rsi/inhand-left.png | Bin 0 -> 796 bytes .../dresswestern.rsi/inhand-right.png | Bin 0 -> 727 bytes .../Jumpskirt/dresswestern.rsi/meta.json | 26 + .../color-equipped-INNERCLOTHING.png | Bin 0 -> 836 bytes .../equipped-INNERCLOTHING.png | Bin 0 -> 561 bytes .../Jumpskirt/elegantdress.rsi/icon-color.png | Bin 0 -> 394 bytes .../Jumpskirt/elegantdress.rsi/icon.png | Bin 0 -> 277 bytes .../elegantdress.rsi/inhand-left.png | Bin 0 -> 713 bytes .../elegantdress.rsi/inhand-right.png | Bin 0 -> 669 bytes .../Jumpskirt/elegantdress.rsi/meta.json | 33 ++ .../color-equipped-INNERCLOTHING.png | Bin 0 -> 791 bytes .../equipped-INNERCLOTHING.png | Bin 0 -> 604 bytes .../Jumpskirt/stripeddress.rsi/icon-color.png | Bin 0 -> 394 bytes .../Jumpskirt/stripeddress.rsi/icon.png | Bin 0 -> 271 bytes .../stripeddress.rsi/inhand-left.png | Bin 0 -> 696 bytes .../stripeddress.rsi/inhand-right.png | Bin 0 -> 634 bytes .../Jumpskirt/stripeddress.rsi/meta.json | 33 ++ .../color-equipped-INNERCLOTHING.png | Bin 0 -> 1077 bytes .../equipped-INNERCLOTHING.png | Bin 0 -> 713 bytes .../Jumpskirt/turtledress.rsi/icon-color.png | Bin 0 -> 405 bytes .../Jumpskirt/turtledress.rsi/icon.png | Bin 0 -> 329 bytes .../Jumpskirt/turtledress.rsi/inhand-left.png | Bin 0 -> 675 bytes .../turtledress.rsi/inhand-right.png | Bin 0 -> 633 bytes .../Jumpskirt/turtledress.rsi/meta.json | 33 ++ 28 files changed, 628 insertions(+) create mode 100644 Resources/Prototypes/Entities/Clothing/Uniforms/color_dress.yml create mode 100644 Resources/Textures/Clothing/Uniforms/Jumpskirt/dresswestern.rsi/equipped-INNERCLOTHING.png create mode 100644 Resources/Textures/Clothing/Uniforms/Jumpskirt/dresswestern.rsi/icon.png create mode 100644 Resources/Textures/Clothing/Uniforms/Jumpskirt/dresswestern.rsi/inhand-left.png create mode 100644 Resources/Textures/Clothing/Uniforms/Jumpskirt/dresswestern.rsi/inhand-right.png create mode 100644 Resources/Textures/Clothing/Uniforms/Jumpskirt/dresswestern.rsi/meta.json create mode 100644 Resources/Textures/Clothing/Uniforms/Jumpskirt/elegantdress.rsi/color-equipped-INNERCLOTHING.png create mode 100644 Resources/Textures/Clothing/Uniforms/Jumpskirt/elegantdress.rsi/equipped-INNERCLOTHING.png create mode 100644 Resources/Textures/Clothing/Uniforms/Jumpskirt/elegantdress.rsi/icon-color.png create mode 100644 Resources/Textures/Clothing/Uniforms/Jumpskirt/elegantdress.rsi/icon.png create mode 100644 Resources/Textures/Clothing/Uniforms/Jumpskirt/elegantdress.rsi/inhand-left.png create mode 100644 Resources/Textures/Clothing/Uniforms/Jumpskirt/elegantdress.rsi/inhand-right.png create mode 100644 Resources/Textures/Clothing/Uniforms/Jumpskirt/elegantdress.rsi/meta.json create mode 100644 Resources/Textures/Clothing/Uniforms/Jumpskirt/stripeddress.rsi/color-equipped-INNERCLOTHING.png create mode 100644 Resources/Textures/Clothing/Uniforms/Jumpskirt/stripeddress.rsi/equipped-INNERCLOTHING.png create mode 100644 Resources/Textures/Clothing/Uniforms/Jumpskirt/stripeddress.rsi/icon-color.png create mode 100644 Resources/Textures/Clothing/Uniforms/Jumpskirt/stripeddress.rsi/icon.png create mode 100644 Resources/Textures/Clothing/Uniforms/Jumpskirt/stripeddress.rsi/inhand-left.png create mode 100644 Resources/Textures/Clothing/Uniforms/Jumpskirt/stripeddress.rsi/inhand-right.png create mode 100644 Resources/Textures/Clothing/Uniforms/Jumpskirt/stripeddress.rsi/meta.json create mode 100644 Resources/Textures/Clothing/Uniforms/Jumpskirt/turtledress.rsi/color-equipped-INNERCLOTHING.png create mode 100644 Resources/Textures/Clothing/Uniforms/Jumpskirt/turtledress.rsi/equipped-INNERCLOTHING.png create mode 100644 Resources/Textures/Clothing/Uniforms/Jumpskirt/turtledress.rsi/icon-color.png create mode 100644 Resources/Textures/Clothing/Uniforms/Jumpskirt/turtledress.rsi/icon.png create mode 100644 Resources/Textures/Clothing/Uniforms/Jumpskirt/turtledress.rsi/inhand-left.png create mode 100644 Resources/Textures/Clothing/Uniforms/Jumpskirt/turtledress.rsi/inhand-right.png create mode 100644 Resources/Textures/Clothing/Uniforms/Jumpskirt/turtledress.rsi/meta.json diff --git a/Resources/Prototypes/Entities/Clothing/Uniforms/color_dress.yml b/Resources/Prototypes/Entities/Clothing/Uniforms/color_dress.yml new file mode 100644 index 0000000000..8ef3ea0451 --- /dev/null +++ b/Resources/Prototypes/Entities/Clothing/Uniforms/color_dress.yml @@ -0,0 +1,455 @@ +# Black elegant dress +- type: entity + parent: ClothingUniformSkirtBase + id: ClothingUniformJumpskirtBlackElegantDress + name: black elegant dress + description: Elegant dress with a beautiful bow. + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpskirt/elegantdress.rsi + layers: + - state: icon + - state: icon-color + color: "#212030" + - type: Item + inhandVisuals: + left: + - state: inhand-left + color: "#212030" + right: + - state: inhand-right + color: "#212030" + - type: Clothing + sprite: Clothing/Uniforms/Jumpskirt/elegantdress.rsi + clothingVisuals: + jumpsuit: + - state: color-equipped-INNERCLOTHING + color: "#212030" + - state: equipped-INNERCLOTHING + +# Red elegant dress +- type: entity + parent: ClothingUniformSkirtBase + id: ClothingUniformJumpskirtRedElegantDress + name: red elegant dress + description: Elegant dress with a beautiful bow. + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpskirt/elegantdress.rsi + layers: + - state: icon + - state: icon-color + color: "#60171d" + - type: Item + inhandVisuals: + left: + - state: inhand-left + color: "#60171d" + right: + - state: inhand-right + color: "#60171d" + - type: Clothing + sprite: Clothing/Uniforms/Jumpskirt/elegantdress.rsi + clothingVisuals: + jumpsuit: + - state: color-equipped-INNERCLOTHING + color: "#60171d" + - state: equipped-INNERCLOTHING + +# Green elegant dress +- type: entity + parent: ClothingUniformSkirtBase + id: ClothingUniformJumpskirtGreenElegantDress + name: green elegant dress + description: Elegant dress with a beautiful bow. + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpskirt/elegantdress.rsi + layers: + - state: icon + - state: icon-color + color: "#0e6316" + - type: Item + inhandVisuals: + left: + - state: inhand-left + color: "#0e6316" + right: + - state: inhand-right + color: "#0e6316" + - type: Clothing + sprite: Clothing/Uniforms/Jumpskirt/elegantdress.rsi + clothingVisuals: + jumpsuit: + - state: color-equipped-INNERCLOTHING + color: "#0e6316" + - state: equipped-INNERCLOTHING + +# Blue elegant dress +- type: entity + parent: ClothingUniformSkirtBase + id: ClothingUniformJumpskirtBlueElegantDress + name: blue elegant dress + description: Elegant dress with a beautiful bow. + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpskirt/elegantdress.rsi + layers: + - state: icon + - state: icon-color + color: "#076183" + - type: Item + inhandVisuals: + left: + - state: inhand-left + color: "#076183" + right: + - state: inhand-right + color: "#076183" + - type: Clothing + sprite: Clothing/Uniforms/Jumpskirt/elegantdress.rsi + clothingVisuals: + jumpsuit: + - state: color-equipped-INNERCLOTHING + color: "#076183" + - state: equipped-INNERCLOTHING + +# Purple elegant dress +- type: entity + parent: ClothingUniformSkirtBase + id: ClothingUniformJumpskirtPurpleElegantDress + name: purple elegant dress + description: Elegant dress with a beautiful bow. + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpskirt/elegantdress.rsi + layers: + - state: icon + - state: icon-color + color: "#630767" + - type: Item + inhandVisuals: + left: + - state: inhand-left + color: "#630767" + right: + - state: inhand-right + color: "#630767" + - type: Clothing + sprite: Clothing/Uniforms/Jumpskirt/elegantdress.rsi + clothingVisuals: + jumpsuit: + - state: color-equipped-INNERCLOTHING + color: "#630767" + - state: equipped-INNERCLOTHING + +# Cyan striped dress +- type: entity + parent: ClothingUniformSkirtBase + id: ClothingUniformJumpskirtCyanStripedDress + name: cyan striped dress + description: Cute striped dress. + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpskirt/stripeddress.rsi + layers: + - state: icon + - state: icon-color + color: "#009a9a" + - type: Item + inhandVisuals: + left: + - state: inhand-left + color: "#009a9a" + right: + - state: inhand-right + color: "#009a9a" + - type: Clothing + sprite: Clothing/Uniforms/Jumpskirt/stripeddress.rsi + clothingVisuals: + jumpsuit: + - state: color-equipped-INNERCLOTHING + color: "#009a9a" + - state: equipped-INNERCLOTHING + +# Red striped dress +- type: entity + parent: ClothingUniformSkirtBase + id: ClothingUniformJumpskirtRedStripedDress + name: red striped dress + description: Cute striped dress. + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpskirt/stripeddress.rsi + layers: + - state: icon + - state: icon-color + color: "#a40000" + - type: Item + inhandVisuals: + left: + - state: inhand-left + color: "#a40000" + right: + - state: inhand-right + color: "#a40000" + - type: Clothing + sprite: Clothing/Uniforms/Jumpskirt/stripeddress.rsi + clothingVisuals: + jumpsuit: + - state: color-equipped-INNERCLOTHING + color: "#a40000" + - state: equipped-INNERCLOTHING + +# Green striped dress +- type: entity + parent: ClothingUniformSkirtBase + id: ClothingUniformJumpskirtGreenStripedDress + name: green striped dress + description: Cute striped dress. + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpskirt/stripeddress.rsi + layers: + - state: icon + - state: icon-color + color: "#008c1f" + - type: Item + inhandVisuals: + left: + - state: inhand-left + color: "#008c1f" + right: + - state: inhand-right + color: "#008c1f" + - type: Clothing + sprite: Clothing/Uniforms/Jumpskirt/stripeddress.rsi + clothingVisuals: + jumpsuit: + - state: color-equipped-INNERCLOTHING + color: "#008c1f" + - state: equipped-INNERCLOTHING + + +# Pink striped dress +- type: entity + parent: ClothingUniformSkirtBase + id: ClothingUniformJumpskirtPinkStripedDress + name: pink striped dress + description: Cute striped dress. + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpskirt/stripeddress.rsi + layers: + - state: icon + - state: icon-color + color: "#a40086" + - type: Item + inhandVisuals: + left: + - state: inhand-left + color: "#a40086" + right: + - state: inhand-right + color: "#a40086" + - type: Clothing + sprite: Clothing/Uniforms/Jumpskirt/stripeddress.rsi + clothingVisuals: + jumpsuit: + - state: color-equipped-INNERCLOTHING + color: "#a40086" + - state: equipped-INNERCLOTHING + +# Orange striped dress +- type: entity + parent: ClothingUniformSkirtBase + id: ClothingUniformJumpskirtOrangeStripedDress + name: orange striped dress + description: Cute striped dress. + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpskirt/stripeddress.rsi + layers: + - state: icon + - state: icon-color + color: "#9d4000" + - type: Item + inhandVisuals: + left: + - state: inhand-left + color: "#9d4000" + right: + - state: inhand-right + color: "#9d4000" + - type: Clothing + sprite: Clothing/Uniforms/Jumpskirt/stripeddress.rsi + clothingVisuals: + jumpsuit: + - state: color-equipped-INNERCLOTHING + color: "#9d4000" + - state: equipped-INNERCLOTHING + +# Purple turtleneck dress +- type: entity + parent: ClothingUniformSkirtBase + id: ClothingUniformJumpskirtPurpleTurtleneckDress + name: purple turtleneck dress + description: Turtleneck dress with a unique design. + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpskirt/turtledress.rsi + layers: + - state: icon + - state: icon-color + color: "#341255" + - type: Item + inhandVisuals: + left: + - state: inhand-left + color: "#341255" + right: + - state: inhand-right + color: "#341255" + - type: Clothing + sprite: Clothing/Uniforms/Jumpskirt/turtledress.rsi + clothingVisuals: + jumpsuit: + - state: color-equipped-INNERCLOTHING + color: "#341255" + - state: equipped-INNERCLOTHING + +# Red turtleneck dress +- type: entity + parent: ClothingUniformSkirtBase + id: ClothingUniformJumpskirtRedTurtleneckDress + name: red turtleneck dress + description: Turtleneck dress with a unique design. + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpskirt/turtledress.rsi + layers: + - state: icon + - state: icon-color + color: "#730404" + - type: Item + inhandVisuals: + left: + - state: inhand-left + color: "#730404" + right: + - state: inhand-right + color: "#730404" + - type: Clothing + sprite: Clothing/Uniforms/Jumpskirt/turtledress.rsi + clothingVisuals: + jumpsuit: + - state: color-equipped-INNERCLOTHING + color: "#730404" + - state: equipped-INNERCLOTHING + +# Green turtleneck dress +- type: entity + parent: ClothingUniformSkirtBase + id: ClothingUniformJumpskirtGreenTurtleneckDress + name: green turtleneck dress + description: Turtleneck dress with a unique design. + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpskirt/turtledress.rsi + layers: + - state: icon + - state: icon-color + color: "#0e6316" + - type: Item + inhandVisuals: + left: + - state: inhand-left + color: "#0e6316" + right: + - state: inhand-right + color: "#0e6316" + - type: Clothing + sprite: Clothing/Uniforms/Jumpskirt/turtledress.rsi + clothingVisuals: + jumpsuit: + - state: color-equipped-INNERCLOTHING + color: "#0e6316" + - state: equipped-INNERCLOTHING + +# Blue turtleneck dress +- type: entity + parent: ClothingUniformSkirtBase + id: ClothingUniformJumpskirtBlueTurtleneckDress + name: blue turtleneck dress + description: Turtleneck dress with a unique design. + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpskirt/turtledress.rsi + layers: + - state: icon + - state: icon-color + color: "#193f68" + - type: Item + inhandVisuals: + left: + - state: inhand-left + color: "#193f68" + right: + - state: inhand-right + color: "#193f68" + - type: Clothing + sprite: Clothing/Uniforms/Jumpskirt/turtledress.rsi + clothingVisuals: + jumpsuit: + - state: color-equipped-INNERCLOTHING + color: "#193f68" + - state: equipped-INNERCLOTHING + +# Yellow turtleneck dress +- type: entity + parent: ClothingUniformSkirtBase + id: ClothingUniformJumpskirtYellowTurtleneckDress + name: yellow turtleneck dress + description: Turtleneck dress with a unique design. + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpskirt/turtledress.rsi + layers: + - state: icon + - state: icon-color + color: "#684519" + - type: Item + inhandVisuals: + left: + - state: inhand-left + color: "#684519" + right: + - state: inhand-right + color: "#684519" + - type: Clothing + sprite: Clothing/Uniforms/Jumpskirt/turtledress.rsi + clothingVisuals: + jumpsuit: + - state: color-equipped-INNERCLOTHING + color: "#684519" + - state: equipped-INNERCLOTHING + +# Yellow old dress +- type: entity + parent: ClothingUniformSkirtBase + id: ClothingUniformJumpskirtYellowOldDress + name: yellow old dress + description: Classic western dress. + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpskirt/dresswestern.rsi + layers: + - state: icon + - type: Item + inhandVisuals: + left: + - state: inhand-left + right: + - state: inhand-right + - type: Clothing + sprite: Clothing/Uniforms/Jumpskirt/dresswestern.rsi diff --git a/Resources/Prototypes/Entities/Structures/Furniture/dresser.yml b/Resources/Prototypes/Entities/Structures/Furniture/dresser.yml index 1309060bac..92d1118d4e 100644 --- a/Resources/Prototypes/Entities/Structures/Furniture/dresser.yml +++ b/Resources/Prototypes/Entities/Structures/Furniture/dresser.yml @@ -77,6 +77,54 @@ - id: ClothingUniformJumpsuitLoungewear prob: 0.05 orGroup: dressermainloot + - id: ClothingUniformJumpskirtBlackElegantDress + prob: 0.05 + orGroup: dressermainloot + - id: ClothingUniformJumpskirtRedElegantDress + prob: 0.05 + orGroup: dressermainloot + - id: ClothingUniformJumpskirtGreenElegantDress + prob: 0.05 + orGroup: dressermainloot + - id: ClothingUniformJumpskirtBlueElegantDress + prob: 0.05 + orGroup: dressermainloot + - id: ClothingUniformJumpskirtPurpleElegantDress + prob: 0.05 + orGroup: dressermainloot + - id: ClothingUniformJumpskirtCyanStripedDress + prob: 0.05 + orGroup: dressermainloot + - id: ClothingUniformJumpskirtRedStripedDress + prob: 0.05 + orGroup: dressermainloot + - id: ClothingUniformJumpskirtGreenStripedDress + prob: 0.05 + orGroup: dressermainloot + - id: ClothingUniformJumpskirtPinkStripedDress + prob: 0.05 + orGroup: dressermainloot + - id: ClothingUniformJumpskirtOrangeStripedDress + prob: 0.05 + orGroup: dressermainloot + - id: ClothingUniformJumpskirtPurpleTurtleneckDress + prob: 0.05 + orGroup: dressermainloot + - id: ClothingUniformJumpskirtRedTurtleneckDress + prob: 0.05 + orGroup: dressermainloot + - id: ClothingUniformJumpskirtGreenTurtleneckDress + prob: 0.05 + orGroup: dressermainloot + - id: ClothingUniformJumpskirtBlueTurtleneckDress + prob: 0.05 + orGroup: dressermainloot + - id: ClothingUniformJumpskirtYellowTurtleneckDress + prob: 0.05 + orGroup: dressermainloot + - id: ClothingUniformJumpskirtYellowOldDress + prob: 0.05 + orGroup: dressermainloot - id: Pen # It`s pen. prob: 0.03 orGroup: dressersecondloot diff --git a/Resources/Textures/Clothing/Uniforms/Jumpskirt/dresswestern.rsi/equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/dresswestern.rsi/equipped-INNERCLOTHING.png new file mode 100644 index 0000000000000000000000000000000000000000..b05f55716a0d047f9b0f16e0b5b544f264b450b7 GIT binary patch literal 1605 zcmV-L2DfWKd$@D?RS~IY%+6i?pT5|use6o%st;Z zXMXRTBjS^{fVY6RfVY6RfVY6R!2i?&O{-Dw$L_v0$hX`SiC($*hYbCC!M!d3K67YC z@Zz@iAVRM_pbR?db$Zs%qlX_<*du+Lg34f5^g|gkpgt+)b+#{*k9S2@KlsK@`JlTa zIJT!0AV(Pqy@GbOJd?Bo-_50>9DIMLjGy^S*00Y+Ka@d7eNud@VFSy8Int5-@8!8? zA5zcz51kMyf8^sD{Xj^sR;yBY{3|Qoijmmyyhssa^(VTxrnK z;4eD`FvQXG07p7onUQ=~tBijAn^GD>9?GK(I_e=L+T%UEuU<^~r{jY!__HJ8EJ06$ zZ|ot=@yXdm+0lKo!hGQVjjD&^Ll8>;_|q?{&37YLq_b_UygYK&$Um+~yxa&s-iGBg z_|^hqg@0;qi*)4M)i5V0bN0}uM=v_rl7Ro#>zf*4>oUCk&O{>oWv2icBj-NLVW!tn zdA*%ppT#rq8)1_-WuTm-n{wkKs53zCV&2cRovST+^HJ6G!k;oZdqub?Mim)9g!tj~ z!lKI*;xDGcPh&6hk( zxhs6@5pYk-9kQ;rvQydk2w-{5JXn@Z`XsJ*g>StGLSpoAU4Hd^5T~m~8UEogREJAB zmcVCw8xgDwfR7C@+uT}I2NC3)_n_nS$5R-}Y>($G(%eM2uGr$Uy$!aN0X`Z!CwCR{ z>eLqw@o+Rypxi$>0ln#WQSHz^%j6Q@xLBb#OU1mr`^jl_y2FPMj=u|+7o~aK+9=1@ z89s<`YJBaPEiyh;k?!{WX1CKs@pXyzV3MTY6%3pqaN|B!bylTytJWRPO{KSG%kVXQ#P&|%v zt?}dX__DWvw}7{Rw}7{Rw}7{Rw}7{Rw}7|6sk>A~t&7)(^4!qwmh{Yre>lXnUPL2u~SWG3LW^}qlYFoBH( zzXW80a|T98;uo+U0gU`$fCBu0v^b(Q5HEnWz;(i0$J_@dltzLqNH7FLc@)9o&)NV8 zKIq6_5Kwp*m5WZ4XW*Z=HGnG_tPDVZJKf=?_yD%IRnDdzd14Ccu?Xl4Og~*V z@MC)uZz}^V<)6ovql9rB9{?pN17Z*mi-4E}U_%CuNYLTBM=XPFjDm3u;Mp62(b#?% zAF$?f_^JZ^I|L2m15V4pE8FFTsY+(w1ukVU?gH?}7B}3c&iVM_Lufko{yoRWqjwS0 zi*o?t18`PHJNWgp@)o<4!8ig!C8{#GZ%!S1dv=HM0s7p+H+fI7Q^f~lsW>rQ%3w4= zy$~XUa!MJ3bmUOlh2lB}o;jkfr3`VIeA!!Im0RFnmmgsaBG9pg00000NkvXXu0mjf DWK0aS literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/Uniforms/Jumpskirt/dresswestern.rsi/icon.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/dresswestern.rsi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..d0e3be19d9fcc451a595489b67071e67da8988dd GIT binary patch literal 724 zcmV;_0xSKAP)F|82^m^m}qOP147Z5Tehea)u96EBDh2l9ioEhQhM=_2O|Q{hKCdi>JZf}sHn?A z#2yTaAVMnW5LUag8?2b)>e|MlK}`Gmwr>~*o%iPCWj>gB^Zj|>@Atmi6w!smx#6sKUhN8WM0QerrLzUhTLICx7LhUwURt8vgvf>! zt{DH{4_-S5X}+`wqz3o#h0@+wy%90Wz5@rx61SinPl?yzphe(La+Y?tn0)J1OI)pn z5HkK61bfX-;yA?Xin3X-nt05ln-4$p?S`KZuHVw{GBwoKIb&0kLH8fLs~pENUA?rQ zCNnuojDA(-S2kfLKhKMiqyWK>$1C|#n&O?C=ya?KF7v!`UR}vhCfq%5a37pJevns( zs)%5<&|kdy#HRkomUdq4KmijXqPxc+!0vhBcUXaSt-wF*HZ!|F-crf{0000zL_jB)MaP9j+Ix1x)Cq^;HUtD5bULUOdf|}U6PLy1 zb2q4WB|YDGZ+7yuy?fuh*>`igz{7Zs&Bponsz2{L^XAZ7{}yf*2L|kD4`+;pk!ZDb zx{PUZs#CuI_PKoVO>!`?T~Ti<3?==2OcXdjI~p+&}AQ z{`c-};@@la&PKTYJl>_|@uv7;&rL>00g3H93pOyT7{0l)Hc4>n+Q_w`w;BJiotRhr zZoXAv$etT}A|5%Y%3NW%5mWQ-t@Fg=JKk5PCjUHE_;aNUV_nACYtFxRU0isy>VM{a z5iPbmQFfTU!OPi;r$9Hi@1pTk!vw(A~INU!H}xd(X&=pZ~l3yq)mv z_57_qNi;XUCqZGq6 zmJjBC=kB>A#dQCVR(yNz#Wm~_9x1oVGHyp*6F6gbXrcc{yPsFf8rE*WKAAfE>K2p_ktY(JK&6R&Vo$ma3&hn3u^FVdQ&MBb@03-u%a{vGU literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/Uniforms/Jumpskirt/dresswestern.rsi/inhand-right.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/dresswestern.rsi/inhand-right.png new file mode 100644 index 0000000000000000000000000000000000000000..224a449ba2527a45e6f18cdf12583877076fe46e GIT binary patch literal 727 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7T#lEV4COY;uuoF_;!}RhiIV4vGd%~ zU)(E&l;1sjdAVw*wn(&xcQngdr%N}{o z+Berge~QvAjqr0*D`V2$UA4?#v-4oNO4a&#-65x}(2MMf;|2Jz2Qz@eKd)mCq)5|9Snh>lJ^s z=8j8OceU34?w_A?;nQ~8=ew?*W%ZS4a4CMpGGo_Ikt5CB>9?Ohid-F1>>jh=?J9o< z0Tm}}&fEIIt8P{@x#h>H*2&Fi&aGQD%kKWOptF7qHBTNN`1Un(`;o$U#+X?Zk-zSn z{5~bL%YlIf7t&#=`PX$v$$b9m7xNgS|J8^JF5M@#JvXA{`~lCOxmBwBd>FdkNftbv zF7j$u_?M=|FXlB`{6Fz!m&f+i72ndW7@x6S`u_get<5uv|80H~Y_-w;fLg?_e|NXJ z%gwi~|83pn=Wx-MdE1}2)9dyGu^f0Kc;eST+w~5`duQJN@_c6cKVgGvwtrLjOZGbi z@b%1V)PI%#jbHWibIn^ZvtP1^+%--(oi8>qUGG= literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/Uniforms/Jumpskirt/dresswestern.rsi/meta.json b/Resources/Textures/Clothing/Uniforms/Jumpskirt/dresswestern.rsi/meta.json new file mode 100644 index 0000000000..a6cb5521a2 --- /dev/null +++ b/Resources/Textures/Clothing/Uniforms/Jumpskirt/dresswestern.rsi/meta.json @@ -0,0 +1,26 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Add new dress by DreamlyJack", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-INNERCLOTHING", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Uniforms/Jumpskirt/elegantdress.rsi/color-equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/elegantdress.rsi/color-equipped-INNERCLOTHING.png new file mode 100644 index 0000000000000000000000000000000000000000..b42fc9d2d5f3153bf8c10c4e1d2086902c4fad53 GIT binary patch literal 836 zcmV-K1H1f*P)$&*1BC-q%cVDex9{+h$12z`)M`&kw>@+I3!B z0aw5ka0OfeSHKl;1zdqF1rCS9qi$7462&pX?<-EOnv50sRH~j z2pGiv%F>sMpIiZUHv7~EsMRWHjh{RQb+%Kq+mk3?1KU==N_Z;p{Qub2_(=gc(l~~+ z3P_ZMXp3y^m!a19+3x`)ix9M}g{<|}u1}3$J0Fkd3b+EUfGgk%xB{+#E8q&a0d({G3Dv+Z`aQq`uGi~le#_-@CjS_}1z17B)bEAzpU>xy?oqjjfXJv=fj-z& zu2?LRuMANhPNc>pgXrZ<8$B^*@}w{YCfF}l%m2mM*f9lWd}Sc@D96gFMs1ay8GzOo$QV8IGf!Er7kD;bB=WO6b=N zwK|6tTR8e4gyo_-JVJl0oh<->+I(j3MFH(bq?LdeF|eV2IN0mBkWUj1T2?xCDAYr% ze)(92Lld^ox8jhz9kwJ?m%@DixD#YX!m+K|qjO-UnPIFFqz`NRY6=j>w>b1+ZC{E* zas}As+^2kO71-xeBvSo0R&5T+P0;SDN|xo$aVRV0-zig|-Er#mffkVj2+pfS5SKu#jcFCW#-Ifvn O0000JAGgn5wk6zhGWa(O}1#yUNzuG=&{Tq5ge=lSC_vY3o zs`Jv#^gNoD`ey#q3R|}R>)VNLv458?zr6e1`E@n_mtCJ!U$J3+*Uu+!+9KxsJNezP z^iS#Q@LLOV)wmB%%GY|S6i~l5evAGwMVL2`e1yvUz|Rq~ig(qsyrr3;X{oEH?4SK0 z`K<81p8Bh{-RD+sv3X}c!#9pKaAvMUT~2UQ((75*4RW=Qb2sswIIeS9Gk3y=$#0)r zsmL?-J-wZrh8tVW#n^M%G~WTqiI@8lN?+3_xBkLSz!T@cjDKd?#{cV zxA@81u!_cIe`mU>Io@wyaM@hJ@YY3!Jsr9^oVP=_e7s-befi7YdG-qSef-dfKsbAb b@DD~^<>ZX>$11i0lLCXMtDnm{r-UW|KNI!5 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/Uniforms/Jumpskirt/elegantdress.rsi/icon-color.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/elegantdress.rsi/icon-color.png new file mode 100644 index 0000000000000000000000000000000000000000..c7b16320f822204accadfb2f68643f3314adcfff GIT binary patch literal 394 zcmV;50d@X~P)Q;;+I{u6>8P@S*j5`Gj=eVV5>bbA$>e(y?GXmU%2+Bg3fO~;@C0IamyS8chnMdG>lHdKZPu9>8pq(Ctjb|JFLrLGD!W{xg1A07r^r2o#uz0jZ0Y%kBMlu;AYz*Ea9)yaQ7Y>#$#Ra&qdCE}!pmLi&aAkKMDHWpfwk9p$**(%NXw zBBmGFnYdH&LO~9L8efshiH!>G&0mC>Dq`6$#YEiZ=lk^UnUkA9w7+{ZW18fqhZ_W% zRx+P#ym2CH!$wXmhifdwJiC+=rZry?O4i&obMc#t0usE9$OHq!wTZFVdQ&MBb@0DtmkbN~PV literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/Uniforms/Jumpskirt/elegantdress.rsi/inhand-left.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/elegantdress.rsi/inhand-left.png new file mode 100644 index 0000000000000000000000000000000000000000..cd56f71e4d773b60a33cdf7cb9dd063aff2ddf04 GIT binary patch literal 713 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7T#lEV4C3R;uuoF_;!|W*C7Xi*2e}f zylk4Z^PirR-tkE!?<+%c3aquCZG$)_t^_3vXji+Z<^+ z(`VL^i{6L(KD=w1qq|Y^X8T(PPA_$RhYj0qzm3_=V3iv^qdQNLVZt20FgZq+?#h_Q zA8&|MPuHEyxIEy5azna2=e_sekL~?a#GsgvvQ~fJoV8DjetuzJeZH^M_DaUP)a`NW z@78TuD^#n^aB3n0=L8&xNBP6b&41_2zf+^Pdcyicw=7;UeqfboU-EWwk*;|P&+XrT z?S8Jj`}+Rht=seqSk#T)vrPE>@T+{0fzrW>*NiUbFQ3jV4(Dy$DtGYlbrtSk83NNB ze7pP=7!aDMg3uB!Eej!PC{xWt~$(697N=M~?si literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/Uniforms/Jumpskirt/elegantdress.rsi/inhand-right.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/elegantdress.rsi/inhand-right.png new file mode 100644 index 0000000000000000000000000000000000000000..e661218a4ed282cede0d5028d65ccdeeaec32c8d GIT binary patch literal 669 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7T#lEU`p|HaSW+od^^jw?~s8&>)9+; z(MKF1ce?td0}8pKEEr7g@HMt9Nc^m`ck0H+M-(|fuA8Tta@^iHf8PuNLo+v578eKX z=uFz?%14hEcYOW*xoFqA%YWW>*NUa?zd!q9#T)*aN0Soe`r8lA2@GuFlq|cF$=Bpi z&~uo7Q&vxQ(dySG^*fvNr+4%ItJ-_(-QkBHxS4am6!#S0{c5=-Iy)^v;kkI<#&e1Z z@y_Y91ee4Y@y$_~mBlC5WKv=^_e_e>!=-Dv8T7AP#>w;_7mqBR6+e$LX6N0!`OiP! zO!)Y>ZoB-J{Oz+3N<4oWVSY7c7jxR?nR|b8+`G$q`Q?>ECkzX+4xKL-K6{Yw#@n)Q ze7_Iwc6+IF{MOZ%+mfT08B|Uj{lj&DVWB|KnHLxS`kJsad|1ZLtidB;aN(K6Jl37l zo)%d)-3vK;aJB+-x9yv2uS-jKuRjae{p)|%GnpNp%^V#K*wF+Bo%3?9pYK}tYTqB0 z|6J2K-S*|$&AesA(t@S^*R^{*E*KFoTW;Oyjj91<@C|yr`uXCPt z%enpLAu&()FI@S3c`(bnmUxR}ziRbAPTk^r|Km*Ceus{DjdkhQukCrgU%P(k&4)8S zEr%Q2j5)OzmqzE?B; z>-|w>l=yYVVdj6mruNlx0mE--k0&icGDv&8GqXBytuZ;evjd%!U~HZ$eK*`2%F m?5EbZ|M&k{^!WGnxAN26tu^1wxflXWb_|}belF{r5}E+|g+TNG literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/Uniforms/Jumpskirt/elegantdress.rsi/meta.json b/Resources/Textures/Clothing/Uniforms/Jumpskirt/elegantdress.rsi/meta.json new file mode 100644 index 0000000000..4ff6d9a9f4 --- /dev/null +++ b/Resources/Textures/Clothing/Uniforms/Jumpskirt/elegantdress.rsi/meta.json @@ -0,0 +1,33 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Add new dress by DreamlyJack", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "icon-color" + }, + { + "name": "equipped-INNERCLOTHING", + "directions": 4 + }, + { + "name": "color-equipped-INNERCLOTHING", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Uniforms/Jumpskirt/stripeddress.rsi/color-equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/stripeddress.rsi/color-equipped-INNERCLOTHING.png new file mode 100644 index 0000000000000000000000000000000000000000..5fdda87272f410b5a592c89291f5f2642f6bddb9 GIT binary patch literal 791 zcmV+y1L*vTP)p{P)U2hu((?0WU>DQsMj0vHpXQryIQTX zL$25Bu;1@T*~}VCGc)7_f~?b(6?caR|EZeCpbR_f6OE_37`hyv|R&G zRxz}HZ!uc%dt=@|LmBcK?x?e6*7YPo4B9T`Y-`1@Mu5&{N__%(MS(YdbsN;x9jiSZ zG|CjjoxnF!iM+;?O35bMDXI!BmhGlx!w@-PLLO*+RyX( z&p9x}5%PRS4kxv&uS9PFh|`-xH3Br8Q_AO}K*!A$Lv8iuP~8S|_w)SXLypxfc{{w*G$)PF$owOuakKD^~#lZ6;aIl=B911+S0-q+a VdNx!&>HPoz002ovPDHLkV1k)cbVvXI literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/Uniforms/Jumpskirt/stripeddress.rsi/equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/stripeddress.rsi/equipped-INNERCLOTHING.png new file mode 100644 index 0000000000000000000000000000000000000000..197c68a0ff4f0ba25723ac33d78d6254dc6b99a3 GIT binary patch literal 604 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7T#lEU{dyUaSW+od^^k5kJ(V5Roul< zwzI8CjEm)=#GHm%%1;B|Um@H}bt<+@yX?iu! z$D4`e-PbDP&##WnRttH${Kw}lrpr6~Yp$+)?Hzi0ld{2~>)Xo@_SRJy98xJH`fz#b&1Dy~?%(;v9nPRHKJUeke+wTxb&fxJ^vP6}$3GsP zx7j;qRoCjx;V&m2-m^OU?}YoCTy?DVTQ}v*S@tfBg$QW9=ZyX1z<3|K-KFxL&6(AFt14pQ6hD_h0iR`F$Iu zZtY=@n#=F;`r3Qnbk-Y-&zJeUjg~nZ!C#zrc1zxO-vcZ@sOX<{qyNPHe}DaKmR$@? OH4L7velF{r5}E)N8W`#T literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/Uniforms/Jumpskirt/stripeddress.rsi/icon-color.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/stripeddress.rsi/icon-color.png new file mode 100644 index 0000000000000000000000000000000000000000..b820ea97fd29fdff84604489d3a4d901c837e157 GIT binary patch literal 394 zcmV;50d@X~P)c*Gc?{V`Tk%6!1ClmvGs!fmAw2Rh@a+t|atT=1 zb@R>?MUn0A?K$)8ecyXar|dsDr-otJ+O~~m2|Lzv{9$j@*`AYWnzp*GUtQPzg8`iO zy|bs+Y-Zb^0FWQ%>Kt~RUoy5qcup?`x1gXMwwS=K*Ma~q3C0HDWp3@4fYT{6BLi@5 zKV^XXdH&Wqs|}G^S(a`>%d!OTo$wB2y@UX7nkH0L_2F-x=MP(oA(;WaR|Z0WaU27( zr{uE0bGsTvhQfnTvmH5y&h47yD~M4K+Vwhgyqk?ICjE>wD+>x=0M;7N{epFVNQ#wy z+N|cDRJ20|G@DrV1Uk#YVGU??quL3?`_?(uA+{?RWK16hz?Nbgf&~K6A?FA-_c2O- o2MESMI9JVJ)%J`Z25!&5FSzMTjc7X^jsO4v07*qoM6N<$f{|vg8vp@Vn;*gA+z2Jd+qsHeYFc;_xdWEx@IjhnZD-BU{rIMR#$< z(;wR$5_A98Pv}1vzvD!~%Tv5;0`h4VRU8dR96u?PDtuL_Rj^ZJ7nq`cT_EXeqKaCL zUX$di<%rE7e2=83tSvLIc0@JbrGvH^meiPM*k`y!pk=~3iTa>atChkrm2W;Cewfg9_+fH}jofaf zwC%TNUd%}O_IvM4AGNsIim`L&e_QlNzf^(Y=dB-)D$e~CtXaOgGh)B#)OFXho3Ado zXi^&bb8#70!vF4taqH6y7RKs{C--;yT)jJOduiy|ld{3ljCb?4^X$6+e)>P3<-wb8 z=A8OIgMI$G89P@RWifnM`sTnxj(h&ggE`f%S9RIW$5eTf7b*v& zIPY@td%bQG(}wecAAZ)<)#Nrd+C{QTbZ6SdHXmFtQ8^$~vu@9uzuFFInLMu(sK zBXMJjBBPK34z$8w!CW-#{-!YxtIDToG6?m#Op>W0AwpERLYpf>**CR9t}e z7BSqg^w`L2yYkyrh66iNno>;{tY7(TwW5QY)zN*o#7?YbKfx%nO7TE@hb`09FovKT z23o5Xw#g~5H>__{ST3h%nJ-@?A#m%|^V_B@A6EO>I%%w{x2|Ik{;jXE*lDUFFhw$W My85}Sb4q9e0GwYl-2eap literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/Uniforms/Jumpskirt/stripeddress.rsi/inhand-right.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/stripeddress.rsi/inhand-right.png new file mode 100644 index 0000000000000000000000000000000000000000..af99737e9934f10b43749d2aa8ab9e856c74dc0c GIT binary patch literal 634 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7T#lEU~=?yaSW+od^^iFkI7J=)pttY zG_FkT2Rt28Z+m`T+bw-G znMay)0t0pw!?`AG^)LGcKR8q)>$g7s_+n>*`)3(e>3yQ=8F6~kjgEPT$gn9bBxMf-Y> z+v3&vn{UoIn4s`%QQzLU^C=g5rZ2dY>(lYidXGe~>HW0~zr!BsJxh)nY_%$ObNv=4ClzPjaQ$zcdF{cG87dA; z*wKfMhHuwy_#gTHPgjn);jiP_S&b2YX53p-cGIk#qde)xg_!DdpWQzeALBfydT`_G zRe{DIH0lBh7G1n8QFuK2I#0`^-BR^$V|J$<=$o(oa=G>az9q570$us9tC!yYp}ip1 z_`$Jy)teV)9Mg8#ou(jWKP9AVts%qPS&adIrnn?uH>+a#pm}MHB*)Qy)hSu|TbQ}qWZ#-2>(|C!j6;Lvrg zwG6Zjv<$Qiv<$Qiv<&>G8F-4uz2EPTtJUh;c{zR5|Cs%DyFKP?Hk-{iUO8Lof2iY; zfFBNr?&IU*f&^pq*T{MNkJ-ro%gakQo6R~aIakNW{Mh5}j1lB~KJRwBT@Oyckg#&S zUU#3LpWR}yaQn4nuJwhFe|TJz5q#GSV0w!jSzj)feeloE&%I=fUd`G|cZ-bri`f74 z5M#}!P6?hXVH;QF{r$Zgsu9*M+PC=lPjT^wUNVC3ngKY*>D${|50d-hcmzD>XGm}H zm1yh;oH4Bg-<^OS;q;|UYb=@5>v#>Rz;E&GS^^e;qWAjxdQtpT46R+QKKQhpyelrZ z6{>H#{>MPD=e&~JlJ)%v{Lt+RLR{@!9_{I zdj{C)EFvOE0o0mAS7R1Tjo+yb98Zn)O(R$waJZ)*G?09^8Y*JYF zbAV2~mV_Gjf8k0_jV~TewhXikv<$Qiv<$Qiv<$Qiv<$Qi{M#7_{}>`%evoKmH1a04 zMyl8k(57t3kFn6UxD7zNz!1=um_1&*&t{0;5sFJ~%-%m&pGM&HwO@$tAp}k!!q>)& zc7?9}RL*e=EdgC^-VLhpqJ+1y{67F$G7aU+BMDMiNc&Qj|0yslBFoaf1PC55K6(X? z4${;EEBisEuM~?5pn$b?2QTR=(fD$!=?|c$WmUkFbBje^L6_pX-V5ZMtN3UdO6KhE zp%;dvIF$N!FM%Nd);m`K60E&j$qZqnx)iH+&AGt0Q@*6)UO2ns04^+tk|hzk3yiHd zu1@VMLa-`?xg4rU=Z;%_cnM7T{{V>V33TY6VtO#L0gmH7E>I+mR?*vK3z6)C;)*WaiFpKvUfRm8Yy_p;gMo%ERJ3d z?&kmiy#Ehy#uPT=>5c&5K@Cu_8e5KG=VK=TMpLG(w0inuzDkeFH`p}G0iV*#t`z6$ zc|G#!OGXa?71(bn4oU!O=@Qf1hg4qgdJ-D;wqi?PD*px!{Vvd0P#hn#DNO?BdHj29 vZs9OUqB>~8cnEJumz>syEdwnB-!t$Fj9s(lP5sOi00000NkvXXu0mjf1yK$f literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/Uniforms/Jumpskirt/turtledress.rsi/equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/turtledress.rsi/equipped-INNERCLOTHING.png new file mode 100644 index 0000000000000000000000000000000000000000..71ea0285e9e3e65f32141ae9fa763e49093fe77f GIT binary patch literal 713 zcmV;)0yh1LP)I0r5%{ZB0(`s=tIVaguhC0x3yo*u(D=6S^D~I) z{B(Qmb@oXd{%u?aoc<~CdnD~MVtVfk`q}2zAmgQhI4F*@^*S77r{@=qa{A!Xr)zxj z&?p6#RRMQBz{gzEn^dTNo#K}j%jZ6datVeOS`^nce$J_?*fN4e#e^Q~4BwsNWyO-9 z>mOP$7d(51N3+Eb7e&tx_p=33 zZ*AK(7eg-O=XQ~+;w!%oa0Bgo zciRti{TBUu%4OP=O^VH_2Bo2LTdnUH(%@icXVXhFs(lS`elLI`FctSt&Z2VGiGgC# ziYFeiEbm1(rzx-2804yx}x??AZ92ePWUWbGaOOJbEM$ftk=g36g;mY%*F5 ziwzfYBUcmF&aMTtM39}JB+(Ay?<~wsELtrW%aQizk-$vk65w%yyZ*GtjLb<4Hu_8+s1NNa00000NkvXXu0mjfn5Ilj literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/Uniforms/Jumpskirt/turtledress.rsi/icon-color.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/turtledress.rsi/icon-color.png new file mode 100644 index 0000000000000000000000000000000000000000..4673c4fd344fb44a8051628a7c9a92c89ba55786 GIT binary patch literal 405 zcmV;G0c!q;esjbH(#K zZ)uu}uIogSBtJk^mt`rIWf57HIp;Ohq`N>b3V=}42QB;Y9X>rvq=$ekz@$UKoqGTc zHjbn98Tjtoh|UENd7363clyY`3o;nqV~B1ahQZi`hWv3Zj8({gWxu$LrPsJ1Ax7^= zz>e=m^!fz=&+}YhAE<))4-sbuWI9<00nvbXJ;j#=-m_{h7kTO60&MD#ezAh89|^N`suqI5Z&)fDdOd+ zxuhs0JWJ{lqZZ@52YAs|#4Ynr;730U+{M5bR=|6Obrcnw00000NkvXXu0mjfsx_~^ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/Uniforms/Jumpskirt/turtledress.rsi/icon.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/turtledress.rsi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..cb2f5f98f9e342537d1cfcc793b040d3e6c8976a GIT binary patch literal 329 zcmV-P0k-~$P)coa_|cnCqz-c~KFG!X>F6GUF%g|H;cY%0OoW&`s+ zvoG0+F}gGXO+XXS1b#h%*J+=opL^=lC-C4&I1LvFBJFUPLAL9aNRT-2BuzA34Ll#Q zt6W)oC)cc2m!AtW{_}1lIz5TNOJ74kl4M7`gD5oJUS#-cz{81g+FP8M`C{p%De+az z?<8Q)170)VkF?f_s?2!rA!;{R%g9A?nN>CD(i7M0h^x%lpp6$Bl>s3NL&RH*m}W%| zF~FDAi&#F3hX%wsa7L49K72ToF2_SYpn_Iktn2(4u}ka_?TlvurX834jJl6Gb#&JR b{yTv$n=iOHXN-pb00000NkvXXu0mjf^<#?n literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/Uniforms/Jumpskirt/turtledress.rsi/inhand-left.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/turtledress.rsi/inhand-left.png new file mode 100644 index 0000000000000000000000000000000000000000..bd61c158f51cb30734a8ae3e9705e8a9a5be9130 GIT binary patch literal 675 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7T#lEV9N4zaSW+od>eJK_mBaP?(*Mm zNeo9A4zT$!d^Td#KEQB*!9(Z3$5o#bS6%597Js=UUGut1*}S*YO>8Hc@V4?yU^H@I zJHa5Sz?{;6$`LW%aR0sh(PnFA<&9Ps^3Sf{9_Gr-s4v#d)pGrU#~SghUEk)u`}S3? z|M>9|D__2*ysc5blTNuTZ=Gte{@toL#{T7Z^0q&#+rRyw0%Ipv>5ByA+H1uer=_A- ztyW{`GoS5SY&ZXZlEb6vr=K?3y|772tSl(XUClY;50{KmV(f+GUo)Z?<(l%nO7=V< zx1jm;ZSF0;dA35U&!>OoV%YF63@mQrEv$gORz*&oZn=pn%MgWxJDVdPAEh3!CR+4J=qN{*dQ+ai7;) zneS)ho-37SEmY(Sef`C+exO+1fqh=I{4QsMZS}@} zI^KepH#lkj_|R(2F{4oAiua%PwGD?KUbx3}G5wEdtHPdEDTdps;~wsok~U-!JYdUf3n`Kau5ugv|VBq(r$@!h>wT9M=CcdASZx3HR_2%2MW6PqIW{Sw~o3WN5 zuY70R`ut9>OSSiz4_Qju5jOo*67#^<;`egN>>#o@6$ED`if)Bfu##Z0%xf$)X{P911 zL;a&|bsh~Y*iix(3!?{`OlZzs86#?Df`ROzuxuY0PZ)U&#HH zsRYBmtyvKh}D7)%w5wTUFmKc4Mfr zeLj=%ZM;y#m#$Wh?s`QU~4FccoKYvklX3ig`=GT{A*~vC{0h1Jir>mdKI;Vst0J6Xo-v9sr literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/Uniforms/Jumpskirt/turtledress.rsi/meta.json b/Resources/Textures/Clothing/Uniforms/Jumpskirt/turtledress.rsi/meta.json new file mode 100644 index 0000000000..4ff6d9a9f4 --- /dev/null +++ b/Resources/Textures/Clothing/Uniforms/Jumpskirt/turtledress.rsi/meta.json @@ -0,0 +1,33 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Add new dress by DreamlyJack", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "icon-color" + }, + { + "name": "equipped-INNERCLOTHING", + "directions": 4 + }, + { + "name": "color-equipped-INNERCLOTHING", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} From 379013a5da172c55b0ce4fb83b8e3271d2604116 Mon Sep 17 00:00:00 2001 From: deltanedas <39013340+deltanedas@users.noreply.github.com> Date: Sun, 30 Jun 2024 13:51:33 +0000 Subject: [PATCH 13/86] show sleeper agents in round end summary (#29468) fix sleeper agents not showing in summary Co-authored-by: deltanedas <@deltanedas:kde.org> --- Resources/Prototypes/GameRules/events.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml index 3e63ad0283..bfd9dd6230 100644 --- a/Resources/Prototypes/GameRules/events.yml +++ b/Resources/Prototypes/GameRules/events.yml @@ -455,6 +455,7 @@ startAnnouncement: station-event-communication-interception startAudio: path: /Audio/Announcements/intercept.ogg + duration: null # the rule has to last the whole round not 1 second - type: AlertLevelInterceptionRule - type: AntagSelection definitions: From af1acf6e60a84d2276110403b03c148ec82c36ee Mon Sep 17 00:00:00 2001 From: PJBot Date: Sun, 30 Jun 2024 13:52:39 +0000 Subject: [PATCH 14/86] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 2b4b325ddd..1ef3f36e70 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +1,4 @@ Entries: -- author: DrSmugleaf - changes: - - message: Fixed rocket launchers and laser guns looking like they have nothing - loaded. - type: Fix - id: 6347 - time: '2024-04-14T03:17:17.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26933 - author: Vermidia changes: - message: You can now see paper attached on crates. Color differs depending on @@ -3831,3 +3823,10 @@ id: 6846 time: '2024-06-30T13:47:22.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29603 +- author: deltanedas + changes: + - message: Fixed sleeper agents not showing up in the round end summary. + type: Fix + id: 6847 + time: '2024-06-30T13:51:33.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29468 From f6bb10503faaac793a96cef500d6bb3bec434a93 Mon Sep 17 00:00:00 2001 From: Plykiya <58439124+Plykiya@users.noreply.github.com> Date: Sun, 30 Jun 2024 07:32:48 -0700 Subject: [PATCH 15/86] Removes obsolete AnchorEntity() functions (#28613) Obsolete anchor entity functions Co-authored-by: plykiya --- Content.Server/Parallax/BiomeSystem.cs | 2 +- Content.Server/Procedural/DungeonSystem.Rooms.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Content.Server/Parallax/BiomeSystem.cs b/Content.Server/Parallax/BiomeSystem.cs index ec780d1f99..7e854e8bbf 100644 --- a/Content.Server/Parallax/BiomeSystem.cs +++ b/Content.Server/Parallax/BiomeSystem.cs @@ -810,7 +810,7 @@ public sealed partial class BiomeSystem : SharedBiomeSystem // At least for now unless we do lookups or smth, only work with anchoring. if (_xformQuery.TryGetComponent(ent, out var xform) && !xform.Anchored) { - _transform.AnchorEntity(ent, xform, gridUid, grid, indices); + _transform.AnchorEntity((ent, xform), (gridUid, grid), indices); } loadedEntities.Add(ent, indices); diff --git a/Content.Server/Procedural/DungeonSystem.Rooms.cs b/Content.Server/Procedural/DungeonSystem.Rooms.cs index 5b4de34906..ddd4a4732f 100644 --- a/Content.Server/Procedural/DungeonSystem.Rooms.cs +++ b/Content.Server/Procedural/DungeonSystem.Rooms.cs @@ -177,7 +177,7 @@ public sealed partial class DungeonSystem // If the templated entity was anchored then anchor us too. if (anchored && !childXform.Anchored) - _transform.AnchorEntity(ent, childXform, grid); + _transform.AnchorEntity((ent, childXform), (gridUid, grid)); else if (!anchored && childXform.Anchored) _transform.Unanchor(ent, childXform); } From 162913ccd0eca36ef93964d6519da9f044b9b250 Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Sun, 30 Jun 2024 12:20:57 -0400 Subject: [PATCH 16/86] Selectable Bar Signs (#29068) * make bar sign selectable * ajcm strongest soldier * AJCM comes down hard for round 2 * good shit * ok ballin * bless'ed be the webedit --- Content.Client/BarSign/BarSignSystem.cs | 9 +- .../BarSign/Ui/BarSignBoundUserInterface.cs | 50 ++++++++ Content.Client/BarSign/Ui/BarSignMenu.xaml | 19 +++ Content.Client/BarSign/Ui/BarSignMenu.xaml.cs | 50 ++++++++ .../BarSign/Systems/BarSignSystem.cs | 40 ------- Content.Shared/BarSign/BarSignComponent.cs | 19 ++- Content.Shared/BarSign/BarSignPrototype.cs | 30 ++--- Content.Shared/BarSign/BarSignSystem.cs | 58 ++++++++++ .../en-US/barsign/barsign-component.ftl | 2 + .../Structures/Wallmounts/Signs/bar_sign.yml | 12 ++ Resources/Prototypes/bar_signs.yml | 109 +++++++++++------- 11 files changed, 300 insertions(+), 98 deletions(-) create mode 100644 Content.Client/BarSign/Ui/BarSignBoundUserInterface.cs create mode 100644 Content.Client/BarSign/Ui/BarSignMenu.xaml create mode 100644 Content.Client/BarSign/Ui/BarSignMenu.xaml.cs delete mode 100644 Content.Server/BarSign/Systems/BarSignSystem.cs create mode 100644 Content.Shared/BarSign/BarSignSystem.cs diff --git a/Content.Client/BarSign/BarSignSystem.cs b/Content.Client/BarSign/BarSignSystem.cs index 05db00d819..9fd8ba2e4b 100644 --- a/Content.Client/BarSign/BarSignSystem.cs +++ b/Content.Client/BarSign/BarSignSystem.cs @@ -1,3 +1,4 @@ +using Content.Client.BarSign.Ui; using Content.Shared.BarSign; using Content.Shared.Power; using Robust.Client.GameObjects; @@ -8,6 +9,7 @@ namespace Content.Client.BarSign; public sealed class BarSignSystem : VisualizerSystem { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly UserInterfaceSystem _ui = default!; public override void Initialize() { @@ -17,6 +19,9 @@ public sealed class BarSignSystem : VisualizerSystem private void OnAfterAutoHandleState(EntityUid uid, BarSignComponent component, ref AfterAutoHandleStateEvent args) { + if (_ui.TryGetOpenUi(uid, BarSignUiKey.Key, out var bui)) + bui.Update(component.Current); + UpdateAppearance(uid, component); } @@ -34,9 +39,9 @@ public sealed class BarSignSystem : VisualizerSystem if (powered && sign.Current != null - && _prototypeManager.TryIndex(sign.Current, out BarSignPrototype? proto)) + && _prototypeManager.TryIndex(sign.Current, out var proto)) { - sprite.LayerSetState(0, proto.Icon); + sprite.LayerSetSprite(0, proto.Icon); sprite.LayerSetShader(0, "unshaded"); } else diff --git a/Content.Client/BarSign/Ui/BarSignBoundUserInterface.cs b/Content.Client/BarSign/Ui/BarSignBoundUserInterface.cs new file mode 100644 index 0000000000..1d1280b2f3 --- /dev/null +++ b/Content.Client/BarSign/Ui/BarSignBoundUserInterface.cs @@ -0,0 +1,50 @@ +using System.Linq; +using Content.Shared.BarSign; +using JetBrains.Annotations; +using Robust.Shared.Prototypes; + +namespace Content.Client.BarSign.Ui; + +[UsedImplicitly] +public sealed class BarSignBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey) +{ + [Dependency] private readonly IPrototypeManager _prototype = default!; + + private BarSignMenu? _menu; + + protected override void Open() + { + base.Open(); + + var sign = EntMan.GetComponentOrNull(Owner)?.Current is { } current + ? _prototype.Index(current) + : null; + var allSigns = Shared.BarSign.BarSignSystem.GetAllBarSigns(_prototype) + .OrderBy(p => Loc.GetString(p.Name)) + .ToList(); + _menu = new(sign, allSigns); + + _menu.OnSignSelected += id => + { + SendMessage(new SetBarSignMessage(id)); + }; + + _menu.OnClose += Close; + _menu.OpenCentered(); + } + + public void Update(ProtoId? sign) + { + if (_prototype.TryIndex(sign, out var signPrototype)) + _menu?.UpdateState(signPrototype); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (!disposing) + return; + _menu?.Dispose(); + } +} + diff --git a/Content.Client/BarSign/Ui/BarSignMenu.xaml b/Content.Client/BarSign/Ui/BarSignMenu.xaml new file mode 100644 index 0000000000..5b1155ae35 --- /dev/null +++ b/Content.Client/BarSign/Ui/BarSignMenu.xaml @@ -0,0 +1,19 @@ + + + + + + + + + + + diff --git a/Content.Client/BarSign/Ui/BarSignMenu.xaml.cs b/Content.Client/BarSign/Ui/BarSignMenu.xaml.cs new file mode 100644 index 0000000000..a9333339b7 --- /dev/null +++ b/Content.Client/BarSign/Ui/BarSignMenu.xaml.cs @@ -0,0 +1,50 @@ +using Content.Client.UserInterface.Controls; +using Content.Shared.BarSign; +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface.XAML; + +namespace Content.Client.BarSign.Ui; + +[GenerateTypedNameReferences] +public sealed partial class BarSignMenu : FancyWindow +{ + private string? _currentId; + + private readonly List _cachedPrototypes = new(); + + public event Action? OnSignSelected; + + public BarSignMenu(BarSignPrototype? currentSign, List signs) + { + RobustXamlLoader.Load(this); + _currentId = currentSign?.ID; + + _cachedPrototypes.Clear(); + _cachedPrototypes = signs; + foreach (var proto in _cachedPrototypes) + { + SignOptions.AddItem(Loc.GetString(proto.Name)); + } + + SignOptions.OnItemSelected += idx => + { + OnSignSelected?.Invoke(_cachedPrototypes[idx.Id].ID); + SignOptions.SelectId(idx.Id); + }; + + if (currentSign != null) + { + var idx = _cachedPrototypes.IndexOf(currentSign); + SignOptions.TrySelectId(idx); + } + } + + public void UpdateState(BarSignPrototype newSign) + { + if (_currentId != null && newSign.ID == _currentId) + return; + _currentId = newSign.ID; + var idx = _cachedPrototypes.IndexOf(newSign); + SignOptions.TrySelectId(idx); + } +} diff --git a/Content.Server/BarSign/Systems/BarSignSystem.cs b/Content.Server/BarSign/Systems/BarSignSystem.cs deleted file mode 100644 index e42394f5a3..0000000000 --- a/Content.Server/BarSign/Systems/BarSignSystem.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Linq; -using Content.Shared.BarSign; -using Robust.Shared.Prototypes; -using Robust.Shared.Random; - -namespace Content.Server.BarSign.Systems -{ - public sealed class BarSignSystem : EntitySystem - { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly MetaDataSystem _metaData = default!; - - public override void Initialize() - { - SubscribeLocalEvent(OnMapInit); - } - - private void OnMapInit(EntityUid uid, BarSignComponent component, MapInitEvent args) - { - if (component.Current != null) - return; - - var prototypes = _prototypeManager - .EnumeratePrototypes() - .Where(p => !p.Hidden) - .ToList(); - - var newPrototype = _random.Pick(prototypes); - - var meta = Comp(uid); - var name = newPrototype.Name != string.Empty ? newPrototype.Name : "barsign-component-name"; - _metaData.SetEntityName(uid, Loc.GetString(name), meta); - _metaData.SetEntityDescription(uid, Loc.GetString(newPrototype.Description), meta); - - component.Current = newPrototype.ID; - Dirty(uid, component); - } - } -} diff --git a/Content.Shared/BarSign/BarSignComponent.cs b/Content.Shared/BarSign/BarSignComponent.cs index d50726216e..98c6e815cc 100644 --- a/Content.Shared/BarSign/BarSignComponent.cs +++ b/Content.Shared/BarSign/BarSignComponent.cs @@ -1,10 +1,27 @@ using Robust.Shared.GameStates; using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; namespace Content.Shared.BarSign; [RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)] public sealed partial class BarSignComponent : Component { - [DataField, AutoNetworkedField] public ProtoId? Current; + /// + /// The current bar sign prototype being displayed. + /// + [DataField, AutoNetworkedField] + public ProtoId? Current; +} + +[Serializable, NetSerializable] +public enum BarSignUiKey : byte +{ + Key +} + +[Serializable, NetSerializable] +public sealed class SetBarSignMessage(ProtoId sign) : BoundUserInterfaceMessage +{ + public ProtoId Sign = sign; } diff --git a/Content.Shared/BarSign/BarSignPrototype.cs b/Content.Shared/BarSign/BarSignPrototype.cs index a0566d9f46..d63ebda4a7 100644 --- a/Content.Shared/BarSign/BarSignPrototype.cs +++ b/Content.Shared/BarSign/BarSignPrototype.cs @@ -1,23 +1,23 @@ using Robust.Shared.Prototypes; +using Robust.Shared.Utility; -namespace Content.Shared.BarSign +namespace Content.Shared.BarSign; + +[Prototype] +public sealed partial class BarSignPrototype : IPrototype { - [Prototype("barSign")] - public sealed partial class BarSignPrototype : IPrototype - { - [ViewVariables] - [IdDataField] - public string ID { get; private set; } = default!; + [IdDataField, ViewVariables] + public string ID { get; private set; } = default!; + [DataField(required: true)] + public SpriteSpecifier Icon { get; private set; } = default!; - [DataField("icon")] public string Icon { get; private set; } = string.Empty; + [DataField] + public LocId Name { get; private set; } = "barsign-component-name"; - [DataField("name")] public string Name { get; set; } = ""; - [DataField("description")] public string Description { get; set; } = ""; + [DataField] + public LocId Description { get; private set; } - [DataField("renameArea")] - public bool RenameArea { get; private set; } = true; - [DataField("hidden")] - public bool Hidden { get; private set; } - } + [DataField] + public bool Hidden { get; private set; } } diff --git a/Content.Shared/BarSign/BarSignSystem.cs b/Content.Shared/BarSign/BarSignSystem.cs new file mode 100644 index 0000000000..bf28cfe6b7 --- /dev/null +++ b/Content.Shared/BarSign/BarSignSystem.cs @@ -0,0 +1,58 @@ +using System.Linq; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; + +namespace Content.Shared.BarSign; + +public sealed class BarSignSystem : EntitySystem +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly MetaDataSystem _metaData = default!; + + public override void Initialize() + { + SubscribeLocalEvent(OnMapInit); + Subs.BuiEvents(BarSignUiKey.Key, + subs => + { + subs.Event(OnSetBarSignMessage); + }); + } + + private void OnMapInit(Entity ent, ref MapInitEvent args) + { + if (ent.Comp.Current != null) + return; + + var newPrototype = _random.Pick(GetAllBarSigns(_prototypeManager)); + SetBarSign(ent, newPrototype); + } + + private void OnSetBarSignMessage(Entity ent, ref SetBarSignMessage args) + { + if (!_prototypeManager.TryIndex(args.Sign, out var signPrototype)) + return; + + SetBarSign(ent, signPrototype); + } + + public void SetBarSign(Entity ent, BarSignPrototype newPrototype) + { + var meta = MetaData(ent); + var name = Loc.GetString(newPrototype.Name); + _metaData.SetEntityName(ent, name, meta); + _metaData.SetEntityDescription(ent, Loc.GetString(newPrototype.Description), meta); + + ent.Comp.Current = newPrototype.ID; + Dirty(ent); + } + + public static List GetAllBarSigns(IPrototypeManager prototypeManager) + { + return prototypeManager + .EnumeratePrototypes() + .Where(p => !p.Hidden) + .ToList(); + } +} diff --git a/Resources/Locale/en-US/barsign/barsign-component.ftl b/Resources/Locale/en-US/barsign/barsign-component.ftl index 3451d70bc9..cf7ddd1e24 100644 --- a/Resources/Locale/en-US/barsign/barsign-component.ftl +++ b/Resources/Locale/en-US/barsign/barsign-component.ftl @@ -1,4 +1,6 @@ barsign-component-name = bar sign +barsign-ui-menu = Bar Sign Configuration +barsign-ui-set-label = Set Sign: # Bar signs prototypes diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/Signs/bar_sign.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/Signs/bar_sign.yml index 8e957abfe7..b2a0ffe592 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/Signs/bar_sign.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/Signs/bar_sign.yml @@ -9,6 +9,7 @@ Brute: collection: GlassSmash - type: WallMount + arc: 360 - type: Sprite drawdepth: Objects sprite: Structures/Wallmounts/barsign.rsi @@ -16,6 +17,17 @@ - type: ApcPowerReceiver - type: ExtensionCableReceiver - type: BarSign + - type: InteractionOutline + - type: AccessReader + access: [["Bar"]] + - type: ActivatableUIRequiresPower + - type: ActivatableUIRequiresAccess + - type: ActivatableUI + key: enum.BarSignUiKey.Key + - type: UserInterface + interfaces: + enum.BarSignUiKey.Key: + type: BarSignBoundUserInterface - type: Appearance - type: Destructible thresholds: diff --git a/Resources/Prototypes/bar_signs.yml b/Resources/Prototypes/bar_signs.yml index 8c6476831b..e3f9d30a42 100644 --- a/Resources/Prototypes/bar_signs.yml +++ b/Resources/Prototypes/bar_signs.yml @@ -1,154 +1,183 @@ - type: barSign id: Harmbaton name: barsign-prototype-name-harmbaton - icon: "theharmbaton" + icon: + sprite: Structures/Wallmounts/barsign.rsi + state: theharmbaton description: barsign-prototype-description-harmbaton - type: barSign id: TheSingulo name: barsign-prototype-name-singulo - icon: "thesingulo" + icon: + sprite: Structures/Wallmounts/barsign.rsi + state: thesingulo description: barsign-prototype-description-singulo - type: barSign id: TheDrunkCarp name: barsign-prototype-name-drunk-carp - icon: "thedrunkcarp" + icon: + sprite: Structures/Wallmounts/barsign.rsi + state: thedrunkcarp description: barsign-prototype-description-drunk-carp - type: barSign id: OfficerBeersky name: barsign-prototype-name-officer-beersky - icon: "officerbeersky" + icon: + sprite: Structures/Wallmounts/barsign.rsi + state: officerbeersky description: barsign-prototype-description-officer-beersky - type: barSign id: TheOuterSpess name: barsign-prototype-name-outer-spess - icon: "theouterspess" + icon: + sprite: Structures/Wallmounts/barsign.rsi + state: theouterspess description: barsign-prototype-description-outer-spess - type: barSign id: TheCoderbus name: barsign-prototype-name-coderbus - icon: "thecoderbus" + icon: + sprite: Structures/Wallmounts/barsign.rsi + state: thecoderbus description: barsign-prototype-description-coderbus - type: barSign id: RobustaCafe name: barsign-prototype-name-robusta-cafe - icon: "robustacafe" + icon: + sprite: Structures/Wallmounts/barsign.rsi + state: robustacafe description: barsign-prototype-description-robusta-cafe - type: barSign id: EmergencyRumParty name: barsign-prototype-name-emergency-rum-party - icon: "emergencyrumparty" + icon: + sprite: Structures/Wallmounts/barsign.rsi + state: emergencyrumparty description: barsign-prototype-description-emergency-rum-party - type: barSign id: ComboCafe name: barsign-prototype-name-combo-cafe - icon: "combocafe" + icon: + sprite: Structures/Wallmounts/barsign.rsi + state: combocafe description: barsign-prototype-description-combo-cafe - type: barSign id: TheAleNath name: barsign-prototype-name-ale-nath - icon: "thealenath" + icon: + sprite: Structures/Wallmounts/barsign.rsi + state: thealenath description: barsign-prototype-description-ale-nath - type: barSign id: TheNet name: barsign-prototype-name-the-net - icon: "thenet" + icon: + sprite: Structures/Wallmounts/barsign.rsi + state: thenet description: barsign-prototype-description-the-net - type: barSign id: MaidCafe name: barsign-prototype-name-maid-cafe - icon: "maidcafe" + icon: + sprite: Structures/Wallmounts/barsign.rsi + state: maidcafe description: barsign-prototype-description-maid-cafe - type: barSign id: MalteseFalcon name: barsign-prototype-name-maltese-falcon - icon: "maltesefalcon" + icon: + sprite: Structures/Wallmounts/barsign.rsi + state: maltesefalcon description: barsign-prototype-description-maltese-falcon - type: barSign id: TheSun name: barsign-prototype-name-the-sun - icon: "thesun" + icon: + sprite: Structures/Wallmounts/barsign.rsi + state: thesun description: barsign-prototype-description-the-sun - type: barSign id: TheBirdCage name: barsign-prototype-name-the-birdcage - icon: "birdcage" + icon: + sprite: Structures/Wallmounts/barsign.rsi + state: birdcage description: barsign-prototype-description-the-birdcage - type: barSign id: Zocalo name: barsign-prototype-name-zocalo - icon: "zocalo" + icon: + sprite: Structures/Wallmounts/barsign.rsi + state: zocalo description: barsign-prototype-description-zocalo - type: barSign id: LV426 name: barsign-prototype-name-lv426 - icon: "lv426" + icon: + sprite: Structures/Wallmounts/barsign.rsi + state: lv426 description: barsign-prototype-description-lv426 - type: barSign id: WiggleRoom name: barsign-prototype-name-wiggle-room - icon: "thewiggleroom" + icon: + sprite: Structures/Wallmounts/barsign.rsi + state: thewiggleroom description: barsign-prototype-description-wiggle-room - type: barSign id: TheLightbulb name: barsign-prototype-name-the-lightbulb - icon: "the_lightbulb" + icon: + sprite: Structures/Wallmounts/barsign.rsi + state: the_lightbulb description: barsign-prototype-description-the-lightbulb - type: barSign id: Goose name: barsign-prototype-name-goose - icon: "goose" + icon: + sprite: Structures/Wallmounts/barsign.rsi + state: goose description: barsign-prototype-description-goose - type: barSign id: EngineChange name: barsign-prototype-name-enginechange - icon: "enginechange" + icon: + sprite: Structures/Wallmounts/barsign.rsi + state: enginechange description: barsign-prototype-description-enginechange - type: barSign id: Emprah name: barsign-prototype-name-emprah - icon: "emprah" + icon: + sprite: Structures/Wallmounts/barsign.rsi + state: emprah description: barsign-prototype-description-emprah - type: barSign id: Spacebucks name: barsign-prototype-name-spacebucks - icon: "spacebucks" + icon: + sprite: Structures/Wallmounts/barsign.rsi + state: spacebucks description: barsign-prototype-description-spacebucks - -# Hidden signs list below this point -- type: barSign - id: EmpBarSign - name: "" - icon: "empbarsign" - description: barsign-prototype-description-empbarsign - renameArea: false - hidden: true - -- type: barSign - id: SignOff - name: "" - icon: "empty" - description: barsign-prototype-description-sign-off - renameArea: false - hidden: true From a02992581890484c300f6031812d3dd0dc6570a5 Mon Sep 17 00:00:00 2001 From: PJBot Date: Sun, 30 Jun 2024 16:22:04 +0000 Subject: [PATCH 17/86] Automatic changelog update --- Resources/Changelog/Changelog.yml | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 1ef3f36e70..7e6a5d009f 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,17 +1,4 @@ Entries: -- author: Vermidia - changes: - - message: You can now see paper attached on crates. Color differs depending on - the paper used. - type: Add - - message: The labels on bodybags also have different colors depending on the paper - used. - type: Tweak - - message: Cargo Invoices are now blueish (to differentiate them from Cargo Bounties) - type: Tweak - id: 6348 - time: '2024-04-14T03:39:02.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26834 - author: Terraspark4941 changes: - message: Autism pins have been added to maintenance loot! @@ -3830,3 +3817,10 @@ id: 6847 time: '2024-06-30T13:51:33.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29468 +- author: EmoGarbage404 + changes: + - message: The design on the bar sign can now be changed via a menu. + type: Add + id: 6848 + time: '2024-06-30T16:20:58.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29068 From 7140d01336a0e28f50eebf3df2d6abd4f46c6886 Mon Sep 17 00:00:00 2001 From: Hmeister-real <118129069+Hmeister-real@users.noreply.github.com> Date: Sun, 30 Jun 2024 18:56:31 +0100 Subject: [PATCH 18/86] CE hardsuit is fireproof again (#29516) * helm fireproofing * suit fireproofing * Update hardsuit-helmets.yml * Update hardsuits.yml --- .../Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml | 2 ++ .../Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml b/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml index 398c383406..7f0c057300 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml @@ -260,6 +260,8 @@ - type: PressureProtection highPressureMultiplier: 0.08 lowPressureMultiplier: 1000 + - type: FireProtection + reduction: 0.2 #Chief Medical Officer's Hardsuit - type: entity diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml index e88b184708..73020898c3 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml @@ -319,6 +319,8 @@ - type: PressureProtection highPressureMultiplier: 0.02 lowPressureMultiplier: 1000 + - type: FireProtection + reduction: 0.8 - type: ExplosionResistance damageCoefficient: 0.2 - type: Armor From a3db80af0413a1bd8572967a9fa9162eb2d227ff Mon Sep 17 00:00:00 2001 From: PJBot Date: Sun, 30 Jun 2024 17:57:37 +0000 Subject: [PATCH 19/86] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 7e6a5d009f..8a849d31a6 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Terraspark4941 - changes: - - message: Autism pins have been added to maintenance loot! - type: Add - id: 6349 - time: '2024-04-14T05:35:35.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25597 - author: Flareguy changes: - message: Colored jumpsuits, shoes, and (most) colored gloves are now colorized @@ -3824,3 +3817,10 @@ id: 6848 time: '2024-06-30T16:20:58.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29068 +- author: Hmeister + changes: + - message: The CE hardsuit is now fireproof again. + type: Tweak + id: 6849 + time: '2024-06-30T17:56:31.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29516 From 40394886546276a30596ddc992551182a3881511 Mon Sep 17 00:00:00 2001 From: TurboTracker <130304754+TurboTrackerss14@users.noreply.github.com> Date: Mon, 1 Jul 2024 04:01:53 +0100 Subject: [PATCH 20/86] comment out Exterminator reference in Ion Laws (#29628) 1984'ed via pull/26978 but was likely missed. --- Resources/Prototypes/Datasets/ion_storm.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Prototypes/Datasets/ion_storm.yml b/Resources/Prototypes/Datasets/ion_storm.yml index 0a1439898a..df96274783 100644 --- a/Resources/Prototypes/Datasets/ion_storm.yml +++ b/Resources/Prototypes/Datasets/ion_storm.yml @@ -628,7 +628,7 @@ - ENGINES - EQUIPMENT - ERRORS - - EXTERMINATORS +# - EXTERMINATORS - EXPLOSIVES - EYEWEAR - FEDORAS From 84282ca0162f812d600e5279d618dc835e2a5531 Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Sun, 30 Jun 2024 23:06:28 -0400 Subject: [PATCH 21/86] Remove blurry vision examine mispredict (#29633) remove blurry vision examine mispredict --- Content.Client/Examine/ExamineSystem.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Content.Client/Examine/ExamineSystem.cs b/Content.Client/Examine/ExamineSystem.cs index 60d2b6a6ef..a941f0acff 100644 --- a/Content.Client/Examine/ExamineSystem.cs +++ b/Content.Client/Examine/ExamineSystem.cs @@ -1,5 +1,4 @@ using Content.Client.Verbs; -using Content.Shared.Eye.Blinding; using Content.Shared.Examine; using Content.Shared.IdentityManagement; using Content.Shared.Input; @@ -16,8 +15,6 @@ using Robust.Shared.Utility; using System.Linq; using System.Numerics; using System.Threading; -using Content.Shared.Eye.Blinding.Components; -using Robust.Client; using static Content.Shared.Interaction.SharedInteractionSystem; using static Robust.Client.UserInterface.Controls.BoxContainer; using Content.Shared.Interaction.Events; @@ -360,10 +357,7 @@ namespace Content.Client.Examine FormattedMessage message; - // Basically this just predicts that we can't make out the entity if we have poor vision. - var canSeeClearly = !HasComp(playerEnt); - - OpenTooltip(playerEnt.Value, entity, centeredOnCursor, false, knowTarget: canSeeClearly); + OpenTooltip(playerEnt.Value, entity, centeredOnCursor, false); // Always update tooltip info from client first. // If we get it wrong, server will correct us later anyway. From 195a1c181094ede51a7607447779fec756520705 Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 1 Jul 2024 03:07:34 +0000 Subject: [PATCH 22/86] Automatic changelog update --- Resources/Changelog/Changelog.yml | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 8a849d31a6..102a6b7eb0 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,13 +1,4 @@ Entries: -- author: Flareguy - changes: - - message: Colored jumpsuits, shoes, and (most) colored gloves are now colorized - off of one set of greyscale sprites. They should now be more consistent & significantly - less ugly. - type: Tweak - id: 6350 - time: '2024-04-14T08:41:39.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26943 - author: BramvanZijp changes: - message: Nanotrasen's Small Arms Division has slightly improved the firerate and @@ -3824,3 +3815,11 @@ id: 6849 time: '2024-06-30T17:56:31.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29516 +- author: EmoGarbage404 + changes: + - message: Blurry vision no longer causes names to momentarily appear as "???" when + examining. + type: Remove + id: 6850 + time: '2024-07-01T03:06:28.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29633 From 7555952738c07c8b8c4cd98d87a25d6393ab26eb Mon Sep 17 00:00:00 2001 From: Cojoke <83733158+Cojoke-dot@users.noreply.github.com> Date: Mon, 1 Jul 2024 00:33:08 -0500 Subject: [PATCH 23/86] Add Slowdown to Dragging Items that Slow when Held (#29364) * Add slowdown to dragging Items that slow when held * Heh, fancy * Heh, fancy * rename SetMovementSpeedModifiers to GetHeldMovementSpeedModifiers because it was not setting anything --- Content.Shared/Item/HeldSpeedModifierSystem.cs | 8 +++++++- .../Movement/Pulling/Systems/PullingSystem.cs | 11 +++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Content.Shared/Item/HeldSpeedModifierSystem.cs b/Content.Shared/Item/HeldSpeedModifierSystem.cs index d7afa8f40f..c3286191b9 100644 --- a/Content.Shared/Item/HeldSpeedModifierSystem.cs +++ b/Content.Shared/Item/HeldSpeedModifierSystem.cs @@ -29,7 +29,7 @@ public sealed class HeldSpeedModifierSystem : EntitySystem _movementSpeedModifier.RefreshMovementSpeedModifiers(args.User); } - private void OnRefreshMovementSpeedModifiers(EntityUid uid, HeldSpeedModifierComponent component, HeldRelayedEvent args) + public (float,float) GetHeldMovementSpeedModifiers(EntityUid uid, HeldSpeedModifierComponent component) { var walkMod = component.WalkModifier; var sprintMod = component.SprintModifier; @@ -39,6 +39,12 @@ public sealed class HeldSpeedModifierSystem : EntitySystem sprintMod = clothingSpeedModifier.SprintModifier; } + return (walkMod, sprintMod); + } + + private void OnRefreshMovementSpeedModifiers(EntityUid uid, HeldSpeedModifierComponent component, HeldRelayedEvent args) + { + var (walkMod, sprintMod) = GetHeldMovementSpeedModifiers(uid, component); args.Args.ModifySpeed(walkMod, sprintMod); } } diff --git a/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs b/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs index eb2872df9c..edc8ad5161 100644 --- a/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs +++ b/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs @@ -8,6 +8,7 @@ using Content.Shared.Hands; using Content.Shared.Hands.EntitySystems; using Content.Shared.Input; using Content.Shared.Interaction; +using Content.Shared.Item; using Content.Shared.Movement.Events; using Content.Shared.Movement.Pulling.Components; using Content.Shared.Movement.Pulling.Events; @@ -43,6 +44,7 @@ public sealed class PullingSystem : EntitySystem [Dependency] private readonly SharedInteractionSystem _interaction = default!; [Dependency] private readonly SharedPhysicsSystem _physics = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly HeldSpeedModifierSystem _clothingMoveSpeed = default!; public override void Initialize() { @@ -192,6 +194,14 @@ public sealed class PullingSystem : EntitySystem private void OnRefreshMovespeed(EntityUid uid, PullerComponent component, RefreshMovementSpeedModifiersEvent args) { + if (TryComp(component.Pulling, out var heldMoveSpeed) && component.Pulling.HasValue) + { + var (walkMod, sprintMod) = + _clothingMoveSpeed.GetHeldMovementSpeedModifiers(component.Pulling.Value, heldMoveSpeed); + args.ModifySpeed(walkMod, sprintMod); + return; + } + args.ModifySpeed(component.WalkSpeedModifier, component.SprintSpeedModifier); } @@ -459,6 +469,7 @@ public sealed class PullingSystem : EntitySystem // Messaging var message = new PullStartedMessage(pullerUid, pullableUid); + _modifierSystem.RefreshMovementSpeedModifiers(pullerUid); _alertsSystem.ShowAlert(pullerUid, pullerComp.PullingAlert); _alertsSystem.ShowAlert(pullableUid, pullableComp.PulledAlert); From a5ccf97a64485be89a10c563dd4b3902ddf1f5f9 Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 1 Jul 2024 05:34:15 +0000 Subject: [PATCH 24/86] Automatic changelog update --- Resources/Changelog/Changelog.yml | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 102a6b7eb0..adb7665029 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,15 +1,4 @@ Entries: -- author: BramvanZijp - changes: - - message: Nanotrasen's Small Arms Division has slightly improved the firerate and - drastically improved the accuracy on its WT550 SMGs. - type: Tweak - - message: The WT550 and C-20R SMGs can now fire in a 5 round burst-fire mode, making - it easier for their users to control the recoil on these weapons. - type: Add - id: 6351 - time: '2024-04-14T08:51:07.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26886 - author: Dutch-VanDerLinde changes: - message: Holoparasites and holoclowns will now phase through projectiles such @@ -3823,3 +3812,10 @@ id: 6850 time: '2024-07-01T03:06:28.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29633 +- author: Cojoke-dot + changes: + - message: Items that slow down a person when held now slow down when dragged too. + type: Tweak + id: 6851 + time: '2024-07-01T05:33:08.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29364 From 5543689c144a9e48d9ef290f8c25350ac934999a Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Mon, 1 Jul 2024 16:09:41 +1000 Subject: [PATCH 25/86] Update submodule to 227.0.0 (#29634) --- RobustToolbox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RobustToolbox b/RobustToolbox index 860c9af2bf..a9aea7027f 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit 860c9af2bfbf1477b96519067ef5e62b2525d987 +Subproject commit a9aea7027f1840c83bcaf1c973caf099745f9eed From b6cf2ce5243d66f5b79f3b3d1b7bf327b1e1761c Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Mon, 1 Jul 2024 16:11:30 +1000 Subject: [PATCH 26/86] Add FTL arrival visuals (#29402) * Add FTL arrival visuals * weh * Update Content.Shared/Shuttles/Components/FTLComponent.cs Co-authored-by: Tayrtahn --------- Co-authored-by: Tayrtahn --- Content.Client/Shuttles/FtlArrivalOverlay.cs | 82 ++++++++++++++++++ .../Systems/ShuttleSystem.EmergencyConsole.cs | 3 +- .../Shuttles/Systems/ShuttleSystem.cs | 21 +++++ .../Systems/ShuttleSystem.FasterThanLight.cs | 20 ++++- .../Shuttles/Systems/ShuttleSystem.cs | 2 + .../Shuttles/Components/FTLComponent.cs | 16 ++-- .../Components/FtlVisualizerComponent.cs | 23 +++++ .../Prototypes/Entities/Effects/shuttle.yml | 12 +++ .../Effects/medi_holo.rsi/medi_holo.png | Bin 0 -> 1286 bytes .../Textures/Effects/medi_holo.rsi/meta.json | 26 ++++++ 10 files changed, 196 insertions(+), 9 deletions(-) create mode 100644 Content.Client/Shuttles/FtlArrivalOverlay.cs create mode 100644 Content.Client/Shuttles/Systems/ShuttleSystem.cs rename {Content.Server => Content.Shared}/Shuttles/Components/FTLComponent.cs (81%) create mode 100644 Content.Shared/Shuttles/Components/FtlVisualizerComponent.cs create mode 100644 Resources/Prototypes/Entities/Effects/shuttle.yml create mode 100644 Resources/Textures/Effects/medi_holo.rsi/medi_holo.png create mode 100644 Resources/Textures/Effects/medi_holo.rsi/meta.json diff --git a/Content.Client/Shuttles/FtlArrivalOverlay.cs b/Content.Client/Shuttles/FtlArrivalOverlay.cs new file mode 100644 index 0000000000..f24a1e9648 --- /dev/null +++ b/Content.Client/Shuttles/FtlArrivalOverlay.cs @@ -0,0 +1,82 @@ +using System.Numerics; +using Content.Shared.Shuttles.Components; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Shared.Enums; +using Robust.Shared.Map.Components; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; + +namespace Content.Client.Shuttles; + +/// +/// Plays a visualization whenever a shuttle is arriving from FTL. +/// +public sealed class FtlArrivalOverlay : Overlay +{ + public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowEntities; + + private EntityLookupSystem _lookups; + private SharedMapSystem _maps; + private SharedTransformSystem _transforms; + private SpriteSystem _sprites; + [Dependency] private readonly IEntityManager _entManager = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly IPrototypeManager _protos = default!; + + private readonly HashSet> _visualizers = new(); + + private ShaderInstance _shader; + + public FtlArrivalOverlay() + { + IoCManager.InjectDependencies(this); + _lookups = _entManager.System(); + _transforms = _entManager.System(); + _maps = _entManager.System(); + _sprites = _entManager.System(); + + _shader = _protos.Index("unshaded").Instance(); + } + + protected override bool BeforeDraw(in OverlayDrawArgs args) + { + _visualizers.Clear(); + _lookups.GetEntitiesOnMap(args.MapId, _visualizers); + + return _visualizers.Count > 0; + } + + protected override void Draw(in OverlayDrawArgs args) + { + args.WorldHandle.UseShader(_shader); + + foreach (var (uid, comp) in _visualizers) + { + var grid = comp.Grid; + + if (!_entManager.TryGetComponent(grid, out MapGridComponent? mapGrid)) + continue; + + var texture = _sprites.GetFrame(comp.Sprite, TimeSpan.FromSeconds(comp.Elapsed), loop: false); + comp.Elapsed += (float) _timing.FrameTime.TotalSeconds; + + // Need to manually transform the viewport in terms of the visualizer entity as the grid isn't in position. + var (_, _, worldMatrix, invMatrix) = _transforms.GetWorldPositionRotationMatrixWithInv(uid); + args.WorldHandle.SetTransform(worldMatrix); + var localAABB = invMatrix.TransformBox(args.WorldBounds); + + var tilesEnumerator = _maps.GetLocalTilesEnumerator(grid, mapGrid, localAABB); + + while (tilesEnumerator.MoveNext(out var tile)) + { + var bounds = _lookups.GetLocalBounds(tile, mapGrid.TileSize); + + args.WorldHandle.DrawTextureRect(texture, bounds); + } + } + + args.WorldHandle.UseShader(null); + args.WorldHandle.SetTransform(Matrix3x2.Identity); + } +} diff --git a/Content.Client/Shuttles/Systems/ShuttleSystem.EmergencyConsole.cs b/Content.Client/Shuttles/Systems/ShuttleSystem.EmergencyConsole.cs index d5154a87be..73c11de279 100644 --- a/Content.Client/Shuttles/Systems/ShuttleSystem.EmergencyConsole.cs +++ b/Content.Client/Shuttles/Systems/ShuttleSystem.EmergencyConsole.cs @@ -38,9 +38,8 @@ public sealed partial class ShuttleSystem : SharedShuttleSystem private bool _enableShuttlePosition; private EmergencyShuttleOverlay? _overlay; - public override void Initialize() + private void InitializeEmergency() { - base.Initialize(); SubscribeNetworkEvent(OnShuttlePosMessage); } diff --git a/Content.Client/Shuttles/Systems/ShuttleSystem.cs b/Content.Client/Shuttles/Systems/ShuttleSystem.cs new file mode 100644 index 0000000000..a2c048ff90 --- /dev/null +++ b/Content.Client/Shuttles/Systems/ShuttleSystem.cs @@ -0,0 +1,21 @@ +using Robust.Client.Graphics; + +namespace Content.Client.Shuttles.Systems; + +public sealed partial class ShuttleSystem +{ + [Dependency] private readonly IOverlayManager _overlays = default!; + + public override void Initialize() + { + base.Initialize(); + InitializeEmergency(); + _overlays.AddOverlay(new FtlArrivalOverlay()); + } + + public override void Shutdown() + { + base.Shutdown(); + _overlays.RemoveOverlay(); + } +} diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs index 28f784f1dd..518867b555 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs @@ -82,6 +82,8 @@ public sealed partial class ShuttleSystem private void InitializeFTL() { SubscribeLocalEvent(OnStationPostInit); + SubscribeLocalEvent(OnFtlShutdown); + _bodyQuery = GetEntityQuery(); _buckleQuery = GetEntityQuery(); _beaconQuery = GetEntityQuery(); @@ -98,6 +100,12 @@ public sealed partial class ShuttleSystem _cfg.OnValueChanged(CCVars.HyperspaceKnockdownTime, time => _hyperspaceKnockdownTime = TimeSpan.FromSeconds(time), true); } + private void OnFtlShutdown(Entity ent, ref ComponentShutdown args) + { + Del(ent.Comp.VisualizerEntity); + ent.Comp.VisualizerEntity = null; + } + private void OnStationPostInit(ref StationPostInitEvent ev) { // Add all grid maps as ftl destinations that anyone can FTL to. @@ -422,8 +430,16 @@ public sealed partial class ShuttleSystem var comp = entity.Comp1; comp.StateTime = StartEndTime.FromCurTime(_gameTiming, DefaultArrivalTime); comp.State = FTLState.Arriving; - // TODO: Arrival effects - // For now we'll just use the ss13 bubbles but we can do fancier. + + if (entity.Comp1.VisualizerProto != null) + { + comp.VisualizerEntity = SpawnAtPosition(entity.Comp1.VisualizerProto, entity.Comp1.TargetCoordinates); + var visuals = Comp(comp.VisualizerEntity.Value); + visuals.Grid = entity.Owner; + Dirty(comp.VisualizerEntity.Value, visuals); + _transform.SetLocalRotation(comp.VisualizerEntity.Value, entity.Comp1.TargetAngle); + _pvs.AddGlobalOverride(comp.VisualizerEntity.Value); + } _thruster.DisableLinearThrusters(shuttle); _thruster.EnableLinearThrustDirection(shuttle, DirectionFlag.South); diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.cs index bbafef022c..b8f216db73 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.cs @@ -11,6 +11,7 @@ using Content.Shared.Shuttles.Systems; using Content.Shared.Throwing; using JetBrains.Annotations; using Robust.Server.GameObjects; +using Robust.Server.GameStates; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Configuration; @@ -40,6 +41,7 @@ public sealed partial class ShuttleSystem : SharedShuttleSystem [Dependency] private readonly FixtureSystem _fixtures = default!; [Dependency] private readonly MapLoaderSystem _loader = default!; [Dependency] private readonly MetaDataSystem _metadata = default!; + [Dependency] private readonly PvsOverrideSystem _pvs = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedMapSystem _maps = default!; [Dependency] private readonly SharedPhysicsSystem _physics = default!; diff --git a/Content.Server/Shuttles/Components/FTLComponent.cs b/Content.Shared/Shuttles/Components/FTLComponent.cs similarity index 81% rename from Content.Server/Shuttles/Components/FTLComponent.cs rename to Content.Shared/Shuttles/Components/FTLComponent.cs index 0a01bb7636..bce33492b9 100644 --- a/Content.Server/Shuttles/Components/FTLComponent.cs +++ b/Content.Shared/Shuttles/Components/FTLComponent.cs @@ -2,16 +2,16 @@ using Content.Shared.Shuttles.Systems; using Content.Shared.Tag; using Content.Shared.Timing; using Robust.Shared.Audio; +using Robust.Shared.GameStates; using Robust.Shared.Map; using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -namespace Content.Server.Shuttles.Components; +namespace Content.Shared.Shuttles.Components; /// /// Added to a component when it is queued or is travelling via FTL. /// -[RegisterComponent] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] public sealed partial class FTLComponent : Component { // TODO Full game save / add datafields @@ -29,13 +29,19 @@ public sealed partial class FTLComponent : Component [ViewVariables(VVAccess.ReadWrite)] public float TravelTime = 0f; + [DataField] + public EntProtoId? VisualizerProto = "FtlVisualizerEntity"; + + [DataField, AutoNetworkedField] + public EntityUid? VisualizerEntity; + /// /// Coordinates to arrive it: May be relative to another grid (for docking) or map coordinates. /// - [ViewVariables(VVAccess.ReadWrite), DataField] + [DataField, AutoNetworkedField] public EntityCoordinates TargetCoordinates; - [DataField] + [DataField, AutoNetworkedField] public Angle TargetAngle; /// diff --git a/Content.Shared/Shuttles/Components/FtlVisualizerComponent.cs b/Content.Shared/Shuttles/Components/FtlVisualizerComponent.cs new file mode 100644 index 0000000000..628a4f828b --- /dev/null +++ b/Content.Shared/Shuttles/Components/FtlVisualizerComponent.cs @@ -0,0 +1,23 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Utility; + +namespace Content.Shared.Shuttles.Components; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class FtlVisualizerComponent : Component +{ + /// + /// Clientside time tracker for the animation. + /// + [ViewVariables(VVAccess.ReadWrite)] + public float Elapsed; + + [DataField(required: true)] + public SpriteSpecifier.Rsi Sprite; + + /// + /// Target grid to pull FTL visualization from. + /// + [DataField, AutoNetworkedField] + public EntityUid Grid; +} diff --git a/Resources/Prototypes/Entities/Effects/shuttle.yml b/Resources/Prototypes/Entities/Effects/shuttle.yml new file mode 100644 index 0000000000..d4538116ac --- /dev/null +++ b/Resources/Prototypes/Entities/Effects/shuttle.yml @@ -0,0 +1,12 @@ +- type: entity + id: FtlVisualizerEntity + noSpawn: true + description: Visualizer for shuttles arriving. You shouldn't see this! + components: + - type: FtlVisualizer + sprite: + sprite: /Textures/Effects/medi_holo.rsi + state: medi_holo + - type: Tag + tags: + - HideContextMenu diff --git a/Resources/Textures/Effects/medi_holo.rsi/medi_holo.png b/Resources/Textures/Effects/medi_holo.rsi/medi_holo.png new file mode 100644 index 0000000000000000000000000000000000000000..9b024faa2d799b09ede0769fb89521a2c66e6b29 GIT binary patch literal 1286 zcmV+h1^N1kP)5Ot-`|VJ$T9$<&?@>gayZ*Tuo?pzy z+r{3C$N+d#F!?wE@4xWf|`9J;Z?ayYjDYg=5pyA&PlRxNhpP!wb?ViK? zKvLe1?*Yb$48TZ-$cM*oA6{4810b>>?OgwVowEt1>LI8_QxL3#i!XPgv9ne{?ahy- zI7owZxtB_RQ{zI;$Fu{JZ}Ok8_1U}U(+}c%Kn8RLO6k^sb*KONhIDJdbHNSBKw}Sb z1B7U$KsUe`H^3M-z!*2c7&pKeH^3M-z!*2cnC=W%tZrW&`u+AFq~C@0uOj+O&UjrI zKqoym!JM`X{bAB$6U=D~G;aV*C}$#RTW;S8{VPsX)3zynn>7FV}i=HydwH6-jIYLg%6FUQ7i&VXY>Y(R&I3BC;CAjN>hN;s)(^wkhKD^EJLF%N0NFZwV_icluA7#`g#=z)9 z=9hK#A>uVO#Rg#X(O+g`8E066pkwmgq<+QZ!Keb=f0Gh+-Q4?}L zy+sH1gjoSlKQ#s9oQ@9Zm+I#!$e1P!NWD@?!P5@HiTqG&>PlrPR4zsA^89)JjmpPP z)(W5-Z*5?cv%FDTay4{}ECVznUy_C-6F;R1SgjU~UA!+~|HKjMB<@7xkJOvpOH9%S)pWF9X zVBH_U#q)n0PeI1)45)oB4z;ZHE%6m#j2mFgU;sA_NN-S>(x{b&7OO@tM+2Nm5Z1>Z z9J{m$iYxOyOt@Ue>bSc8JG|*bV wG)bFqSnikFRtszWlCV!f)_>r-vk8vz7wA2u47-?TCIA2c07*qoM6N<$f_7g|Z2$lO literal 0 HcmV?d00001 diff --git a/Resources/Textures/Effects/medi_holo.rsi/meta.json b/Resources/Textures/Effects/medi_holo.rsi/meta.json new file mode 100644 index 0000000000..1be502223e --- /dev/null +++ b/Resources/Textures/Effects/medi_holo.rsi/meta.json @@ -0,0 +1,26 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "https://github.com/tgstation/tgstation/tree/217b39cc85e45302d407d5c1ab60809bd9e18987", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "medi_holo", + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + } + ] +} \ No newline at end of file From cb87787ffb9b7092726d43cfb9a816bed7e81fe7 Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 1 Jul 2024 06:12:36 +0000 Subject: [PATCH 27/86] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index adb7665029..cd23805277 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +1,4 @@ Entries: -- author: Dutch-VanDerLinde - changes: - - message: Holoparasites and holoclowns will now phase through projectiles such - as bullets, like a holocarp. - type: Tweak - id: 6352 - time: '2024-04-14T08:52:57.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26862 - author: Tyzemol changes: - message: Board game crate now comes with character sheets @@ -3819,3 +3811,10 @@ id: 6851 time: '2024-07-01T05:33:08.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29364 +- author: metalgearsloth + changes: + - message: Add effects for when shuttles are arriving. + type: Add + id: 6852 + time: '2024-07-01T06:11:30.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29402 From 7032499e2a647585c081ff240991f0d1b47e48e3 Mon Sep 17 00:00:00 2001 From: Plykiya <58439124+Plykiya@users.noreply.github.com> Date: Mon, 1 Jul 2024 08:39:05 -0700 Subject: [PATCH 28/86] Fixes gravity wells (#29617) * Fixes gravity wells * thank you slarticodefast * Minor nitpicks addressed * NITPICKS UNDONE * REDO THE NITPICK, WE LOVE MATRIX MULTIPLCATION * Revert "REDO THE NITPICK, WE LOVE MATRIX MULTIPLCATION" This reverts commit c782eee1a1c7bda90c7ca686928019cc5f25c8cf. * NITPICK REDO --------- Co-authored-by: plykiya --- .../Singularity/EntitySystems/GravityWellSystem.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/Content.Server/Singularity/EntitySystems/GravityWellSystem.cs b/Content.Server/Singularity/EntitySystems/GravityWellSystem.cs index f53d658ebd..8c884d023c 100644 --- a/Content.Server/Singularity/EntitySystems/GravityWellSystem.cs +++ b/Content.Server/Singularity/EntitySystems/GravityWellSystem.cs @@ -156,7 +156,7 @@ public sealed class GravityWellSystem : SharedGravityWellSystem /// The minimum distance at which entities can be affected by the gravity pulse. /// The base velocity added to any entities within affected by the gravity pulse scaled by the displacement of those entities from the epicenter. public void GravPulse(EntityCoordinates entityPos, float maxRange, float minRange, in Matrix3x2 baseMatrixDeltaV) - => GravPulse(entityPos.ToMap(EntityManager, _transform), maxRange, minRange, in baseMatrixDeltaV); + => GravPulse(_transform.ToMapCoordinates(entityPos), maxRange, minRange, in baseMatrixDeltaV); /// /// Greates a gravitational pulse, shoving around all entities within some distance of an epicenter. @@ -167,7 +167,7 @@ public sealed class GravityWellSystem : SharedGravityWellSystem /// The base radial velocity that will be added to entities within range towards the center of the gravitational pulse. /// The base tangential velocity that will be added to entities within countrclockwise around the center of the gravitational pulse. public void GravPulse(EntityCoordinates entityPos, float maxRange, float minRange, float baseRadialDeltaV = 0.0f, float baseTangentialDeltaV = 0.0f) - => GravPulse(entityPos.ToMap(EntityManager, _transform), maxRange, minRange, baseRadialDeltaV, baseTangentialDeltaV); + => GravPulse(_transform.ToMapCoordinates(entityPos), maxRange, minRange, baseRadialDeltaV, baseTangentialDeltaV); /// /// Causes a gravitational pulse, shoving around all entities within some distance of an epicenter. @@ -206,7 +206,7 @@ public sealed class GravityWellSystem : SharedGravityWellSystem continue; var scaling = (1f / distance2) * physics.Mass; // TODO: Variable falloff gradiants. - _physics.ApplyLinearImpulse(entity, Vector2.Transform(displacement, baseMatrixDeltaV) * scaling, body: physics); + _physics.ApplyLinearImpulse(entity, Vector2.TransformNormal(displacement, baseMatrixDeltaV) * scaling, body: physics); } } @@ -219,10 +219,7 @@ public sealed class GravityWellSystem : SharedGravityWellSystem /// The base amount of velocity that will be added to entities in range towards the epicenter of the pulse. /// The base amount of velocity that will be added to entities in range counterclockwise relative to the epicenter of the pulse. public void GravPulse(MapCoordinates mapPos, float maxRange, float minRange = 0.0f, float baseRadialDeltaV = 0.0f, float baseTangentialDeltaV = 0.0f) - => GravPulse(mapPos, maxRange, minRange, new Matrix3x2( - baseRadialDeltaV, -baseTangentialDeltaV, 0.0f, - +baseTangentialDeltaV, baseRadialDeltaV, 0.0f - )); + => GravPulse(mapPos, maxRange, minRange, new Matrix3x2(baseRadialDeltaV, -baseTangentialDeltaV, baseTangentialDeltaV, baseRadialDeltaV, 0.0f, 0.0f)); #endregion GravPulse From e0697763f849001822e12160bf0e015357997672 Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 1 Jul 2024 15:40:11 +0000 Subject: [PATCH 29/86] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index cd23805277..87fff0538a 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Tyzemol - changes: - - message: Board game crate now comes with character sheets - type: Add - id: 6353 - time: '2024-04-14T08:54:40.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26926 - author: FairlySadPanda changes: - message: Clown shoes make you waddle, as God intended. @@ -3818,3 +3811,10 @@ id: 6852 time: '2024-07-01T06:11:30.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29402 +- author: Plykiya + changes: + - message: Gravity wells, like the supermatter grenade, now function properly again. + type: Fix + id: 6853 + time: '2024-07-01T15:39:05.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29617 From ca87fb8bc5954a4604ce84d1413a88f22a6fc5f9 Mon Sep 17 00:00:00 2001 From: jmcb Date: Mon, 1 Jul 2024 17:27:49 +0100 Subject: [PATCH 30/86] Fix showing tips on the login screen from the localized tip dataset (#29640) Regression introduced in #28285 --- Content.Client/Launcher/LauncherConnectingGui.xaml.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Content.Client/Launcher/LauncherConnectingGui.xaml.cs b/Content.Client/Launcher/LauncherConnectingGui.xaml.cs index ac74ad7b60..5015b710eb 100644 --- a/Content.Client/Launcher/LauncherConnectingGui.xaml.cs +++ b/Content.Client/Launcher/LauncherConnectingGui.xaml.cs @@ -116,7 +116,7 @@ namespace Content.Client.Launcher private void ChangeLoginTip() { var tipsDataset = _cfg.GetCVar(CCVars.LoginTipsDataset); - var loginTipsEnabled = _prototype.TryIndex(tipsDataset, out var tips); + var loginTipsEnabled = _prototype.TryIndex(tipsDataset, out var tips); LoginTips.Visible = loginTipsEnabled; if (!loginTipsEnabled) @@ -131,7 +131,7 @@ namespace Content.Client.Launcher var randomIndex = _random.Next(tipList.Count); var tip = tipList[randomIndex]; - LoginTip.SetMessage(tip); + LoginTip.SetMessage(Loc.GetString(tip)); LoginTipTitle.Text = Loc.GetString("connecting-window-tip", ("numberTip", randomIndex)); } From 732489ee4a7d2e8b9cee74c74c61dc3c811dca74 Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 1 Jul 2024 16:28:55 +0000 Subject: [PATCH 31/86] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 87fff0538a..67c5cd23d9 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: FairlySadPanda - changes: - - message: Clown shoes make you waddle, as God intended. - type: Add - id: 6354 - time: '2024-04-14T12:12:54.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26338 - author: beck-thompson changes: - message: Cybersun pen now makes a slashing noise when doing damage. @@ -3818,3 +3811,10 @@ id: 6853 time: '2024-07-01T15:39:05.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29617 +- author: joelsgp + changes: + - message: Fixed login tips + type: Fix + id: 6854 + time: '2024-07-01T16:27:49.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29640 From ed2789a0e405a99095da17be196d550c14be734e Mon Sep 17 00:00:00 2001 From: jmcb Date: Mon, 1 Jul 2024 18:10:49 +0100 Subject: [PATCH 32/86] Remove outdated NotNullWhen annotation (#29641) A previous version of this function returned a bool. Since this no longer does that, remove the annotation. --- .../Atmos/Piping/Other/EntitySystems/GasMinerSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Server/Atmos/Piping/Other/EntitySystems/GasMinerSystem.cs b/Content.Server/Atmos/Piping/Other/EntitySystems/GasMinerSystem.cs index 251ee79764..1aa5973c96 100644 --- a/Content.Server/Atmos/Piping/Other/EntitySystems/GasMinerSystem.cs +++ b/Content.Server/Atmos/Piping/Other/EntitySystems/GasMinerSystem.cs @@ -39,7 +39,7 @@ namespace Content.Server.Atmos.Piping.Other.EntitySystems _atmosphereSystem.Merge(environment, merger); } - private float CapSpawnAmount(Entity ent, float toSpawnTarget, [NotNullWhen(true)] out GasMixture? environment) + private float CapSpawnAmount(Entity ent, float toSpawnTarget, out GasMixture? environment) { var (uid, miner) = ent; var transform = Transform(uid); From fe7dbb42b3a6a94bcf1a9c90396eb4e4ed5eb492 Mon Sep 17 00:00:00 2001 From: Cojoke <83733158+Cojoke-dot@users.noreply.github.com> Date: Mon, 1 Jul 2024 16:14:38 -0500 Subject: [PATCH 33/86] Allow Flares to light cigarettes (#29476) * Allow Flares to light cigarettes * !IsHot check * nicer looking(and I think the right way to do that...) * heh, whoops * Adds IgnitionEvent, IgnitionSource now functions as IsHot when Ignited * Fixes + remove redundancy * Hows this? * press enter Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> * Flare is not forever hot anymore * Formatting fixes * Make IgnitionEvent readonly --------- Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> --- Content.Server/IgnitionSource/IgnitionEvent.cs | 7 +++++++ Content.Server/IgnitionSource/IgnitionSourceSystem.cs | 9 ++++++++- .../Light/EntitySystems/ExpendableLightSystem.cs | 9 +++++---- Resources/Prototypes/Entities/Objects/Tools/flare.yml | 1 - 4 files changed, 20 insertions(+), 6 deletions(-) create mode 100644 Content.Server/IgnitionSource/IgnitionEvent.cs diff --git a/Content.Server/IgnitionSource/IgnitionEvent.cs b/Content.Server/IgnitionSource/IgnitionEvent.cs new file mode 100644 index 0000000000..b86bf52b2d --- /dev/null +++ b/Content.Server/IgnitionSource/IgnitionEvent.cs @@ -0,0 +1,7 @@ +namespace Content.Server.IgnitionSource; + +/// +/// Raised in order to toggle the ignitionSourceComponent on an entity on or off +/// +[ByRefEvent] +public readonly record struct IgnitionEvent(bool Ignite = false); diff --git a/Content.Server/IgnitionSource/IgnitionSourceSystem.cs b/Content.Server/IgnitionSource/IgnitionSourceSystem.cs index c20e5207a4..3925cc86b5 100644 --- a/Content.Server/IgnitionSource/IgnitionSourceSystem.cs +++ b/Content.Server/IgnitionSource/IgnitionSourceSystem.cs @@ -19,18 +19,25 @@ public sealed class IgnitionSourceSystem : EntitySystem SubscribeLocalEvent(OnIsHot); SubscribeLocalEvent(OnItemToggle); + SubscribeLocalEvent(OnIgnitionEvent); } private void OnIsHot(Entity ent, ref IsHotEvent args) { - SetIgnited((ent.Owner, ent.Comp), args.IsHot); + args.IsHot = ent.Comp.Ignited; } + private void OnItemToggle(Entity ent, ref ItemToggledEvent args) { if (TryComp(ent, out var comp)) SetIgnited((ent.Owner, comp), args.Activated); } + private void OnIgnitionEvent(Entity ent, ref IgnitionEvent args) + { + SetIgnited((ent.Owner, ent.Comp), args.Ignite); + } + /// /// Simply sets the ignited field to the ignited param. /// diff --git a/Content.Server/Light/EntitySystems/ExpendableLightSystem.cs b/Content.Server/Light/EntitySystems/ExpendableLightSystem.cs index 571c23c5db..ba44b42ead 100644 --- a/Content.Server/Light/EntitySystems/ExpendableLightSystem.cs +++ b/Content.Server/Light/EntitySystems/ExpendableLightSystem.cs @@ -1,3 +1,4 @@ +using Content.Server.IgnitionSource; using Content.Server.Light.Components; using Content.Shared.Clothing.Components; using Content.Shared.Clothing.EntitySystems; @@ -99,8 +100,8 @@ namespace Content.Server.Light.EntitySystems _item.SetHeldPrefix(ent, "lit", component: item); } - var isHotEvent = new IsHotEvent() {IsHot = true}; - RaiseLocalEvent(ent, isHotEvent); + var ignite = new IgnitionEvent(true); + RaiseLocalEvent(ent, ref ignite); component.CurrentState = ExpendableLightState.Lit; component.StateExpiryTime = component.GlowDuration; @@ -134,8 +135,8 @@ namespace Content.Server.Light.EntitySystems case ExpendableLightState.Dead: _appearance.SetData(ent, ExpendableLightVisuals.Behavior, string.Empty, appearance); - var isHotEvent = new IsHotEvent() {IsHot = true}; - RaiseLocalEvent(ent, isHotEvent); + var ignite = new IgnitionEvent(false); + RaiseLocalEvent(ent, ref ignite); break; } } diff --git a/Resources/Prototypes/Entities/Objects/Tools/flare.yml b/Resources/Prototypes/Entities/Objects/Tools/flare.yml index fdf5314863..d36f67d00d 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/flare.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/flare.yml @@ -26,7 +26,6 @@ loop: true volume: -10 maxDistance: 5 - - type: Sprite sprite: Objects/Misc/flare.rsi layers: From 854762092d2fb4d9f306880c51fd72c0d046f7e2 Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 1 Jul 2024 21:15:44 +0000 Subject: [PATCH 34/86] Automatic changelog update --- Resources/Changelog/Changelog.yml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 67c5cd23d9..22285bf393 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: beck-thompson - changes: - - message: Cybersun pen now makes a slashing noise when doing damage. - type: Fix - id: 6355 - time: '2024-04-14T20:04:06.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26951 - author: TokenStyle changes: - message: Lockers cannot be deconstructed with a screwdriver when locked now. @@ -3818,3 +3811,12 @@ id: 6854 time: '2024-07-01T16:27:49.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29640 +- author: Cojoke-dot + changes: + - message: Flares can now light Cigarettes and other similar things + type: Tweak + - message: Flares now are no longer hot after burning out + type: Tweak + id: 6855 + time: '2024-07-01T21:14:38.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29476 From 340332cf5b473543a48487efc5d68dded394f375 Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Mon, 1 Jul 2024 18:23:36 -0400 Subject: [PATCH 35/86] Fix nukeops ending early if an operative dies at base (#29642) Fix nukeops ending early if an operative died at base --- .../Tests/GameRules/NukeOpsTest.cs | 46 ++++++++++++++----- .../GameTicking/Rules/NukeopsRuleSystem.cs | 24 +++++----- 2 files changed, 46 insertions(+), 24 deletions(-) diff --git a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs index fc50d0bd33..7b3c994302 100644 --- a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs @@ -7,6 +7,7 @@ using Content.Server.GameTicking.Rules.Components; using Content.Server.Mind; using Content.Server.Pinpointer; using Content.Server.Roles; +using Content.Server.RoundEnd; using Content.Server.Shuttles.Components; using Content.Server.Station.Components; using Content.Shared.CCVar; @@ -49,6 +50,7 @@ public sealed class NukeOpsTest var roleSys = server.System(); var invSys = server.System(); var factionSys = server.System(); + var roundEndSys = server.System(); server.CfgMan.SetCVar(CCVars.GridFill, true); @@ -63,11 +65,11 @@ public sealed class NukeOpsTest // Opt into the nukies role. await pair.SetAntagPreference("NukeopsCommander", true); - await pair.SetAntagPreference( "NukeopsMedic", true, dummies[1].UserId); + await pair.SetAntagPreference("NukeopsMedic", true, dummies[1].UserId); // Initially, the players have no attached entities Assert.That(pair.Player?.AttachedEntity, Is.Null); - Assert.That(dummies.All(x => x.AttachedEntity == null)); + Assert.That(dummies.All(x => x.AttachedEntity == null)); // There are no grids or maps Assert.That(entMan.Count(), Is.Zero); @@ -150,7 +152,8 @@ public sealed class NukeOpsTest } // The game rule exists, and all the stations/shuttles/maps are properly initialized - var rule = entMan.AllComponents().Single().Component; + var rule = entMan.AllComponents().Single(); + var ruleComp = rule.Component; var gridsRule = entMan.AllComponents().Single().Component; foreach (var grid in gridsRule.MapGrids) { @@ -158,12 +161,14 @@ public sealed class NukeOpsTest Assert.That(entMan.HasComponent(grid)); Assert.That(entMan.HasComponent(grid)); } - Assert.That(entMan.EntityExists(rule.TargetStation)); + Assert.That(entMan.EntityExists(ruleComp.TargetStation)); - Assert.That(entMan.HasComponent(rule.TargetStation)); + Assert.That(entMan.HasComponent(ruleComp.TargetStation)); - var nukieShuttlEnt = entMan.AllComponents().FirstOrDefault().Uid; + var nukieShuttle = entMan.AllComponents().Single(); + var nukieShuttlEnt = nukieShuttle.Uid; Assert.That(entMan.EntityExists(nukieShuttlEnt)); + Assert.That(nukieShuttle.Component.AssociatedRule, Is.EqualTo(rule.Uid)); EntityUid? nukieStationEnt = null; foreach (var grid in gridsRule.MapGrids) @@ -179,12 +184,12 @@ public sealed class NukeOpsTest var nukieStation = entMan.GetComponent(nukieStationEnt!.Value); Assert.That(entMan.EntityExists(nukieStation.Station)); - Assert.That(nukieStation.Station, Is.Not.EqualTo(rule.TargetStation)); + Assert.That(nukieStation.Station, Is.Not.EqualTo(ruleComp.TargetStation)); Assert.That(server.MapMan.MapExists(gridsRule.Map)); var nukieMap = mapSys.GetMap(gridsRule.Map!.Value); - var targetStation = entMan.GetComponent(rule.TargetStation!.Value); + var targetStation = entMan.GetComponent(ruleComp.TargetStation!.Value); var targetGrid = targetStation.Grids.First(); var targetMap = entMan.GetComponent(targetGrid).MapUid!.Value; Assert.That(targetMap, Is.Not.EqualTo(nukieMap)); @@ -206,7 +211,7 @@ public sealed class NukeOpsTest Assert.That(LifeStage(targetMap), Is.GreaterThan(EntityLifeStage.Initialized)); Assert.That(LifeStage(nukieStationEnt.Value), Is.GreaterThan(EntityLifeStage.Initialized)); Assert.That(LifeStage(nukieShuttlEnt), Is.GreaterThan(EntityLifeStage.Initialized)); - Assert.That(LifeStage(rule.TargetStation), Is.GreaterThan(EntityLifeStage.Initialized)); + Assert.That(LifeStage(ruleComp.TargetStation), Is.GreaterThan(EntityLifeStage.Initialized)); // Make sure the player has hands. We've had fucking disarmed nukies before. Assert.That(entMan.HasComponent(player)); @@ -222,10 +227,10 @@ public sealed class NukeOpsTest } Assert.That(total, Is.GreaterThan(3)); - // Finally lets check the nukie commander passed basic training and figured out how to breathe. + // Check the nukie commander passed basic training and figured out how to breathe. var totalSeconds = 30; var totalTicks = (int) Math.Ceiling(totalSeconds / server.Timing.TickPeriod.TotalSeconds); - int increment = 5; + var increment = 5; var resp = entMan.GetComponent(player); var damage = entMan.GetComponent(player); for (var tick = 0; tick < totalTicks; tick += increment) @@ -235,7 +240,24 @@ public sealed class NukeOpsTest Assert.That(damage.TotalDamage, Is.EqualTo(FixedPoint2.Zero)); } - ticker.SetGamePreset((GamePresetPrototype?)null); + // Check that the round does not end prematurely when agents are deleted in the outpost + var nukies = dummyEnts.Where(entMan.HasComponent).Append(player).ToArray(); + await server.WaitAssertion(() => + { + for (var i = 0; i < nukies.Length - 1; i++) + { + entMan.DeleteEntity(nukies[i]); + Assert.That(roundEndSys.IsRoundEndRequested, Is.False, + $"The round ended, but {nukies.Length - i - 1} nukies are still alive!"); + } + // Delete the last nukie and make sure the round ends. + entMan.DeleteEntity(nukies[^1]); + + Assert.That(roundEndSys.IsRoundEndRequested, + "All nukies were deleted, but the round didn't end!"); + }); + + ticker.SetGamePreset((GamePresetPrototype?) null); await pair.CleanReturnAsync(); } } diff --git a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs index 5c3459ef81..2a67e62e3f 100644 --- a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs @@ -57,13 +57,12 @@ public sealed class NukeopsRuleSystem : GameRuleSystem SubscribeLocalEvent(OnMobStateChanged); SubscribeLocalEvent(OnOperativeZombified); - SubscribeLocalEvent(OnMapInit); - SubscribeLocalEvent(OnShuttleFTLAttempt); SubscribeLocalEvent(OnWarDeclared); SubscribeLocalEvent(OnShuttleCallAttempt); SubscribeLocalEvent(OnAfterAntagEntSelected); + SubscribeLocalEvent(OnRuleLoadedGrids); } protected override void Started(EntityUid uid, NukeopsRuleComponent component, GameRuleComponent gameRule, @@ -256,17 +255,18 @@ public sealed class NukeopsRuleSystem : GameRuleSystem RemCompDeferred(uid, component); } - private void OnMapInit(Entity ent, ref MapInitEvent args) + private void OnRuleLoadedGrids(Entity ent, ref RuleLoadedGridsEvent args) { - var map = Transform(ent).MapID; - - var rules = EntityQueryEnumerator(); - while (rules.MoveNext(out var uid, out _, out var grids)) + // Check each nukie shuttle + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var shuttle)) { - if (map != grids.Map) - continue; - ent.Comp.AssociatedRule = uid; - break; + // Check if the shuttle's mapID is the one that just got loaded for this rule + if (Transform(uid).MapID == args.Map) + { + shuttle.AssociatedRule = ent; + break; + } } } @@ -376,7 +376,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem if (Transform(uid).MapID != Transform(outpost).MapID) // Will receive bonus TC only on their start outpost continue; - _store.TryAddCurrency(new () { { TelecrystalCurrencyPrototype, nukieRule.Comp.WarTcAmountPerNukie } }, uid, component); + _store.TryAddCurrency(new() { { TelecrystalCurrencyPrototype, nukieRule.Comp.WarTcAmountPerNukie } }, uid, component); var msg = Loc.GetString("store-currency-war-boost-given", ("target", uid)); _popupSystem.PopupEntity(msg, uid); From 2f1bc7ab93f095a2dec5ea98c531d3af31d5eb1d Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 1 Jul 2024 22:24:42 +0000 Subject: [PATCH 36/86] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 22285bf393..e0d6bbb194 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: TokenStyle - changes: - - message: Lockers cannot be deconstructed with a screwdriver when locked now. - type: Fix - id: 6356 - time: '2024-04-14T22:26:47.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26961 - author: pissdemon changes: - message: Catwalks over lava are slightly less likely to catch you on fire again. @@ -3820,3 +3813,11 @@ id: 6855 time: '2024-07-01T21:14:38.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29476 +- author: Tayrtahn + changes: + - message: Fixed NukeOps ending prematurely in some situations where operatives + were still alive. + type: Fix + id: 6856 + time: '2024-07-01T22:23:36.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29642 From b565258f128c1acdd68d04c8aa834891af9f9bf3 Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Mon, 1 Jul 2024 23:30:53 -0400 Subject: [PATCH 37/86] Fixed buckled players thrashing while speaking in beds (#29653) * Fix characters thrashing when talking while lying down * Remove weird design choice --- Content.Client/Rotation/RotationVisualizerSystem.cs | 4 ++-- Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Content.Client/Rotation/RotationVisualizerSystem.cs b/Content.Client/Rotation/RotationVisualizerSystem.cs index 6105c10c80..6d3be4d1c0 100644 --- a/Content.Client/Rotation/RotationVisualizerSystem.cs +++ b/Content.Client/Rotation/RotationVisualizerSystem.cs @@ -23,8 +23,8 @@ public sealed class RotationVisualizerSystem : SharedRotationVisualsSystem if (args.Sprite == null) return; - // If not defined, defaults to standing. - _appearance.TryGetData(uid, RotationVisuals.RotationState, out var state, args.Component); + if (!_appearance.TryGetData(uid, RotationVisuals.RotationState, out var state, args.Component)) + return; switch (state) { diff --git a/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs b/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs index fd6ffd30e3..0ce8c99268 100644 --- a/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs +++ b/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs @@ -9,6 +9,7 @@ using Content.Shared.Movement.Events; using Content.Shared.Movement.Pulling.Events; using Content.Shared.Popups; using Content.Shared.Pulling.Events; +using Content.Shared.Rotation; using Content.Shared.Standing; using Content.Shared.Storage.Components; using Content.Shared.Stunnable; @@ -195,6 +196,7 @@ public abstract partial class SharedBuckleSystem buckle.Comp.BuckleTime = _gameTiming.CurTime; ActionBlocker.UpdateCanMove(buckle); Appearance.SetData(buckle, StrapVisuals.State, buckle.Comp.Buckled); + Appearance.SetData(buckle, RotationVisuals.RotationState, RotationState.Horizontal); Dirty(buckle); } @@ -348,6 +350,7 @@ public abstract partial class SharedBuckleSystem SetBuckledTo(buckle, strap!); Appearance.SetData(strap, StrapVisuals.State, true); Appearance.SetData(buckle, BuckleVisuals.Buckled, true); + Appearance.SetData(buckle, RotationVisuals.RotationState, RotationState.Horizontal); _rotationVisuals.SetHorizontalAngle(buckle.Owner, strap.Comp.Rotation); @@ -455,6 +458,7 @@ public abstract partial class SharedBuckleSystem _rotationVisuals.ResetHorizontalAngle(buckle.Owner); Appearance.SetData(strap, StrapVisuals.State, strap.Comp.BuckledEntities.Count != 0); Appearance.SetData(buckle, BuckleVisuals.Buckled, false); + Appearance.SetData(buckle, RotationVisuals.RotationState, RotationState.Vertical); if (HasComp(buckle) || _mobState.IsIncapacitated(buckle)) _standing.Down(buckle); From cab2ea757d2d33aff89941a468d190accd3ad352 Mon Sep 17 00:00:00 2001 From: PJBot Date: Tue, 2 Jul 2024 03:31:59 +0000 Subject: [PATCH 38/86] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index e0d6bbb194..8da32dc372 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: pissdemon - changes: - - message: Catwalks over lava are slightly less likely to catch you on fire again. - type: Fix - id: 6357 - time: '2024-04-15T01:27:58.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26968 - author: Velcroboy changes: - message: Fixed sec/lawyer and vault airlocks @@ -3821,3 +3814,10 @@ id: 6856 time: '2024-07-01T22:23:36.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29642 +- author: Tayrtahn + changes: + - message: Fixed characters thrashing when speaking in beds. + type: Fix + id: 6857 + time: '2024-07-02T03:30:53.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29653 From 763a25e9345bd1f02aada9a47d7829b239a74e52 Mon Sep 17 00:00:00 2001 From: Chief-Engineer <119664036+Chief-Engineer@users.noreply.github.com> Date: Tue, 2 Jul 2024 04:09:24 -0500 Subject: [PATCH 39/86] add more info to entity json in logs (#18672) * add more info to entity json in logs * replace TryGetSessionById * remove unused dependency * get admin status from the entity * group values by component * alphabetize * I've discovered that my original plans may be bad for performance --- .../EntityStringRepresentationConverter.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Content.Server/Administration/Logs/Converters/EntityStringRepresentationConverter.cs b/Content.Server/Administration/Logs/Converters/EntityStringRepresentationConverter.cs index 32551e5a7d..39d34e5f18 100644 --- a/Content.Server/Administration/Logs/Converters/EntityStringRepresentationConverter.cs +++ b/Content.Server/Administration/Logs/Converters/EntityStringRepresentationConverter.cs @@ -1,10 +1,14 @@ using System.Text.Json; +using Content.Server.Administration.Managers; +using Robust.Server.Player; namespace Content.Server.Administration.Logs.Converters; [AdminLogConverter] public sealed class EntityStringRepresentationConverter : AdminLogConverter { + [Dependency] private readonly IAdminManager _adminManager = default!; + public override void Write(Utf8JsonWriter writer, EntityStringRepresentation value, JsonSerializerOptions options) { writer.WriteStartObject(); @@ -19,6 +23,21 @@ public sealed class EntityStringRepresentationConverter : AdminLogConverter Date: Tue, 2 Jul 2024 15:04:15 +0200 Subject: [PATCH 40/86] Fix MouseRotator on rotated grids (#29663) * fix harm mode rotation * cleanup * -pi to pi --- .../MouseRotator/MouseRotatorSystem.cs | 15 ++++++++++----- .../MouseRotator/MouseRotatorComponent.cs | 13 +------------ .../MouseRotator/SharedMouseRotatorSystem.cs | 17 +---------------- 3 files changed, 12 insertions(+), 33 deletions(-) diff --git a/Content.Client/MouseRotator/MouseRotatorSystem.cs b/Content.Client/MouseRotator/MouseRotatorSystem.cs index ce174c6144..18d60d9a7b 100644 --- a/Content.Client/MouseRotator/MouseRotatorSystem.cs +++ b/Content.Client/MouseRotator/MouseRotatorSystem.cs @@ -2,7 +2,6 @@ using Robust.Client.Graphics; using Robust.Client.Input; using Robust.Client.Player; -using Robust.Client.Replays.Loading; using Robust.Shared.Map; using Robust.Shared.Timing; @@ -46,13 +45,19 @@ public sealed class MouseRotatorSystem : SharedMouseRotatorSystem // only raise event if the cardinal direction has changed if (rotator.Simple4DirMode) { - var angleDir = angle.GetCardinalDir(); - if (angleDir == curRot.GetCardinalDir()) + var eyeRot = _eye.CurrentEye.Rotation; // camera rotation + var angleDir = (angle + eyeRot).GetCardinalDir(); // apply GetCardinalDir in the camera frame, not in the world frame + if (angleDir == (curRot + eyeRot).GetCardinalDir()) return; - RaisePredictiveEvent(new RequestMouseRotatorRotationSimpleEvent() + var rotation = angleDir.ToAngle() - eyeRot; // convert back to world frame + if (rotation >= Math.PI) // convert to [-PI, +PI) + rotation -= 2 * Math.PI; + else if (rotation < -Math.PI) + rotation += 2 * Math.PI; + RaisePredictiveEvent(new RequestMouseRotatorRotationEvent { - Direction = angleDir, + Rotation = rotation }); return; diff --git a/Content.Shared/MouseRotator/MouseRotatorComponent.cs b/Content.Shared/MouseRotator/MouseRotatorComponent.cs index a35dfe0a28..2844b3cb8b 100644 --- a/Content.Shared/MouseRotator/MouseRotatorComponent.cs +++ b/Content.Shared/MouseRotator/MouseRotatorComponent.cs @@ -30,8 +30,7 @@ public sealed partial class MouseRotatorComponent : Component public double RotationSpeed = float.MaxValue; /// - /// This one is important. If this is true, does not apply, and the system will - /// use instead. In this mode, the client will only send + /// This one is important. If this is true, does not apply. In this mode, the client will only send /// events when an entity should snap to a different cardinal direction, rather than for every angle change. /// /// This is useful for cases like humans, where what really matters is the visual sprite direction, as opposed to something @@ -50,13 +49,3 @@ public sealed class RequestMouseRotatorRotationEvent : EntityEventArgs { public Angle Rotation; } - -/// -/// Simpler version of for implementations -/// that only require snapping to 4-dir and not full angle rotation. -/// -[Serializable, NetSerializable] -public sealed class RequestMouseRotatorRotationSimpleEvent : EntityEventArgs -{ - public Direction Direction; -} diff --git a/Content.Shared/MouseRotator/SharedMouseRotatorSystem.cs b/Content.Shared/MouseRotator/SharedMouseRotatorSystem.cs index c57d477bd2..9663b3363d 100644 --- a/Content.Shared/MouseRotator/SharedMouseRotatorSystem.cs +++ b/Content.Shared/MouseRotator/SharedMouseRotatorSystem.cs @@ -1,5 +1,4 @@ using Content.Shared.Interaction; -using Robust.Shared.Timing; namespace Content.Shared.MouseRotator; @@ -16,7 +15,6 @@ public abstract class SharedMouseRotatorSystem : EntitySystem base.Initialize(); SubscribeAllEvent(OnRequestRotation); - SubscribeAllEvent(OnRequestSimpleRotation); } public override void Update(float frameTime) @@ -50,7 +48,7 @@ public abstract class SharedMouseRotatorSystem : EntitySystem private void OnRequestRotation(RequestMouseRotatorRotationEvent msg, EntitySessionEventArgs args) { if (args.SenderSession.AttachedEntity is not { } ent - || !TryComp(ent, out var rotator) || rotator.Simple4DirMode) + || !TryComp(ent, out var rotator)) { Log.Error($"User {args.SenderSession.Name} ({args.SenderSession.UserId}) tried setting local rotation directly without a valid mouse rotator component attached!"); return; @@ -59,17 +57,4 @@ public abstract class SharedMouseRotatorSystem : EntitySystem rotator.GoalRotation = msg.Rotation; Dirty(ent, rotator); } - - private void OnRequestSimpleRotation(RequestMouseRotatorRotationSimpleEvent ev, EntitySessionEventArgs args) - { - if (args.SenderSession.AttachedEntity is not { } ent - || !TryComp(ent, out var rotator) || !rotator.Simple4DirMode) - { - Log.Error($"User {args.SenderSession.Name} ({args.SenderSession.UserId}) tried setting 4-dir rotation directly without a valid mouse rotator component attached!"); - return; - } - - rotator.GoalRotation = ev.Direction.ToAngle(); - Dirty(ent, rotator); - } } From 5c8962823e42441a8c8e3022974ca86ae90011ab Mon Sep 17 00:00:00 2001 From: PJBot Date: Tue, 2 Jul 2024 13:05:23 +0000 Subject: [PATCH 41/86] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 8da32dc372..6e97ab1da4 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Velcroboy - changes: - - message: Fixed sec/lawyer and vault airlocks - type: Fix - id: 6358 - time: '2024-04-15T22:22:16.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26980 - author: tosatur changes: - message: Made clown snoring quieter @@ -3821,3 +3814,10 @@ id: 6857 time: '2024-07-02T03:30:53.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29653 +- author: slarticodefast + changes: + - message: Fixed sprite rotation in harm mode on rotated grids. + type: Fix + id: 6858 + time: '2024-07-02T13:04:15.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29663 From 2a6b7dbaf95632d3596511ce6f38988d3d455475 Mon Sep 17 00:00:00 2001 From: lzk <124214523+lzk228@users.noreply.github.com> Date: Tue, 2 Jul 2024 15:32:21 +0200 Subject: [PATCH 42/86] Add ability to add additional friendly and hostile factions in prototypes (#29636) * Make friendly and hostile factions in NpcFactionMemberComponent datafiels * :trollface: * :trollface: --- .../NPC/Components/NpcFactionMemberComponent.cs | 12 ++++++++++++ Content.Shared/NPC/Systems/NpcFactionSystem.cs | 9 +++++++++ 2 files changed, 21 insertions(+) diff --git a/Content.Shared/NPC/Components/NpcFactionMemberComponent.cs b/Content.Shared/NPC/Components/NpcFactionMemberComponent.cs index 91521e9854..208dfd4681 100644 --- a/Content.Shared/NPC/Components/NpcFactionMemberComponent.cs +++ b/Content.Shared/NPC/Components/NpcFactionMemberComponent.cs @@ -25,4 +25,16 @@ public sealed partial class NpcFactionMemberComponent : Component /// [ViewVariables] public readonly HashSet> HostileFactions = new(); + + /// + /// Used to add friendly factions in prototypes. + /// + [DataField, ViewVariables] + public HashSet>? AddFriendlyFactions; + + /// + /// Used to add hostile factions in prototypes. + /// + [DataField, ViewVariables] + public HashSet>? AddHostileFactions; } diff --git a/Content.Shared/NPC/Systems/NpcFactionSystem.cs b/Content.Shared/NPC/Systems/NpcFactionSystem.cs index ad81f01e1d..355f5bbb3a 100644 --- a/Content.Shared/NPC/Systems/NpcFactionSystem.cs +++ b/Content.Shared/NPC/Systems/NpcFactionSystem.cs @@ -59,6 +59,15 @@ public sealed partial class NpcFactionSystem : EntitySystem ent.Comp.FriendlyFactions.UnionWith(factionData.Friendly); ent.Comp.HostileFactions.UnionWith(factionData.Hostile); } + // Add additional factions if it is written in prototype + if (ent.Comp.AddFriendlyFactions != null) + { + ent.Comp.FriendlyFactions.UnionWith(ent.Comp.AddFriendlyFactions); + } + if (ent.Comp.AddHostileFactions != null) + { + ent.Comp.HostileFactions.UnionWith(ent.Comp.AddHostileFactions); + } } /// From c0a1e6ea15a44a48e3f11c14509ea24406b7b352 Mon Sep 17 00:00:00 2001 From: lzk <124214523+lzk228@users.noreply.github.com> Date: Tue, 2 Jul 2024 15:33:48 +0200 Subject: [PATCH 43/86] space law book (#29392) * Space Law Book * crate --- .../Catalog/Fills/Books/bookshelf.yml | 2 ++ .../Catalog/Fills/Crates/service.yml | 1 + .../Catalog/Fills/Lockers/heads.yml | 2 ++ .../Catalog/Fills/Lockers/security.yml | 4 +++ .../VendingMachines/Inventories/lawdrobe.yml | 1 + .../Entities/Objects/Misc/books.yml | 28 +++++++++++++++++++ .../Prototypes/Roles/Jobs/Civilian/lawyer.yml | 2 +- 7 files changed, 39 insertions(+), 1 deletion(-) diff --git a/Resources/Prototypes/Catalog/Fills/Books/bookshelf.yml b/Resources/Prototypes/Catalog/Fills/Books/bookshelf.yml index 09c7e165bf..a0b6b57ec1 100644 --- a/Resources/Prototypes/Catalog/Fills/Books/bookshelf.yml +++ b/Resources/Prototypes/Catalog/Fills/Books/bookshelf.yml @@ -35,6 +35,8 @@ orGroup: BookPool - id: BookChemicalCompendium orGroup: BookPool + - id: BookSpaceLaw + orGroup: BookPool - id: BookNarsieLegend prob: 0.1 orGroup: BookAuthor diff --git a/Resources/Prototypes/Catalog/Fills/Crates/service.yml b/Resources/Prototypes/Catalog/Fills/Crates/service.yml index f4c47cfe9e..3950c4f8fd 100644 --- a/Resources/Prototypes/Catalog/Fills/Crates/service.yml +++ b/Resources/Prototypes/Catalog/Fills/Crates/service.yml @@ -183,6 +183,7 @@ - id: BookMedicalReferenceBook - id: BookHowToSurvive - id: BookChemicalCompendium + - id: BookSpaceLaw - type: entity id: CrateServiceSodaDispenser diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml b/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml index 11863ea51d..af45bdfc6a 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml @@ -278,6 +278,7 @@ - id: BoxEncryptionKeySecurity - id: HoloprojectorSecurity - id: BookSecretDocuments + - id: BookSpaceLaw - type: entity id: LockerHeadOfSecurityFilled @@ -301,6 +302,7 @@ - id: BoxEncryptionKeySecurity - id: HoloprojectorSecurity - id: BookSecretDocuments + - id: BookSpaceLaw - type: entity id: LockerFreezerVaultFilled diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/security.yml b/Resources/Prototypes/Catalog/Fills/Lockers/security.yml index 2896494978..002996330b 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/security.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/security.yml @@ -19,6 +19,7 @@ - id: DoorRemoteArmory - id: ClothingOuterHardsuitWarden - id: HoloprojectorSecurity + - id: BookSpaceLaw - type: entity id: LockerWardenFilled @@ -40,6 +41,7 @@ - id: RubberStampWarden - id: DoorRemoteArmory - id: HoloprojectorSecurity + - id: BookSpaceLaw - type: entity id: LockerSecurityFilled @@ -66,6 +68,8 @@ prob: 0.1 - id: HoloprojectorSecurity prob: 0.6 + - id: BookSpaceLaw + prob: 0.5 - type: entity id: LockerBrigmedicFilled diff --git a/Resources/Prototypes/Catalog/VendingMachines/Inventories/lawdrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Inventories/lawdrobe.yml index 28f63e44e2..75760ce519 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Inventories/lawdrobe.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Inventories/lawdrobe.yml @@ -15,6 +15,7 @@ ClothingHeadsetService: 2 ClothingNeckLawyerbadge: 2 BriefcaseBrown: 2 + BookSpaceLaw: 3 LuxuryPen: 2 contrabandInventory: ClothingOuterRobesJudge: 1 diff --git a/Resources/Prototypes/Entities/Objects/Misc/books.yml b/Resources/Prototypes/Entities/Objects/Misc/books.yml index fd4cac781b..d5cf78c65b 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/books.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/books.yml @@ -353,6 +353,34 @@ guides: - Chemicals +- type: entity + id: BookSpaceLaw + parent: BaseItem + name: space law + description: A set of Nanotrasen guidelines for keeping law and order on their space stations. + components: + - type: Sprite + sprite: Objects/Misc/books.rsi + layers: + - state: paper + - state: cover_strong + color: "#87011c" + - state: icon_law + color: "#f7d61a" + - type: Tag + tags: + - Book + - type: GuideHelp + openOnActivation: true + guides: + - SpaceLaw + - type: MeleeWeapon # so you can beat stupid cadets + soundHit: + collection: Punch + damage: + types: + Blunt: 1 + - type: entity parent: BookBase id: BookRandom diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/lawyer.yml b/Resources/Prototypes/Roles/Jobs/Civilian/lawyer.yml index 58a8ebece1..713cde0e0c 100644 --- a/Resources/Prototypes/Roles/Jobs/Civilian/lawyer.yml +++ b/Resources/Prototypes/Roles/Jobs/Civilian/lawyer.yml @@ -21,9 +21,9 @@ shoes: ClothingShoesBootsLaceup id: LawyerPDA ears: ClothingHeadsetSecurity - # TODO add copy of space law inhand: - BriefcaseBrownFilled storage: back: - RubberStampLawyer + - BookSpaceLaw From d89ea4dac731bf030a9bcce330a5d1cb34cf8f8b Mon Sep 17 00:00:00 2001 From: PJBot Date: Tue, 2 Jul 2024 13:34:55 +0000 Subject: [PATCH 44/86] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 6e97ab1da4..fe1e352e09 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: tosatur - changes: - - message: Made clown snoring quieter - type: Tweak - id: 6359 - time: '2024-04-16T18:48:38.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/27012 - author: DEATHB4DEFEAT, Dutch-VanDerLinde, metalgearsloth and musicmanvr changes: - message: Added loadouts @@ -3821,3 +3814,10 @@ id: 6858 time: '2024-07-02T13:04:15.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29663 +- author: lzk228 + changes: + - message: Space Law book added to the game. + type: Add + id: 6859 + time: '2024-07-02T13:33:49.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29392 From 95f76efd805043a34cb858ff7d61850a5c0a253e Mon Sep 17 00:00:00 2001 From: Brandon Hu <103440971+Brandon-Huu@users.noreply.github.com> Date: Tue, 2 Jul 2024 13:51:25 +0000 Subject: [PATCH 45/86] fix(greenshift): remove meteors from greenshift (#29271) --- Resources/Prototypes/game_presets.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/Resources/Prototypes/game_presets.yml b/Resources/Prototypes/game_presets.yml index 39523a1a37..3a3f07760b 100644 --- a/Resources/Prototypes/game_presets.yml +++ b/Resources/Prototypes/game_presets.yml @@ -46,7 +46,6 @@ description: greenshift-description rules: - BasicRoundstartVariation - - GameRuleMeteorScheduler - type: gamePreset id: Secret From 59ce9e6dc7d982cfed0abde2870450d030afbd94 Mon Sep 17 00:00:00 2001 From: Ko4ergaPunk <62609550+Ko4ergaPunk@users.noreply.github.com> Date: Tue, 2 Jul 2024 18:28:49 +0300 Subject: [PATCH 46/86] pipetka) (#29667) --- Resources/Prototypes/Entities/Clothing/Belt/belts.yml | 2 ++ .../Entities/Objects/Specific/Chemistry/chem_bag.yml | 1 + Resources/Prototypes/Entities/Objects/Specific/chemistry.yml | 4 ++++ Resources/Prototypes/tags.yml | 3 +++ 4 files changed, 10 insertions(+) diff --git a/Resources/Prototypes/Entities/Clothing/Belt/belts.yml b/Resources/Prototypes/Entities/Clothing/Belt/belts.yml index 66a9c60511..32170331ff 100644 --- a/Resources/Prototypes/Entities/Clothing/Belt/belts.yml +++ b/Resources/Prototypes/Entities/Clothing/Belt/belts.yml @@ -269,6 +269,7 @@ - Radio - DiscreteHealthAnalyzer - SurgeryTool + - Dropper components: - Hypospray - Injector @@ -343,6 +344,7 @@ - Bottle - Syringe - CigPack + - Dropper components: - Seed - Smokable diff --git a/Resources/Prototypes/Entities/Objects/Specific/Chemistry/chem_bag.yml b/Resources/Prototypes/Entities/Objects/Specific/Chemistry/chem_bag.yml index a4692db9b5..4af29b6632 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Chemistry/chem_bag.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Chemistry/chem_bag.yml @@ -29,4 +29,5 @@ - PillCanister - Bottle - Syringe + - Dropper - type: Dumpable diff --git a/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml b/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml index dd15a90baa..c888559bc3 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml @@ -294,6 +294,7 @@ solution: injector - type: Item sprite: Objects/Specific/Chemistry/dropper.rsi + size: Tiny - type: Appearance - type: SolutionContainerVisuals maxFillLevels: 1 @@ -302,6 +303,9 @@ inHandsFillBaseName: -fill- - type: StaticPrice price: 40 + - type: Tag + tags: + - Dropper - type: entity name: borgdropper diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index dee16b7414..ca593e8631 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -545,6 +545,9 @@ - type: Tag id: DrinkSpaceGlue +- type: Tag + id: Dropper + - type: Tag id: Duck From 542e1db91316e20ea74582f79095a76cea7299ed Mon Sep 17 00:00:00 2001 From: PJBot Date: Tue, 2 Jul 2024 15:29:55 +0000 Subject: [PATCH 47/86] Automatic changelog update --- Resources/Changelog/Changelog.yml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index fe1e352e09..45ebc2d596 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: DEATHB4DEFEAT, Dutch-VanDerLinde, metalgearsloth and musicmanvr - changes: - - message: Added loadouts - type: Add - id: 6360 - time: '2024-04-16T19:57:41.736477+00:00' - url: null - author: Dutch-VanDerLinde changes: - message: Senior role ID cards now function properly. @@ -3821,3 +3814,12 @@ id: 6859 time: '2024-07-02T13:33:49.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29392 +- author: Ko4erga + changes: + - message: Dropper can be placed in med belt, plant belt and chemistry bag. + type: Tweak + - message: Dropper size changed. + type: Tweak + id: 6860 + time: '2024-07-02T15:28:49.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29667 From 4ec15c84fa220c6ff60477f0a53c6598eac2685d Mon Sep 17 00:00:00 2001 From: Mervill Date: Tue, 2 Jul 2024 13:18:56 -0700 Subject: [PATCH 48/86] Clean up gas miners (#29657) Separate the environment check from CapSpawnAmount into GetValidEnvironment to make the code a little cleaner, and also makes these two checks independent. CapSpawnAmount and GetValidEnvironment now both have zero side-effects Broken renamed Idle to reflect its use. Broken in my mind implies that there's some method for fixing. --------- Co-authored-by: Partmedia --- .../Other/Components/GasMinerComponent.cs | 10 +++++- .../Other/EntitySystems/GasMinerSystem.cs | 36 ++++++++++--------- .../Structures/Piping/Atmospherics/miners.yml | 14 -------- 3 files changed, 29 insertions(+), 31 deletions(-) diff --git a/Content.Server/Atmos/Piping/Other/Components/GasMinerComponent.cs b/Content.Server/Atmos/Piping/Other/Components/GasMinerComponent.cs index 18a0b9b078..1775ec5dad 100644 --- a/Content.Server/Atmos/Piping/Other/Components/GasMinerComponent.cs +++ b/Content.Server/Atmos/Piping/Other/Components/GasMinerComponent.cs @@ -5,14 +5,22 @@ namespace Content.Server.Atmos.Piping.Other.Components [RegisterComponent] public sealed partial class GasMinerComponent : Component { + [ViewVariables(VVAccess.ReadWrite)] public bool Enabled { get; set; } = true; - public bool Broken { get; set; } = false; + [ViewVariables(VVAccess.ReadOnly)] + public bool Idle { get; set; } = false; + /// + /// If the number of moles in the external environment exceeds this number, no gas will be mined. + /// [ViewVariables(VVAccess.ReadWrite)] [DataField("maxExternalAmount")] public float MaxExternalAmount { get; set; } = float.PositiveInfinity; + /// + /// If the pressure (in kPA) of the external environment exceeds this number, no gas will be mined. + /// [ViewVariables(VVAccess.ReadWrite)] [DataField("maxExternalPressure")] public float MaxExternalPressure { get; set; } = Atmospherics.GasMinerDefaultMaxExternalPressure; diff --git a/Content.Server/Atmos/Piping/Other/EntitySystems/GasMinerSystem.cs b/Content.Server/Atmos/Piping/Other/EntitySystems/GasMinerSystem.cs index 1aa5973c96..dd46fb6b4c 100644 --- a/Content.Server/Atmos/Piping/Other/EntitySystems/GasMinerSystem.cs +++ b/Content.Server/Atmos/Piping/Other/EntitySystems/GasMinerSystem.cs @@ -25,10 +25,17 @@ namespace Content.Server.Atmos.Piping.Other.EntitySystems { var miner = ent.Comp; + if (!GetValidEnvironment(ent, out var environment)) + { + miner.Idle = true; + return; + } + // SpawnAmount is declared in mol/s so to get the amount of gas we hope to mine, we have to multiply this by // how long we have been waiting to spawn it and further cap the number according to the miner's state. - var toSpawn = CapSpawnAmount(ent, miner.SpawnAmount * args.dt, out var environment); - if (toSpawn <= 0f || environment == null || !miner.Enabled || !miner.SpawnGas.HasValue) + var toSpawn = CapSpawnAmount(ent, miner.SpawnAmount * args.dt, environment); + miner.Idle = toSpawn == 0; + if (miner.Idle || !miner.Enabled || !miner.SpawnGas.HasValue) return; // Time to mine some gas. @@ -39,27 +46,26 @@ namespace Content.Server.Atmos.Piping.Other.EntitySystems _atmosphereSystem.Merge(environment, merger); } - private float CapSpawnAmount(Entity ent, float toSpawnTarget, out GasMixture? environment) + private bool GetValidEnvironment(Entity ent, [NotNullWhen(true)] out GasMixture? environment) { var (uid, miner) = ent; var transform = Transform(uid); - environment = _atmosphereSystem.GetContainingMixture((uid, transform), true, true); - var position = _transformSystem.GetGridOrMapTilePosition(uid, transform); - // Space. + // Treat space as an invalid environment if (_atmosphereSystem.IsTileSpace(transform.GridUid, transform.MapUid, position)) { - miner.Broken = true; - return 0f; + environment = null; + return false; } - // Air-blocked location. - if (environment == null) - { - miner.Broken = true; - return 0f; - } + environment = _atmosphereSystem.GetContainingMixture((uid, transform), true, true); + return environment != null; + } + + private float CapSpawnAmount(Entity ent, float toSpawnTarget, GasMixture environment) + { + var (uid, miner) = ent; // How many moles could we theoretically spawn. Cap by pressure and amount. var allowableMoles = Math.Min( @@ -69,11 +75,9 @@ namespace Content.Server.Atmos.Piping.Other.EntitySystems var toSpawnReal = Math.Clamp(allowableMoles, 0f, toSpawnTarget); if (toSpawnReal < Atmospherics.GasMinMoles) { - miner.Broken = true; return 0f; } - miner.Broken = false; return toSpawnReal; } } diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/miners.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/miners.yml index 6d10b1521e..64dd38accb 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/miners.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/miners.yml @@ -66,8 +66,6 @@ parent: GasMinerBase id: GasMinerNitrogen suffix: Shuttle, 300kPa - placement: - mode: SnapgridCenter components: - type: GasMiner spawnGas: Nitrogen @@ -94,8 +92,6 @@ name: CO2 gas miner parent: GasMinerBase id: GasMinerCarbonDioxide - placement: - mode: SnapgridCenter components: - type: GasMiner spawnGas: CarbonDioxide @@ -104,8 +100,6 @@ name: plasma gas miner parent: GasMinerBase id: GasMinerPlasma - placement: - mode: SnapgridCenter components: - type: GasMiner spawnGas: Plasma @@ -114,8 +108,6 @@ name: tritium gas miner parent: GasMinerBase id: GasMinerTritium - placement: - mode: SnapgridCenter components: - type: GasMiner spawnGas: Tritium @@ -124,8 +116,6 @@ name: water vapor gas miner parent: GasMinerBase id: GasMinerWaterVapor - placement: - mode: SnapgridCenter components: - type: GasMiner spawnGas: WaterVapor @@ -134,8 +124,6 @@ name: ammonia gas miner parent: GasMinerBase id: GasMinerAmmonia - placement: - mode: SnapgridCenter components: - type: GasMiner spawnGas: Ammonia @@ -144,8 +132,6 @@ name: nitrous oxide gas miner parent: GasMinerBase id: GasMinerNitrousOxide - placement: - mode: SnapgridCenter components: - type: GasMiner spawnGas: NitrousOxide From 58d46ddd46c250a4df283638c3ba0295cae26194 Mon Sep 17 00:00:00 2001 From: lzk <124214523+lzk228@users.noreply.github.com> Date: Wed, 3 Jul 2024 02:01:17 +0200 Subject: [PATCH 49/86] Fix GhostCommand naming (#29671) --- Content.Server/Ghost/{Ghost.cs => GhostCommand.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename Content.Server/Ghost/{Ghost.cs => GhostCommand.cs} (96%) diff --git a/Content.Server/Ghost/Ghost.cs b/Content.Server/Ghost/GhostCommand.cs similarity index 96% rename from Content.Server/Ghost/Ghost.cs rename to Content.Server/Ghost/GhostCommand.cs index 69d81d9592..b553d64201 100644 --- a/Content.Server/Ghost/Ghost.cs +++ b/Content.Server/Ghost/GhostCommand.cs @@ -7,7 +7,7 @@ using Robust.Shared.Console; namespace Content.Server.Ghost { [AnyCommand] - public sealed class Ghost : IConsoleCommand + public sealed class GhostCommand : IConsoleCommand { [Dependency] private readonly IEntityManager _entities = default!; From cfc0247e5c45e234b860fbd64e0bd70dfe1dc83b Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Tue, 2 Jul 2024 20:01:37 -0400 Subject: [PATCH 50/86] Code Cleanup: Integration Tests (#29584) * Cleanup PuddleTest * Cleanup GravityGridTest * Cleanup PowerTest * Cleanup SaveLoadMapTest * Cleanup Body tests * Cleanup ContainerOcclusionTest * Cleanup AirlockTest * Cleanup DamageableTest * Cleanup EntityTest * Cleanup FluidSpillTest * Cleanup FollowerSystemTest * Cleanup HandCuffTest * Cleanup InteractionSystemTests * Cleanup InRangeUnobstructed * Cleanup SimplePredictReconcileTest * Cleanup PostMapInitTest * Cleanup SalvageTest * Cleanup SaveLoadSaveTest * Cleanup ShuttleTest * Cleanup MaterialArbitrageTest * Cleanup PrototypeSaveTest * Fix ShuttleTest * Bunch of small ones * Move JobTests to Station directory * More small fixes * Cleanup InteractionTest.Helpers Had to change a method signature, so some callers were modified too. * Missed one --- .../Tests/Actions/ActionsAddedTest.cs | 2 +- .../Tests/Body/GibTest.cs | 2 +- .../Tests/Body/LegTest.cs | 8 +- .../Tests/Body/LungTest.cs | 8 +- .../Tests/Body/SaveLoadReparentTest.cs | 5 +- .../Tests/Buckle/BuckleDragTest.cs | 4 +- Content.IntegrationTests/Tests/CargoTest.cs | 6 +- .../Chemistry/FixedPoint2SerializationTest.cs | 8 +- .../Tests/Chemistry/SolutionRoundingTest.cs | 4 +- .../Tests/Chemistry/SolutionSystemTests.cs | 12 +- .../Tests/Chemistry/TryAllReactionsTest.cs | 4 +- .../Tests/Commands/PardonCommand.cs | 10 +- .../Tests/Commands/RejuvenateTest.cs | 6 +- .../Tests/Commands/RestartRoundTest.cs | 2 +- .../Tests/ContainerOcclusionTest.cs | 14 +- .../Tests/Damageable/DamageSpecifierTest.cs | 44 +-- .../Tests/Damageable/DamageableTest.cs | 5 +- .../Tests/Doors/AirlockTest.cs | 20 +- .../Tests/DummyIconTest.cs | 2 +- Content.IntegrationTests/Tests/EntityTest.cs | 12 +- .../Tests/Fluids/FluidSpillTest.cs | 27 +- .../Tests/Fluids/PuddleTest.cs | 14 +- .../Tests/FollowerSystemTest.cs | 5 +- .../Components/ActionBlocking/HandCuffTest.cs | 7 +- .../Tests/GameRules/AntagPreferenceTest.cs | 2 +- .../Tests/GameRules/FailAndStartPresetTest.cs | 2 +- .../Tests/GravityGridTest.cs | 30 +- .../Click/InteractionSystemTests.cs | 50 +--- .../Tests/Interaction/InRangeUnobstructed.cs | 5 +- .../Interaction/InteractionTest.Helpers.cs | 28 +- .../Tests/Interaction/InteractionTest.cs | 15 +- .../Tests/Linter/StaticFieldValidationTest.cs | 118 ++++---- .../Tests/MachineBoardTest.cs | 11 +- .../Tests/Mapping/MappingTests.cs | 2 +- .../Tests/MaterialArbitrageTest.cs | 17 +- .../Tests/Minds/GhostTests.cs | 28 +- .../Tests/Minds/MindTests.Helpers.cs | 2 +- .../Tests/Movement/BuckleMovementTest.cs | 4 +- .../Tests/Movement/MovementTest.cs | 2 +- .../Tests/Networking/PvsCommandTest.cs | 4 +- .../Networking/SimplePredictReconcileTest.cs | 9 +- .../Tests/PostMapInitTest.cs | 13 +- .../Tests/Power/PowerTest.cs | 272 +++++++++--------- .../Tests/PrototypeSaveTest.cs | 5 +- .../Tests/Puller/PullerTest.cs | 2 +- .../Tests/ResearchTest.cs | 4 +- .../Tests/Round/JobTest.cs | 44 +-- Content.IntegrationTests/Tests/SalvageTest.cs | 4 +- .../Tests/SaveLoadMapTest.cs | 21 +- .../Tests/SaveLoadSaveTest.cs | 20 +- .../Tests/Serialization/SerializationTest.cs | 16 +- Content.IntegrationTests/Tests/ShuttleTest.cs | 19 +- .../Tests/Sprite/ItemSpriteTest.cs | 10 +- .../Tests/{Minds => Station}/JobTests.cs | 0 .../Tests/Toolshed/ToolshedTest.cs | 8 +- .../Tests/VendingMachineRestockTest.cs | 3 +- 56 files changed, 510 insertions(+), 491 deletions(-) rename Content.IntegrationTests/Tests/{Minds => Station}/JobTests.cs (100%) diff --git a/Content.IntegrationTests/Tests/Actions/ActionsAddedTest.cs b/Content.IntegrationTests/Tests/Actions/ActionsAddedTest.cs index 32b1525226..c232e82313 100644 --- a/Content.IntegrationTests/Tests/Actions/ActionsAddedTest.cs +++ b/Content.IntegrationTests/Tests/Actions/ActionsAddedTest.cs @@ -18,7 +18,7 @@ public sealed class ActionsAddedTest [Test] public async Task TestCombatActionsAdded() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings { Connected = true, DummyTicker = false}); + await using var pair = await PoolManager.GetServerClient(new PoolSettings { Connected = true, DummyTicker = false }); var server = pair.Server; var client = pair.Client; var sEntMan = server.ResolveDependency(); diff --git a/Content.IntegrationTests/Tests/Body/GibTest.cs b/Content.IntegrationTests/Tests/Body/GibTest.cs index c0032a8524..4627c79f64 100644 --- a/Content.IntegrationTests/Tests/Body/GibTest.cs +++ b/Content.IntegrationTests/Tests/Body/GibTest.cs @@ -5,7 +5,7 @@ using Robust.Shared.GameObjects; namespace Content.IntegrationTests.Tests.Body; [TestFixture] -public sealed class GibTest +public sealed class GibTest { [Test] public async Task TestGib() diff --git a/Content.IntegrationTests/Tests/Body/LegTest.cs b/Content.IntegrationTests/Tests/Body/LegTest.cs index e86966f8f5..7b49bbe84a 100644 --- a/Content.IntegrationTests/Tests/Body/LegTest.cs +++ b/Content.IntegrationTests/Tests/Body/LegTest.cs @@ -5,7 +5,6 @@ using Content.Shared.Body.Part; using Content.Shared.Rotation; using Robust.Shared.GameObjects; using Robust.Shared.Map; -using Robust.Shared.Maths; namespace Content.IntegrationTests.Tests.Body { @@ -40,13 +39,14 @@ namespace Content.IntegrationTests.Tests.Body var appearanceSystem = entityManager.System(); var xformSystem = entityManager.System(); + var map = await pair.CreateTestMap(); + await server.WaitAssertion(() => { - var mapId = mapManager.CreateMap(); BodyComponent body = null; human = entityManager.SpawnEntity("HumanBodyAndAppearanceDummy", - new MapCoordinates(Vector2.Zero, mapId)); + new MapCoordinates(Vector2.Zero, map.MapId)); Assert.Multiple(() => { @@ -61,7 +61,7 @@ namespace Content.IntegrationTests.Tests.Body foreach (var leg in legs) { - xformSystem.DetachParentToNull(leg.Id, entityManager.GetComponent(leg.Id)); + xformSystem.DetachEntity(leg.Id, entityManager.GetComponent(leg.Id)); } }); diff --git a/Content.IntegrationTests/Tests/Body/LungTest.cs b/Content.IntegrationTests/Tests/Body/LungTest.cs index dce3741c98..9b5ee431f1 100644 --- a/Content.IntegrationTests/Tests/Body/LungTest.cs +++ b/Content.IntegrationTests/Tests/Body/LungTest.cs @@ -60,8 +60,8 @@ namespace Content.IntegrationTests.Tests.Body var mapManager = server.ResolveDependency(); var entityManager = server.ResolveDependency(); var mapLoader = entityManager.System(); + var mapSys = entityManager.System(); - MapId mapId; EntityUid? grid = null; BodyComponent body = default; RespiratorComponent resp = default; @@ -73,7 +73,7 @@ namespace Content.IntegrationTests.Tests.Body await server.WaitPost(() => { - mapId = mapManager.CreateMap(); + mapSys.CreateMap(out var mapId); Assert.That(mapLoader.TryLoad(mapId, testMapName, out var roots)); var query = entityManager.GetEntityQuery(); @@ -142,8 +142,8 @@ namespace Content.IntegrationTests.Tests.Body var entityManager = server.ResolveDependency(); var cfg = server.ResolveDependency(); var mapLoader = entityManager.System(); + var mapSys = entityManager.System(); - MapId mapId; EntityUid? grid = null; RespiratorComponent respirator = null; EntityUid human = default; @@ -152,7 +152,7 @@ namespace Content.IntegrationTests.Tests.Body await server.WaitPost(() => { - mapId = mapManager.CreateMap(); + mapSys.CreateMap(out var mapId); Assert.That(mapLoader.TryLoad(mapId, testMapName, out var ents), Is.True); var query = entityManager.GetEntityQuery(); diff --git a/Content.IntegrationTests/Tests/Body/SaveLoadReparentTest.cs b/Content.IntegrationTests/Tests/Body/SaveLoadReparentTest.cs index 670ce1a474..01482ba8ee 100644 --- a/Content.IntegrationTests/Tests/Body/SaveLoadReparentTest.cs +++ b/Content.IntegrationTests/Tests/Body/SaveLoadReparentTest.cs @@ -33,10 +33,11 @@ public sealed class SaveLoadReparentTest var mapLoader = entities.System(); var bodySystem = entities.System(); var containerSystem = entities.System(); + var mapSys = entities.System(); await server.WaitAssertion(() => { - var mapId = maps.CreateMap(); + mapSys.CreateMap(out var mapId); maps.CreateGrid(mapId); var human = entities.SpawnEntity("HumanBodyDummy", new MapCoordinates(0, 0, mapId)); @@ -115,7 +116,7 @@ public sealed class SaveLoadReparentTest mapLoader.SaveMap(mapId, mapPath); maps.DeleteMap(mapId); - mapId = maps.CreateMap(); + mapSys.CreateMap(out mapId); Assert.That(mapLoader.TryLoad(mapId, mapPath, out _), Is.True); var query = EnumerateQueryEnumerator( diff --git a/Content.IntegrationTests/Tests/Buckle/BuckleDragTest.cs b/Content.IntegrationTests/Tests/Buckle/BuckleDragTest.cs index 8df151d5a0..82d5d3baa0 100644 --- a/Content.IntegrationTests/Tests/Buckle/BuckleDragTest.cs +++ b/Content.IntegrationTests/Tests/Buckle/BuckleDragTest.cs @@ -38,13 +38,13 @@ public sealed class BuckleDragTest : InteractionTest await RunTicks(5); Assert.That(buckle.Buckled, Is.True); Assert.That(buckle.BuckledTo, Is.EqualTo(STarget)); - Assert.That(strap.BuckledEntities, Is.EquivalentTo(new[]{sUrist})); + Assert.That(strap.BuckledEntities, Is.EquivalentTo(new[] { sUrist })); Assert.That(puller.Pulling, Is.Null); Assert.That(pullable.Puller, Is.Null); Assert.That(pullable.BeingPulled, Is.False); // Start pulling, and thus unbuckle them - await PressKey(ContentKeyFunctions.TryPullObject, cursorEntity:urist); + await PressKey(ContentKeyFunctions.TryPullObject, cursorEntity: urist); await RunTicks(5); Assert.That(buckle.Buckled, Is.False); Assert.That(buckle.BuckledTo, Is.Null); diff --git a/Content.IntegrationTests/Tests/CargoTest.cs b/Content.IntegrationTests/Tests/CargoTest.cs index 8e1d536054..89018d9d17 100644 --- a/Content.IntegrationTests/Tests/CargoTest.cs +++ b/Content.IntegrationTests/Tests/CargoTest.cs @@ -19,11 +19,11 @@ namespace Content.IntegrationTests.Tests; [TestFixture] public sealed class CargoTest { - public static HashSet> Ignored = new () - { + private static readonly HashSet> Ignored = + [ // This is ignored because it is explicitly intended to be able to sell for more than it costs. new("FunCrateGambling") - }; + ]; [Test] public async Task NoCargoOrderArbitrage() diff --git a/Content.IntegrationTests/Tests/Chemistry/FixedPoint2SerializationTest.cs b/Content.IntegrationTests/Tests/Chemistry/FixedPoint2SerializationTest.cs index 8e3b89bff1..0e3f89c282 100644 --- a/Content.IntegrationTests/Tests/Chemistry/FixedPoint2SerializationTest.cs +++ b/Content.IntegrationTests/Tests/Chemistry/FixedPoint2SerializationTest.cs @@ -9,10 +9,10 @@ namespace Content.IntegrationTests.Tests.Chemistry { public sealed class FixedPoint2SerializationTest : SerializationTest { - protected override Assembly[] Assemblies => new[] - { + protected override Assembly[] Assemblies => + [ typeof(FixedPoint2SerializationTest).Assembly - }; + ]; [Test] public void DeserializeNullTest() @@ -53,6 +53,6 @@ namespace Content.IntegrationTests.Tests.Chemistry [DataDefinition] public sealed partial class FixedPoint2TestDefinition { - [DataField("unit")] public FixedPoint2? Unit { get; set; } = FixedPoint2.New(5); + [DataField] public FixedPoint2? Unit { get; set; } = FixedPoint2.New(5); } } diff --git a/Content.IntegrationTests/Tests/Chemistry/SolutionRoundingTest.cs b/Content.IntegrationTests/Tests/Chemistry/SolutionRoundingTest.cs index 4d19a96d9e..89d33186a2 100644 --- a/Content.IntegrationTests/Tests/Chemistry/SolutionRoundingTest.cs +++ b/Content.IntegrationTests/Tests/Chemistry/SolutionRoundingTest.cs @@ -1,5 +1,5 @@ -using Content.Server.Chemistry.Containers.EntitySystems; using Content.Shared.Chemistry.Components; +using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Chemistry.Reaction; using Content.Shared.Chemistry.Reagent; using Content.Shared.FixedPoint; @@ -76,7 +76,7 @@ public sealed class SolutionRoundingTest await server.WaitPost(() => { - var system = server.System(); + var system = server.System(); var beaker = server.EntMan.SpawnEntity("SolutionRoundingTestContainer", testMap.GridCoords); system.TryGetSolution(beaker, "beaker", out var newSolutionEnt, out var newSolution); diff --git a/Content.IntegrationTests/Tests/Chemistry/SolutionSystemTests.cs b/Content.IntegrationTests/Tests/Chemistry/SolutionSystemTests.cs index d96a035b2d..6b71dd08be 100644 --- a/Content.IntegrationTests/Tests/Chemistry/SolutionSystemTests.cs +++ b/Content.IntegrationTests/Tests/Chemistry/SolutionSystemTests.cs @@ -1,5 +1,5 @@ -using Content.Server.Chemistry.Containers.EntitySystems; using Content.Shared.Chemistry.Components; +using Content.Shared.Chemistry.EntitySystems; using Content.Shared.FixedPoint; using Robust.Shared.GameObjects; using Robust.Shared.Prototypes; @@ -11,7 +11,7 @@ namespace Content.IntegrationTests.Tests.Chemistry; // To ensure volume(A) + volume(B) = volume(A+B) // reactions can change this assumption [TestFixture] -[TestOf(typeof(SolutionContainerSystem))] +[TestOf(typeof(SharedSolutionContainerSystem))] public sealed class SolutionSystemTests { [TestPrototypes] @@ -51,7 +51,7 @@ public sealed class SolutionSystemTests var entityManager = server.ResolveDependency(); var protoMan = server.ResolveDependency(); - var containerSystem = entityManager.System(); + var containerSystem = entityManager.System(); var testMap = await pair.CreateTestMap(); var coordinates = testMap.GridCoords; @@ -97,7 +97,7 @@ public sealed class SolutionSystemTests var entityManager = server.ResolveDependency(); var protoMan = server.ResolveDependency(); - var containerSystem = entityManager.System(); + var containerSystem = entityManager.System(); var coordinates = testMap.GridCoords; EntityUid beaker; @@ -141,7 +141,7 @@ public sealed class SolutionSystemTests var entityManager = server.ResolveDependency(); var protoMan = server.ResolveDependency(); var testMap = await pair.CreateTestMap(); - var containerSystem = entityManager.System(); + var containerSystem = entityManager.System(); var coordinates = testMap.GridCoords; EntityUid beaker; @@ -194,7 +194,7 @@ public sealed class SolutionSystemTests var entityManager = server.ResolveDependency(); var protoMan = server.ResolveDependency(); - var containerSystem = entityManager.System(); + var containerSystem = entityManager.System(); var testMap = await pair.CreateTestMap(); var coordinates = testMap.GridCoords; diff --git a/Content.IntegrationTests/Tests/Chemistry/TryAllReactionsTest.cs b/Content.IntegrationTests/Tests/Chemistry/TryAllReactionsTest.cs index ddfe7b3481..3664cda922 100644 --- a/Content.IntegrationTests/Tests/Chemistry/TryAllReactionsTest.cs +++ b/Content.IntegrationTests/Tests/Chemistry/TryAllReactionsTest.cs @@ -1,4 +1,3 @@ -using Content.Server.Chemistry.Containers.EntitySystems; using Content.Shared.Chemistry.Reaction; using Content.Shared.Chemistry.Components; using Robust.Shared.GameObjects; @@ -6,6 +5,7 @@ using Robust.Shared.Map; using Robust.Shared.Prototypes; using Robust.Shared.Utility; using System.Linq; +using Content.Shared.Chemistry.EntitySystems; namespace Content.IntegrationTests.Tests.Chemistry { @@ -34,7 +34,7 @@ namespace Content.IntegrationTests.Tests.Chemistry var prototypeManager = server.ResolveDependency(); var testMap = await pair.CreateTestMap(); var coordinates = testMap.GridCoords; - var solutionContainerSystem = entityManager.System(); + var solutionContainerSystem = entityManager.System(); foreach (var reactionPrototype in prototypeManager.EnumeratePrototypes()) { diff --git a/Content.IntegrationTests/Tests/Commands/PardonCommand.cs b/Content.IntegrationTests/Tests/Commands/PardonCommand.cs index b3a66e3211..4db9eabf5c 100644 --- a/Content.IntegrationTests/Tests/Commands/PardonCommand.cs +++ b/Content.IntegrationTests/Tests/Commands/PardonCommand.cs @@ -28,7 +28,7 @@ namespace Content.IntegrationTests.Tests.Commands Assert.That(netMan.IsConnected); - Assert.That(sPlayerManager.Sessions.Count(), Is.EqualTo(1)); + Assert.That(sPlayerManager.Sessions, Has.Length.EqualTo(1)); // No bans on record Assert.Multiple(async () => { @@ -50,7 +50,7 @@ namespace Content.IntegrationTests.Tests.Commands var banReason = "test"; - Assert.That(sPlayerManager.Sessions.Count(), Is.EqualTo(1)); + Assert.That(sPlayerManager.Sessions, Has.Length.EqualTo(1)); // Ban the client for 24 hours await server.WaitPost(() => sConsole.ExecuteCommand($"ban {clientSession.Name} {banReason} 1440")); @@ -63,7 +63,7 @@ namespace Content.IntegrationTests.Tests.Commands }); await pair.RunTicksSync(5); - Assert.That(sPlayerManager.Sessions.Count(), Is.EqualTo(0)); + Assert.That(sPlayerManager.Sessions, Has.Length.EqualTo(0)); Assert.That(!netMan.IsConnected); // Try to pardon a ban that does not exist @@ -143,11 +143,11 @@ namespace Content.IntegrationTests.Tests.Commands }); // Reconnect client. Slightly faster than dirtying the pair. - Assert.That(sPlayerManager.Sessions.Count(), Is.EqualTo(0)); + Assert.That(sPlayerManager.Sessions, Is.Empty); client.SetConnectTarget(server); await client.WaitPost(() => netMan.ClientConnect(null!, 0, null!)); await pair.RunTicksSync(5); - Assert.That(sPlayerManager.Sessions.Count(), Is.EqualTo(1)); + Assert.That(sPlayerManager.Sessions, Has.Length.EqualTo(1)); await pair.CleanReturnAsync(); } diff --git a/Content.IntegrationTests/Tests/Commands/RejuvenateTest.cs b/Content.IntegrationTests/Tests/Commands/RejuvenateTest.cs index 2fda3ad58e..cfc8007306 100644 --- a/Content.IntegrationTests/Tests/Commands/RejuvenateTest.cs +++ b/Content.IntegrationTests/Tests/Commands/RejuvenateTest.cs @@ -37,9 +37,9 @@ namespace Content.IntegrationTests.Tests.Commands var server = pair.Server; var entManager = server.ResolveDependency(); var prototypeManager = server.ResolveDependency(); - var mobStateSystem = entManager.EntitySysManager.GetEntitySystem(); - var damSystem = entManager.EntitySysManager.GetEntitySystem(); - var rejuvenateSystem = entManager.EntitySysManager.GetEntitySystem(); + var mobStateSystem = entManager.System(); + var damSystem = entManager.System(); + var rejuvenateSystem = entManager.System(); await server.WaitAssertion(() => { diff --git a/Content.IntegrationTests/Tests/Commands/RestartRoundTest.cs b/Content.IntegrationTests/Tests/Commands/RestartRoundTest.cs index ff24ec0968..72a05b5246 100644 --- a/Content.IntegrationTests/Tests/Commands/RestartRoundTest.cs +++ b/Content.IntegrationTests/Tests/Commands/RestartRoundTest.cs @@ -26,7 +26,7 @@ namespace Content.IntegrationTests.Tests.Commands var configManager = server.ResolveDependency(); var entityManager = server.ResolveDependency(); - var gameTicker = entityManager.EntitySysManager.GetEntitySystem(); + var gameTicker = entityManager.System(); await pair.RunTicksSync(5); diff --git a/Content.IntegrationTests/Tests/ContainerOcclusionTest.cs b/Content.IntegrationTests/Tests/ContainerOcclusionTest.cs index c61a70faf0..37c4b0c9b5 100644 --- a/Content.IntegrationTests/Tests/ContainerOcclusionTest.cs +++ b/Content.IntegrationTests/Tests/ContainerOcclusionTest.cs @@ -43,11 +43,11 @@ namespace Content.IntegrationTests.Tests EntityUid dummy = default; var mapManager = server.ResolveDependency(); - var mapId = mapManager.CreateMap(); + var map = await pair.CreateTestMap(); await server.WaitPost(() => { - var pos = new MapCoordinates(Vector2.Zero, mapId); + var pos = new MapCoordinates(Vector2.Zero, map.MapId); var entStorage = serverEntManager.EntitySysManager.GetEntitySystem(); var container = serverEntManager.SpawnEntity("ContainerOcclusionA", pos); dummy = serverEntManager.SpawnEntity("ContainerOcclusionDummy", pos); @@ -85,11 +85,12 @@ namespace Content.IntegrationTests.Tests EntityUid dummy = default; var mapManager = server.ResolveDependency(); - var mapId = mapManager.CreateMap(); + + var map = await pair.CreateTestMap(); await server.WaitPost(() => { - var pos = new MapCoordinates(Vector2.Zero, mapId); + var pos = new MapCoordinates(Vector2.Zero, map.MapId); var entStorage = serverEntManager.EntitySysManager.GetEntitySystem(); var container = serverEntManager.SpawnEntity("ContainerOcclusionB", pos); dummy = serverEntManager.SpawnEntity("ContainerOcclusionDummy", pos); @@ -127,11 +128,12 @@ namespace Content.IntegrationTests.Tests EntityUid dummy = default; var mapManager = server.ResolveDependency(); - var mapId = mapManager.CreateMap(); + + var map = await pair.CreateTestMap(); await server.WaitPost(() => { - var pos = new MapCoordinates(Vector2.Zero, mapId); + var pos = new MapCoordinates(Vector2.Zero, map.MapId); var entStorage = serverEntManager.EntitySysManager.GetEntitySystem(); var containerA = serverEntManager.SpawnEntity("ContainerOcclusionA", pos); var containerB = serverEntManager.SpawnEntity("ContainerOcclusionB", pos); diff --git a/Content.IntegrationTests/Tests/Damageable/DamageSpecifierTest.cs b/Content.IntegrationTests/Tests/Damageable/DamageSpecifierTest.cs index 41d17ddeda..bd5cac05dd 100644 --- a/Content.IntegrationTests/Tests/Damageable/DamageSpecifierTest.cs +++ b/Content.IntegrationTests/Tests/Damageable/DamageSpecifierTest.cs @@ -14,39 +14,39 @@ public sealed class DamageSpecifierTest // Test basic math operations. // I've already nearly broken these once. When editing the operators. - DamageSpecifier input1 = new() { DamageDict = _input1 }; - DamageSpecifier input2 = new() { DamageDict = _input2 }; - DamageSpecifier output1 = new() { DamageDict = _output1 }; - DamageSpecifier output2 = new() { DamageDict = _output2 }; - DamageSpecifier output3 = new() { DamageDict = _output3 }; - DamageSpecifier output4 = new() { DamageDict = _output4 }; - DamageSpecifier output5 = new() { DamageDict = _output5 }; + DamageSpecifier input1 = new() { DamageDict = Input1 }; + DamageSpecifier input2 = new() { DamageDict = Input2 }; + DamageSpecifier output1 = new() { DamageDict = Output1 }; + DamageSpecifier output2 = new() { DamageDict = Output2 }; + DamageSpecifier output3 = new() { DamageDict = Output3 }; + DamageSpecifier output4 = new() { DamageDict = Output4 }; + DamageSpecifier output5 = new() { DamageDict = Output5 }; Assert.Multiple(() => { - Assert.That((-input1).Equals(output1)); - Assert.That((input1 / 2).Equals(output2)); - Assert.That((input1 * 2).Equals(output3)); + Assert.That(-input1, Is.EqualTo(output1)); + Assert.That(input1 / 2, Is.EqualTo(output2)); + Assert.That(input1 * 2, Is.EqualTo(output3)); }); - var difference = (input1 - input2); - Assert.That(difference.Equals(output4)); + var difference = input1 - input2; + Assert.That(difference, Is.EqualTo(output4)); - var difference2 = (-input2) + input1; - Assert.That(difference.Equals(difference2)); + var difference2 = -input2 + input1; + Assert.That(difference, Is.EqualTo(difference2)); difference.Clamp(-0.25f, 0.25f); - Assert.That(difference.Equals(output5)); + Assert.That(difference, Is.EqualTo(output5)); } - static Dictionary _input1 = new() + private static readonly Dictionary Input1 = new() { { "A", 1.5f }, { "B", 2 }, { "C", 3 } }; - static Dictionary _input2 = new() + private static readonly Dictionary Input2 = new() { { "A", 1 }, { "B", 2 }, @@ -54,28 +54,28 @@ public sealed class DamageSpecifierTest { "D", 0.05f } }; - static Dictionary _output1 = new() + private static readonly Dictionary Output1 = new() { { "A", -1.5f }, { "B", -2 }, { "C", -3 } }; - static Dictionary _output2 = new() + private static readonly Dictionary Output2 = new() { { "A", 0.75f }, { "B", 1 }, { "C", 1.5 } }; - static Dictionary _output3 = new() + private static readonly Dictionary Output3 = new() { { "A", 3f }, { "B", 4 }, { "C", 6 } }; - static Dictionary _output4 = new() + private static readonly Dictionary Output4 = new() { { "A", 0.5f }, { "B", 0 }, @@ -83,7 +83,7 @@ public sealed class DamageSpecifierTest { "D", -0.05f } }; - static Dictionary _output5 = new() + private static readonly Dictionary Output5 = new() { { "A", 0.25f }, { "B", 0 }, diff --git a/Content.IntegrationTests/Tests/Damageable/DamageableTest.cs b/Content.IntegrationTests/Tests/Damageable/DamageableTest.cs index c40b8ed286..69069fc82f 100644 --- a/Content.IntegrationTests/Tests/Damageable/DamageableTest.cs +++ b/Content.IntegrationTests/Tests/Damageable/DamageableTest.cs @@ -107,10 +107,11 @@ namespace Content.IntegrationTests.Tests.Damageable FixedPoint2 typeDamage; + var map = await pair.CreateTestMap(); + await server.WaitPost(() => { - var map = sMapManager.CreateMap(); - var coordinates = new MapCoordinates(0, 0, map); + var coordinates = map.MapCoords; sDamageableEntity = sEntityManager.SpawnEntity("TestDamageableEntityId", coordinates); sDamageableComponent = sEntityManager.GetComponent(sDamageableEntity); diff --git a/Content.IntegrationTests/Tests/Doors/AirlockTest.cs b/Content.IntegrationTests/Tests/Doors/AirlockTest.cs index 2fbaa91456..e47c73611a 100644 --- a/Content.IntegrationTests/Tests/Doors/AirlockTest.cs +++ b/Content.IntegrationTests/Tests/Doors/AirlockTest.cs @@ -123,24 +123,24 @@ namespace Content.IntegrationTests.Tests.Doors var xformSystem = entityManager.System(); PhysicsComponent physBody = null; - EntityUid AirlockPhysicsDummy = default; + EntityUid airlockPhysicsDummy = default; EntityUid airlock = default; DoorComponent doorComponent = null; - var AirlockPhysicsDummyStartingX = -1; + var airlockPhysicsDummyStartingX = -1; + + var map = await pair.CreateTestMap(); await server.WaitAssertion(() => { - var mapId = mapManager.CreateMap(); + var humanCoordinates = new MapCoordinates(new Vector2(airlockPhysicsDummyStartingX, 0), map.MapId); + airlockPhysicsDummy = entityManager.SpawnEntity("AirlockPhysicsDummy", humanCoordinates); - var humanCoordinates = new MapCoordinates(new Vector2(AirlockPhysicsDummyStartingX, 0), mapId); - AirlockPhysicsDummy = entityManager.SpawnEntity("AirlockPhysicsDummy", humanCoordinates); - - airlock = entityManager.SpawnEntity("AirlockDummy", new MapCoordinates(new Vector2(0, 0), mapId)); + airlock = entityManager.SpawnEntity("AirlockDummy", new MapCoordinates(new Vector2(0, 0), map.MapId)); Assert.Multiple(() => { - Assert.That(entityManager.TryGetComponent(AirlockPhysicsDummy, out physBody), Is.True); + Assert.That(entityManager.TryGetComponent(airlockPhysicsDummy, out physBody), Is.True); Assert.That(entityManager.TryGetComponent(airlock, out doorComponent), Is.True); }); Assert.That(doorComponent.State, Is.EqualTo(DoorState.Closed)); @@ -152,7 +152,7 @@ namespace Content.IntegrationTests.Tests.Doors await server.WaitAssertion(() => Assert.That(physBody, Is.Not.EqualTo(null))); await server.WaitPost(() => { - physicsSystem.SetLinearVelocity(AirlockPhysicsDummy, new Vector2(0.5f, 0f), body: physBody); + physicsSystem.SetLinearVelocity(airlockPhysicsDummy, new Vector2(0.5f, 0f), body: physBody); }); for (var i = 0; i < 240; i += 10) @@ -176,7 +176,7 @@ namespace Content.IntegrationTests.Tests.Doors // Blocked by the airlock await server.WaitAssertion(() => { - Assert.That(Math.Abs(xformSystem.GetWorldPosition(AirlockPhysicsDummy).X - 1), Is.GreaterThan(0.01f)); + Assert.That(Math.Abs(xformSystem.GetWorldPosition(airlockPhysicsDummy).X - 1), Is.GreaterThan(0.01f)); }); await pair.CleanReturnAsync(); } diff --git a/Content.IntegrationTests/Tests/DummyIconTest.cs b/Content.IntegrationTests/Tests/DummyIconTest.cs index a11191a51e..df2d28a2ea 100644 --- a/Content.IntegrationTests/Tests/DummyIconTest.cs +++ b/Content.IntegrationTests/Tests/DummyIconTest.cs @@ -21,7 +21,7 @@ namespace Content.IntegrationTests.Tests { foreach (var proto in prototypeManager.EnumeratePrototypes()) { - if (proto.NoSpawn || proto.Abstract || pair.IsTestPrototype(proto) || !proto.Components.ContainsKey("Sprite")) + if (proto.HideSpawnMenu || proto.Abstract || pair.IsTestPrototype(proto) || !proto.Components.ContainsKey("Sprite")) continue; Assert.DoesNotThrow(() => diff --git a/Content.IntegrationTests/Tests/EntityTest.cs b/Content.IntegrationTests/Tests/EntityTest.cs index 1fc739fb0c..42bea8989c 100644 --- a/Content.IntegrationTests/Tests/EntityTest.cs +++ b/Content.IntegrationTests/Tests/EntityTest.cs @@ -1,15 +1,11 @@ using System.Collections.Generic; using System.Linq; using System.Numerics; -using Content.Server.Humanoid.Components; -using Content.Shared.Coordinates; -using Content.Shared.Prototypes; using Robust.Shared; using Robust.Shared.Configuration; using Robust.Shared.GameObjects; using Robust.Shared.Log; using Robust.Shared.Map; -using Robust.Shared.Map.Components; using Robust.Shared.Maths; using Robust.Shared.Prototypes; @@ -47,7 +43,7 @@ namespace Content.IntegrationTests.Tests foreach (var protoId in protoIds) { - var mapId = mapManager.CreateMap(); + mapSystem.CreateMap(out var mapId); var grid = mapManager.CreateGridEntity(mapId); // TODO: Fix this better in engine. mapSystem.SetTile(grid.Owner, grid.Comp, Vector2i.Zero, new Tile(1)); @@ -155,6 +151,7 @@ namespace Content.IntegrationTests.Tests var prototypeMan = server.ResolveDependency(); var mapManager = server.ResolveDependency(); var sEntMan = server.ResolveDependency(); + var mapSys = server.System(); Assert.That(cfg.GetCVar(CVars.NetPVS), Is.False); @@ -170,7 +167,7 @@ namespace Content.IntegrationTests.Tests { foreach (var protoId in protoIds) { - var mapId = mapManager.CreateMap(); + mapSys.CreateMap(out var mapId); var grid = mapManager.CreateGridEntity(mapId); var ent = sEntMan.SpawnEntity(protoId, new EntityCoordinates(grid.Owner, 0.5f, 0.5f)); foreach (var (_, component) in sEntMan.GetNetComponents(ent)) @@ -227,6 +224,7 @@ namespace Content.IntegrationTests.Tests var settings = new PoolSettings { Connected = true, Dirty = true }; await using var pair = await PoolManager.GetServerClient(settings); var mapManager = pair.Server.ResolveDependency(); + var mapSys = pair.Server.System(); var server = pair.Server; var client = pair.Client; @@ -256,7 +254,7 @@ namespace Content.IntegrationTests.Tests await server.WaitPost(() => { - mapId = mapManager.CreateMap(); + mapSys.CreateMap(out mapId); }); var coords = new MapCoordinates(Vector2.Zero, mapId); diff --git a/Content.IntegrationTests/Tests/Fluids/FluidSpillTest.cs b/Content.IntegrationTests/Tests/Fluids/FluidSpillTest.cs index 6e88d6928e..d6f9bf3598 100644 --- a/Content.IntegrationTests/Tests/Fluids/FluidSpillTest.cs +++ b/Content.IntegrationTests/Tests/Fluids/FluidSpillTest.cs @@ -16,14 +16,15 @@ namespace Content.IntegrationTests.Tests.Fluids; [TestOf(typeof(SpreaderSystem))] public sealed class FluidSpill { - private static PuddleComponent? GetPuddle(IEntityManager entityManager, MapGridComponent mapGrid, Vector2i pos) + private static PuddleComponent? GetPuddle(IEntityManager entityManager, Entity mapGrid, Vector2i pos) { return GetPuddleEntity(entityManager, mapGrid, pos)?.Comp; } - private static Entity? GetPuddleEntity(IEntityManager entityManager, MapGridComponent mapGrid, Vector2i pos) + private static Entity? GetPuddleEntity(IEntityManager entityManager, Entity mapGrid, Vector2i pos) { - foreach (var uid in mapGrid.GetAnchoredEntities(pos)) + var mapSys = entityManager.System(); + foreach (var uid in mapSys.GetAnchoredEntities(mapGrid, mapGrid.Comp, pos)) { if (entityManager.TryGetComponent(uid, out PuddleComponent? puddleComponent)) return (uid, puddleComponent); @@ -39,9 +40,9 @@ public sealed class FluidSpill var server = pair.Server; var mapManager = server.ResolveDependency(); var entityManager = server.ResolveDependency(); - var puddleSystem = server.ResolveDependency().GetEntitySystem(); + var puddleSystem = server.System(); + var mapSystem = server.System(); var gameTiming = server.ResolveDependency(); - MapId mapId; EntityUid gridId = default; /* @@ -52,7 +53,7 @@ public sealed class FluidSpill */ await server.WaitPost(() => { - mapId = mapManager.CreateMap(); + mapSystem.CreateMap(out var mapId); var grid = mapManager.CreateGridEntity(mapId); gridId = grid.Owner; @@ -60,12 +61,12 @@ public sealed class FluidSpill { for (var y = 0; y < 3; y++) { - grid.Comp.SetTile(new Vector2i(x, y), new Tile(1)); + mapSystem.SetTile(grid, new Vector2i(x, y), new Tile(1)); } } - entityManager.SpawnEntity("WallReinforced", grid.Comp.GridTileToLocal(new Vector2i(0, 1))); - entityManager.SpawnEntity("WallReinforced", grid.Comp.GridTileToLocal(new Vector2i(1, 0))); + entityManager.SpawnEntity("WallReinforced", mapSystem.GridTileToLocal(grid, grid.Comp, new Vector2i(0, 1))); + entityManager.SpawnEntity("WallReinforced", mapSystem.GridTileToLocal(grid, grid.Comp, new Vector2i(1, 0))); }); @@ -74,10 +75,10 @@ public sealed class FluidSpill { var grid = entityManager.GetComponent(gridId); var solution = new Solution("Blood", FixedPoint2.New(100)); - var tileRef = grid.GetTileRef(puddleOrigin); + var tileRef = mapSystem.GetTileRef(gridId, grid, puddleOrigin); #pragma warning disable NUnit2045 // Interdependent tests Assert.That(puddleSystem.TrySpillAt(tileRef, solution, out _), Is.True); - Assert.That(GetPuddle(entityManager, grid, puddleOrigin), Is.Not.Null); + Assert.That(GetPuddle(entityManager, (gridId, grid), puddleOrigin), Is.Not.Null); #pragma warning restore NUnit2045 }); @@ -87,7 +88,7 @@ public sealed class FluidSpill await server.WaitAssertion(() => { var grid = entityManager.GetComponent(gridId); - var puddle = GetPuddleEntity(entityManager, grid, puddleOrigin); + var puddle = GetPuddleEntity(entityManager, (gridId, grid), puddleOrigin); #pragma warning disable NUnit2045 // Interdependent tests Assert.That(puddle, Is.Not.Null); @@ -104,7 +105,7 @@ public sealed class FluidSpill } var newPos = new Vector2i(x, y); - var sidePuddle = GetPuddle(entityManager, grid, newPos); + var sidePuddle = GetPuddle(entityManager, (gridId, grid), newPos); Assert.That(sidePuddle, Is.Null); } } diff --git a/Content.IntegrationTests/Tests/Fluids/PuddleTest.cs b/Content.IntegrationTests/Tests/Fluids/PuddleTest.cs index a9069892df..ee2d0cb1f7 100644 --- a/Content.IntegrationTests/Tests/Fluids/PuddleTest.cs +++ b/Content.IntegrationTests/Tests/Fluids/PuddleTest.cs @@ -5,7 +5,6 @@ using Content.Shared.FixedPoint; using Content.Shared.Fluids.Components; using Robust.Shared.GameObjects; using Robust.Shared.Map; -using Robust.Shared.Map.Components; namespace Content.IntegrationTests.Tests.Fluids { @@ -21,8 +20,7 @@ namespace Content.IntegrationTests.Tests.Fluids var testMap = await pair.CreateTestMap(); - var entitySystemManager = server.ResolveDependency(); - var spillSystem = entitySystemManager.GetEntitySystem(); + var spillSystem = server.System(); await server.WaitAssertion(() => { @@ -46,17 +44,19 @@ namespace Content.IntegrationTests.Tests.Fluids var server = pair.Server; var testMap = await pair.CreateTestMap(); - var grid = testMap.Grid.Comp; + var grid = testMap.Grid; var entitySystemManager = server.ResolveDependency(); - var spillSystem = entitySystemManager.GetEntitySystem(); + var spillSystem = server.System(); + var mapSystem = server.System(); // Remove all tiles await server.WaitPost(() => { - foreach (var tile in grid.GetAllTiles()) + var tiles = mapSystem.GetAllTiles(grid.Owner, grid.Comp); + foreach (var tile in tiles) { - grid.SetTile(tile.GridIndices, Tile.Empty); + mapSystem.SetTile(grid, tile.GridIndices, Tile.Empty); } }); diff --git a/Content.IntegrationTests/Tests/FollowerSystemTest.cs b/Content.IntegrationTests/Tests/FollowerSystemTest.cs index 4d308c6d91..f4447426c7 100644 --- a/Content.IntegrationTests/Tests/FollowerSystemTest.cs +++ b/Content.IntegrationTests/Tests/FollowerSystemTest.cs @@ -22,6 +22,7 @@ public sealed class FollowerSystemTest var mapMan = server.ResolveDependency(); var sysMan = server.ResolveDependency(); var logMan = server.ResolveDependency(); + var mapSys = server.System(); var logger = logMan.RootSawmill; await server.WaitPost(() => @@ -29,7 +30,7 @@ public sealed class FollowerSystemTest var followerSystem = sysMan.GetEntitySystem(); // Create a map to spawn the observers on. - var map = mapMan.CreateMap(); + mapSys.CreateMap(out var map); // Spawn an observer to be followed. var followed = entMan.SpawnEntity(GameTicker.ObserverPrototypeName, new MapCoordinates(0, 0, map)); @@ -41,7 +42,7 @@ public sealed class FollowerSystemTest followerSystem.StartFollowingEntity(follower, followed); - entMan.DeleteEntity(mapMan.GetMapEntityId(map)); + entMan.DeleteEntity(mapSys.GetMap(map)); }); await pair.CleanReturnAsync(); } diff --git a/Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/HandCuffTest.cs b/Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/HandCuffTest.cs index 0ac6b68a3e..2570e2246a 100644 --- a/Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/HandCuffTest.cs +++ b/Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/HandCuffTest.cs @@ -1,5 +1,4 @@ #nullable enable -using System.Numerics; using Content.Server.Cuffs; using Content.Shared.Body.Components; using Content.Shared.Cuffs.Components; @@ -7,7 +6,6 @@ using Content.Shared.Hands.Components; using Robust.Server.Console; using Robust.Shared.GameObjects; using Robust.Shared.Map; -using Robust.Shared.Maths; namespace Content.IntegrationTests.Tests.GameObjects.Components.ActionBlocking { @@ -52,10 +50,11 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.ActionBlocking var mapManager = server.ResolveDependency(); var host = server.ResolveDependency(); + var map = await pair.CreateTestMap(); + await server.WaitAssertion(() => { - var mapId = mapManager.CreateMap(); - var coordinates = new MapCoordinates(Vector2.Zero, mapId); + var coordinates = map.MapCoords; var cuffableSys = entityManager.System(); var xformSys = entityManager.System(); diff --git a/Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs b/Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs index 1bea33a82b..b215584c57 100644 --- a/Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs @@ -47,7 +47,7 @@ public sealed class AntagPreferenceTest Assert.That(sys.IsEntityValid(client.AttachedEntity, def), Is.True); // By default, traitor/antag preferences are disabled, so the pool should be empty. - var sessions = new List{pair.Player!}; + var sessions = new List { pair.Player! }; var pool = sys.GetPlayerPool(rule, sessions, def); Assert.That(pool.Count, Is.EqualTo(0)); diff --git a/Content.IntegrationTests/Tests/GameRules/FailAndStartPresetTest.cs b/Content.IntegrationTests/Tests/GameRules/FailAndStartPresetTest.cs index f660eccf30..518166ed86 100644 --- a/Content.IntegrationTests/Tests/GameRules/FailAndStartPresetTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/FailAndStartPresetTest.cs @@ -110,7 +110,7 @@ public sealed class FailAndStartPresetTest player = pair.Player!.AttachedEntity!.Value; Assert.That(entMan.EntityExists(player)); - ticker.SetGamePreset((GamePresetPrototype?)null); + ticker.SetGamePreset((GamePresetPrototype?) null); server.CfgMan.SetCVar(CCVars.GridFill, false); server.CfgMan.SetCVar(CCVars.GameLobbyFallbackEnabled, true); server.CfgMan.SetCVar(CCVars.GameLobbyDefaultPreset, "secret"); diff --git a/Content.IntegrationTests/Tests/GravityGridTest.cs b/Content.IntegrationTests/Tests/GravityGridTest.cs index 7f817e8a1e..64f7a6d082 100644 --- a/Content.IntegrationTests/Tests/GravityGridTest.cs +++ b/Content.IntegrationTests/Tests/GravityGridTest.cs @@ -34,29 +34,25 @@ namespace Content.IntegrationTests.Tests var testMap = await pair.CreateTestMap(); - EntityUid generator = default; - var entityMan = server.ResolveDependency(); - var mapMan = server.ResolveDependency(); + var entityMan = server.EntMan; + var mapMan = server.MapMan; var mapSys = entityMan.System(); - MapGridComponent grid1 = null; - MapGridComponent grid2 = null; - EntityUid grid1Entity = default!; - EntityUid grid2Entity = default!; + EntityUid generator = default; + Entity grid1 = default; + Entity grid2 = default; // Create grids await server.WaitAssertion(() => { var mapId = testMap.MapId; - grid1 = mapMan.CreateGrid(mapId); - grid2 = mapMan.CreateGrid(mapId); - grid1Entity = grid1.Owner; - grid2Entity = grid2.Owner; + grid1 = mapMan.CreateGridEntity(mapId); + grid2 = mapMan.CreateGridEntity(mapId); - mapSys.SetTile(grid1Entity, grid1, Vector2i.Zero, new Tile(1)); - mapSys.SetTile(grid2Entity, grid2, Vector2i.Zero, new Tile(1)); + mapSys.SetTile(grid1, grid1, Vector2i.Zero, new Tile(1)); + mapSys.SetTile(grid2, grid2, Vector2i.Zero, new Tile(1)); - generator = entityMan.SpawnEntity("GridGravityGeneratorDummy", new EntityCoordinates(grid1Entity, 0.5f, 0.5f)); + generator = entityMan.SpawnEntity("GridGravityGeneratorDummy", new EntityCoordinates(grid1, 0.5f, 0.5f)); Assert.Multiple(() => { Assert.That(entityMan.HasComponent(generator)); @@ -77,8 +73,8 @@ namespace Content.IntegrationTests.Tests Assert.Multiple(() => { Assert.That(generatorComponent.GravityActive, Is.True); - Assert.That(!entityMan.GetComponent(grid1Entity).EnabledVV); - Assert.That(entityMan.GetComponent(grid2Entity).EnabledVV); + Assert.That(!entityMan.GetComponent(grid1).EnabledVV); + Assert.That(entityMan.GetComponent(grid2).EnabledVV); }); // Re-enable needs power so it turns off again. @@ -95,7 +91,7 @@ namespace Content.IntegrationTests.Tests Assert.Multiple(() => { Assert.That(generatorComponent.GravityActive, Is.False); - Assert.That(entityMan.GetComponent(grid2Entity).EnabledVV, Is.False); + Assert.That(entityMan.GetComponent(grid2).EnabledVV, Is.False); }); }); diff --git a/Content.IntegrationTests/Tests/Interaction/Click/InteractionSystemTests.cs b/Content.IntegrationTests/Tests/Interaction/Click/InteractionSystemTests.cs index 2b844d34f0..6ac40e92a1 100644 --- a/Content.IntegrationTests/Tests/Interaction/Click/InteractionSystemTests.cs +++ b/Content.IntegrationTests/Tests/Interaction/Click/InteractionSystemTests.cs @@ -48,13 +48,9 @@ namespace Content.IntegrationTests.Tests.Interaction.Click var sysMan = server.ResolveDependency(); var handSys = sysMan.GetEntitySystem(); - var mapId = MapId.Nullspace; - var coords = MapCoordinates.Nullspace; - await server.WaitAssertion(() => - { - mapId = mapManager.CreateMap(); - coords = new MapCoordinates(Vector2.Zero, mapId); - }); + var map = await pair.CreateTestMap(); + var mapId = map.MapId; + var coords = map.MapCoords; await server.WaitIdleAsync(); EntityUid user = default; @@ -119,13 +115,9 @@ namespace Content.IntegrationTests.Tests.Interaction.Click var sysMan = server.ResolveDependency(); var handSys = sysMan.GetEntitySystem(); - var mapId = MapId.Nullspace; - var coords = MapCoordinates.Nullspace; - await server.WaitAssertion(() => - { - mapId = mapManager.CreateMap(); - coords = new MapCoordinates(Vector2.Zero, mapId); - }); + var map = await pair.CreateTestMap(); + var mapId = map.MapId; + var coords = map.MapCoords; await server.WaitIdleAsync(); EntityUid user = default; @@ -190,13 +182,9 @@ namespace Content.IntegrationTests.Tests.Interaction.Click var sysMan = server.ResolveDependency(); var handSys = sysMan.GetEntitySystem(); - var mapId = MapId.Nullspace; - var coords = MapCoordinates.Nullspace; - await server.WaitAssertion(() => - { - mapId = mapManager.CreateMap(); - coords = new MapCoordinates(Vector2.Zero, mapId); - }); + var map = await pair.CreateTestMap(); + var mapId = map.MapId; + var coords = map.MapCoords; await server.WaitIdleAsync(); EntityUid user = default; @@ -261,13 +249,9 @@ namespace Content.IntegrationTests.Tests.Interaction.Click var sysMan = server.ResolveDependency(); var handSys = sysMan.GetEntitySystem(); - var mapId = MapId.Nullspace; - var coords = MapCoordinates.Nullspace; - await server.WaitAssertion(() => - { - mapId = mapManager.CreateMap(); - coords = new MapCoordinates(Vector2.Zero, mapId); - }); + var map = await pair.CreateTestMap(); + var mapId = map.MapId; + var coords = map.MapCoords; await server.WaitIdleAsync(); EntityUid user = default; @@ -331,13 +315,9 @@ namespace Content.IntegrationTests.Tests.Interaction.Click var handSys = sysMan.GetEntitySystem(); var conSystem = sysMan.GetEntitySystem(); - var mapId = MapId.Nullspace; - var coords = MapCoordinates.Nullspace; - await server.WaitAssertion(() => - { - mapId = mapManager.CreateMap(); - coords = new MapCoordinates(Vector2.Zero, mapId); - }); + var map = await pair.CreateTestMap(); + var mapId = map.MapId; + var coords = map.MapCoords; await server.WaitIdleAsync(); EntityUid user = default; diff --git a/Content.IntegrationTests/Tests/Interaction/InRangeUnobstructed.cs b/Content.IntegrationTests/Tests/Interaction/InRangeUnobstructed.cs index 719367e54e..801433ae72 100644 --- a/Content.IntegrationTests/Tests/Interaction/InRangeUnobstructed.cs +++ b/Content.IntegrationTests/Tests/Interaction/InRangeUnobstructed.cs @@ -39,10 +39,11 @@ namespace Content.IntegrationTests.Tests.Interaction EntityUid other = default; MapCoordinates mapCoordinates = default; + var map = await pair.CreateTestMap(); + await server.WaitAssertion(() => { - var mapId = mapManager.CreateMap(); - var coordinates = new MapCoordinates(Vector2.Zero, mapId); + var coordinates = map.MapCoords; origin = sEntities.SpawnEntity(HumanId, coordinates); other = sEntities.SpawnEntity(HumanId, coordinates); diff --git a/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs b/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs index a61a059301..a09126a7f7 100644 --- a/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs +++ b/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs @@ -571,11 +571,11 @@ public abstract partial class InteractionTest var tile = Tile.Empty; var serverCoords = SEntMan.GetCoordinates(coords ?? TargetCoords); - var pos = serverCoords.ToMap(SEntMan, Transform); + var pos = Transform.ToMapCoordinates(serverCoords); await Server.WaitPost(() => { - if (MapMan.TryFindGridAt(pos, out _, out var grid)) - tile = grid.GetTileRef(serverCoords).Tile; + if (MapMan.TryFindGridAt(pos, out var gridUid, out var grid)) + tile = MapSystem.GetTileRef(gridUid, grid, serverCoords).Tile; }); Assert.That(tile.TypeId, Is.EqualTo(targetTile.TypeId)); @@ -757,33 +757,41 @@ public abstract partial class InteractionTest /// /// Set the tile at the target position to some prototype. /// - protected async Task SetTile(string? proto, NetCoordinates? coords = null, MapGridComponent? grid = null) + protected async Task SetTile(string? proto, NetCoordinates? coords = null, Entity? grid = null) { var tile = proto == null ? Tile.Empty : new Tile(TileMan[proto].TileId); - var pos = SEntMan.GetCoordinates(coords ?? TargetCoords).ToMap(SEntMan, Transform); + var pos = Transform.ToMapCoordinates(SEntMan.GetCoordinates(coords ?? TargetCoords)); + EntityUid gridUid; + MapGridComponent? gridComp; await Server.WaitPost(() => { - if (grid != null || MapMan.TryFindGridAt(pos, out var gridUid, out grid)) + if (grid is { } gridEnt) { - grid.SetTile(SEntMan.GetCoordinates(coords ?? TargetCoords), tile); + MapSystem.SetTile(gridEnt, SEntMan.GetCoordinates(coords ?? TargetCoords), tile); + return; + } + else if (MapMan.TryFindGridAt(pos, out var gUid, out var gComp)) + { + MapSystem.SetTile(gUid, gComp, SEntMan.GetCoordinates(coords ?? TargetCoords), tile); return; } if (proto == null) return; - var gridEnt = MapMan.CreateGridEntity(MapData.MapId); + gridEnt = MapMan.CreateGridEntity(MapData.MapId); grid = gridEnt; gridUid = gridEnt; + gridComp = gridEnt.Comp; var gridXform = SEntMan.GetComponent(gridUid); Transform.SetWorldPosition(gridXform, pos.Position); - grid.SetTile(SEntMan.GetCoordinates(coords ?? TargetCoords), tile); + MapSystem.SetTile((gridUid, gridComp), SEntMan.GetCoordinates(coords ?? TargetCoords), tile); - if (!MapMan.TryFindGridAt(pos, out _, out grid)) + if (!MapMan.TryFindGridAt(pos, out _, out _)) Assert.Fail("Failed to create grid?"); }); await AssertTile(proto, coords); diff --git a/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs b/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs index 37102481ed..457d3e3192 100644 --- a/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs +++ b/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs @@ -1,5 +1,4 @@ #nullable enable -using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Numerics; using Content.Client.Construction; @@ -108,6 +107,7 @@ public abstract partial class InteractionTest protected SharedItemToggleSystem ItemToggleSys = default!; protected InteractionTestSystem STestSystem = default!; protected SharedTransformSystem Transform = default!; + protected SharedMapSystem MapSystem = default!; protected ISawmill SLogger = default!; protected SharedUserInterfaceSystem SUiSys = default!; @@ -153,7 +153,7 @@ public abstract partial class InteractionTest [SetUp] public virtual async Task Setup() { - Pair = await PoolManager.GetServerClient(new PoolSettings { Connected = true, Dirty = true}); + Pair = await PoolManager.GetServerClient(new PoolSettings { Connected = true, Dirty = true }); // server dependencies SEntMan = Server.ResolveDependency(); @@ -168,6 +168,7 @@ public abstract partial class InteractionTest ItemToggleSys = SEntMan.System(); DoAfterSys = SEntMan.System(); Transform = SEntMan.System(); + MapSystem = SEntMan.System(); SConstruction = SEntMan.System(); STestSystem = SEntMan.System(); Stack = SEntMan.System(); @@ -188,9 +189,10 @@ public abstract partial class InteractionTest // Setup map. await Pair.CreateTestMap(); - PlayerCoords = SEntMan.GetNetCoordinates(MapData.GridCoords.Offset(new Vector2(0.5f, 0.5f)).WithEntityId(MapData.MapUid, Transform, SEntMan)); - TargetCoords = SEntMan.GetNetCoordinates(MapData.GridCoords.Offset(new Vector2(1.5f, 0.5f)).WithEntityId(MapData.MapUid, Transform, SEntMan)); - await SetTile(Plating, grid: MapData.Grid.Comp); + + PlayerCoords = SEntMan.GetNetCoordinates(Transform.WithEntityId(MapData.GridCoords.Offset(new Vector2(0.5f, 0.5f)), MapData.MapUid)); + TargetCoords = SEntMan.GetNetCoordinates(Transform.WithEntityId(MapData.GridCoords.Offset(new Vector2(1.5f, 0.5f)), MapData.MapUid)); + await SetTile(Plating, grid: MapData.Grid); // Get player data var sPlayerMan = Server.ResolveDependency(); @@ -263,7 +265,8 @@ public abstract partial class InteractionTest await TearDown(); } - protected virtual async Task TearDown() + protected virtual Task TearDown() { + return Task.CompletedTask; } } diff --git a/Content.IntegrationTests/Tests/Linter/StaticFieldValidationTest.cs b/Content.IntegrationTests/Tests/Linter/StaticFieldValidationTest.cs index 30724b50a6..0632fe1347 100644 --- a/Content.IntegrationTests/Tests/Linter/StaticFieldValidationTest.cs +++ b/Content.IntegrationTests/Tests/Linter/StaticFieldValidationTest.cs @@ -26,26 +26,26 @@ public sealed class StaticFieldValidationTest protos.Add(kind, ids); } - Assert.That(protoMan.ValidateStaticFields(typeof(StringValid), protos).Count, Is.Zero); - Assert.That(protoMan.ValidateStaticFields(typeof(StringArrayValid), protos).Count, Is.Zero); - Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdValid), protos).Count, Is.Zero); - Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdArrayValid), protos).Count, Is.Zero); - Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdTestValid), protos).Count, Is.Zero); - Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdArrayValid), protos).Count, Is.Zero); - Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdListValid), protos).Count, Is.Zero); - Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdSetValid), protos).Count, Is.Zero); - Assert.That(protoMan.ValidateStaticFields(typeof(PrivateProtoIdArrayValid), protos).Count, Is.Zero); - - Assert.That(protoMan.ValidateStaticFields(typeof(StringInvalid), protos).Count, Is.EqualTo(1)); - Assert.That(protoMan.ValidateStaticFields(typeof(StringArrayInvalid), protos).Count, Is.EqualTo(2)); - Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdInvalid), protos).Count, Is.EqualTo(1)); - Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdArrayInvalid), protos).Count, Is.EqualTo(2)); - Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdTestInvalid), protos).Count, Is.EqualTo(1)); - Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdArrayInvalid), protos).Count, Is.EqualTo(2)); - Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdListInvalid), protos).Count, Is.EqualTo(2)); - Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdSetInvalid), protos).Count, Is.EqualTo(2)); - Assert.That(protoMan.ValidateStaticFields(typeof(PrivateProtoIdArrayInvalid), protos).Count, Is.EqualTo(2)); - + Assert.That(protoMan.ValidateStaticFields(typeof(StringValid), protos), Is.Empty); + Assert.That(protoMan.ValidateStaticFields(typeof(StringArrayValid), protos), Is.Empty); + Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdValid), protos), Is.Empty); + Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdArrayValid), protos), Is.Empty); + Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdTestValid), protos), Is.Empty); + Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdArrayValid), protos), Is.Empty); + Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdListValid), protos), Is.Empty); + Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdSetValid), protos), Is.Empty); + Assert.That(protoMan.ValidateStaticFields(typeof(PrivateProtoIdArrayValid), protos), Is.Empty); + + Assert.That(protoMan.ValidateStaticFields(typeof(StringInvalid), protos), Has.Count.EqualTo(1)); + Assert.That(protoMan.ValidateStaticFields(typeof(StringArrayInvalid), protos), Has.Count.EqualTo(2)); + Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdInvalid), protos), Has.Count.EqualTo(1)); + Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdArrayInvalid), protos), Has.Count.EqualTo(2)); + Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdTestInvalid), protos), Has.Count.EqualTo(1)); + Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdArrayInvalid), protos), Has.Count.EqualTo(2)); + Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdListInvalid), protos), Has.Count.EqualTo(2)); + Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdSetInvalid), protos), Has.Count.EqualTo(2)); + Assert.That(protoMan.ValidateStaticFields(typeof(PrivateProtoIdArrayInvalid), protos), Has.Count.EqualTo(2)); + await pair.CleanReturnAsync(); } @@ -58,93 +58,111 @@ public sealed class StaticFieldValidationTest id: StaticFieldTestTag "; - [Reflect(false)] private sealed class StringValid + [Reflect(false)] + private sealed class StringValid { [ValidatePrototypeId] public static string Tag = "StaticFieldTestTag"; } - [Reflect(false)] private sealed class StringInvalid + [Reflect(false)] + private sealed class StringInvalid { [ValidatePrototypeId] public static string Tag = string.Empty; } - [Reflect(false)] private sealed class StringArrayValid + [Reflect(false)] + private sealed class StringArrayValid { - [ValidatePrototypeId] public static string[] Tag = {"StaticFieldTestTag", "StaticFieldTestTag"}; + [ValidatePrototypeId] public static string[] Tag = ["StaticFieldTestTag", "StaticFieldTestTag"]; } - [Reflect(false)] private sealed class StringArrayInvalid + [Reflect(false)] + private sealed class StringArrayInvalid { - [ValidatePrototypeId] public static string[] Tag = {string.Empty, "StaticFieldTestTag", string.Empty}; + [ValidatePrototypeId] public static string[] Tag = [string.Empty, "StaticFieldTestTag", string.Empty]; } - [Reflect(false)] private sealed class EntProtoIdValid + [Reflect(false)] + private sealed class EntProtoIdValid { public static EntProtoId Tag = "StaticFieldTestEnt"; } - [Reflect(false)] private sealed class EntProtoIdInvalid + [Reflect(false)] + private sealed class EntProtoIdInvalid { public static EntProtoId Tag = string.Empty; } - [Reflect(false)] private sealed class EntProtoIdArrayValid + [Reflect(false)] + private sealed class EntProtoIdArrayValid { - public static EntProtoId[] Tag = {"StaticFieldTestEnt", "StaticFieldTestEnt"}; + public static EntProtoId[] Tag = ["StaticFieldTestEnt", "StaticFieldTestEnt"]; } - [Reflect(false)] private sealed class EntProtoIdArrayInvalid + [Reflect(false)] + private sealed class EntProtoIdArrayInvalid { - public static EntProtoId[] Tag = {string.Empty, "StaticFieldTestEnt", string.Empty}; + public static EntProtoId[] Tag = [string.Empty, "StaticFieldTestEnt", string.Empty]; } - [Reflect(false)] private sealed class ProtoIdTestValid + [Reflect(false)] + private sealed class ProtoIdTestValid { public static ProtoId Tag = "StaticFieldTestTag"; } - [Reflect(false)] private sealed class ProtoIdTestInvalid + [Reflect(false)] + private sealed class ProtoIdTestInvalid { public static ProtoId Tag = string.Empty; } - [Reflect(false)] private sealed class ProtoIdArrayValid + [Reflect(false)] + private sealed class ProtoIdArrayValid { - public static ProtoId[] Tag = {"StaticFieldTestTag", "StaticFieldTestTag"}; + public static ProtoId[] Tag = ["StaticFieldTestTag", "StaticFieldTestTag"]; } - [Reflect(false)] private sealed class ProtoIdArrayInvalid + [Reflect(false)] + private sealed class ProtoIdArrayInvalid { - public static ProtoId[] Tag = {string.Empty, "StaticFieldTestTag", string.Empty}; + public static ProtoId[] Tag = [string.Empty, "StaticFieldTestTag", string.Empty]; } - [Reflect(false)] private sealed class ProtoIdListValid + [Reflect(false)] + private sealed class ProtoIdListValid { - public static List> Tag = new() {"StaticFieldTestTag", "StaticFieldTestTag"}; + public static List> Tag = ["StaticFieldTestTag", "StaticFieldTestTag"]; } - [Reflect(false)] private sealed class ProtoIdListInvalid + [Reflect(false)] + private sealed class ProtoIdListInvalid { - public static List> Tag = new() {string.Empty, "StaticFieldTestTag", string.Empty}; + public static List> Tag = [string.Empty, "StaticFieldTestTag", string.Empty]; } - [Reflect(false)] private sealed class ProtoIdSetValid + [Reflect(false)] + private sealed class ProtoIdSetValid { - public static HashSet> Tag = new() {"StaticFieldTestTag", "StaticFieldTestTag"}; + public static HashSet> Tag = ["StaticFieldTestTag", "StaticFieldTestTag"]; } - [Reflect(false)] private sealed class ProtoIdSetInvalid + [Reflect(false)] + private sealed class ProtoIdSetInvalid { - public static HashSet> Tag = new() {string.Empty, "StaticFieldTestTag", string.Empty, " "}; + public static HashSet> Tag = [string.Empty, "StaticFieldTestTag", string.Empty, " "]; } - [Reflect(false)] private sealed class PrivateProtoIdArrayValid + [Reflect(false)] + private sealed class PrivateProtoIdArrayValid { - private static ProtoId[] Tag = {"StaticFieldTestTag", "StaticFieldTestTag"}; + private static readonly ProtoId[] Tag = ["StaticFieldTestTag", "StaticFieldTestTag"]; } - [Reflect(false)] private sealed class PrivateProtoIdArrayInvalid + [Reflect(false)] + private sealed class PrivateProtoIdArrayInvalid { - private static ProtoId[] Tag = {string.Empty, "StaticFieldTestTag", string.Empty}; + private static readonly ProtoId[] Tag = [string.Empty, "StaticFieldTestTag", string.Empty]; } } diff --git a/Content.IntegrationTests/Tests/MachineBoardTest.cs b/Content.IntegrationTests/Tests/MachineBoardTest.cs index e741935be3..e1533bbb8d 100644 --- a/Content.IntegrationTests/Tests/MachineBoardTest.cs +++ b/Content.IntegrationTests/Tests/MachineBoardTest.cs @@ -2,7 +2,6 @@ using System.Linq; using Content.Server.Construction.Components; using Content.Shared.Construction.Components; -using Content.Shared.Prototypes; using Robust.Shared.GameObjects; using Robust.Shared.Prototypes; @@ -37,6 +36,7 @@ public sealed class MachineBoardTest var server = pair.Server; var protoMan = server.ResolveDependency(); + var compFact = server.ResolveDependency(); await server.WaitAssertion(() => { @@ -45,7 +45,7 @@ public sealed class MachineBoardTest .Where(p => !pair.IsTestPrototype(p)) .Where(p => !_ignoredPrototypes.Contains(p.ID))) { - if (!p.TryGetComponent(out var mbc)) + if (!p.TryGetComponent(out var mbc, compFact)) continue; var mId = mbc.Prototype; @@ -53,7 +53,7 @@ public sealed class MachineBoardTest { Assert.That(protoMan.TryIndex(mId, out var mProto), $"Machine board {p.ID}'s corresponding machine has an invalid prototype."); - Assert.That(mProto.TryGetComponent(out var mComp), + Assert.That(mProto.TryGetComponent(out var mComp, compFact), $"Machine board {p.ID}'s corresponding machine {mId} does not have MachineComponent"); Assert.That(mComp.Board, Is.EqualTo(p.ID), $"Machine {mId}'s BoardPrototype is not equal to it's corresponding machine board, {p.ID}"); @@ -75,6 +75,7 @@ public sealed class MachineBoardTest var server = pair.Server; var protoMan = server.ResolveDependency(); + var compFact = server.ResolveDependency(); await server.WaitAssertion(() => { @@ -83,7 +84,7 @@ public sealed class MachineBoardTest .Where(p => !pair.IsTestPrototype(p)) .Where(p => !_ignoredPrototypes.Contains(p.ID))) { - if (!p.TryGetComponent(out var cbc)) + if (!p.TryGetComponent(out var cbc, compFact)) continue; var cId = cbc.Prototype; @@ -92,7 +93,7 @@ public sealed class MachineBoardTest Assert.That(cId, Is.Not.Null, $"Computer board \"{p.ID}\" does not have a corresponding computer."); Assert.That(protoMan.TryIndex(cId, out var cProto), $"Computer board \"{p.ID}\"'s corresponding computer has an invalid prototype."); - Assert.That(cProto.TryGetComponent(out var cComp), + Assert.That(cProto.TryGetComponent(out var cComp, compFact), $"Computer board {p.ID}'s corresponding computer \"{cId}\" does not have ComputerComponent"); Assert.That(cComp.BoardPrototype, Is.EqualTo(p.ID), $"Computer \"{cId}\"'s BoardPrototype is not equal to it's corresponding computer board, \"{p.ID}\""); diff --git a/Content.IntegrationTests/Tests/Mapping/MappingTests.cs b/Content.IntegrationTests/Tests/Mapping/MappingTests.cs index 287e30eb8b..be8bad229b 100644 --- a/Content.IntegrationTests/Tests/Mapping/MappingTests.cs +++ b/Content.IntegrationTests/Tests/Mapping/MappingTests.cs @@ -13,7 +13,7 @@ public sealed class MappingTests [Test] public async Task MappingTest() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings {Dirty = true, Connected = true, DummyTicker = false}); + await using var pair = await PoolManager.GetServerClient(new PoolSettings { Dirty = true, Connected = true, DummyTicker = false }); var server = pair.Server; var entMan = server.EntMan; diff --git a/Content.IntegrationTests/Tests/MaterialArbitrageTest.cs b/Content.IntegrationTests/Tests/MaterialArbitrageTest.cs index 19780591bd..f64b7c79df 100644 --- a/Content.IntegrationTests/Tests/MaterialArbitrageTest.cs +++ b/Content.IntegrationTests/Tests/MaterialArbitrageTest.cs @@ -38,15 +38,16 @@ public sealed class MaterialArbitrageTest await server.WaitIdleAsync(); var entManager = server.ResolveDependency(); - var sysManager = server.ResolveDependency(); var mapManager = server.ResolveDependency(); - Assert.That(mapManager.IsMapInitialized(testMap.MapId)); - var protoManager = server.ResolveDependency(); - var pricing = sysManager.GetEntitySystem(); - var stackSys = sysManager.GetEntitySystem(); + + var pricing = entManager.System(); + var stackSys = entManager.System(); + var mapSystem = server.System(); var compFact = server.ResolveDependency(); + Assert.That(mapSystem.IsInitialized(testMap.MapId)); + var constructionName = compFact.GetComponentName(typeof(ConstructionComponent)); var compositionName = compFact.GetComponentName(typeof(PhysicalCompositionComponent)); var materialName = compFact.GetComponentName(typeof(MaterialComponent)); @@ -67,7 +68,7 @@ public sealed class MaterialArbitrageTest Dictionary constructionRecipes = new(); foreach (var proto in protoManager.EnumeratePrototypes()) { - if (proto.NoSpawn || proto.Abstract || pair.IsTestPrototype(proto)) + if (proto.HideSpawnMenu || proto.Abstract || pair.IsTestPrototype(proto)) continue; if (!proto.Components.TryGetValue(constructionName, out var destructible)) @@ -127,7 +128,7 @@ public sealed class MaterialArbitrageTest // Here we get the set of entities/materials spawned when destroying an entity. foreach (var proto in protoManager.EnumeratePrototypes()) { - if (proto.NoSpawn || proto.Abstract || pair.IsTestPrototype(proto)) + if (proto.HideSpawnMenu || proto.Abstract || pair.IsTestPrototype(proto)) continue; if (!proto.Components.TryGetValue(destructibleName, out var destructible)) @@ -298,7 +299,7 @@ public sealed class MaterialArbitrageTest Dictionary physicalCompositions = new(); foreach (var proto in protoManager.EnumeratePrototypes()) { - if (proto.NoSpawn || proto.Abstract || pair.IsTestPrototype(proto)) + if (proto.HideSpawnMenu || proto.Abstract || pair.IsTestPrototype(proto)) continue; if (!proto.Components.TryGetValue(compositionName, out var composition)) diff --git a/Content.IntegrationTests/Tests/Minds/GhostTests.cs b/Content.IntegrationTests/Tests/Minds/GhostTests.cs index 7a156e71e4..3a860267e5 100644 --- a/Content.IntegrationTests/Tests/Minds/GhostTests.cs +++ b/Content.IntegrationTests/Tests/Minds/GhostTests.cs @@ -14,7 +14,7 @@ namespace Content.IntegrationTests.Tests.Minds; [TestFixture] public sealed class GhostTests { - struct GhostTestData + private struct GhostTestData { public IEntityManager SEntMan; public Robust.Server.Player.IPlayerManager SPlayerMan; @@ -23,10 +23,10 @@ public sealed class GhostTests public TestPair Pair = default!; - public TestMapData MapData => Pair.TestMap!; + public readonly TestMapData MapData => Pair.TestMap!; - public RobustIntegrationTest.ServerIntegrationInstance Server => Pair.Server; - public RobustIntegrationTest.ClientIntegrationInstance Client => Pair.Client; + public readonly RobustIntegrationTest.ServerIntegrationInstance Server => Pair.Server; + public readonly RobustIntegrationTest.ClientIntegrationInstance Client => Pair.Client; /// /// Initial player coordinates. Note that this does not necessarily correspond to the position of the @@ -47,15 +47,16 @@ public sealed class GhostTests private async Task SetupData() { - var data = new GhostTestData(); - - // Client is needed to create a session for the ghost system. Creating a dummy session was too difficult. - data.Pair = await PoolManager.GetServerClient(new PoolSettings + var data = new GhostTestData { - DummyTicker = false, - Connected = true, - Dirty = true - }); + // Client is needed to create a session for the ghost system. Creating a dummy session was too difficult. + Pair = await PoolManager.GetServerClient(new PoolSettings + { + DummyTicker = false, + Connected = true, + Dirty = true + }) + }; data.SEntMan = data.Pair.Server.ResolveDependency(); data.SPlayerMan = data.Pair.Server.ResolveDependency(); @@ -64,7 +65,8 @@ public sealed class GhostTests // Setup map. await data.Pair.CreateTestMap(); - data.PlayerCoords = data.SEntMan.GetNetCoordinates(data.MapData.GridCoords.Offset(new Vector2(0.5f, 0.5f)).WithEntityId(data.MapData.MapUid, data.STransformSys, data.SEntMan)); + var test = data.MapData.GridCoords.Offset(new Vector2(0.5f, 0.5f)); + data.PlayerCoords = data.SEntMan.GetNetCoordinates(data.STransformSys.WithEntityId(data.MapData.GridCoords.Offset(new Vector2(0.5f, 0.5f)), data.MapData.MapUid)); if (data.Client.Session == null) Assert.Fail("No player"); diff --git a/Content.IntegrationTests/Tests/Minds/MindTests.Helpers.cs b/Content.IntegrationTests/Tests/Minds/MindTests.Helpers.cs index 428380631d..b12c90e16e 100644 --- a/Content.IntegrationTests/Tests/Minds/MindTests.Helpers.cs +++ b/Content.IntegrationTests/Tests/Minds/MindTests.Helpers.cs @@ -169,7 +169,7 @@ public sealed partial class MindTests { var netManager = pair.Client.ResolveDependency(); var playerMan = pair.Server.ResolveDependency(); - Assert.That(!playerMan.Sessions.Any()); + Assert.That(playerMan.Sessions, Is.Empty); await Task.WhenAll(pair.Client.WaitIdleAsync(), pair.Client.WaitIdleAsync()); pair.Client.SetConnectTarget(pair.Server); diff --git a/Content.IntegrationTests/Tests/Movement/BuckleMovementTest.cs b/Content.IntegrationTests/Tests/Movement/BuckleMovementTest.cs index 8d91855098..3119ee5592 100644 --- a/Content.IntegrationTests/Tests/Movement/BuckleMovementTest.cs +++ b/Content.IntegrationTests/Tests/Movement/BuckleMovementTest.cs @@ -34,7 +34,7 @@ public sealed class BuckleMovementTest : MovementTest Assert.That(Delta(), Is.InRange(-0.01f, 0.01f)); Assert.That(buckle.Buckled, Is.True); Assert.That(buckle.BuckledTo, Is.EqualTo(STarget)); - Assert.That(strap.BuckledEntities, Is.EquivalentTo(new[]{SPlayer})); + Assert.That(strap.BuckledEntities, Is.EquivalentTo(new[] { SPlayer })); Assert.That(cAlert.IsShowingAlert(CPlayer, strap.BuckledAlertType), Is.True); Assert.That(sAlert.IsShowingAlert(SPlayer, strap.BuckledAlertType), Is.True); @@ -43,7 +43,7 @@ public sealed class BuckleMovementTest : MovementTest Assert.That(Delta(), Is.InRange(-0.01f, 0.01f)); Assert.That(buckle.Buckled, Is.True); Assert.That(buckle.BuckledTo, Is.EqualTo(STarget)); - Assert.That(strap.BuckledEntities, Is.EquivalentTo(new[]{SPlayer})); + Assert.That(strap.BuckledEntities, Is.EquivalentTo(new[] { SPlayer })); Assert.That(cAlert.IsShowingAlert(CPlayer, strap.BuckledAlertType), Is.True); Assert.That(sAlert.IsShowingAlert(SPlayer, strap.BuckledAlertType), Is.True); diff --git a/Content.IntegrationTests/Tests/Movement/MovementTest.cs b/Content.IntegrationTests/Tests/Movement/MovementTest.cs index ad7b1d0459..eba9253038 100644 --- a/Content.IntegrationTests/Tests/Movement/MovementTest.cs +++ b/Content.IntegrationTests/Tests/Movement/MovementTest.cs @@ -32,7 +32,7 @@ public abstract class MovementTest : InteractionTest for (var i = -Tiles; i <= Tiles; i++) { - await SetTile(Plating, SEntMan.GetNetCoordinates(pCoords.Offset(new Vector2(i, 0))), MapData.Grid.Comp); + await SetTile(Plating, SEntMan.GetNetCoordinates(pCoords.Offset(new Vector2(i, 0))), MapData.Grid); } AssertGridCount(1); diff --git a/Content.IntegrationTests/Tests/Networking/PvsCommandTest.cs b/Content.IntegrationTests/Tests/Networking/PvsCommandTest.cs index 4783d21a05..b395569848 100644 --- a/Content.IntegrationTests/Tests/Networking/PvsCommandTest.cs +++ b/Content.IntegrationTests/Tests/Networking/PvsCommandTest.cs @@ -7,12 +7,12 @@ namespace Content.IntegrationTests.Tests.Networking; [TestFixture] public sealed class PvsCommandTest { - public static EntProtoId TestEnt = "MobHuman"; + private static readonly EntProtoId TestEnt = "MobHuman"; [Test] public async Task TestPvsCommands() { - await using var pair = await PoolManager.GetServerClient(new PoolSettings { Connected = true, DummyTicker = false}); + await using var pair = await PoolManager.GetServerClient(new PoolSettings { Connected = true, DummyTicker = false }); var (server, client) = pair; await pair.RunTicksSync(5); diff --git a/Content.IntegrationTests/Tests/Networking/SimplePredictReconcileTest.cs b/Content.IntegrationTests/Tests/Networking/SimplePredictReconcileTest.cs index 52d464fa41..29f2573c2d 100644 --- a/Content.IntegrationTests/Tests/Networking/SimplePredictReconcileTest.cs +++ b/Content.IntegrationTests/Tests/Networking/SimplePredictReconcileTest.cs @@ -51,11 +51,12 @@ namespace Content.IntegrationTests.Tests.Networking PredictionTestComponent clientComponent = default!; var serverSystem = sEntityManager.System(); var clientSystem = cEntityManager.System(); + var sMapSys = sEntityManager.System(); await server.WaitPost(() => { // Spawn dummy component entity. - var map = sMapManager.CreateMap(); + sMapSys.CreateMap(out var map); serverEnt = sEntityManager.SpawnEntity(null, new MapCoordinates(new Vector2(0, 0), map)); serverComponent = sEntityManager.AddComponent(serverEnt); }); @@ -67,7 +68,7 @@ namespace Content.IntegrationTests.Tests.Networking Assert.That(sGameTiming.TickTimingAdjustment, Is.EqualTo(0)); // Check client buffer is full - Assert.That(cGameStateManager.CurrentBufferSize, Is.EqualTo(cGameStateManager.TargetBufferSize)); + Assert.That(cGameStateManager.GetApplicableStateCount(), Is.EqualTo(cGameStateManager.TargetBufferSize)); Assert.That(cGameStateManager.TargetBufferSize, Is.EqualTo(2)); // This isn't required anymore, but the test had this for the sake of "technical things", and I cbf shifting @@ -99,7 +100,7 @@ namespace Content.IntegrationTests.Tests.Networking // Client last ran tick 15 meaning it's ahead of the last server tick it processed (12) Assert.That(cGameTiming.CurTick, Is.EqualTo(expected)); - Assert.That(cGameTiming.LastProcessedTick, Is.EqualTo(new GameTick((uint)(baseTick - cGameStateManager.TargetBufferSize)))); + Assert.That(cGameTiming.LastProcessedTick, Is.EqualTo(new GameTick((uint) (baseTick - cGameStateManager.TargetBufferSize)))); }); // *** I am using block scopes to visually distinguish these sections of the test to make it more readable. @@ -264,7 +265,7 @@ namespace Content.IntegrationTests.Tests.Networking // Assert timing is still correct. Assert.That(sGameTiming.CurTick, Is.EqualTo(new GameTick(baseTick + 8))); Assert.That(cGameTiming.CurTick, Is.EqualTo(new GameTick(baseTick + 8 + delta))); - Assert.That(cGameTiming.LastProcessedTick, Is.EqualTo(new GameTick((uint)(baseTick + 8 - cGameStateManager.TargetBufferSize)))); + Assert.That(cGameTiming.LastProcessedTick, Is.EqualTo(new GameTick((uint) (baseTick + 8 - cGameStateManager.TargetBufferSize)))); }); { diff --git a/Content.IntegrationTests/Tests/PostMapInitTest.cs b/Content.IntegrationTests/Tests/PostMapInitTest.cs index 2ae4389841..cd14bfdc90 100644 --- a/Content.IntegrationTests/Tests/PostMapInitTest.cs +++ b/Content.IntegrationTests/Tests/PostMapInitTest.cs @@ -78,13 +78,14 @@ namespace Content.IntegrationTests.Tests var entManager = server.ResolveDependency(); var mapLoader = entManager.System(); + var mapSystem = entManager.System(); var mapManager = server.ResolveDependency(); var cfg = server.ResolveDependency(); Assert.That(cfg.GetCVar(CCVars.GridFill), Is.False); await server.WaitPost(() => { - var mapId = mapManager.CreateMap(); + mapSystem.CreateMap(out var mapId); try { #pragma warning disable NUnit2045 @@ -165,6 +166,7 @@ namespace Content.IntegrationTests.Tests var mapManager = server.ResolveDependency(); var entManager = server.ResolveDependency(); var mapLoader = entManager.System(); + var mapSystem = entManager.System(); var protoManager = server.ResolveDependency(); var ticker = entManager.EntitySysManager.GetEntitySystem(); var shuttleSystem = entManager.EntitySysManager.GetEntitySystem(); @@ -174,7 +176,7 @@ namespace Content.IntegrationTests.Tests await server.WaitPost(() => { - var mapId = mapManager.CreateMap(); + mapSystem.CreateMap(out var mapId); try { ticker.LoadGameMap(protoManager.Index(mapProto), mapId, null); @@ -184,7 +186,7 @@ namespace Content.IntegrationTests.Tests throw new Exception($"Failed to load map {mapProto}", ex); } - var shuttleMap = mapManager.CreateMap(); + mapSystem.CreateMap(out var shuttleMap); var largest = 0f; EntityUid? targetGrid = null; var memberQuery = entManager.GetEntityQuery(); @@ -253,7 +255,7 @@ namespace Content.IntegrationTests.Tests .Select(x => x.Job!.Value); jobs.ExceptWith(spawnPoints); - Assert.That(jobs, Is.Empty,$"There is no spawnpoints for {string.Join(", ", jobs)} on {mapProto}."); + Assert.That(jobs, Is.Empty, $"There is no spawnpoints for {string.Join(", ", jobs)} on {mapProto}."); } try @@ -326,6 +328,7 @@ namespace Content.IntegrationTests.Tests var resourceManager = server.ResolveDependency(); var protoManager = server.ResolveDependency(); var cfg = server.ResolveDependency(); + var mapSystem = server.System(); Assert.That(cfg.GetCVar(CCVars.GridFill), Is.False); var gameMaps = protoManager.EnumeratePrototypes().Select(o => o.MapPath).ToHashSet(); @@ -356,7 +359,7 @@ namespace Content.IntegrationTests.Tests { foreach (var mapName in mapNames) { - var mapId = mapManager.CreateMap(); + mapSystem.CreateMap(out var mapId); try { Assert.That(mapLoader.TryLoad(mapId, mapName, out _)); diff --git a/Content.IntegrationTests/Tests/Power/PowerTest.cs b/Content.IntegrationTests/Tests/Power/PowerTest.cs index a94e94489c..55bb42f8ce 100644 --- a/Content.IntegrationTests/Tests/Power/PowerTest.cs +++ b/Content.IntegrationTests/Tests/Power/PowerTest.cs @@ -166,6 +166,7 @@ namespace Content.IntegrationTests.Tests.Power var server = pair.Server; var mapManager = server.ResolveDependency(); var entityManager = server.ResolveDependency(); + var mapSys = entityManager.System(); const float loadPower = 200; PowerSupplierComponent supplier = default!; PowerConsumerComponent consumer1 = default!; @@ -173,21 +174,19 @@ namespace Content.IntegrationTests.Tests.Power await server.WaitAssertion(() => { - var map = mapManager.CreateMap(); - var grid = mapManager.CreateGrid(map); - - var gridOwner = grid.Owner; + var map = mapSys.CreateMap(out var mapId); + var grid = mapManager.CreateGridEntity(mapId); // Power only works when anchored for (var i = 0; i < 3; i++) { - grid.SetTile(new Vector2i(0, i), new Tile(1)); - entityManager.SpawnEntity("CableHV", gridOwner.ToCoordinates(0, i)); + mapSys.SetTile(grid, new Vector2i(0, i), new Tile(1)); + entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, i)); } - var generatorEnt = entityManager.SpawnEntity("GeneratorDummy", gridOwner.ToCoordinates()); - var consumerEnt1 = entityManager.SpawnEntity("ConsumerDummy", gridOwner.ToCoordinates(0, 1)); - var consumerEnt2 = entityManager.SpawnEntity("ConsumerDummy", gridOwner.ToCoordinates(0, 2)); + var generatorEnt = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates()); + var consumerEnt1 = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 1)); + var consumerEnt2 = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 2)); supplier = entityManager.GetComponent(generatorEnt); consumer1 = entityManager.GetComponent(consumerEnt1); @@ -229,6 +228,7 @@ namespace Content.IntegrationTests.Tests.Power var server = pair.Server; var mapManager = server.ResolveDependency(); var entityManager = server.ResolveDependency(); + var mapSys = entityManager.System(); const float loadPower = 200; PowerSupplierComponent supplier = default!; PowerConsumerComponent consumer1 = default!; @@ -236,21 +236,19 @@ namespace Content.IntegrationTests.Tests.Power await server.WaitAssertion(() => { - var map = mapManager.CreateMap(); - var grid = mapManager.CreateGrid(map); - - var gridOwner = grid.Owner; + var map = mapSys.CreateMap(out var mapId); + var grid = mapManager.CreateGridEntity(mapId); // Power only works when anchored for (var i = 0; i < 3; i++) { - grid.SetTile(new Vector2i(0, i), new Tile(1)); - entityManager.SpawnEntity("CableHV", gridOwner.ToCoordinates(0, i)); + mapSys.SetTile(grid, new Vector2i(0, i), new Tile(1)); + entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, i)); } - var generatorEnt = entityManager.SpawnEntity("GeneratorDummy", gridOwner.ToCoordinates()); - var consumerEnt1 = entityManager.SpawnEntity("ConsumerDummy", gridOwner.ToCoordinates(0, 1)); - var consumerEnt2 = entityManager.SpawnEntity("ConsumerDummy", gridOwner.ToCoordinates(0, 2)); + var generatorEnt = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates()); + var consumerEnt1 = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 1)); + var consumerEnt2 = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 2)); supplier = entityManager.GetComponent(generatorEnt); consumer1 = entityManager.GetComponent(consumerEnt1); @@ -288,25 +286,25 @@ namespace Content.IntegrationTests.Tests.Power var server = pair.Server; var mapManager = server.ResolveDependency(); var entityManager = server.ResolveDependency(); + var mapSys = entityManager.System(); var gameTiming = server.ResolveDependency(); PowerSupplierComponent supplier = default!; PowerConsumerComponent consumer = default!; await server.WaitAssertion(() => { - var map = mapManager.CreateMap(); - var grid = mapManager.CreateGrid(map); - var gridOwner = grid.Owner; + var map = mapSys.CreateMap(out var mapId); + var grid = mapManager.CreateGridEntity(mapId); // Power only works when anchored for (var i = 0; i < 3; i++) { - grid.SetTile(new Vector2i(0, i), new Tile(1)); - entityManager.SpawnEntity("CableHV", gridOwner.ToCoordinates(0, i)); + mapSys.SetTile(grid, new Vector2i(0, i), new Tile(1)); + entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, i)); } - var generatorEnt = entityManager.SpawnEntity("GeneratorDummy", gridOwner.ToCoordinates()); - var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", gridOwner.ToCoordinates(0, 2)); + var generatorEnt = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates()); + var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 2)); supplier = entityManager.GetComponent(generatorEnt); consumer = entityManager.GetComponent(consumerEnt); @@ -378,6 +376,7 @@ namespace Content.IntegrationTests.Tests.Power var entityManager = server.ResolveDependency(); var gameTiming = server.ResolveDependency(); var batterySys = entityManager.System(); + var mapSys = entityManager.System(); const float startingCharge = 100_000; PowerNetworkBatteryComponent netBattery = default!; @@ -386,19 +385,18 @@ namespace Content.IntegrationTests.Tests.Power await server.WaitAssertion(() => { - var map = mapManager.CreateMap(); - var grid = mapManager.CreateGrid(map); - var gridOwner = grid.Owner; + var map = mapSys.CreateMap(out var mapId); + var grid = mapManager.CreateGridEntity(mapId); // Power only works when anchored for (var i = 0; i < 3; i++) { - grid.SetTile(new Vector2i(0, i), new Tile(1)); - entityManager.SpawnEntity("CableHV", gridOwner.ToCoordinates(0, i)); + mapSys.SetTile(grid, new Vector2i(0, i), new Tile(1)); + entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, i)); } - var generatorEnt = entityManager.SpawnEntity("DischargingBatteryDummy", gridOwner.ToCoordinates()); - var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", gridOwner.ToCoordinates(0, 2)); + var generatorEnt = entityManager.SpawnEntity("DischargingBatteryDummy", grid.Owner.ToCoordinates()); + var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 2)); netBattery = entityManager.GetComponent(generatorEnt); battery = entityManager.GetComponent(generatorEnt); @@ -479,6 +477,7 @@ namespace Content.IntegrationTests.Tests.Power var mapManager = server.ResolveDependency(); var entityManager = server.ResolveDependency(); var batterySys = entityManager.System(); + var mapSys = entityManager.System(); PowerSupplierComponent supplier = default!; PowerNetworkBatteryComponent netBattery = default!; BatteryComponent battery = default!; @@ -490,20 +489,19 @@ namespace Content.IntegrationTests.Tests.Power await server.WaitAssertion(() => { - var map = mapManager.CreateMap(); - var grid = mapManager.CreateGrid(map); - var gridOwner = grid.Owner; + var map = mapSys.CreateMap(out var mapId); + var grid = mapManager.CreateGridEntity(mapId); // Power only works when anchored for (var i = 0; i < 3; i++) { - grid.SetTile(new Vector2i(0, i), new Tile(1)); - entityManager.SpawnEntity("CableHV", gridOwner.ToCoordinates(0, i)); + mapSys.SetTile(grid, new Vector2i(0, i), new Tile(1)); + entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, i)); } - var generatorEnt = entityManager.SpawnEntity("GeneratorDummy", gridOwner.ToCoordinates()); - var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", gridOwner.ToCoordinates(0, 1)); - var batteryEnt = entityManager.SpawnEntity("DischargingBatteryDummy", gridOwner.ToCoordinates(0, 2)); + var generatorEnt = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates()); + var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 1)); + var batteryEnt = entityManager.SpawnEntity("DischargingBatteryDummy", grid.Owner.ToCoordinates(0, 2)); netBattery = entityManager.GetComponent(batteryEnt); battery = entityManager.GetComponent(batteryEnt); supplier = entityManager.GetComponent(generatorEnt); @@ -577,24 +575,24 @@ namespace Content.IntegrationTests.Tests.Power var gameTiming = server.ResolveDependency(); var entityManager = server.ResolveDependency(); var batterySys = entityManager.System(); + var mapSys = entityManager.System(); PowerSupplierComponent supplier = default!; BatteryComponent battery = default!; await server.WaitAssertion(() => { - var map = mapManager.CreateMap(); - var grid = mapManager.CreateGrid(map); - var gridOwner = grid.Owner; + var map = mapSys.CreateMap(out var mapId); + var grid = mapManager.CreateGridEntity(mapId); // Power only works when anchored for (var i = 0; i < 3; i++) { - grid.SetTile(new Vector2i(0, i), new Tile(1)); - entityManager.SpawnEntity("CableHV", gridOwner.ToCoordinates(0, i)); + mapSys.SetTile(grid, new Vector2i(0, i), new Tile(1)); + entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, i)); } - var generatorEnt = entityManager.SpawnEntity("GeneratorDummy", gridOwner.ToCoordinates()); - var batteryEnt = entityManager.SpawnEntity("ChargingBatteryDummy", gridOwner.ToCoordinates(0, 2)); + var generatorEnt = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates()); + var batteryEnt = entityManager.SpawnEntity("ChargingBatteryDummy", grid.Owner.ToCoordinates(0, 2)); supplier = entityManager.GetComponent(generatorEnt); var netBattery = entityManager.GetComponent(batteryEnt); @@ -634,6 +632,7 @@ namespace Content.IntegrationTests.Tests.Power var entityManager = server.ResolveDependency(); var gameTiming = server.ResolveDependency(); var batterySys = entityManager.System(); + var mapSys = entityManager.System(); PowerConsumerComponent consumer = default!; PowerSupplierComponent supplier = default!; PowerNetworkBatteryComponent netBattery = default!; @@ -641,23 +640,22 @@ namespace Content.IntegrationTests.Tests.Power await server.WaitAssertion(() => { - var map = mapManager.CreateMap(); - var grid = mapManager.CreateGrid(map); - var gridOwner = grid.Owner; + var map = mapSys.CreateMap(out var mapId); + var grid = mapManager.CreateGridEntity(mapId); // Power only works when anchored for (var i = 0; i < 4; i++) { - grid.SetTile(new Vector2i(0, i), new Tile(1)); - entityManager.SpawnEntity("CableHV", gridOwner.ToCoordinates(0, i)); + mapSys.SetTile(grid, new Vector2i(0, i), new Tile(1)); + entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, i)); } - var terminal = entityManager.SpawnEntity("CableTerminal", gridOwner.ToCoordinates(0, 1)); + var terminal = entityManager.SpawnEntity("CableTerminal", grid.Owner.ToCoordinates(0, 1)); entityManager.GetComponent(terminal).LocalRotation = Angle.FromDegrees(180); - var batteryEnt = entityManager.SpawnEntity("FullBatteryDummy", gridOwner.ToCoordinates(0, 2)); - var supplyEnt = entityManager.SpawnEntity("GeneratorDummy", gridOwner.ToCoordinates(0, 0)); - var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", gridOwner.ToCoordinates(0, 3)); + var batteryEnt = entityManager.SpawnEntity("FullBatteryDummy", grid.Owner.ToCoordinates(0, 2)); + var supplyEnt = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates(0, 0)); + var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 3)); consumer = entityManager.GetComponent(consumerEnt); supplier = entityManager.GetComponent(supplyEnt); @@ -712,6 +710,7 @@ namespace Content.IntegrationTests.Tests.Power var entityManager = server.ResolveDependency(); var gameTiming = server.ResolveDependency(); var batterySys = entityManager.System(); + var mapSys = entityManager.System(); PowerConsumerComponent consumer = default!; PowerSupplierComponent supplier = default!; PowerNetworkBatteryComponent netBattery = default!; @@ -719,23 +718,22 @@ namespace Content.IntegrationTests.Tests.Power await server.WaitAssertion(() => { - var map = mapManager.CreateMap(); - var grid = mapManager.CreateGrid(map); - var gridOwner = grid.Owner; + var map = mapSys.CreateMap(out var mapId); + var grid = mapManager.CreateGridEntity(mapId); // Power only works when anchored for (var i = 0; i < 4; i++) { - grid.SetTile(new Vector2i(0, i), new Tile(1)); - entityManager.SpawnEntity("CableHV", gridOwner.ToCoordinates(0, i)); + mapSys.SetTile(grid, new Vector2i(0, i), new Tile(1)); + entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, i)); } - var terminal = entityManager.SpawnEntity("CableTerminal", gridOwner.ToCoordinates(0, 1)); + var terminal = entityManager.SpawnEntity("CableTerminal", grid.Owner.ToCoordinates(0, 1)); entityManager.GetComponent(terminal).LocalRotation = Angle.FromDegrees(180); - var batteryEnt = entityManager.SpawnEntity("FullBatteryDummy", gridOwner.ToCoordinates(0, 2)); - var supplyEnt = entityManager.SpawnEntity("GeneratorDummy", gridOwner.ToCoordinates(0, 0)); - var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", gridOwner.ToCoordinates(0, 3)); + var batteryEnt = entityManager.SpawnEntity("FullBatteryDummy", grid.Owner.ToCoordinates(0, 2)); + var supplyEnt = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates(0, 0)); + var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 3)); consumer = entityManager.GetComponent(consumerEnt); supplier = entityManager.GetComponent(supplyEnt); @@ -789,15 +787,15 @@ namespace Content.IntegrationTests.Tests.Power var mapManager = server.ResolveDependency(); var entityManager = server.ResolveDependency(); var batterySys = entityManager.System(); + var mapSys = entityManager.System(); PowerConsumerComponent consumer1 = default!; PowerConsumerComponent consumer2 = default!; PowerSupplierComponent supplier = default!; await server.WaitAssertion(() => { - var map = mapManager.CreateMap(); - var grid = mapManager.CreateGrid(map); - var gridOwner = grid.Owner; + var map = mapSys.CreateMap(out var mapId); + var grid = mapManager.CreateGridEntity(mapId); // Map layout here is // C - consumer @@ -810,19 +808,19 @@ namespace Content.IntegrationTests.Tests.Power // Power only works when anchored for (var i = 0; i < 5; i++) { - grid.SetTile(new Vector2i(0, i), new Tile(1)); - entityManager.SpawnEntity("CableHV", gridOwner.ToCoordinates(0, i)); + mapSys.SetTile(grid, new Vector2i(0, i), new Tile(1)); + entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, i)); } - entityManager.SpawnEntity("CableTerminal", gridOwner.ToCoordinates(0, 2)); - var terminal = entityManager.SpawnEntity("CableTerminal", gridOwner.ToCoordinates(0, 2)); + entityManager.SpawnEntity("CableTerminal", grid.Owner.ToCoordinates(0, 2)); + var terminal = entityManager.SpawnEntity("CableTerminal", grid.Owner.ToCoordinates(0, 2)); entityManager.GetComponent(terminal).LocalRotation = Angle.FromDegrees(180); - var batteryEnt1 = entityManager.SpawnEntity("FullBatteryDummy", gridOwner.ToCoordinates(0, 1)); - var batteryEnt2 = entityManager.SpawnEntity("FullBatteryDummy", gridOwner.ToCoordinates(0, 3)); - var supplyEnt = entityManager.SpawnEntity("GeneratorDummy", gridOwner.ToCoordinates(0, 2)); - var consumerEnt1 = entityManager.SpawnEntity("ConsumerDummy", gridOwner.ToCoordinates(0, 0)); - var consumerEnt2 = entityManager.SpawnEntity("ConsumerDummy", gridOwner.ToCoordinates(0, 4)); + var batteryEnt1 = entityManager.SpawnEntity("FullBatteryDummy", grid.Owner.ToCoordinates(0, 1)); + var batteryEnt2 = entityManager.SpawnEntity("FullBatteryDummy", grid.Owner.ToCoordinates(0, 3)); + var supplyEnt = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates(0, 2)); + var consumerEnt1 = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 0)); + var consumerEnt2 = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 4)); consumer1 = entityManager.GetComponent(consumerEnt1); consumer2 = entityManager.GetComponent(consumerEnt2); @@ -887,6 +885,7 @@ namespace Content.IntegrationTests.Tests.Power var entityManager = server.ResolveDependency(); var gameTiming = server.ResolveDependency(); var batterySys = entityManager.System(); + var mapSys = entityManager.System(); PowerConsumerComponent consumer = default!; PowerSupplierComponent supplier1 = default!; PowerSupplierComponent supplier2 = default!; @@ -897,9 +896,8 @@ namespace Content.IntegrationTests.Tests.Power await server.WaitAssertion(() => { - var map = mapManager.CreateMap(); - var grid = mapManager.CreateGrid(map); - var gridOwner = grid.Owner; + var map = mapSys.CreateMap(out var mapId); + var grid = mapManager.CreateGridEntity(mapId); // Layout is two generators, two batteries, and one load. As to why two: because previously this test // would fail ONLY if there were more than two batteries present, because each of them tries to supply @@ -911,17 +909,17 @@ namespace Content.IntegrationTests.Tests.Power // Place cables for (var i = -2; i <= 2; i++) { - grid.SetTile(new Vector2i(0, i), new Tile(1)); - entityManager.SpawnEntity("CableHV", gridOwner.ToCoordinates(0, i)); + mapSys.SetTile(grid, new Vector2i(0, i), new Tile(1)); + entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, i)); } - var batteryEnt1 = entityManager.SpawnEntity("FullBatteryDummy", gridOwner.ToCoordinates(0, 2)); - var batteryEnt2 = entityManager.SpawnEntity("FullBatteryDummy", gridOwner.ToCoordinates(0, -2)); + var batteryEnt1 = entityManager.SpawnEntity("FullBatteryDummy", grid.Owner.ToCoordinates(0, 2)); + var batteryEnt2 = entityManager.SpawnEntity("FullBatteryDummy", grid.Owner.ToCoordinates(0, -2)); - var supplyEnt1 = entityManager.SpawnEntity("GeneratorDummy", gridOwner.ToCoordinates(0, 1)); - var supplyEnt2 = entityManager.SpawnEntity("GeneratorDummy", gridOwner.ToCoordinates(0, -1)); + var supplyEnt1 = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates(0, 1)); + var supplyEnt2 = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates(0, -1)); - var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", gridOwner.ToCoordinates(0, 0)); + var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 0)); consumer = entityManager.GetComponent(consumerEnt); supplier1 = entityManager.GetComponent(supplyEnt1); @@ -985,15 +983,15 @@ namespace Content.IntegrationTests.Tests.Power var mapManager = server.ResolveDependency(); var entityManager = server.ResolveDependency(); var batterySys = entityManager.System(); + var mapSys = entityManager.System(); PowerConsumerComponent consumer1 = default!; PowerConsumerComponent consumer2 = default!; PowerSupplierComponent supplier = default!; await server.WaitAssertion(() => { - var map = mapManager.CreateMap(); - var grid = mapManager.CreateGrid(map); - var gridOwner = grid.Owner; + var map = mapSys.CreateMap(out var mapId); + var grid = mapManager.CreateGridEntity(mapId); // Map layout here is // C - consumer @@ -1006,19 +1004,19 @@ namespace Content.IntegrationTests.Tests.Power // Power only works when anchored for (var i = 0; i < 5; i++) { - grid.SetTile(new Vector2i(0, i), new Tile(1)); - entityManager.SpawnEntity("CableHV", gridOwner.ToCoordinates(0, i)); + mapSys.SetTile(grid, new Vector2i(0, i), new Tile(1)); + entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, i)); } - entityManager.SpawnEntity("CableTerminal", gridOwner.ToCoordinates(0, 2)); - var terminal = entityManager.SpawnEntity("CableTerminal", gridOwner.ToCoordinates(0, 2)); + entityManager.SpawnEntity("CableTerminal", grid.Owner.ToCoordinates(0, 2)); + var terminal = entityManager.SpawnEntity("CableTerminal", grid.Owner.ToCoordinates(0, 2)); entityManager.GetComponent(terminal).LocalRotation = Angle.FromDegrees(180); - var batteryEnt1 = entityManager.SpawnEntity("FullBatteryDummy", gridOwner.ToCoordinates(0, 1)); - var batteryEnt2 = entityManager.SpawnEntity("FullBatteryDummy", gridOwner.ToCoordinates(0, 3)); - var supplyEnt = entityManager.SpawnEntity("GeneratorDummy", gridOwner.ToCoordinates(0, 2)); - var consumerEnt1 = entityManager.SpawnEntity("ConsumerDummy", gridOwner.ToCoordinates(0, 0)); - var consumerEnt2 = entityManager.SpawnEntity("ConsumerDummy", gridOwner.ToCoordinates(0, 4)); + var batteryEnt1 = entityManager.SpawnEntity("FullBatteryDummy", grid.Owner.ToCoordinates(0, 1)); + var batteryEnt2 = entityManager.SpawnEntity("FullBatteryDummy", grid.Owner.ToCoordinates(0, 3)); + var supplyEnt = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates(0, 2)); + var consumerEnt1 = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 0)); + var consumerEnt2 = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 4)); consumer1 = entityManager.GetComponent(consumerEnt1); consumer2 = entityManager.GetComponent(consumerEnt2); @@ -1073,29 +1071,29 @@ namespace Content.IntegrationTests.Tests.Power var mapManager = server.ResolveDependency(); var entityManager = server.ResolveDependency(); var batterySys = entityManager.System(); + var mapSys = entityManager.System(); PowerConsumerComponent consumer = default!; PowerSupplierComponent supplier = default!; PowerNetworkBatteryComponent netBattery = default!; await server.WaitPost(() => { - var map = mapManager.CreateMap(); - var grid = mapManager.CreateGrid(map); - var gridOwner = grid.Owner; + var map = mapSys.CreateMap(out var mapId); + var grid = mapManager.CreateGridEntity(mapId); // Power only works when anchored for (var i = 0; i < 4; i++) { - grid.SetTile(new Vector2i(0, i), new Tile(1)); - entityManager.SpawnEntity("CableHV", gridOwner.ToCoordinates(0, i)); + mapSys.SetTile(grid, new Vector2i(0, i), new Tile(1)); + entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, i)); } - var terminal = entityManager.SpawnEntity("CableTerminal", gridOwner.ToCoordinates(0, 1)); + var terminal = entityManager.SpawnEntity("CableTerminal", grid.Owner.ToCoordinates(0, 1)); entityManager.GetComponent(terminal).LocalRotation = Angle.FromDegrees(180); - var batteryEnt = entityManager.SpawnEntity("FullBatteryDummy", gridOwner.ToCoordinates(0, 2)); - var supplyEnt = entityManager.SpawnEntity("GeneratorDummy", gridOwner.ToCoordinates(0, 0)); - var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", gridOwner.ToCoordinates(0, 3)); + var batteryEnt = entityManager.SpawnEntity("FullBatteryDummy", grid.Owner.ToCoordinates(0, 2)); + var supplyEnt = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates(0, 0)); + var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 3)); consumer = entityManager.GetComponent(consumerEnt); supplier = entityManager.GetComponent(supplyEnt); @@ -1158,6 +1156,7 @@ namespace Content.IntegrationTests.Tests.Power var mapManager = server.ResolveDependency(); var entityManager = server.ResolveDependency(); var nodeContainer = entityManager.System(); + var mapSys = entityManager.System(); CableNode leftNode = default!; CableNode rightNode = default!; Node batteryInput = default!; @@ -1165,25 +1164,24 @@ namespace Content.IntegrationTests.Tests.Power await server.WaitAssertion(() => { - var map = mapManager.CreateMap(); - var grid = mapManager.CreateGrid(map); - var gridOwner = grid.Owner; + var map = mapSys.CreateMap(out var mapId); + var grid = mapManager.CreateGridEntity(mapId); // Power only works when anchored for (var i = 0; i < 4; i++) { - grid.SetTile(new Vector2i(0, i), new Tile(1)); + mapSys.SetTile(grid, new Vector2i(0, i), new Tile(1)); } - var leftEnt = entityManager.SpawnEntity("CableHV", gridOwner.ToCoordinates(0, 0)); - entityManager.SpawnEntity("CableHV", gridOwner.ToCoordinates(0, 1)); - entityManager.SpawnEntity("CableHV", gridOwner.ToCoordinates(0, 2)); - var rightEnt = entityManager.SpawnEntity("CableHV", gridOwner.ToCoordinates(0, 3)); + var leftEnt = entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, 0)); + entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, 1)); + entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, 2)); + var rightEnt = entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, 3)); - var terminal = entityManager.SpawnEntity("CableTerminal", gridOwner.ToCoordinates(0, 1)); + var terminal = entityManager.SpawnEntity("CableTerminal", grid.Owner.ToCoordinates(0, 1)); entityManager.GetComponent(terminal).LocalRotation = Angle.FromDegrees(180); - var battery = entityManager.SpawnEntity("FullBatteryDummy", gridOwner.ToCoordinates(0, 2)); + var battery = entityManager.SpawnEntity("FullBatteryDummy", grid.Owner.ToCoordinates(0, 2)); var batteryNodeContainer = entityManager.GetComponent(battery); if (nodeContainer.TryGetNode(entityManager.GetComponent(leftEnt), @@ -1224,29 +1222,29 @@ namespace Content.IntegrationTests.Tests.Power var mapManager = server.ResolveDependency(); var entityManager = server.ResolveDependency(); var batterySys = entityManager.System(); + var mapSys = entityManager.System(); PowerNetworkBatteryComponent substationNetBattery = default!; BatteryComponent apcBattery = default!; await server.WaitAssertion(() => { - var map = mapManager.CreateMap(); - var grid = mapManager.CreateGrid(map); - var gridOwner = grid.Owner; + var map = mapSys.CreateMap(out var mapId); + var grid = mapManager.CreateGridEntity(mapId); // Power only works when anchored for (var i = 0; i < 3; i++) { - grid.SetTile(new Vector2i(0, i), new Tile(1)); + mapSys.SetTile(grid, new Vector2i(0, i), new Tile(1)); } - entityManager.SpawnEntity("CableHV", gridOwner.ToCoordinates(0, 0)); - entityManager.SpawnEntity("CableHV", gridOwner.ToCoordinates(0, 1)); - entityManager.SpawnEntity("CableMV", gridOwner.ToCoordinates(0, 1)); - entityManager.SpawnEntity("CableMV", gridOwner.ToCoordinates(0, 2)); + entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, 0)); + entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, 1)); + entityManager.SpawnEntity("CableMV", grid.Owner.ToCoordinates(0, 1)); + entityManager.SpawnEntity("CableMV", grid.Owner.ToCoordinates(0, 2)); - var generatorEnt = entityManager.SpawnEntity("GeneratorDummy", gridOwner.ToCoordinates(0, 0)); - var substationEnt = entityManager.SpawnEntity("SubstationDummy", gridOwner.ToCoordinates(0, 1)); - var apcEnt = entityManager.SpawnEntity("ApcDummy", gridOwner.ToCoordinates(0, 2)); + var generatorEnt = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates(0, 0)); + var substationEnt = entityManager.SpawnEntity("SubstationDummy", grid.Owner.ToCoordinates(0, 1)); + var apcEnt = entityManager.SpawnEntity("ApcDummy", grid.Owner.ToCoordinates(0, 2)); var generatorSupplier = entityManager.GetComponent(generatorEnt); substationNetBattery = entityManager.GetComponent(substationEnt); @@ -1281,33 +1279,33 @@ namespace Content.IntegrationTests.Tests.Power var entityManager = server.ResolveDependency(); var batterySys = entityManager.System(); var extensionCableSystem = entityManager.System(); + var mapSys = entityManager.System(); PowerNetworkBatteryComponent apcNetBattery = default!; ApcPowerReceiverComponent receiver = default!; ApcPowerReceiverComponent unpoweredReceiver = default!; await server.WaitAssertion(() => { - var map = mapManager.CreateMap(); - var grid = mapManager.CreateGrid(map); - var gridOwner = grid.Owner; + var map = mapSys.CreateMap(out var mapId); + var grid = mapManager.CreateGridEntity(mapId); const int range = 5; // Power only works when anchored for (var i = 0; i < range; i++) { - grid.SetTile(new Vector2i(0, i), new Tile(1)); + mapSys.SetTile(grid, new Vector2i(0, i), new Tile(1)); } - var apcEnt = entityManager.SpawnEntity("ApcDummy", gridOwner.ToCoordinates(0, 0)); - var apcExtensionEnt = entityManager.SpawnEntity("CableApcExtension", gridOwner.ToCoordinates(0, 0)); + var apcEnt = entityManager.SpawnEntity("ApcDummy", grid.Owner.ToCoordinates(0, 0)); + var apcExtensionEnt = entityManager.SpawnEntity("CableApcExtension", grid.Owner.ToCoordinates(0, 0)); // Create a powered receiver in range (range is 0 indexed) - var powerReceiverEnt = entityManager.SpawnEntity("ApcPowerReceiverDummy", gridOwner.ToCoordinates(0, range - 1)); + var powerReceiverEnt = entityManager.SpawnEntity("ApcPowerReceiverDummy", grid.Owner.ToCoordinates(0, range - 1)); receiver = entityManager.GetComponent(powerReceiverEnt); // Create an unpowered receiver outside range - var unpoweredReceiverEnt = entityManager.SpawnEntity("ApcPowerReceiverDummy", gridOwner.ToCoordinates(0, range)); + var unpoweredReceiverEnt = entityManager.SpawnEntity("ApcPowerReceiverDummy", grid.Owner.ToCoordinates(0, range)); unpoweredReceiver = entityManager.GetComponent(unpoweredReceiverEnt); var battery = entityManager.GetComponent(apcEnt); diff --git a/Content.IntegrationTests/Tests/PrototypeSaveTest.cs b/Content.IntegrationTests/Tests/PrototypeSaveTest.cs index 9e26fa5eaa..1ef34365ea 100644 --- a/Content.IntegrationTests/Tests/PrototypeSaveTest.cs +++ b/Content.IntegrationTests/Tests/PrototypeSaveTest.cs @@ -40,6 +40,7 @@ public sealed class PrototypeSaveTest var prototypeMan = server.ResolveDependency(); var seriMan = server.ResolveDependency(); var compFact = server.ResolveDependency(); + var mapSystem = server.System(); var prototypes = new List(); EntityUid uid; @@ -77,7 +78,7 @@ public sealed class PrototypeSaveTest await server.WaitAssertion(() => { - Assert.That(!mapManager.IsMapInitialized(mapId)); + Assert.That(!mapSystem.IsInitialized(mapId)); var testLocation = grid.Owner.ToCoordinates(); Assert.Multiple(() => @@ -184,7 +185,7 @@ public sealed class PrototypeSaveTest IDependencyCollection dependencies, bool alwaysWrite = false, ISerializationContext? context = null) { - if (WritingComponent != "Transform" && (Prototype?.NoSpawn == false)) + if (WritingComponent != "Transform" && Prototype?.HideSpawnMenu == false) { // Maybe this will be necessary in the future, but at the moment it just indicates that there is some // issue, like a non-nullable entityUid data-field. If a component MUST have an entity uid to work with, diff --git a/Content.IntegrationTests/Tests/Puller/PullerTest.cs b/Content.IntegrationTests/Tests/Puller/PullerTest.cs index 87d174f727..a4fde86dbf 100644 --- a/Content.IntegrationTests/Tests/Puller/PullerTest.cs +++ b/Content.IntegrationTests/Tests/Puller/PullerTest.cs @@ -29,7 +29,7 @@ public sealed class PullerTest { foreach (var proto in protoManager.EnumeratePrototypes()) { - if (!proto.TryGetComponent(out PullerComponent? puller)) + if (!proto.TryGetComponent(out PullerComponent? puller, compFactory)) continue; if (!puller.NeedsHands) diff --git a/Content.IntegrationTests/Tests/ResearchTest.cs b/Content.IntegrationTests/Tests/ResearchTest.cs index ee319daa43..7ae29a79ff 100644 --- a/Content.IntegrationTests/Tests/ResearchTest.cs +++ b/Content.IntegrationTests/Tests/ResearchTest.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using Content.Shared.Lathe; using Content.Shared.Research.Prototypes; +using Robust.Shared.GameObjects; using Robust.Shared.Prototypes; namespace Content.IntegrationTests.Tests; @@ -52,6 +53,7 @@ public sealed class ResearchTest var server = pair.Server; var protoManager = server.ResolveDependency(); + var compFact = server.ResolveDependency(); await server.WaitAssertion(() => { @@ -65,7 +67,7 @@ public sealed class ResearchTest if (pair.IsTestPrototype(proto)) continue; - if (!proto.TryGetComponent(out var lathe)) + if (!proto.TryGetComponent(out var lathe, compFact)) continue; allLathes.Add(lathe); } diff --git a/Content.IntegrationTests/Tests/Round/JobTest.cs b/Content.IntegrationTests/Tests/Round/JobTest.cs index 716e3cf4c2..215890791d 100644 --- a/Content.IntegrationTests/Tests/Round/JobTest.cs +++ b/Content.IntegrationTests/Tests/Round/JobTest.cs @@ -18,14 +18,14 @@ namespace Content.IntegrationTests.Tests.Round; [TestFixture] public sealed class JobTest { - private static ProtoId _passenger = "Passenger"; - private static ProtoId _engineer = "StationEngineer"; - private static ProtoId _captain = "Captain"; + private static readonly ProtoId Passenger = "Passenger"; + private static readonly ProtoId Engineer = "StationEngineer"; + private static readonly ProtoId Captain = "Captain"; private static string _map = "JobTestMap"; [TestPrototypes] - public static string JobTestMap = @$" + private static readonly string JobTestMap = @$" - type: gameMap id: {_map} mapName: {_map} @@ -39,12 +39,12 @@ public sealed class JobTest mapNameTemplate: ""Empty"" - type: StationJobs availableJobs: - {_passenger}: [ -1, -1 ] - {_engineer}: [ -1, -1 ] - {_captain}: [ 1, 1 ] + {Passenger}: [ -1, -1 ] + {Engineer}: [ -1, -1 ] + {Captain}: [ 1, 1 ] "; - public void AssertJob(TestPair pair, ProtoId job, NetUserId? user = null, bool isAntag = false) + private void AssertJob(TestPair pair, ProtoId job, NetUserId? user = null, bool isAntag = false) { var jobSys = pair.Server.System(); var mindSys = pair.Server.System(); @@ -92,7 +92,7 @@ public sealed class JobTest await pair.Server.WaitPost(() => ticker.StartRound()); await pair.RunTicksSync(10); - AssertJob(pair, _passenger); + AssertJob(pair, Passenger); await pair.Server.WaitPost(() => ticker.RestartRound()); await pair.CleanReturnAsync(); @@ -116,21 +116,21 @@ public sealed class JobTest Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby)); Assert.That(pair.Client.AttachedEntity, Is.Null); - await pair.SetJobPriorities((_passenger, JobPriority.Medium), (_engineer, JobPriority.High)); + await pair.SetJobPriorities((Passenger, JobPriority.Medium), (Engineer, JobPriority.High)); ticker.ToggleReadyAll(true); await pair.Server.WaitPost(() => ticker.StartRound()); await pair.RunTicksSync(10); - AssertJob(pair, _engineer); + AssertJob(pair, Engineer); await pair.Server.WaitPost(() => ticker.RestartRound()); Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby)); - await pair.SetJobPriorities((_passenger, JobPriority.High), (_engineer, JobPriority.Medium)); + await pair.SetJobPriorities((Passenger, JobPriority.High), (Engineer, JobPriority.Medium)); ticker.ToggleReadyAll(true); await pair.Server.WaitPost(() => ticker.StartRound()); await pair.RunTicksSync(10); - AssertJob(pair, _passenger); + AssertJob(pair, Passenger); await pair.Server.WaitPost(() => ticker.RestartRound()); await pair.CleanReturnAsync(); @@ -155,18 +155,18 @@ public sealed class JobTest Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby)); Assert.That(pair.Client.AttachedEntity, Is.Null); - var captain = pair.Server.ProtoMan.Index(_captain); - var engineer = pair.Server.ProtoMan.Index(_engineer); - var passenger = pair.Server.ProtoMan.Index(_passenger); + var captain = pair.Server.ProtoMan.Index(Captain); + var engineer = pair.Server.ProtoMan.Index(Engineer); + var passenger = pair.Server.ProtoMan.Index(Passenger); Assert.That(captain.Weight, Is.GreaterThan(engineer.Weight)); Assert.That(engineer.Weight, Is.EqualTo(passenger.Weight)); - await pair.SetJobPriorities((_passenger, JobPriority.Medium), (_engineer, JobPriority.High), (_captain, JobPriority.Low)); + await pair.SetJobPriorities((Passenger, JobPriority.Medium), (Engineer, JobPriority.High), (Captain, JobPriority.Low)); ticker.ToggleReadyAll(true); await pair.Server.WaitPost(() => ticker.StartRound()); await pair.RunTicksSync(10); - AssertJob(pair, _captain); + AssertJob(pair, Captain); await pair.Server.WaitPost(() => ticker.RestartRound()); await pair.CleanReturnAsync(); @@ -197,22 +197,22 @@ public sealed class JobTest var captain = engineers[3]; engineers.RemoveAt(3); - await pair.SetJobPriorities(captain, (_captain, JobPriority.High), (_engineer, JobPriority.Medium)); + await pair.SetJobPriorities(captain, (Captain, JobPriority.High), (Engineer, JobPriority.Medium)); foreach (var engi in engineers) { - await pair.SetJobPriorities(engi, (_captain, JobPriority.Medium), (_engineer, JobPriority.High)); + await pair.SetJobPriorities(engi, (Captain, JobPriority.Medium), (Engineer, JobPriority.High)); } ticker.ToggleReadyAll(true); await pair.Server.WaitPost(() => ticker.StartRound()); await pair.RunTicksSync(10); - AssertJob(pair, _captain, captain); + AssertJob(pair, Captain, captain); Assert.Multiple(() => { foreach (var engi in engineers) { - AssertJob(pair, _engineer, engi); + AssertJob(pair, Engineer, engi); } }); diff --git a/Content.IntegrationTests/Tests/SalvageTest.cs b/Content.IntegrationTests/Tests/SalvageTest.cs index 9d75428beb..5dfba82308 100644 --- a/Content.IntegrationTests/Tests/SalvageTest.cs +++ b/Content.IntegrationTests/Tests/SalvageTest.cs @@ -1,5 +1,4 @@ using System.Linq; -using Content.Server.Salvage; using Content.Shared.CCVar; using Content.Shared.Salvage; using Robust.Server.GameObjects; @@ -28,6 +27,7 @@ public sealed class SalvageTest var mapManager = server.ResolveDependency(); var prototypeManager = server.ResolveDependency(); var cfg = server.ResolveDependency(); + var mapSystem = entManager.System(); Assert.That(cfg.GetCVar(CCVars.GridFill), Is.False); await server.WaitPost(() => @@ -36,7 +36,7 @@ public sealed class SalvageTest { var mapFile = salvage.MapPath; - var mapId = mapManager.CreateMap(); + mapSystem.CreateMap(out var mapId); try { Assert.That(mapLoader.TryLoad(mapId, mapFile.ToString(), out var roots)); diff --git a/Content.IntegrationTests/Tests/SaveLoadMapTest.cs b/Content.IntegrationTests/Tests/SaveLoadMapTest.cs index db2109ca59..213da5d786 100644 --- a/Content.IntegrationTests/Tests/SaveLoadMapTest.cs +++ b/Content.IntegrationTests/Tests/SaveLoadMapTest.cs @@ -23,6 +23,7 @@ namespace Content.IntegrationTests.Tests var mapManager = server.ResolveDependency(); var sEntities = server.ResolveDependency(); var mapLoader = sEntities.System(); + var mapSystem = sEntities.System(); var xformSystem = sEntities.EntitySysManager.GetEntitySystem(); var resManager = server.ResolveDependency(); var cfg = server.ResolveDependency(); @@ -33,19 +34,17 @@ namespace Content.IntegrationTests.Tests var dir = new ResPath(mapPath).Directory; resManager.UserData.CreateDir(dir); - var mapId = mapManager.CreateMap(); + mapSystem.CreateMap(out var mapId); { - var mapGrid = mapManager.CreateGrid(mapId); - var mapGridEnt = mapGrid.Owner; - xformSystem.SetWorldPosition(mapGridEnt, new Vector2(10, 10)); - mapGrid.SetTile(new Vector2i(0, 0), new Tile(1, (TileRenderFlag) 1, 255)); + var mapGrid = mapManager.CreateGridEntity(mapId); + xformSystem.SetWorldPosition(mapGrid, new Vector2(10, 10)); + mapSystem.SetTile(mapGrid, new Vector2i(0, 0), new Tile(1, (TileRenderFlag) 1, 255)); } { - var mapGrid = mapManager.CreateGrid(mapId); - var mapGridEnt = mapGrid.Owner; - xformSystem.SetWorldPosition(mapGridEnt, new Vector2(-8, -8)); - mapGrid.SetTile(new Vector2i(0, 0), new Tile(2, (TileRenderFlag) 1, 254)); + var mapGrid = mapManager.CreateGridEntity(mapId); + xformSystem.SetWorldPosition(mapGrid, new Vector2(-8, -8)); + mapSystem.SetTile(mapGrid, new Vector2i(0, 0), new Tile(2, (TileRenderFlag) 1, 254)); } Assert.Multiple(() => mapLoader.SaveMap(mapId, mapPath)); @@ -74,7 +73,7 @@ namespace Content.IntegrationTests.Tests Assert.Multiple(() => { Assert.That(xformSystem.GetWorldPosition(gridXform), Is.EqualTo(new Vector2(10, 10))); - Assert.That(mapGrid.GetTileRef(new Vector2i(0, 0)).Tile, Is.EqualTo(new Tile(1, (TileRenderFlag) 1, 255))); + Assert.That(mapSystem.GetTileRef(gridUid, mapGrid, new Vector2i(0, 0)).Tile, Is.EqualTo(new Tile(1, (TileRenderFlag) 1, 255))); }); } { @@ -88,7 +87,7 @@ namespace Content.IntegrationTests.Tests Assert.Multiple(() => { Assert.That(xformSystem.GetWorldPosition(gridXform), Is.EqualTo(new Vector2(-8, -8))); - Assert.That(mapGrid.GetTileRef(new Vector2i(0, 0)).Tile, Is.EqualTo(new Tile(2, (TileRenderFlag) 1, 254))); + Assert.That(mapSystem.GetTileRef(gridUid, mapGrid, new Vector2i(0, 0)).Tile, Is.EqualTo(new Tile(2, (TileRenderFlag) 1, 254))); }); } }); diff --git a/Content.IntegrationTests/Tests/SaveLoadSaveTest.cs b/Content.IntegrationTests/Tests/SaveLoadSaveTest.cs index 72c6d464e2..4facd5ee40 100644 --- a/Content.IntegrationTests/Tests/SaveLoadSaveTest.cs +++ b/Content.IntegrationTests/Tests/SaveLoadSaveTest.cs @@ -25,17 +25,18 @@ namespace Content.IntegrationTests.Tests var server = pair.Server; var entManager = server.ResolveDependency(); var mapLoader = entManager.System(); + var mapSystem = entManager.System(); var mapManager = server.ResolveDependency(); var cfg = server.ResolveDependency(); Assert.That(cfg.GetCVar(CCVars.GridFill), Is.False); await server.WaitPost(() => { - var mapId0 = mapManager.CreateMap(); + mapSystem.CreateMap(out var mapId0); // TODO: Properly find the "main" station grid. - var grid0 = mapManager.CreateGrid(mapId0); + var grid0 = mapManager.CreateGridEntity(mapId0); mapLoader.Save(grid0.Owner, "save load save 1.yml"); - var mapId1 = mapManager.CreateMap(); + mapSystem.CreateMap(out var mapId1); EntityUid grid1 = default!; #pragma warning disable NUnit2045 Assert.That(mapLoader.TryLoad(mapId1, "save load save 1.yml", out var roots, new MapLoadOptions() { LoadMap = false }), $"Failed to load test map {TestMap}"); @@ -101,6 +102,7 @@ namespace Content.IntegrationTests.Tests var server = pair.Server; var mapLoader = server.ResolveDependency().GetEntitySystem(); var mapManager = server.ResolveDependency(); + var mapSystem = server.System(); MapId mapId = default; var cfg = server.ResolveDependency(); @@ -109,8 +111,7 @@ namespace Content.IntegrationTests.Tests // Load bagel.yml as uninitialized map, and save it to ensure it's up to date. server.Post(() => { - mapId = mapManager.CreateMap(); - mapManager.AddUninitializedMap(mapId); + mapSystem.CreateMap(out mapId, runMapInit: false); mapManager.SetMapPaused(mapId, true); Assert.That(mapLoader.TryLoad(mapId, TestMap, out _), $"Failed to load test map {TestMap}"); mapLoader.SaveMap(mapId, "load save ticks save 1.yml"); @@ -182,7 +183,8 @@ namespace Content.IntegrationTests.Tests await using var pair = await PoolManager.GetServerClient(); var server = pair.Server; - var mapLoader = server.ResolveDependency().GetEntitySystem(); + var mapLoader = server.System(); + var mapSystem = server.System(); var mapManager = server.ResolveDependency(); var userData = server.ResolveDependency().UserData; var cfg = server.ResolveDependency(); @@ -197,8 +199,7 @@ namespace Content.IntegrationTests.Tests // Load & save the first map server.Post(() => { - mapId = mapManager.CreateMap(); - mapManager.AddUninitializedMap(mapId); + mapSystem.CreateMap(out mapId, runMapInit: false); mapManager.SetMapPaused(mapId, true); Assert.That(mapLoader.TryLoad(mapId, TestMap, out _), $"Failed to load test map {TestMap}"); mapLoader.SaveMap(mapId, fileA); @@ -217,8 +218,7 @@ namespace Content.IntegrationTests.Tests server.Post(() => { mapManager.DeleteMap(mapId); - mapManager.CreateMap(mapId); - mapManager.AddUninitializedMap(mapId); + mapSystem.CreateMap(out mapId, runMapInit: false); mapManager.SetMapPaused(mapId, true); Assert.That(mapLoader.TryLoad(mapId, TestMap, out _), $"Failed to load test map {TestMap}"); mapLoader.SaveMap(mapId, fileB); diff --git a/Content.IntegrationTests/Tests/Serialization/SerializationTest.cs b/Content.IntegrationTests/Tests/Serialization/SerializationTest.cs index 052ea997c0..339420362c 100644 --- a/Content.IntegrationTests/Tests/Serialization/SerializationTest.cs +++ b/Content.IntegrationTests/Tests/Serialization/SerializationTest.cs @@ -24,7 +24,7 @@ public sealed partial class SerializationTest Enum value = TestEnum.Bb; - var node = seriMan.WriteValue(value, notNullableOverride:true); + var node = seriMan.WriteValue(value, notNullableOverride: true); var valueNode = node as ValueDataNode; Assert.That(valueNode, Is.Not.Null); @@ -34,22 +34,22 @@ public sealed partial class SerializationTest var errors = seriMan.ValidateNode(valueNode).GetErrors(); Assert.That(errors.Any(), Is.False); - var deserialized = seriMan.Read(node, notNullableOverride:true); + var deserialized = seriMan.Read(node, notNullableOverride: true); Assert.That(deserialized, Is.EqualTo(value)); // Repeat test with enums in a data definitions. var data = new TestData { Value = TestEnum.Cc, - Sequence = new() {TestEnum.Dd, TestEnum.Aa} + Sequence = [TestEnum.Dd, TestEnum.Aa] }; - node = seriMan.WriteValue(data, notNullableOverride:true); + node = seriMan.WriteValue(data, notNullableOverride: true); errors = seriMan.ValidateNode(node).GetErrors(); Assert.That(errors.Any(), Is.False); - var deserializedData = seriMan.Read(node, notNullableOverride:false); + var deserializedData = seriMan.Read(node, notNullableOverride: false); Assert.That(deserializedData.Value, Is.EqualTo(data.Value)); Assert.That(deserializedData.Sequence.Count, Is.EqualTo(data.Sequence.Count)); @@ -60,7 +60,7 @@ public sealed partial class SerializationTest Enum genericValue = TestEnum.Bb; TestEnum typedValue = TestEnum.Bb; - var genericNode = seriMan.WriteValue(genericValue, notNullableOverride:true); + var genericNode = seriMan.WriteValue(genericValue, notNullableOverride: true); var typedNode = seriMan.WriteValue(typedValue); Assert.That(seriMan.ValidateNode(genericNode).GetErrors().Any(), Is.False); @@ -76,7 +76,7 @@ public sealed partial class SerializationTest [DataDefinition] private sealed partial class TestData { - [DataField("value")] public Enum Value = default!; - [DataField("sequence")] public List Sequence = default!; + [DataField] public Enum Value = default!; + [DataField] public List Sequence = default!; } } diff --git a/Content.IntegrationTests/Tests/ShuttleTest.cs b/Content.IntegrationTests/Tests/ShuttleTest.cs index fb786373a5..da5b82d91e 100644 --- a/Content.IntegrationTests/Tests/ShuttleTest.cs +++ b/Content.IntegrationTests/Tests/ShuttleTest.cs @@ -2,7 +2,6 @@ using System.Numerics; using Content.Server.Shuttles.Components; using Robust.Shared.GameObjects; using Robust.Shared.Map; -using Robust.Shared.Maths; using Robust.Shared.Physics; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Systems; @@ -23,33 +22,33 @@ namespace Content.IntegrationTests.Tests var entManager = server.ResolveDependency(); var physicsSystem = entManager.System(); - EntityUid gridEnt = default; PhysicsComponent gridPhys = null; + var map = await pair.CreateTestMap(); + await server.WaitAssertion(() => { - var mapId = mapMan.CreateMap(); - var grid = mapMan.CreateGridEntity(mapId); - gridEnt = grid.Owner; + var mapId = map.MapId; + var grid = map.Grid; Assert.Multiple(() => { - Assert.That(entManager.HasComponent(gridEnt)); - Assert.That(entManager.TryGetComponent(gridEnt, out gridPhys)); + Assert.That(entManager.HasComponent(grid)); + Assert.That(entManager.TryGetComponent(grid, out gridPhys)); }); Assert.Multiple(() => { Assert.That(gridPhys.BodyType, Is.EqualTo(BodyType.Dynamic)); - Assert.That(entManager.GetComponent(gridEnt).LocalPosition, Is.EqualTo(Vector2.Zero)); + Assert.That(entManager.GetComponent(grid).LocalPosition, Is.EqualTo(Vector2.Zero)); }); - physicsSystem.ApplyLinearImpulse(gridEnt, Vector2.One, body: gridPhys); + physicsSystem.ApplyLinearImpulse(grid, Vector2.One, body: gridPhys); }); await server.WaitRunTicks(1); await server.WaitAssertion(() => { - Assert.That(entManager.GetComponent(gridEnt).LocalPosition, Is.Not.EqualTo(Vector2.Zero)); + Assert.That(entManager.GetComponent(map.Grid).LocalPosition, Is.Not.EqualTo(Vector2.Zero)); }); await pair.CleanReturnAsync(); } diff --git a/Content.IntegrationTests/Tests/Sprite/ItemSpriteTest.cs b/Content.IntegrationTests/Tests/Sprite/ItemSpriteTest.cs index 1762c4213c..bf75188f02 100644 --- a/Content.IntegrationTests/Tests/Sprite/ItemSpriteTest.cs +++ b/Content.IntegrationTests/Tests/Sprite/ItemSpriteTest.cs @@ -19,12 +19,12 @@ namespace Content.IntegrationTests.Tests.Sprite; /// - Shouldn't have an item component /// - Is missing the required sprite information. /// If none of the abveo are true, it might need to be added to the list of ignored components, see -/// +/// /// [TestFixture] public sealed class PrototypeSaveTest { - private static HashSet _ignored = new() + private static readonly HashSet Ignored = new() { // The only prototypes that should get ignored are those that REQUIRE setup to get a sprite. At that point it is // the responsibility of the spawner to ensure that a valid sprite is set. @@ -34,13 +34,13 @@ public sealed class PrototypeSaveTest [Test] public async Task AllItemsHaveSpritesTest() { - var settings = new PoolSettings() {Connected = true}; // client needs to be in-game + var settings = new PoolSettings() { Connected = true }; // client needs to be in-game await using var pair = await PoolManager.GetServerClient(settings); - List badPrototypes = new(); + List badPrototypes = []; await pair.Client.WaitPost(() => { - foreach (var proto in pair.GetPrototypesWithComponent(_ignored)) + foreach (var proto in pair.GetPrototypesWithComponent(Ignored)) { var dummy = pair.Client.EntMan.Spawn(proto.ID); pair.Client.EntMan.RunMapInit(dummy, pair.Client.MetaData(dummy)); diff --git a/Content.IntegrationTests/Tests/Minds/JobTests.cs b/Content.IntegrationTests/Tests/Station/JobTests.cs similarity index 100% rename from Content.IntegrationTests/Tests/Minds/JobTests.cs rename to Content.IntegrationTests/Tests/Station/JobTests.cs diff --git a/Content.IntegrationTests/Tests/Toolshed/ToolshedTest.cs b/Content.IntegrationTests/Tests/Toolshed/ToolshedTest.cs index dd68ff1ccf..7de81fb3dc 100644 --- a/Content.IntegrationTests/Tests/Toolshed/ToolshedTest.cs +++ b/Content.IntegrationTests/Tests/Toolshed/ToolshedTest.cs @@ -36,16 +36,18 @@ public abstract class ToolshedTest : IInvocationContext await TearDown(); } - protected virtual async Task TearDown() + protected virtual Task TearDown() { Assert.That(_expectedErrors, Is.Empty); ClearErrors(); + + return Task.CompletedTask; } [SetUp] public virtual async Task Setup() { - Pair = await PoolManager.GetServerClient(new PoolSettings {Connected = Connected}); + Pair = await PoolManager.GetServerClient(new PoolSettings { Connected = Connected }); Server = Pair.Server; if (Connected) @@ -142,7 +144,7 @@ public abstract class ToolshedTest : IInvocationContext ); } - done: + done: _errors.Add(err); } diff --git a/Content.IntegrationTests/Tests/VendingMachineRestockTest.cs b/Content.IntegrationTests/Tests/VendingMachineRestockTest.cs index 3cceaefbdc..e067a27854 100644 --- a/Content.IntegrationTests/Tests/VendingMachineRestockTest.cs +++ b/Content.IntegrationTests/Tests/VendingMachineRestockTest.cs @@ -111,6 +111,7 @@ namespace Content.IntegrationTests.Tests await server.WaitIdleAsync(); var prototypeManager = server.ResolveDependency(); + var compFact = server.ResolveDependency(); await server.WaitAssertion(() => { @@ -133,7 +134,7 @@ namespace Content.IntegrationTests.Tests // Collect all the prototypes with StorageFills referencing those entities. foreach (var proto in prototypeManager.EnumeratePrototypes()) { - if (!proto.TryGetComponent(out var storage)) + if (!proto.TryGetComponent(out var storage, compFact)) continue; List restockStore = new(); From c56f495cf184f8a840bd864af1735ef552666ef3 Mon Sep 17 00:00:00 2001 From: Plykiya <58439124+Plykiya@users.noreply.github.com> Date: Tue, 2 Jul 2024 17:01:59 -0700 Subject: [PATCH 51/86] Thieving glove pickups actually stealthy (#29665) Co-authored-by: plykiya --- Content.Server/Strip/StrippableSystem.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Content.Server/Strip/StrippableSystem.cs b/Content.Server/Strip/StrippableSystem.cs index ded9eab3eb..194df7b3d0 100644 --- a/Content.Server/Strip/StrippableSystem.cs +++ b/Content.Server/Strip/StrippableSystem.cs @@ -351,7 +351,7 @@ namespace Content.Server.Strip RaiseLocalEvent(item, new DroppedEvent(user), true); // Gas tank internals etc. - _handsSystem.PickupOrDrop(user, item, animateUser: stealth, animate: stealth); + _handsSystem.PickupOrDrop(user, item, animateUser: stealth, animate: !stealth); _adminLogger.Add(LogType.Stripping, LogImpact.Medium, $"{ToPrettyString(user):actor} has stripped the item {ToPrettyString(item):item} from {ToPrettyString(target):target}'s {slot} slot"); } @@ -450,7 +450,7 @@ namespace Content.Server.Strip return; _handsSystem.TryDrop(user, checkActionBlocker: false, handsComp: user.Comp); - _handsSystem.TryPickup(target, held, handName, checkActionBlocker: false, animateUser: stealth, animate: stealth, handsComp: target.Comp); + _handsSystem.TryPickup(target, held, handName, checkActionBlocker: false, animateUser: stealth, animate: !stealth, handsComp: target.Comp); _adminLogger.Add(LogType.Stripping, LogImpact.Medium, $"{ToPrettyString(user):actor} has placed the item {ToPrettyString(held):item} in {ToPrettyString(target):target}'s hands"); // Hand update will trigger strippable update. @@ -550,7 +550,7 @@ namespace Content.Server.Strip return; _handsSystem.TryDrop(target, item, checkActionBlocker: false, handsComp: target.Comp); - _handsSystem.PickupOrDrop(user, item, animateUser: stealth, animate: stealth, handsComp: user.Comp); + _handsSystem.PickupOrDrop(user, item, animateUser: stealth, animate: !stealth, handsComp: user.Comp); _adminLogger.Add(LogType.Stripping, LogImpact.Medium, $"{ToPrettyString(user):actor} has stripped the item {ToPrettyString(item):item} from {ToPrettyString(target):target}'s hands"); // Hand update will trigger strippable update. From 7b3140fbb32ff16e799a09374c8daabe2ffded83 Mon Sep 17 00:00:00 2001 From: PJBot Date: Wed, 3 Jul 2024 00:03:05 +0000 Subject: [PATCH 52/86] Automatic changelog update --- Resources/Changelog/Changelog.yml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 45ebc2d596..97cd86e218 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Dutch-VanDerLinde - changes: - - message: Senior role ID cards now function properly. - type: Fix - id: 6361 - time: '2024-04-16T20:17:06.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/27017 - author: Dutch-VanDerLinde changes: - message: Most job loadouts now have winter clothing available. @@ -3823,3 +3816,12 @@ id: 6860 time: '2024-07-02T15:28:49.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29667 +- author: Plykiya + changes: + - message: You now see pickup animations when stripping objects off of people. + type: Fix + - message: Thieving gloves no longer have a pickup animation when stripping people. + type: Fix + id: 6861 + time: '2024-07-03T00:01:59.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29665 From 7defc4b87d009e85c5a601a515971313efd69653 Mon Sep 17 00:00:00 2001 From: nikthechampiongr <32041239+nikthechampiongr@users.noreply.github.com> Date: Wed, 3 Jul 2024 00:31:38 +0000 Subject: [PATCH 53/86] Nerf the elite hardsuit (#29429) --- Resources/Prototypes/Catalog/uplink_catalog.yml | 2 +- .../Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Resources/Prototypes/Catalog/uplink_catalog.yml b/Resources/Prototypes/Catalog/uplink_catalog.yml index ace354f43c..9bd10c8ef0 100644 --- a/Resources/Prototypes/Catalog/uplink_catalog.yml +++ b/Resources/Prototypes/Catalog/uplink_catalog.yml @@ -1305,7 +1305,7 @@ icon: { sprite: /Textures/Clothing/OuterClothing/Hardsuits/syndieelite.rsi, state: icon } productEntity: ClothingBackpackDuffelSyndicateEliteHardsuitBundle cost: - Telecrystal: 10 + Telecrystal: 12 categories: - UplinkWearables diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml index 73020898c3..6d061ebc4d 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml @@ -561,7 +561,7 @@ size: Huge - type: ClothingSpeedModifier walkModifier: 1.0 - sprintModifier: 1.0 + sprintModifier: 0.9 - type: HeldSpeedModifier - type: ToggleableClothing clothingPrototype: ClothingHeadHelmetHardsuitSyndieElite From ae4cff49829d77d3cfe0870614b7340f13ec3116 Mon Sep 17 00:00:00 2001 From: PJBot Date: Wed, 3 Jul 2024 00:32:44 +0000 Subject: [PATCH 54/86] Automatic changelog update --- Resources/Changelog/Changelog.yml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 97cd86e218..027070103a 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Dutch-VanDerLinde - changes: - - message: Most job loadouts now have winter clothing available. - type: Tweak - id: 6362 - time: '2024-04-17T02:49:53.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/27022 - author: metalgearsloth changes: - message: 'Fix the following in lobby: ShowClothes button not working, Skin Color @@ -3825,3 +3818,12 @@ id: 6861 time: '2024-07-03T00:01:59.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29665 +- author: nikthechampiongr + changes: + - message: The Elite Syndicate hardsuit is now slightly slower. + type: Tweak + - message: The Elite Syndicate hardsuit now costs 12 Telecrystals. + type: Tweak + id: 6862 + time: '2024-07-03T00:31:39.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29429 From c6d718d126f0772854bd821b8f91d8d912e1db5d Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Tue, 2 Jul 2024 19:51:16 -0700 Subject: [PATCH 55/86] Fix camera recoil system overriding all other eye offsets (#29146) --- Content.Shared/Camera/GetEyeOffsetEvent.cs | 19 +++++++++ .../Camera/SharedCameraRecoilSystem.cs | 42 ++++++++++++++----- .../Systems/SharedContentEyeSystem.cs | 8 ++++ 3 files changed, 59 insertions(+), 10 deletions(-) create mode 100644 Content.Shared/Camera/GetEyeOffsetEvent.cs diff --git a/Content.Shared/Camera/GetEyeOffsetEvent.cs b/Content.Shared/Camera/GetEyeOffsetEvent.cs new file mode 100644 index 0000000000..de9c7c9e17 --- /dev/null +++ b/Content.Shared/Camera/GetEyeOffsetEvent.cs @@ -0,0 +1,19 @@ +using System.Numerics; +using Content.Shared.Movement.Systems; + +namespace Content.Shared.Camera; + +/// +/// Raised directed by-ref when is called. +/// Should be subscribed to by any systems that want to modify an entity's eye offset, +/// so that they do not override each other. +/// +/// +/// The total offset to apply. +/// +/// +/// Note that in most cases should be incremented or decremented by subscribers, not set. +/// Otherwise, any offsets applied by previous subscribing systems will be overridden. +/// +[ByRefEvent] +public record struct GetEyeOffsetEvent(Vector2 Offset); diff --git a/Content.Shared/Camera/SharedCameraRecoilSystem.cs b/Content.Shared/Camera/SharedCameraRecoilSystem.cs index 3507bf1023..d42fe9dcee 100644 --- a/Content.Shared/Camera/SharedCameraRecoilSystem.cs +++ b/Content.Shared/Camera/SharedCameraRecoilSystem.cs @@ -1,6 +1,7 @@ using System.Numerics; +using Content.Shared.Movement.Systems; using JetBrains.Annotations; -using Robust.Shared.Player; +using Robust.Shared.Network; using Robust.Shared.Serialization; namespace Content.Shared.Camera; @@ -28,7 +29,18 @@ public abstract class SharedCameraRecoilSystem : EntitySystem /// protected const float KickMagnitudeMax = 1f; - [Dependency] private readonly SharedEyeSystem _eye = default!; + [Dependency] private readonly SharedContentEyeSystem _eye = default!; + [Dependency] private readonly INetManager _net = default!; + + public override void Initialize() + { + SubscribeLocalEvent(OnCameraRecoilGetEyeOffset); + } + + private void OnCameraRecoilGetEyeOffset(Entity ent, ref GetEyeOffsetEvent args) + { + args.Offset += ent.Comp.BaseOffset + ent.Comp.CurrentKick; + } /// /// Applies explosion/recoil/etc kickback to the view of the entity. @@ -39,10 +51,8 @@ public abstract class SharedCameraRecoilSystem : EntitySystem /// public abstract void KickCamera(EntityUid euid, Vector2 kickback, CameraRecoilComponent? component = null); - public override void FrameUpdate(float frameTime) + private void UpdateEyes(float frameTime) { - base.FrameUpdate(frameTime); - var query = AllEntityQuery(); while (query.MoveNext(out var uid, out var eye, out var recoil)) @@ -51,7 +61,7 @@ public abstract class SharedCameraRecoilSystem : EntitySystem if (magnitude <= 0.005f) { recoil.CurrentKick = Vector2.Zero; - _eye.SetOffset(uid, recoil.BaseOffset + recoil.CurrentKick, eye); + _eye.UpdateEyeOffset((uid, eye)); } else // Continually restore camera to 0. { @@ -60,16 +70,28 @@ public abstract class SharedCameraRecoilSystem : EntitySystem var restoreRate = MathHelper.Lerp(RestoreRateMin, RestoreRateMax, Math.Min(1, recoil.LastKickTime / RestoreRateRamp)); var restore = normalized * restoreRate * frameTime; var (x, y) = recoil.CurrentKick - restore; - if (Math.Sign(x) != Math.Sign(recoil.CurrentKick.X)) x = 0; + if (Math.Sign(x) != Math.Sign(recoil.CurrentKick.X)) + x = 0; - if (Math.Sign(y) != Math.Sign(recoil.CurrentKick.Y)) y = 0; + if (Math.Sign(y) != Math.Sign(recoil.CurrentKick.Y)) + y = 0; recoil.CurrentKick = new Vector2(x, y); - - _eye.SetOffset(uid, recoil.BaseOffset + recoil.CurrentKick, eye); + _eye.UpdateEyeOffset((uid, eye)); } } } + + public override void Update(float frameTime) + { + if (_net.IsServer) + UpdateEyes(frameTime); + } + + public override void FrameUpdate(float frameTime) + { + UpdateEyes(frameTime); + } } [Serializable] diff --git a/Content.Shared/Movement/Systems/SharedContentEyeSystem.cs b/Content.Shared/Movement/Systems/SharedContentEyeSystem.cs index 207f14a258..0c4304d374 100644 --- a/Content.Shared/Movement/Systems/SharedContentEyeSystem.cs +++ b/Content.Shared/Movement/Systems/SharedContentEyeSystem.cs @@ -1,6 +1,7 @@ using System.Numerics; using Content.Shared.Administration; using Content.Shared.Administration.Managers; +using Content.Shared.Camera; using Content.Shared.Ghost; using Content.Shared.Input; using Content.Shared.Movement.Components; @@ -128,6 +129,13 @@ public abstract class SharedContentEyeSystem : EntitySystem Dirty(uid, component); } + public void UpdateEyeOffset(Entity eye) + { + var ev = new GetEyeOffsetEvent(); + RaiseLocalEvent(eye, ref ev); + _eye.SetOffset(eye, ev.Offset, eye); + } + /// /// Sendable from client to server to request a target zoom. /// From 347bed8be55d1536860d04f2e6d6cf297b4e6710 Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Tue, 2 Jul 2024 22:16:18 -0700 Subject: [PATCH 56/86] Make camera recoil system only refresh offset when its values change (#29673) --- Content.Shared/Camera/CameraRecoilComponent.cs | 1 + Content.Shared/Camera/SharedCameraRecoilSystem.cs | 13 +++++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Content.Shared/Camera/CameraRecoilComponent.cs b/Content.Shared/Camera/CameraRecoilComponent.cs index 5d615f87be..8b8b290845 100644 --- a/Content.Shared/Camera/CameraRecoilComponent.cs +++ b/Content.Shared/Camera/CameraRecoilComponent.cs @@ -8,6 +8,7 @@ namespace Content.Shared.Camera; public sealed partial class CameraRecoilComponent : Component { public Vector2 CurrentKick { get; set; } + public Vector2 LastKick { get; set; } public float LastKickTime { get; set; } /// diff --git a/Content.Shared/Camera/SharedCameraRecoilSystem.cs b/Content.Shared/Camera/SharedCameraRecoilSystem.cs index d42fe9dcee..5ba97dabe2 100644 --- a/Content.Shared/Camera/SharedCameraRecoilSystem.cs +++ b/Content.Shared/Camera/SharedCameraRecoilSystem.cs @@ -1,5 +1,4 @@ using System.Numerics; -using Content.Shared.Movement.Systems; using JetBrains.Annotations; using Robust.Shared.Network; using Robust.Shared.Serialization; @@ -29,7 +28,7 @@ public abstract class SharedCameraRecoilSystem : EntitySystem /// protected const float KickMagnitudeMax = 1f; - [Dependency] private readonly SharedContentEyeSystem _eye = default!; + [Dependency] private readonly SharedEyeSystem _eye = default!; [Dependency] private readonly INetManager _net = default!; public override void Initialize() @@ -61,7 +60,6 @@ public abstract class SharedCameraRecoilSystem : EntitySystem if (magnitude <= 0.005f) { recoil.CurrentKick = Vector2.Zero; - _eye.UpdateEyeOffset((uid, eye)); } else // Continually restore camera to 0. { @@ -77,8 +75,15 @@ public abstract class SharedCameraRecoilSystem : EntitySystem y = 0; recoil.CurrentKick = new Vector2(x, y); - _eye.UpdateEyeOffset((uid, eye)); } + + if (recoil.CurrentKick == recoil.LastKick) + continue; + + recoil.LastKick = recoil.CurrentKick; + var ev = new GetEyeOffsetEvent(); + RaiseLocalEvent(uid, ref ev); + _eye.SetOffset(uid, ev.Offset, eye); } } From a05d05088ee1300187b6771a233a5fa339f95cc4 Mon Sep 17 00:00:00 2001 From: eoineoineoin Date: Wed, 3 Jul 2024 06:27:33 +0100 Subject: [PATCH 57/86] Make artifact analyzer obey laws of physics (#28117) --- Resources/Maps/Test/dev_map.yml | 1 - Resources/Maps/cluster.yml | 1 - Resources/Maps/meta.yml | 1 - Resources/Maps/origin.yml | 1 - Resources/Maps/train.yml | 1 - .../Entities/Structures/Machines/artifact_analyzer.yml | 9 ++++++++- 6 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Resources/Maps/Test/dev_map.yml b/Resources/Maps/Test/dev_map.yml index 48b2eddf62..ca885d584b 100644 --- a/Resources/Maps/Test/dev_map.yml +++ b/Resources/Maps/Test/dev_map.yml @@ -4221,7 +4221,6 @@ entities: - uid: 1078 components: - type: Transform - rot: 1.5707963267948966 rad pos: 12.5,24.5 parent: 179 - type: DeviceLinkSink diff --git a/Resources/Maps/cluster.yml b/Resources/Maps/cluster.yml index 52393f3fb2..01a2cdd798 100644 --- a/Resources/Maps/cluster.yml +++ b/Resources/Maps/cluster.yml @@ -56131,7 +56131,6 @@ entities: - uid: 6124 components: - type: Transform - rot: 1.5707963267948966 rad pos: -20.5,-13.5 parent: 1 - type: DeviceLinkSink diff --git a/Resources/Maps/meta.yml b/Resources/Maps/meta.yml index a8143f716e..8dd8e3a8a4 100644 --- a/Resources/Maps/meta.yml +++ b/Resources/Maps/meta.yml @@ -117638,7 +117638,6 @@ entities: - uid: 19357 components: - type: Transform - rot: 3.141592653589793 rad pos: 24.5,-30.5 parent: 5350 - uid: 19358 diff --git a/Resources/Maps/origin.yml b/Resources/Maps/origin.yml index a11e94fe83..7dd4d4386f 100644 --- a/Resources/Maps/origin.yml +++ b/Resources/Maps/origin.yml @@ -142225,7 +142225,6 @@ entities: - uid: 21604 components: - type: Transform - rot: -1.5707963267948966 rad pos: 72.5,-28.5 parent: 2 - type: DeviceLinkSink diff --git a/Resources/Maps/train.yml b/Resources/Maps/train.yml index ae2783e643..6b929468a2 100644 --- a/Resources/Maps/train.yml +++ b/Resources/Maps/train.yml @@ -75820,7 +75820,6 @@ entities: - uid: 10029 components: - type: Transform - rot: 1.5707963267948966 rad pos: -8.5,-314.5 parent: 2 - proto: MachineCentrifuge diff --git a/Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml b/Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml index 9c878c7e7c..741e0a3971 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml @@ -25,6 +25,14 @@ - type: Fixtures fixtures: fix1: + shape: + !type:PhysShapeAabb + bounds: "-0.35,-0.35,0.35,0.35" + mask: + - Impassable + - HighImpassable + - MidImpassable + fix2: shape: !type:PhysShapeAabb bounds: "-0.35,-0.35,0.35,0.35" @@ -39,7 +47,6 @@ hard: False - type: Transform anchored: true - noRot: false - type: ApcPowerReceiver powerLoad: 12000 needsPower: false #only turns on when scanning From a2a3233f5ea8c84aa9d87917c7dba20622960dc9 Mon Sep 17 00:00:00 2001 From: PJBot Date: Wed, 3 Jul 2024 05:28:41 +0000 Subject: [PATCH 58/86] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 027070103a..e1a72faa15 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +1,4 @@ Entries: -- author: metalgearsloth - changes: - - message: 'Fix the following in lobby: ShowClothes button not working, Skin Color - not updating, Skin Color slider not updating upon closing and re-opening character.' - type: Fix - id: 6363 - time: '2024-04-17T02:54:54.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/27033 - author: MACMAN2003 changes: - message: Nuclear operatives now only need 20 players to be readied up again instead @@ -3827,3 +3819,10 @@ id: 6862 time: '2024-07-03T00:31:39.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29429 +- author: eoineoineoin + changes: + - message: Artifact Analyzer now collides with walls + type: Fix + id: 6863 + time: '2024-07-03T05:27:33.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28117 From 1faa1b5df6e74b9cb1309d0400b12ee0b96039e4 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Wed, 3 Jul 2024 18:44:55 +1200 Subject: [PATCH 59/86] Allow `zoom` command to modify an eye's PVS range (#29245) Allow zoom command to modify an eye's PVS range Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> --- Content.Client/Commands/ZoomCommand.cs | 11 ++++++++-- .../Movement/Systems/ContentEyeSystem.cs | 10 ++++++++- .../Camera/CameraRecoilComponent.cs | 7 ++++++ .../Camera/SharedCameraRecoilSystem.cs | 4 ++-- .../Systems/SharedContentEyeSystem.cs | 22 ++++++++++++++++++- .../Locale/en-US/commands/zoom-command.ftl | 4 ++-- 6 files changed, 50 insertions(+), 8 deletions(-) diff --git a/Content.Client/Commands/ZoomCommand.cs b/Content.Client/Commands/ZoomCommand.cs index 2bdc85e1fe..c63eeea836 100644 --- a/Content.Client/Commands/ZoomCommand.cs +++ b/Content.Client/Commands/ZoomCommand.cs @@ -20,7 +20,7 @@ public sealed class ZoomCommand : LocalizedCommands public override void Execute(IConsoleShell shell, string argStr, string[] args) { Vector2 zoom; - if (args.Length is not (1 or 2)) + if (args.Length is not (1 or 2 or 3)) { shell.WriteLine(Help); return; @@ -57,11 +57,18 @@ public sealed class ZoomCommand : LocalizedCommands } } + var scalePvs = true; + if (args.Length == 3 && !bool.TryParse(args[2], out scalePvs)) + { + shell.WriteError(LocalizationManager.GetString("cmd-parse-failure-bool", ("arg", args[2]))); + return; + } + var player = _playerManager.LocalSession?.AttachedEntity; if (_entityManager.TryGetComponent(player, out var content)) { - _entityManager.System().RequestZoom(player.Value, zoom, true, content); + _entityManager.System().RequestZoom(player.Value, zoom, true, scalePvs, content); return; } diff --git a/Content.Client/Movement/Systems/ContentEyeSystem.cs b/Content.Client/Movement/Systems/ContentEyeSystem.cs index 182ac92ae0..9fbd4b5c37 100644 --- a/Content.Client/Movement/Systems/ContentEyeSystem.cs +++ b/Content.Client/Movement/Systems/ContentEyeSystem.cs @@ -9,7 +9,7 @@ public sealed class ContentEyeSystem : SharedContentEyeSystem { [Dependency] private readonly IPlayerManager _player = default!; - public void RequestZoom(EntityUid uid, Vector2 zoom, bool ignoreLimit, ContentEyeComponent? content = null) + public void RequestZoom(EntityUid uid, Vector2 zoom, bool ignoreLimit, bool scalePvs, ContentEyeComponent? content = null) { if (!Resolve(uid, ref content, false)) return; @@ -19,6 +19,14 @@ public sealed class ContentEyeSystem : SharedContentEyeSystem TargetZoom = zoom, IgnoreLimit = ignoreLimit, }); + + if (scalePvs) + RequestPvsScale(Math.Max(zoom.X, zoom.Y)); + } + + public void RequestPvsScale(float scale) + { + RaiseNetworkEvent(new RequestPvsScaleEvent(scale)); } public void RequestToggleFov() diff --git a/Content.Shared/Camera/CameraRecoilComponent.cs b/Content.Shared/Camera/CameraRecoilComponent.cs index 8b8b290845..2cbb632408 100644 --- a/Content.Shared/Camera/CameraRecoilComponent.cs +++ b/Content.Shared/Camera/CameraRecoilComponent.cs @@ -7,12 +7,19 @@ namespace Content.Shared.Camera; [NetworkedComponent] public sealed partial class CameraRecoilComponent : Component { + [ViewVariables(VVAccess.ReadWrite)] public Vector2 CurrentKick { get; set; } + + [ViewVariables(VVAccess.ReadWrite)] public Vector2 LastKick { get; set; } + + [ViewVariables(VVAccess.ReadWrite)] public float LastKickTime { get; set; } /// /// Basically I needed a way to chain this effect for the attack lunge animation. Sorry! /// + /// + [ViewVariables(VVAccess.ReadWrite)] public Vector2 BaseOffset { get; set; } } diff --git a/Content.Shared/Camera/SharedCameraRecoilSystem.cs b/Content.Shared/Camera/SharedCameraRecoilSystem.cs index 5ba97dabe2..a2ce0e77e2 100644 --- a/Content.Shared/Camera/SharedCameraRecoilSystem.cs +++ b/Content.Shared/Camera/SharedCameraRecoilSystem.cs @@ -52,9 +52,9 @@ public abstract class SharedCameraRecoilSystem : EntitySystem private void UpdateEyes(float frameTime) { - var query = AllEntityQuery(); + var query = AllEntityQuery(); - while (query.MoveNext(out var uid, out var eye, out var recoil)) + while (query.MoveNext(out var uid, out var recoil, out var eye)) { var magnitude = recoil.CurrentKick.Length(); if (magnitude <= 0.005f) diff --git a/Content.Shared/Movement/Systems/SharedContentEyeSystem.cs b/Content.Shared/Movement/Systems/SharedContentEyeSystem.cs index 0c4304d374..faade44c85 100644 --- a/Content.Shared/Movement/Systems/SharedContentEyeSystem.cs +++ b/Content.Shared/Movement/Systems/SharedContentEyeSystem.cs @@ -18,6 +18,9 @@ public abstract class SharedContentEyeSystem : EntitySystem { [Dependency] private readonly ISharedAdminManager _admin = default!; + // Admin flags required to ignore normal eye restrictions. + public const AdminFlags EyeFlag = AdminFlags.Debug; + public const float ZoomMod = 1.5f; public static readonly Vector2 DefaultZoom = Vector2.One; public static readonly Vector2 MinZoom = DefaultZoom * (float)Math.Pow(ZoomMod, -3); @@ -29,6 +32,7 @@ public abstract class SharedContentEyeSystem : EntitySystem base.Initialize(); SubscribeLocalEvent(OnContentEyeStartup); SubscribeAllEvent(OnContentZoomRequest); + SubscribeAllEvent(OnPvsScale); SubscribeAllEvent(OnRequestEye); CommandBinds.Builder @@ -84,12 +88,18 @@ public abstract class SharedContentEyeSystem : EntitySystem private void OnContentZoomRequest(RequestTargetZoomEvent msg, EntitySessionEventArgs args) { - var ignoreLimit = msg.IgnoreLimit && _admin.HasAdminFlag(args.SenderSession, AdminFlags.Debug); + var ignoreLimit = msg.IgnoreLimit && _admin.HasAdminFlag(args.SenderSession, EyeFlag); if (TryComp(args.SenderSession.AttachedEntity, out var content)) SetZoom(args.SenderSession.AttachedEntity.Value, msg.TargetZoom, ignoreLimit, eye: content); } + private void OnPvsScale(RequestPvsScaleEvent ev, EntitySessionEventArgs args) + { + if (args.SenderSession.AttachedEntity is {} uid && _admin.HasAdminFlag(args.SenderSession, EyeFlag)) + _eye.SetPvsScale(uid, ev.Scale); + } + private void OnRequestEye(RequestEyeEvent msg, EntitySessionEventArgs args) { if (args.SenderSession.AttachedEntity is not { } player) @@ -116,6 +126,7 @@ public abstract class SharedContentEyeSystem : EntitySystem public void ResetZoom(EntityUid uid, ContentEyeComponent? component = null) { + _eye.SetPvsScale(uid, 1); SetZoom(uid, DefaultZoom, eye: component); } @@ -146,6 +157,15 @@ public abstract class SharedContentEyeSystem : EntitySystem public bool IgnoreLimit; } + /// + /// Client->Server request for new PVS scale. + /// + [Serializable, NetSerializable] + public sealed class RequestPvsScaleEvent(float scale) : EntityEventArgs + { + public float Scale = scale; + } + /// /// Sendable from client to server to request changing fov. /// diff --git a/Resources/Locale/en-US/commands/zoom-command.ftl b/Resources/Locale/en-US/commands/zoom-command.ftl index 0132f22240..64bf6bca7a 100644 --- a/Resources/Locale/en-US/commands/zoom-command.ftl +++ b/Resources/Locale/en-US/commands/zoom-command.ftl @@ -1,3 +1,3 @@ -cmd-zoom-desc = Sets the zoom of the main eye. -cmd-zoom-help = zoom ( | ) +cmd-zoom-desc = Sets the zoom of the main eye. Optionally also changes the eye's PVS range. +cmd-zoom-help = zoom ( | [bool]) cmd-zoom-error = scale has to be greater than 0 From a2f99cc69e4c86595c5cff8f4db33885e57bef27 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Wed, 3 Jul 2024 22:23:11 +1000 Subject: [PATCH 60/86] VGRoid support (#27659) * Dungeon spawn support for grid spawns * Recursive dungeons working * Mask approach working * zack * More work * Fix recursive dungeons * Heap of work * weh * the cud * rar * Job * weh * weh * weh * Master merges * orch * weh * vgroid most of the work * Tweaks * Tweaks * weh * do do do do do do * Basic layout * Ore spawning working * Big breaking changes * Mob gen working * weh * Finalising * emo * More finalising * reverty * Reduce distance --- .../Pathfinding/PathfindingSystem.Breadth.cs | 123 ++ .../NPC/Pathfinding/PathfindingSystem.Line.cs | 74 + .../Pathfinding/PathfindingSystem.Simple.cs | 154 ++ .../Pathfinding/PathfindingSystem.Splines.cs | 180 +++ .../Pathfinding/PathfindingSystem.Widen.cs | 89 ++ .../NPC/Systems/NPCSteeringSystem.Context.cs | 15 + .../Procedural/DungeonJob.PostGen.cs | 1258 ----------------- .../Procedural/DungeonJob.PostGenBiome.cs | 138 -- Content.Server/Procedural/DungeonJob.cs | 192 --- .../DungeonJob/DungeonJob.DunGenExterior.cs | 58 + .../DungeonJob/DungeonJob.DunGenFill.cs | 50 + .../DungeonJob.DunGenNoise.cs} | 55 +- .../DungeonJob.DunGenNoiseDistance.cs | 112 ++ .../DungeonJob.DunGenPrefab.cs} | 87 +- .../DungeonJob.DunGenReplaceTile.cs | 60 + .../DungeonJob/DungeonJob.MobDunGen.cs | 58 + .../DungeonJob/DungeonJob.OreDunGen.cs | 149 ++ .../DungeonJob/DungeonJob.PostGen.cs | 134 ++ .../DungeonJob.PostGenAutoCabling.cs | 162 +++ .../DungeonJob/DungeonJob.PostGenBiome.cs | 67 + .../DungeonJob.PostGenBiomeMarkerLayer.cs | 105 ++ .../DungeonJob.PostGenBoundaryWall.cs | 113 ++ .../DungeonJob.PostGenCornerClutter.cs | 56 + .../DungeonJob/DungeonJob.PostGenCorridor.cs | 116 ++ .../DungeonJob.PostGenCorridorClutter.cs} | 9 +- ...DungeonJob.PostGenCorridorDecalSkirting.cs | 124 ++ .../DungeonJob.PostGenDungeonConnector.cs | 6 + .../DungeonJob.PostGenDungeonEntrance.cs | 114 ++ .../DungeonJob.PostGenEntranceFlank.cs | 58 + .../DungeonJob.PostGenExternalWindow.cs | 138 ++ .../DungeonJob.PostGenInternalWindow.cs | 108 ++ .../DungeonJob/DungeonJob.PostGenJunction.cs | 144 ++ .../DungeonJob.PostGenMiddleConnection.cs | 147 ++ .../DungeonJob.PostGenRoomEntrance.cs | 48 + ...ungeonJob.PostGenSplineDungeonConnector.cs | 147 ++ .../DungeonJob/DungeonJob.PostGenWallMount.cs | 56 + .../DungeonJob.PostGenWorm.cs} | 22 +- .../Procedural/DungeonJob/DungeonJob.cs | 309 ++++ .../Procedural/DungeonSystem.Commands.cs | 2 + .../Procedural/DungeonSystem.Rooms.cs | 19 +- Content.Server/Procedural/DungeonSystem.cs | 13 +- Content.Server/Procedural/RoomFillSystem.cs | 1 + .../Salvage/SpawnSalvageMissionJob.cs | 4 +- .../Shuttles/Components/GridSpawnComponent.cs | 81 +- .../Systems/ShuttleSystem.GridFill.cs | 167 ++- .../Shuttles/Systems/ShuttleSystem.cs | 8 +- .../Components/EntityRemapComponent.cs | 13 + .../DunGenEuclideanSquaredDistance.cs | 10 + .../Procedural/Distance/DunGenSquareBump.cs | 10 + .../Procedural/Distance/IDunGenDistance.cs | 14 + Content.Shared/Procedural/Dungeon.cs | 66 +- .../Procedural/DungeonConfigPrototype.cs | 46 +- Content.Shared/Procedural/DungeonData.cs | 105 ++ .../DungeonGenerators/ExteriorDunGen.cs | 13 + .../DungeonGenerators/FillGridDunGen.cs | 10 + .../Procedural/DungeonGenerators/IDunGen.cs | 7 - .../DungeonGenerators/NoiseDistanceDunGen.cs | 18 + .../DungeonGenerators/NoiseDunGen.cs | 7 +- .../DungeonGenerators/PrefabDunGen.cs | 28 +- .../DungeonGenerators/PrototypeDunGen.cs | 13 + .../DungeonGenerators/ReplaceTileDunGen.cs | 30 + .../Procedural/DungeonLayers/MobsDunGen.cs | 21 + .../Procedural/DungeonLayers/OreDunGen.cs | 42 + Content.Shared/Procedural/DungeonRoom.cs | 1 + Content.Shared/Procedural/IDunGenLayer.cs | 7 + .../PostGeneration/AutoCablingDunGen.cs | 10 + .../PostGeneration/AutoCablingPostGen.cs | 12 - .../{BiomePostGen.cs => BiomeDunGen.cs} | 3 +- ...erPostGen.cs => BiomeMarkerLayerDunGen.cs} | 4 +- .../PostGeneration/BoundaryWallDunGen.cs | 23 + .../PostGeneration/BoundaryWallPostGen.cs | 33 - .../PostGeneration/CornerClutterDunGen.cs | 14 + .../PostGeneration/CornerClutterPostGen.cs | 18 - ...terPostGen.cs => CorridorClutterDunGen.cs} | 2 +- ...tGen.cs => CorridorDecalSkirtingDunGen.cs} | 14 +- .../{CorridorPostGen.cs => CorridorDunGen.cs} | 12 +- .../PostGeneration/DungeonEntranceDunGen.cs | 18 + .../PostGeneration/DungeonEntrancePostGen.cs | 28 - .../PostGeneration/EntranceFlankDunGen.cs | 11 + .../PostGeneration/EntranceFlankPostGen.cs | 16 - .../PostGeneration/ExternalWindowDunGen.cs | 11 + .../PostGeneration/ExternalWindowPostGen.cs | 22 - .../Procedural/PostGeneration/IPostDunGen.cs | 10 - .../PostGeneration/InternalWindowDunGen.cs | 11 + .../PostGeneration/InternalWindowPostGen.cs | 22 - .../PostGeneration/JunctionDunGen.cs | 18 + .../PostGeneration/JunctionPostGen.cs | 28 - .../PostGeneration/MiddleConnectionDunGen.cs | 19 + .../PostGeneration/MiddleConnectionPostGen.cs | 39 - .../PostGeneration/RoomEntranceDunGen.cs | 11 + .../PostGeneration/RoomEntrancePostGen.cs | 22 - .../SplineDungeonConnectorDunGen.cs | 19 + .../PostGeneration/WallMountDunGen.cs | 13 + .../PostGeneration/WallMountPostGen.cs | 23 - ...rridorPostGen.cs => WormCorridorDunGen.cs} | 9 +- .../Salvage/SharedSalvageSystem.Magnet.cs | 6 +- .../Shuttles/Systems/SharedShuttleSystem.cs | 2 +- Content.Shared/Storage/EntitySpawnEntry.cs | 13 + .../Prototypes/Entities/Stations/base.yml | 15 +- .../Entities/Structures/Walls/asteroid.yml | 342 +++-- .../Prototypes/Procedural/Magnet/asteroid.yml | 71 +- .../Prototypes/Procedural/dungeon_configs.yml | 629 ++++----- Resources/Prototypes/Procedural/vgroid.yml | 191 +++ 103 files changed, 4928 insertions(+), 2627 deletions(-) create mode 100644 Content.Server/NPC/Pathfinding/PathfindingSystem.Breadth.cs create mode 100644 Content.Server/NPC/Pathfinding/PathfindingSystem.Line.cs create mode 100644 Content.Server/NPC/Pathfinding/PathfindingSystem.Simple.cs create mode 100644 Content.Server/NPC/Pathfinding/PathfindingSystem.Splines.cs create mode 100644 Content.Server/NPC/Pathfinding/PathfindingSystem.Widen.cs delete mode 100644 Content.Server/Procedural/DungeonJob.PostGen.cs delete mode 100644 Content.Server/Procedural/DungeonJob.PostGenBiome.cs delete mode 100644 Content.Server/Procedural/DungeonJob.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.DunGenExterior.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.DunGenFill.cs rename Content.Server/Procedural/{DungeonJob.NoiseDunGen.cs => DungeonJob/DungeonJob.DunGenNoise.cs} (73%) create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.DunGenNoiseDistance.cs rename Content.Server/Procedural/{DungeonJob.PrefabDunGen.cs => DungeonJob/DungeonJob.DunGenPrefab.cs} (82%) create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.DunGenReplaceTile.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.MobDunGen.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.OreDunGen.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.PostGen.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.PostGenAutoCabling.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.PostGenBiome.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.PostGenBiomeMarkerLayer.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.PostGenBoundaryWall.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.PostGenCornerClutter.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.PostGenCorridor.cs rename Content.Server/Procedural/{DungeonJob.CorridorClutterPost.cs => DungeonJob/DungeonJob.PostGenCorridorClutter.cs} (83%) create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.PostGenCorridorDecalSkirting.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.PostGenDungeonConnector.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.PostGenDungeonEntrance.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.PostGenEntranceFlank.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.PostGenExternalWindow.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.PostGenInternalWindow.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.PostGenJunction.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.PostGenMiddleConnection.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.PostGenRoomEntrance.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.PostGenSplineDungeonConnector.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.PostGenWallMount.cs rename Content.Server/Procedural/{DungeonJob.WormPost.cs => DungeonJob/DungeonJob.PostGenWorm.cs} (88%) create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.cs create mode 100644 Content.Shared/Procedural/Components/EntityRemapComponent.cs create mode 100644 Content.Shared/Procedural/Distance/DunGenEuclideanSquaredDistance.cs create mode 100644 Content.Shared/Procedural/Distance/DunGenSquareBump.cs create mode 100644 Content.Shared/Procedural/Distance/IDunGenDistance.cs create mode 100644 Content.Shared/Procedural/DungeonData.cs create mode 100644 Content.Shared/Procedural/DungeonGenerators/ExteriorDunGen.cs create mode 100644 Content.Shared/Procedural/DungeonGenerators/FillGridDunGen.cs delete mode 100644 Content.Shared/Procedural/DungeonGenerators/IDunGen.cs create mode 100644 Content.Shared/Procedural/DungeonGenerators/NoiseDistanceDunGen.cs create mode 100644 Content.Shared/Procedural/DungeonGenerators/PrototypeDunGen.cs create mode 100644 Content.Shared/Procedural/DungeonGenerators/ReplaceTileDunGen.cs create mode 100644 Content.Shared/Procedural/DungeonLayers/MobsDunGen.cs create mode 100644 Content.Shared/Procedural/DungeonLayers/OreDunGen.cs create mode 100644 Content.Shared/Procedural/IDunGenLayer.cs create mode 100644 Content.Shared/Procedural/PostGeneration/AutoCablingDunGen.cs delete mode 100644 Content.Shared/Procedural/PostGeneration/AutoCablingPostGen.cs rename Content.Shared/Procedural/PostGeneration/{BiomePostGen.cs => BiomeDunGen.cs} (78%) rename Content.Shared/Procedural/PostGeneration/{BiomeMarkerLayerPostGen.cs => BiomeMarkerLayerDunGen.cs} (73%) create mode 100644 Content.Shared/Procedural/PostGeneration/BoundaryWallDunGen.cs delete mode 100644 Content.Shared/Procedural/PostGeneration/BoundaryWallPostGen.cs create mode 100644 Content.Shared/Procedural/PostGeneration/CornerClutterDunGen.cs delete mode 100644 Content.Shared/Procedural/PostGeneration/CornerClutterPostGen.cs rename Content.Shared/Procedural/PostGeneration/{CorridorClutterPostGen.cs => CorridorClutterDunGen.cs} (85%) rename Content.Shared/Procedural/PostGeneration/{CorridorDecalSkirtingPostGen.cs => CorridorDecalSkirtingDunGen.cs} (72%) rename Content.Shared/Procedural/PostGeneration/{CorridorPostGen.cs => CorridorDunGen.cs} (73%) create mode 100644 Content.Shared/Procedural/PostGeneration/DungeonEntranceDunGen.cs delete mode 100644 Content.Shared/Procedural/PostGeneration/DungeonEntrancePostGen.cs create mode 100644 Content.Shared/Procedural/PostGeneration/EntranceFlankDunGen.cs delete mode 100644 Content.Shared/Procedural/PostGeneration/EntranceFlankPostGen.cs create mode 100644 Content.Shared/Procedural/PostGeneration/ExternalWindowDunGen.cs delete mode 100644 Content.Shared/Procedural/PostGeneration/ExternalWindowPostGen.cs delete mode 100644 Content.Shared/Procedural/PostGeneration/IPostDunGen.cs create mode 100644 Content.Shared/Procedural/PostGeneration/InternalWindowDunGen.cs delete mode 100644 Content.Shared/Procedural/PostGeneration/InternalWindowPostGen.cs create mode 100644 Content.Shared/Procedural/PostGeneration/JunctionDunGen.cs delete mode 100644 Content.Shared/Procedural/PostGeneration/JunctionPostGen.cs create mode 100644 Content.Shared/Procedural/PostGeneration/MiddleConnectionDunGen.cs delete mode 100644 Content.Shared/Procedural/PostGeneration/MiddleConnectionPostGen.cs create mode 100644 Content.Shared/Procedural/PostGeneration/RoomEntranceDunGen.cs delete mode 100644 Content.Shared/Procedural/PostGeneration/RoomEntrancePostGen.cs create mode 100644 Content.Shared/Procedural/PostGeneration/SplineDungeonConnectorDunGen.cs create mode 100644 Content.Shared/Procedural/PostGeneration/WallMountDunGen.cs delete mode 100644 Content.Shared/Procedural/PostGeneration/WallMountPostGen.cs rename Content.Shared/Procedural/PostGeneration/{WormCorridorPostGen.cs => WormCorridorDunGen.cs} (73%) create mode 100644 Resources/Prototypes/Procedural/vgroid.yml diff --git a/Content.Server/NPC/Pathfinding/PathfindingSystem.Breadth.cs b/Content.Server/NPC/Pathfinding/PathfindingSystem.Breadth.cs new file mode 100644 index 0000000000..ee8eaa9ad1 --- /dev/null +++ b/Content.Server/NPC/Pathfinding/PathfindingSystem.Breadth.cs @@ -0,0 +1,123 @@ +namespace Content.Server.NPC.Pathfinding; + +public sealed partial class PathfindingSystem +{ + /* + * Handle BFS searches from Start->End. Doesn't consider NPC pathfinding. + */ + + /// + /// Pathfinding args for a 1-many path. + /// + public record struct BreadthPathArgs() + { + public Vector2i Start; + public List Ends; + + public bool Diagonals = false; + + public Func? TileCost; + + public int Limit = 10000; + } + + /// + /// Gets a BFS path from start to any end. Can also supply an optional tile-cost for tiles. + /// + public SimplePathResult GetBreadthPath(BreadthPathArgs args) + { + var cameFrom = new Dictionary(); + var costSoFar = new Dictionary(); + var frontier = new PriorityQueue(); + + costSoFar[args.Start] = 0f; + frontier.Enqueue(args.Start, 0f); + var count = 0; + + while (frontier.TryDequeue(out var node, out _) && count < args.Limit) + { + count++; + + if (args.Ends.Contains(node)) + { + // Found target + var path = ReconstructPath(node, cameFrom); + + return new SimplePathResult() + { + CameFrom = cameFrom, + Path = path, + }; + } + + var gCost = costSoFar[node]; + + if (args.Diagonals) + { + for (var x = -1; x <= 1; x++) + { + for (var y = -1; y <= 1; y++) + { + var neighbor = node + new Vector2i(x, y); + var neighborCost = OctileDistance(node, neighbor) * args.TileCost?.Invoke(neighbor) ?? 1f; + + if (neighborCost.Equals(0f)) + { + continue; + } + + // f = g + h + // gScore is distance to the start node + // hScore is distance to the end node + var gScore = gCost + neighborCost; + + // Slower to get here so just ignore it. + if (costSoFar.TryGetValue(neighbor, out var nextValue) && gScore >= nextValue) + { + continue; + } + + cameFrom[neighbor] = node; + costSoFar[neighbor] = gScore; + // pFactor is tie-breaker where the fscore is otherwise equal. + // See http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html#breaking-ties + // There's other ways to do it but future consideration + // The closer the fScore is to the actual distance then the better the pathfinder will be + // (i.e. somewhere between 1 and infinite) + // Can use hierarchical pathfinder or whatever to improve the heuristic but this is fine for now. + frontier.Enqueue(neighbor, gScore); + } + } + } + else + { + for (var x = -1; x <= 1; x++) + { + for (var y = -1; y <= 1; y++) + { + if (x != 0 && y != 0) + continue; + + var neighbor = node + new Vector2i(x, y); + var neighborCost = ManhattanDistance(node, neighbor) * args.TileCost?.Invoke(neighbor) ?? 1f; + + if (neighborCost.Equals(0f)) + continue; + + var gScore = gCost + neighborCost; + + if (costSoFar.TryGetValue(neighbor, out var nextValue) && gScore >= nextValue) + continue; + + cameFrom[neighbor] = node; + costSoFar[neighbor] = gScore; + + frontier.Enqueue(neighbor, gScore); + } + } + } + } + + return SimplePathResult.NoPath; + } +} diff --git a/Content.Server/NPC/Pathfinding/PathfindingSystem.Line.cs b/Content.Server/NPC/Pathfinding/PathfindingSystem.Line.cs new file mode 100644 index 0000000000..479d5ad77f --- /dev/null +++ b/Content.Server/NPC/Pathfinding/PathfindingSystem.Line.cs @@ -0,0 +1,74 @@ +namespace Content.Server.NPC.Pathfinding; + +public sealed partial class PathfindingSystem +{ + public void GridCast(Vector2i start, Vector2i end, Vector2iCallback callback) + { + // https://gist.github.com/Pyr3z/46884d67641094d6cf353358566db566 + // declare all locals at the top so it's obvious how big the footprint is + int dx, dy, xinc, yinc, side, i, error; + + // starting cell is always returned + if (!callback(start)) + return; + + xinc = (end.X < start.X) ? -1 : 1; + yinc = (end.Y < start.Y) ? -1 : 1; + dx = xinc * (end.X - start.X); + dy = yinc * (end.Y - start.Y); + var ax = start.X; + var ay = start.Y; + + if (dx == dy) // Handle perfect diagonals + { + // I include this "optimization" for more aesthetic reasons, actually. + // While Bresenham's Line can handle perfect diagonals just fine, it adds + // additional cells to the line that make it not a perfect diagonal + // anymore. So, while this branch is ~twice as fast as the next branch, + // the real reason it is here is for style. + + // Also, there *is* the reason of performance. If used for cell-based + // raycasts, for example, then perfect diagonals will check half as many + // cells. + + while (dx --> 0) + { + ax += xinc; + ay += yinc; + if (!callback(new Vector2i(ax, ay))) + return; + } + + return; + } + + // Handle all other lines + + side = -1 * ((dx == 0 ? yinc : xinc) - 1); + + i = dx + dy; + error = dx - dy; + + dx *= 2; + dy *= 2; + + while (i --> 0) + { + if (error > 0 || error == side) + { + ax += xinc; + error -= dy; + } + else + { + ay += yinc; + error += dx; + } + + if (!callback(new Vector2i(ax, ay))) + return; + } + } + + public delegate bool Vector2iCallback(Vector2i index); +} diff --git a/Content.Server/NPC/Pathfinding/PathfindingSystem.Simple.cs b/Content.Server/NPC/Pathfinding/PathfindingSystem.Simple.cs new file mode 100644 index 0000000000..7afd3d78df --- /dev/null +++ b/Content.Server/NPC/Pathfinding/PathfindingSystem.Simple.cs @@ -0,0 +1,154 @@ +namespace Content.Server.NPC.Pathfinding; + +public sealed partial class PathfindingSystem +{ + /// + /// Pathfinding args for a 1-1 path. + /// + public record struct SimplePathArgs() + { + public Vector2i Start; + public Vector2i End; + + public bool Diagonals = false; + + public int Limit = 10000; + + /// + /// Custom tile-costs if applicable. + /// + public Func? TileCost; + } + + public record struct SimplePathResult + { + public static SimplePathResult NoPath = new(); + + public List Path; + public Dictionary CameFrom; + } + + /// + /// Gets simple A* path from start to end. Can also supply an optional tile-cost for tiles. + /// + public SimplePathResult GetPath(SimplePathArgs args) + { + var cameFrom = new Dictionary(); + var costSoFar = new Dictionary(); + var frontier = new PriorityQueue(); + + costSoFar[args.Start] = 0f; + frontier.Enqueue(args.Start, 0f); + var count = 0; + + while (frontier.TryDequeue(out var node, out _) && count < args.Limit) + { + count++; + + if (node == args.End) + { + // Found target + var path = ReconstructPath(args.End, cameFrom); + + return new SimplePathResult() + { + CameFrom = cameFrom, + Path = path, + }; + } + + var gCost = costSoFar[node]; + + if (args.Diagonals) + { + for (var x = -1; x <= 1; x++) + { + for (var y = -1; y <= 1; y++) + { + var neighbor = node + new Vector2i(x, y); + var neighborCost = OctileDistance(node, neighbor) * args.TileCost?.Invoke(neighbor) ?? 1f; + + if (neighborCost.Equals(0f)) + { + continue; + } + + // f = g + h + // gScore is distance to the start node + // hScore is distance to the end node + var gScore = gCost + neighborCost; + + // Slower to get here so just ignore it. + if (costSoFar.TryGetValue(neighbor, out var nextValue) && gScore >= nextValue) + { + continue; + } + + cameFrom[neighbor] = node; + costSoFar[neighbor] = gScore; + // pFactor is tie-breaker where the fscore is otherwise equal. + // See http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html#breaking-ties + // There's other ways to do it but future consideration + // The closer the fScore is to the actual distance then the better the pathfinder will be + // (i.e. somewhere between 1 and infinite) + // Can use hierarchical pathfinder or whatever to improve the heuristic but this is fine for now. + var hScore = OctileDistance(args.End, neighbor) * (1.0f + 1.0f / 1000.0f); + var fScore = gScore + hScore; + frontier.Enqueue(neighbor, fScore); + } + } + } + else + { + for (var x = -1; x <= 1; x++) + { + for (var y = -1; y <= 1; y++) + { + if (x != 0 && y != 0) + continue; + + var neighbor = node + new Vector2i(x, y); + var neighborCost = ManhattanDistance(node, neighbor) * args.TileCost?.Invoke(neighbor) ?? 1f; + + if (neighborCost.Equals(0f)) + continue; + + var gScore = gCost + neighborCost; + + if (costSoFar.TryGetValue(neighbor, out var nextValue) && gScore >= nextValue) + continue; + + cameFrom[neighbor] = node; + costSoFar[neighbor] = gScore; + + // Still use octile even for manhattan distance. + var hScore = OctileDistance(args.End, neighbor) * 1.001f; + var fScore = gScore + hScore; + frontier.Enqueue(neighbor, fScore); + } + } + } + } + + return SimplePathResult.NoPath; + } + + private List ReconstructPath(Vector2i end, Dictionary cameFrom) + { + var path = new List() + { + end, + }; + var node = end; + + while (cameFrom.TryGetValue(node, out var source)) + { + path.Add(source); + node = source; + } + + path.Reverse(); + + return path; + } +} diff --git a/Content.Server/NPC/Pathfinding/PathfindingSystem.Splines.cs b/Content.Server/NPC/Pathfinding/PathfindingSystem.Splines.cs new file mode 100644 index 0000000000..9979755f99 --- /dev/null +++ b/Content.Server/NPC/Pathfinding/PathfindingSystem.Splines.cs @@ -0,0 +1,180 @@ +using Robust.Shared.Collections; +using Robust.Shared.Random; + +namespace Content.Server.NPC.Pathfinding; + +public sealed partial class PathfindingSystem +{ + public record struct SimplifyPathArgs + { + public Vector2i Start; + public Vector2i End; + public List Path; + } + + public record struct SplinePathResult() + { + public static SplinePathResult NoPath = new(); + + public List Points = new(); + + public List Path = new(); + public Dictionary CameFrom; + } + + public record struct SplinePathArgs(SimplePathArgs Args) + { + public SimplePathArgs Args = Args; + + public float MaxRatio = 0.25f; + + /// + /// Minimum distance between subdivisions. + /// + public int Distance = 20; + } + + /// + /// Gets a spline path from start to end. + /// + public SplinePathResult GetSplinePath(SplinePathArgs args, Random random) + { + var start = args.Args.Start; + var end = args.Args.End; + + var path = new List(); + + var pairs = new ValueList<(Vector2i Start, Vector2i End)> { (start, end) }; + var subdivided = true; + + // Sub-divide recursively + while (subdivided) + { + // Sometimes we might inadvertantly get 2 nodes too close together so better to just check each one as it comes up instead. + var i = 0; + subdivided = false; + + while (i < pairs.Count) + { + var pointA = pairs[i].Start; + var pointB = pairs[i].End; + var vector = pointB - pointA; + + var halfway = vector / 2f; + + // Finding the point + var adj = halfway.Length(); + + // Should we even subdivide. + if (adj <= args.Distance) + { + // Just check the next entry no double skip. + i++; + continue; + } + + subdivided = true; + var opposite = args.MaxRatio * adj; + var hypotenuse = MathF.Sqrt(MathF.Pow(adj, 2) + MathF.Pow(opposite, 2)); + + // Okay so essentially we have 2 points and no poly + // We add 2 other points to form a diamond and want some point halfway between randomly offset. + var angle = new Angle(MathF.Atan(opposite / adj)); + var pointAPerp = pointA + angle.RotateVec(halfway).Normalized() * hypotenuse; + var pointBPerp = pointA + (-angle).RotateVec(halfway).Normalized() * hypotenuse; + + var perpLine = pointBPerp - pointAPerp; + var perpHalfway = perpLine.Length() / 2f; + + var splinePoint = (pointAPerp + perpLine.Normalized() * random.NextFloat(-args.MaxRatio, args.MaxRatio) * perpHalfway).Floored(); + + // We essentially take (A, B) and turn it into (A, C) & (C, B) + pairs[i] = (pointA, splinePoint); + pairs.Insert(i + 1, (splinePoint, pointB)); + + i+= 2; + } + } + + var spline = new ValueList(pairs.Count - 1) + { + start + }; + + foreach (var pair in pairs) + { + spline.Add(pair.End); + } + + // Now we need to pathfind between each node on the spline. + + // TODO: Add rotation version or straight-line version for pathfinder config + // Move the worm pathfinder to here I think. + var cameFrom = new Dictionary(); + + // TODO: Need to get rid of the branch bullshit. + var points = new List(); + + for (var i = 0; i < spline.Count - 1; i++) + { + var point = spline[i]; + var target = spline[i + 1]; + points.Add(point); + var aStarArgs = args.Args with { Start = point, End = target }; + + var aStarResult = GetPath(aStarArgs); + + if (aStarResult == SimplePathResult.NoPath) + return SplinePathResult.NoPath; + + path.AddRange(aStarResult.Path[0..]); + + foreach (var a in aStarResult.CameFrom) + { + cameFrom[a.Key] = a.Value; + } + } + + points.Add(spline[^1]); + + var simple = SimplifyPath(new SimplifyPathArgs() + { + Start = args.Args.Start, + End = args.Args.End, + Path = path, + }); + + return new SplinePathResult() + { + Path = simple, + CameFrom = cameFrom, + Points = points, + }; + } + + /// + /// Does a simpler pathfinder over the nodes to prune unnecessary branches. + /// + public List SimplifyPath(SimplifyPathArgs args) + { + var nodes = new HashSet(args.Path); + + var result = GetBreadthPath(new BreadthPathArgs() + { + Start = args.Start, + Ends = new List() + { + args.End, + }, + TileCost = node => + { + if (!nodes.Contains(node)) + return 0f; + + return 1f; + } + }); + + return result.Path; + } +} diff --git a/Content.Server/NPC/Pathfinding/PathfindingSystem.Widen.cs b/Content.Server/NPC/Pathfinding/PathfindingSystem.Widen.cs new file mode 100644 index 0000000000..f7bcd019f5 --- /dev/null +++ b/Content.Server/NPC/Pathfinding/PathfindingSystem.Widen.cs @@ -0,0 +1,89 @@ +using System.Numerics; +using Robust.Shared.Random; + +namespace Content.Server.NPC.Pathfinding; + +public sealed partial class PathfindingSystem +{ + /// + /// Widens the path by the specified amount. + /// + public HashSet GetWiden(WidenArgs args, Random random) + { + var tiles = new HashSet(args.Path.Count * 2); + var variance = (args.MaxWiden - args.MinWiden) / 2f + args.MinWiden; + var counter = 0; + + foreach (var tile in args.Path) + { + counter++; + + if (counter != args.TileSkip) + continue; + + counter = 0; + + var center = new Vector2(tile.X + 0.5f, tile.Y + 0.5f); + + if (args.Square) + { + for (var x = -variance; x <= variance; x++) + { + for (var y = -variance; y <= variance; y++) + { + var neighbor = center + new Vector2(x, y); + + tiles.Add(neighbor.Floored()); + } + } + } + else + { + for (var x = -variance; x <= variance; x++) + { + for (var y = -variance; y <= variance; y++) + { + var offset = new Vector2(x, y); + + if (offset.Length() > variance) + continue; + + var neighbor = center + offset; + + tiles.Add(neighbor.Floored()); + } + } + } + + variance += random.NextFloat(-args.Variance * args.TileSkip, args.Variance * args.TileSkip); + variance = Math.Clamp(variance, args.MinWiden, args.MaxWiden); + } + + return tiles; + } + + public record struct WidenArgs() + { + public bool Square = false; + + /// + /// How many tiles to skip between iterations., 1-in-n + /// + public int TileSkip = 3; + + /// + /// Maximum amount to vary per tile. + /// + public float Variance = 0.25f; + + /// + /// Minimum width. + /// + public float MinWiden = 2f; + + + public float MaxWiden = 7f; + + public List Path; + } +} diff --git a/Content.Server/NPC/Systems/NPCSteeringSystem.Context.cs b/Content.Server/NPC/Systems/NPCSteeringSystem.Context.cs index 5f871a6ecf..e0bcb97a11 100644 --- a/Content.Server/NPC/Systems/NPCSteeringSystem.Context.cs +++ b/Content.Server/NPC/Systems/NPCSteeringSystem.Context.cs @@ -142,6 +142,13 @@ public sealed partial class NPCSteeringSystem // Grab the target position, either the next path node or our end goal.. var targetCoordinates = GetTargetCoordinates(steering); + + if (!targetCoordinates.IsValid(EntityManager)) + { + steering.Status = SteeringStatus.NoPath; + return false; + } + var needsPath = false; // If the next node is invalid then get new ones @@ -243,6 +250,14 @@ public sealed partial class NPCSteeringSystem // Alright just adjust slightly and grab the next node so we don't stop moving for a tick. // TODO: If it's the last node just grab the target instead. targetCoordinates = GetTargetCoordinates(steering); + + if (!targetCoordinates.IsValid(EntityManager)) + { + SetDirection(mover, steering, Vector2.Zero); + steering.Status = SteeringStatus.NoPath; + return false; + } + targetMap = targetCoordinates.ToMap(EntityManager, _transform); // Can't make it again. diff --git a/Content.Server/Procedural/DungeonJob.PostGen.cs b/Content.Server/Procedural/DungeonJob.PostGen.cs deleted file mode 100644 index cb9e64f04e..0000000000 --- a/Content.Server/Procedural/DungeonJob.PostGen.cs +++ /dev/null @@ -1,1258 +0,0 @@ -using System.Linq; -using System.Numerics; -using System.Threading.Tasks; -using Content.Server.NodeContainer; -using Content.Shared.Doors.Components; -using Content.Shared.Maps; -using Content.Shared.Physics; -using Content.Shared.Procedural; -using Content.Shared.Procedural.PostGeneration; -using Content.Shared.Storage; -using Content.Shared.Tag; -using Robust.Shared.Collections; -using Robust.Shared.Map; -using Robust.Shared.Map.Components; -using Robust.Shared.Physics.Components; -using Robust.Shared.Prototypes; -using Robust.Shared.Random; -using Robust.Shared.Utility; - -namespace Content.Server.Procedural; - -public sealed partial class DungeonJob -{ - /* - * Run after the main dungeon generation - */ - - private static readonly ProtoId WallTag = "Wall"; - - private bool HasWall(MapGridComponent grid, Vector2i tile) - { - var anchored = _maps.GetAnchoredEntitiesEnumerator(_gridUid, _grid, tile); - - while (anchored.MoveNext(out var uid)) - { - if (_tag.HasTag(uid.Value, WallTag)) - return true; - } - - return false; - } - - private async Task PostGen(AutoCablingPostGen gen, Dungeon dungeon, EntityUid gridUid, MapGridComponent grid, - Random random) - { - // There's a lot of ways you could do this. - // For now we'll just connect every LV cable in the dungeon. - var cableTiles = new HashSet(); - var allTiles = new HashSet(dungeon.CorridorTiles); - allTiles.UnionWith(dungeon.RoomTiles); - allTiles.UnionWith(dungeon.RoomExteriorTiles); - allTiles.UnionWith(dungeon.CorridorExteriorTiles); - var nodeQuery = _entManager.GetEntityQuery(); - - // Gather existing nodes - foreach (var tile in allTiles) - { - var anchored = _maps.GetAnchoredEntitiesEnumerator(_gridUid, _grid, tile); - - while (anchored.MoveNext(out var anc)) - { - if (!nodeQuery.TryGetComponent(anc, out var nodeContainer) || - !nodeContainer.Nodes.ContainsKey("power")) - { - continue; - } - - cableTiles.Add(tile); - break; - } - } - - // Iterating them all might be expensive. - await SuspendIfOutOfTime(); - - if (!ValidateResume()) - return; - - var startNodes = new List(cableTiles); - random.Shuffle(startNodes); - var start = startNodes[0]; - var remaining = new HashSet(startNodes); - var frontier = new PriorityQueue(); - frontier.Enqueue(start, 0f); - var cameFrom = new Dictionary(); - var costSoFar = new Dictionary(); - var lastDirection = new Dictionary(); - costSoFar[start] = 0f; - lastDirection[start] = Direction.Invalid; - - while (remaining.Count > 0) - { - if (frontier.Count == 0) - { - var newStart = remaining.First(); - frontier.Enqueue(newStart, 0f); - lastDirection[newStart] = Direction.Invalid; - } - - var node = frontier.Dequeue(); - - if (remaining.Remove(node)) - { - var weh = node; - - while (cameFrom.TryGetValue(weh, out var receiver)) - { - cableTiles.Add(weh); - weh = receiver; - - if (weh == start) - break; - } - } - - if (!grid.TryGetTileRef(node, out var tileRef) || tileRef.Tile.IsEmpty) - { - continue; - } - - for (var i = 0; i < 4; i++) - { - var dir = (Direction) (i * 2); - - var neighbor = node + dir.ToIntVec(); - var tileCost = 1f; - - // Prefer straight lines. - if (lastDirection[node] != dir) - { - tileCost *= 1.1f; - } - - if (cableTiles.Contains(neighbor)) - { - tileCost *= 0.1f; - } - - // Prefer tiles without walls on them - if (HasWall(grid, neighbor)) - { - tileCost *= 20f; - } - - var gScore = costSoFar[node] + tileCost; - - if (costSoFar.TryGetValue(neighbor, out var nextValue) && gScore >= nextValue) - { - continue; - } - - cameFrom[neighbor] = node; - costSoFar[neighbor] = gScore; - lastDirection[neighbor] = dir; - frontier.Enqueue(neighbor, gScore); - } - } - - foreach (var tile in cableTiles) - { - var anchored = grid.GetAnchoredEntitiesEnumerator(tile); - var found = false; - - while (anchored.MoveNext(out var anc)) - { - if (!nodeQuery.TryGetComponent(anc, out var nodeContainer) || - !nodeContainer.Nodes.ContainsKey("power")) - { - continue; - } - - found = true; - break; - } - - if (found) - continue; - - _entManager.SpawnEntity(gen.Entity, _grid.GridTileToLocal(tile)); - } - } - - private async Task PostGen(BoundaryWallPostGen gen, Dungeon dungeon, EntityUid gridUid, MapGridComponent grid, Random random) - { - var tileDef = _tileDefManager[gen.Tile]; - var tiles = new List<(Vector2i Index, Tile Tile)>(dungeon.RoomExteriorTiles.Count); - - // Spawn wall outline - // - Tiles first - foreach (var neighbor in dungeon.RoomExteriorTiles) - { - DebugTools.Assert(!dungeon.RoomTiles.Contains(neighbor)); - - if (dungeon.Entrances.Contains(neighbor)) - continue; - - if (!_anchorable.TileFree(grid, neighbor, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) - continue; - - tiles.Add((neighbor, _tile.GetVariantTile((ContentTileDefinition) tileDef, random))); - } - - foreach (var index in dungeon.CorridorExteriorTiles) - { - if (dungeon.RoomTiles.Contains(index)) - continue; - - if (!_anchorable.TileFree(grid, index, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) - continue; - - tiles.Add((index, _tile.GetVariantTile((ContentTileDefinition)tileDef, random))); - } - - grid.SetTiles(tiles); - - // Double iteration coz we bulk set tiles for speed. - for (var i = 0; i < tiles.Count; i++) - { - var index = tiles[i]; - if (!_anchorable.TileFree(grid, index.Index, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) - continue; - - // If no cardinal neighbors in dungeon then we're a corner. - var isCorner = false; - - if (gen.CornerWall != null) - { - isCorner = true; - - for (var x = -1; x <= 1; x++) - { - for (var y = -1; y <= 1; y++) - { - if (x != 0 && y != 0) - { - continue; - } - - var neighbor = new Vector2i(index.Index.X + x, index.Index.Y + y); - - if (dungeon.RoomTiles.Contains(neighbor) || dungeon.CorridorTiles.Contains(neighbor)) - { - isCorner = false; - break; - } - } - - if (!isCorner) - break; - } - - if (isCorner) - _entManager.SpawnEntity(gen.CornerWall, grid.GridTileToLocal(index.Index)); - } - - if (!isCorner) - _entManager.SpawnEntity(gen.Wall, grid.GridTileToLocal(index.Index)); - - if (i % 20 == 0) - { - await SuspendIfOutOfTime(); - - if (!ValidateResume()) - return; - } - } - } - - private async Task PostGen(CornerClutterPostGen gen, Dungeon dungeon, EntityUid gridUid, MapGridComponent grid, - Random random) - { - var physicsQuery = _entManager.GetEntityQuery(); - - foreach (var tile in dungeon.CorridorTiles) - { - var enumerator = _grid.GetAnchoredEntitiesEnumerator(tile); - var blocked = false; - - while (enumerator.MoveNext(out var ent)) - { - // TODO: TileFree - if (!physicsQuery.TryGetComponent(ent, out var physics) || - !physics.CanCollide || - !physics.Hard) - { - continue; - } - - blocked = true; - break; - } - - if (blocked) - continue; - - // If at least 2 adjacent tiles are blocked consider it a corner - for (var i = 0; i < 4; i++) - { - var dir = (Direction) (i * 2); - blocked = HasWall(grid, tile + dir.ToIntVec()); - - if (!blocked) - continue; - - var nextDir = (Direction) ((i + 1) * 2 % 8); - blocked = HasWall(grid, tile + nextDir.ToIntVec()); - - if (!blocked) - continue; - - if (random.Prob(gen.Chance)) - { - var coords = _grid.GridTileToLocal(tile); - var protos = EntitySpawnCollection.GetSpawns(gen.Contents, random); - _entManager.SpawnEntities(coords, protos); - } - - break; - } - } - } - - private async Task PostGen(CorridorDecalSkirtingPostGen decks, Dungeon dungeon, EntityUid gridUid, MapGridComponent grid, Random random) - { - var directions = new ValueList(4); - var pocketDirections = new ValueList(4); - var doorQuery = _entManager.GetEntityQuery(); - var physicsQuery = _entManager.GetEntityQuery(); - var offset = -_grid.TileSizeHalfVector; - var color = decks.Color; - - foreach (var tile in dungeon.CorridorTiles) - { - DebugTools.Assert(!dungeon.RoomTiles.Contains(tile)); - directions.Clear(); - - // Do cardinals 1 step - // Do corners the other step - for (var i = 0; i < 4; i++) - { - var dir = (DirectionFlag) Math.Pow(2, i); - var neighbor = tile + dir.AsDir().ToIntVec(); - - var anc = _grid.GetAnchoredEntitiesEnumerator(neighbor); - - while (anc.MoveNext(out var ent)) - { - if (!physicsQuery.TryGetComponent(ent, out var physics) || - !physics.CanCollide || - !physics.Hard || - doorQuery.HasComponent(ent.Value)) - { - continue; - } - - directions.Add(dir); - break; - } - } - - // Pockets - if (directions.Count == 0) - { - pocketDirections.Clear(); - - for (var i = 1; i < 5; i++) - { - var dir = (Direction) (i * 2 - 1); - var neighbor = tile + dir.ToIntVec(); - - var anc = _grid.GetAnchoredEntitiesEnumerator(neighbor); - - while (anc.MoveNext(out var ent)) - { - if (!physicsQuery.TryGetComponent(ent, out var physics) || - !physics.CanCollide || - !physics.Hard || - doorQuery.HasComponent(ent.Value)) - { - continue; - } - - pocketDirections.Add(dir); - break; - } - } - - if (pocketDirections.Count == 1) - { - if (decks.PocketDecals.TryGetValue(pocketDirections[0], out var cDir)) - { - // Decals not being centered biting my ass again - var gridPos = _grid.GridTileToLocal(tile).Offset(offset); - _decals.TryAddDecal(cDir, gridPos, out _, color: color); - } - } - - continue; - } - - if (directions.Count == 1) - { - if (decks.CardinalDecals.TryGetValue(directions[0], out var cDir)) - { - // Decals not being centered biting my ass again - var gridPos = _grid.GridTileToLocal(tile).Offset(offset); - _decals.TryAddDecal(cDir, gridPos, out _, color: color); - } - - continue; - } - - // Corners - if (directions.Count == 2) - { - // Auehghegueugegegeheh help me - var dirFlag = directions[0] | directions[1]; - - if (decks.CornerDecals.TryGetValue(dirFlag, out var cDir)) - { - var gridPos = _grid.GridTileToLocal(tile).Offset(offset); - _decals.TryAddDecal(cDir, gridPos, out _, color: color); - } - } - } - } - - private async Task PostGen(DungeonEntrancePostGen gen, Dungeon dungeon, EntityUid gridUid, MapGridComponent grid, Random random) - { - var rooms = new List(dungeon.Rooms); - var roomTiles = new List(); - var tileDef = _tileDefManager[gen.Tile]; - - for (var i = 0; i < gen.Count; i++) - { - var roomIndex = random.Next(rooms.Count); - var room = rooms[roomIndex]; - - // Move out 3 tiles in a direction away from center of the room - // If none of those intersect another tile it's probably external - // TODO: Maybe need to take top half of furthest rooms in case there's interior exits? - roomTiles.AddRange(room.Exterior); - random.Shuffle(roomTiles); - - foreach (var tile in roomTiles) - { - var isValid = false; - - // Check if one side is dungeon and the other side is nothing. - for (var j = 0; j < 4; j++) - { - var dir = (Direction) (j * 2); - var oppositeDir = dir.GetOpposite(); - var dirVec = tile + dir.ToIntVec(); - var oppositeDirVec = tile + oppositeDir.ToIntVec(); - - if (!dungeon.RoomTiles.Contains(dirVec)) - { - continue; - } - - if (dungeon.RoomTiles.Contains(oppositeDirVec) || - dungeon.RoomExteriorTiles.Contains(oppositeDirVec) || - dungeon.CorridorExteriorTiles.Contains(oppositeDirVec) || - dungeon.CorridorTiles.Contains(oppositeDirVec)) - { - continue; - } - - // Check if exterior spot free. - if (!_anchorable.TileFree(_grid, tile, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) - { - continue; - } - - // Check if interior spot free (no guarantees on exterior but ClearDoor should handle it) - if (!_anchorable.TileFree(_grid, dirVec, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) - { - continue; - } - - // Valid pick! - isValid = true; - - // Entrance wew - grid.SetTile(tile, _tile.GetVariantTile((ContentTileDefinition) tileDef, random)); - ClearDoor(dungeon, grid, tile); - var gridCoords = grid.GridTileToLocal(tile); - // Need to offset the spawn to avoid spawning in the room. - - _entManager.SpawnEntities(gridCoords, gen.Entities); - - // Clear out any biome tiles nearby to avoid blocking it - foreach (var nearTile in grid.GetTilesIntersecting(new Circle(gridCoords.Position, 1.5f), false)) - { - if (dungeon.RoomTiles.Contains(nearTile.GridIndices) || - dungeon.RoomExteriorTiles.Contains(nearTile.GridIndices) || - dungeon.CorridorTiles.Contains(nearTile.GridIndices) || - dungeon.CorridorExteriorTiles.Contains(nearTile.GridIndices)) - { - continue; - } - - grid.SetTile(nearTile.GridIndices, _tile.GetVariantTile((ContentTileDefinition) tileDef, random));; - } - - break; - } - - if (isValid) - break; - } - - roomTiles.Clear(); - } - } - - private async Task PostGen(ExternalWindowPostGen gen, Dungeon dungeon, EntityUid gridUid, MapGridComponent grid, - Random random) - { - // Iterate every tile with N chance to spawn windows on that wall per cardinal dir. - var chance = 0.25 / 3f; - - var allExterior = new HashSet(dungeon.CorridorExteriorTiles); - allExterior.UnionWith(dungeon.RoomExteriorTiles); - var validTiles = allExterior.ToList(); - random.Shuffle(validTiles); - - var tiles = new List<(Vector2i, Tile)>(); - var tileDef = _tileDefManager[gen.Tile]; - var count = Math.Floor(validTiles.Count * chance); - var index = 0; - var takenTiles = new HashSet(); - - // There's a bunch of shit here but tl;dr - // - don't spawn over cap - // - Check if we have 3 tiles in a row that aren't corners and aren't obstructed - foreach (var tile in validTiles) - { - if (index > count) - break; - - // Room tile / already used. - if (!_anchorable.TileFree(_grid, tile, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask) || - takenTiles.Contains(tile)) - { - continue; - } - - // Check we're not on a corner - for (var i = 0; i < 2; i++) - { - var dir = (Direction) (i * 2); - var dirVec = dir.ToIntVec(); - var isValid = true; - - // Check 1 beyond either side to ensure it's not a corner. - for (var j = -1; j < 4; j++) - { - var neighbor = tile + dirVec * j; - - if (!allExterior.Contains(neighbor) || - takenTiles.Contains(neighbor) || - !_anchorable.TileFree(grid, neighbor, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) - { - isValid = false; - break; - } - - // Also check perpendicular that it is free - foreach (var k in new [] {2, 6}) - { - var perp = (Direction) ((i * 2 + k) % 8); - var perpVec = perp.ToIntVec(); - var perpTile = tile + perpVec; - - if (allExterior.Contains(perpTile) || - takenTiles.Contains(neighbor) || - !_anchorable.TileFree(_grid, perpTile, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) - { - isValid = false; - break; - } - } - - if (!isValid) - break; - } - - if (!isValid) - continue; - - for (var j = 0; j < 3; j++) - { - var neighbor = tile + dirVec * j; - - tiles.Add((neighbor, _tile.GetVariantTile((ContentTileDefinition) tileDef, random))); - index++; - takenTiles.Add(neighbor); - } - } - } - - grid.SetTiles(tiles); - index = 0; - - foreach (var tile in tiles) - { - var gridPos = grid.GridTileToLocal(tile.Item1); - - index += gen.Entities.Count; - _entManager.SpawnEntities(gridPos, gen.Entities); - - if (index > 20) - { - index -= 20; - await SuspendIfOutOfTime(); - - if (!ValidateResume()) - return; - } - } - } - - /* - * You may be wondering why these are different. - * It's because for internals we want to force it as it looks nicer and not leave it up to chance. - */ - - // TODO: Can probably combine these a bit, their differences are in really annoying to pull out spots. - - private async Task PostGen(InternalWindowPostGen gen, Dungeon dungeon, EntityUid gridUid, MapGridComponent grid, - Random random) - { - // Iterate every room and check if there's a gap beyond it that leads to another room within N tiles - // If so then consider windows - var minDistance = 4; - var maxDistance = 6; - var tileDef = _tileDefManager[gen.Tile]; - - foreach (var room in dungeon.Rooms) - { - var validTiles = new List(); - - for (var i = 0; i < 4; i++) - { - var dir = (DirectionFlag) Math.Pow(2, i); - var dirVec = dir.AsDir().ToIntVec(); - - foreach (var tile in room.Tiles) - { - var tileAngle = ((Vector2) tile + grid.TileSizeHalfVector - room.Center).ToAngle(); - var roundedAngle = Math.Round(tileAngle.Theta / (Math.PI / 2)) * (Math.PI / 2); - - var tileVec = (Vector2i) new Angle(roundedAngle).ToVec().Rounded(); - - if (!tileVec.Equals(dirVec)) - continue; - - var valid = false; - - for (var j = 1; j < maxDistance; j++) - { - var edgeNeighbor = tile + dirVec * j; - - if (dungeon.RoomTiles.Contains(edgeNeighbor)) - { - if (j < minDistance) - { - valid = false; - } - else - { - valid = true; - } - - break; - } - } - - if (!valid) - continue; - - var windowTile = tile + dirVec; - - if (!_anchorable.TileFree(grid, windowTile, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) - continue; - - validTiles.Add(windowTile); - } - - validTiles.Sort((x, y) => ((Vector2) x + grid.TileSizeHalfVector - room.Center).LengthSquared().CompareTo((y + grid.TileSizeHalfVector - room.Center).LengthSquared)); - - for (var j = 0; j < Math.Min(validTiles.Count, 3); j++) - { - var tile = validTiles[j]; - var gridPos = grid.GridTileToLocal(tile); - grid.SetTile(tile, _tile.GetVariantTile((ContentTileDefinition) tileDef, random)); - - _entManager.SpawnEntities(gridPos, gen.Entities); - } - - if (validTiles.Count > 0) - { - await SuspendIfOutOfTime(); - - if (!ValidateResume()) - return; - } - - validTiles.Clear(); - } - } - } - - /// - /// Simply places tiles / entities on the entrances to rooms. - /// - private async Task PostGen(RoomEntrancePostGen gen, Dungeon dungeon, EntityUid gridUid, MapGridComponent grid, - Random random) - { - var setTiles = new List<(Vector2i, Tile)>(); - var tileDef = _tileDefManager[gen.Tile]; - - foreach (var room in dungeon.Rooms) - { - foreach (var entrance in room.Entrances) - { - setTiles.Add((entrance, _tile.GetVariantTile((ContentTileDefinition) tileDef, random))); - } - } - - grid.SetTiles(setTiles); - - foreach (var room in dungeon.Rooms) - { - foreach (var entrance in room.Entrances) - { - _entManager.SpawnEntities(grid.GridTileToLocal(entrance), gen.Entities); - } - } - } - - /// - /// Generates corridor connections between entrances to all the rooms. - /// - private async Task PostGen(CorridorPostGen gen, Dungeon dungeon, EntityUid gridUid, MapGridComponent grid, Random random) - { - var entrances = new List(dungeon.Rooms.Count); - - // Grab entrances - foreach (var room in dungeon.Rooms) - { - entrances.AddRange(room.Entrances); - } - - var edges = _dungeon.MinimumSpanningTree(entrances, random); - await SuspendIfOutOfTime(); - - if (!ValidateResume()) - return; - - // TODO: Add in say 1/3 of edges back in to add some cyclic to it. - - var expansion = gen.Width - 2; - // Okay so tl;dr is that we don't want to cut close to rooms as it might go from 3 width to 2 width suddenly - // So we will add a buffer range around each room to deter pathfinding there unless necessary - var deterredTiles = new HashSet(); - - if (expansion >= 1) - { - foreach (var tile in dungeon.RoomExteriorTiles) - { - for (var x = -expansion; x <= expansion; x++) - { - for (var y = -expansion; y <= expansion; y++) - { - var neighbor = new Vector2(tile.X + x, tile.Y + y).Floored(); - - if (dungeon.RoomTiles.Contains(neighbor) || - dungeon.RoomExteriorTiles.Contains(neighbor) || - entrances.Contains(neighbor)) - { - continue; - } - - deterredTiles.Add(neighbor); - } - } - } - } - - foreach (var room in dungeon.Rooms) - { - foreach (var entrance in room.Entrances) - { - // Just so we can still actually get in to the entrance we won't deter from a tile away from it. - var normal = (entrance + grid.TileSizeHalfVector - room.Center).ToWorldAngle().GetCardinalDir().ToIntVec(); - deterredTiles.Remove(entrance + normal); - } - } - - var excludedTiles = new HashSet(dungeon.RoomExteriorTiles); - excludedTiles.UnionWith(dungeon.RoomTiles); - var corridorTiles = new HashSet(); - - _dungeon.GetCorridorNodes(corridorTiles, edges, gen.PathLimit, excludedTiles, tile => - { - var mod = 1f; - - if (corridorTiles.Contains(tile)) - { - mod *= 0.1f; - } - - if (deterredTiles.Contains(tile)) - { - mod *= 2f; - } - - return mod; - }); - - WidenCorridor(dungeon, gen.Width, corridorTiles); - - var setTiles = new List<(Vector2i, Tile)>(); - var tileDef = _prototype.Index(gen.Tile); - - foreach (var tile in corridorTiles) - { - setTiles.Add((tile, _tile.GetVariantTile(tileDef, random))); - } - - grid.SetTiles(setTiles); - dungeon.CorridorTiles.UnionWith(corridorTiles); - BuildCorridorExterior(dungeon); - } - - private void BuildCorridorExterior(Dungeon dungeon) - { - var exterior = dungeon.CorridorExteriorTiles; - - // Just ignore entrances or whatever for now. - foreach (var tile in dungeon.CorridorTiles) - { - for (var x = -1; x <= 1; x++) - { - for (var y = -1; y <= 1; y++) - { - var neighbor = new Vector2i(tile.X + x, tile.Y + y); - - if (dungeon.CorridorTiles.Contains(neighbor) || - dungeon.RoomExteriorTiles.Contains(neighbor) || - dungeon.RoomTiles.Contains(neighbor) || - dungeon.Entrances.Contains(neighbor)) - { - continue; - } - - exterior.Add(neighbor); - } - } - } - } - - private void WidenCorridor(Dungeon dungeon, float width, ICollection corridorTiles) - { - var expansion = width - 2; - - // Widen the path - if (expansion >= 1) - { - var toAdd = new ValueList(); - - foreach (var node in corridorTiles) - { - // Uhhh not sure on the cleanest way to do this but tl;dr we don't want to hug - // exterior walls and make the path smaller. - - for (var x = -expansion; x <= expansion; x++) - { - for (var y = -expansion; y <= expansion; y++) - { - var neighbor = new Vector2(node.X + x, node.Y + y).Floored(); - - // Diagonals still matter here. - if (dungeon.RoomTiles.Contains(neighbor) || - dungeon.RoomExteriorTiles.Contains(neighbor)) - { - // Try - - continue; - } - - toAdd.Add(neighbor); - } - } - } - - foreach (var node in toAdd) - { - corridorTiles.Add(node); - } - } - } - - private async Task PostGen(EntranceFlankPostGen gen, Dungeon dungeon, EntityUid gridUid, MapGridComponent grid, - Random random) - { - var tiles = new List<(Vector2i Index, Tile)>(); - var tileDef = _tileDefManager[gen.Tile]; - var spawnPositions = new ValueList(dungeon.Rooms.Count); - - foreach (var room in dungeon.Rooms) - { - foreach (var entrance in room.Entrances) - { - for (var i = 0; i < 8; i++) - { - var dir = (Direction) i; - var neighbor = entrance + dir.ToIntVec(); - - if (!dungeon.RoomExteriorTiles.Contains(neighbor)) - continue; - - tiles.Add((neighbor, _tile.GetVariantTile((ContentTileDefinition) tileDef, random))); - spawnPositions.Add(neighbor); - } - } - } - - grid.SetTiles(tiles); - - foreach (var entrance in spawnPositions) - { - _entManager.SpawnEntities(_grid.GridTileToLocal(entrance), gen.Entities); - } - } - - private async Task PostGen(JunctionPostGen gen, Dungeon dungeon, EntityUid gridUid, MapGridComponent grid, - Random random) - { - var tileDef = _tileDefManager[gen.Tile]; - - // N-wide junctions - foreach (var tile in dungeon.CorridorTiles) - { - if (!_anchorable.TileFree(_grid, tile, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) - continue; - - // Check each direction: - // - Check if immediate neighbors are free - // - Check if the neighbors beyond that are not free - // - Then check either side if they're slightly more free - var exteriorWidth = (int) Math.Floor(gen.Width / 2f); - var width = (int) Math.Ceiling(gen.Width / 2f); - - for (var i = 0; i < 2; i++) - { - var isValid = true; - var neighborDir = (Direction) (i * 2); - var neighborVec = neighborDir.ToIntVec(); - - for (var j = -width; j <= width; j++) - { - if (j == 0) - continue; - - var neighbor = tile + neighborVec * j; - - // If it's an end tile then check it's occupied. - if (j == -width || - j == width) - { - if (!HasWall(grid, neighbor)) - { - isValid = false; - break; - } - - continue; - } - - // If we're not at the end tile then check it + perpendicular are free. - if (!_anchorable.TileFree(_grid, neighbor, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) - { - isValid = false; - break; - } - - var perp1 = tile + neighborVec * j + ((Direction) ((i * 2 + 2) % 8)).ToIntVec(); - var perp2 = tile + neighborVec * j + ((Direction) ((i * 2 + 6) % 8)).ToIntVec(); - - if (!_anchorable.TileFree(_grid, perp1, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) - { - isValid = false; - break; - } - - if (!_anchorable.TileFree(_grid, perp2, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) - { - isValid = false; - break; - } - } - - if (!isValid) - continue; - - // Check corners to see if either side opens up (if it's just a 1x wide corridor do nothing, needs to be a funnel. - foreach (var j in new [] {-exteriorWidth, exteriorWidth}) - { - var freeCount = 0; - - // Need at least 3 of 4 free - for (var k = 0; k < 4; k++) - { - var cornerDir = (Direction) (k * 2 + 1); - var cornerVec = cornerDir.ToIntVec(); - var cornerNeighbor = tile + neighborVec * j + cornerVec; - - if (_anchorable.TileFree(_grid, cornerNeighbor, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) - { - freeCount++; - } - } - - if (freeCount < gen.Width) - continue; - - // Valid! - isValid = true; - - for (var x = -width + 1; x < width; x++) - { - var weh = tile + neighborDir.ToIntVec() * x; - grid.SetTile(weh, _tile.GetVariantTile((ContentTileDefinition) tileDef, random)); - - var coords = grid.GridTileToLocal(weh); - _entManager.SpawnEntities(coords, gen.Entities); - } - - break; - } - - if (isValid) - { - await SuspendIfOutOfTime(); - - if (!ValidateResume()) - return; - } - - break; - } - } - } - - private async Task PostGen(MiddleConnectionPostGen gen, Dungeon dungeon, EntityUid gridUid, MapGridComponent grid, Random random) - { - // TODO: Need a minimal spanning tree version tbh - - // Grab all of the room bounds - // Then, work out connections between them - var roomBorders = new Dictionary>(dungeon.Rooms.Count); - - foreach (var room in dungeon.Rooms) - { - var roomEdges = new HashSet(); - - foreach (var index in room.Tiles) - { - for (var x = -1; x <= 1; x++) - { - for (var y = -1; y <= 1; y++) - { - // Cardinals only - if (x != 0 && y != 0 || - x == 0 && y == 0) - { - continue; - } - - var neighbor = new Vector2i(index.X + x, index.Y + y); - - if (dungeon.RoomTiles.Contains(neighbor)) - continue; - - if (!_anchorable.TileFree(grid, neighbor, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) - continue; - - roomEdges.Add(neighbor); - } - } - } - - roomBorders.Add(room, roomEdges); - } - - // Do pathfind from first room to work out graph. - // TODO: Optional loops - - var roomConnections = new Dictionary>(); - var frontier = new Queue(); - frontier.Enqueue(dungeon.Rooms.First()); - var tileDef = _tileDefManager[gen.Tile]; - - foreach (var (room, border) in roomBorders) - { - var conns = roomConnections.GetOrNew(room); - - foreach (var (otherRoom, otherBorders) in roomBorders) - { - if (room.Equals(otherRoom) || - conns.Contains(otherRoom)) - { - continue; - } - - var flipp = new HashSet(border); - flipp.IntersectWith(otherBorders); - - if (flipp.Count == 0 || - gen.OverlapCount != -1 && flipp.Count != gen.OverlapCount) - continue; - - var center = Vector2.Zero; - - foreach (var node in flipp) - { - center += (Vector2) node + grid.TileSizeHalfVector; - } - - center /= flipp.Count; - // Weight airlocks towards center more. - var nodeDistances = new List<(Vector2i Node, float Distance)>(flipp.Count); - - foreach (var node in flipp) - { - nodeDistances.Add((node, ((Vector2) node + grid.TileSizeHalfVector - center).LengthSquared())); - } - - nodeDistances.Sort((x, y) => x.Distance.CompareTo(y.Distance)); - - var width = gen.Count; - - for (var i = 0; i < nodeDistances.Count; i++) - { - var node = nodeDistances[i].Node; - var gridPos = grid.GridTileToLocal(node); - if (!_anchorable.TileFree(grid, node, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) - continue; - - width--; - grid.SetTile(node, _tile.GetVariantTile((ContentTileDefinition) tileDef, random)); - - if (gen.EdgeEntities != null && nodeDistances.Count - i <= 2) - { - _entManager.SpawnEntities(gridPos, gen.EdgeEntities); - } - else - { - // Iterate neighbors and check for blockers, if so bulldoze - ClearDoor(dungeon, grid, node); - - _entManager.SpawnEntities(gridPos, gen.Entities); - } - - if (width == 0) - break; - } - - conns.Add(otherRoom); - var otherConns = roomConnections.GetOrNew(otherRoom); - otherConns.Add(room); - await SuspendIfOutOfTime(); - - if (!ValidateResume()) - return; - } - } - } - - /// - /// Removes any unwanted obstacles around a door tile. - /// - private void ClearDoor(Dungeon dungeon, MapGridComponent grid, Vector2i indices, bool strict = false) - { - var flags = strict - ? LookupFlags.Dynamic | LookupFlags.Static | LookupFlags.StaticSundries - : LookupFlags.Dynamic | LookupFlags.Static; - var physicsQuery = _entManager.GetEntityQuery(); - - for (var x = -1; x <= 1; x++) - { - for (var y = -1; y <= 1; y++) - { - if (x != 0 && y != 0) - continue; - - var neighbor = new Vector2i(indices.X + x, indices.Y + y); - - if (!dungeon.RoomTiles.Contains(neighbor)) - continue; - - // Shrink by 0.01 to avoid polygon overlap from neighboring tiles. - foreach (var ent in _lookup.GetEntitiesIntersecting(_gridUid, new Box2(neighbor * grid.TileSize, (neighbor + 1) * grid.TileSize).Enlarged(-0.1f), flags)) - { - if (!physicsQuery.TryGetComponent(ent, out var physics) || - !physics.Hard || - (DungeonSystem.CollisionMask & physics.CollisionLayer) == 0x0 && - (DungeonSystem.CollisionLayer & physics.CollisionMask) == 0x0) - { - continue; - } - - _entManager.DeleteEntity(ent); - } - } - } - } - - private async Task PostGen(WallMountPostGen gen, Dungeon dungeon, EntityUid gridUid, MapGridComponent grid, - Random random) - { - var tileDef = _tileDefManager[gen.Tile]; - var checkedTiles = new HashSet(); - var allExterior = new HashSet(dungeon.CorridorExteriorTiles); - allExterior.UnionWith(dungeon.RoomExteriorTiles); - var count = 0; - - foreach (var neighbor in allExterior) - { - // Occupado - if (dungeon.RoomTiles.Contains(neighbor) || checkedTiles.Contains(neighbor) || !_anchorable.TileFree(grid, neighbor, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) - continue; - - if (!random.Prob(gen.Prob) || !checkedTiles.Add(neighbor)) - continue; - - grid.SetTile(neighbor, _tile.GetVariantTile((ContentTileDefinition) tileDef, random)); - var gridPos = grid.GridTileToLocal(neighbor); - var protoNames = EntitySpawnCollection.GetSpawns(gen.Spawns, random); - - _entManager.SpawnEntities(gridPos, protoNames); - count += protoNames.Count; - - if (count > 20) - { - count -= 20; - await SuspendIfOutOfTime(); - - if (!ValidateResume()) - return; - } - } - } -} diff --git a/Content.Server/Procedural/DungeonJob.PostGenBiome.cs b/Content.Server/Procedural/DungeonJob.PostGenBiome.cs deleted file mode 100644 index 4d3f573f4d..0000000000 --- a/Content.Server/Procedural/DungeonJob.PostGenBiome.cs +++ /dev/null @@ -1,138 +0,0 @@ -using System.Threading.Tasks; -using Content.Server.Parallax; -using Content.Shared.Parallax.Biomes; -using Content.Shared.Parallax.Biomes.Markers; -using Content.Shared.Procedural; -using Content.Shared.Procedural.PostGeneration; -using Content.Shared.Random.Helpers; -using Robust.Shared.Map; -using Robust.Shared.Map.Components; -using Robust.Shared.Utility; - -namespace Content.Server.Procedural; - -public sealed partial class DungeonJob -{ - /* - * Handles PostGen code for marker layers + biomes. - */ - - private async Task PostGen(BiomePostGen postGen, Dungeon dungeon, EntityUid gridUid, MapGridComponent grid, Random random) - { - if (_entManager.TryGetComponent(gridUid, out BiomeComponent? biomeComp)) - return; - - biomeComp = _entManager.AddComponent(gridUid); - var biomeSystem = _entManager.System(); - biomeSystem.SetTemplate(gridUid, biomeComp, _prototype.Index(postGen.BiomeTemplate)); - var seed = random.Next(); - var xformQuery = _entManager.GetEntityQuery(); - - foreach (var node in dungeon.RoomTiles) - { - // Need to set per-tile to override data. - if (biomeSystem.TryGetTile(node, biomeComp.Layers, seed, grid, out var tile)) - { - _maps.SetTile(gridUid, grid, node, tile.Value); - } - - if (biomeSystem.TryGetDecals(node, biomeComp.Layers, seed, grid, out var decals)) - { - foreach (var decal in decals) - { - _decals.TryAddDecal(decal.ID, new EntityCoordinates(gridUid, decal.Position), out _); - } - } - - if (biomeSystem.TryGetEntity(node, biomeComp, grid, out var entityProto)) - { - var ent = _entManager.SpawnEntity(entityProto, new EntityCoordinates(gridUid, node + grid.TileSizeHalfVector)); - var xform = xformQuery.Get(ent); - - if (!xform.Comp.Anchored) - { - _transform.AnchorEntity(ent, xform); - } - - // TODO: Engine bug with SpawnAtPosition - DebugTools.Assert(xform.Comp.Anchored); - } - - await SuspendIfOutOfTime(); - ValidateResume(); - } - - biomeComp.Enabled = false; - } - - private async Task PostGen(BiomeMarkerLayerPostGen postGen, Dungeon dungeon, EntityUid gridUid, MapGridComponent grid, Random random) - { - if (!_entManager.TryGetComponent(gridUid, out BiomeComponent? biomeComp)) - return; - - var biomeSystem = _entManager.System(); - var weightedRandom = _prototype.Index(postGen.MarkerTemplate); - var xformQuery = _entManager.GetEntityQuery(); - var templates = new Dictionary(); - - for (var i = 0; i < postGen.Count; i++) - { - var template = weightedRandom.Pick(random); - var count = templates.GetOrNew(template); - count++; - templates[template] = count; - } - - foreach (var (template, count) in templates) - { - var markerTemplate = _prototype.Index(template); - - var bounds = new Box2i(); - - foreach (var tile in dungeon.RoomTiles) - { - bounds = bounds.UnionTile(tile); - } - - await SuspendIfOutOfTime(); - ValidateResume(); - - biomeSystem.GetMarkerNodes(gridUid, biomeComp, grid, markerTemplate, true, bounds, count, - random, out var spawnSet, out var existing, false); - - await SuspendIfOutOfTime(); - ValidateResume(); - - foreach (var ent in existing) - { - _entManager.DeleteEntity(ent); - } - - await SuspendIfOutOfTime(); - ValidateResume(); - - foreach (var (node, mask) in spawnSet) - { - string? proto; - - if (mask != null && markerTemplate.EntityMask.TryGetValue(mask, out var maskedProto)) - { - proto = maskedProto; - } - else - { - proto = markerTemplate.Prototype; - } - - var ent = _entManager.SpawnAtPosition(proto, new EntityCoordinates(gridUid, node + grid.TileSizeHalfVector)); - var xform = xformQuery.Get(ent); - - if (!xform.Comp.Anchored) - _transform.AnchorEntity(ent, xform); - - await SuspendIfOutOfTime(); - ValidateResume(); - } - } - } -} diff --git a/Content.Server/Procedural/DungeonJob.cs b/Content.Server/Procedural/DungeonJob.cs deleted file mode 100644 index bf2822ff42..0000000000 --- a/Content.Server/Procedural/DungeonJob.cs +++ /dev/null @@ -1,192 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using Content.Server.Construction; -using Robust.Shared.CPUJob.JobQueues; -using Content.Server.Decals; -using Content.Shared.Construction.EntitySystems; -using Content.Shared.Maps; -using Content.Shared.Procedural; -using Content.Shared.Procedural.DungeonGenerators; -using Content.Shared.Procedural.PostGeneration; -using Content.Shared.Tag; -using Robust.Server.Physics; -using Robust.Shared.Map; -using Robust.Shared.Map.Components; -using Robust.Shared.Prototypes; -using Robust.Shared.Utility; - -namespace Content.Server.Procedural; - -public sealed partial class DungeonJob : Job -{ - private readonly IEntityManager _entManager; - private readonly IMapManager _mapManager; - private readonly IPrototypeManager _prototype; - private readonly ITileDefinitionManager _tileDefManager; - - private readonly AnchorableSystem _anchorable; - private readonly DecalSystem _decals; - private readonly DungeonSystem _dungeon; - private readonly EntityLookupSystem _lookup; - private readonly TagSystem _tag; - private readonly TileSystem _tile; - private readonly SharedMapSystem _maps; - private readonly SharedTransformSystem _transform; - - private readonly DungeonConfigPrototype _gen; - private readonly int _seed; - private readonly Vector2i _position; - - private readonly MapGridComponent _grid; - private readonly EntityUid _gridUid; - - private readonly ISawmill _sawmill; - - public DungeonJob( - ISawmill sawmill, - double maxTime, - IEntityManager entManager, - IMapManager mapManager, - IPrototypeManager prototype, - ITileDefinitionManager tileDefManager, - AnchorableSystem anchorable, - DecalSystem decals, - DungeonSystem dungeon, - EntityLookupSystem lookup, - TagSystem tag, - TileSystem tile, - SharedTransformSystem transform, - DungeonConfigPrototype gen, - MapGridComponent grid, - EntityUid gridUid, - int seed, - Vector2i position, - CancellationToken cancellation = default) : base(maxTime, cancellation) - { - _sawmill = sawmill; - _entManager = entManager; - _mapManager = mapManager; - _prototype = prototype; - _tileDefManager = tileDefManager; - - _anchorable = anchorable; - _decals = decals; - _dungeon = dungeon; - _lookup = lookup; - _tag = tag; - _tile = tile; - _maps = _entManager.System(); - _transform = transform; - - _gen = gen; - _grid = grid; - _gridUid = gridUid; - _seed = seed; - _position = position; - } - - protected override async Task Process() - { - Dungeon dungeon; - _sawmill.Info($"Generating dungeon {_gen.ID} with seed {_seed} on {_entManager.ToPrettyString(_gridUid)}"); - _grid.CanSplit = false; - - switch (_gen.Generator) - { - case NoiseDunGen noise: - dungeon = await GenerateNoiseDungeon(noise, _gridUid, _grid, _seed); - break; - case PrefabDunGen prefab: - dungeon = await GeneratePrefabDungeon(prefab, _gridUid, _grid, _seed); - DebugTools.Assert(dungeon.RoomExteriorTiles.Count > 0); - break; - default: - throw new NotImplementedException(); - } - - DebugTools.Assert(dungeon.RoomTiles.Count > 0); - - // To make it slightly more deterministic treat this RNG as separate ig. - var random = new Random(_seed); - - foreach (var post in _gen.PostGeneration) - { - _sawmill.Debug($"Doing postgen {post.GetType()} for {_gen.ID} with seed {_seed}"); - - switch (post) - { - case AutoCablingPostGen cabling: - await PostGen(cabling, dungeon, _gridUid, _grid, random); - break; - case BiomePostGen biome: - await PostGen(biome, dungeon, _gridUid, _grid, random); - break; - case BoundaryWallPostGen boundary: - await PostGen(boundary, dungeon, _gridUid, _grid, random); - break; - case CornerClutterPostGen clutter: - await PostGen(clutter, dungeon, _gridUid, _grid, random); - break; - case CorridorClutterPostGen corClutter: - await PostGen(corClutter, dungeon, _gridUid, _grid, random); - break; - case CorridorPostGen cordor: - await PostGen(cordor, dungeon, _gridUid, _grid, random); - break; - case CorridorDecalSkirtingPostGen decks: - await PostGen(decks, dungeon, _gridUid, _grid, random); - break; - case EntranceFlankPostGen flank: - await PostGen(flank, dungeon, _gridUid, _grid, random); - break; - case JunctionPostGen junc: - await PostGen(junc, dungeon, _gridUid, _grid, random); - break; - case MiddleConnectionPostGen dordor: - await PostGen(dordor, dungeon, _gridUid, _grid, random); - break; - case DungeonEntrancePostGen entrance: - await PostGen(entrance, dungeon, _gridUid, _grid, random); - break; - case ExternalWindowPostGen externalWindow: - await PostGen(externalWindow, dungeon, _gridUid, _grid, random); - break; - case InternalWindowPostGen internalWindow: - await PostGen(internalWindow, dungeon, _gridUid, _grid, random); - break; - case BiomeMarkerLayerPostGen markerPost: - await PostGen(markerPost, dungeon, _gridUid, _grid, random); - break; - case RoomEntrancePostGen rEntrance: - await PostGen(rEntrance, dungeon, _gridUid, _grid, random); - break; - case WallMountPostGen wall: - await PostGen(wall, dungeon, _gridUid, _grid, random); - break; - case WormCorridorPostGen worm: - await PostGen(worm, dungeon, _gridUid, _grid, random); - break; - default: - throw new NotImplementedException(); - } - - await SuspendIfOutOfTime(); - - if (!ValidateResume()) - break; - } - - // Defer splitting so they don't get spammed and so we don't have to worry about tracking the grid along the way. - _grid.CanSplit = true; - _entManager.System().CheckSplits(_gridUid); - return dungeon; - } - - private bool ValidateResume() - { - if (_entManager.Deleted(_gridUid)) - return false; - - return true; - } -} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenExterior.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenExterior.cs new file mode 100644 index 0000000000..acffd057fa --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenExterior.cs @@ -0,0 +1,58 @@ +using System.Threading.Tasks; +using Content.Server.NPC.Pathfinding; +using Content.Shared.Maps; +using Content.Shared.Procedural; +using Content.Shared.Procedural.DungeonGenerators; +using Robust.Shared.Collections; +using Robust.Shared.Random; +using Robust.Shared.Utility; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /// + /// + /// + private async Task> GenerateExteriorDungen(Vector2i position, ExteriorDunGen dungen, HashSet reservedTiles, Random random) + { + DebugTools.Assert(_grid.ChunkCount > 0); + + var aabb = new Box2i(_grid.LocalAABB.BottomLeft.Floored(), _grid.LocalAABB.TopRight.Floored()); + var angle = random.NextAngle(); + + var distance = Math.Max(aabb.Width / 2f + 1f, aabb.Height / 2f + 1f); + + var startTile = new Vector2i(0, (int) distance).Rotate(angle); + + Vector2i? dungeonSpawn = null; + var pathfinder = _entManager.System(); + + // Gridcast + pathfinder.GridCast(startTile, position, tile => + { + if (!_maps.TryGetTileRef(_gridUid, _grid, tile, out var tileRef) || + tileRef.Tile.IsSpace(_tileDefManager)) + { + return true; + } + + dungeonSpawn = tile; + return false; + }); + + if (dungeonSpawn == null) + { + return new List() + { + Dungeon.Empty + }; + } + + var config = _prototype.Index(dungen.Proto); + var nextSeed = random.Next(); + var dungeons = await GetDungeons(dungeonSpawn.Value, config, config.Data, config.Layers, reservedTiles, nextSeed, new Random(nextSeed)); + + return dungeons; + } +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenFill.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenFill.cs new file mode 100644 index 0000000000..5a0d77c615 --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenFill.cs @@ -0,0 +1,50 @@ +using System.Numerics; +using System.Threading.Tasks; +using Content.Shared.Procedural; +using Content.Shared.Procedural.DungeonGenerators; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /// + /// + /// + private async Task GenerateFillDunGen(DungeonData data, HashSet reservedTiles) + { + if (!data.Entities.TryGetValue(DungeonDataKey.Fill, out var fillEnt)) + { + LogDataError(typeof(FillGridDunGen)); + return Dungeon.Empty; + } + + var roomTiles = new HashSet(); + var tiles = _maps.GetAllTilesEnumerator(_gridUid, _grid); + + while (tiles.MoveNext(out var tileRef)) + { + var tile = tileRef.Value.GridIndices; + + if (reservedTiles.Contains(tile)) + continue; + + if (!_anchorable.TileFree(_grid, tile, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) + continue; + + var gridPos = _maps.GridTileToLocal(_gridUid, _grid, tile); + _entManager.SpawnEntity(fillEnt, gridPos); + + roomTiles.Add(tile); + + await SuspendDungeon(); + if (!ValidateResume()) + break; + } + + var dungeon = new Dungeon(); + var room = new DungeonRoom(roomTiles, Vector2.Zero, Box2i.Empty, new HashSet()); + dungeon.AddRoom(room); + + return dungeon; + } +} diff --git a/Content.Server/Procedural/DungeonJob.NoiseDunGen.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenNoise.cs similarity index 73% rename from Content.Server/Procedural/DungeonJob.NoiseDunGen.cs rename to Content.Server/Procedural/DungeonJob/DungeonJob.DunGenNoise.cs index 73c3386ead..b2526ec17d 100644 --- a/Content.Server/Procedural/DungeonJob.NoiseDunGen.cs +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenNoise.cs @@ -4,19 +4,25 @@ using Content.Shared.Maps; using Content.Shared.Procedural; using Content.Shared.Procedural.DungeonGenerators; using Robust.Shared.Map; -using Robust.Shared.Map.Components; using Robust.Shared.Random; using Robust.Shared.Utility; -namespace Content.Server.Procedural; +namespace Content.Server.Procedural.DungeonJob; public sealed partial class DungeonJob { - private async Task GenerateNoiseDungeon(NoiseDunGen dungen, EntityUid gridUid, MapGridComponent grid, - int seed) + /// + /// + /// + private async Task GenerateNoiseDunGen( + Vector2i position, + NoiseDunGen dungen, + HashSet reservedTiles, + int seed, + Random random) { - var rand = new Random(seed); var tiles = new List<(Vector2i, Tile)>(); + var matrix = Matrix3Helpers.CreateTranslation(position); foreach (var layer in dungen.Layers) { @@ -30,7 +36,7 @@ public sealed partial class DungeonJob var frontier = new Queue(); var rooms = new List(); var tileCount = 0; - var tileCap = rand.NextGaussian(dungen.TileCap, dungen.CapStd); + var tileCap = random.NextGaussian(dungen.TileCap, dungen.CapStd); var visited = new HashSet(); while (iterations > 0 && tileCount < tileCap) @@ -39,22 +45,22 @@ public sealed partial class DungeonJob iterations--; // Get a random exterior tile to start floodfilling from. - var edge = rand.Next(4); + var edge = random.Next(4); Vector2i seedTile; switch (edge) { case 0: - seedTile = new Vector2i(rand.Next(area.Left - 2, area.Right + 1), area.Bottom - 2); + seedTile = new Vector2i(random.Next(area.Left - 2, area.Right + 1), area.Bottom - 2); break; case 1: - seedTile = new Vector2i(area.Right + 1, rand.Next(area.Bottom - 2, area.Top + 1)); + seedTile = new Vector2i(area.Right + 1, random.Next(area.Bottom - 2, area.Top + 1)); break; case 2: - seedTile = new Vector2i(rand.Next(area.Left - 2, area.Right + 1), area.Top + 1); + seedTile = new Vector2i(random.Next(area.Left - 2, area.Right + 1), area.Top + 1); break; case 3: - seedTile = new Vector2i(area.Left - 2, rand.Next(area.Bottom - 2, area.Top + 1)); + seedTile = new Vector2i(area.Left - 2, random.Next(area.Bottom - 2, area.Top + 1)); break; default: throw new ArgumentOutOfRangeException(); @@ -80,14 +86,20 @@ public sealed partial class DungeonJob if (value < layer.Threshold) continue; - roomArea = roomArea.UnionTile(node); foundNoise = true; noiseFill = true; - var tileDef = _tileDefManager[layer.Tile]; - var variant = _tile.PickVariant((ContentTileDefinition) tileDef, rand); - tiles.Add((node, new Tile(tileDef.TileId, variant: variant))); - roomTiles.Add(node); + // Still want the tile to gen as normal but can't do anything with it. + if (reservedTiles.Contains(node)) + break; + + roomArea = roomArea.UnionTile(node); + var tileDef = _tileDefManager[layer.Tile]; + var variant = _tile.PickVariant((ContentTileDefinition) tileDef, random); + var adjusted = Vector2.Transform(node + _grid.TileSizeHalfVector, matrix).Floored(); + + tiles.Add((adjusted, new Tile(tileDef.TileId, variant: variant))); + roomTiles.Add(adjusted); tileCount++; break; } @@ -123,7 +135,7 @@ public sealed partial class DungeonJob foreach (var tile in roomTiles) { - center += tile + grid.TileSizeHalfVector; + center += tile + _grid.TileSizeHalfVector; } center /= roomTiles.Count; @@ -132,15 +144,8 @@ public sealed partial class DungeonJob ValidateResume(); } - grid.SetTiles(tiles); - + _maps.SetTiles(_gridUid, _grid, tiles); var dungeon = new Dungeon(rooms); - - foreach (var tile in tiles) - { - dungeon.RoomTiles.Add(tile.Item1); - } - return dungeon; } } diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenNoiseDistance.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenNoiseDistance.cs new file mode 100644 index 0000000000..f1808ec90c --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenNoiseDistance.cs @@ -0,0 +1,112 @@ +using System.Numerics; +using System.Threading.Tasks; +using Content.Shared.Maps; +using Content.Shared.Procedural; +using Content.Shared.Procedural.Distance; +using Content.Shared.Procedural.DungeonGenerators; +using Robust.Shared.Map; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /* + * See https://www.redblobgames.com/maps/terrain-from-noise/#islands + * Really it's just blending from the original noise (which may occupy the entire area) + * with some other shape to confine it into a bounds more naturally. + * https://old.reddit.com/r/proceduralgeneration/comments/kaen7h/new_video_on_procedural_island_noise_generation/gfjmgen/ also has more variations + */ + + /// + /// + /// + private async Task GenerateNoiseDistanceDunGen( + Vector2i position, + NoiseDistanceDunGen dungen, + HashSet reservedTiles, + int seed, + Random random) + { + var tiles = new List<(Vector2i, Tile)>(); + var matrix = Matrix3Helpers.CreateTranslation(position); + + foreach (var layer in dungen.Layers) + { + layer.Noise.SetSeed(seed); + } + + // First we have to find a seed tile, then floodfill from there until we get to noise + // at which point we floodfill the entire noise. + var area = Box2i.FromDimensions(-dungen.Size / 2, dungen.Size); + var roomTiles = new HashSet(); + var width = (float) area.Width; + var height = (float) area.Height; + + for (var x = area.Left; x <= area.Right; x++) + { + for (var y = area.Bottom; y <= area.Top; y++) + { + var node = new Vector2i(x, y); + + foreach (var layer in dungen.Layers) + { + var value = layer.Noise.GetNoise(node.X, node.Y); + + if (dungen.DistanceConfig != null) + { + // Need to get dx - dx in a range from -1 -> 1 + var dx = 2 * x / width; + var dy = 2 * y / height; + + var distance = GetDistance(dx, dy, dungen.DistanceConfig); + + value = MathHelper.Lerp(value, 1f - distance, dungen.DistanceConfig.BlendWeight); + } + + if (value < layer.Threshold) + continue; + + var tileDef = _tileDefManager[layer.Tile]; + var variant = _tile.PickVariant((ContentTileDefinition) tileDef, random); + var adjusted = Vector2.Transform(node + _grid.TileSizeHalfVector, matrix).Floored(); + + // Do this down here because noise has a much higher chance of failing than reserved tiles. + if (reservedTiles.Contains(adjusted)) + { + break; + } + + tiles.Add((adjusted, new Tile(tileDef.TileId, variant: variant))); + roomTiles.Add(adjusted); + break; + } + } + + await SuspendDungeon(); + } + + var room = new DungeonRoom(roomTiles, area.Center, area, new HashSet()); + + _maps.SetTiles(_gridUid, _grid, tiles); + var dungeon = new Dungeon(new List() + { + room, + }); + + await SuspendDungeon(); + return dungeon; + } + + private float GetDistance(float dx, float dy, IDunGenDistance distance) + { + switch (distance) + { + case DunGenEuclideanSquaredDistance: + return MathF.Min(1f, (dx * dx + dy * dy) / MathF.Sqrt(2)); + case DunGenSquareBump: + return 1f - (1f - dx * dx) * (1f - dy * dy); + default: + throw new ArgumentOutOfRangeException(); + } + } +} diff --git a/Content.Server/Procedural/DungeonJob.PrefabDunGen.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenPrefab.cs similarity index 82% rename from Content.Server/Procedural/DungeonJob.PrefabDunGen.cs rename to Content.Server/Procedural/DungeonJob/DungeonJob.DunGenPrefab.cs index a19f7e4701..33bbeba4b5 100644 --- a/Content.Server/Procedural/DungeonJob.PrefabDunGen.cs +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenPrefab.cs @@ -1,25 +1,33 @@ using System.Numerics; using System.Threading.Tasks; -using Content.Shared.Decals; using Content.Shared.Procedural; using Content.Shared.Procedural.DungeonGenerators; +using Content.Shared.Whitelist; using Robust.Shared.Map; -using Robust.Shared.Map.Components; using Robust.Shared.Random; using Robust.Shared.Utility; -namespace Content.Server.Procedural; +namespace Content.Server.Procedural.DungeonJob; public sealed partial class DungeonJob { - private async Task GeneratePrefabDungeon(PrefabDunGen prefab, EntityUid gridUid, MapGridComponent grid, int seed) + /// + /// + /// + private async Task GeneratePrefabDunGen(Vector2i position, DungeonData data, PrefabDunGen prefab, HashSet reservedTiles, Random random) { - var random = new Random(seed); - var preset = prefab.Presets[random.Next(prefab.Presets.Count)]; - var gen = _prototype.Index(preset); + if (!data.Tiles.TryGetValue(DungeonDataKey.FallbackTile, out var tileProto) || + !data.Whitelists.TryGetValue(DungeonDataKey.Rooms, out var roomWhitelist)) + { + LogDataError(typeof(PrefabDunGen)); + return Dungeon.Empty; + } - var dungeonRotation = _dungeon.GetDungeonRotation(seed); - var dungeonTransform = Matrix3Helpers.CreateTransform(_position, dungeonRotation); + var preset = prefab.Presets[random.Next(prefab.Presets.Count)]; + var gen = _prototype.Index(preset); + + var dungeonRotation = _dungeon.GetDungeonRotation(random.Next()); + var dungeonTransform = Matrix3Helpers.CreateTransform(position, dungeonRotation); var roomPackProtos = new Dictionary>(); foreach (var pack in _prototype.EnumeratePrototypes()) @@ -42,12 +50,15 @@ public sealed partial class DungeonJob { var whitelisted = false; - foreach (var tag in prefab.RoomWhitelist) + if (roomWhitelist?.Tags != null) { - if (proto.Tags.Contains(tag)) + foreach (var tag in roomWhitelist.Tags) { - whitelisted = true; - break; + if (proto.Tags.Contains(tag)) + { + whitelisted = true; + break; + } } } @@ -182,12 +193,16 @@ public sealed partial class DungeonJob { for (var y = roomSize.Bottom; y < roomSize.Top; y++) { - var index = Vector2.Transform(new Vector2(x, y) + grid.TileSizeHalfVector - packCenter, matty).Floored(); - tiles.Add((index, new Tile(_tileDefManager["FloorPlanetGrass"].TileId))); + var index = Vector2.Transform(new Vector2(x, y) + _grid.TileSizeHalfVector - packCenter, matty).Floored(); + + if (reservedTiles.Contains(index)) + continue; + + tiles.Add((index, new Tile(_tileDefManager[tileProto].TileId))); } } - grid.SetTiles(tiles); + _maps.SetTiles(_gridUid, _grid, tiles); tiles.Clear(); _sawmill.Error($"Unable to find room variant for {roomDimensions}, leaving empty."); continue; @@ -215,12 +230,12 @@ public sealed partial class DungeonJob var dungeonMatty = Matrix3x2.Multiply(matty, dungeonTransform); // The expensive bit yippy. - _dungeon.SpawnRoom(gridUid, grid, dungeonMatty, room); + _dungeon.SpawnRoom(_gridUid, _grid, dungeonMatty, room, reservedTiles); - var roomCenter = (room.Offset + room.Size / 2f) * grid.TileSize; + var roomCenter = (room.Offset + room.Size / 2f) * _grid.TileSize; var roomTiles = new HashSet(room.Size.X * room.Size.Y); var exterior = new HashSet(room.Size.X * 2 + room.Size.Y * 2); - var tileOffset = -roomCenter + grid.TileSizeHalfVector; + var tileOffset = -roomCenter + _grid.TileSizeHalfVector; Box2i? mapBounds = null; for (var x = -1; x <= room.Size.X; x++) @@ -232,8 +247,12 @@ public sealed partial class DungeonJob continue; } - var tilePos = Vector2.Transform(new Vector2i(x + room.Offset.X, y + room.Offset.Y) + tileOffset, dungeonMatty); - exterior.Add(tilePos.Floored()); + var tilePos = Vector2.Transform(new Vector2i(x + room.Offset.X, y + room.Offset.Y) + tileOffset, dungeonMatty).Floored(); + + if (reservedTiles.Contains(tilePos)) + continue; + + exterior.Add(tilePos); } } @@ -249,16 +268,18 @@ public sealed partial class DungeonJob roomTiles.Add(tileIndex); mapBounds = mapBounds?.Union(tileIndex) ?? new Box2i(tileIndex, tileIndex); - center += tilePos + grid.TileSizeHalfVector; + center += tilePos + _grid.TileSizeHalfVector; } } center /= roomTiles.Count; - dungeon.Rooms.Add(new DungeonRoom(roomTiles, center, mapBounds!.Value, exterior)); + dungeon.AddRoom(new DungeonRoom(roomTiles, center, mapBounds!.Value, exterior)); - await SuspendIfOutOfTime(); - ValidateResume(); + await SuspendDungeon(); + + if (!ValidateResume()) + return Dungeon.Empty; } } @@ -267,20 +288,16 @@ public sealed partial class DungeonJob foreach (var room in dungeon.Rooms) { - dungeon.RoomTiles.UnionWith(room.Tiles); - dungeon.RoomExteriorTiles.UnionWith(room.Exterior); + dungeonCenter += room.Center; + SetDungeonEntrance(dungeon, room, reservedTiles, random); } - foreach (var room in dungeon.Rooms) - { - dungeonCenter += room.Center; - SetDungeonEntrance(dungeon, room, random); - } + dungeon.Rebuild(); return dungeon; } - private void SetDungeonEntrance(Dungeon dungeon, DungeonRoom room, Random random) + private void SetDungeonEntrance(Dungeon dungeon, DungeonRoom room, HashSet reservedTiles, Random random) { // TODO: Move to dungeonsystem. @@ -323,8 +340,10 @@ public sealed partial class DungeonJob continue; } + if (reservedTiles.Contains(entrancePos)) + continue; + room.Entrances.Add(entrancePos); - dungeon.Entrances.Add(entrancePos); break; } } diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenReplaceTile.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenReplaceTile.cs new file mode 100644 index 0000000000..6b36d10109 --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenReplaceTile.cs @@ -0,0 +1,60 @@ +using System.Threading.Tasks; +using Content.Shared.Procedural; +using Content.Shared.Procedural.DungeonGenerators; +using Content.Shared.Procedural.PostGeneration; +using Robust.Shared.Map; +using Robust.Shared.Random; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /// + /// + /// + private async Task GenerateTileReplacementDunGen(ReplaceTileDunGen gen, DungeonData data, HashSet reservedTiles, Random random) + { + var tiles = _maps.GetAllTilesEnumerator(_gridUid, _grid); + var replacements = new List<(Vector2i Index, Tile Tile)>(); + var reserved = new HashSet(); + + while (tiles.MoveNext(out var tileRef)) + { + var node = tileRef.Value.GridIndices; + + if (reservedTiles.Contains(node)) + continue; + + foreach (var layer in gen.Layers) + { + var value = layer.Noise.GetNoise(node.X, node.Y); + + if (value < layer.Threshold) + continue; + + Tile tile; + + if (random.Prob(gen.VariantWeight)) + { + tile = _tileDefManager.GetVariantTile(_prototype.Index(layer.Tile), random); + } + else + { + tile = new Tile(_prototype.Index(layer.Tile).TileId); + } + + replacements.Add((node, tile)); + reserved.Add(node); + break; + } + + await SuspendDungeon(); + } + + _maps.SetTiles(_gridUid, _grid, replacements); + return new Dungeon(new List() + { + new DungeonRoom(reserved, _position, Box2i.Empty, new HashSet()), + }); + } +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.MobDunGen.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.MobDunGen.cs new file mode 100644 index 0000000000..150849d2c5 --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.MobDunGen.cs @@ -0,0 +1,58 @@ +using System.Threading.Tasks; +using Content.Server.Ghost.Roles.Components; +using Content.Server.NPC.Components; +using Content.Server.NPC.Systems; +using Content.Shared.Physics; +using Content.Shared.Procedural; +using Content.Shared.Procedural.DungeonLayers; +using Content.Shared.Storage; +using Robust.Shared.Collections; +using Robust.Shared.Random; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + private async Task PostGen( + MobsDunGen gen, + Dungeon dungeon, + Random random) + { + var availableRooms = new ValueList(); + availableRooms.AddRange(dungeon.Rooms); + var availableTiles = new ValueList(dungeon.AllTiles); + + var entities = EntitySpawnCollection.GetSpawns(gen.Groups, random); + var count = random.Next(gen.MinCount, gen.MaxCount + 1); + var npcs = _entManager.System(); + + for (var i = 0; i < count; i++) + { + while (availableTiles.Count > 0) + { + var tile = availableTiles.RemoveSwap(random.Next(availableTiles.Count)); + + if (!_anchorable.TileFree(_grid, tile, (int) CollisionGroup.MachineLayer, + (int) CollisionGroup.MachineLayer)) + { + continue; + } + + foreach (var ent in entities) + { + var uid = _entManager.SpawnAtPosition(ent, _maps.GridTileToLocal(_gridUid, _grid, tile)); + _entManager.RemoveComponent(uid); + _entManager.RemoveComponent(uid); + npcs.SleepNPC(uid); + } + + break; + } + + await SuspendDungeon(); + + if (!ValidateResume()) + return; + } + } +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.OreDunGen.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.OreDunGen.cs new file mode 100644 index 0000000000..e89c1d7e47 --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.OreDunGen.cs @@ -0,0 +1,149 @@ +using System.Threading.Tasks; +using Content.Shared.Procedural; +using Content.Shared.Procedural.Components; +using Content.Shared.Procedural.DungeonLayers; +using Robust.Shared.Collections; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /// + /// + /// + private async Task PostGen( + OreDunGen gen, + Dungeon dungeon, + Random random) + { + // Doesn't use dungeon data because layers and we don't need top-down support at the moment. + + var emptyTiles = false; + var replaceEntities = new Dictionary(); + var availableTiles = new List(); + + foreach (var node in dungeon.AllTiles) + { + // Empty tile, skip if relevant. + if (!emptyTiles && (!_maps.TryGetTile(_grid, node, out var tile) || tile.IsEmpty)) + continue; + + // Check if it's a valid spawn, if so then use it. + var enumerator = _maps.GetAnchoredEntitiesEnumerator(_gridUid, _grid, node); + var found = false; + + // We use existing entities as a mark to spawn in place + // OR + // We check for any existing entities to see if we can spawn there. + while (enumerator.MoveNext(out var uid)) + { + // We can't replace so just stop here. + if (gen.Replacement == null) + break; + + var prototype = _entManager.GetComponent(uid.Value).EntityPrototype; + + if (prototype?.ID == gen.Replacement) + { + replaceEntities[node] = uid.Value; + found = true; + break; + } + } + + if (!found) + continue; + + // Add it to valid nodes. + availableTiles.Add(node); + + await SuspendDungeon(); + + if (!ValidateResume()) + return; + } + + var remapping = new Dictionary(); + + // TODO: Move this to engine + if (_prototype.TryIndex(gen.Entity, out var proto) && + proto.Components.TryGetComponent("EntityRemap", out var comps)) + { + var remappingComp = (EntityRemapComponent) comps; + remapping = remappingComp.Mask; + } + + var frontier = new ValueList(32); + + // Iterate the group counts and pathfind out each group. + for (var i = 0; i < gen.Count; i++) + { + await SuspendDungeon(); + + if (!ValidateResume()) + return; + + var groupSize = random.Next(gen.MinGroupSize, gen.MaxGroupSize + 1); + + // While we have remaining tiles keep iterating + while (groupSize >= 0 && availableTiles.Count > 0) + { + var startNode = random.PickAndTake(availableTiles); + frontier.Clear(); + frontier.Add(startNode); + + // This essentially may lead to a vein being split in multiple areas but the count matters more than position. + while (frontier.Count > 0 && groupSize >= 0) + { + // Need to pick a random index so we don't just get straight lines of ores. + var frontierIndex = random.Next(frontier.Count); + var node = frontier[frontierIndex]; + frontier.RemoveSwap(frontierIndex); + availableTiles.Remove(node); + + // Add neighbors if they're valid, worst case we add no more and pick another random seed tile. + for (var x = -1; x <= 1; x++) + { + for (var y = -1; y <= 1; y++) + { + if (x != 0 && y != 0) + continue; + + var neighbor = new Vector2i(node.X + x, node.Y + y); + + if (frontier.Contains(neighbor) || !availableTiles.Contains(neighbor)) + continue; + + frontier.Add(neighbor); + } + } + + var prototype = gen.Entity; + + if (replaceEntities.TryGetValue(node, out var existingEnt)) + { + var existingProto = _entManager.GetComponent(existingEnt).EntityPrototype; + _entManager.DeleteEntity(existingEnt); + + if (existingProto != null && remapping.TryGetValue(existingProto.ID, out var remapped)) + { + prototype = remapped; + } + } + + // Tile valid salad so add it. + _entManager.SpawnAtPosition(prototype, _maps.GridTileToLocal(_gridUid, _grid, node)); + + groupSize--; + } + } + + if (groupSize > 0) + { + _sawmill.Warning($"Found remaining group size for ore veins!"); + } + } + } +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.PostGen.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGen.cs new file mode 100644 index 0000000000..b1c83346d8 --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGen.cs @@ -0,0 +1,134 @@ +using System.Numerics; +using Content.Shared.Procedural; +using Robust.Shared.Collections; +using Robust.Shared.Map.Components; +using Robust.Shared.Physics.Components; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /* + * Run after the main dungeon generation + */ + + private bool HasWall(Vector2i tile) + { + var anchored = _maps.GetAnchoredEntitiesEnumerator(_gridUid, _grid, tile); + + while (anchored.MoveNext(out var uid)) + { + if (_tags.HasTag(uid.Value, "Wall")) + return true; + } + + return false; + } + + private void BuildCorridorExterior(Dungeon dungeon) + { + var exterior = dungeon.CorridorExteriorTiles; + + // Just ignore entrances or whatever for now. + foreach (var tile in dungeon.CorridorTiles) + { + for (var x = -1; x <= 1; x++) + { + for (var y = -1; y <= 1; y++) + { + var neighbor = new Vector2i(tile.X + x, tile.Y + y); + + if (dungeon.CorridorTiles.Contains(neighbor) || + dungeon.RoomExteriorTiles.Contains(neighbor) || + dungeon.RoomTiles.Contains(neighbor) || + dungeon.Entrances.Contains(neighbor)) + { + continue; + } + + exterior.Add(neighbor); + } + } + } + } + + private void WidenCorridor(Dungeon dungeon, float width, ICollection corridorTiles) + { + var expansion = width - 2; + + // Widen the path + if (expansion >= 1) + { + var toAdd = new ValueList(); + + foreach (var node in corridorTiles) + { + // Uhhh not sure on the cleanest way to do this but tl;dr we don't want to hug + // exterior walls and make the path smaller. + + for (var x = -expansion; x <= expansion; x++) + { + for (var y = -expansion; y <= expansion; y++) + { + var neighbor = new Vector2(node.X + x, node.Y + y).Floored(); + + // Diagonals still matter here. + if (dungeon.RoomTiles.Contains(neighbor) || + dungeon.RoomExteriorTiles.Contains(neighbor)) + { + // Try + + continue; + } + + toAdd.Add(neighbor); + } + } + } + + foreach (var node in toAdd) + { + corridorTiles.Add(node); + } + } + } + + /// + /// Removes any unwanted obstacles around a door tile. + /// + private void ClearDoor(Dungeon dungeon, MapGridComponent grid, Vector2i indices, bool strict = false) + { + var flags = strict + ? LookupFlags.Dynamic | LookupFlags.Static | LookupFlags.StaticSundries + : LookupFlags.Dynamic | LookupFlags.Static; + + for (var x = -1; x <= 1; x++) + { + for (var y = -1; y <= 1; y++) + { + if (x != 0 && y != 0) + continue; + + var neighbor = new Vector2i(indices.X + x, indices.Y + y); + + if (!dungeon.RoomTiles.Contains(neighbor)) + continue; + + // Shrink by 0.01 to avoid polygon overlap from neighboring tiles. + // TODO: Uhh entityset re-usage. + foreach (var ent in _lookup.GetEntitiesIntersecting(_gridUid, new Box2(neighbor * grid.TileSize, (neighbor + 1) * grid.TileSize).Enlarged(-0.1f), flags)) + { + if (!_physicsQuery.TryGetComponent(ent, out var physics) || + !physics.Hard || + (DungeonSystem.CollisionMask & physics.CollisionLayer) == 0x0 && + (DungeonSystem.CollisionLayer & physics.CollisionMask) == 0x0) + { + continue; + } + + _entManager.DeleteEntity(ent); + } + } + } + } +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenAutoCabling.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenAutoCabling.cs new file mode 100644 index 0000000000..aaea23ddd5 --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenAutoCabling.cs @@ -0,0 +1,162 @@ +using System.Linq; +using System.Threading.Tasks; +using Content.Server.NodeContainer; +using Content.Shared.Procedural; +using Content.Shared.Procedural.PostGeneration; +using Robust.Shared.Random; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /// + /// + /// + private async Task PostGen(AutoCablingDunGen gen, DungeonData data, Dungeon dungeon, HashSet reservedTiles, Random random) + { + if (!data.Entities.TryGetValue(DungeonDataKey.Cabling, out var ent)) + { + LogDataError(typeof(AutoCablingDunGen)); + return; + } + + // There's a lot of ways you could do this. + // For now we'll just connect every LV cable in the dungeon. + var cableTiles = new HashSet(); + var allTiles = new HashSet(dungeon.CorridorTiles); + allTiles.UnionWith(dungeon.RoomTiles); + allTiles.UnionWith(dungeon.RoomExteriorTiles); + allTiles.UnionWith(dungeon.CorridorExteriorTiles); + var nodeQuery = _entManager.GetEntityQuery(); + + // Gather existing nodes + foreach (var tile in allTiles) + { + var anchored = _maps.GetAnchoredEntitiesEnumerator(_gridUid, _grid, tile); + + while (anchored.MoveNext(out var anc)) + { + if (!nodeQuery.TryGetComponent(anc, out var nodeContainer) || + !nodeContainer.Nodes.ContainsKey("power")) + { + continue; + } + + cableTiles.Add(tile); + break; + } + } + + // Iterating them all might be expensive. + await SuspendDungeon(); + + if (!ValidateResume()) + return; + + var startNodes = new List(cableTiles); + random.Shuffle(startNodes); + var start = startNodes[0]; + var remaining = new HashSet(startNodes); + var frontier = new PriorityQueue(); + frontier.Enqueue(start, 0f); + var cameFrom = new Dictionary(); + var costSoFar = new Dictionary(); + var lastDirection = new Dictionary(); + costSoFar[start] = 0f; + lastDirection[start] = Direction.Invalid; + + while (remaining.Count > 0) + { + if (frontier.Count == 0) + { + var newStart = remaining.First(); + frontier.Enqueue(newStart, 0f); + lastDirection[newStart] = Direction.Invalid; + } + + var node = frontier.Dequeue(); + + if (remaining.Remove(node)) + { + var weh = node; + + while (cameFrom.TryGetValue(weh, out var receiver)) + { + cableTiles.Add(weh); + weh = receiver; + + if (weh == start) + break; + } + } + + if (!_maps.TryGetTileRef(_gridUid, _grid, node, out var tileRef) || tileRef.Tile.IsEmpty) + { + continue; + } + + for (var i = 0; i < 4; i++) + { + var dir = (Direction) (i * 2); + + var neighbor = node + dir.ToIntVec(); + var tileCost = 1f; + + // Prefer straight lines. + if (lastDirection[node] != dir) + { + tileCost *= 1.1f; + } + + if (cableTiles.Contains(neighbor)) + { + tileCost *= 0.1f; + } + + // Prefer tiles without walls on them + if (HasWall(neighbor)) + { + tileCost *= 20f; + } + + var gScore = costSoFar[node] + tileCost; + + if (costSoFar.TryGetValue(neighbor, out var nextValue) && gScore >= nextValue) + { + continue; + } + + cameFrom[neighbor] = node; + costSoFar[neighbor] = gScore; + lastDirection[neighbor] = dir; + frontier.Enqueue(neighbor, gScore); + } + } + + foreach (var tile in cableTiles) + { + if (reservedTiles.Contains(tile)) + continue; + + var anchored = _maps.GetAnchoredEntitiesEnumerator(_gridUid, _grid, tile); + var found = false; + + while (anchored.MoveNext(out var anc)) + { + if (!nodeQuery.TryGetComponent(anc, out var nodeContainer) || + !nodeContainer.Nodes.ContainsKey("power")) + { + continue; + } + + found = true; + break; + } + + if (found) + continue; + + _entManager.SpawnEntity(ent, _maps.GridTileToLocal(_gridUid, _grid, tile)); + } + } +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenBiome.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenBiome.cs new file mode 100644 index 0000000000..65f6d2d14f --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenBiome.cs @@ -0,0 +1,67 @@ +using System.Threading.Tasks; +using Content.Server.Parallax; +using Content.Shared.Parallax.Biomes; +using Content.Shared.Procedural; +using Content.Shared.Procedural.PostGeneration; +using Robust.Shared.Map; +using Robust.Shared.Utility; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /// + /// + /// + private async Task PostGen(BiomeDunGen dunGen, DungeonData data, Dungeon dungeon, HashSet reservedTiles, Random random) + { + if (_entManager.TryGetComponent(_gridUid, out BiomeComponent? biomeComp)) + return; + + biomeComp = _entManager.AddComponent(_gridUid); + var biomeSystem = _entManager.System(); + biomeSystem.SetTemplate(_gridUid, biomeComp, _prototype.Index(dunGen.BiomeTemplate)); + var seed = random.Next(); + var xformQuery = _entManager.GetEntityQuery(); + + foreach (var node in dungeon.RoomTiles) + { + if (reservedTiles.Contains(node)) + continue; + + // Need to set per-tile to override data. + if (biomeSystem.TryGetTile(node, biomeComp.Layers, seed, _grid, out var tile)) + { + _maps.SetTile(_gridUid, _grid, node, tile.Value); + } + + if (biomeSystem.TryGetDecals(node, biomeComp.Layers, seed, _grid, out var decals)) + { + foreach (var decal in decals) + { + _decals.TryAddDecal(decal.ID, new EntityCoordinates(_gridUid, decal.Position), out _); + } + } + + if (biomeSystem.TryGetEntity(node, biomeComp, _grid, out var entityProto)) + { + var ent = _entManager.SpawnEntity(entityProto, new EntityCoordinates(_gridUid, node + _grid.TileSizeHalfVector)); + var xform = xformQuery.Get(ent); + + if (!xform.Comp.Anchored) + { + _transform.AnchorEntity(ent, xform); + } + + // TODO: Engine bug with SpawnAtPosition + DebugTools.Assert(xform.Comp.Anchored); + } + + await SuspendDungeon(); + if (!ValidateResume()) + return; + } + + biomeComp.Enabled = false; + } +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenBiomeMarkerLayer.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenBiomeMarkerLayer.cs new file mode 100644 index 0000000000..fb0eaa0157 --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenBiomeMarkerLayer.cs @@ -0,0 +1,105 @@ +using System.Threading.Tasks; +using Content.Server.Parallax; +using Content.Shared.Parallax.Biomes; +using Content.Shared.Parallax.Biomes.Markers; +using Content.Shared.Procedural; +using Content.Shared.Procedural.PostGeneration; +using Content.Shared.Random.Helpers; +using Robust.Shared.Map; +using Robust.Shared.Utility; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /// + /// + /// + private async Task PostGen(BiomeMarkerLayerDunGen dunGen, DungeonData data, Dungeon dungeon, HashSet reservedTiles, Random random) + { + // If we're adding biome then disable it and just use for markers. + if (_entManager.EnsureComponent(_gridUid, out BiomeComponent biomeComp)) + { + biomeComp.Enabled = false; + } + + var biomeSystem = _entManager.System(); + var weightedRandom = _prototype.Index(dunGen.MarkerTemplate); + var xformQuery = _entManager.GetEntityQuery(); + var templates = new Dictionary(); + + for (var i = 0; i < dunGen.Count; i++) + { + var template = weightedRandom.Pick(random); + var count = templates.GetOrNew(template); + count++; + templates[template] = count; + } + + foreach (var (template, count) in templates) + { + var markerTemplate = _prototype.Index(template); + + var bounds = new Box2i(); + + foreach (var tile in dungeon.RoomTiles) + { + bounds = bounds.UnionTile(tile); + } + + await SuspendDungeon(); + if (!ValidateResume()) + return; + + biomeSystem.GetMarkerNodes(_gridUid, biomeComp, _grid, markerTemplate, true, bounds, count, + random, out var spawnSet, out var existing, false); + + await SuspendDungeon(); + if (!ValidateResume()) + return; + + var checkTile = reservedTiles.Count > 0; + + foreach (var ent in existing) + { + if (checkTile && reservedTiles.Contains(_maps.LocalToTile(_gridUid, _grid, _xformQuery.GetComponent(ent).Coordinates))) + { + continue; + } + + _entManager.DeleteEntity(ent); + + await SuspendDungeon(); + if (!ValidateResume()) + return; + } + + foreach (var (node, mask) in spawnSet) + { + if (reservedTiles.Contains(node)) + continue; + + string? proto; + + if (mask != null && markerTemplate.EntityMask.TryGetValue(mask, out var maskedProto)) + { + proto = maskedProto; + } + else + { + proto = markerTemplate.Prototype; + } + + var ent = _entManager.SpawnAtPosition(proto, new EntityCoordinates(_gridUid, node + _grid.TileSizeHalfVector)); + var xform = xformQuery.Get(ent); + + if (!xform.Comp.Anchored) + _transform.AnchorEntity(ent, xform); + + await SuspendDungeon(); + if (!ValidateResume()) + return; + } + } + } +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenBoundaryWall.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenBoundaryWall.cs new file mode 100644 index 0000000000..84697a56bc --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenBoundaryWall.cs @@ -0,0 +1,113 @@ +using System.Threading.Tasks; +using Content.Shared.Maps; +using Content.Shared.Procedural; +using Content.Shared.Procedural.PostGeneration; +using Robust.Shared.Map; +using Robust.Shared.Utility; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /// + /// + /// + private async Task PostGen(BoundaryWallDunGen gen, DungeonData data, Dungeon dungeon, HashSet reservedTiles, Random random) + { + if (!data.Tiles.TryGetValue(DungeonDataKey.FallbackTile, out var protoTileDef) || + !data.Entities.TryGetValue(DungeonDataKey.Walls, out var wall)) + { + _sawmill.Error($"Error finding dungeon data for {nameof(gen)}"); + return; + } + + var tileDef = _tileDefManager[protoTileDef]; + var tiles = new List<(Vector2i Index, Tile Tile)>(dungeon.RoomExteriorTiles.Count); + + if (!data.Entities.TryGetValue(DungeonDataKey.CornerWalls, out var cornerWall)) + { + cornerWall = wall; + } + + if (cornerWall == default) + { + cornerWall = wall; + } + + // Spawn wall outline + // - Tiles first + foreach (var neighbor in dungeon.RoomExteriorTiles) + { + DebugTools.Assert(!dungeon.RoomTiles.Contains(neighbor)); + + if (dungeon.Entrances.Contains(neighbor)) + continue; + + if (!_anchorable.TileFree(_grid, neighbor, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) + continue; + + tiles.Add((neighbor, _tile.GetVariantTile((ContentTileDefinition) tileDef, random))); + } + + foreach (var index in dungeon.CorridorExteriorTiles) + { + if (dungeon.RoomTiles.Contains(index)) + continue; + + if (!_anchorable.TileFree(_grid, index, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) + continue; + + tiles.Add((index, _tile.GetVariantTile((ContentTileDefinition)tileDef, random))); + } + + _maps.SetTiles(_gridUid, _grid, tiles); + + // Double iteration coz we bulk set tiles for speed. + for (var i = 0; i < tiles.Count; i++) + { + var index = tiles[i]; + + if (!_anchorable.TileFree(_grid, index.Index, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) + continue; + + // If no cardinal neighbors in dungeon then we're a corner. + var isCorner = true; + + for (var x = -1; x <= 1; x++) + { + for (var y = -1; y <= 1; y++) + { + if (x != 0 && y != 0) + { + continue; + } + + var neighbor = new Vector2i(index.Index.X + x, index.Index.Y + y); + + if (dungeon.RoomTiles.Contains(neighbor) || dungeon.CorridorTiles.Contains(neighbor)) + { + isCorner = false; + break; + } + } + + if (!isCorner) + break; + } + + if (isCorner) + _entManager.SpawnEntity(cornerWall, _maps.GridTileToLocal(_gridUid, _grid, index.Index)); + + if (!isCorner) + _entManager.SpawnEntity(wall, _maps.GridTileToLocal(_gridUid, _grid, index.Index)); + + if (i % 20 == 0) + { + await SuspendDungeon(); + + if (!ValidateResume()) + return; + } + } + } +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenCornerClutter.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenCornerClutter.cs new file mode 100644 index 0000000000..f785829850 --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenCornerClutter.cs @@ -0,0 +1,56 @@ +using System.Threading.Tasks; +using Content.Shared.Procedural; +using Content.Shared.Procedural.PostGeneration; +using Content.Shared.Storage; +using Robust.Shared.Physics.Components; +using Robust.Shared.Random; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /// + /// + /// + private async Task PostGen(CornerClutterDunGen gen, DungeonData data, Dungeon dungeon, HashSet reservedTiles, Random random) + { + if (!data.SpawnGroups.TryGetValue(DungeonDataKey.CornerClutter, out var corner)) + { + _sawmill.Error(Environment.StackTrace); + return; + } + + foreach (var tile in dungeon.CorridorTiles) + { + var blocked = _anchorable.TileFree(_grid, tile, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask); + + if (blocked) + continue; + + // If at least 2 adjacent tiles are blocked consider it a corner + for (var i = 0; i < 4; i++) + { + var dir = (Direction) (i * 2); + blocked = HasWall(tile + dir.ToIntVec()); + + if (!blocked) + continue; + + var nextDir = (Direction) ((i + 1) * 2 % 8); + blocked = HasWall(tile + nextDir.ToIntVec()); + + if (!blocked) + continue; + + if (random.Prob(gen.Chance)) + { + var coords = _maps.GridTileToLocal(_gridUid, _grid, tile); + var protos = EntitySpawnCollection.GetSpawns(_prototype.Index(corner).Entries, random); + _entManager.SpawnEntities(coords, protos); + } + + break; + } + } + } +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenCorridor.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenCorridor.cs new file mode 100644 index 0000000000..8ea79ffe54 --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenCorridor.cs @@ -0,0 +1,116 @@ +using System.Numerics; +using System.Threading.Tasks; +using Content.Shared.Maps; +using Content.Shared.Procedural; +using Content.Shared.Procedural.PostGeneration; +using Robust.Shared.Map; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /// + /// + /// + private async Task PostGen(CorridorDunGen gen, DungeonData data, Dungeon dungeon, HashSet reservedTiles, Random random) + { + if (!data.Tiles.TryGetValue(DungeonDataKey.FallbackTile, out var tileProto)) + { + LogDataError(typeof(CorridorDunGen)); + return; + } + + var entrances = new List(dungeon.Rooms.Count); + + // Grab entrances + foreach (var room in dungeon.Rooms) + { + entrances.AddRange(room.Entrances); + } + + var edges = _dungeon.MinimumSpanningTree(entrances, random); + await SuspendDungeon(); + + if (!ValidateResume()) + return; + + // TODO: Add in say 1/3 of edges back in to add some cyclic to it. + + var expansion = gen.Width - 2; + // Okay so tl;dr is that we don't want to cut close to rooms as it might go from 3 width to 2 width suddenly + // So we will add a buffer range around each room to deter pathfinding there unless necessary + var deterredTiles = new HashSet(); + + if (expansion >= 1) + { + foreach (var tile in dungeon.RoomExteriorTiles) + { + for (var x = -expansion; x <= expansion; x++) + { + for (var y = -expansion; y <= expansion; y++) + { + var neighbor = new Vector2(tile.X + x, tile.Y + y).Floored(); + + if (dungeon.RoomTiles.Contains(neighbor) || + dungeon.RoomExteriorTiles.Contains(neighbor) || + entrances.Contains(neighbor)) + { + continue; + } + + deterredTiles.Add(neighbor); + } + } + } + } + + foreach (var room in dungeon.Rooms) + { + foreach (var entrance in room.Entrances) + { + // Just so we can still actually get in to the entrance we won't deter from a tile away from it. + var normal = (entrance + _grid.TileSizeHalfVector - room.Center).ToWorldAngle().GetCardinalDir().ToIntVec(); + deterredTiles.Remove(entrance + normal); + } + } + + var excludedTiles = new HashSet(dungeon.RoomExteriorTiles); + excludedTiles.UnionWith(dungeon.RoomTiles); + var corridorTiles = new HashSet(); + + _dungeon.GetCorridorNodes(corridorTiles, edges, gen.PathLimit, excludedTiles, tile => + { + var mod = 1f; + + if (corridorTiles.Contains(tile)) + { + mod *= 0.1f; + } + + if (deterredTiles.Contains(tile)) + { + mod *= 2f; + } + + return mod; + }); + + WidenCorridor(dungeon, gen.Width, corridorTiles); + + var setTiles = new List<(Vector2i, Tile)>(); + var tileDef = (ContentTileDefinition) _tileDefManager[tileProto]; + + foreach (var tile in corridorTiles) + { + if (reservedTiles.Contains(tile)) + continue; + + setTiles.Add((tile, _tile.GetVariantTile(tileDef, random))); + } + + _maps.SetTiles(_gridUid, _grid, setTiles); + dungeon.CorridorTiles.UnionWith(corridorTiles); + dungeon.RefreshAllTiles(); + BuildCorridorExterior(dungeon); + } +} diff --git a/Content.Server/Procedural/DungeonJob.CorridorClutterPost.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenCorridorClutter.cs similarity index 83% rename from Content.Server/Procedural/DungeonJob.CorridorClutterPost.cs rename to Content.Server/Procedural/DungeonJob/DungeonJob.PostGenCorridorClutter.cs index 8099157cc5..cb7c4b210c 100644 --- a/Content.Server/Procedural/DungeonJob.CorridorClutterPost.cs +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenCorridorClutter.cs @@ -2,16 +2,17 @@ using System.Threading.Tasks; using Content.Shared.Procedural; using Content.Shared.Procedural.PostGeneration; using Content.Shared.Storage; -using Robust.Shared.Map.Components; using Robust.Shared.Physics.Components; using Robust.Shared.Random; -namespace Content.Server.Procedural; +namespace Content.Server.Procedural.DungeonJob; public sealed partial class DungeonJob { - private async Task PostGen(CorridorClutterPostGen gen, Dungeon dungeon, EntityUid gridUid, MapGridComponent grid, - Random random) + /// + /// + /// + private async Task PostGen(CorridorClutterDunGen gen, DungeonData data, Dungeon dungeon, HashSet reservedTiles, Random random) { var physicsQuery = _entManager.GetEntityQuery(); var count = (int) Math.Ceiling(dungeon.CorridorTiles.Count * gen.Chance); diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenCorridorDecalSkirting.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenCorridorDecalSkirting.cs new file mode 100644 index 0000000000..3b516c3fa8 --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenCorridorDecalSkirting.cs @@ -0,0 +1,124 @@ +using System.Threading.Tasks; +using Content.Shared.Doors.Components; +using Content.Shared.Procedural; +using Content.Shared.Procedural.PostGeneration; +using Robust.Shared.Collections; +using Robust.Shared.Physics.Components; +using Robust.Shared.Utility; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /// + /// + /// + private async Task PostGen(CorridorDecalSkirtingDunGen decks, DungeonData data, Dungeon dungeon, HashSet reservedTiles, Random random) + { + if (!data.Colors.TryGetValue(DungeonDataKey.Decals, out var color)) + { + _sawmill.Error(Environment.StackTrace); + } + + var directions = new ValueList(4); + var pocketDirections = new ValueList(4); + var doorQuery = _entManager.GetEntityQuery(); + var physicsQuery = _entManager.GetEntityQuery(); + var offset = -_grid.TileSizeHalfVector; + + foreach (var tile in dungeon.CorridorTiles) + { + DebugTools.Assert(!dungeon.RoomTiles.Contains(tile)); + directions.Clear(); + + // Do cardinals 1 step + // Do corners the other step + for (var i = 0; i < 4; i++) + { + var dir = (DirectionFlag) Math.Pow(2, i); + var neighbor = tile + dir.AsDir().ToIntVec(); + + var anc = _maps.GetAnchoredEntitiesEnumerator(_gridUid, _grid, neighbor); + + while (anc.MoveNext(out var ent)) + { + if (!physicsQuery.TryGetComponent(ent, out var physics) || + !physics.CanCollide || + !physics.Hard || + doorQuery.HasComponent(ent.Value)) + { + continue; + } + + directions.Add(dir); + break; + } + } + + // Pockets + if (directions.Count == 0) + { + pocketDirections.Clear(); + + for (var i = 1; i < 5; i++) + { + var dir = (Direction) (i * 2 - 1); + var neighbor = tile + dir.ToIntVec(); + + var anc = _maps.GetAnchoredEntitiesEnumerator(_gridUid, _grid, neighbor); + + while (anc.MoveNext(out var ent)) + { + if (!physicsQuery.TryGetComponent(ent, out var physics) || + !physics.CanCollide || + !physics.Hard || + doorQuery.HasComponent(ent.Value)) + { + continue; + } + + pocketDirections.Add(dir); + break; + } + } + + if (pocketDirections.Count == 1) + { + if (decks.PocketDecals.TryGetValue(pocketDirections[0], out var cDir)) + { + // Decals not being centered biting my ass again + var gridPos = _maps.GridTileToLocal(_gridUid, _grid, tile).Offset(offset); + _decals.TryAddDecal(cDir, gridPos, out _, color: color); + } + } + + continue; + } + + if (directions.Count == 1) + { + if (decks.CardinalDecals.TryGetValue(directions[0], out var cDir)) + { + // Decals not being centered biting my ass again + var gridPos = _maps.GridTileToLocal(_gridUid, _grid, tile).Offset(offset); + _decals.TryAddDecal(cDir, gridPos, out _, color: color); + } + + continue; + } + + // Corners + if (directions.Count == 2) + { + // Auehghegueugegegeheh help me + var dirFlag = directions[0] | directions[1]; + + if (decks.CornerDecals.TryGetValue(dirFlag, out var cDir)) + { + var gridPos = _maps.GridTileToLocal(_gridUid, _grid, tile).Offset(offset); + _decals.TryAddDecal(cDir, gridPos, out _, color: color); + } + } + } + } +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenDungeonConnector.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenDungeonConnector.cs new file mode 100644 index 0000000000..917b1ffc9c --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenDungeonConnector.cs @@ -0,0 +1,6 @@ +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenDungeonEntrance.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenDungeonEntrance.cs new file mode 100644 index 0000000000..abc52f07c6 --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenDungeonEntrance.cs @@ -0,0 +1,114 @@ +using System.Threading.Tasks; +using Content.Shared.Maps; +using Content.Shared.Procedural; +using Content.Shared.Procedural.PostGeneration; +using Content.Shared.Storage; +using Robust.Shared.Random; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /// + /// + /// + private async Task PostGen(DungeonEntranceDunGen gen, DungeonData data, Dungeon dungeon, HashSet reservedTiles, Random random) + { + if (!data.Tiles.TryGetValue(DungeonDataKey.FallbackTile, out var tileProto) || + !data.SpawnGroups.TryGetValue(DungeonDataKey.Entrance, out var entrance)) + { + LogDataError(typeof(DungeonEntranceDunGen)); + return; + } + + var rooms = new List(dungeon.Rooms); + var roomTiles = new List(); + var tileDef = (ContentTileDefinition) _tileDefManager[tileProto]; + + for (var i = 0; i < gen.Count; i++) + { + var roomIndex = random.Next(rooms.Count); + var room = rooms[roomIndex]; + + // Move out 3 tiles in a direction away from center of the room + // If none of those intersect another tile it's probably external + // TODO: Maybe need to take top half of furthest rooms in case there's interior exits? + roomTiles.AddRange(room.Exterior); + random.Shuffle(roomTiles); + + foreach (var tile in roomTiles) + { + var isValid = false; + + // Check if one side is dungeon and the other side is nothing. + for (var j = 0; j < 4; j++) + { + var dir = (Direction) (j * 2); + var oppositeDir = dir.GetOpposite(); + var dirVec = tile + dir.ToIntVec(); + var oppositeDirVec = tile + oppositeDir.ToIntVec(); + + if (!dungeon.RoomTiles.Contains(dirVec)) + { + continue; + } + + if (dungeon.RoomTiles.Contains(oppositeDirVec) || + dungeon.RoomExteriorTiles.Contains(oppositeDirVec) || + dungeon.CorridorExteriorTiles.Contains(oppositeDirVec) || + dungeon.CorridorTiles.Contains(oppositeDirVec)) + { + continue; + } + + // Check if exterior spot free. + if (!_anchorable.TileFree(_grid, tile, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) + { + continue; + } + + // Check if interior spot free (no guarantees on exterior but ClearDoor should handle it) + if (!_anchorable.TileFree(_grid, dirVec, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) + { + continue; + } + + // Valid pick! + isValid = true; + + // Entrance wew + _maps.SetTile(_gridUid, _grid, tile, _tile.GetVariantTile(tileDef, random)); + ClearDoor(dungeon, _grid, tile); + var gridCoords = _maps.GridTileToLocal(_gridUid, _grid, tile); + // Need to offset the spawn to avoid spawning in the room. + + foreach (var ent in EntitySpawnCollection.GetSpawns(_prototype.Index(entrance).Entries, random)) + { + _entManager.SpawnAtPosition(ent, gridCoords); + } + + // Clear out any biome tiles nearby to avoid blocking it + foreach (var nearTile in _maps.GetLocalTilesIntersecting(_gridUid, _grid, new Circle(gridCoords.Position, 1.5f), false)) + { + if (dungeon.RoomTiles.Contains(nearTile.GridIndices) || + dungeon.RoomExteriorTiles.Contains(nearTile.GridIndices) || + dungeon.CorridorTiles.Contains(nearTile.GridIndices) || + dungeon.CorridorExteriorTiles.Contains(nearTile.GridIndices)) + { + continue; + } + + _maps.SetTile(_gridUid, _grid, nearTile.GridIndices, _tile.GetVariantTile(tileDef, random)); + } + + break; + } + + if (isValid) + break; + } + + roomTiles.Clear(); + } + } +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenEntranceFlank.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenEntranceFlank.cs new file mode 100644 index 0000000000..3a1c7a3779 --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenEntranceFlank.cs @@ -0,0 +1,58 @@ +using System.Threading.Tasks; +using Content.Shared.Maps; +using Content.Shared.Procedural; +using Content.Shared.Procedural.PostGeneration; +using Content.Shared.Storage; +using Robust.Shared.Collections; +using Robust.Shared.Map; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /// + /// + /// + private async Task PostGen(EntranceFlankDunGen gen, DungeonData data, Dungeon dungeon, HashSet reservedTiles, Random random) + { + if (!data.Tiles.TryGetValue(DungeonDataKey.FallbackTile, out var tileProto) || + !data.SpawnGroups.TryGetValue(DungeonDataKey.EntranceFlank, out var flankProto)) + { + _sawmill.Error($"Unable to get dungeon data for {nameof(gen)}"); + return; + } + + var tiles = new List<(Vector2i Index, Tile)>(); + var tileDef = _tileDefManager[tileProto]; + var spawnPositions = new ValueList(dungeon.Rooms.Count); + + foreach (var room in dungeon.Rooms) + { + foreach (var entrance in room.Entrances) + { + for (var i = 0; i < 8; i++) + { + var dir = (Direction) i; + var neighbor = entrance + dir.ToIntVec(); + + if (!dungeon.RoomExteriorTiles.Contains(neighbor)) + continue; + + if (reservedTiles.Contains(neighbor)) + continue; + + tiles.Add((neighbor, _tile.GetVariantTile((ContentTileDefinition) tileDef, random))); + spawnPositions.Add(neighbor); + } + } + } + + _maps.SetTiles(_gridUid, _grid, tiles); + var entGroup = _prototype.Index(flankProto); + + foreach (var entrance in spawnPositions) + { + _entManager.SpawnEntities(_maps.GridTileToLocal(_gridUid, _grid, entrance), EntitySpawnCollection.GetSpawns(entGroup.Entries, random)); + } + } +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenExternalWindow.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenExternalWindow.cs new file mode 100644 index 0000000000..9a1b44ec91 --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenExternalWindow.cs @@ -0,0 +1,138 @@ +using System.Linq; +using System.Threading.Tasks; +using Content.Shared.Maps; +using Content.Shared.Procedural; +using Content.Shared.Procedural.PostGeneration; +using Content.Shared.Storage; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; +using Robust.Shared.Random; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + // (Comment refers to internal & external). + + /* + * You may be wondering why these are different. + * It's because for internals we want to force it as it looks nicer and not leave it up to chance. + */ + + // TODO: Can probably combine these a bit, their differences are in really annoying to pull out spots. + + /// + /// + /// + private async Task PostGen(ExternalWindowDunGen gen, DungeonData data, Dungeon dungeon, HashSet reservedTiles, Random random) + { + if (!data.Tiles.TryGetValue(DungeonDataKey.FallbackTile, out var tileProto) || + !data.SpawnGroups.TryGetValue(DungeonDataKey.Window, out var windowGroup)) + { + _sawmill.Error($"Unable to get dungeon data for {nameof(gen)}"); + return; + } + + // Iterate every tile with N chance to spawn windows on that wall per cardinal dir. + var chance = 0.25 / 3f; + + var allExterior = new HashSet(dungeon.CorridorExteriorTiles); + allExterior.UnionWith(dungeon.RoomExteriorTiles); + var validTiles = allExterior.ToList(); + random.Shuffle(validTiles); + + var tiles = new List<(Vector2i, Tile)>(); + var tileDef = _tileDefManager[tileProto]; + var count = Math.Floor(validTiles.Count * chance); + var index = 0; + var takenTiles = new HashSet(); + + // There's a bunch of shit here but tl;dr + // - don't spawn over cap + // - Check if we have 3 tiles in a row that aren't corners and aren't obstructed + foreach (var tile in validTiles) + { + if (index > count) + break; + + // Room tile / already used. + if (!_anchorable.TileFree(_grid, tile, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask) || + takenTiles.Contains(tile)) + { + continue; + } + + // Check we're not on a corner + for (var i = 0; i < 2; i++) + { + var dir = (Direction) (i * 2); + var dirVec = dir.ToIntVec(); + var isValid = true; + + // Check 1 beyond either side to ensure it's not a corner. + for (var j = -1; j < 4; j++) + { + var neighbor = tile + dirVec * j; + + if (!allExterior.Contains(neighbor) || + takenTiles.Contains(neighbor) || + !_anchorable.TileFree(_grid, neighbor, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) + { + isValid = false; + break; + } + + // Also check perpendicular that it is free + foreach (var k in new [] {2, 6}) + { + var perp = (Direction) ((i * 2 + k) % 8); + var perpVec = perp.ToIntVec(); + var perpTile = tile + perpVec; + + if (allExterior.Contains(perpTile) || + takenTiles.Contains(neighbor) || + !_anchorable.TileFree(_grid, perpTile, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) + { + isValid = false; + break; + } + } + + if (!isValid) + break; + } + + if (!isValid) + continue; + + for (var j = 0; j < 3; j++) + { + var neighbor = tile + dirVec * j; + + if (reservedTiles.Contains(neighbor)) + continue; + + tiles.Add((neighbor, _tile.GetVariantTile((ContentTileDefinition) tileDef, random))); + index++; + takenTiles.Add(neighbor); + } + } + } + + _maps.SetTiles(_gridUid, _grid, tiles); + index = 0; + var spawnEntry = _prototype.Index(windowGroup); + + foreach (var tile in tiles) + { + var gridPos = _maps.GridTileToLocal(_gridUid, _grid, tile.Item1); + + index += spawnEntry.Entries.Count; + _entManager.SpawnEntities(gridPos, EntitySpawnCollection.GetSpawns(spawnEntry.Entries, random)); + await SuspendDungeon(); + + if (!ValidateResume()) + return; + } + } +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenInternalWindow.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenInternalWindow.cs new file mode 100644 index 0000000000..d3b8c6d2f5 --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenInternalWindow.cs @@ -0,0 +1,108 @@ +using System.Numerics; +using System.Threading.Tasks; +using Content.Shared.Maps; +using Content.Shared.Procedural; +using Content.Shared.Procedural.PostGeneration; +using Content.Shared.Storage; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /// + /// + /// + private async Task PostGen(InternalWindowDunGen gen, DungeonData data, Dungeon dungeon, HashSet reservedTiles, Random random) + { + if (!data.Tiles.TryGetValue(DungeonDataKey.FallbackTile, out var tileProto) || + !data.SpawnGroups.TryGetValue(DungeonDataKey.Window, out var windowGroup)) + { + _sawmill.Error($"Unable to find dungeon data keys for {nameof(gen)}"); + return; + } + + // Iterate every room and check if there's a gap beyond it that leads to another room within N tiles + // If so then consider windows + var minDistance = 4; + var maxDistance = 6; + var tileDef = _tileDefManager[tileProto]; + var window = _prototype.Index(windowGroup); + + foreach (var room in dungeon.Rooms) + { + var validTiles = new List(); + + for (var i = 0; i < 4; i++) + { + var dir = (DirectionFlag) Math.Pow(2, i); + var dirVec = dir.AsDir().ToIntVec(); + + foreach (var tile in room.Tiles) + { + var tileAngle = (tile + _grid.TileSizeHalfVector - room.Center).ToAngle(); + var roundedAngle = Math.Round(tileAngle.Theta / (Math.PI / 2)) * (Math.PI / 2); + + var tileVec = (Vector2i) new Angle(roundedAngle).ToVec().Rounded(); + + if (!tileVec.Equals(dirVec)) + continue; + + var valid = false; + + for (var j = 1; j < maxDistance; j++) + { + var edgeNeighbor = tile + dirVec * j; + + if (dungeon.RoomTiles.Contains(edgeNeighbor)) + { + if (j < minDistance) + { + valid = false; + } + else + { + valid = true; + } + + break; + } + } + + if (!valid) + continue; + + var windowTile = tile + dirVec; + + if (reservedTiles.Contains(windowTile)) + continue; + + if (!_anchorable.TileFree(_grid, windowTile, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) + continue; + + validTiles.Add(windowTile); + } + + validTiles.Sort((x, y) => (x + _grid.TileSizeHalfVector - room.Center).LengthSquared().CompareTo((y + _grid.TileSizeHalfVector - room.Center).LengthSquared())); + + for (var j = 0; j < Math.Min(validTiles.Count, 3); j++) + { + var tile = validTiles[j]; + var gridPos = _maps.GridTileToLocal(_gridUid, _grid, tile); + _maps.SetTile(_gridUid, _grid, tile, _tile.GetVariantTile((ContentTileDefinition) tileDef, random)); + + _entManager.SpawnEntities(gridPos, EntitySpawnCollection.GetSpawns(window.Entries, random)); + } + + if (validTiles.Count > 0) + { + await SuspendDungeon(); + + if (!ValidateResume()) + return; + } + + validTiles.Clear(); + } + } + } +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenJunction.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenJunction.cs new file mode 100644 index 0000000000..700406eb89 --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenJunction.cs @@ -0,0 +1,144 @@ +using System.Threading.Tasks; +using Content.Shared.Maps; +using Content.Shared.Procedural; +using Content.Shared.Procedural.PostGeneration; +using Content.Shared.Storage; +using Robust.Shared.Map.Components; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /// + /// + /// + private async Task PostGen(JunctionDunGen gen, DungeonData data, Dungeon dungeon, HashSet reservedTiles, Random random) + { + if (!data.Tiles.TryGetValue(DungeonDataKey.FallbackTile, out var tileProto) || + !data.SpawnGroups.TryGetValue(DungeonDataKey.Junction, out var junctionProto)) + { + _sawmill.Error($"Dungeon data keys are missing for {nameof(gen)}"); + return; + } + + var tileDef = _tileDefManager[tileProto]; + var entranceGroup = _prototype.Index(junctionProto); + + // N-wide junctions + foreach (var tile in dungeon.CorridorTiles) + { + if (!_anchorable.TileFree(_grid, tile, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) + continue; + + // Check each direction: + // - Check if immediate neighbors are free + // - Check if the neighbors beyond that are not free + // - Then check either side if they're slightly more free + var exteriorWidth = (int) Math.Floor(gen.Width / 2f); + var width = (int) Math.Ceiling(gen.Width / 2f); + + for (var i = 0; i < 2; i++) + { + var isValid = true; + var neighborDir = (Direction) (i * 2); + var neighborVec = neighborDir.ToIntVec(); + + for (var j = -width; j <= width; j++) + { + if (j == 0) + continue; + + var neighbor = tile + neighborVec * j; + + // If it's an end tile then check it's occupied. + if (j == -width || + j == width) + { + if (!HasWall(neighbor)) + { + isValid = false; + break; + } + + continue; + } + + // If we're not at the end tile then check it + perpendicular are free. + if (!_anchorable.TileFree(_grid, neighbor, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) + { + isValid = false; + break; + } + + var perp1 = tile + neighborVec * j + ((Direction) ((i * 2 + 2) % 8)).ToIntVec(); + var perp2 = tile + neighborVec * j + ((Direction) ((i * 2 + 6) % 8)).ToIntVec(); + + if (!_anchorable.TileFree(_grid, perp1, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) + { + isValid = false; + break; + } + + if (!_anchorable.TileFree(_grid, perp2, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) + { + isValid = false; + break; + } + } + + if (!isValid) + continue; + + // Check corners to see if either side opens up (if it's just a 1x wide corridor do nothing, needs to be a funnel. + foreach (var j in new [] {-exteriorWidth, exteriorWidth}) + { + var freeCount = 0; + + // Need at least 3 of 4 free + for (var k = 0; k < 4; k++) + { + var cornerDir = (Direction) (k * 2 + 1); + var cornerVec = cornerDir.ToIntVec(); + var cornerNeighbor = tile + neighborVec * j + cornerVec; + + if (_anchorable.TileFree(_grid, cornerNeighbor, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) + { + freeCount++; + } + } + + if (freeCount < gen.Width) + continue; + + // Valid! + isValid = true; + + for (var x = -width + 1; x < width; x++) + { + var weh = tile + neighborDir.ToIntVec() * x; + + if (reservedTiles.Contains(weh)) + continue; + + _maps.SetTile(_gridUid, _grid, weh, _tile.GetVariantTile((ContentTileDefinition) tileDef, random)); + + var coords = _maps.GridTileToLocal(_gridUid, _grid, weh); + _entManager.SpawnEntities(coords, EntitySpawnCollection.GetSpawns(entranceGroup.Entries, random)); + } + + break; + } + + if (isValid) + { + await SuspendDungeon(); + + if (!ValidateResume()) + return; + } + + break; + } + } + } +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenMiddleConnection.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenMiddleConnection.cs new file mode 100644 index 0000000000..15d0f63423 --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenMiddleConnection.cs @@ -0,0 +1,147 @@ +using System.Numerics; +using System.Threading.Tasks; +using Content.Shared.Maps; +using Content.Shared.Procedural; +using Content.Shared.Procedural.PostGeneration; +using Content.Shared.Storage; +using Robust.Shared.Utility; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /// + /// + /// + private async Task PostGen(MiddleConnectionDunGen gen, DungeonData data, Dungeon dungeon, HashSet reservedTiles, Random random) + { + if (!data.Tiles.TryGetValue(DungeonDataKey.FallbackTile, out var tileProto) || + !data.SpawnGroups.TryGetValue(DungeonDataKey.Entrance, out var entranceProto) || + !_prototype.TryIndex(entranceProto, out var entrance)) + { + _sawmill.Error($"Tried to run {nameof(MiddleConnectionDunGen)} without any dungeon data set which is unsupported"); + return; + } + + data.SpawnGroups.TryGetValue(DungeonDataKey.EntranceFlank, out var flankProto); + _prototype.TryIndex(flankProto, out var flank); + + // Grab all of the room bounds + // Then, work out connections between them + var roomBorders = new Dictionary>(dungeon.Rooms.Count); + + foreach (var room in dungeon.Rooms) + { + var roomEdges = new HashSet(); + + foreach (var index in room.Tiles) + { + for (var x = -1; x <= 1; x++) + { + for (var y = -1; y <= 1; y++) + { + // Cardinals only + if (x != 0 && y != 0 || + x == 0 && y == 0) + { + continue; + } + + var neighbor = new Vector2i(index.X + x, index.Y + y); + + if (dungeon.RoomTiles.Contains(neighbor)) + continue; + + if (!_anchorable.TileFree(_grid, neighbor, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) + continue; + + roomEdges.Add(neighbor); + } + } + } + + roomBorders.Add(room, roomEdges); + } + + // Do pathfind from first room to work out graph. + // TODO: Optional loops + + var roomConnections = new Dictionary>(); + var tileDef = _tileDefManager[tileProto]; + + foreach (var (room, border) in roomBorders) + { + var conns = roomConnections.GetOrNew(room); + + foreach (var (otherRoom, otherBorders) in roomBorders) + { + if (room.Equals(otherRoom) || + conns.Contains(otherRoom)) + { + continue; + } + + var flipp = new HashSet(border); + flipp.IntersectWith(otherBorders); + + if (flipp.Count == 0 || + gen.OverlapCount != -1 && flipp.Count != gen.OverlapCount) + continue; + + var center = Vector2.Zero; + + foreach (var node in flipp) + { + center += node + _grid.TileSizeHalfVector; + } + + center /= flipp.Count; + // Weight airlocks towards center more. + var nodeDistances = new List<(Vector2i Node, float Distance)>(flipp.Count); + + foreach (var node in flipp) + { + nodeDistances.Add((node, (node + _grid.TileSizeHalfVector - center).LengthSquared())); + } + + nodeDistances.Sort((x, y) => x.Distance.CompareTo(y.Distance)); + + var width = gen.Count; + + for (var i = 0; i < nodeDistances.Count; i++) + { + var node = nodeDistances[i].Node; + var gridPos = _maps.GridTileToLocal(_gridUid, _grid, node); + if (!_anchorable.TileFree(_grid, node, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) + continue; + + width--; + _maps.SetTile(_gridUid, _grid, node, _tile.GetVariantTile((ContentTileDefinition) tileDef, random)); + + if (flank != null && nodeDistances.Count - i <= 2) + { + _entManager.SpawnEntities(gridPos, EntitySpawnCollection.GetSpawns(flank.Entries, random)); + } + else + { + // Iterate neighbors and check for blockers, if so bulldoze + ClearDoor(dungeon, _grid, node); + + _entManager.SpawnEntities(gridPos, EntitySpawnCollection.GetSpawns(entrance.Entries, random)); + } + + if (width == 0) + break; + } + + conns.Add(otherRoom); + var otherConns = roomConnections.GetOrNew(otherRoom); + otherConns.Add(room); + await SuspendDungeon(); + + if (!ValidateResume()) + return; + } + } + } +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenRoomEntrance.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenRoomEntrance.cs new file mode 100644 index 0000000000..09d223e86c --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenRoomEntrance.cs @@ -0,0 +1,48 @@ +using System.Threading.Tasks; +using Content.Shared.Maps; +using Content.Shared.Procedural; +using Content.Shared.Procedural.PostGeneration; +using Content.Shared.Storage; +using Robust.Shared.Map; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /// + /// + /// + private async Task PostGen(RoomEntranceDunGen gen, DungeonData data, Dungeon dungeon, HashSet reservedTiles, Random random) + { + if (!data.Tiles.TryGetValue(DungeonDataKey.FallbackTile, out var tileProto) || + !data.SpawnGroups.TryGetValue(DungeonDataKey.Entrance, out var entranceProtos) || + !_prototype.TryIndex(entranceProtos, out var entranceIn)) + { + LogDataError(typeof(RoomEntranceDunGen)); + return; + } + + var setTiles = new List<(Vector2i, Tile)>(); + var tileDef = _tileDefManager[tileProto]; + + foreach (var room in dungeon.Rooms) + { + foreach (var entrance in room.Entrances) + { + setTiles.Add((entrance, _tile.GetVariantTile((ContentTileDefinition) tileDef, random))); + } + } + + _maps.SetTiles(_gridUid, _grid, setTiles); + + foreach (var room in dungeon.Rooms) + { + foreach (var entrance in room.Entrances) + { + _entManager.SpawnEntities( + _maps.GridTileToLocal(_gridUid, _grid, entrance), + EntitySpawnCollection.GetSpawns(entranceIn.Entries, random)); + } + } + } +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenSplineDungeonConnector.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenSplineDungeonConnector.cs new file mode 100644 index 0000000000..8fe2f36665 --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenSplineDungeonConnector.cs @@ -0,0 +1,147 @@ +using System.Numerics; +using System.Threading.Tasks; +using Content.Server.NPC.Pathfinding; +using Content.Shared.Procedural; +using Content.Shared.Procedural.PostGeneration; +using Robust.Shared.Map; +using Robust.Shared.Random; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /// + /// + /// + private async Task PostGen( + SplineDungeonConnectorDunGen gen, + DungeonData data, + List dungeons, + HashSet reservedTiles, + Random random) + { + // TODO: The path itself use the tile + // Widen it randomly (probably for each tile offset it by some changing amount). + + // NOOP + if (dungeons.Count <= 1) + return Dungeon.Empty; + + if (!data.Tiles.TryGetValue(DungeonDataKey.FallbackTile, out var fallback) || + !data.Tiles.TryGetValue(DungeonDataKey.WidenTile, out var widen)) + { + LogDataError(typeof(SplineDungeonConnectorDunGen)); + return Dungeon.Empty; + } + + var nodes = new List(); + + foreach (var dungeon in dungeons) + { + foreach (var room in dungeon.Rooms) + { + if (room.Entrances.Count == 0) + continue; + + nodes.Add(room.Entrances[0]); + break; + } + } + + var tree = _dungeon.MinimumSpanningTree(nodes, random); + await SuspendDungeon(); + + if (!ValidateResume()) + return Dungeon.Empty; + + var tiles = new List<(Vector2i Index, Tile Tile)>(); + var pathfinding = _entManager.System(); + var allTiles = new HashSet(); + var fallbackTile = new Tile(_prototype.Index(fallback).TileId); + + foreach (var pair in tree) + { + var path = pathfinding.GetSplinePath(new PathfindingSystem.SplinePathArgs() + { + Distance = gen.DivisionDistance, + MaxRatio = gen.VarianceMax, + Args = new PathfindingSystem.SimplePathArgs() + { + Start = pair.Start, + End = pair.End, + TileCost = node => + { + // We want these to get prioritised internally and into space if it's a space dungeon. + if (_maps.TryGetTile(_grid, node, out var tile) && !tile.IsEmpty) + return 1f; + + return 5f; + } + }, + }, + random); + + // Welp + if (path.Path.Count == 0) + { + _sawmill.Error($"Unable to connect spline dungeon path for {_entManager.ToPrettyString(_gridUid)} between {pair.Start} and {pair.End}"); + continue; + } + + await SuspendDungeon(); + + if (!ValidateResume()) + return Dungeon.Empty; + + var wide = pathfinding.GetWiden(new PathfindingSystem.WidenArgs() + { + Path = path.Path, + }, + random); + + tiles.Clear(); + allTiles.EnsureCapacity(allTiles.Count + wide.Count); + + foreach (var node in wide) + { + if (reservedTiles.Contains(node)) + continue; + + allTiles.Add(node); + Tile tile; + + if (random.Prob(0.9f)) + { + tile = new Tile(_prototype.Index(widen).TileId); + } + else + { + tile = _tileDefManager.GetVariantTile(widen, random); + } + + tiles.Add((node, tile)); + } + + _maps.SetTiles(_gridUid, _grid, tiles); + tiles.Clear(); + allTiles.EnsureCapacity(allTiles.Count + path.Path.Count); + + foreach (var node in path.Path) + { + if (reservedTiles.Contains(node)) + continue; + + allTiles.Add(node); + tiles.Add((node, fallbackTile)); + } + + _maps.SetTiles(_gridUid, _grid, tiles); + } + + var dungy = new Dungeon(); + var dungyRoom = new DungeonRoom(allTiles, Vector2.Zero, Box2i.Empty, new HashSet()); + dungy.AddRoom(dungyRoom); + + return dungy; + } +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenWallMount.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenWallMount.cs new file mode 100644 index 0000000000..afc7608d64 --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenWallMount.cs @@ -0,0 +1,56 @@ +using System.Threading.Tasks; +using Content.Shared.Procedural; +using Content.Shared.Procedural.PostGeneration; +using Content.Shared.Storage; +using Robust.Shared.Random; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /// + /// + /// + private async Task PostGen(WallMountDunGen gen, DungeonData data, Dungeon dungeon, HashSet reservedTiles, Random random) + { + if (!data.Tiles.TryGetValue(DungeonDataKey.FallbackTile, out var tileProto)) + { + _sawmill.Error($"Tried to run {nameof(WallMountDunGen)} without any dungeon data set which is unsupported"); + return; + } + + var tileDef = _prototype.Index(tileProto); + data.SpawnGroups.TryGetValue(DungeonDataKey.WallMounts, out var spawnProto); + + var checkedTiles = new HashSet(); + var allExterior = new HashSet(dungeon.CorridorExteriorTiles); + allExterior.UnionWith(dungeon.RoomExteriorTiles); + var count = 0; + + foreach (var neighbor in allExterior) + { + // Occupado + if (dungeon.RoomTiles.Contains(neighbor) || checkedTiles.Contains(neighbor) || !_anchorable.TileFree(_grid, neighbor, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) + continue; + + if (!random.Prob(gen.Prob) || !checkedTiles.Add(neighbor)) + continue; + + _maps.SetTile(_gridUid, _grid, neighbor, _tile.GetVariantTile(tileDef, random)); + var gridPos = _maps.GridTileToLocal(_gridUid, _grid, neighbor); + var protoNames = EntitySpawnCollection.GetSpawns(_prototype.Index(spawnProto).Entries, random); + + _entManager.SpawnEntities(gridPos, protoNames); + count += protoNames.Count; + + if (count > 20) + { + count -= 20; + await SuspendDungeon(); + + if (!ValidateResume()) + return; + } + } + } +} diff --git a/Content.Server/Procedural/DungeonJob.WormPost.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenWorm.cs similarity index 88% rename from Content.Server/Procedural/DungeonJob.WormPost.cs rename to Content.Server/Procedural/DungeonJob/DungeonJob.PostGenWorm.cs index 5d2271cae6..6fd00e5482 100644 --- a/Content.Server/Procedural/DungeonJob.WormPost.cs +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenWorm.cs @@ -1,23 +1,27 @@ using System.Linq; -using System.Numerics; using System.Threading.Tasks; using Content.Shared.Procedural; using Content.Shared.Procedural.PostGeneration; using Robust.Shared.Collections; using Robust.Shared.Map; -using Robust.Shared.Map.Components; using Robust.Shared.Random; using Robust.Shared.Utility; -namespace Content.Server.Procedural; +namespace Content.Server.Procedural.DungeonJob; public sealed partial class DungeonJob { /// - /// Tries to connect rooms via worm-like corridors. + /// /// - private async Task PostGen(WormCorridorPostGen gen, Dungeon dungeon, EntityUid gridUid, MapGridComponent grid, Random random) + private async Task PostGen(WormCorridorDunGen gen, DungeonData data, Dungeon dungeon, HashSet reservedTiles, Random random) { + if (!data.Tiles.TryGetValue(DungeonDataKey.FallbackTile, out var tileProto) || !_prototype.TryIndex(tileProto, out var tileDef)) + { + _sawmill.Error($"Tried to run {nameof(WormCorridorDunGen)} without any dungeon data set which is unsupported"); + return; + } + var networks = new List<(Vector2i Start, HashSet Network)>(); // List of places to start from. @@ -32,7 +36,7 @@ public sealed partial class DungeonJob networks.Add((entrance, network)); // Point away from the room to start with. - startAngles.Add(entrance, (entrance + grid.TileSizeHalfVector - room.Center).ToAngle()); + startAngles.Add(entrance, (entrance + _grid.TileSizeHalfVector - room.Center).ToAngle()); } } @@ -46,7 +50,7 @@ public sealed partial class DungeonJob // Find a random network to worm from. var startIndex = (i % networks.Count); var startPos = networks[startIndex].Start; - var position = startPos + grid.TileSizeHalfVector; + var position = startPos + _grid.TileSizeHalfVector; var remainingLength = gen.Length; worm.Clear(); @@ -108,7 +112,7 @@ public sealed partial class DungeonJob costSoFar[startNode] = 0f; var count = 0; - await SuspendIfOutOfTime(); + await SuspendDungeon(); if (!ValidateResume()) return; @@ -174,9 +178,9 @@ public sealed partial class DungeonJob WidenCorridor(dungeon, gen.Width, main.Network); dungeon.CorridorTiles.UnionWith(main.Network); BuildCorridorExterior(dungeon); + dungeon.RefreshAllTiles(); var tiles = new List<(Vector2i Index, Tile Tile)>(); - var tileDef = _prototype.Index(gen.Tile); foreach (var tile in dungeon.CorridorTiles) { diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.cs new file mode 100644 index 0000000000..1468a80902 --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.cs @@ -0,0 +1,309 @@ +using System.Threading; +using System.Threading.Tasks; +using Content.Server.Decals; +using Content.Server.NPC.Components; +using Content.Server.NPC.HTN; +using Content.Server.NPC.Systems; +using Content.Shared.Construction.EntitySystems; +using Content.Shared.Maps; +using Content.Shared.Procedural; +using Content.Shared.Procedural.DungeonGenerators; +using Content.Shared.Procedural.DungeonLayers; +using Content.Shared.Procedural.PostGeneration; +using Content.Shared.Tag; +using JetBrains.Annotations; +using Robust.Server.Physics; +using Robust.Shared.CPUJob.JobQueues; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; +using Robust.Shared.Physics.Components; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using Robust.Shared.Utility; +using IDunGenLayer = Content.Shared.Procedural.IDunGenLayer; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob : Job> +{ + public bool TimeSlice = true; + + private readonly IEntityManager _entManager; + private readonly IPrototypeManager _prototype; + private readonly ITileDefinitionManager _tileDefManager; + + private readonly AnchorableSystem _anchorable; + private readonly DecalSystem _decals; + private readonly DungeonSystem _dungeon; + private readonly EntityLookupSystem _lookup; + private readonly TagSystem _tags; + private readonly TileSystem _tile; + private readonly SharedMapSystem _maps; + private readonly SharedTransformSystem _transform; + + private EntityQuery _physicsQuery; + private EntityQuery _xformQuery; + + private readonly DungeonConfigPrototype _gen; + private readonly int _seed; + private readonly Vector2i _position; + + private readonly EntityUid _gridUid; + private readonly MapGridComponent _grid; + + private readonly ISawmill _sawmill; + + public DungeonJob( + ISawmill sawmill, + double maxTime, + IEntityManager entManager, + IPrototypeManager prototype, + ITileDefinitionManager tileDefManager, + AnchorableSystem anchorable, + DecalSystem decals, + DungeonSystem dungeon, + EntityLookupSystem lookup, + TileSystem tile, + SharedTransformSystem transform, + DungeonConfigPrototype gen, + MapGridComponent grid, + EntityUid gridUid, + int seed, + Vector2i position, + CancellationToken cancellation = default) : base(maxTime, cancellation) + { + _sawmill = sawmill; + _entManager = entManager; + _prototype = prototype; + _tileDefManager = tileDefManager; + + _anchorable = anchorable; + _decals = decals; + _dungeon = dungeon; + _lookup = lookup; + _tile = tile; + _tags = _entManager.System(); + _maps = _entManager.System(); + _transform = transform; + + _physicsQuery = _entManager.GetEntityQuery(); + _xformQuery = _entManager.GetEntityQuery(); + + _gen = gen; + _grid = grid; + _gridUid = gridUid; + _seed = seed; + _position = position; + } + + /// + /// Gets the relevant dungeon, running recursively as relevant. + /// + /// Should we reserve tiles even if the config doesn't specify. + private async Task> GetDungeons( + Vector2i position, + DungeonConfigPrototype config, + DungeonData data, + List layers, + HashSet reservedTiles, + int seed, + Random random) + { + var dungeons = new List(); + var count = random.Next(config.MinCount, config.MaxCount + 1); + + for (var i = 0; i < count; i++) + { + position += random.NextPolarVector2(config.MinOffset, config.MaxOffset).Floored(); + + foreach (var layer in layers) + { + await RunLayer(dungeons, data, position, layer, reservedTiles, seed, random); + + if (config.ReserveTiles) + { + foreach (var dungeon in dungeons) + { + reservedTiles.UnionWith(dungeon.AllTiles); + } + } + + await SuspendDungeon(); + if (!ValidateResume()) + return new List(); + } + } + + return dungeons; + } + + protected override async Task?> Process() + { + _sawmill.Info($"Generating dungeon {_gen.ID} with seed {_seed} on {_entManager.ToPrettyString(_gridUid)}"); + _grid.CanSplit = false; + var random = new Random(_seed); + var position = (_position + random.NextPolarVector2(_gen.MinOffset, _gen.MaxOffset)).Floored(); + + // Tiles we can no longer generate on due to being reserved elsewhere. + var reservedTiles = new HashSet(); + + var dungeons = await GetDungeons(position, _gen, _gen.Data, _gen.Layers, reservedTiles, _seed, random); + // To make it slightly more deterministic treat this RNG as separate ig. + + // Post-processing after finishing loading. + + // Defer splitting so they don't get spammed and so we don't have to worry about tracking the grid along the way. + _grid.CanSplit = true; + _entManager.System().CheckSplits(_gridUid); + var npcSystem = _entManager.System(); + var npcs = new HashSet>(); + + _lookup.GetChildEntities(_gridUid, npcs); + + foreach (var npc in npcs) + { + npcSystem.WakeNPC(npc.Owner, npc.Comp); + } + + return dungeons; + } + + private async Task RunLayer( + List dungeons, + DungeonData data, + Vector2i position, + IDunGenLayer layer, + HashSet reservedTiles, + int seed, + Random random) + { + _sawmill.Debug($"Doing postgen {layer.GetType()} for {_gen.ID} with seed {_seed}"); + + // If there's a way to just call the methods directly for the love of god tell me. + // Some of these don't care about reservedtiles because they only operate on dungeon tiles (which should + // never be reserved) + + // Some may or may not return dungeons. + // It's clamplicated but yeah procgen layering moment I'll take constructive feedback. + + switch (layer) + { + case AutoCablingDunGen cabling: + await PostGen(cabling, data, dungeons[^1], reservedTiles, random); + break; + case BiomeMarkerLayerDunGen markerPost: + await PostGen(markerPost, data, dungeons[^1], reservedTiles, random); + break; + case BiomeDunGen biome: + await PostGen(biome, data, dungeons[^1], reservedTiles, random); + break; + case BoundaryWallDunGen boundary: + await PostGen(boundary, data, dungeons[^1], reservedTiles, random); + break; + case CornerClutterDunGen clutter: + await PostGen(clutter, data, dungeons[^1], reservedTiles, random); + break; + case CorridorClutterDunGen corClutter: + await PostGen(corClutter, data, dungeons[^1], reservedTiles, random); + break; + case CorridorDunGen cordor: + await PostGen(cordor, data, dungeons[^1], reservedTiles, random); + break; + case CorridorDecalSkirtingDunGen decks: + await PostGen(decks, data, dungeons[^1], reservedTiles, random); + break; + case EntranceFlankDunGen flank: + await PostGen(flank, data, dungeons[^1], reservedTiles, random); + break; + case ExteriorDunGen exterior: + dungeons.AddRange(await GenerateExteriorDungen(position, exterior, reservedTiles, random)); + break; + case FillGridDunGen fill: + dungeons.Add(await GenerateFillDunGen(data, reservedTiles)); + break; + case JunctionDunGen junc: + await PostGen(junc, data, dungeons[^1], reservedTiles, random); + break; + case MiddleConnectionDunGen dordor: + await PostGen(dordor, data, dungeons[^1], reservedTiles, random); + break; + case DungeonEntranceDunGen entrance: + await PostGen(entrance, data, dungeons[^1], reservedTiles, random); + break; + case ExternalWindowDunGen externalWindow: + await PostGen(externalWindow, data, dungeons[^1], reservedTiles, random); + break; + case InternalWindowDunGen internalWindow: + await PostGen(internalWindow, data, dungeons[^1], reservedTiles, random); + break; + case MobsDunGen mob: + await PostGen(mob, dungeons[^1], random); + break; + case NoiseDistanceDunGen distance: + dungeons.Add(await GenerateNoiseDistanceDunGen(position, distance, reservedTiles, seed, random)); + break; + case NoiseDunGen noise: + dungeons.Add(await GenerateNoiseDunGen(position, noise, reservedTiles, seed, random)); + break; + case OreDunGen ore: + await PostGen(ore, dungeons[^1], random); + break; + case PrefabDunGen prefab: + dungeons.Add(await GeneratePrefabDunGen(position, data, prefab, reservedTiles, random)); + break; + case PrototypeDunGen prototypo: + var groupConfig = _prototype.Index(prototypo.Proto); + position = (position + random.NextPolarVector2(groupConfig.MinOffset, groupConfig.MaxOffset)).Floored(); + + var dataCopy = groupConfig.Data.Clone(); + dataCopy.Apply(data); + + dungeons.AddRange(await GetDungeons(position, groupConfig, dataCopy, groupConfig.Layers, reservedTiles, seed, random)); + break; + case ReplaceTileDunGen replace: + dungeons.Add(await GenerateTileReplacementDunGen(replace, data, reservedTiles, random)); + break; + case RoomEntranceDunGen rEntrance: + await PostGen(rEntrance, data, dungeons[^1], reservedTiles, random); + break; + case SplineDungeonConnectorDunGen spline: + dungeons.Add(await PostGen(spline, data, dungeons, reservedTiles, random)); + break; + case WallMountDunGen wall: + await PostGen(wall, data, dungeons[^1], reservedTiles, random); + break; + case WormCorridorDunGen worm: + await PostGen(worm, data, dungeons[^1], reservedTiles, random); + break; + default: + throw new NotImplementedException(); + } + } + + private void LogDataError(Type type) + { + _sawmill.Error($"Unable to find dungeon data keys for {type}"); + } + + [Pure] + private bool ValidateResume() + { + if (_entManager.Deleted(_gridUid)) + { + return false; + } + + return true; + } + + /// + /// Wrapper around + /// + private async Task SuspendDungeon() + { + if (!TimeSlice) + return; + + await SuspendIfOutOfTime(); + } +} diff --git a/Content.Server/Procedural/DungeonSystem.Commands.cs b/Content.Server/Procedural/DungeonSystem.Commands.cs index d783eb60c6..51a6a57bbe 100644 --- a/Content.Server/Procedural/DungeonSystem.Commands.cs +++ b/Content.Server/Procedural/DungeonSystem.Commands.cs @@ -51,6 +51,8 @@ public sealed partial class DungeonSystem dungeonUid = EntityManager.CreateEntityUninitialized(null, new EntityCoordinates(dungeonUid, position)); dungeonGrid = EntityManager.AddComponent(dungeonUid); EntityManager.InitializeAndStartEntity(dungeonUid, mapId); + // If we created a grid (e.g. space dungen) then offset it so we don't double-apply positions + position = Vector2i.Zero; } int seed; diff --git a/Content.Server/Procedural/DungeonSystem.Rooms.cs b/Content.Server/Procedural/DungeonSystem.Rooms.cs index ddd4a4732f..8a1606c488 100644 --- a/Content.Server/Procedural/DungeonSystem.Rooms.cs +++ b/Content.Server/Procedural/DungeonSystem.Rooms.cs @@ -64,6 +64,7 @@ public sealed partial class DungeonSystem Vector2i origin, DungeonRoomPrototype room, Random random, + HashSet? reservedTiles, bool clearExisting = false, bool rotation = false) { @@ -78,7 +79,7 @@ public sealed partial class DungeonSystem var roomTransform = Matrix3Helpers.CreateTransform((Vector2) room.Size / 2f, roomRotation); var finalTransform = Matrix3x2.Multiply(roomTransform, originTransform); - SpawnRoom(gridUid, grid, finalTransform, room, clearExisting); + SpawnRoom(gridUid, grid, finalTransform, room, reservedTiles, clearExisting); } public Angle GetRoomRotation(DungeonRoomPrototype room, Random random) @@ -103,6 +104,7 @@ public sealed partial class DungeonSystem MapGridComponent grid, Matrix3x2 roomTransform, DungeonRoomPrototype room, + HashSet? reservedTiles = null, bool clearExisting = false) { // Ensure the underlying template exists. @@ -150,6 +152,10 @@ public sealed partial class DungeonSystem var tilePos = Vector2.Transform(indices + tileOffset, roomTransform); var rounded = tilePos.Floored(); + + if (!clearExisting && reservedTiles?.Contains(rounded) == true) + continue; + _tiles.Add((rounded, tileRef.Tile)); } } @@ -165,6 +171,10 @@ public sealed partial class DungeonSystem { var templateXform = _xformQuery.GetComponent(templateEnt); var childPos = Vector2.Transform(templateXform.LocalPosition - roomCenter, roomTransform); + + if (!clearExisting && reservedTiles?.Contains(childPos.Floored()) == true) + continue; + var childRot = templateXform.LocalRotation + finalRoomRotation; var protoId = _metaQuery.GetComponent(templateEnt).EntityPrototype?.ID; @@ -192,8 +202,11 @@ public sealed partial class DungeonSystem // Offset by 0.5 because decals are offset from bot-left corner // So we convert it to center of tile then convert it back again after transform. // Do these shenanigans because 32x32 decals assume as they are centered on bottom-left of tiles. - var position = Vector2.Transform(decal.Coordinates + Vector2Helpers.Half - roomCenter, roomTransform); - position -= Vector2Helpers.Half; + var position = Vector2.Transform(decal.Coordinates + grid.TileSizeHalfVector - roomCenter, roomTransform); + position -= grid.TileSizeHalfVector; + + if (!clearExisting && reservedTiles?.Contains(position.Floored()) == true) + continue; // Umm uhh I love decals so uhhhh idk what to do about this var angle = (decal.Angle + finalRoomRotation).Reduced(); diff --git a/Content.Server/Procedural/DungeonSystem.cs b/Content.Server/Procedural/DungeonSystem.cs index 36009896a2..b73e843fff 100644 --- a/Content.Server/Procedural/DungeonSystem.cs +++ b/Content.Server/Procedural/DungeonSystem.cs @@ -12,6 +12,7 @@ using Content.Shared.Physics; using Content.Shared.Procedural; using Content.Shared.Tag; using Robust.Server.GameObjects; +using Robust.Shared.Collections; using Robust.Shared.Configuration; using Robust.Shared.Console; using Robust.Shared.Map; @@ -49,7 +50,7 @@ public sealed partial class DungeonSystem : SharedDungeonSystem public const int CollisionLayer = (int) CollisionGroup.Impassable; private readonly JobQueue _dungeonJobQueue = new(DungeonJobTime); - private readonly Dictionary _dungeonJobs = new(); + private readonly Dictionary _dungeonJobs = new(); [ValidatePrototypeId] public const string FallbackTileId = "FloorSteel"; @@ -190,18 +191,16 @@ public sealed partial class DungeonSystem : SharedDungeonSystem int seed) { var cancelToken = new CancellationTokenSource(); - var job = new DungeonJob( + var job = new DungeonJob.DungeonJob( Log, DungeonJobTime, EntityManager, - _mapManager, _prototype, _tileDefManager, _anchorable, _decals, this, _lookup, - _tag, _tile, _transform, gen, @@ -215,7 +214,7 @@ public sealed partial class DungeonSystem : SharedDungeonSystem _dungeonJobQueue.EnqueueJob(job); } - public async Task GenerateDungeonAsync( + public async Task> GenerateDungeonAsync( DungeonConfigPrototype gen, EntityUid gridUid, MapGridComponent grid, @@ -223,18 +222,16 @@ public sealed partial class DungeonSystem : SharedDungeonSystem int seed) { var cancelToken = new CancellationTokenSource(); - var job = new DungeonJob( + var job = new DungeonJob.DungeonJob( Log, DungeonJobTime, EntityManager, - _mapManager, _prototype, _tileDefManager, _anchorable, _decals, this, _lookup, - _tag, _tile, _transform, gen, diff --git a/Content.Server/Procedural/RoomFillSystem.cs b/Content.Server/Procedural/RoomFillSystem.cs index 20ffa98586..b539cc9780 100644 --- a/Content.Server/Procedural/RoomFillSystem.cs +++ b/Content.Server/Procedural/RoomFillSystem.cs @@ -35,6 +35,7 @@ public sealed class RoomFillSystem : EntitySystem _maps.LocalToTile(xform.GridUid.Value, mapGrid, xform.Coordinates), room, random, + null, clearExisting: component.ClearExisting, rotation: component.Rotation); } diff --git a/Content.Server/Salvage/SpawnSalvageMissionJob.cs b/Content.Server/Salvage/SpawnSalvageMissionJob.cs index ce844e57a1..e9318792b7 100644 --- a/Content.Server/Salvage/SpawnSalvageMissionJob.cs +++ b/Content.Server/Salvage/SpawnSalvageMissionJob.cs @@ -176,9 +176,11 @@ public sealed class SpawnSalvageMissionJob : Job dungeonOffset = dungeonRotation.RotateVec(dungeonOffset); var dungeonMod = _prototypeManager.Index(mission.Dungeon); var dungeonConfig = _prototypeManager.Index(dungeonMod.Proto); - var dungeon = await WaitAsyncTask(_dungeon.GenerateDungeonAsync(dungeonConfig, mapUid, grid, (Vector2i) dungeonOffset, + var dungeons = await WaitAsyncTask(_dungeon.GenerateDungeonAsync(dungeonConfig, mapUid, grid, (Vector2i) dungeonOffset, _missionParams.Seed)); + var dungeon = dungeons.First(); + // Aborty if (dungeon.Rooms.Count == 0) { diff --git a/Content.Server/Shuttles/Components/GridSpawnComponent.cs b/Content.Server/Shuttles/Components/GridSpawnComponent.cs index 5f0fa7dd62..d8144354b8 100644 --- a/Content.Server/Shuttles/Components/GridSpawnComponent.cs +++ b/Content.Server/Shuttles/Components/GridSpawnComponent.cs @@ -1,4 +1,6 @@ using Content.Server.Shuttles.Systems; +using Content.Shared.Dataset; +using Content.Shared.Procedural; using Robust.Shared.Prototypes; using Robust.Shared.Utility; @@ -14,39 +16,92 @@ public sealed partial class GridSpawnComponent : Component /// Dictionary of groups where each group will have entries selected. /// String is just an identifier to make yaml easier. /// - [DataField(required: true)] public Dictionary Groups = new(); + [DataField(required: true)] public Dictionary Groups = new(); } -[DataRecord] -public record struct GridSpawnGroup +public interface IGridSpawnGroup { - public List Paths = new(); - public int MinCount = 1; - public int MaxCount = 1; + /// + /// Minimum distance to spawn away from the station. + /// + public float MinimumDistance { get; } + + /// + public ProtoId? NameDataset { get; } + + /// + int MinCount { get; set; } + + /// + int MaxCount { get; set; } /// /// Components to be added to any spawned grids. /// - public ComponentRegistry AddComponents = new(); + public ComponentRegistry AddComponents { get; set; } /// /// Hide the IFF label of the grid. /// - public bool Hide = false; + public bool Hide { get; set; } /// /// Should we set the metadata name of a grid. Useful for admin purposes. /// - public bool NameGrid = false; + public bool NameGrid { get; set; } /// /// Should we add this to the station's grids (if possible / relevant). /// - public bool StationGrid = true; + public bool StationGrid { get; set; } +} - public GridSpawnGroup() - { - } +[DataRecord] +public sealed class DungeonSpawnGroup : IGridSpawnGroup +{ + /// + /// Prototypes we can choose from to spawn. + /// + public List> Protos = new(); + + /// + public float MinimumDistance { get; } + + /// + public ProtoId? NameDataset { get; } + + /// + public int MinCount { get; set; } = 1; + + /// + public int MaxCount { get; set; } = 1; + + /// + public ComponentRegistry AddComponents { get; set; } = new(); + + /// + public bool Hide { get; set; } = false; + + /// + public bool NameGrid { get; set; } = false; + + /// + public bool StationGrid { get; set; } = false; +} + +[DataRecord] +public sealed class GridSpawnGroup : IGridSpawnGroup +{ + public List Paths = new(); + + public float MinimumDistance { get; } + public ProtoId? NameDataset { get; } + public int MinCount { get; set; } = 1; + public int MaxCount { get; set; } = 1; + public ComponentRegistry AddComponents { get; set; } = new(); + public bool Hide { get; set; } = false; + public bool NameGrid { get; set; } = true; + public bool StationGrid { get; set; } = true; } diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.GridFill.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.GridFill.cs index 853548add3..b4fcccd805 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.GridFill.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.GridFill.cs @@ -1,9 +1,14 @@ +using System.Numerics; using Content.Server.Shuttles.Components; using Content.Server.Station.Components; using Content.Server.Station.Events; using Content.Shared.Cargo.Components; using Content.Shared.CCVar; +using Content.Shared.Procedural; +using Content.Shared.Salvage; using Content.Shared.Shuttles.Components; +using Robust.Shared.Collections; +using Robust.Shared.Map; using Robust.Shared.Random; using Robust.Shared.Utility; @@ -80,6 +85,76 @@ public sealed partial class ShuttleSystem _mapManager.DeleteMap(mapId); } + private bool TryDungeonSpawn(EntityUid targetGrid, EntityUid stationUid, MapId mapId, DungeonSpawnGroup group, out EntityUid spawned) + { + spawned = EntityUid.Invalid; + var dungeonProtoId = _random.Pick(group.Protos); + + if (!_protoManager.TryIndex(dungeonProtoId, out var dungeonProto)) + { + return false; + } + + var spawnCoords = new EntityCoordinates(targetGrid, Vector2.Zero); + + if (group.MinimumDistance > 0f) + { + spawnCoords = spawnCoords.Offset(_random.NextVector2(group.MinimumDistance, group.MinimumDistance * 1.5f)); + } + + var spawnMapCoords = _transform.ToMapCoordinates(spawnCoords); + var spawnedGrid = _mapManager.CreateGridEntity(mapId); + + _transform.SetMapCoordinates(spawnedGrid, spawnMapCoords); + _dungeon.GenerateDungeon(dungeonProto, spawnedGrid.Owner, spawnedGrid.Comp, Vector2i.Zero, _random.Next()); + + spawned = spawnedGrid.Owner; + return true; + } + + private bool TryGridSpawn(EntityUid targetGrid, EntityUid stationUid, MapId mapId, GridSpawnGroup group, out EntityUid spawned) + { + spawned = EntityUid.Invalid; + + if (group.Paths.Count == 0) + { + Log.Error($"Found no paths for GridSpawn"); + return false; + } + + var paths = new ValueList(); + + // Round-robin so we try to avoid dupes where possible. + if (paths.Count == 0) + { + paths.AddRange(group.Paths); + _random.Shuffle(paths); + } + + var path = paths[^1]; + paths.RemoveAt(paths.Count - 1); + + if (_loader.TryLoad(mapId, path.ToString(), out var ent) && ent.Count == 1) + { + if (TryComp(ent[0], out var shuttle)) + { + TryFTLProximity(ent[0], targetGrid); + } + + if (group.NameGrid) + { + var name = path.FilenameWithoutExtension; + _metadata.SetEntityName(ent[0], name); + } + + spawned = ent[0]; + return true; + } + + Log.Error($"Error loading gridspawn for {ToPrettyString(stationUid)} / {path}"); + return false; + } + private void GridSpawns(EntityUid uid, GridSpawnComponent component) { if (!_cfg.GetCVar(CCVars.GridFill)) @@ -97,81 +172,49 @@ public sealed partial class ShuttleSystem // Spawn on a dummy map and try to FTL if possible, otherwise dump it. var mapId = _mapManager.CreateMap(); - var valid = true; - var paths = new List(); foreach (var group in component.Groups.Values) { - if (group.Paths.Count == 0) - { - Log.Error($"Found no paths for GridSpawn"); - continue; - } - - var count = _random.Next(group.MinCount, group.MaxCount); - paths.Clear(); + var count = _random.Next(group.MinCount, group.MaxCount + 1); for (var i = 0; i < count; i++) { - // Round-robin so we try to avoid dupes where possible. - if (paths.Count == 0) + EntityUid spawned; + + switch (group) { - paths.AddRange(group.Paths); - _random.Shuffle(paths); - } - - var path = paths[^1]; - paths.RemoveAt(paths.Count - 1); - - if (_loader.TryLoad(mapId, path.ToString(), out var ent) && ent.Count == 1) - { - if (TryComp(ent[0], out var shuttle)) - { - TryFTLProximity(ent[0], targetGrid.Value); - } - else - { - valid = false; - } - - if (group.Hide) - { - var iffComp = EnsureComp(ent[0]); - iffComp.Flags |= IFFFlags.HideLabel; - Dirty(ent[0], iffComp); - } - - if (group.StationGrid) - { - _station.AddGridToStation(uid, ent[0]); - } - - if (group.NameGrid) - { - var name = path.FilenameWithoutExtension; - _metadata.SetEntityName(ent[0], name); - } - - foreach (var compReg in group.AddComponents.Values) - { - var compType = compReg.Component.GetType(); - - if (HasComp(ent[0], compType)) + case DungeonSpawnGroup dungeon: + if (!TryDungeonSpawn(targetGrid.Value, uid, mapId, dungeon, out spawned)) continue; - var comp = _factory.GetComponent(compType); - AddComp(ent[0], comp, true); - } - } - else - { - valid = false; + break; + case GridSpawnGroup grid: + if (!TryGridSpawn(targetGrid.Value, uid, mapId, grid, out spawned)) + continue; + + break; + default: + throw new NotImplementedException(); } - if (!valid) + if (_protoManager.TryIndex(group.NameDataset, out var dataset)) { - Log.Error($"Error loading gridspawn for {ToPrettyString(uid)} / {path}"); + _metadata.SetEntityName(spawned, SharedSalvageSystem.GetFTLName(dataset, _random.Next())); } + + if (group.Hide) + { + var iffComp = EnsureComp(spawned); + iffComp.Flags |= IFFFlags.HideLabel; + Dirty(spawned, iffComp); + } + + if (group.StationGrid) + { + _station.AddGridToStation(uid, spawned); + } + + EntityManager.AddComponents(spawned, group.AddComponents); } } diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.cs index b8f216db73..85703389e9 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.cs @@ -2,6 +2,7 @@ using Content.Server.Administration.Logs; using Content.Server.Body.Systems; using Content.Server.Doors.Systems; using Content.Server.Parallax; +using Content.Server.Procedural; using Content.Server.Shuttles.Components; using Content.Server.Station.Systems; using Content.Server.Stunnable; @@ -20,6 +21,7 @@ using Robust.Shared.Map.Components; using Robust.Shared.Physics; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Systems; +using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Timing; @@ -28,15 +30,18 @@ namespace Content.Server.Shuttles.Systems; [UsedImplicitly] public sealed partial class ShuttleSystem : SharedShuttleSystem { + [Dependency] private readonly IAdminLogManager _logger = default!; [Dependency] private readonly IComponentFactory _factory = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly IPrototypeManager _protoManager = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly ITileDefinitionManager _tileDefManager = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly BiomeSystem _biomes = default!; [Dependency] private readonly BodySystem _bobby = default!; [Dependency] private readonly DockingSystem _dockSystem = default!; + [Dependency] private readonly DungeonSystem _dungeon = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly FixtureSystem _fixtures = default!; [Dependency] private readonly MapLoaderSystem _loader = default!; @@ -52,7 +57,6 @@ public sealed partial class ShuttleSystem : SharedShuttleSystem [Dependency] private readonly ThrowingSystem _throwing = default!; [Dependency] private readonly ThrusterSystem _thruster = default!; [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; - [Dependency] private readonly IAdminLogManager _logger = default!; public const float TileMassMultiplier = 0.5f; diff --git a/Content.Shared/Procedural/Components/EntityRemapComponent.cs b/Content.Shared/Procedural/Components/EntityRemapComponent.cs new file mode 100644 index 0000000000..3d7199743a --- /dev/null +++ b/Content.Shared/Procedural/Components/EntityRemapComponent.cs @@ -0,0 +1,13 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.Procedural.Components; + +/// +/// Indicates this entity prototype should be re-mapped to another +/// +[RegisterComponent] +public sealed partial class EntityRemapComponent : Component +{ + [DataField(required: true)] + public Dictionary Mask = new(); +} diff --git a/Content.Shared/Procedural/Distance/DunGenEuclideanSquaredDistance.cs b/Content.Shared/Procedural/Distance/DunGenEuclideanSquaredDistance.cs new file mode 100644 index 0000000000..617304729e --- /dev/null +++ b/Content.Shared/Procedural/Distance/DunGenEuclideanSquaredDistance.cs @@ -0,0 +1,10 @@ +namespace Content.Shared.Procedural.Distance; + +/// +/// Produces a rounder shape useful for more natural areas. +/// +public sealed partial class DunGenEuclideanSquaredDistance : IDunGenDistance +{ + [DataField] + public float BlendWeight { get; set; } = 0.50f; +} diff --git a/Content.Shared/Procedural/Distance/DunGenSquareBump.cs b/Content.Shared/Procedural/Distance/DunGenSquareBump.cs new file mode 100644 index 0000000000..48b0c4bcb7 --- /dev/null +++ b/Content.Shared/Procedural/Distance/DunGenSquareBump.cs @@ -0,0 +1,10 @@ +namespace Content.Shared.Procedural.Distance; + +/// +/// Produces a squarish-shape that's better for filling in most of the area. +/// +public sealed partial class DunGenSquareBump : IDunGenDistance +{ + [DataField] + public float BlendWeight { get; set; } = 0.50f; +} diff --git a/Content.Shared/Procedural/Distance/IDunGenDistance.cs b/Content.Shared/Procedural/Distance/IDunGenDistance.cs new file mode 100644 index 0000000000..b1071a14e3 --- /dev/null +++ b/Content.Shared/Procedural/Distance/IDunGenDistance.cs @@ -0,0 +1,14 @@ +namespace Content.Shared.Procedural.Distance; + +/// +/// Used if you want to limit the distance noise is generated by some arbitrary config +/// +[ImplicitDataDefinitionForInheritors] +public partial interface IDunGenDistance +{ + /// + /// How much to blend between the original noise value and the adjusted one. + /// + float BlendWeight { get; } +} + diff --git a/Content.Shared/Procedural/Dungeon.cs b/Content.Shared/Procedural/Dungeon.cs index aecfef2c78..0d290b6790 100644 --- a/Content.Shared/Procedural/Dungeon.cs +++ b/Content.Shared/Procedural/Dungeon.cs @@ -1,8 +1,16 @@ namespace Content.Shared.Procedural; +/// +/// Procedurally generated dungeon data. +/// public sealed class Dungeon { - public readonly List Rooms; + public static Dungeon Empty = new Dungeon(); + + private List _rooms; + private HashSet _allTiles = new(); + + public IReadOnlyList Rooms => _rooms; /// /// Hashset of the tiles across all rooms. @@ -17,18 +25,64 @@ public sealed class Dungeon public readonly HashSet Entrances = new(); - public Dungeon() + public IReadOnlySet AllTiles => _allTiles; + + public Dungeon() : this(new List()) { - Rooms = new List(); } public Dungeon(List rooms) { - Rooms = rooms; + // This reftype is mine now. + _rooms = rooms; - foreach (var room in Rooms) + foreach (var room in _rooms) { - Entrances.UnionWith(room.Entrances); + InternalAddRoom(room); } + + RefreshAllTiles(); + } + + public void RefreshAllTiles() + { + _allTiles.Clear(); + _allTiles.UnionWith(RoomTiles); + _allTiles.UnionWith(RoomExteriorTiles); + _allTiles.UnionWith(CorridorTiles); + _allTiles.UnionWith(CorridorExteriorTiles); + _allTiles.UnionWith(Entrances); + } + + public void Rebuild() + { + _allTiles.Clear(); + + RoomTiles.Clear(); + RoomExteriorTiles.Clear(); + Entrances.Clear(); + + foreach (var room in _rooms) + { + InternalAddRoom(room, false); + } + + RefreshAllTiles(); + } + + public void AddRoom(DungeonRoom room) + { + _rooms.Add(room); + InternalAddRoom(room); + } + + private void InternalAddRoom(DungeonRoom room, bool refreshAll = true) + { + Entrances.UnionWith(room.Entrances); + RoomTiles.UnionWith(room.Tiles); + RoomExteriorTiles.UnionWith(room.Exterior); + + if (refreshAll) + RefreshAllTiles(); } } diff --git a/Content.Shared/Procedural/DungeonConfigPrototype.cs b/Content.Shared/Procedural/DungeonConfigPrototype.cs index 07a7000d63..d0d8e0ff12 100644 --- a/Content.Shared/Procedural/DungeonConfigPrototype.cs +++ b/Content.Shared/Procedural/DungeonConfigPrototype.cs @@ -1,21 +1,53 @@ -using Content.Shared.Procedural.DungeonGenerators; using Content.Shared.Procedural.PostGeneration; using Robust.Shared.Prototypes; namespace Content.Shared.Procedural; -[Prototype("dungeonConfig")] +[Prototype] public sealed partial class DungeonConfigPrototype : IPrototype { [IdDataField] public string ID { get; private set; } = default!; - [DataField("generator", required: true)] - public IDunGen Generator = default!; + /// + /// + /// + [DataField] + public DungeonData Data = DungeonData.Empty; /// - /// Ran after the main dungeon is created. + /// The secret sauce, procedural generation layers that get run. /// - [DataField("postGeneration")] - public List PostGeneration = new(); + [DataField(required: true)] + public List Layers = new(); + + /// + /// Should we reserve the tiles generated by this config so no other dungeons can spawn on it within the same job? + /// + [DataField] + public bool ReserveTiles; + + /// + /// Minimum times to run the config. + /// + [DataField] + public int MinCount = 1; + + /// + /// Maximum times to run the config. + /// + [DataField] + public int MaxCount = 1; + + /// + /// Minimum amount we can offset the dungeon by. + /// + [DataField] + public int MinOffset; + + /// + /// Maximum amount we can offset the dungeon by. + /// + [DataField] + public int MaxOffset; } diff --git a/Content.Shared/Procedural/DungeonData.cs b/Content.Shared/Procedural/DungeonData.cs new file mode 100644 index 0000000000..58ec966786 --- /dev/null +++ b/Content.Shared/Procedural/DungeonData.cs @@ -0,0 +1,105 @@ +using System.Linq; +using Content.Shared.Maps; +using Content.Shared.Storage; +using Content.Shared.Whitelist; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Shared.Procedural; + +/// +/// Used to set dungeon values for all layers. +/// +/// +/// This lets us share data between different dungeon configs without having to repeat entire configs. +/// +[DataRecord] +public sealed class DungeonData +{ + // I hate this but it also significantly reduces yaml bloat if we add like 10 variations on the same set of layers + // e.g. science rooms, engi rooms, cargo rooms all under PlanetBase for example. + // without having to do weird nesting. It also means we don't need to copy-paste the same prototype across several layers + // The alternative is doing like, + // 2 layer prototype, 1 layer with the specified data, 3 layer prototype, 2 layers with specified data, etc. + // As long as we just keep the code clean over time it won't be bad to maintain. + + public static DungeonData Empty = new(); + + public Dictionary Colors = new(); + public Dictionary Entities = new(); + public Dictionary> SpawnGroups = new(); + public Dictionary> Tiles = new(); + public Dictionary Whitelists = new(); + + /// + /// Applies the specified data to this data. + /// + public void Apply(DungeonData data) + { + // Copy-paste moment. + foreach (var color in data.Colors) + { + Colors[color.Key] = color.Value; + } + + foreach (var color in data.Entities) + { + Entities[color.Key] = color.Value; + } + + foreach (var color in data.SpawnGroups) + { + SpawnGroups[color.Key] = color.Value; + } + + foreach (var color in data.Tiles) + { + Tiles[color.Key] = color.Value; + } + + foreach (var color in data.Whitelists) + { + Whitelists[color.Key] = color.Value; + } + } + + public DungeonData Clone() + { + return new DungeonData + { + // Only shallow clones but won't matter for DungeonJob purposes. + Colors = Colors.ShallowClone(), + Entities = Entities.ShallowClone(), + SpawnGroups = SpawnGroups.ShallowClone(), + Tiles = Tiles.ShallowClone(), + Whitelists = Whitelists.ShallowClone(), + }; + } +} + +public enum DungeonDataKey : byte +{ + // Colors + Decals, + + // Entities + Cabling, + CornerWalls, + Fill, + Junction, + Walls, + + // SpawnGroups + CornerClutter, + Entrance, + EntranceFlank, + WallMounts, + Window, + + // Tiles + FallbackTile, + WidenTile, + + // Whitelists + Rooms, +} diff --git a/Content.Shared/Procedural/DungeonGenerators/ExteriorDunGen.cs b/Content.Shared/Procedural/DungeonGenerators/ExteriorDunGen.cs new file mode 100644 index 0000000000..e9a5181f8d --- /dev/null +++ b/Content.Shared/Procedural/DungeonGenerators/ExteriorDunGen.cs @@ -0,0 +1,13 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.Procedural.DungeonGenerators; + +/// +/// Generates the specified config on an exterior tile of the attached dungeon. +/// Useful if you're using or otherwise want a dungeon on the outside of a grid. +/// +public sealed partial class ExteriorDunGen : IDunGenLayer +{ + [DataField(required: true)] + public ProtoId Proto; +} diff --git a/Content.Shared/Procedural/DungeonGenerators/FillGridDunGen.cs b/Content.Shared/Procedural/DungeonGenerators/FillGridDunGen.cs new file mode 100644 index 0000000000..368ec5cc3e --- /dev/null +++ b/Content.Shared/Procedural/DungeonGenerators/FillGridDunGen.cs @@ -0,0 +1,10 @@ +namespace Content.Shared.Procedural.DungeonGenerators; + +/// +/// Fills unreserved tiles with the specified entity prototype. +/// +/// +/// DungeonData keys are: +/// - Fill +/// +public sealed partial class FillGridDunGen : IDunGenLayer; diff --git a/Content.Shared/Procedural/DungeonGenerators/IDunGen.cs b/Content.Shared/Procedural/DungeonGenerators/IDunGen.cs deleted file mode 100644 index 5aa82f1596..0000000000 --- a/Content.Shared/Procedural/DungeonGenerators/IDunGen.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Content.Shared.Procedural.DungeonGenerators; - -[ImplicitDataDefinitionForInheritors] -public partial interface IDunGen -{ - -} diff --git a/Content.Shared/Procedural/DungeonGenerators/NoiseDistanceDunGen.cs b/Content.Shared/Procedural/DungeonGenerators/NoiseDistanceDunGen.cs new file mode 100644 index 0000000000..0dfb3daef8 --- /dev/null +++ b/Content.Shared/Procedural/DungeonGenerators/NoiseDistanceDunGen.cs @@ -0,0 +1,18 @@ +using Content.Shared.Procedural.Distance; + +namespace Content.Shared.Procedural.DungeonGenerators; + +/// +/// Like except with maximum dimensions +/// +public sealed partial class NoiseDistanceDunGen : IDunGenLayer +{ + [DataField] + public IDunGenDistance? DistanceConfig; + + [DataField] + public Vector2i Size; + + [DataField(required: true)] + public List Layers = new(); +} diff --git a/Content.Shared/Procedural/DungeonGenerators/NoiseDunGen.cs b/Content.Shared/Procedural/DungeonGenerators/NoiseDunGen.cs index 3ea0d989a2..56d63bec8f 100644 --- a/Content.Shared/Procedural/DungeonGenerators/NoiseDunGen.cs +++ b/Content.Shared/Procedural/DungeonGenerators/NoiseDunGen.cs @@ -1,15 +1,12 @@ -using Content.Shared.Maps; +using Content.Shared.Procedural.Distance; using Robust.Shared.Noise; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; namespace Content.Shared.Procedural.DungeonGenerators; /// /// Generates dungeon flooring based on the specified noise. /// -public sealed partial class NoiseDunGen : IDunGen +public sealed partial class NoiseDunGen : IDunGenLayer { /* * Floodfills out from 0 until it finds a valid tile. diff --git a/Content.Shared/Procedural/DungeonGenerators/PrefabDunGen.cs b/Content.Shared/Procedural/DungeonGenerators/PrefabDunGen.cs index ef61fff4b0..aeb24d0144 100644 --- a/Content.Shared/Procedural/DungeonGenerators/PrefabDunGen.cs +++ b/Content.Shared/Procedural/DungeonGenerators/PrefabDunGen.cs @@ -1,30 +1,20 @@ -using Content.Shared.Maps; -using Content.Shared.Tag; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; +using Robust.Shared.Prototypes; namespace Content.Shared.Procedural.DungeonGenerators; /// /// Places rooms in pre-selected pack layouts. Chooses rooms from the specified whitelist. /// -public sealed partial class PrefabDunGen : IDunGen +/// +/// DungeonData keys are: +/// - FallbackTile +/// - Rooms +/// +public sealed partial class PrefabDunGen : IDunGenLayer { - /// - /// Rooms need to match any of these tags - /// - [DataField("roomWhitelist", customTypeSerializer:typeof(PrototypeIdListSerializer))] - public List RoomWhitelist = new(); - /// /// Room pack presets we can use for this prefab. /// - [DataField("presets", required: true, customTypeSerializer:typeof(PrototypeIdListSerializer))] - public List Presets = new(); - - /// - /// Fallback tile. - /// - [DataField("tile", customTypeSerializer:typeof(PrototypeIdSerializer))] - public string Tile = "FloorSteel"; + [DataField(required: true)] + public List> Presets = new(); } diff --git a/Content.Shared/Procedural/DungeonGenerators/PrototypeDunGen.cs b/Content.Shared/Procedural/DungeonGenerators/PrototypeDunGen.cs new file mode 100644 index 0000000000..346c60a6cb --- /dev/null +++ b/Content.Shared/Procedural/DungeonGenerators/PrototypeDunGen.cs @@ -0,0 +1,13 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.Procedural.DungeonGenerators; + +/// +/// Runs another . +/// Used for storing data on 1 system. +/// +public sealed partial class PrototypeDunGen : IDunGenLayer +{ + [DataField(required: true)] + public ProtoId Proto; +} diff --git a/Content.Shared/Procedural/DungeonGenerators/ReplaceTileDunGen.cs b/Content.Shared/Procedural/DungeonGenerators/ReplaceTileDunGen.cs new file mode 100644 index 0000000000..64b76b4ccc --- /dev/null +++ b/Content.Shared/Procedural/DungeonGenerators/ReplaceTileDunGen.cs @@ -0,0 +1,30 @@ +using Content.Shared.Maps; +using Robust.Shared.Noise; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Procedural.DungeonGenerators; + +/// +/// Replaces existing tiles if they're not empty. +/// +public sealed partial class ReplaceTileDunGen : IDunGenLayer +{ + /// + /// Chance for a non-variant tile to be used, in case they're too noisy. + /// + [DataField] + public float VariantWeight = 0.1f; + + [DataField(required: true)] + public List Layers = new(); +} + +[DataRecord] +public record struct ReplaceTileLayer +{ + public ProtoId Tile; + + public float Threshold; + + public FastNoiseLite Noise; +} diff --git a/Content.Shared/Procedural/DungeonLayers/MobsDunGen.cs b/Content.Shared/Procedural/DungeonLayers/MobsDunGen.cs new file mode 100644 index 0000000000..30b502efe0 --- /dev/null +++ b/Content.Shared/Procedural/DungeonLayers/MobsDunGen.cs @@ -0,0 +1,21 @@ +using Content.Shared.Storage; + +namespace Content.Shared.Procedural.DungeonLayers; + + +/// +/// Spawns mobs inside of the dungeon randomly. +/// +public sealed partial class MobsDunGen : IDunGenLayer +{ + // Counts separate to config to avoid some duplication. + + [DataField] + public int MinCount = 1; + + [DataField] + public int MaxCount = 1; + + [DataField(required: true)] + public List Groups = new(); +} diff --git a/Content.Shared/Procedural/DungeonLayers/OreDunGen.cs b/Content.Shared/Procedural/DungeonLayers/OreDunGen.cs new file mode 100644 index 0000000000..31bf367d0e --- /dev/null +++ b/Content.Shared/Procedural/DungeonLayers/OreDunGen.cs @@ -0,0 +1,42 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.Procedural.DungeonLayers; + +/// +/// Generates veins inside of the specified dungeon. +/// +/// +/// Generates on top of existing entities for sanity reasons moreso than performance. +/// +public sealed partial class OreDunGen : IDunGenLayer +{ + /// + /// If the vein generation should occur on top of existing entities what are we replacing. + /// + [DataField] + public EntProtoId? Replacement; + + /// + /// Entity to spawn. + /// + [DataField(required: true)] + public EntProtoId Entity; + + /// + /// Maximum amount of group spawns + /// + [DataField] + public int Count = 10; + + /// + /// Minimum entities to spawn in one group. + /// + [DataField] + public int MinGroupSize = 1; + + /// + /// Maximum entities to spawn in one group. + /// + [DataField] + public int MaxGroupSize = 1; +} diff --git a/Content.Shared/Procedural/DungeonRoom.cs b/Content.Shared/Procedural/DungeonRoom.cs index 4802949d2f..0c6af8f23d 100644 --- a/Content.Shared/Procedural/DungeonRoom.cs +++ b/Content.Shared/Procedural/DungeonRoom.cs @@ -2,6 +2,7 @@ using System.Numerics; namespace Content.Shared.Procedural; +// TODO: Cache center and bounds and shit and don't make the caller deal with it. public sealed record DungeonRoom(HashSet Tiles, Vector2 Center, Box2i Bounds, HashSet Exterior) { public readonly List Entrances = new(); diff --git a/Content.Shared/Procedural/IDunGenLayer.cs b/Content.Shared/Procedural/IDunGenLayer.cs new file mode 100644 index 0000000000..a4e8045af1 --- /dev/null +++ b/Content.Shared/Procedural/IDunGenLayer.cs @@ -0,0 +1,7 @@ +namespace Content.Shared.Procedural; + +[ImplicitDataDefinitionForInheritors] +public partial interface IDunGenLayer +{ + +} diff --git a/Content.Shared/Procedural/PostGeneration/AutoCablingDunGen.cs b/Content.Shared/Procedural/PostGeneration/AutoCablingDunGen.cs new file mode 100644 index 0000000000..5afad7edb1 --- /dev/null +++ b/Content.Shared/Procedural/PostGeneration/AutoCablingDunGen.cs @@ -0,0 +1,10 @@ +namespace Content.Shared.Procedural.PostGeneration; + +/// +/// Runs cables throughout the dungeon. +/// +/// +/// DungeonData keys are: +/// - Cabling +/// +public sealed partial class AutoCablingDunGen : IDunGenLayer; diff --git a/Content.Shared/Procedural/PostGeneration/AutoCablingPostGen.cs b/Content.Shared/Procedural/PostGeneration/AutoCablingPostGen.cs deleted file mode 100644 index 8278352b03..0000000000 --- a/Content.Shared/Procedural/PostGeneration/AutoCablingPostGen.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Robust.Shared.Prototypes; - -namespace Content.Shared.Procedural.PostGeneration; - -/// -/// Runs cables throughout the dungeon. -/// -public sealed partial class AutoCablingPostGen : IPostDunGen -{ - [DataField] - public EntProtoId Entity = "CableApcExtension"; -} diff --git a/Content.Shared/Procedural/PostGeneration/BiomePostGen.cs b/Content.Shared/Procedural/PostGeneration/BiomeDunGen.cs similarity index 78% rename from Content.Shared/Procedural/PostGeneration/BiomePostGen.cs rename to Content.Shared/Procedural/PostGeneration/BiomeDunGen.cs index d02de24135..833cf2dec7 100644 --- a/Content.Shared/Procedural/PostGeneration/BiomePostGen.cs +++ b/Content.Shared/Procedural/PostGeneration/BiomeDunGen.cs @@ -1,5 +1,4 @@ using Content.Shared.Parallax.Biomes; -using Content.Shared.Procedural.PostGeneration; using Robust.Shared.Prototypes; namespace Content.Shared.Procedural.PostGeneration; @@ -8,7 +7,7 @@ namespace Content.Shared.Procedural.PostGeneration; /// Generates a biome on top of valid tiles, then removes the biome when done. /// Only works if no existing biome is present. /// -public sealed partial class BiomePostGen : IPostDunGen +public sealed partial class BiomeDunGen : IDunGenLayer { [DataField(required: true)] public ProtoId BiomeTemplate; diff --git a/Content.Shared/Procedural/PostGeneration/BiomeMarkerLayerPostGen.cs b/Content.Shared/Procedural/PostGeneration/BiomeMarkerLayerDunGen.cs similarity index 73% rename from Content.Shared/Procedural/PostGeneration/BiomeMarkerLayerPostGen.cs rename to Content.Shared/Procedural/PostGeneration/BiomeMarkerLayerDunGen.cs index dc64febe7b..af5d7c5d8f 100644 --- a/Content.Shared/Procedural/PostGeneration/BiomeMarkerLayerPostGen.cs +++ b/Content.Shared/Procedural/PostGeneration/BiomeMarkerLayerDunGen.cs @@ -1,5 +1,3 @@ -using Content.Shared.Parallax.Biomes.Markers; -using Content.Shared.Procedural.PostGeneration; using Content.Shared.Random; using Robust.Shared.Prototypes; @@ -8,7 +6,7 @@ namespace Content.Shared.Procedural.PostGeneration; /// /// Spawns the specified marker layer on top of the dungeon rooms. /// -public sealed partial class BiomeMarkerLayerPostGen : IPostDunGen +public sealed partial class BiomeMarkerLayerDunGen : IDunGenLayer { /// /// How many times to spawn marker layers; can duplicate. diff --git a/Content.Shared/Procedural/PostGeneration/BoundaryWallDunGen.cs b/Content.Shared/Procedural/PostGeneration/BoundaryWallDunGen.cs new file mode 100644 index 0000000000..4151527f8a --- /dev/null +++ b/Content.Shared/Procedural/PostGeneration/BoundaryWallDunGen.cs @@ -0,0 +1,23 @@ +namespace Content.Shared.Procedural.PostGeneration; + +/// +/// Iterates room edges and places the relevant tiles and walls on any free indices. +/// +/// +/// Dungeon data keys are: +/// - CornerWalls (Optional) +/// - FallbackTile +/// - Walls +/// +public sealed partial class BoundaryWallDunGen : IDunGenLayer +{ + [DataField] + public BoundaryWallFlags Flags = BoundaryWallFlags.Corridors | BoundaryWallFlags.Rooms; +} + +[Flags] +public enum BoundaryWallFlags : byte +{ + Rooms = 1 << 0, + Corridors = 1 << 1, +} diff --git a/Content.Shared/Procedural/PostGeneration/BoundaryWallPostGen.cs b/Content.Shared/Procedural/PostGeneration/BoundaryWallPostGen.cs deleted file mode 100644 index 390ff42fee..0000000000 --- a/Content.Shared/Procedural/PostGeneration/BoundaryWallPostGen.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Content.Shared.Maps; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Shared.Procedural.PostGeneration; - -/// -/// Iterates room edges and places the relevant tiles and walls on any free indices. -/// -public sealed partial class BoundaryWallPostGen : IPostDunGen -{ - [DataField] - public ProtoId Tile = "FloorSteel"; - - [DataField] - public EntProtoId Wall = "WallSolid"; - - /// - /// Walls to use in corners if applicable. - /// - [DataField] - public string? CornerWall; - - [DataField] - public BoundaryWallFlags Flags = BoundaryWallFlags.Corridors | BoundaryWallFlags.Rooms; -} - -[Flags] -public enum BoundaryWallFlags : byte -{ - Rooms = 1 << 0, - Corridors = 1 << 1, -} diff --git a/Content.Shared/Procedural/PostGeneration/CornerClutterDunGen.cs b/Content.Shared/Procedural/PostGeneration/CornerClutterDunGen.cs new file mode 100644 index 0000000000..2a904281c8 --- /dev/null +++ b/Content.Shared/Procedural/PostGeneration/CornerClutterDunGen.cs @@ -0,0 +1,14 @@ +namespace Content.Shared.Procedural.PostGeneration; + +/// +/// Spawns entities inside corners. +/// +/// +/// Dungeon data keys are: +/// - CornerClutter +/// +public sealed partial class CornerClutterDunGen : IDunGenLayer +{ + [DataField] + public float Chance = 0.50f; +} diff --git a/Content.Shared/Procedural/PostGeneration/CornerClutterPostGen.cs b/Content.Shared/Procedural/PostGeneration/CornerClutterPostGen.cs deleted file mode 100644 index a16c7f9ab3..0000000000 --- a/Content.Shared/Procedural/PostGeneration/CornerClutterPostGen.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Content.Shared.Storage; - -namespace Content.Shared.Procedural.PostGeneration; - -/// -/// Spawns entities inside corners. -/// -public sealed partial class CornerClutterPostGen : IPostDunGen -{ - [DataField] - public float Chance = 0.50f; - - /// - /// The default starting bulbs - /// - [DataField(required: true)] - public List Contents = new(); -} diff --git a/Content.Shared/Procedural/PostGeneration/CorridorClutterPostGen.cs b/Content.Shared/Procedural/PostGeneration/CorridorClutterDunGen.cs similarity index 85% rename from Content.Shared/Procedural/PostGeneration/CorridorClutterPostGen.cs rename to Content.Shared/Procedural/PostGeneration/CorridorClutterDunGen.cs index a8a74ba6cc..5b397b40df 100644 --- a/Content.Shared/Procedural/PostGeneration/CorridorClutterPostGen.cs +++ b/Content.Shared/Procedural/PostGeneration/CorridorClutterDunGen.cs @@ -5,7 +5,7 @@ namespace Content.Shared.Procedural.PostGeneration; /// /// Adds entities randomly to the corridors. /// -public sealed partial class CorridorClutterPostGen : IPostDunGen +public sealed partial class CorridorClutterDunGen : IDunGenLayer { [DataField] public float Chance = 0.05f; diff --git a/Content.Shared/Procedural/PostGeneration/CorridorDecalSkirtingPostGen.cs b/Content.Shared/Procedural/PostGeneration/CorridorDecalSkirtingDunGen.cs similarity index 72% rename from Content.Shared/Procedural/PostGeneration/CorridorDecalSkirtingPostGen.cs rename to Content.Shared/Procedural/PostGeneration/CorridorDecalSkirtingDunGen.cs index 4b139a8be6..e609043655 100644 --- a/Content.Shared/Procedural/PostGeneration/CorridorDecalSkirtingPostGen.cs +++ b/Content.Shared/Procedural/PostGeneration/CorridorDecalSkirtingDunGen.cs @@ -7,29 +7,23 @@ namespace Content.Shared.Procedural.PostGeneration; /// /// Applies decal skirting to corridors. /// -public sealed partial class CorridorDecalSkirtingPostGen : IPostDunGen +public sealed partial class CorridorDecalSkirtingDunGen : IDunGenLayer { - /// - /// Color to apply to decals. - /// - [DataField("color")] - public Color? Color; - /// /// Decal where 1 edge is found. /// - [DataField("cardinalDecals")] + [DataField] public Dictionary CardinalDecals = new(); /// /// Decal where 1 corner edge is found. /// - [DataField("pocketDecals")] + [DataField] public Dictionary PocketDecals = new(); /// /// Decal where 2 or 3 edges are found. /// - [DataField("cornerDecals")] + [DataField] public Dictionary CornerDecals = new(); } diff --git a/Content.Shared/Procedural/PostGeneration/CorridorPostGen.cs b/Content.Shared/Procedural/PostGeneration/CorridorDunGen.cs similarity index 73% rename from Content.Shared/Procedural/PostGeneration/CorridorPostGen.cs rename to Content.Shared/Procedural/PostGeneration/CorridorDunGen.cs index 705ae99dce..6d75cd9cb2 100644 --- a/Content.Shared/Procedural/PostGeneration/CorridorPostGen.cs +++ b/Content.Shared/Procedural/PostGeneration/CorridorDunGen.cs @@ -1,12 +1,13 @@ -using Content.Shared.Maps; -using Robust.Shared.Prototypes; - namespace Content.Shared.Procedural.PostGeneration; /// /// Connects room entrances via corridor segments. /// -public sealed partial class CorridorPostGen : IPostDunGen +/// +/// Dungeon data keys are: +/// - FallbackTile +/// +public sealed partial class CorridorDunGen : IDunGenLayer { /// /// How far we're allowed to generate a corridor before calling it. @@ -17,9 +18,6 @@ public sealed partial class CorridorPostGen : IPostDunGen [DataField] public int PathLimit = 2048; - [DataField] - public ProtoId Tile = "FloorSteel"; - /// /// How wide to make the corridor. /// diff --git a/Content.Shared/Procedural/PostGeneration/DungeonEntranceDunGen.cs b/Content.Shared/Procedural/PostGeneration/DungeonEntranceDunGen.cs new file mode 100644 index 0000000000..40cc95f5fc --- /dev/null +++ b/Content.Shared/Procedural/PostGeneration/DungeonEntranceDunGen.cs @@ -0,0 +1,18 @@ +namespace Content.Shared.Procedural.PostGeneration; + +/// +/// Selects [count] rooms and places external doors to them. +/// +/// +/// Dungeon data keys are: +/// - Entrance +/// - FallbackTile +/// +public sealed partial class DungeonEntranceDunGen : IDunGenLayer +{ + /// + /// How many rooms we place doors on. + /// + [DataField] + public int Count = 1; +} diff --git a/Content.Shared/Procedural/PostGeneration/DungeonEntrancePostGen.cs b/Content.Shared/Procedural/PostGeneration/DungeonEntrancePostGen.cs deleted file mode 100644 index 3398b51317..0000000000 --- a/Content.Shared/Procedural/PostGeneration/DungeonEntrancePostGen.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Content.Shared.Maps; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; - -namespace Content.Shared.Procedural.PostGeneration; - -/// -/// Selects [count] rooms and places external doors to them. -/// -public sealed partial class DungeonEntrancePostGen : IPostDunGen -{ - /// - /// How many rooms we place doors on. - /// - [DataField("count")] - public int Count = 1; - - [DataField("entities", customTypeSerializer: typeof(PrototypeIdListSerializer))] - public List Entities = new() - { - "CableApcExtension", - "AirlockGlass", - }; - - [DataField("tile", customTypeSerializer:typeof(PrototypeIdSerializer))] - public string Tile = "FloorSteel"; -} diff --git a/Content.Shared/Procedural/PostGeneration/EntranceFlankDunGen.cs b/Content.Shared/Procedural/PostGeneration/EntranceFlankDunGen.cs new file mode 100644 index 0000000000..27baa48ec6 --- /dev/null +++ b/Content.Shared/Procedural/PostGeneration/EntranceFlankDunGen.cs @@ -0,0 +1,11 @@ +namespace Content.Shared.Procedural.PostGeneration; + +/// +/// Spawns entities on either side of an entrance. +/// +/// +/// Dungeon data keys are: +/// - FallbackTile +/// - +/// +public sealed partial class EntranceFlankDunGen : IDunGenLayer; diff --git a/Content.Shared/Procedural/PostGeneration/EntranceFlankPostGen.cs b/Content.Shared/Procedural/PostGeneration/EntranceFlankPostGen.cs deleted file mode 100644 index 96e9bd5d6d..0000000000 --- a/Content.Shared/Procedural/PostGeneration/EntranceFlankPostGen.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Content.Shared.Maps; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Shared.Procedural.PostGeneration; - -/// -/// Spawns entities on either side of an entrance. -/// -public sealed partial class EntranceFlankPostGen : IPostDunGen -{ - [DataField("tile", customTypeSerializer:typeof(PrototypeIdSerializer))] - public string Tile = "FloorSteel"; - - [DataField("entities")] - public List Entities = new(); -} diff --git a/Content.Shared/Procedural/PostGeneration/ExternalWindowDunGen.cs b/Content.Shared/Procedural/PostGeneration/ExternalWindowDunGen.cs new file mode 100644 index 0000000000..0b29344b90 --- /dev/null +++ b/Content.Shared/Procedural/PostGeneration/ExternalWindowDunGen.cs @@ -0,0 +1,11 @@ +namespace Content.Shared.Procedural.PostGeneration; + +/// +/// If external areas are found will try to generate windows. +/// +/// +/// Dungeon data keys are: +/// - EntranceFlank +/// - FallbackTile +/// +public sealed partial class ExternalWindowDunGen : IDunGenLayer; diff --git a/Content.Shared/Procedural/PostGeneration/ExternalWindowPostGen.cs b/Content.Shared/Procedural/PostGeneration/ExternalWindowPostGen.cs deleted file mode 100644 index d5580baeaa..0000000000 --- a/Content.Shared/Procedural/PostGeneration/ExternalWindowPostGen.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Content.Shared.Maps; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; - -namespace Content.Shared.Procedural.PostGeneration; - -/// -/// If external areas are found will try to generate windows. -/// -public sealed partial class ExternalWindowPostGen : IPostDunGen -{ - [DataField("entities", customTypeSerializer: typeof(PrototypeIdListSerializer))] - public List Entities = new() - { - "Grille", - "Window", - }; - - [DataField("tile", customTypeSerializer:typeof(PrototypeIdSerializer))] - public string Tile = "FloorSteel"; -} diff --git a/Content.Shared/Procedural/PostGeneration/IPostDunGen.cs b/Content.Shared/Procedural/PostGeneration/IPostDunGen.cs deleted file mode 100644 index b55cab8e63..0000000000 --- a/Content.Shared/Procedural/PostGeneration/IPostDunGen.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Content.Shared.Procedural.PostGeneration; - -/// -/// Ran after generating dungeon rooms. Can be used for additional loot, contents, etc. -/// -[ImplicitDataDefinitionForInheritors] -public partial interface IPostDunGen -{ - -} diff --git a/Content.Shared/Procedural/PostGeneration/InternalWindowDunGen.cs b/Content.Shared/Procedural/PostGeneration/InternalWindowDunGen.cs new file mode 100644 index 0000000000..11b1c6a785 --- /dev/null +++ b/Content.Shared/Procedural/PostGeneration/InternalWindowDunGen.cs @@ -0,0 +1,11 @@ +namespace Content.Shared.Procedural.PostGeneration; + +/// +/// If internal areas are found will try to generate windows. +/// +/// +/// Dungeon data keys are: +/// - FallbackTile +/// - Window +/// +public sealed partial class InternalWindowDunGen : IDunGenLayer; diff --git a/Content.Shared/Procedural/PostGeneration/InternalWindowPostGen.cs b/Content.Shared/Procedural/PostGeneration/InternalWindowPostGen.cs deleted file mode 100644 index 4c6223eb92..0000000000 --- a/Content.Shared/Procedural/PostGeneration/InternalWindowPostGen.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Content.Shared.Maps; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; - -namespace Content.Shared.Procedural.PostGeneration; - -/// -/// If internal areas are found will try to generate windows. -/// -public sealed partial class InternalWindowPostGen : IPostDunGen -{ - [DataField("entities", customTypeSerializer: typeof(PrototypeIdListSerializer))] - public List Entities = new() - { - "Grille", - "Window", - }; - - [DataField("tile", customTypeSerializer:typeof(PrototypeIdSerializer))] - public string Tile = "FloorSteel"; -} diff --git a/Content.Shared/Procedural/PostGeneration/JunctionDunGen.cs b/Content.Shared/Procedural/PostGeneration/JunctionDunGen.cs new file mode 100644 index 0000000000..899f271621 --- /dev/null +++ b/Content.Shared/Procedural/PostGeneration/JunctionDunGen.cs @@ -0,0 +1,18 @@ +namespace Content.Shared.Procedural.PostGeneration; + +/// +/// Places the specified entities at junction areas. +/// +/// +/// Dungeon data keys are: +/// - Entrance +/// - FallbackTile +/// +public sealed partial class JunctionDunGen : IDunGenLayer +{ + /// + /// Width to check for junctions. + /// + [DataField] + public int Width = 3; +} diff --git a/Content.Shared/Procedural/PostGeneration/JunctionPostGen.cs b/Content.Shared/Procedural/PostGeneration/JunctionPostGen.cs deleted file mode 100644 index 5c4cf43b7f..0000000000 --- a/Content.Shared/Procedural/PostGeneration/JunctionPostGen.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Content.Shared.Maps; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; - -namespace Content.Shared.Procedural.PostGeneration; - -/// -/// Places the specified entities at junction areas. -/// -public sealed partial class JunctionPostGen : IPostDunGen -{ - /// - /// Width to check for junctions. - /// - [DataField("width")] - public int Width = 3; - - [DataField("tile", customTypeSerializer:typeof(PrototypeIdSerializer))] - public string Tile = "FloorSteel"; - - [DataField("entities", customTypeSerializer: typeof(PrototypeIdListSerializer))] - public List Entities = new() - { - "CableApcExtension", - "AirlockGlass" - }; -} diff --git a/Content.Shared/Procedural/PostGeneration/MiddleConnectionDunGen.cs b/Content.Shared/Procedural/PostGeneration/MiddleConnectionDunGen.cs new file mode 100644 index 0000000000..a5758c1498 --- /dev/null +++ b/Content.Shared/Procedural/PostGeneration/MiddleConnectionDunGen.cs @@ -0,0 +1,19 @@ +namespace Content.Shared.Procedural.PostGeneration; + +/// +/// Places the specified entities on the middle connections between rooms +/// +public sealed partial class MiddleConnectionDunGen : IDunGenLayer +{ + /// + /// How much overlap there needs to be between 2 rooms exactly. + /// + [DataField] + public int OverlapCount = -1; + + /// + /// How many connections to spawn between rooms. + /// + [DataField] + public int Count = 1; +} diff --git a/Content.Shared/Procedural/PostGeneration/MiddleConnectionPostGen.cs b/Content.Shared/Procedural/PostGeneration/MiddleConnectionPostGen.cs deleted file mode 100644 index d29a65434c..0000000000 --- a/Content.Shared/Procedural/PostGeneration/MiddleConnectionPostGen.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Content.Shared.Maps; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; - -namespace Content.Shared.Procedural.PostGeneration; - -/// -/// Places the specified entities on the middle connections between rooms -/// -public sealed partial class MiddleConnectionPostGen : IPostDunGen -{ - /// - /// How much overlap there needs to be between 2 rooms exactly. - /// - [DataField("overlapCount")] - public int OverlapCount = -1; - - /// - /// How many connections to spawn between rooms. - /// - [DataField("count")] - public int Count = 1; - - [DataField("tile", customTypeSerializer:typeof(PrototypeIdSerializer))] - public string Tile = "FloorSteel"; - - [DataField("entities", customTypeSerializer: typeof(PrototypeIdListSerializer))] - public List Entities = new() - { - "CableApcExtension", - "AirlockGlass" - }; - - /// - /// If overlap > 1 then what should spawn on the edges. - /// - [DataField("edgeEntities")] public List EdgeEntities = new(); -} diff --git a/Content.Shared/Procedural/PostGeneration/RoomEntranceDunGen.cs b/Content.Shared/Procedural/PostGeneration/RoomEntranceDunGen.cs new file mode 100644 index 0000000000..d3b5672dcb --- /dev/null +++ b/Content.Shared/Procedural/PostGeneration/RoomEntranceDunGen.cs @@ -0,0 +1,11 @@ +namespace Content.Shared.Procedural.PostGeneration; + +/// +/// Places tiles / entities onto room entrances. +/// +/// +/// DungeonData keys are: +/// - Entrance +/// - FallbackTile +/// +public sealed partial class RoomEntranceDunGen : IDunGenLayer; diff --git a/Content.Shared/Procedural/PostGeneration/RoomEntrancePostGen.cs b/Content.Shared/Procedural/PostGeneration/RoomEntrancePostGen.cs deleted file mode 100644 index 5fd78b0540..0000000000 --- a/Content.Shared/Procedural/PostGeneration/RoomEntrancePostGen.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Content.Shared.Maps; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; - -namespace Content.Shared.Procedural.PostGeneration; - -/// -/// Places tiles / entities onto room entrances. -/// -public sealed partial class RoomEntrancePostGen : IPostDunGen -{ - [DataField("entities", customTypeSerializer: typeof(PrototypeIdListSerializer))] - public List Entities = new() - { - "CableApcExtension", - "AirlockGlass", - }; - - [DataField("tile", customTypeSerializer:typeof(PrototypeIdSerializer))] - public string Tile = "FloorSteel"; -} diff --git a/Content.Shared/Procedural/PostGeneration/SplineDungeonConnectorDunGen.cs b/Content.Shared/Procedural/PostGeneration/SplineDungeonConnectorDunGen.cs new file mode 100644 index 0000000000..ec8349c671 --- /dev/null +++ b/Content.Shared/Procedural/PostGeneration/SplineDungeonConnectorDunGen.cs @@ -0,0 +1,19 @@ +namespace Content.Shared.Procedural.PostGeneration; + +/// +/// Connects dungeons via points that get subdivided. +/// +public sealed partial class SplineDungeonConnectorDunGen : IDunGenLayer +{ + /// + /// Will divide the distance between the start and end points so that no subdivision is more than these metres away. + /// + [DataField] + public int DivisionDistance = 10; + + /// + /// How much each subdivision can vary from the middle. + /// + [DataField] + public float VarianceMax = 0.35f; +} diff --git a/Content.Shared/Procedural/PostGeneration/WallMountDunGen.cs b/Content.Shared/Procedural/PostGeneration/WallMountDunGen.cs new file mode 100644 index 0000000000..a5c790cb22 --- /dev/null +++ b/Content.Shared/Procedural/PostGeneration/WallMountDunGen.cs @@ -0,0 +1,13 @@ +namespace Content.Shared.Procedural.PostGeneration; + +/// +/// Spawns on the boundary tiles of rooms. +/// +public sealed partial class WallMountDunGen : IDunGenLayer +{ + /// + /// Chance per free tile to spawn a wallmount. + /// + [DataField] + public double Prob = 0.1; +} diff --git a/Content.Shared/Procedural/PostGeneration/WallMountPostGen.cs b/Content.Shared/Procedural/PostGeneration/WallMountPostGen.cs deleted file mode 100644 index 1fbdedf561..0000000000 --- a/Content.Shared/Procedural/PostGeneration/WallMountPostGen.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Content.Shared.Maps; -using Content.Shared.Storage; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Shared.Procedural.PostGeneration; - -/// -/// Spawns on the boundary tiles of rooms. -/// -public sealed partial class WallMountPostGen : IPostDunGen -{ - [DataField("tile", customTypeSerializer:typeof(PrototypeIdSerializer))] - public string Tile = "FloorSteel"; - - [DataField("spawns")] - public List Spawns = new(); - - /// - /// Chance per free tile to spawn a wallmount. - /// - [DataField("prob")] - public double Prob = 0.1; -} diff --git a/Content.Shared/Procedural/PostGeneration/WormCorridorPostGen.cs b/Content.Shared/Procedural/PostGeneration/WormCorridorDunGen.cs similarity index 73% rename from Content.Shared/Procedural/PostGeneration/WormCorridorPostGen.cs rename to Content.Shared/Procedural/PostGeneration/WormCorridorDunGen.cs index c57d92ef95..b71e845a73 100644 --- a/Content.Shared/Procedural/PostGeneration/WormCorridorPostGen.cs +++ b/Content.Shared/Procedural/PostGeneration/WormCorridorDunGen.cs @@ -1,14 +1,10 @@ -using Content.Shared.Maps; -using Content.Shared.Procedural.DungeonGenerators; -using Robust.Shared.Prototypes; - namespace Content.Shared.Procedural.PostGeneration; // Ime a worm /// /// Generates worm corridors. /// -public sealed partial class WormCorridorPostGen : IPostDunGen +public sealed partial class WormCorridorDunGen : IDunGenLayer { [DataField] public int PathLimit = 2048; @@ -31,9 +27,6 @@ public sealed partial class WormCorridorPostGen : IPostDunGen [DataField] public Angle MaxAngleChange = Angle.FromDegrees(45); - [DataField] - public ProtoId Tile = "FloorSteel"; - /// /// How wide to make the corridor. /// diff --git a/Content.Shared/Salvage/SharedSalvageSystem.Magnet.cs b/Content.Shared/Salvage/SharedSalvageSystem.Magnet.cs index 81390e5f65..62edb36db9 100644 --- a/Content.Shared/Salvage/SharedSalvageSystem.Magnet.cs +++ b/Content.Shared/Salvage/SharedSalvageSystem.Magnet.cs @@ -32,14 +32,14 @@ public abstract partial class SharedSalvageSystem var layers = new Dictionary(); // If we ever add more random layers will need to Next on these. - foreach (var layer in configProto.PostGeneration) + foreach (var layer in configProto.Layers) { switch (layer) { - case BiomePostGen: + case BiomeDunGen: rand.Next(); break; - case BiomeMarkerLayerPostGen marker: + case BiomeMarkerLayerDunGen marker: for (var i = 0; i < marker.Count; i++) { var proto = _proto.Index(marker.MarkerTemplate).Pick(rand); diff --git a/Content.Shared/Shuttles/Systems/SharedShuttleSystem.cs b/Content.Shared/Shuttles/Systems/SharedShuttleSystem.cs index a382e943ff..db2cbaa138 100644 --- a/Content.Shared/Shuttles/Systems/SharedShuttleSystem.cs +++ b/Content.Shared/Shuttles/Systems/SharedShuttleSystem.cs @@ -18,7 +18,7 @@ public abstract partial class SharedShuttleSystem : EntitySystem [Dependency] protected readonly SharedTransformSystem XformSystem = default!; [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; - public const float FTLRange = 512f; + public const float FTLRange = 256f; public const float FTLBufferRange = 8f; private EntityQuery _gridQuery; diff --git a/Content.Shared/Storage/EntitySpawnEntry.cs b/Content.Shared/Storage/EntitySpawnEntry.cs index 792459c72f..6e24681c2d 100644 --- a/Content.Shared/Storage/EntitySpawnEntry.cs +++ b/Content.Shared/Storage/EntitySpawnEntry.cs @@ -5,6 +5,19 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototy namespace Content.Shared.Storage; +/// +/// Prototype wrapper around +/// +[Prototype] +public sealed class EntitySpawnEntryPrototype : IPrototype +{ + [IdDataField] + public string ID { get; } = string.Empty; + + [DataField] + public List Entries = new(); +} + /// /// Dictates a list of items that can be spawned. /// diff --git a/Resources/Prototypes/Entities/Stations/base.yml b/Resources/Prototypes/Entities/Stations/base.yml index 8a0d6c4003..7b58091588 100644 --- a/Resources/Prototypes/Entities/Stations/base.yml +++ b/Resources/Prototypes/Entities/Stations/base.yml @@ -46,17 +46,26 @@ path: /Maps/Shuttles/cargo.yml - type: GridSpawn groups: - trade: + vgroid: !type:DungeonSpawnGroup + minimumDistance: 1000 + nameDataset: names_borer + addComponents: + - type: Gravity + enabled: true + inherent: true + protos: + - VGRoid + trade: !type:GridSpawnGroup addComponents: - type: ProtectedGrid - type: TradeStation paths: - /Maps/Shuttles/trading_outpost.yml - mining: + mining: !type:GridSpawnGroup paths: - /Maps/Shuttles/mining.yml # Spawn last - ruins: + ruins: !type:GridSpawnGroup hide: true nameGrid: true minCount: 2 diff --git a/Resources/Prototypes/Entities/Structures/Walls/asteroid.yml b/Resources/Prototypes/Entities/Structures/Walls/asteroid.yml index d131805bf5..e14bf26e0d 100644 --- a/Resources/Prototypes/Entities/Structures/Walls/asteroid.yml +++ b/Resources/Prototypes/Entities/Structures/Walls/asteroid.yml @@ -1,5 +1,7 @@ #TODO: Someone should probably move the ore vein prototypes into their own file, or otherwise split this up in some way. This should not be 1.5k lines long. +# Anyway +# See WallRock variants for the remappings. #Asteroid rock - type: entity @@ -639,21 +641,28 @@ description: An ore vein rich with coal. suffix: Coal components: - - type: OreVein - oreChance: 1.0 - currentOre: OreCoal - - type: Sprite - layers: - - state: rock - - map: [ "enum.EdgeLayer.South" ] - state: rock_south - - map: [ "enum.EdgeLayer.East" ] - state: rock_east - - map: [ "enum.EdgeLayer.North" ] - state: rock_north - - map: [ "enum.EdgeLayer.West" ] - state: rock_west - - state: rock_coal + - type: EntityRemap + mask: + AsteroidRock: AsteroidRockCoal + WallRockBasalt: WallRockBasaltCoal + WallRockChromite: WallRockChromiteCoal + WallRockSand: WallRockSandCoal + WallRockSnow: WallRockSnowCoal + - type: OreVein + oreChance: 1.0 + currentOre: OreCoal + - type: Sprite + layers: + - state: rock + - map: [ "enum.EdgeLayer.South" ] + state: rock_south + - map: [ "enum.EdgeLayer.East" ] + state: rock_east + - map: [ "enum.EdgeLayer.North" ] + state: rock_north + - map: [ "enum.EdgeLayer.West" ] + state: rock_west + - state: rock_coal - type: entity id: WallRockGold @@ -661,21 +670,28 @@ description: An ore vein rich with gold. suffix: Gold components: - - type: OreVein - oreChance: 1.0 - currentOre: OreGold - - type: Sprite - layers: - - state: rock - - map: [ "enum.EdgeLayer.South" ] - state: rock_south - - map: [ "enum.EdgeLayer.East" ] - state: rock_east - - map: [ "enum.EdgeLayer.North" ] - state: rock_north - - map: [ "enum.EdgeLayer.West" ] - state: rock_west - - state: rock_gold + - type: EntityRemap + mask: + AsteroidRock: AsteroidRockGold + WallRockBasalt: WallRockBasaltGold + WallRockChromite: WallRockChromiteGold + WallRockSand: WallRockSandGold + WallRockSnow: WallRockSnowGold + - type: OreVein + oreChance: 1.0 + currentOre: OreGold + - type: Sprite + layers: + - state: rock + - map: [ "enum.EdgeLayer.South" ] + state: rock_south + - map: [ "enum.EdgeLayer.East" ] + state: rock_east + - map: [ "enum.EdgeLayer.North" ] + state: rock_north + - map: [ "enum.EdgeLayer.West" ] + state: rock_west + - state: rock_gold - type: entity id: WallRockPlasma @@ -683,21 +699,28 @@ description: An ore vein rich with plasma. suffix: Plasma components: - - type: OreVein - oreChance: 1.0 - currentOre: OrePlasma - - type: Sprite - layers: - - state: rock - - map: [ "enum.EdgeLayer.South" ] - state: rock_south - - map: [ "enum.EdgeLayer.East" ] - state: rock_east - - map: [ "enum.EdgeLayer.North" ] - state: rock_north - - map: [ "enum.EdgeLayer.West" ] - state: rock_west - - state: rock_phoron + - type: EntityRemap + mask: + AsteroidRock: AsteroidRockPlasma + WallRockBasalt: WallRockBasaltPlasma + WallRockChromite: WallRockChromitePlasma + WallRockSand: WallRockSandPlasma + WallRockSnow: WallRockSnowPlasma + - type: OreVein + oreChance: 1.0 + currentOre: OrePlasma + - type: Sprite + layers: + - state: rock + - map: [ "enum.EdgeLayer.South" ] + state: rock_south + - map: [ "enum.EdgeLayer.East" ] + state: rock_east + - map: [ "enum.EdgeLayer.North" ] + state: rock_north + - map: [ "enum.EdgeLayer.West" ] + state: rock_west + - state: rock_phoron - type: entity id: WallRockQuartz @@ -705,21 +728,28 @@ description: An ore vein rich with quartz. suffix: Quartz components: - - type: OreVein - oreChance: 1.0 - currentOre: OreSpaceQuartz - - type: Sprite - layers: - - state: rock - - map: [ "enum.EdgeLayer.South" ] - state: rock_south - - map: [ "enum.EdgeLayer.East" ] - state: rock_east - - map: [ "enum.EdgeLayer.North" ] - state: rock_north - - map: [ "enum.EdgeLayer.West" ] - state: rock_west - - state: rock_quartz + - type: EntityRemap + mask: + AsteroidRock: AsteroidRockQuartz + WallRockBasalt: WallRockBasaltQuartz + WallRockChromite: WallRockChromiteQuartz + WallRockSand: WallRockSandQuartz + WallRockSnow: WallRockSnowQuartz + - type: OreVein + oreChance: 1.0 + currentOre: OreSpaceQuartz + - type: Sprite + layers: + - state: rock + - map: [ "enum.EdgeLayer.South" ] + state: rock_south + - map: [ "enum.EdgeLayer.East" ] + state: rock_east + - map: [ "enum.EdgeLayer.North" ] + state: rock_north + - map: [ "enum.EdgeLayer.West" ] + state: rock_west + - state: rock_quartz - type: entity id: WallRockSilver @@ -727,21 +757,28 @@ description: An ore vein rich with silver. suffix: Silver components: - - type: OreVein - oreChance: 1.0 - currentOre: OreSilver - - type: Sprite - layers: - - state: rock - - map: [ "enum.EdgeLayer.South" ] - state: rock_south - - map: [ "enum.EdgeLayer.East" ] - state: rock_east - - map: [ "enum.EdgeLayer.North" ] - state: rock_north - - map: [ "enum.EdgeLayer.West" ] - state: rock_west - - state: rock_silver + - type: EntityRemap + mask: + AsteroidRock: AsteroidRockSilver + WallRockBasalt: WallRockBasaltSilver + WallRockChromite: WallRockChromiteSilver + WallRockSand: WallRockSandSilver + WallRockSnow: WallRockSnowSilver + - type: OreVein + oreChance: 1.0 + currentOre: OreSilver + - type: Sprite + layers: + - state: rock + - map: [ "enum.EdgeLayer.South" ] + state: rock_south + - map: [ "enum.EdgeLayer.East" ] + state: rock_east + - map: [ "enum.EdgeLayer.North" ] + state: rock_north + - map: [ "enum.EdgeLayer.West" ] + state: rock_west + - state: rock_silver # Yes I know it drops steel but we may get smelting at some point - type: entity @@ -750,6 +787,13 @@ description: An ore vein rich with iron. suffix: Iron components: + - type: EntityRemap + mask: + AsteroidRock: AsteroidRockTin + WallRockBasalt: WallRockBasaltTin + WallRockChromite: WallRockChromiteTin + WallRockSand: WallRockSandTin + WallRockSnow: WallRockSnowTin - type: OreVein oreChance: 1.0 currentOre: OreSteel @@ -772,21 +816,28 @@ description: An ore vein rich with uranium. suffix: Uranium components: - - type: OreVein - oreChance: 1.0 - currentOre: OreUranium - - type: Sprite - layers: - - state: rock - - map: [ "enum.EdgeLayer.South" ] - state: rock_south - - map: [ "enum.EdgeLayer.East" ] - state: rock_east - - map: [ "enum.EdgeLayer.North" ] - state: rock_north - - map: [ "enum.EdgeLayer.West" ] - state: rock_west - - state: rock_uranium + - type: EntityRemap + mask: + AsteroidRock: AsteroidRockUranium + WallRockBasalt: WallRockBasaltUranium + WallRockChromite: WallRockChromiteUranium + WallRockSand: WallRockSandUranium + WallRockSnow: WallRockSnowUranium + - type: OreVein + oreChance: 1.0 + currentOre: OreUranium + - type: Sprite + layers: + - state: rock + - map: [ "enum.EdgeLayer.South" ] + state: rock_south + - map: [ "enum.EdgeLayer.East" ] + state: rock_east + - map: [ "enum.EdgeLayer.North" ] + state: rock_north + - map: [ "enum.EdgeLayer.West" ] + state: rock_west + - state: rock_uranium - type: entity @@ -795,21 +846,28 @@ description: An ore vein rich with bananium. suffix: Bananium components: - - type: OreVein - oreChance: 1.0 - currentOre: OreBananium - - type: Sprite - layers: - - state: rock - - map: [ "enum.EdgeLayer.South" ] - state: rock_south - - map: [ "enum.EdgeLayer.East" ] - state: rock_east - - map: [ "enum.EdgeLayer.North" ] - state: rock_north - - map: [ "enum.EdgeLayer.West" ] - state: rock_west - - state: rock_bananium + - type: EntityRemap + mask: + AsteroidRock: AsteroidRockBananium + WallRockBasalt: WallRockBasaltBananium + WallRockChromite: WallRockChromiteBananium + WallRockSand: WallRockSandBananium + WallRockSnow: WallRockSnowBananium + - type: OreVein + oreChance: 1.0 + currentOre: OreBananium + - type: Sprite + layers: + - state: rock + - map: [ "enum.EdgeLayer.South" ] + state: rock_south + - map: [ "enum.EdgeLayer.East" ] + state: rock_east + - map: [ "enum.EdgeLayer.North" ] + state: rock_north + - map: [ "enum.EdgeLayer.West" ] + state: rock_west + - state: rock_bananium - type: entity id: WallRockArtifactFragment @@ -817,21 +875,28 @@ description: A rock wall. What's that sticking out of it? suffix: Artifact Fragment components: - - type: OreVein - oreChance: 1.0 - currentOre: OreArtifactFragment - - type: Sprite - layers: - - state: rock - - map: [ "enum.EdgeLayer.South" ] - state: rock_south - - map: [ "enum.EdgeLayer.East" ] - state: rock_east - - map: [ "enum.EdgeLayer.North" ] - state: rock_north - - map: [ "enum.EdgeLayer.West" ] - state: rock_west - - state: rock_artifact_fragment + - type: EntityRemap + mask: + AsteroidRock: AsteroidRockArtifactFragment + WallRockBasalt: WallRockBasaltArtifactFragment + WallRockChromite: WallRockChromiteArtifactFragment + WallRockSand: WallRockSandArtifactFragment + WallRockSnow: WallRockSnowArtifactFragment + - type: OreVein + oreChance: 1.0 + currentOre: OreArtifactFragment + - type: Sprite + layers: + - state: rock + - map: [ "enum.EdgeLayer.South" ] + state: rock_south + - map: [ "enum.EdgeLayer.East" ] + state: rock_east + - map: [ "enum.EdgeLayer.North" ] + state: rock_north + - map: [ "enum.EdgeLayer.West" ] + state: rock_west + - state: rock_artifact_fragment - type: entity id: WallRockSalt @@ -839,21 +904,28 @@ description: An ore vein rich with salt. suffix: Salt components: - - type: OreVein - oreChance: 1.0 - currentOre: OreSalt - - type: Sprite - layers: - - state: rock - - map: [ "enum.EdgeLayer.South" ] - state: rock_south - - map: [ "enum.EdgeLayer.East" ] - state: rock_east - - map: [ "enum.EdgeLayer.North" ] - state: rock_north - - map: [ "enum.EdgeLayer.West" ] - state: rock_west - - state: rock_salt + - type: EntityRemap + mask: + AsteroidRock: AsteroidRockSalt + WallRockBasalt: WallRockBasaltSalt + WallRockChromite: WallRockChromiteSalt + WallRockSand: WallRockSandSalt + WallRockSnow: WallRockSnowSalt + - type: OreVein + oreChance: 1.0 + currentOre: OreSalt + - type: Sprite + layers: + - state: rock + - map: [ "enum.EdgeLayer.South" ] + state: rock_south + - map: [ "enum.EdgeLayer.East" ] + state: rock_east + - map: [ "enum.EdgeLayer.North" ] + state: rock_north + - map: [ "enum.EdgeLayer.West" ] + state: rock_west + - state: rock_salt # Basalt variants - type: entity diff --git a/Resources/Prototypes/Procedural/Magnet/asteroid.yml b/Resources/Prototypes/Procedural/Magnet/asteroid.yml index a21b709afa..c20b80af55 100644 --- a/Resources/Prototypes/Procedural/Magnet/asteroid.yml +++ b/Resources/Prototypes/Procedural/Magnet/asteroid.yml @@ -15,7 +15,8 @@ - type: dungeonConfig id: BlobAsteroid # Floor generation - generator: !type:NoiseDunGen + layers: + - !type:NoiseDunGen tileCap: 1500 capStd: 32 iterations: 3 @@ -28,22 +29,22 @@ fractalType: FBm octaves: 2 lacunarity: 2 - # Everything else - postGeneration: - # Generate biome - - !type:BiomePostGen - biomeTemplate: Asteroid - # Generate ore veins - - !type:MarkerLayerPostGen - markerTemplate: AsteroidOre + # Generate biome + - !type:BiomeDunGen + biomeTemplate: Asteroid + + # Generate ore veins + - !type:BiomeMarkerLayerDunGen + markerTemplate: AsteroidOre # Multiple smaller asteroids # This is a pain so we generate fewer tiles - type: dungeonConfig id: ClusterAsteroid # Floor generation - generator: !type:NoiseDunGen + layers: + - !type:NoiseDunGen tileCap: 1000 capStd: 32 layers: @@ -55,21 +56,21 @@ fractalType: FBm octaves: 2 lacunarity: 2 - # Everything else - postGeneration: - # Generate biome - - !type:BiomePostGen - biomeTemplate: Asteroid - # Generate ore veins - - !type:MarkerLayerPostGen - markerTemplate: AsteroidOre + # Generate biome + - !type:BiomeDunGen + biomeTemplate: Asteroid + + # Generate ore veins + - !type:BiomeMarkerLayerDunGen + markerTemplate: AsteroidOre # Long and spindly, less smooth than blob - type: dungeonConfig id: SpindlyAsteroid # Floor generation - generator: !type:NoiseDunGen + layers: + - !type:NoiseDunGen tileCap: 1500 capStd: 32 layers: @@ -82,20 +83,21 @@ octaves: 3 lacunarity: 2 cellularDistanceFunction: Euclidean - postGeneration: - # Generate biome - - !type:BiomePostGen - biomeTemplate: Asteroid - # Generate ore veins - - !type:MarkerLayerPostGen - markerTemplate: AsteroidOre + # Generate biome + - !type:BiomeDunGen + biomeTemplate: Asteroid + + # Generate ore veins + - !type:BiomeMarkerLayerDunGen + markerTemplate: AsteroidOre # Lots of holes in it - type: dungeonConfig id: SwissCheeseAsteroid # Floor generation - generator: !type:NoiseDunGen + layers: + - !type:NoiseDunGen tileCap: 1500 capStd: 32 layers: @@ -107,12 +109,11 @@ fractalType: FBm octaves: 2 lacunarity: 2 - # Everything else - postGeneration: - # Generate biome - - !type:BiomePostGen - biomeTemplate: Asteroid - # Generate ore veins - - !type:MarkerLayerPostGen - markerTemplate: AsteroidOre + # Generate biome + - !type:BiomeDunGen + biomeTemplate: Asteroid + + # Generate ore veins + - !type:BiomeMarkerLayerDunGen + markerTemplate: AsteroidOre diff --git a/Resources/Prototypes/Procedural/dungeon_configs.yml b/Resources/Prototypes/Procedural/dungeon_configs.yml index 3614e4e787..b55d5a9e69 100644 --- a/Resources/Prototypes/Procedural/dungeon_configs.yml +++ b/Resources/Prototypes/Procedural/dungeon_configs.yml @@ -1,361 +1,284 @@ +# Base configs - type: dungeonConfig - id: Experiment - generator: !type:PrefabDunGen - roomWhitelist: - - SalvageExperiment - presets: - - Bucket - - Wow - - SpaceShip - - Tall - postGeneration: - - !type:CorridorPostGen - width: 3 - - - !type:DungeonEntrancePostGen - count: 2 - - - !type:RoomEntrancePostGen - entities: - - CableApcExtension - - AirlockGlass - - - !type:EntranceFlankPostGen - entities: - - Grille - - Window - - - !type:ExternalWindowPostGen - entities: - - Grille - - Window - - - !type:WallMountPostGen - spawns: - # Posters - - id: RandomPosterLegit - orGroup: content - - id: ExtinguisherCabinetFilled - prob: 0.2 - orGroup: content - - id: RandomPainting - prob: 0.05 - orGroup: content - - id: IntercomCommon - prob: 0.1 - orGroup: content - - - !type:BoundaryWallPostGen - tile: FloorSteel - wall: WallSolid - cornerWall: WallReinforced - - - !type:JunctionPostGen - width: 1 - - - !type:JunctionPostGen - - - !type:AutoCablingPostGen - - - !type:CornerClutterPostGen - contents: - - id: PottedPlantRandom - amount: 1 - - - !type:CorridorDecalSkirtingPostGen - color: "#D381C996" - cardinalDecals: - South: BrickTileWhiteLineS - East: BrickTileWhiteLineE - North: BrickTileWhiteLineN - West: BrickTileWhiteLineW - cornerDecals: - SouthEast: BrickTileWhiteCornerSe - SouthWest: BrickTileWhiteCornerSw - NorthEast: BrickTileWhiteCornerNe - NorthWest: BrickTileWhiteCornerNw - pocketDecals: - SouthWest: BrickTileWhiteInnerSw - SouthEast: BrickTileWhiteInnerSe - NorthWest: BrickTileWhiteInnerNw - NorthEast: BrickTileWhiteInnerNe - - -- type: dungeonConfig - id: LavaBrig - generator: !type:PrefabDunGen - roomWhitelist: - - LavaBrig - presets: - - Bucket - - Wow - - SpaceShip - - Tall - postGeneration: - - !type:CorridorPostGen - width: 3 - - - !type:DungeonEntrancePostGen - count: 2 - - - !type:RoomEntrancePostGen - entities: - - CableApcExtension - - AirlockSecurityGlassLocked - - - !type:EntranceFlankPostGen - entities: - - Grille - - Window - - - !type:ExternalWindowPostGen - entities: - - Grille - - Window - - - !type:WallMountPostGen - spawns: - # Posters - - id: RandomPosterLegit - orGroup: content - - id: ExtinguisherCabinetFilled - prob: 0.2 - orGroup: content - - id: RandomPainting - prob: 0.05 - orGroup: content - - id: IntercomCommon - prob: 0.1 - orGroup: content - - - !type:BoundaryWallPostGen - tile: FloorSteel - wall: WallSolid - cornerWall: WallReinforced - - - !type:JunctionPostGen - width: 1 - - - !type:JunctionPostGen - - - !type:AutoCablingPostGen - - - !type:CornerClutterPostGen - contents: - - id: PottedPlantRandom - amount: 1 - - - !type:CorridorDecalSkirtingPostGen - color: "#DE3A3A96" - cardinalDecals: - South: BrickTileWhiteLineS - East: BrickTileWhiteLineE - North: BrickTileWhiteLineN - West: BrickTileWhiteLineW - cornerDecals: - SouthEast: BrickTileWhiteCornerSe - SouthWest: BrickTileWhiteCornerSw - NorthEast: BrickTileWhiteCornerNe - NorthWest: BrickTileWhiteCornerNw - pocketDecals: - SouthWest: BrickTileWhiteInnerSw - SouthEast: BrickTileWhiteInnerSe - NorthWest: BrickTileWhiteInnerNw - NorthEast: BrickTileWhiteInnerNe - -- type: dungeonConfig - id: Mineshaft - generator: !type:PrefabDunGen - tile: FloorCaveDrought - roomWhitelist: - - Mineshaft - presets: - - Bucket - - Wow - - SpaceShip - - Tall - postGeneration: - - - !type:CorridorPostGen - tile: FloorCaveDrought - width: 3 - - - !type:DungeonEntrancePostGen - count: 5 - tile: FloorCaveDrought - entities: - - RandomWoodenWall - - - !type:RoomEntrancePostGen - tile: FloorCaveDrought - entities: - - RandomWoodenWall - - - !type:EntranceFlankPostGen - tile: FloorCaveDrought - entities: - - RandomWoodenWall - - - !type:ExternalWindowPostGen - tile: FloorCaveDrought - entities: - - RandomWoodenWall - - - !type:WallMountPostGen - tile: FloorCaveDrought - spawns: - # Ore - - id: WallRockSalt - prob: 0.6 - orGroup: content - - id: WallRockCoal - prob: 0.6 - orGroup: content - - id: WallRockTin - prob: 0.4 - orGroup: content - - id: WallMining - prob: 0.8 - orGroup: content - - - !type:BoundaryWallPostGen - tile: FloorCaveDrought - wall: WallRock - cornerWall: WallRock - - - !type:AutoCablingPostGen - entity: Catwalk - - - !type:JunctionPostGen - tile: FloorCaveDrought - width: 3 - entities: - - RandomWoodenSupport - - - !type:CornerClutterPostGen - contents: - - id: RandomStalagmiteOrCrystal - amount: 1 - -- type: dungeonConfig - id: SnowyLabs - generator: !type:PrefabDunGen - roomWhitelist: - - SnowyLabs - presets: - - Bucket - - Wow - - SpaceShip - - Tall - postGeneration: - - !type:CorridorPostGen - width: 3 - - - !type:DungeonEntrancePostGen - count: 2 - - - !type:RoomEntrancePostGen - entities: - - CableApcExtension - - AirlockFreezerHydroponicsLocked - - - !type:EntranceFlankPostGen - entities: - - Grille - - Window - - - !type:ExternalWindowPostGen - entities: - - Grille - - Window - - - !type:WallMountPostGen - spawns: - # Posters - - id: RandomPosterLegit - orGroup: content - - id: ExtinguisherCabinetFilled - prob: 0.2 - orGroup: content - - id: RandomPainting - prob: 0.05 - orGroup: content - - id: IntercomScience - prob: 0.1 - orGroup: content - - - !type:BoundaryWallPostGen - tile: FloorSteel - wall: WallSilver - cornerWall: WallSilver - - - !type:JunctionPostGen - width: 1 - entities: - - AirlockGlass - - - !type:JunctionPostGen - entities: - - AirlockGlass - - - !type:AutoCablingPostGen - - - !type:CornerClutterPostGen - contents: - - id: PottedPlantRandom - amount: 1 - - - !type:CorridorDecalSkirtingPostGen - color: "#4cc7aa96" - cardinalDecals: - South: BrickTileWhiteLineS - East: BrickTileWhiteLineE - North: BrickTileWhiteLineN - West: BrickTileWhiteLineW - cornerDecals: - SouthEast: BrickTileWhiteCornerSe - SouthWest: BrickTileWhiteCornerSw - NorthEast: BrickTileWhiteCornerNe - NorthWest: BrickTileWhiteCornerNw - pocketDecals: - SouthWest: BrickTileWhiteInnerSw - SouthEast: BrickTileWhiteInnerSe - NorthWest: BrickTileWhiteInnerNw - NorthEast: BrickTileWhiteInnerNe - -# todo: Add a biome dungeon generator -# Add corridor first gens that place rooms on top -# Add a worm corridor gen (place subsequent corridors somewhere randomly along the path) -# Place room entrances on ends of corridors touching a tile -# Remove all room tiles from corridors -# Fix paths up and try to reconnect all corridor tiles -# Add a postgen step to spread rooms out, though it shouldn't spread into corridor exteriors - -- type: dungeonConfig - id: Haunted - generator: !type:PrefabDunGen - tile: FloorCaveDrought - roomWhitelist: - - Mineshaft + id: PlanetBase + layers: + - !type:PrefabDunGen presets: - Bucket - Wow - SpaceShip - Tall - postGeneration: - - !type:WormCorridorPostGen - width: 3 - tile: FloorCaveDrought - - !type:CorridorClutterPostGen - contents: - - id: FloraStalagmite1 - - id: FloraStalagmite2 - - id: FloraStalagmite3 - - id: FloraStalagmite4 - - id: FloraStalagmite5 - - id: FloraStalagmite6 + - !type:CorridorDunGen + width: 3 - - !type:BoundaryWallPostGen - tile: FloorCaveDrought - wall: WallRock + - !type:DungeonEntranceDunGen + count: 2 + + - !type:RoomEntranceDunGen + + - !type:EntranceFlankDunGen + + - !type:ExternalWindowDunGen + + - !type:WallMountDunGen + + - !type:BoundaryWallDunGen + + - !type:JunctionDunGen + width: 1 + + - !type:JunctionDunGen + + - !type:AutoCablingDunGen + + - !type:CornerClutterDunGen + + - !type:CorridorDecalSkirtingDunGen + cardinalDecals: + South: BrickTileWhiteLineS + East: BrickTileWhiteLineE + North: BrickTileWhiteLineN + West: BrickTileWhiteLineW + cornerDecals: + SouthEast: BrickTileWhiteCornerSe + SouthWest: BrickTileWhiteCornerSw + NorthEast: BrickTileWhiteCornerNe + NorthWest: BrickTileWhiteCornerNw + pocketDecals: + SouthWest: BrickTileWhiteInnerSw + SouthEast: BrickTileWhiteInnerSe + NorthWest: BrickTileWhiteInnerNw + NorthEast: BrickTileWhiteInnerNe + +# Setups +- type: dungeonConfig + id: Experiment + data: + colors: + Decals: "#D381C996" + entities: + Cabling: CableApcExtension + CornerWalls: WallReinforced + Walls: WallSolid + spawnGroups: + CornerClutter: BaseClutter + Entrance: BaseAirlock + EntranceFlank: BaseWindow + Junction: BaseAirlock + WallMounts: ScienceLabsWalls + Window: BaseWindow + tiles: + FallbackTile: FloorSteel + whitelists: + Rooms: + tags: + - SalvageExperiment + layers: + - !type:PrototypeDunGen + proto: PlanetBase + +- type: dungeonConfig + id: Haunted + data: + entities: + Walls: WallRock + tiles: + FallbackTile: FloorCaveDrought + whitelists: + Rooms: + tags: + - Mineshaft + layers: + - !type:PrefabDunGen + presets: + - Bucket + - Wow + - SpaceShip + - Tall + + - !type:WormCorridorDunGen + width: 3 + + - !type:CorridorClutterDunGen + contents: + - id: FloraStalagmite1 + - id: FloraStalagmite2 + - id: FloraStalagmite3 + - id: FloraStalagmite4 + - id: FloraStalagmite5 + - id: FloraStalagmite6 + + - !type:BoundaryWallDunGen + +- type: dungeonConfig + id: LavaBrig + data: + colors: + Decals: "#DE3A3A96" + entities: + Cabling: CableApcExtension + CornerWalls: WallReinforced + Walls: WallSolid + spawnGroups: + CornerClutter: BaseClutter + Entrance: LavaBrigEntrance + EntranceFlank: BaseWindow + Junction: BaseAirlock + WallMounts: ScienceLabsWalls + Window: BaseWindow + whitelists: + Rooms: + tags: + - LavaBrig + layers: + - !type:PrototypeDunGen + proto: PlanetBase + +- type: dungeonConfig + id: Mineshaft + data: + entities: + Cabling: Catwalk + spawnGroups: + CornerClutter: MineshaftClutter + Entrance: BaseWoodWall + EntranceFlank: BaseWoodWall + Junction: BaseWoodSupport + Window: BaseWoodWall + tiles: + FallbackTile: FloorCaveDrought + whitelists: + Rooms: + tags: + - Mineshaft + layers: + - !type:PrototypeDunGen + proto: PlanetBase + +- type: dungeonConfig + id: SnowyLabs + data: + colors: + Decals: "#4cc7aa96" + entities: + Cabling: CableApcExtension + CornerWalls: WallSilver + Walls: WallSilver + spawnGroups: + CornerClutter: BaseClutter + Entrance: SnowyLabsEntrance + EntranceFlank: BaseWindow + Junction: BaseAirlock + WallMounts: SnowyLabsWalls + Window: BaseWindow + tiles: + FallbackTile: FloorSteel + whitelists: + Rooms: + tags: + - SnowyLabs + layers: + - !type:PrototypeDunGen + proto: PlanetBase + +# Spawn groups +# Basic +- type: entitySpawnEntry + id: BaseClutter + entries: + - id: PottedPlantRandom + amount: 1 + +- type: entitySpawnEntry + id: BaseAirlock + entries: + - id: CableApcExtension + - id: AirlockGlass + +- type: entitySpawnEntry + id: BaseWindow + entries: + - id: Grille + - id: Window + +# Lava brig +- type: entitySpawnEntry + id: LavaBrigEntrance + entries: + - id: CableApcExtension + - id: AirlockSecurityGlassLocked + +# Mineshaft +- type: entitySpawnEntry + id: BaseWoodWall + entries: + - id: RandomWoodenWall + +- type: entitySpawnEntry + id: BaseWoodSupport + entries: + - id: RandomWoodenSupport + +- type: entitySpawnEntry + id: MineshaftClutter + entries: + - id: RandomStalagmiteOrCrystal + amount: 1 + +- type: entitySpawnEntry + id: MineshaftWalls + entries: + # Ore + - id: WallRockSalt + prob: 0.6 + orGroup: content + - id: WallRockCoal + prob: 0.6 + orGroup: content + - id: WallRockTin + prob: 0.4 + orGroup: content + - id: WallMining + prob: 0.8 + orGroup: content + +# Science lab +- type: entitySpawnEntry + id: ScienceLabsWalls + entries: + # Posters + - id: RandomPosterLegit + orGroup: content + - id: ExtinguisherCabinetFilled + prob: 0.2 + orGroup: content + - id: RandomPainting + prob: 0.05 + orGroup: content + - id: IntercomCommon + prob: 0.1 + orGroup: content + +# Snowy labs +- type: entitySpawnEntry + id: SnowyLabsEntrance + entries: + - id: CableApcExtension + - id: AirlockFreezerHydroponicsLocked + +- type: entitySpawnEntry + id: SnowyLabsWalls + entries: + # Posters + - id: RandomPosterLegit + orGroup: content + - id: ExtinguisherCabinetFilled + prob: 0.2 + orGroup: content + - id: RandomPainting + prob: 0.05 + orGroup: content + - id: IntercomScience + prob: 0.1 + orGroup: content diff --git a/Resources/Prototypes/Procedural/vgroid.yml b/Resources/Prototypes/Procedural/vgroid.yml new file mode 100644 index 0000000000..49e956e73f --- /dev/null +++ b/Resources/Prototypes/Procedural/vgroid.yml @@ -0,0 +1,191 @@ +# Okay so my general thought is this: +# 1. Generate the large mass +# 2. Generate smaller masses offset +# 3. Generate N normal dungeons around the larger mass, preferably near the border +# 4. Generate large paths / small paths around the place +# 5. Spawn ores + fill the rest and the normal stuff + +# If you want mobs they needed to be added at specific steps due to how dungeons work at the moment. + +- type: dungeonConfig + id: VGRoid + layers: + - !type:PrototypeDunGen + proto: VGRoidBlob + - !type:PrototypeDunGen + proto: VGRoidExterior + - !type:PrototypeDunGen + proto: VGRoidSmaller + - !type:PrototypeDunGen + proto: VGRoidSmallPaths + # Fill + - !type:PrototypeDunGen + proto: VGRoidFill + # Ores + - !type:OreDunGen + replacement: IronRock + entity: IronRockIron + count: 50 + minGroupSize: 20 + maxGroupSize: 30 + - !type:OreDunGen + replacement: IronRock + entity: IronRockCoal + count: 50 + minGroupSize: 20 + maxGroupSize: 30 + - !type:OreDunGen + replacement: IronRock + entity: IronRockQuartz + count: 50 + minGroupSize: 20 + maxGroupSize: 30 + - !type:OreDunGen + replacement: IronRock + entity: IronRockSalt + count: 50 + minGroupSize: 20 + maxGroupSize: 30 + - !type:OreDunGen + replacement: IronRock + entity: IronRockGold + count: 50 + minGroupSize: 10 + maxGroupSize: 20 + - !type:OreDunGen + replacement: IronRock + entity: IronRockSilver + count: 50 + minGroupSize: 10 + maxGroupSize: 20 + - !type:OreDunGen + replacement: IronRock + entity: IronRockPlasma + count: 50 + minGroupSize: 10 + maxGroupSize: 20 + - !type:OreDunGen + replacement: IronRock + entity: IronRockUranium + count: 50 + minGroupSize: 10 + maxGroupSize: 20 + - !type:OreDunGen + replacement: IronRock + entity: IronRockBananium + count: 50 + minGroupSize: 10 + maxGroupSize: 20 + - !type:OreDunGen + replacement: IronRock + entity: IronRockArtifactFragment + count: 50 + minGroupSize: 2 + maxGroupSize: 4 + +# Configs +- type: dungeonConfig + id: VGRoidBlob + layers: + - !type:NoiseDistanceDunGen + size: 272, 272 + distanceConfig: !type:DunGenEuclideanSquaredDistance + blendWeight: 0.80 + layers: + - tile: FloorAsteroidSand + threshold: 0.50 + noise: + frequency: 0.010 + noiseType: OpenSimplex2 + fractalType: FBm + octaves: 5 + lacunarity: 2 + gain: 0.5 + +- type: dungeonConfig + id: VGRoidSmaller + minOffset: 40 + maxOffset: 60 + layers: + - !type:NoiseDistanceDunGen + size: 150, 150 + distanceConfig: !type:DunGenEuclideanSquaredDistance + layers: + - tile: FloorAsteroidSand + threshold: 0.50 + noise: + frequency: 0.080 + noiseType: OpenSimplex2 + fractalType: FBm + octaves: 5 + lacunarity: 1.5 + gain: 0.5 + +- type: dungeonConfig + id: VGRoidExterior + reserveTiles: true + data: + tiles: + FallbackTile: PlatingAsteroid + WidenTile: FloorAsteroidSand + layers: + - !type:PrototypeDunGen + proto: VGRoidExteriorDungeons + - !type:SplineDungeonConnectorDunGen + +- type: dungeonConfig + id: VGRoidExteriorDungeons + reserveTiles: true + minCount: 2 + maxCount: 3 + layers: + - !type:ExteriorDunGen + proto: Experiment + - !type:MobsDunGen + minCount: 5 + maxCount: 8 + groups: + - id: MobXeno + amount: 1 + +#- type: dungeonConfig +# id: VGRoidInteriorDungeons +# minCount: 3 +# maxCount: 5 +# # Just randomly spawn these in bounds, doesn't really matter if they go out. + +- type: dungeonConfig + id: VGRoidSmallPaths + reserveTiles: true + layers: + - !type:ReplaceTileDunGen + layers: + - tile: FloorAsteroidSand + threshold: 0.75 + noise: + frequency: 0.040 + noiseType: OpenSimplex2 + fractalType: Ridged + lacunarity: 1.5 + octaves: 2 + gain: 2.0 + # Mobs + # If you want exterior dungeon mobs add them under the prototype. + - !type:MobsDunGen + minCount: 20 + maxCount: 30 + groups: + - id: MobXeno + amount: 1 + +#- type: dungeonConfig +# id: VGRoidOres + +# Fill with rocks. +- type: dungeonConfig + id: VGRoidFill + data: + entities: + Fill: IronRock + layers: + - !type:FillGridDunGen From a981f99b066cf6bf11990ca155c41a44777a15f6 Mon Sep 17 00:00:00 2001 From: Ed <96445749+TheShuEd@users.noreply.github.com> Date: Wed, 3 Jul 2024 18:14:39 +0300 Subject: [PATCH 61/86] New anomaly behaviour: Invisibility (#29120) * invisible anomaly * good luck --- Resources/Locale/en-US/anomaly/anomaly.ftl | 1 + Resources/Prototypes/Anomaly/behaviours.yml | 33 +++++++++++++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/Resources/Locale/en-US/anomaly/anomaly.ftl b/Resources/Locale/en-US/anomaly/anomaly.ftl index da5882fa62..c8d099777d 100644 --- a/Resources/Locale/en-US/anomaly/anomaly.ftl +++ b/Resources/Locale/en-US/anomaly/anomaly.ftl @@ -89,6 +89,7 @@ anomaly-behavior-rapid = The frequency of the pulsation is much higher, but its anomaly-behavior-reflect = A protective coating was detected. anomaly-behavior-nonsensivity = A weak reaction to particles was detected. anomaly-behavior-sensivity = Amplified reaction to particles was detected. +anomaly-behavior-invisibility = Light wave distortion has been detected. anomaly-behavior-secret = Interference detected. Some data cannot be read anomaly-behavior-inconstancy = [color=crimson]Impermanence has been detected. Particle types can change over time.[/color] anomaly-behavior-fast = [color=crimson]The pulsation frequency is strongly increased.[/color] diff --git a/Resources/Prototypes/Anomaly/behaviours.yml b/Resources/Prototypes/Anomaly/behaviours.yml index dea1ddb69c..924a4a7500 100644 --- a/Resources/Prototypes/Anomaly/behaviours.yml +++ b/Resources/Prototypes/Anomaly/behaviours.yml @@ -20,10 +20,12 @@ InconstancyParticle: 0.5 FullUnknown: 0.5 Jumping: 0.3 + Invisibility: 0.5 #Complex FastUnknown: 0.2 JumpingUnknown: 0.1 InconstancyParticleUnknown: 0.1 + InvisibilityJumping: 0.1 # Easy x0.5 point production @@ -153,6 +155,17 @@ randomStartSecretMin: 4 randomStartSecretMax: 6 +- type: anomalyBehavior + id: Invisibility + earnPointModifier: 1.6 + description: anomaly-behavior-invisibility + components: + - type: Stealth + maxVisibility: 1.2 + - type: StealthOnMove + passiveVisibilityRate: -0.37 + movementVisibilityRate: 0.20 + # Complex Effects - type: anomalyBehavior @@ -170,7 +183,6 @@ randomStartSecretMin: 3 randomStartSecretMax: 5 - - type: anomalyBehavior id: FastUnknown earnPointModifier: 1.9 @@ -191,4 +203,21 @@ prob: 0.5 - type: SecretDataAnomaly randomStartSecretMin: 3 - randomStartSecretMax: 5 \ No newline at end of file + randomStartSecretMax: 5 + +- type: anomalyBehavior + id: InvisibilityJumping + earnPointModifier: 1.95 + description: anomaly-behavior-invisibility + components: + - type: ChaoticJump + jumpMinInterval: 15 + jumpMaxInterval: 25 + rangeMin: 1 + rangeMax: 1 + effect: PuddleSparkle + - type: Stealth + maxVisibility: 1.2 + - type: StealthOnMove + passiveVisibilityRate: -0.37 + movementVisibilityRate: 0.20 \ No newline at end of file From 833320a34a5c724770ce72424472b108fe1e6f11 Mon Sep 17 00:00:00 2001 From: PJBot Date: Wed, 3 Jul 2024 15:15:47 +0000 Subject: [PATCH 62/86] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index e1a72faa15..f19099f899 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +1,4 @@ Entries: -- author: MACMAN2003 - changes: - - message: Nuclear operatives now only need 20 players to be readied up again instead - of 35. - type: Tweak - id: 6364 - time: '2024-04-17T03:19:30.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/27036 - author: Bellwether changes: - message: The nun hood now appears in the Chaplain's loadout. @@ -3826,3 +3818,10 @@ id: 6863 time: '2024-07-03T05:27:33.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28117 +- author: TheShuEd + changes: + - message: anomalies have the ability to gain invisibility behavior + type: Add + id: 6864 + time: '2024-07-03T15:14:39.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29120 From ac87f7a9777c919b2b743e0d880b2cad3cbc2cd3 Mon Sep 17 00:00:00 2001 From: Alex Pavlenko Date: Wed, 3 Jul 2024 18:37:40 +0300 Subject: [PATCH 63/86] feat: allow developers to customize vscode settings, closes #29285 (#29294) --- .vscode/settings.json | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 0e0d3ae890..0000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "omnisharp.analyzeOpenDocumentsOnly": true, - "dotnet.defaultSolution": "SpaceStation14.sln" -} From 54c659f4aaabbaebca012a099a00530b08a56fa4 Mon Sep 17 00:00:00 2001 From: ArkiveDev <95712736+ArkiveDev@users.noreply.github.com> Date: Wed, 3 Jul 2024 12:59:29 -0400 Subject: [PATCH 64/86] Allow construction of rotated railings (#29687) * Remove southRotation from railing structures * Curly Braces --- .../Construction/Graphs/structures/railing.yml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/Resources/Prototypes/Recipes/Construction/Graphs/structures/railing.yml b/Resources/Prototypes/Recipes/Construction/Graphs/structures/railing.yml index f050c65c42..1772bddea0 100644 --- a/Resources/Prototypes/Recipes/Construction/Graphs/structures/railing.yml +++ b/Resources/Prototypes/Recipes/Construction/Graphs/structures/railing.yml @@ -6,32 +6,28 @@ edges: - to: railing completed: - - !type:SnapToGrid - southRotation: true + - !type:SnapToGrid { } steps: - material: MetalRod amount: 1 doAfter: 2 - to: railingCorner completed: - - !type:SnapToGrid - southRotation: true + - !type:SnapToGrid { } steps: - material: MetalRod amount: 2 doAfter: 2.5 - to: railingCornerSmall completed: - - !type:SnapToGrid - southRotation: true + - !type:SnapToGrid { } steps: - material: MetalRod amount: 1 doAfter: 2 - to: railingRound completed: - - !type:SnapToGrid - southRotation: true + - !type:SnapToGrid { } steps: - material: MetalRod amount: 2 From 5c1aa578ef991a2d3327321b18dbb04180402f90 Mon Sep 17 00:00:00 2001 From: PJBot Date: Wed, 3 Jul 2024 17:00:36 +0000 Subject: [PATCH 65/86] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index f19099f899..51396e8a18 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Bellwether - changes: - - message: The nun hood now appears in the Chaplain's loadout. - type: Tweak - id: 6365 - time: '2024-04-17T03:36:44.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/27025 - author: iNV3RT3D & metalgearsloth changes: - message: Added a jukebox. @@ -3825,3 +3818,10 @@ id: 6864 time: '2024-07-03T15:14:39.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29120 +- author: ArkiveDev + changes: + - message: Railings can now be constructed in all 4 orientations. + type: Fix + id: 6865 + time: '2024-07-03T16:59:29.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29687 From 5198c87597c496d21e503a567e7ffb75051b519a Mon Sep 17 00:00:00 2001 From: Interrobang01 <113810873+Interrobang01@users.noreply.github.com> Date: Wed, 3 Jul 2024 17:43:42 -0700 Subject: [PATCH 66/86] improved wrench description (#29700) Lefty latchy, righty removey --- Resources/Prototypes/Entities/Objects/Tools/tools.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Prototypes/Entities/Objects/Tools/tools.yml b/Resources/Prototypes/Entities/Objects/Tools/tools.yml index 7930482960..36e629c21c 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/tools.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/tools.yml @@ -92,7 +92,7 @@ name: wrench parent: BaseItem id: Wrench - description: 'A common tool for assembly and disassembly. Remember: righty tighty, lefty loosey.' + description: 'A common tool for assembly and disassembly. Remember: lefty latchy, righty removey.' components: - type: EmitSoundOnLand sound: From e612ccda92edf052053b17a2c2f2ee08bdad08e9 Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Wed, 3 Jul 2024 21:13:49 -0400 Subject: [PATCH 67/86] add apc power draw to stat value command (#29701) add apc stat value --- .../UserInterface/StatValuesCommand.cs | 46 ++++++++++++++++++- .../en-US/commands/stat-values-command.ftl | 5 ++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/Content.Server/UserInterface/StatValuesCommand.cs b/Content.Server/UserInterface/StatValuesCommand.cs index f0c4f531d0..cb599f7b09 100644 --- a/Content.Server/UserInterface/StatValuesCommand.cs +++ b/Content.Server/UserInterface/StatValuesCommand.cs @@ -4,6 +4,7 @@ using Content.Server.Administration; using Content.Server.Cargo.Systems; using Content.Server.EUI; using Content.Server.Item; +using Content.Server.Power.Components; using Content.Shared.Administration; using Content.Shared.Item; using Content.Shared.Materials; @@ -56,6 +57,9 @@ public sealed class StatValuesCommand : IConsoleCommand case "itemsize": message = GetItem(); break; + case "drawrate": + message = GetDrawRateMessage(); + break; default: shell.WriteError(Loc.GetString("stat-values-invalid", ("arg", args[0]))); return; @@ -70,7 +74,7 @@ public sealed class StatValuesCommand : IConsoleCommand { if (args.Length == 1) { - return CompletionResult.FromOptions(new[] { "cargosell", "lathesell", "melee" }); + return CompletionResult.FromOptions(new[] { "cargosell", "lathesell", "melee", "itemsize", "drawrate" }); } return CompletionResult.Empty; @@ -250,4 +254,44 @@ public sealed class StatValuesCommand : IConsoleCommand return state; } + + private StatValuesEuiMessage GetDrawRateMessage() + { + var values = new List(); + var powerName = _factory.GetComponentName(typeof(ApcPowerReceiverComponent)); + + foreach (var proto in _proto.EnumeratePrototypes()) + { + if (proto.Abstract || + !proto.Components.TryGetValue(powerName, + out var powerConsumer)) + { + continue; + } + + var comp = (ApcPowerReceiverComponent) powerConsumer.Component; + + if (comp.Load == 0) + continue; + + values.Add(new[] + { + proto.ID, + comp.Load.ToString(CultureInfo.InvariantCulture), + }); + } + + var state = new StatValuesEuiMessage + { + Title = Loc.GetString("stat-drawrate-values"), + Headers = new List + { + Loc.GetString("stat-drawrate-id"), + Loc.GetString("stat-drawrate-rate"), + }, + Values = values, + }; + + return state; + } } diff --git a/Resources/Locale/en-US/commands/stat-values-command.ftl b/Resources/Locale/en-US/commands/stat-values-command.ftl index 99c6bd194e..67a211adab 100644 --- a/Resources/Locale/en-US/commands/stat-values-command.ftl +++ b/Resources/Locale/en-US/commands/stat-values-command.ftl @@ -18,3 +18,8 @@ stat-lathe-sell = Sell price stat-item-values = Item sizes stat-item-id = ID stat-item-price = Size + +# Draw Rate +stat-drawrate-values = APC draw rate +stat-drawrate-id = ID +stat-drawrate-rate = Draw Rate (W) From 9120f5fada1a2f3f9714ff64245b80161b0e691e Mon Sep 17 00:00:00 2001 From: Rinary <72972221+Rinary1@users.noreply.github.com> Date: Thu, 4 Jul 2024 04:25:25 +0300 Subject: [PATCH 68/86] Dynamic Radial Menus (#29678) * fix * Clean Some Code * Some Commentaries * Update Content.Client/UserInterface/Controls/RadialContainer.cs * Update Content.Client/UserInterface/Controls/RadialContainer.cs --------- Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> --- Content.Client/UserInterface/Controls/RadialContainer.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Content.Client/UserInterface/Controls/RadialContainer.cs b/Content.Client/UserInterface/Controls/RadialContainer.cs index be263d1277..be9b8817a0 100644 --- a/Content.Client/UserInterface/Controls/RadialContainer.cs +++ b/Content.Client/UserInterface/Controls/RadialContainer.cs @@ -67,11 +67,18 @@ public class RadialContainer : LayoutContainer { } - + protected override void Draw(DrawingHandleScreen handle) { + + const float baseRadius = 100f; + const float radiusIncrement = 5f; + var children = ReserveSpaceForHiddenChildren ? Children : Children.Where(x => x.Visible); var childCount = children.Count(); + + // Add padding from the center at higher child counts so they don't overlap. + Radius = baseRadius + (childCount * radiusIncrement); // Determine the size of the arc, accounting for clockwise and anti-clockwise arrangements var arc = AngularRange.Y - AngularRange.X; From 143151f2846979702cc23cd26e848c66aaa6f8a1 Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 4 Jul 2024 01:26:31 +0000 Subject: [PATCH 69/86] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 51396e8a18..074bfc3492 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: iNV3RT3D & metalgearsloth - changes: - - message: Added a jukebox. - type: Add - id: 6366 - time: '2024-04-17T09:27:01.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26736 - author: Vermidia changes: - message: Pirate Accent and Mobster accent will respect capitalization better now @@ -3825,3 +3818,10 @@ id: 6865 time: '2024-07-03T16:59:29.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29687 +- author: Rinary + changes: + - message: Fixed radial menus overlapping where there's many icons. + type: Fix + id: 6866 + time: '2024-07-04T01:25:25.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29678 From 48ae8ce0a88d0ecfcb48d22cfa103405ddbd7d63 Mon Sep 17 00:00:00 2001 From: Plykiya <58439124+Plykiya@users.noreply.github.com> Date: Wed, 3 Jul 2024 18:29:07 -0700 Subject: [PATCH 70/86] Fixes objects changing physics behavior after being pulled (#29694) * Fixes pull rotation logic * cleaner condition * even less code * I CHANGED MY MIND * first one * second one --------- Co-authored-by: plykiya --- Content.Shared/Movement/Pulling/Systems/PullingSystem.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs b/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs index edc8ad5161..f563440af0 100644 --- a/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs +++ b/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs @@ -310,7 +310,7 @@ public sealed class PullingSystem : EntitySystem private void OnReleasePulledObject(ICommonSession? session) { - if (session?.AttachedEntity is not {Valid: true} player) + if (session?.AttachedEntity is not { Valid: true } player) { return; } @@ -447,6 +447,9 @@ public sealed class PullingSystem : EntitySystem pullerComp.Pulling = pullableUid; pullableComp.Puller = pullerUid; + // store the pulled entity's physics FixedRotation setting in case we change it + pullableComp.PrevFixedRotation = pullablePhysics.FixedRotation; + // joint state handling will manage its own state if (!_timing.ApplyingState) { @@ -465,8 +468,6 @@ public sealed class PullingSystem : EntitySystem _physics.SetFixedRotation(pullableUid, pullableComp.FixedRotationOnPull, body: pullablePhysics); } - pullableComp.PrevFixedRotation = pullablePhysics.FixedRotation; - // Messaging var message = new PullStartedMessage(pullerUid, pullableUid); _modifierSystem.RefreshMovementSpeedModifiers(pullerUid); From 406cf7adcdf604e2ba4e11a899a223024aee767c Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 4 Jul 2024 01:30:13 +0000 Subject: [PATCH 71/86] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 074bfc3492..fa2e53f8b6 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Vermidia - changes: - - message: Pirate Accent and Mobster accent will respect capitalization better now - type: Fix - id: 6367 - time: '2024-04-17T10:04:24.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26644 - author: metalgearsloth changes: - message: Fix lobby character preview not updating upon changing characters. @@ -3825,3 +3818,10 @@ id: 6866 time: '2024-07-04T01:25:25.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29678 +- author: Plykiya + changes: + - message: An object's physics properly returns to normal after being pulled. + type: Fix + id: 6867 + time: '2024-07-04T01:29:07.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29694 From 62fcb6bd96802b2540ecee245f2797c9e7ff52f4 Mon Sep 17 00:00:00 2001 From: Plykiya <58439124+Plykiya@users.noreply.github.com> Date: Wed, 3 Jul 2024 18:51:46 -0700 Subject: [PATCH 72/86] Makes portable flashers destructable (#29564) Makes portable flashers destructible Co-authored-by: plykiya --- Resources/Prototypes/Entities/Objects/Weapons/security.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Resources/Prototypes/Entities/Objects/Weapons/security.yml b/Resources/Prototypes/Entities/Objects/Weapons/security.yml index 74a42c0774..24bc21c436 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/security.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/security.yml @@ -180,7 +180,7 @@ - type: entity name: portable flasher - parent: BaseStructure + parent: BaseMachine id: PortableFlasher description: An ultrabright flashbulb with a proximity trigger, useful for making an area security-only. components: @@ -196,14 +196,11 @@ !type:PhysShapeCircle radius: 2 repeating: true - - type: Anchorable - type: Sprite sprite: Objects/Weapons/pflash.rsi layers: - state: "off" map: ["enum.ProximityTriggerVisualLayers.Base"] - - type: InteractionOutline - - type: Physics - type: Fixtures fixtures: fix1: From 0d80021433564b2ce6ab2bc06d887be457a95d34 Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 4 Jul 2024 01:52:52 +0000 Subject: [PATCH 73/86] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index fa2e53f8b6..4089a09eaf 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: metalgearsloth - changes: - - message: Fix lobby character preview not updating upon changing characters. - type: Fix - id: 6368 - time: '2024-04-17T10:06:10.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/27043 - author: Beck Thompson changes: - message: You now must equip gloved weapons (E.g boxing gloves) to use them. @@ -3825,3 +3818,10 @@ id: 6867 time: '2024-07-04T01:29:07.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29694 +- author: Plykiya + changes: + - message: You can now destroy portable flashers. + type: Fix + id: 6868 + time: '2024-07-04T01:51:46.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29564 From 223ade9b3fe00a2bfbfbd0e79394a88b407fe7df Mon Sep 17 00:00:00 2001 From: Errant <35878406+Errant-4@users.noreply.github.com> Date: Thu, 4 Jul 2024 04:01:03 +0200 Subject: [PATCH 74/86] Starting gear for vox crewmembers (#29685) * tank harness * weh * Suit Storage Whitelist * Revert "Suit Storage Whitelist" This reverts commit b1f503573c2936642a2d7627c4852153ec71ce79. * suit storage filter * vox spawn gear * weh --- .../en-US/preferences/loadout-groups.ftl | 5 + .../Entities/Clothing/OuterClothing/vests.yml | 12 +++ .../Entities/Clothing/base_clothing.yml | 9 ++ .../Entities/Objects/Tools/gas_tanks.yml | 3 + .../Loadouts/Miscellaneous/survival.yml | 72 +++++++++++++ .../Prototypes/Loadouts/loadout_groups.yml | 47 +++++++++ .../Prototypes/Loadouts/role_loadouts.yml | 96 ++++++++++++++++-- Resources/Prototypes/tags.yml | 3 + .../equipped-OUTERCLOTHING-vox.png | Bin 0 -> 661 bytes .../equipped-OUTERCLOTHING.png | Bin 0 -> 800 bytes .../Vests/tankharness.rsi/icon.png | Bin 0 -> 307 bytes .../Vests/tankharness.rsi/inhand-left.png | Bin 0 -> 265 bytes .../Vests/tankharness.rsi/inhand-right.png | Bin 0 -> 289 bytes .../Vests/tankharness.rsi/meta.json | 30 ++++++ 14 files changed, 269 insertions(+), 8 deletions(-) create mode 100644 Resources/Textures/Clothing/OuterClothing/Vests/tankharness.rsi/equipped-OUTERCLOTHING-vox.png create mode 100644 Resources/Textures/Clothing/OuterClothing/Vests/tankharness.rsi/equipped-OUTERCLOTHING.png create mode 100644 Resources/Textures/Clothing/OuterClothing/Vests/tankharness.rsi/icon.png create mode 100644 Resources/Textures/Clothing/OuterClothing/Vests/tankharness.rsi/inhand-left.png create mode 100644 Resources/Textures/Clothing/OuterClothing/Vests/tankharness.rsi/inhand-right.png create mode 100644 Resources/Textures/Clothing/OuterClothing/Vests/tankharness.rsi/meta.json diff --git a/Resources/Locale/en-US/preferences/loadout-groups.ftl b/Resources/Locale/en-US/preferences/loadout-groups.ftl index 1c28509b2d..28785e305c 100644 --- a/Resources/Locale/en-US/preferences/loadout-groups.ftl +++ b/Resources/Locale/en-US/preferences/loadout-groups.ftl @@ -1,3 +1,6 @@ +# Errors +loadout-group-species-restriction = This item is not available for your current species. + # Miscellaneous loadout-group-trinkets = Trinkets loadout-group-glasses = Glasses @@ -9,6 +12,8 @@ loadout-group-survival-clown = Clown Survival Box loadout-group-survival-medical = Medical Survival Box loadout-group-survival-security = Security Survival Box loadout-group-survival-syndicate = Github is forcing me to write text that is literally twice-impossible for the player to ever see, send help +loadout-group-breath-tool = Species-dependent breath tools +loadout-group-tank-harness = Species-specific survival equipment # Command loadout-group-captain-head = Captain head diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/vests.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/vests.yml index 74b6ec74fb..1fd46e8e76 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/vests.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/vests.yml @@ -78,3 +78,15 @@ sprite: Clothing/OuterClothing/Vests/vest.rsi - type: Clothing sprite: Clothing/OuterClothing/Vests/vest.rsi + +#Tank Harness +- type: entity + parent: [ClothingOuterBase, AllowSuitStorageClothingGasTanks] + id: ClothingOuterVestTank + name: tank harness + description: A simple harness that can hold a gas tank. + components: + - type: Sprite + sprite: Clothing/OuterClothing/Vests/tankharness.rsi + - type: Clothing + sprite: Clothing/OuterClothing/Vests/tankharness.rsi diff --git a/Resources/Prototypes/Entities/Clothing/base_clothing.yml b/Resources/Prototypes/Entities/Clothing/base_clothing.yml index 55bc2fd3e6..a96ca2d23c 100644 --- a/Resources/Prototypes/Entities/Clothing/base_clothing.yml +++ b/Resources/Prototypes/Entities/Clothing/base_clothing.yml @@ -25,6 +25,15 @@ components: - type: AllowSuitStorage +- type: entity + abstract: true + id: AllowSuitStorageClothingGasTanks + components: + - type: AllowSuitStorage + whitelist: + tags: + - GasTank + # for clothing that has a single item slot to insert and alt click out. # inheritors add a whitelisted slot named item - type: entity diff --git a/Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml b/Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml index 53423e84a4..b825647ac1 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml @@ -9,6 +9,9 @@ - type: Item size: Normal sprite: Objects/Tanks/generic.rsi + - type: Tag + tags: + - GasTank - type: Clothing quickEquip: false sprite: Objects/Tanks/generic.rsi diff --git a/Resources/Prototypes/Loadouts/Miscellaneous/survival.yml b/Resources/Prototypes/Loadouts/Miscellaneous/survival.yml index 5a9f6a0d07..7b2cb5d6ff 100644 --- a/Resources/Prototypes/Loadouts/Miscellaneous/survival.yml +++ b/Resources/Prototypes/Loadouts/Miscellaneous/survival.yml @@ -19,6 +19,13 @@ - Moth - Reptilian +- type: loadoutEffectGroup + id: EffectSpeciesVox + effects: + - !type:SpeciesLoadoutEffect + species: + - Vox + # Basic - type: loadout id: EmergencyOxygen @@ -180,3 +187,68 @@ storage: back: - BoxSurvivalSyndicateNitrogen + +# Pre-equipped species gear + +# Full Tank Equipped +- type: loadout + id: LoadoutSpeciesEVANitrogen + equipment: GearEVANitrogen + effects: + - !type:GroupLoadoutEffect + proto: EffectSpeciesVox + +- type: startingGear + id: GearEVANitrogen + equipment: + suitstorage: NitrogenTankFilled + +# Tank Harness +- type: loadout + id: LoadoutTankHarness + equipment: GearTankHarness + effects: + - !type:GroupLoadoutEffect + proto: EffectSpeciesVox + +- type: startingGear + id: GearTankHarness + equipment: + outerClothing: ClothingOuterVestTank + +# Breaths Tool On Face +- type: loadout + id: LoadoutSpeciesBreathTool + equipment: GearSpeciesBreathTool + effects: + - !type:GroupLoadoutEffect + proto: EffectSpeciesVox + +- type: startingGear + id: GearSpeciesBreathTool + equipment: + mask: ClothingMaskBreath + +- type: loadout + id: LoadoutSpeciesBreathToolMedical + equipment: GearSpeciesBreathToolMedical + effects: + - !type:GroupLoadoutEffect + proto: EffectSpeciesVox + +- type: startingGear + id: GearSpeciesBreathToolMedical + equipment: + mask: ClothingMaskBreathMedical + +- type: loadout + id: LoadoutSpeciesBreathToolSecurity + equipment: GearSpeciesBreathToolSecurity + effects: + - !type:GroupLoadoutEffect + proto: EffectSpeciesVox + +- type: startingGear + id: GearSpeciesBreathToolSecurity + equipment: + mask: ClothingMaskGasSecurity diff --git a/Resources/Prototypes/Loadouts/loadout_groups.yml b/Resources/Prototypes/Loadouts/loadout_groups.yml index 57d2e5827d..b1d267cc00 100644 --- a/Resources/Prototypes/Loadouts/loadout_groups.yml +++ b/Resources/Prototypes/Loadouts/loadout_groups.yml @@ -35,13 +35,23 @@ - GlassesJamjar - GlassesJensen +- type: loadoutGroup + id: GroupTankHarness + name: loadout-group-tank-harness + minLimit: 1 + hidden: true + loadouts: + - LoadoutTankHarness + - type: loadoutGroup id: Survival name: loadout-group-survival-basic + minLimit: 3 hidden: true loadouts: - EmergencyNitrogen - EmergencyOxygen + - LoadoutSpeciesEVANitrogen # Command - type: loadoutGroup @@ -408,10 +418,12 @@ - type: loadoutGroup id: SurvivalClown name: loadout-group-survival-clown + minLimit: 2 hidden: true loadouts: - EmergencyNitrogenClown - EmergencyOxygenClown + - LoadoutSpeciesEVANitrogen - type: loadoutGroup id: MimeHead @@ -729,10 +741,12 @@ - type: loadoutGroup id: SurvivalExtended name: loadout-group-survival-extended + minLimit: 2 hidden: true loadouts: - EmergencyNitrogenExtended - EmergencyOxygenExtended + - LoadoutSpeciesEVANitrogen # Science - type: loadoutGroup @@ -1007,10 +1021,12 @@ - type: loadoutGroup id: SurvivalSecurity name: loadout-group-survival-security + minLimit: 2 hidden: true loadouts: - EmergencyNitrogenSecurity - EmergencyOxygenSecurity + - LoadoutSpeciesEVANitrogen # Medical - type: loadoutGroup @@ -1187,10 +1203,12 @@ - type: loadoutGroup id: SurvivalMedical name: loadout-group-survival-medical + minLimit: 2 hidden: true loadouts: - EmergencyNitrogenMedical - EmergencyOxygenMedical + - LoadoutSpeciesEVANitrogen # Wildcards - type: loadoutGroup @@ -1220,7 +1238,36 @@ - type: loadoutGroup id: SurvivalSyndicate name: loadout-group-survival-syndicate + minLimit: 2 hidden: true loadouts: - EmergencyNitrogenSyndicate - EmergencyOxygenSyndicate + - LoadoutSpeciesEVANitrogen + +- type: loadoutGroup + id: GroupSpeciesBreathTool + name: loadout-group-breath-tool + minLimit: 1 + maxLimit: 1 + hidden: true + loadouts: + - LoadoutSpeciesBreathTool + +- type: loadoutGroup + id: GroupSpeciesBreathToolMedical + name: loadout-group-breath-tool + minLimit: 1 + maxLimit: 1 + hidden: true + loadouts: + - LoadoutSpeciesBreathToolMedical + +- type: loadoutGroup + id: GroupSpeciesBreathToolSecurity + name: loadout-group-breath-tool + minLimit: 1 + maxLimit: 1 + hidden: true + loadouts: + - LoadoutSpeciesBreathToolSecurity diff --git a/Resources/Prototypes/Loadouts/role_loadouts.yml b/Resources/Prototypes/Loadouts/role_loadouts.yml index 4f27c6949b..bb30cc182a 100644 --- a/Resources/Prototypes/Loadouts/role_loadouts.yml +++ b/Resources/Prototypes/Loadouts/role_loadouts.yml @@ -9,11 +9,12 @@ - CaptainOuterClothing - Survival - Trinkets - + - GroupSpeciesBreathTool - type: roleLoadout id: JobHeadOfPersonnel groups: + - GroupTankHarness - HoPHead - HoPNeck - HoPJumpsuit @@ -22,11 +23,13 @@ - Glasses - Survival - Trinkets + - GroupSpeciesBreathTool # Civilian - type: roleLoadout id: JobPassenger groups: + - GroupTankHarness - PassengerJumpsuit - CommonBackpack - PassengerFace @@ -36,10 +39,12 @@ - Glasses - Survival - Trinkets + - GroupSpeciesBreathTool - type: roleLoadout id: JobBartender groups: + - GroupTankHarness - BartenderHead - BartenderJumpsuit - CommonBackpack @@ -47,19 +52,23 @@ - Glasses - Survival - Trinkets + - GroupSpeciesBreathTool - type: roleLoadout id: JobServiceWorker groups: + - GroupTankHarness - BartenderJumpsuit - CommonBackpack - Glasses - Survival - Trinkets + - GroupSpeciesBreathTool - type: roleLoadout id: JobChef groups: + - GroupTankHarness - ChefHead - ChefMask - ChefJumpsuit @@ -68,29 +77,35 @@ - Glasses - Survival - Trinkets + - GroupSpeciesBreathTool - type: roleLoadout id: JobLibrarian groups: + - GroupTankHarness - LibrarianJumpsuit - CommonBackpack - Glasses - Survival - Trinkets + - GroupSpeciesBreathTool - type: roleLoadout id: JobLawyer groups: + - GroupTankHarness - LawyerNeck - LawyerJumpsuit - CommonBackpack - Glasses - Survival - Trinkets + - GroupSpeciesBreathTool - type: roleLoadout id: JobChaplain groups: + - GroupTankHarness - ChaplainHead - ChaplainMask - ChaplainNeck @@ -100,10 +115,12 @@ - Glasses - Survival - Trinkets + - GroupSpeciesBreathTool - type: roleLoadout id: JobJanitor groups: + - GroupTankHarness - JanitorHead - JanitorJumpsuit - JanitorGloves @@ -113,10 +130,12 @@ - Survival - Trinkets - JanitorPlunger + - GroupSpeciesBreathTool - type: roleLoadout id: JobBotanist groups: + - GroupTankHarness - BotanistHead - BotanistJumpsuit - BotanistBackpack @@ -124,10 +143,12 @@ - Glasses - Survival - Trinkets + - GroupSpeciesBreathTool - type: roleLoadout id: JobClown groups: + - GroupTankHarness - ClownHead - ClownJumpsuit - ClownBackpack @@ -140,6 +161,7 @@ - type: roleLoadout id: JobMime groups: + - GroupTankHarness - MimeHead - MimeMask - MimeJumpsuit @@ -152,6 +174,7 @@ - type: roleLoadout id: JobMusician groups: + - GroupTankHarness - MusicianJumpsuit - CommonBackpack - MusicianOuterClothing @@ -159,11 +182,13 @@ - Survival - Trinkets - Instruments + - GroupSpeciesBreathTool # Cargo - type: roleLoadout id: JobQuartermaster groups: + - GroupTankHarness - QuartermasterHead - QuartermasterNeck - QuartermasterJumpsuit @@ -173,10 +198,12 @@ - Glasses - Survival - Trinkets + - GroupSpeciesBreathTool - type: roleLoadout id: JobCargoTechnician groups: + - GroupTankHarness - CargoTechnicianHead - CargoTechnicianJumpsuit - CargoTechnicianBackpack @@ -185,21 +212,25 @@ - Glasses - Survival - Trinkets + - GroupSpeciesBreathTool - type: roleLoadout id: JobSalvageSpecialist groups: + - GroupTankHarness - SalvageSpecialistBackpack - SalvageSpecialistOuterClothing - SalvageSpecialistShoes - Glasses - Survival - Trinkets + - GroupSpeciesBreathTool # Engineering - type: roleLoadout id: JobChiefEngineer groups: + - GroupTankHarness - ChiefEngineerHead - ChiefEngineerJumpsuit - StationEngineerBackpack @@ -208,18 +239,22 @@ - ChiefEngineerShoes - SurvivalExtended - Trinkets + - GroupSpeciesBreathTool - type: roleLoadout id: JobTechnicalAssistant groups: + - GroupTankHarness - TechnicalAssistantJumpsuit - StationEngineerBackpack - SurvivalExtended - Trinkets + - GroupSpeciesBreathTool - type: roleLoadout id: JobStationEngineer groups: + - GroupTankHarness - StationEngineerHead - StationEngineerJumpsuit - StationEngineerBackpack @@ -228,21 +263,25 @@ - StationEngineerID - SurvivalExtended - Trinkets + - GroupSpeciesBreathTool - type: roleLoadout id: JobAtmosphericTechnician groups: + - GroupTankHarness - AtmosphericTechnicianJumpsuit - AtmosphericTechnicianBackpack - AtmosphericTechnicianOuterClothing - AtmosphericTechnicianShoes - SurvivalExtended - Trinkets + - GroupSpeciesBreathTool # Science - type: roleLoadout id: JobResearchDirector groups: + - GroupTankHarness - ResearchDirectorHead - ResearchDirectorNeck - ResearchDirectorJumpsuit @@ -253,10 +292,12 @@ - Glasses - Survival - Trinkets + - GroupSpeciesBreathTool - type: roleLoadout id: JobScientist groups: + - GroupTankHarness - ScientistHead - ScientistNeck - ScientistJumpsuit @@ -268,15 +309,18 @@ - Glasses - Survival - Trinkets + - GroupSpeciesBreathTool - type: roleLoadout id: JobResearchAssistant groups: + - GroupTankHarness - ResearchAssistantJumpsuit - ScientistBackpack - Glasses - Survival - Trinkets + - GroupSpeciesBreathTool # Security - type: roleLoadout @@ -291,6 +335,7 @@ - SecurityShoes - SurvivalSecurity - Trinkets + - GroupSpeciesBreathToolSecurity - type: roleLoadout id: JobWarden @@ -303,6 +348,7 @@ - SecurityShoes - SurvivalSecurity - Trinkets + - GroupSpeciesBreathToolSecurity - type: roleLoadout id: JobSecurityOfficer @@ -316,10 +362,12 @@ - SecurityBelt - SurvivalSecurity - Trinkets + - GroupSpeciesBreathToolSecurity - type: roleLoadout id: JobDetective groups: + - GroupTankHarness - DetectiveHead - DetectiveNeck - DetectiveJumpsuit @@ -328,6 +376,7 @@ - SecurityShoes - SurvivalSecurity - Trinkets + - GroupSpeciesBreathToolSecurity - type: roleLoadout id: JobSecurityCadet @@ -336,11 +385,13 @@ - SecurityBackpack - SurvivalSecurity - Trinkets + - GroupSpeciesBreathToolSecurity # Medical - type: roleLoadout id: JobChiefMedicalOfficer groups: + - GroupTankHarness - ChiefMedicalOfficerHead - MedicalMask - ChiefMedicalOfficerJumpsuit @@ -352,10 +403,12 @@ - Glasses - SurvivalMedical - Trinkets + - GroupSpeciesBreathToolMedical - type: roleLoadout id: JobMedicalDoctor groups: + - GroupTankHarness - MedicalDoctorHead - MedicalMask - MedicalDoctorJumpsuit @@ -367,19 +420,23 @@ - Glasses - SurvivalMedical - Trinkets + - GroupSpeciesBreathToolMedical - type: roleLoadout id: JobMedicalIntern groups: + - GroupTankHarness - MedicalInternJumpsuit - MedicalBackpack - Glasses - SurvivalMedical - Trinkets + - GroupSpeciesBreathToolMedical - type: roleLoadout id: JobChemist groups: + - GroupTankHarness - MedicalMask - ChemistJumpsuit - MedicalGloves @@ -388,10 +445,12 @@ - MedicalShoes - SurvivalMedical - Trinkets + - GroupSpeciesBreathToolMedical - type: roleLoadout id: JobParamedic groups: + - GroupTankHarness - ParamedicHead - MedicalMask - ParamedicJumpsuit @@ -402,71 +461,92 @@ - Glasses - SurvivalMedical - Trinkets + - GroupSpeciesBreathToolMedical # Wildcards - type: roleLoadout id: JobZookeeper groups: + - GroupTankHarness - CommonBackpack - Glasses - Survival - Trinkets + - GroupSpeciesBreathTool - type: roleLoadout id: JobReporter groups: + - GroupTankHarness - ReporterJumpsuit - CommonBackpack - Glasses - Survival - Trinkets + - GroupSpeciesBreathTool - type: roleLoadout id: JobPsychologist groups: + - GroupTankHarness - MedicalBackpack - Glasses - Survival - Trinkets + - GroupSpeciesBreathTool - type: roleLoadout id: JobBoxer groups: + - GroupTankHarness - BoxerJumpsuit - BoxerGloves - CommonBackpack - Glasses - Survival - Trinkets + - GroupSpeciesBreathTool -# These loadouts will be used without player configuration, thus they must be designed to work without manual selection +# These loadouts are used for non-crew spawns, like off-station antags and event mobs +# They will be used without player configuration, thus they will only ever apply what is forced by MinLimit - type: roleLoadout - id: LoadoutSurvivalStandard + id: RoleSurvivalStandard groups: - Survival + - GroupSpeciesBreathTool + - GroupTankHarness - type: roleLoadout - id: LoadoutSurvivalClown + id: RoleSurvivalClown groups: - SurvivalClown + - GroupTankHarness - type: roleLoadout - id: LoadoutSurvivalExtended + id: RoleSurvivalExtended groups: - SurvivalExtended + - GroupSpeciesBreathTool + - GroupTankHarness - type: roleLoadout - id: LoadoutSurvivalMedical + id: RoleSurvivalMedical groups: - SurvivalMedical + - GroupSpeciesBreathToolMedical + - GroupTankHarness - type: roleLoadout - id: LoadoutSurvivalSecurity + id: RoleSurvivalSecurity groups: - SurvivalSecurity + - GroupSpeciesBreathToolSecurity + - GroupTankHarness - type: roleLoadout - id: LoadoutSurvivalSyndicate + id: RoleSurvivalSyndicate groups: - SurvivalSyndicate + - GroupSpeciesBreathTool + - GroupTankHarness diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index ca593e8631..bc12ea0af2 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -623,6 +623,9 @@ - type: Tag id: GasScrubber +- type: Tag + id: GasTank + - type: Tag id: GasVent diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/tankharness.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Vests/tankharness.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000000000000000000000000000000000..712a04c614c73d17caba2e57a5a37243c076d601 GIT binary patch literal 661 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=oCO|{#S9F5he4R}c>anM1_mZ~ zPZ!6KinzBkee(_(2(->!;hGc}5abu&kaW6Dx*$vMP}3&;MIxt~ScO*34|UJv5b&Sl zurz%om)X4e-+SLKh|)9@QeZ?vjYcfL^7B0}?(u${TC?lSOdmB*JF#C9=L zI^glglVOI#?XljC1@`K5A3(W!%y2Obs# z9Di)s%JA_;a*f$+OV;T==koTyEt|dTz>XyhkDLSM@8Se%dF6e?Be&#^{qlWpy}2In zEV!Gez4uM;3yZD|QX6D_>P@6p{;bivT6&o;^qANYZT<2WGYf0pS4mjqGU^)KeE&W3 zsC}6BWRoMwG4&#UC3dd-Q+}v;HS3Rbbvu{jm`g6+;tHP*mOORcr{a0Jjm4t@Cz4Wa zsJAySm5pdKOsC=eqsS&y-UkYfjDaezf-dEyX93&xa)#NT~c+ zt@L45?zR2vQ{O)|Ivm9RCH8O9#t)A^3voDj_^E&1`r0&NqW|*M8?~<*E}9r|SNp*7 ziYGCg4eRzu@wLBNdf71N%jRpZey`f~UH4VC{WRY*={Czn1za!QWK5rKvueHl^s51x z^X~q3y}efBxYxl`^GK9+=J;JYD@<);T3K0RU1rCO7~9 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/tankharness.rsi/equipped-OUTERCLOTHING.png b/Resources/Textures/Clothing/OuterClothing/Vests/tankharness.rsi/equipped-OUTERCLOTHING.png new file mode 100644 index 0000000000000000000000000000000000000000..51708c386a685b7888d7b399a57ccb90911d637c GIT binary patch literal 800 zcmV+*1K<3KP)pH(n&-?RCr$Pn!RosK@i86God30Vj0&4MU*HhB1I|E&_#Ze6nPRC zR*Jkvlp=nCl&+Bl)WLEN2y|dBJFCoTy&Q0}=d%s|uY`_WX6NI#vwOgeQfMFm0w4ea zAOHd&00PgGKtURINGy+_jC7{w%e=2Lt|$=9-AxmeYc_b5F~?P7)G?};b>&Cf%@fg zx$vS?$`t?wNudAS+^pE4ar#_(HxL7uPNyc=`FyVGM@MRSeXXcZv{;S}Yc89n=6+x7!uzl%&kI72fu_#1>>h00ck)1V8`; zKw$3)tbA*``tdyu`t_&Nod;mYL&3N)iGL05JP{{=XAfZC78LP-00@8p2!H?xfB*=j zB_RF_oBp?_S^zSTR40&keLzxRS?UACzXiAuf*fHG?)$X#edX-OV0Q4nkH=E`K?W!T z_{D_^c0aW;PeC_$H|X3U*nTqrHtN<5c*eMK8ryJf``Qve8?+4t2M6X1S>E2Rl&ttV zlxAL*J@4ulp8>W49k}W0C$r+iG)3x--+Zy}l%db{d_*h;3{?awZ`}^zJqd$B*D_yYRuoKg<^wi|V zYndCCH8;tmSSnvQ-s9BNv_OE-m0dYRCR?)ce5lS6tCpB z&v~AU4DxC}*Dg>N8+^(2{fv@^juSTL&ntc1Xmonhn{Vqf_Z6u_tO1eFH_Psi=JWp_ zb^rTO{_V2+q`4Gd>{@r!a`|6|P|>TO&e^|ZXJ80;$GCHcX~l|F9x5O`p00i_>zopr E0Ozi1fB*mh literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/tankharness.rsi/inhand-right.png b/Resources/Textures/Clothing/OuterClothing/Vests/tankharness.rsi/inhand-right.png new file mode 100644 index 0000000000000000000000000000000000000000..56e13c98e6323484999554f404ac082a4a2e279c GIT binary patch literal 289 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=FFaiwLn`LHy=mCjY#`A3kn!l0 zg*QCDn29@l616Z*ziDE}$kci0XwFv82@Pw^{5Gxmw|=9+`U)xEbU~nQ5IFF!^8MAw z)%U9Jo_d;XKWA%I^}CD5UzV5`N9@YE9rk`yV=IEt{G9_L`V}UPH$T1D@*_5>3CU`(8HrZRYxMnHt0*kX`>b zJYW7f|91Xy`v+^T?pjxF-ObP0l+XkK!YO<) literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/tankharness.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Vests/tankharness.rsi/meta.json new file mode 100644 index 0000000000..80cd5e0bd8 --- /dev/null +++ b/Resources/Textures/Clothing/OuterClothing/Vests/tankharness.rsi/meta.json @@ -0,0 +1,30 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Modified from tgstation vest sprite at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e by Errant for Space Station 14", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} From 3e3e050aafb93daa1eb017ee06b5e2a15fb3d315 Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Wed, 3 Jul 2024 22:29:26 -0400 Subject: [PATCH 75/86] Make all nukies humans (#29693) --- .../GameTicking/Rules/AntagLoadProfileRuleSystem.cs | 12 ++++++++++-- .../Components/AntagLoadProfileRuleCOmponent.cs | 12 +++++++++++- Resources/Prototypes/GameRules/roundstart.yml | 1 + 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/Content.Server/GameTicking/Rules/AntagLoadProfileRuleSystem.cs b/Content.Server/GameTicking/Rules/AntagLoadProfileRuleSystem.cs index fd3fb6cd65..b93904c685 100644 --- a/Content.Server/GameTicking/Rules/AntagLoadProfileRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/AntagLoadProfileRuleSystem.cs @@ -30,10 +30,18 @@ public sealed class AntagLoadProfileRuleSystem : GameRuleSystem(speciesId, out var species)) + + SpeciesPrototype? species; + if (ent.Comp.SpeciesOverride != null) + { + species = _proto.Index(ent.Comp.SpeciesOverride.Value); + } + else if (profile?.Species is not { } speciesId || !_proto.TryIndex(speciesId, out species)) + { species = _proto.Index(SharedHumanoidAppearanceSystem.DefaultSpecies); + } args.Entity = Spawn(species.Prototype); - _humanoid.LoadProfile(args.Entity.Value, profile); + _humanoid.LoadProfile(args.Entity.Value, profile?.WithSpecies(species.ID)); } } diff --git a/Content.Server/GameTicking/Rules/Components/AntagLoadProfileRuleCOmponent.cs b/Content.Server/GameTicking/Rules/Components/AntagLoadProfileRuleCOmponent.cs index 5e58fd14fc..0816902ad4 100644 --- a/Content.Server/GameTicking/Rules/Components/AntagLoadProfileRuleCOmponent.cs +++ b/Content.Server/GameTicking/Rules/Components/AntagLoadProfileRuleCOmponent.cs @@ -1,7 +1,17 @@ +using Content.Shared.Humanoid.Prototypes; +using Robust.Shared.Prototypes; + namespace Content.Server.GameTicking.Rules.Components; /// /// Makes this rules antags spawn a humanoid, either from the player's profile or a random one. /// [RegisterComponent] -public sealed partial class AntagLoadProfileRuleComponent : Component; +public sealed partial class AntagLoadProfileRuleComponent : Component +{ + /// + /// If specified, the profile loaded will be made into this species. + /// + [DataField] + public ProtoId? SpeciesOverride; +} diff --git a/Resources/Prototypes/GameRules/roundstart.yml b/Resources/Prototypes/GameRules/roundstart.yml index a7b749a35f..da198a25d0 100644 --- a/Resources/Prototypes/GameRules/roundstart.yml +++ b/Resources/Prototypes/GameRules/roundstart.yml @@ -81,6 +81,7 @@ - type: RuleGrids - type: AntagSelection - type: AntagLoadProfileRule + speciesOverride: Human - type: entity parent: BaseNukeopsRule From 97d39e0c2892bba94ea52c38004693d8e9bad1ee Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 4 Jul 2024 02:30:32 +0000 Subject: [PATCH 76/86] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 4089a09eaf..4a0b1b1a53 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Beck Thompson - changes: - - message: You now must equip gloved weapons (E.g boxing gloves) to use them. - type: Fix - id: 6369 - time: '2024-04-17T10:10:04.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26762 - author: slarticodefast changes: - message: The gas analyzer now tells you the volume of scanned objects. Tanks inside @@ -3825,3 +3818,10 @@ id: 6868 time: '2024-07-04T01:51:46.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29564 +- author: EmoGarbage404 + changes: + - message: All nuclear operatives are now humans. + type: Tweak + id: 6869 + time: '2024-07-04T02:29:26.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29693 From 2988ac383924fd428823ee7125241cd27aaf8fb3 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Thu, 4 Jul 2024 17:11:01 +1000 Subject: [PATCH 77/86] Make vox roundstart (#29704) * Make vox roundstart I believe all the issues are fixed. * Click detection bandaid --- Content.Client/Clickable/ClickableComponent.cs | 7 +++++-- Resources/Prototypes/Species/vox.yml | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Content.Client/Clickable/ClickableComponent.cs b/Content.Client/Clickable/ClickableComponent.cs index 6f75df4683..987473ca46 100644 --- a/Content.Client/Clickable/ClickableComponent.cs +++ b/Content.Client/Clickable/ClickableComponent.cs @@ -48,7 +48,7 @@ namespace Content.Client.Clickable Angle cardinalSnapping = sprite.SnapCardinals ? relativeRotation.GetCardinalDir().ToAngle() : Angle.Zero; // First we get `localPos`, the clicked location in the sprite-coordinate frame. - var entityXform = Matrix3Helpers.CreateInverseTransform(transform.WorldPosition, sprite.NoRotation ? -eye.Rotation : spriteRot - cardinalSnapping); + var entityXform = Matrix3Helpers.CreateInverseTransform(spritePos, sprite.NoRotation ? -eye.Rotation : spriteRot - cardinalSnapping); var localPos = Vector2.Transform(Vector2.Transform(worldPos, entityXform), invSpriteMatrix); // Check explicitly defined click-able bounds @@ -58,8 +58,11 @@ namespace Content.Client.Clickable // Next check each individual sprite layer using automatically computed click maps. foreach (var spriteLayer in sprite.AllLayers) { - if (!spriteLayer.Visible || spriteLayer is not Layer layer) + // TODO: Move this to a system and also use SpriteSystem.IsVisible instead. + if (!spriteLayer.Visible || spriteLayer is not Layer layer || layer.CopyToShaderParameters != null) + { continue; + } // Check the layer's texture, if it has one if (layer.Texture != null) diff --git a/Resources/Prototypes/Species/vox.yml b/Resources/Prototypes/Species/vox.yml index e3fdb2bf08..7419f3f277 100644 --- a/Resources/Prototypes/Species/vox.yml +++ b/Resources/Prototypes/Species/vox.yml @@ -1,7 +1,7 @@ - type: species id: Vox name: species-name-vox - roundStart: false # sad + roundStart: true prototype: MobVox sprites: MobVoxSprites markingLimits: MobVoxMarkingLimits From acf186490ca0864b4169f01888ac78eb7ba12695 Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 4 Jul 2024 07:12:08 +0000 Subject: [PATCH 78/86] Automatic changelog update --- Resources/Changelog/Changelog.yml | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 4a0b1b1a53..06c545ef63 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,15 +1,4 @@ Entries: -- author: slarticodefast - changes: - - message: The gas analyzer now tells you the volume of scanned objects. Tanks inside - canisters now show up as well. - type: Add - - message: The gas analyzer now shows the amount of moles inside the scanned pipe - element instead of the whole pipe network. - type: Tweak - id: 6370 - time: '2024-04-17T17:42:24.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25720 - author: deltanedas changes: - message: The Chameleon Projector has been added to the uplink for 7 TC, it lets @@ -3825,3 +3814,10 @@ id: 6869 time: '2024-07-04T02:29:26.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29693 +- author: metalgearsloth + changes: + - message: Made vox roundstart. + type: Tweak + id: 6870 + time: '2024-07-04T07:11:02.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29704 From 1471bd5b53090634f0ed6ef4f08fb54dbc640ced Mon Sep 17 00:00:00 2001 From: JIPDawg <51352440+JIPDawg@users.noreply.github.com> Date: Thu, 4 Jul 2024 02:17:47 -0500 Subject: [PATCH 79/86] Added Health Analyzer to basic treatment module. (#29696) Removed dropped, added Health Analyzer to Basic Treatment Module Co-authored-by: JIP --- .../Entities/Objects/Specific/Robotics/borg_modules.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml b/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml index 74e91a768c..1566a84e52 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml @@ -345,12 +345,12 @@ - state: icon-treatment - type: ItemBorgModule items: + - HandheldHealthAnalyzerUnpowered - Brutepack10Lingering - Ointment10Lingering - Gauze10Lingering - Bloodpack10Lingering - Syringe - - Dropper - type: entity id: BorgModuleDefibrillator From bc7907728c8c4104390f4ad355f36be7157ee8bf Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 4 Jul 2024 07:18:53 +0000 Subject: [PATCH 80/86] Automatic changelog update --- Resources/Changelog/Changelog.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 06c545ef63..e41c5f4389 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +1,4 @@ Entries: -- author: deltanedas - changes: - - message: The Chameleon Projector has been added to the uplink for 7 TC, it lets - you disguise as anything (within reason). - type: Add - id: 6371 - time: '2024-04-17T21:48:35.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26691 - author: ShadowCommander changes: - message: Fixed PDA and ID card name, job, and access not getting set on some jobs. @@ -3821,3 +3813,11 @@ id: 6870 time: '2024-07-04T07:11:02.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29704 +- author: JIPDawg + changes: + - message: Changed the basic treatment module to include a Health Analyzer and removed + the dropper. + type: Tweak + id: 6871 + time: '2024-07-04T07:17:47.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29696 From 8d015f5c9ff60107dccdf35fa48e1558728ff269 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Thu, 4 Jul 2024 10:02:43 +0200 Subject: [PATCH 81/86] Fix animation looping bugs. (#29457) Summary of the problem is in the corresponding engine commit: https://github.com/space-wizards/RobustToolbox/commit/a4ea5a462092c93cca941b073d080e284d73c2a6 This commit requires engine master right now. I think #29144 is probably the most severe one, but I touched Jittering and RotatingLight too since they seemed sus too. Fixes #29144 Co-authored-by: metalgearsloth --- Content.Client/Jittering/JitteringSystem.cs | 3 +++ Content.Client/Light/EntitySystems/LightBehaviorSystem.cs | 3 +++ Content.Client/Light/EntitySystems/RotatingLightSystem.cs | 3 +++ 3 files changed, 9 insertions(+) diff --git a/Content.Client/Jittering/JitteringSystem.cs b/Content.Client/Jittering/JitteringSystem.cs index 185bd490d3..0c11a13963 100644 --- a/Content.Client/Jittering/JitteringSystem.cs +++ b/Content.Client/Jittering/JitteringSystem.cs @@ -48,6 +48,9 @@ namespace Content.Client.Jittering if(args.Key != _jitterAnimationKey) return; + if (!args.Finished) + return; + if (TryComp(uid, out AnimationPlayerComponent? animationPlayer) && TryComp(uid, out SpriteComponent? sprite)) _animationPlayer.Play(uid, animationPlayer, GetAnimation(jittering, sprite), _jitterAnimationKey); diff --git a/Content.Client/Light/EntitySystems/LightBehaviorSystem.cs b/Content.Client/Light/EntitySystems/LightBehaviorSystem.cs index 11f69165cf..ca19d8522c 100644 --- a/Content.Client/Light/EntitySystems/LightBehaviorSystem.cs +++ b/Content.Client/Light/EntitySystems/LightBehaviorSystem.cs @@ -19,6 +19,9 @@ public sealed class LightBehaviorSystem : EntitySystem private void OnBehaviorAnimationCompleted(EntityUid uid, LightBehaviourComponent component, AnimationCompletedEvent args) { + if (!args.Finished) + return; + var container = component.Animations.FirstOrDefault(x => x.FullKey == args.Key); if (container == null) diff --git a/Content.Client/Light/EntitySystems/RotatingLightSystem.cs b/Content.Client/Light/EntitySystems/RotatingLightSystem.cs index 842c13dedf..5c2c4e4c87 100644 --- a/Content.Client/Light/EntitySystems/RotatingLightSystem.cs +++ b/Content.Client/Light/EntitySystems/RotatingLightSystem.cs @@ -69,6 +69,9 @@ public sealed class RotatingLightSystem : SharedRotatingLightSystem private void OnAnimationComplete(EntityUid uid, RotatingLightComponent comp, AnimationCompletedEvent args) { + if (!args.Finished) + return; + PlayAnimation(uid, comp); } From 2cf42bf7a41ad27c37034151ca6e53852106cfff Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 4 Jul 2024 08:03:49 +0000 Subject: [PATCH 82/86] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index e41c5f4389..8d5076c356 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: ShadowCommander - changes: - - message: Fixed PDA and ID card name, job, and access not getting set on some jobs. - type: Fix - id: 6372 - time: '2024-04-17T22:16:25.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/27062 - author: Krunk changes: - message: Head of Security and Warden now have armored and unarmored variants of @@ -3821,3 +3814,10 @@ id: 6871 time: '2024-07-04T07:17:47.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29696 +- author: PJB3005 + changes: + - message: Fixed flashlights and similar permanently getting stuck blinking. + type: Fix + id: 6872 + time: '2024-07-04T08:02:43.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29457 From fb9c03186c557905d6be41bfbf8e782aa24a6c7f Mon Sep 17 00:00:00 2001 From: Brandon Hu <103440971+Brandon-Huu@users.noreply.github.com> Date: Thu, 4 Jul 2024 09:01:31 +0000 Subject: [PATCH 83/86] fix(marathon): Fixing more issues (#29411) --- Resources/Maps/marathon.yml | 38 +++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/Resources/Maps/marathon.yml b/Resources/Maps/marathon.yml index 7657d84e17..0a5da0bd31 100644 --- a/Resources/Maps/marathon.yml +++ b/Resources/Maps/marathon.yml @@ -11089,7 +11089,7 @@ entities: pos: -20.5,-5.5 parent: 30 - type: Door - secondsUntilStateChange: -295.55695 + secondsUntilStateChange: -537.51984 state: Opening - type: DeviceLinkSource lastSignals: @@ -54789,12 +54789,6 @@ entities: parent: 30 - proto: DisposalBend entities: - - uid: 6965 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: -12.5,-9.5 - parent: 30 - uid: 7196 components: - type: Transform @@ -55856,6 +55850,12 @@ entities: - type: Transform pos: -21.5,-18.5 parent: 30 + - uid: 8689 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -11.5,-9.5 + parent: 30 - uid: 11018 components: - type: Transform @@ -55876,6 +55876,12 @@ entities: - type: Transform pos: 4.5,-3.5 parent: 30 + - uid: 11447 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -10.5,-9.5 + parent: 30 - uid: 12678 components: - type: Transform @@ -58721,12 +58727,6 @@ entities: rot: -1.5707963267948966 rad pos: -9.5,-9.5 parent: 30 - - uid: 14440 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: -10.5,-9.5 - parent: 30 - uid: 14442 components: - type: Transform @@ -60147,6 +60147,12 @@ entities: parent: 30 - proto: DisposalYJunction entities: + - uid: 6965 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -12.5,-9.5 + parent: 30 - uid: 14077 components: - type: Transform @@ -64472,14 +64478,14 @@ entities: - type: Transform pos: 26.5,-26.5 parent: 30 -- proto: GasMinerNitrogen +- proto: GasMinerNitrogenStationLarge entities: - - uid: 8689 + - uid: 11446 components: - type: Transform pos: 26.5,-22.5 parent: 30 -- proto: GasMinerOxygen +- proto: GasMinerOxygenStationLarge entities: - uid: 8690 components: From 96ff998d1870fc4963c6697c58ec431a557e6c9f Mon Sep 17 00:00:00 2001 From: Brandon Hu <103440971+Brandon-Huu@users.noreply.github.com> Date: Thu, 4 Jul 2024 09:02:02 +0000 Subject: [PATCH 84/86] fix(packed): Fix a couple of issues (#29422) * fix(packed): Fix a couple of issues * Name the signal button for bar --- Resources/Maps/packed.yml | 50 +++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/Resources/Maps/packed.yml b/Resources/Maps/packed.yml index e41fdcd0eb..6954fe7892 100644 --- a/Resources/Maps/packed.yml +++ b/Resources/Maps/packed.yml @@ -33890,11 +33890,6 @@ entities: - type: Transform pos: 28.5,-40.5 parent: 2 - - uid: 5262 - components: - - type: Transform - pos: 28.5,-41.5 - parent: 2 - proto: CrateEngineeringCableBulk entities: - uid: 782 @@ -65602,25 +65597,6 @@ entities: - Pressed: Toggle 4503: - Pressed: Toggle - - uid: 1151 - components: - - type: MetaData - name: Bar Shutters - - type: Transform - pos: 40.15871,-2.7952938 - parent: 2 - - type: DeviceLinkSource - linkedPorts: - 1154: - - Pressed: Toggle - 913: - - Pressed: Toggle - 6512: - - Pressed: Toggle - 1658: - - Pressed: Toggle - 419: - - Pressed: Toggle - uid: 1471 components: - type: MetaData @@ -65750,6 +65726,28 @@ entities: - Pressed: Toggle 12824: - Pressed: Toggle +- proto: SignalButtonDirectional + entities: + - uid: 543 + components: + - type: MetaData + name: Bar Shutters + - type: Transform + rot: 3.141592653589793 rad + pos: 40.5,-8.5 + parent: 2 + - type: DeviceLinkSource + linkedPorts: + 419: + - Pressed: Toggle + 1658: + - Pressed: Toggle + 6512: + - Pressed: Toggle + 913: + - Pressed: Toggle + 1154: + - Pressed: Toggle - proto: SignAnomaly entities: - uid: 7376 @@ -81933,10 +81931,10 @@ entities: parent: 2 - proto: WindoorKitchenHydroponicsLocked entities: - - uid: 543 + - uid: 1151 components: - type: Transform - rot: -1.5707963267948966 rad + rot: 1.5707963267948966 rad pos: 40.5,-7.5 parent: 2 - uid: 5055 From 38cd8ad6a142f1046d675911de4eeb40a8568a07 Mon Sep 17 00:00:00 2001 From: Ed <96445749+TheShuEd@users.noreply.github.com> Date: Thu, 4 Jul 2024 15:55:00 +0300 Subject: [PATCH 85/86] Fix Vox clothing in character creation menu (#29709) Update vox.yml --- Resources/Prototypes/Entities/Mobs/Species/vox.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Resources/Prototypes/Entities/Mobs/Species/vox.yml b/Resources/Prototypes/Entities/Mobs/Species/vox.yml index ec8035563b..4d5239e50c 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/vox.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/vox.yml @@ -112,4 +112,16 @@ species: Vox - type: Body prototype: Vox + - type: Inventory + speciesId: vox + displacements: + jumpsuit: + layer: + sprite: Mobs/Species/Vox/displacement.rsi + state: jumpsuit + copyToShaderParameters: + # Value required, provide a dummy. Gets overridden when applied. + layerKey: dummy + parameterTexture: displacementMap + parameterUV: displacementUV From b1a2aaf1f5c8ef07348d6d4075b9866441672ac1 Mon Sep 17 00:00:00 2001 From: Ed <96445749+TheShuEd@users.noreply.github.com> Date: Thu, 4 Jul 2024 18:25:23 +0300 Subject: [PATCH 86/86] remove vox --- Resources/Maps/_CP14/dev_map.yml | 23 ----------------------- Resources/Prototypes/Species/vox.yml | 2 +- 2 files changed, 1 insertion(+), 24 deletions(-) diff --git a/Resources/Maps/_CP14/dev_map.yml b/Resources/Maps/_CP14/dev_map.yml index 1301d92b97..43aaf038ab 100644 --- a/Resources/Maps/_CP14/dev_map.yml +++ b/Resources/Maps/_CP14/dev_map.yml @@ -2974,18 +2974,6 @@ entities: - type: Transform pos: -15.5,1.5 parent: 179 -- proto: ComputerAnalysisConsole - entities: - - uid: 1083 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 15.5,23.5 - parent: 179 - - type: DeviceLinkSource - linkedPorts: - 1078: - - ArtifactAnalyzerSender: ArtifactAnalyzerReceiver - proto: ComputerCargoOrders entities: - uid: 326 @@ -4243,17 +4231,6 @@ entities: - type: Transform pos: 15.5,19.5 parent: 179 -- proto: MachineArtifactAnalyzer - entities: - - uid: 1078 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 12.5,24.5 - parent: 179 - - type: DeviceLinkSink - links: - - 1083 - proto: MachineFrame entities: - uid: 533 diff --git a/Resources/Prototypes/Species/vox.yml b/Resources/Prototypes/Species/vox.yml index 7afc323190..44b959e2e6 100644 --- a/Resources/Prototypes/Species/vox.yml +++ b/Resources/Prototypes/Species/vox.yml @@ -1,7 +1,7 @@ - type: species id: Vox name: species-name-vox - roundStart: true + roundStart: false prototype: MobVox sprites: MobVoxSprites markingLimits: MobVoxMarkingLimits