From 241b8d8be5d3dc0391121ae22f0c21d25f297b35 Mon Sep 17 00:00:00 2001 From: Kevin Zheng Date: Wed, 29 May 2024 16:34:41 -0800 Subject: [PATCH 01/75] Fix flow rate display (#28372) --- .../Piping/Binary/EntitySystems/GasPassiveGateSystem.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Content.Server/Atmos/Piping/Binary/EntitySystems/GasPassiveGateSystem.cs b/Content.Server/Atmos/Piping/Binary/EntitySystems/GasPassiveGateSystem.cs index fced4d7988..008d3cb4ce 100644 --- a/Content.Server/Atmos/Piping/Binary/EntitySystems/GasPassiveGateSystem.cs +++ b/Content.Server/Atmos/Piping/Binary/EntitySystems/GasPassiveGateSystem.cs @@ -39,7 +39,7 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems var T2 = outlet.Air.Temperature; var pressureDelta = P1 - P2; - float dt = 1/_atmosphereSystem.AtmosTickRate; + float dt = args.dt; float dV = 0; var denom = (T1*V2 + T2*V1); @@ -63,7 +63,9 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems var transferMoles = n1 - (n1+n2)*T2*V1 / denom; // Get the volume transfered to update our flow meter. - dV = n1*Atmospherics.R*T1/P1; + // When you remove x from one side and add x to the other the total difference is 2x. + // Also account for atmos speedup so that measured flow rate matches the setting on the volume pump. + dV = 2*transferMoles*Atmospherics.R*T1/P1 / _atmosphereSystem.Speedup; // Actually transfer the gas. _atmosphereSystem.Merge(outlet.Air, inlet.Air.Remove(transferMoles)); From a6a54baf0d3f725499ab5ea831579e190e752402 Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 30 May 2024 00:35:48 +0000 Subject: [PATCH 02/75] 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 5dc65ddaf0..f5b8095c90 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: SlamBamActionman - changes: - - message: Renamed Uplink categories and moved items to fit new category names. - type: Tweak - id: 6140 - time: '2024-03-13T09:47:17.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25079 - author: veprolet changes: - message: Injectors like the syringe can now toggle their transfer amount using @@ -3865,3 +3858,10 @@ id: 6639 time: '2024-05-29T16:46:23.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28386 +- author: notafet + changes: + - message: Fix passive gate flow rate display. + type: Fix + id: 6640 + time: '2024-05-30T00:34:41.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28372 From aa94a85721b37ec9f1fbe0d8a62bdc54eb6c288e Mon Sep 17 00:00:00 2001 From: Lamrr <96937466+Lamrr@users.noreply.github.com> Date: Thu, 30 May 2024 14:15:30 +1000 Subject: [PATCH 03/75] Fix some abnormally high-capacity boxes (#28314) --- .../Catalog/Fills/Boxes/general.yml | 127 ++++++------------ .../Catalog/Fills/Boxes/syndicate.yml | 20 +++ .../Entities/Objects/Misc/candles.yml | 3 + Resources/Prototypes/tags.yml | 3 + 4 files changed, 68 insertions(+), 85 deletions(-) diff --git a/Resources/Prototypes/Catalog/Fills/Boxes/general.yml b/Resources/Prototypes/Catalog/Fills/Boxes/general.yml index 5b3f88c1bb..85ee9f9ab8 100644 --- a/Resources/Prototypes/Catalog/Fills/Boxes/general.yml +++ b/Resources/Prototypes/Catalog/Fills/Boxes/general.yml @@ -39,10 +39,6 @@ id: BoxLightbulb description: This box is shaped on the inside so that only light tubes and bulbs fit. components: - - type: StorageFill - contents: - - id: LightBulb - amount: 12 - type: Sprite layers: - state: box @@ -53,50 +49,40 @@ whitelist: components: - LightBulb + - type: StorageFill + contents: + - id: LightBulb + amount: 12 - type: entity name: lighttube box - parent: BoxCardboard + parent: BoxLightbulb id: BoxLighttube - description: This box is shaped on the inside so that only light tubes and bulbs fit. components: - - type: StorageFill - contents: - - id: LightTube - amount: 12 - type: Sprite layers: - state: box - state: lighttube - - type: Storage - grid: - - 0,0,5,3 - whitelist: - components: - - LightBulb + - type: StorageFill + contents: + - id: LightTube + amount: 12 - type: entity name: mixed lights box - parent: BoxCardboard + parent: BoxLightbulb id: BoxLightMixed - description: This box is shaped on the inside so that only light tubes and bulbs fit. components: + - type: Sprite + layers: + - state: box + - state: lightmixed - type: StorageFill contents: - id: LightTube amount: 6 - id: LightBulb amount: 6 - - type: Sprite - layers: - - state: box - - state: lightmixed - - type: Storage - grid: - - 0,0,5,3 - whitelist: - components: - - LightBulb - type: entity name: PDA box @@ -366,28 +352,6 @@ amount: 2 - type: Storage -- type: entity - name: deathrattle implant box - parent: BoxCardboard - id: BoxDeathRattleImplants - description: Six deathrattle implants and handheld GPS devices for the whole squad. - components: - - type: Item - size: Normal - - type: StorageFill - contents: - - id: DeathRattleImplanter - amount: 6 - - id: HandheldGPSBasic - amount: 6 - - type: Storage - grid: - - 0,0,5,3 - - type: Sprite - layers: - - state: box - - state: syringe - - type: entity name: lead-lined box parent: BoxCardboard @@ -404,6 +368,7 @@ name: candle box parent: BoxCardboard id: BoxCandle + description: This box is specifically moulded to only carry candles. components: - type: Sprite layers: @@ -411,67 +376,59 @@ - state: candle - type: Storage grid: - - 0,0,9,2 + - 0,0,5,3 + whitelist: + tags: + - Candle - type: StorageFill contents: - id: Candle - amount: 3 + amount: 4 - id: CandleBlue - amount: 3 + amount: 2 - id: CandleRed - amount: 3 + amount: 2 - id: CandleGreen - amount: 3 + amount: 2 - id: CandlePurple - amount: 3 + amount: 2 - type: entity name: small candle box - parent: BoxCardboard + parent: BoxCandle id: BoxCandleSmall components: - - type: Sprite - layers: - - state: box - - state: candle - - type: Storage - grid: - - 0,0,9,2 - type: StorageFill contents: - id: CandleSmall - amount: 5 + amount: 8 - id: CandleBlueSmall - amount: 5 + amount: 4 - id: CandleRedSmall - amount: 5 + amount: 4 - id: CandleGreenSmall - amount: 5 + amount: 4 - id: CandlePurpleSmall - amount: 5 + amount: 4 - type: entity name: darts box parent: BoxCardboard id: BoxDarts - description: This box filled with colorful darts. + description: A box filled with colorful darts. components: - - type: Item - size: Normal - - type: StorageFill - contents: - - id: Dart - amount: 3 - - id: DartBlue - amount: 3 - - id: DartPurple - amount: 3 - - id: DartYellow - amount: 3 - - type: Storage - grid: - - 0,0,5,3 - type: Sprite layers: - state: box - state: darts + - type: StorageFill + contents: + - id: Dart + amount: 2 + - id: DartBlue + amount: 2 + - id: DartPurple + amount: 2 + - id: DartYellow + amount: 2 + diff --git a/Resources/Prototypes/Catalog/Fills/Boxes/syndicate.yml b/Resources/Prototypes/Catalog/Fills/Boxes/syndicate.yml index d2e889c937..2bfca8362a 100644 --- a/Resources/Prototypes/Catalog/Fills/Boxes/syndicate.yml +++ b/Resources/Prototypes/Catalog/Fills/Boxes/syndicate.yml @@ -49,3 +49,23 @@ layers: - state: box_of_doom - state: throwing_knives + +- type: entity + name: deathrattle implant box + parent: BoxCardboard + id: BoxDeathRattleImplants + description: Six deathrattle implants and handheld GPS devices for the whole squad. + components: + - type: Sprite + layers: + - state: box_of_doom + - state: syringe + - type: Storage + grid: + - 0,0,5,3 + - type: StorageFill + contents: + - id: DeathRattleImplanter + amount: 6 + - id: HandheldGPSBasic + amount: 6 diff --git a/Resources/Prototypes/Entities/Objects/Misc/candles.yml b/Resources/Prototypes/Entities/Objects/Misc/candles.yml index bef37e5fd0..55d3ecb9d3 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/candles.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/candles.yml @@ -4,6 +4,9 @@ id: Candle description: A thin wick threaded through fat. components: + - type: Tag + tags: + - Candle - type: Sprite noRot: true sprite: Objects/Misc/candles.rsi diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index 439ad47c0f..d60b632986 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -254,6 +254,9 @@ - type: Tag id: CableCoil + +- type: Tag + id: Candle - type: Tag id: Cake From 620ce8fb2a6b37ebf9d2a6c2df9a1f2e2dcc535c Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 30 May 2024 04:16:37 +0000 Subject: [PATCH 04/75] 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 f5b8095c90..10ba127a88 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +1,4 @@ Entries: -- author: veprolet - changes: - - message: Injectors like the syringe can now toggle their transfer amount using - alternative interactions. - type: Tweak - id: 6141 - time: '2024-03-13T10:00:45.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25566 - author: Dutch-VanDerLinde changes: - message: Zombies can now wideswing, similar to how a space carp would. @@ -3865,3 +3857,10 @@ id: 6640 time: '2024-05-30T00:34:41.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28372 +- author: Lamrr + changes: + - message: Candle boxes can now only hold candles. + type: Tweak + id: 6641 + time: '2024-05-30T04:15:31.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28314 From 3efd7559b02105a5b1266fc30106b5dd5ca2aa79 Mon Sep 17 00:00:00 2001 From: blueDev2 <89804215+blueDev2@users.noreply.github.com> Date: Thu, 30 May 2024 00:18:07 -0400 Subject: [PATCH 05/75] Fixed hypo and injector entities going into disposal units (#28317) --- .../EntitySystems/HypospraySystem.cs | 21 +++-- .../Chemistry/EntitySystems/InjectorSystem.cs | 80 +++++++++---------- 2 files changed, 47 insertions(+), 54 deletions(-) diff --git a/Content.Server/Chemistry/EntitySystems/HypospraySystem.cs b/Content.Server/Chemistry/EntitySystems/HypospraySystem.cs index 56cc0f9670..7b70497c7d 100644 --- a/Content.Server/Chemistry/EntitySystems/HypospraySystem.cs +++ b/Content.Server/Chemistry/EntitySystems/HypospraySystem.cs @@ -35,16 +35,16 @@ public sealed class HypospraySystem : SharedHypospraySystem SubscribeLocalEvent(OnUseInHand); } - private void UseHypospray(Entity entity, EntityUid target, EntityUid user) + private bool TryUseHypospray(Entity entity, EntityUid target, EntityUid user) { // if target is ineligible but is a container, try to draw from the container if (!EligibleEntity(target, EntityManager, entity) && _solutionContainers.TryGetDrawableSolution(target, out var drawableSolution, out _)) { - TryDraw(entity, target, drawableSolution.Value, user); + return TryDraw(entity, target, drawableSolution.Value, user); } - TryDoInject(entity, target, user); + return TryDoInject(entity, target, user); } private void OnUseInHand(Entity entity, ref UseInHandEvent args) @@ -52,8 +52,7 @@ public sealed class HypospraySystem : SharedHypospraySystem if (args.Handled) return; - TryDoInject(entity, args.User, args.User); - args.Handled = true; + args.Handled = TryDoInject(entity, args.User, args.User); } public void OnAfterInteract(Entity entity, ref AfterInteractEvent args) @@ -61,8 +60,7 @@ public sealed class HypospraySystem : SharedHypospraySystem if (args.Handled || !args.CanReach || args.Target == null) return; - UseHypospray(entity, args.Target.Value, args.User); - args.Handled = true; + args.Handled = TryUseHypospray(entity, args.Target.Value, args.User); } public void OnAttack(Entity entity, ref MeleeHitEvent args) @@ -150,12 +148,12 @@ public sealed class HypospraySystem : SharedHypospraySystem return true; } - private void TryDraw(Entity entity, Entity target, Entity targetSolution, EntityUid user) + private bool TryDraw(Entity entity, Entity target, Entity targetSolution, EntityUid user) { if (!_solutionContainers.TryGetSolution(entity.Owner, entity.Comp.SolutionName, out var soln, out var solution) || solution.AvailableVolume == 0) { - return; + return false; } // Get transfer amount. May be smaller than _transferAmount if not enough room, also make sure there's room in the injector @@ -168,19 +166,20 @@ public sealed class HypospraySystem : SharedHypospraySystem Loc.GetString("injector-component-target-is-empty-message", ("target", Identity.Entity(target, EntityManager))), entity.Owner, user); - return; + return false; } var removedSolution = _solutionContainers.Draw(target.Owner, targetSolution, realTransferAmount); if (!_solutionContainers.TryAddSolution(soln.Value, removedSolution)) { - return; + return false; } _popup.PopupEntity(Loc.GetString("injector-component-draw-success-message", ("amount", removedSolution.Volume), ("target", Identity.Entity(target, EntityManager))), entity.Owner, user); + return true; } private bool EligibleEntity(EntityUid entity, IEntityManager entMan, HyposprayComponent component) diff --git a/Content.Server/Chemistry/EntitySystems/InjectorSystem.cs b/Content.Server/Chemistry/EntitySystems/InjectorSystem.cs index 77c8620ea4..aac171371f 100644 --- a/Content.Server/Chemistry/EntitySystems/InjectorSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/InjectorSystem.cs @@ -29,50 +29,43 @@ public sealed class InjectorSystem : SharedInjectorSystem SubscribeLocalEvent(OnInjectorAfterInteract); } - private void UseInjector(Entity injector, EntityUid target, EntityUid user) + private bool TryUseInjector(Entity injector, EntityUid target, EntityUid user) { // Handle injecting/drawing for solutions if (injector.Comp.ToggleState == InjectorToggleMode.Inject) { if (SolutionContainers.TryGetInjectableSolution(target, out var injectableSolution, out _)) - { - TryInject(injector, target, injectableSolution.Value, user, false); - } - else if (SolutionContainers.TryGetRefillableSolution(target, out var refillableSolution, out _)) - { - TryInject(injector, target, refillableSolution.Value, user, true); - } - else if (TryComp(target, out var bloodstream)) - { - TryInjectIntoBloodstream(injector, (target, bloodstream), user); - } - else - { - Popup.PopupEntity(Loc.GetString("injector-component-cannot-transfer-message", - ("target", Identity.Entity(target, EntityManager))), injector, user); - } + return TryInject(injector, target, injectableSolution.Value, user, false); + + if (SolutionContainers.TryGetRefillableSolution(target, out var refillableSolution, out _)) + return TryInject(injector, target, refillableSolution.Value, user, true); + + if (TryComp(target, out var bloodstream)) + return TryInjectIntoBloodstream(injector, (target, bloodstream), user); + + Popup.PopupEntity(Loc.GetString("injector-component-cannot-transfer-message", + ("target", Identity.Entity(target, EntityManager))), injector, user); + return false; } - else if (injector.Comp.ToggleState == InjectorToggleMode.Draw) + + if (injector.Comp.ToggleState == InjectorToggleMode.Draw) { // Draw from a bloodstream, if the target has that if (TryComp(target, out var stream) && SolutionContainers.ResolveSolution(target, stream.BloodSolutionName, ref stream.BloodSolution)) { - TryDraw(injector, (target, stream), stream.BloodSolution.Value, user); - return; + return TryDraw(injector, (target, stream), stream.BloodSolution.Value, user); } // Draw from an object (food, beaker, etc) if (SolutionContainers.TryGetDrawableSolution(target, out var drawableSolution, out _)) - { - TryDraw(injector, target, drawableSolution.Value, user); - } - else - { - Popup.PopupEntity(Loc.GetString("injector-component-cannot-draw-message", - ("target", Identity.Entity(target, EntityManager))), injector.Owner, user); - } + return TryDraw(injector, target, drawableSolution.Value, user); + + Popup.PopupEntity(Loc.GetString("injector-component-cannot-draw-message", + ("target", Identity.Entity(target, EntityManager))), injector.Owner, user); + return false; } + return false; } private void OnInjectDoAfter(Entity entity, ref InjectorDoAfterEvent args) @@ -80,8 +73,7 @@ public sealed class InjectorSystem : SharedInjectorSystem if (args.Cancelled || args.Handled || args.Args.Target == null) return; - UseInjector(entity, args.Args.Target.Value, args.Args.User); - args.Handled = true; + args.Handled = TryUseInjector(entity, args.Args.Target.Value, args.Args.User); } private void OnInjectorAfterInteract(Entity entity, ref AfterInteractEvent args) @@ -105,8 +97,7 @@ public sealed class InjectorSystem : SharedInjectorSystem return; } - UseInjector(entity, target, args.User); - args.Handled = true; + args.Handled = TryUseInjector(entity, target, args.User); } /// @@ -214,7 +205,7 @@ public sealed class InjectorSystem : SharedInjectorSystem }); } - private void TryInjectIntoBloodstream(Entity injector, Entity target, + private bool TryInjectIntoBloodstream(Entity injector, Entity target, EntityUid user) { // Get transfer amount. May be smaller than _transferAmount if not enough room @@ -224,7 +215,7 @@ public sealed class InjectorSystem : SharedInjectorSystem Popup.PopupEntity( Loc.GetString("injector-component-cannot-inject-message", ("target", Identity.Entity(target, EntityManager))), injector.Owner, user); - return; + return false; } var realTransferAmount = FixedPoint2.Min(injector.Comp.TransferAmount, chemSolution.AvailableVolume); @@ -233,7 +224,7 @@ public sealed class InjectorSystem : SharedInjectorSystem Popup.PopupEntity( Loc.GetString("injector-component-cannot-inject-message", ("target", Identity.Entity(target, EntityManager))), injector.Owner, user); - return; + return false; } // Move units from attackSolution to targetSolution @@ -249,14 +240,15 @@ public sealed class InjectorSystem : SharedInjectorSystem Dirty(injector); AfterInject(injector, target); + return true; } - private void TryInject(Entity injector, EntityUid targetEntity, + private bool TryInject(Entity injector, EntityUid targetEntity, Entity targetSolution, EntityUid user, bool asRefill) { if (!SolutionContainers.TryGetSolution(injector.Owner, injector.Comp.SolutionName, out var soln, out var solution) || solution.Volume == 0) - return; + return false; // Get transfer amount. May be smaller than _transferAmount if not enough room var realTransferAmount = @@ -268,7 +260,7 @@ public sealed class InjectorSystem : SharedInjectorSystem Loc.GetString("injector-component-target-already-full-message", ("target", Identity.Entity(targetEntity, EntityManager))), injector.Owner, user); - return; + return false; } // Move units from attackSolution to targetSolution @@ -291,6 +283,7 @@ public sealed class InjectorSystem : SharedInjectorSystem Dirty(injector); AfterInject(injector, targetEntity); + return true; } private void AfterInject(Entity injector, EntityUid target) @@ -321,13 +314,13 @@ public sealed class InjectorSystem : SharedInjectorSystem RaiseLocalEvent(target, ref ev); } - private void TryDraw(Entity injector, Entity target, + private bool TryDraw(Entity injector, Entity target, Entity targetSolution, EntityUid user) { if (!SolutionContainers.TryGetSolution(injector.Owner, injector.Comp.SolutionName, out var soln, out var solution) || solution.AvailableVolume == 0) { - return; + return false; } // Get transfer amount. May be smaller than _transferAmount if not enough room, also make sure there's room in the injector @@ -340,14 +333,14 @@ public sealed class InjectorSystem : SharedInjectorSystem Loc.GetString("injector-component-target-is-empty-message", ("target", Identity.Entity(target, EntityManager))), injector.Owner, user); - return; + return false; } // We have some snowflaked behavior for streams. if (target.Comp != null) { DrawFromBlood(injector, (target.Owner, target.Comp), soln.Value, realTransferAmount, user); - return; + return true; } // Move units from attackSolution to targetSolution @@ -355,7 +348,7 @@ public sealed class InjectorSystem : SharedInjectorSystem if (!SolutionContainers.TryAddSolution(soln.Value, removedSolution)) { - return; + return false; } Popup.PopupEntity(Loc.GetString("injector-component-draw-success-message", @@ -364,6 +357,7 @@ public sealed class InjectorSystem : SharedInjectorSystem Dirty(injector); AfterDraw(injector, target); + return true; } private void DrawFromBlood(Entity injector, Entity target, From ce2446a6125c2666954eda4a1c559c437c7097b1 Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 30 May 2024 04:19:13 +0000 Subject: [PATCH 06/75] 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 10ba127a88..60289bd35b 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Dutch-VanDerLinde - changes: - - message: Zombies can now wideswing, similar to how a space carp would. - type: Tweak - id: 6142 - time: '2024-03-13T10:02:11.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26064 - author: Krunk changes: - message: Clown & Jester shoes can now hold plastic knives and cap guns! All clowning, @@ -3864,3 +3857,10 @@ id: 6641 time: '2024-05-30T04:15:31.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28314 +- author: blueDev2 + changes: + - message: Epipens can be disposed + type: Fix + id: 6642 + time: '2024-05-30T04:18:07.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28317 From e2bf127323ebf48c99c1ecf473de878a1ce5d88d Mon Sep 17 00:00:00 2001 From: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> Date: Wed, 29 May 2024 23:46:22 -0700 Subject: [PATCH 07/75] Move ApcPowerReceiverComponent Powered state to shared (#28206) * Try syncing powered state to client For some reason the client is not receiving the ApcPowerReceiverComponentState, so it's not working. * Fix powered state not syncing to client The client PowerReceiverSystem was abstract, which prevented it from running initialize. * Flip check so that it runs bigger checks first PowerDisabled skips the others. NeedsPower skips the receiving check. * Disallow changing Powered manually * Move Powered update to PowerReceiverSystem * Move appearance to event subscription * Move metadata component to AllEntityQuery * Cleanup * Move Powered update back to PowerNetSystem It's easier to use the EntityQueries and it dosen't need to be updated anywhere else. * Put appearance updating back * Move IsPowered to shared * Simplify IsPowered * Cleanup * Remove duplicate PowerChangedEvent PowerChangedEvent on ProviderChanged doesn't seem to be needed PowerChangedEvent gets raised by in update if the power state changes after a new provider is connected --- .../Components/ApcPowerReceiverComponent.cs | 8 +++++ .../EntitySystems/PowerReceiverSystem.cs | 23 ++++++++++++++ .../Components/ApcPowerReceiverComponent.cs | 18 ++++------- .../Power/EntitySystems/PowerNetSystem.cs | 21 +++++++++---- .../EntitySystems/PowerReceiverSystem.cs | 30 +++++++++++++------ .../ApcPowerReceiverComponentState.cs | 9 ++++++ .../SharedApcPowerReceiverComponent.cs | 10 +++++++ .../SharedPowerReceiverSystem.cs | 6 ++++ 8 files changed, 98 insertions(+), 27 deletions(-) create mode 100644 Content.Client/Power/Components/ApcPowerReceiverComponent.cs create mode 100644 Content.Client/Power/EntitySystems/PowerReceiverSystem.cs create mode 100644 Content.Shared/Power/Components/ApcPowerReceiverComponentState.cs create mode 100644 Content.Shared/Power/Components/SharedApcPowerReceiverComponent.cs create mode 100644 Content.Shared/Power/EntitySystems/SharedPowerReceiverSystem.cs diff --git a/Content.Client/Power/Components/ApcPowerReceiverComponent.cs b/Content.Client/Power/Components/ApcPowerReceiverComponent.cs new file mode 100644 index 0000000000..fbebcb7cf8 --- /dev/null +++ b/Content.Client/Power/Components/ApcPowerReceiverComponent.cs @@ -0,0 +1,8 @@ +using Content.Shared.Power.Components; + +namespace Content.Client.Power.Components; + +[RegisterComponent] +public sealed partial class ApcPowerReceiverComponent : SharedApcPowerReceiverComponent +{ +} diff --git a/Content.Client/Power/EntitySystems/PowerReceiverSystem.cs b/Content.Client/Power/EntitySystems/PowerReceiverSystem.cs new file mode 100644 index 0000000000..4d56592c23 --- /dev/null +++ b/Content.Client/Power/EntitySystems/PowerReceiverSystem.cs @@ -0,0 +1,23 @@ +using Content.Client.Power.Components; +using Content.Shared.Power.Components; +using Content.Shared.Power.EntitySystems; +using Robust.Shared.GameStates; + +namespace Content.Client.Power.EntitySystems; + +public sealed class PowerReceiverSystem : SharedPowerReceiverSystem +{ + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnHandleState); + } + + private void OnHandleState(EntityUid uid, ApcPowerReceiverComponent component, ref ComponentHandleState args) + { + if (args.Current is not ApcPowerReceiverComponentState state) + return; + + component.Powered = state.Powered; + } +} diff --git a/Content.Server/Power/Components/ApcPowerReceiverComponent.cs b/Content.Server/Power/Components/ApcPowerReceiverComponent.cs index b8340d0be4..9a68e2aabb 100644 --- a/Content.Server/Power/Components/ApcPowerReceiverComponent.cs +++ b/Content.Server/Power/Components/ApcPowerReceiverComponent.cs @@ -1,5 +1,6 @@ using Content.Server.Power.NodeGroups; using Content.Server.Power.Pow3r; +using Content.Shared.Power.Components; namespace Content.Server.Power.Components { @@ -8,11 +9,8 @@ namespace Content.Server.Power.Components /// so that it can receive power from a . /// [RegisterComponent] - public sealed partial class ApcPowerReceiverComponent : Component + public sealed partial class ApcPowerReceiverComponent : SharedApcPowerReceiverComponent { - [ViewVariables] - public bool Powered => (MathHelper.CloseToPercent(NetworkLoad.ReceivingPower, Load) || !NeedsPower) && !PowerDisabled; - /// /// Amount of charge this needs from an APC per second to function. /// @@ -33,7 +31,7 @@ namespace Content.Server.Power.Components { _needsPower = value; // Reset this so next tick will do a power update. - PoweredLastUpdate = null; + Recalculate = true; } } @@ -50,7 +48,8 @@ namespace Content.Server.Power.Components set => NetworkLoad.Enabled = !value; } - public bool? PoweredLastUpdate; + // TODO Is this needed? It forces a PowerChangedEvent when NeedsPower is toggled even if it changes to the same state. + public bool Recalculate; [ViewVariables] public PowerState.Load NetworkLoad { get; } = new PowerState.Load @@ -66,10 +65,5 @@ namespace Content.Server.Power.Components /// Does nothing on the client. /// [ByRefEvent] - public readonly record struct PowerChangedEvent(bool Powered, float ReceivingPower) - { - public readonly bool Powered = Powered; - public readonly float ReceivingPower = ReceivingPower; - } - + public readonly record struct PowerChangedEvent(bool Powered, float ReceivingPower); } diff --git a/Content.Server/Power/EntitySystems/PowerNetSystem.cs b/Content.Server/Power/EntitySystems/PowerNetSystem.cs index 07ecc2eafb..7bd057951c 100644 --- a/Content.Server/Power/EntitySystems/PowerNetSystem.cs +++ b/Content.Server/Power/EntitySystems/PowerNetSystem.cs @@ -19,6 +19,7 @@ namespace Content.Server.Power.EntitySystems [Dependency] private readonly AppearanceSystem _appearance = default!; [Dependency] private readonly PowerNetConnectorSystem _powerNetConnector = default!; [Dependency] private readonly IParallelManager _parMan = default!; + [Dependency] private readonly PowerReceiverSystem _powerReceiver = default!; private readonly PowerState _powerState = new(); private readonly HashSet _powerNetReconnectQueue = new(); @@ -302,19 +303,27 @@ namespace Content.Server.Power.EntitySystems var enumerator = AllEntityQuery(); while (enumerator.MoveNext(out var uid, out var apcReceiver)) { - var powered = apcReceiver.Powered; - if (powered == apcReceiver.PoweredLastUpdate) + var powered = !apcReceiver.PowerDisabled + && (!apcReceiver.NeedsPower + || MathHelper.CloseToPercent(apcReceiver.NetworkLoad.ReceivingPower, + apcReceiver.Load)); + + // If new value is the same as the old, then exit + if (!apcReceiver.Recalculate && apcReceiver.Powered == powered) continue; - if (metaQuery.GetComponent(uid).EntityPaused) + var metadata = metaQuery.Comp(uid); + if (metadata.EntityPaused) continue; - apcReceiver.PoweredLastUpdate = powered; - var ev = new PowerChangedEvent(apcReceiver.Powered, apcReceiver.NetworkLoad.ReceivingPower); + apcReceiver.Recalculate = false; + apcReceiver.Powered = powered; + Dirty(uid, apcReceiver, metadata); + var ev = new PowerChangedEvent(powered, apcReceiver.NetworkLoad.ReceivingPower); RaiseLocalEvent(uid, ref ev); - if (appearanceQuery.TryGetComponent(uid, out var appearance)) + if (appearanceQuery.TryComp(uid, out var appearance)) _appearance.SetData(uid, PowerDeviceVisuals.Powered, powered, appearance); } } diff --git a/Content.Server/Power/EntitySystems/PowerReceiverSystem.cs b/Content.Server/Power/EntitySystems/PowerReceiverSystem.cs index 048fda2355..51520f0464 100644 --- a/Content.Server/Power/EntitySystems/PowerReceiverSystem.cs +++ b/Content.Server/Power/EntitySystems/PowerReceiverSystem.cs @@ -6,15 +6,18 @@ using Content.Shared.Database; using Content.Shared.Examine; using Content.Shared.Hands.Components; using Content.Shared.Power; +using Content.Shared.Power.Components; +using Content.Shared.Power.EntitySystems; using Content.Shared.Verbs; using Robust.Server.Audio; using Robust.Server.GameObjects; using Robust.Shared.Audio; +using Robust.Shared.GameStates; using Robust.Shared.Utility; namespace Content.Server.Power.EntitySystems { - public sealed class PowerReceiverSystem : EntitySystem + public sealed class PowerReceiverSystem : SharedPowerReceiverSystem { [Dependency] private readonly IAdminLogManager _adminLogger = default!; [Dependency] private readonly IAdminManager _adminManager = default!; @@ -38,6 +41,8 @@ namespace Content.Server.Power.EntitySystems SubscribeLocalEvent>(OnGetVerbs); SubscribeLocalEvent>(AddSwitchPowerVerb); + SubscribeLocalEvent(OnGetState); + _recQuery = GetEntityQuery(); _provQuery = GetEntityQuery(); } @@ -140,14 +145,18 @@ namespace Content.Server.Power.EntitySystems args.Verbs.Add(verb); } + private void OnGetState(EntityUid uid, ApcPowerReceiverComponent component, ref ComponentGetState args) + { + args.State = new ApcPowerReceiverComponentState + { + Powered = component.Powered + }; + } + private void ProviderChanged(Entity receiver) { var comp = receiver.Comp; comp.NetworkLoad.LinkedNetwork = default; - var ev = new PowerChangedEvent(comp.Powered, comp.NetworkLoad.ReceivingPower); - - RaiseLocalEvent(receiver, ref ev); - _appearance.SetData(receiver, PowerDeviceVisuals.Powered, comp.Powered); } /// @@ -155,12 +164,10 @@ namespace Content.Server.Power.EntitySystems /// Otherwise, it returns 'true' because if something doesn't take power /// it's effectively always powered. /// + /// True when entity has no ApcPowerReceiverComponent or is Powered. False when not. public bool IsPowered(EntityUid uid, ApcPowerReceiverComponent? receiver = null) { - if (!_recQuery.Resolve(uid, ref receiver, false)) - return true; - - return receiver.Powered; + return !_recQuery.Resolve(uid, ref receiver, false) || receiver.Powered; } /// @@ -192,5 +199,10 @@ namespace Content.Server.Power.EntitySystems return !receiver.PowerDisabled; // i.e. PowerEnabled } + + public void SetLoad(ApcPowerReceiverComponent comp, float load) + { + comp.Load = load; + } } } diff --git a/Content.Shared/Power/Components/ApcPowerReceiverComponentState.cs b/Content.Shared/Power/Components/ApcPowerReceiverComponentState.cs new file mode 100644 index 0000000000..9b18d6ad93 --- /dev/null +++ b/Content.Shared/Power/Components/ApcPowerReceiverComponentState.cs @@ -0,0 +1,9 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.Power.Components; + +[Serializable, NetSerializable] +public sealed class ApcPowerReceiverComponentState : ComponentState +{ + public bool Powered; +} diff --git a/Content.Shared/Power/Components/SharedApcPowerReceiverComponent.cs b/Content.Shared/Power/Components/SharedApcPowerReceiverComponent.cs new file mode 100644 index 0000000000..d73993357a --- /dev/null +++ b/Content.Shared/Power/Components/SharedApcPowerReceiverComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Power.Components; + +[NetworkedComponent] +public abstract partial class SharedApcPowerReceiverComponent : Component +{ + [ViewVariables] + public bool Powered; +} diff --git a/Content.Shared/Power/EntitySystems/SharedPowerReceiverSystem.cs b/Content.Shared/Power/EntitySystems/SharedPowerReceiverSystem.cs new file mode 100644 index 0000000000..be2a9dc3ab --- /dev/null +++ b/Content.Shared/Power/EntitySystems/SharedPowerReceiverSystem.cs @@ -0,0 +1,6 @@ +namespace Content.Shared.Power.EntitySystems; + +public abstract class SharedPowerReceiverSystem : EntitySystem +{ + +} From e4a5f2a1450d226bf281e46f1323f41213707bb6 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Thu, 30 May 2024 17:32:16 +1000 Subject: [PATCH 08/75] Predict ActivatableUIRequiresPower (#28405) A lot of BUIs aren't going to handle the state coming in cleanly but we can fix em as we find em. --- .../Power/ActivatableUIRequiresPowerSystem.cs | 24 +++++++++++------- .../Power/EntitySystems/StaticPowerSystem.cs | 16 ++++++++++++ .../ActivatableUIRequiresPowerSystem.cs | 25 +++++++++---------- .../SharedActivatableUIRequiresPowerSystem.cs | 15 +++++++++++ 4 files changed, 58 insertions(+), 22 deletions(-) create mode 100644 Content.Client/Power/EntitySystems/StaticPowerSystem.cs create mode 100644 Content.Shared/Power/EntitySystems/SharedActivatableUIRequiresPowerSystem.cs diff --git a/Content.Client/Power/ActivatableUIRequiresPowerSystem.cs b/Content.Client/Power/ActivatableUIRequiresPowerSystem.cs index 60ed8d87b9..5a082485a5 100644 --- a/Content.Client/Power/ActivatableUIRequiresPowerSystem.cs +++ b/Content.Client/Power/ActivatableUIRequiresPowerSystem.cs @@ -1,21 +1,27 @@ +using Content.Client.Power.EntitySystems; +using Content.Shared.Popups; using Content.Shared.Power.Components; +using Content.Shared.Power.EntitySystems; using Content.Shared.UserInterface; using Content.Shared.Wires; namespace Content.Client.Power; -public sealed class ActivatableUIRequiresPowerSystem : EntitySystem +public sealed class ActivatableUIRequiresPowerSystem : SharedActivatableUIRequiresPowerSystem { - public override void Initialize() - { - base.Initialize(); + [Dependency] private readonly SharedPopupSystem _popup = default!; - SubscribeLocalEvent(OnActivate); - } - - private void OnActivate(EntityUid uid, ActivatableUIRequiresPowerComponent component, ActivatableUIOpenAttemptEvent args) + protected override void OnActivate(Entity ent, ref ActivatableUIOpenAttemptEvent args) { - // Client can't predict the power properly at the moment so rely upon the server to do it. + if (args.Cancelled || this.IsPowered(ent.Owner, EntityManager)) + { + return; + } + + if (TryComp(ent.Owner, out var panel) && panel.Open) + return; + + _popup.PopupClient(Loc.GetString("base-computer-ui-component-not-powered", ("machine", ent.Owner)), args.User, args.User); args.Cancel(); } } diff --git a/Content.Client/Power/EntitySystems/StaticPowerSystem.cs b/Content.Client/Power/EntitySystems/StaticPowerSystem.cs new file mode 100644 index 0000000000..2ca945cbbd --- /dev/null +++ b/Content.Client/Power/EntitySystems/StaticPowerSystem.cs @@ -0,0 +1,16 @@ +using Content.Client.Power.Components; + +namespace Content.Client.Power.EntitySystems; + +public static class StaticPowerSystem +{ + // Using this makes the call shorter. + // ReSharper disable once UnusedParameter.Global + public static bool IsPowered(this EntitySystem system, EntityUid uid, IEntityManager entManager, ApcPowerReceiverComponent? receiver = null) + { + if (receiver == null && !entManager.TryGetComponent(uid, out receiver)) + return false; + + return receiver.Powered; + } +} diff --git a/Content.Server/Power/EntitySystems/ActivatableUIRequiresPowerSystem.cs b/Content.Server/Power/EntitySystems/ActivatableUIRequiresPowerSystem.cs index 72843a65b8..11f35634b2 100644 --- a/Content.Server/Power/EntitySystems/ActivatableUIRequiresPowerSystem.cs +++ b/Content.Server/Power/EntitySystems/ActivatableUIRequiresPowerSystem.cs @@ -1,34 +1,33 @@ -using Content.Shared.Popups; using Content.Server.Power.Components; -using Content.Shared.UserInterface; -using JetBrains.Annotations; -using Content.Shared.Wires; -using Content.Server.UserInterface; using Content.Shared.Power.Components; +using Content.Shared.Power.EntitySystems; +using Content.Shared.UserInterface; +using Content.Shared.Wires; using ActivatableUISystem = Content.Shared.UserInterface.ActivatableUISystem; namespace Content.Server.Power.EntitySystems; -public sealed class ActivatableUIRequiresPowerSystem : EntitySystem +public sealed class ActivatableUIRequiresPowerSystem : SharedActivatableUIRequiresPowerSystem { [Dependency] private readonly ActivatableUISystem _activatableUI = default!; - [Dependency] private readonly SharedPopupSystem _popup = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnActivate); SubscribeLocalEvent(OnPowerChanged); } - private void OnActivate(EntityUid uid, ActivatableUIRequiresPowerComponent component, ActivatableUIOpenAttemptEvent args) + protected override void OnActivate(Entity ent, ref ActivatableUIOpenAttemptEvent args) { - if (args.Cancelled) return; - if (this.IsPowered(uid, EntityManager)) return; - if (TryComp(uid, out var panel) && panel.Open) + if (args.Cancelled || this.IsPowered(ent.Owner, EntityManager)) + { return; - _popup.PopupCursor(Loc.GetString("base-computer-ui-component-not-powered", ("machine", uid)), args.User); + } + + if (TryComp(ent.Owner, out var panel) && panel.Open) + return; + args.Cancel(); } diff --git a/Content.Shared/Power/EntitySystems/SharedActivatableUIRequiresPowerSystem.cs b/Content.Shared/Power/EntitySystems/SharedActivatableUIRequiresPowerSystem.cs new file mode 100644 index 0000000000..b3ac5bfbff --- /dev/null +++ b/Content.Shared/Power/EntitySystems/SharedActivatableUIRequiresPowerSystem.cs @@ -0,0 +1,15 @@ +using Content.Shared.Power.Components; +using Content.Shared.UserInterface; + +namespace Content.Shared.Power.EntitySystems; + +public abstract class SharedActivatableUIRequiresPowerSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnActivate); + } + + protected abstract void OnActivate(Entity ent, ref ActivatableUIOpenAttemptEvent args); +} From c3b687f7d60f6db8ee8bf75660c16b7379455463 Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 30 May 2024 07:33:22 +0000 Subject: [PATCH 09/75] 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 60289bd35b..7bbc12d0b9 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +1,4 @@ Entries: -- author: Krunk - changes: - - message: Clown & Jester shoes can now hold plastic knives and cap guns! All clowning, - all the time! - type: Add - id: 6143 - time: '2024-03-13T10:03:14.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25661 - author: 778b changes: - message: Guns which use battery as magazines now display ammo. Like Svalinn pistol. @@ -3864,3 +3856,10 @@ id: 6642 time: '2024-05-30T04:18:07.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28317 +- author: metalgearsloth + changes: + - message: Predict opening even more interfaces. + type: Tweak + id: 6643 + time: '2024-05-30T07:32:16.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28405 From a1bcaf58973d1b15ced827ee50bae724339d1ec0 Mon Sep 17 00:00:00 2001 From: Ed <96445749+TheShuEd@users.noreply.github.com> Date: Thu, 30 May 2024 14:39:07 +0300 Subject: [PATCH 10/75] Localize tips (#28285) * localize tip * localizzedDataset --- Content.Server/Tips/TipsSystem.cs | 4 +- Resources/Locale/en-US/tips.ftl | 134 ++++++++++++++++++++++++ Resources/Prototypes/Datasets/tips.yml | 138 +------------------------ 3 files changed, 139 insertions(+), 137 deletions(-) create mode 100644 Resources/Locale/en-US/tips.ftl diff --git a/Content.Server/Tips/TipsSystem.cs b/Content.Server/Tips/TipsSystem.cs index 168f11de8f..2f7edd45d6 100644 --- a/Content.Server/Tips/TipsSystem.cs +++ b/Content.Server/Tips/TipsSystem.cs @@ -185,11 +185,11 @@ public sealed class TipsSystem : EntitySystem private void AnnounceRandomTip() { - if (!_prototype.TryIndex(_tipsDataset, out var tips)) + if (!_prototype.TryIndex(_tipsDataset, out var tips)) return; var tip = _random.Pick(tips.Values); - var msg = Loc.GetString("tips-system-chat-message-wrap", ("tip", tip)); + var msg = Loc.GetString("tips-system-chat-message-wrap", ("tip", Loc.GetString(tip))); if (_random.Prob(_tipTippyChance)) { diff --git a/Resources/Locale/en-US/tips.ftl b/Resources/Locale/en-US/tips.ftl new file mode 100644 index 0000000000..52912d1ba4 --- /dev/null +++ b/Resources/Locale/en-US/tips.ftl @@ -0,0 +1,134 @@ +tips-dataset-1 = If you're on fire, you can click the alert on the right of your screen to stop, drop, and roll. +tips-dataset-2 = You can view and edit all keybindings used in-game at any time through the Options menu. +tips-dataset-3 = You can access the in-game guidebook through the escape menu, or by pressing Numpad 0 by default. +tips-dataset-4 = Some entities ingame have guidebook entries associated with them, which you can view by examining the entity and clicking the question mark icon. +tips-dataset-5 = Artifacts have the ability to gain permanent effects for some triggered nodes, including becoming an intercom or an extremely efficient generator. +tips-dataset-6 = You can avoid slipping on most puddles by walking. However, some strong chemicals like space lube will slip people anyway. +tips-dataset-7 = Some plants, such as galaxy thistle, can be ground up into extremely useful and potent medicines. +tips-dataset-8 = Mopping up puddles and draining them into other containers conserves the reagents found in the puddle. +tips-dataset-9 = Floor drains, usually found in the chef's freezer or janitor's office, rapidly consume reagent found in puddles around them--including blood. +tips-dataset-10 = Cognizine, a hard to manufacture chemical, makes animals sentient when they are injected with it. +tips-dataset-11 = Loaded mousetraps are incredibly effective at dealing with all manner of low-mass mobs--including Rat Servants. +tips-dataset-12 = Fire extinguishers can be loaded with any reagent in the game. +tips-dataset-13 = Some reagents, like chlorine trifluoride, have unique effects when applied by touch, such as through a spray bottle or foam. +tips-dataset-14 = Remember to touch grass in between playing Space Station 14 every once in a while. +tips-dataset-15 = You can use the Activate in World keybind, E by default, to interact with objects while your hands are full, or without picking them up. +tips-dataset-16 = Common sense goes a long way to avoiding conflict. +tips-dataset-17 = Every other player in game is a human being as well. +tips-dataset-18 = When running the Singularity, make sure to check on it periodically. If sabotaged, it could put the entire station at risk. +tips-dataset-19 = If the Singularity is up, make sure to refuel the radiation collectors once in a while. +tips-dataset-20 = Chemicals don't react while inside the ChemMaster's buffer. +tips-dataset-21 = Don't anger the bartender by throwing their glasses! Politely place them on the table by tapping Q. +tips-dataset-22 = You can hold SPACE by default to slow the movement of the shuttle when piloting, to allow for precise movements--or even coming to a complete stop. +tips-dataset-23 = Dexalin, Dexalin Plus, and Epinephrine will all purge heartbreaker toxin from your bloodstream while metabolizing. +tips-dataset-24 = Every crewmember comes with an emergency medipen in their survival box containing epinephrine and tranexamic acid. +tips-dataset-25 = The AME is a high-priority target and is easily sabotaged. Make sure to set up the Singularity or Solars so that you don't run out of power if it blows. +tips-dataset-26 = During a teslaloose, make sure to get rid of all electronic items so you don't become a target. +tips-dataset-27 = You can add labels to any item, including food or pill canisters, using a hand labeller. +tips-dataset-28 = Riot armor is significantly more powerful against opponents that aren't using guns compared to regular armor. +tips-dataset-29 = As a ghost, you can use the Verb Menu to orbit around and follow any entity in game automatically. +tips-dataset-30 = As a Traitor, you may sometimes be assigned to hunt other traitors, and in turn be hunted by others. +tips-dataset-31 = As a Traitor, the syndicate encryption key can be used to communicate through a secure channel with any other traitors who have purchased it. +tips-dataset-32 = As a Traitor, compromising important communications channels like security or engineering can give valuable intelligence. Be aware that this goes in both ways - security can compromise syndicate communications as well! +tips-dataset-33 = As a Traitor, the syndicate toolbox is extremely versatile. For only 2 telecrystals, you can get a full set of tools to help you in an emergency, insulated combat gloves and a syndicate gas mask. +tips-dataset-34 = As a Traitor, never underestimate the web vest. It may not provide space protection, but its cheap cost and robust protection makes it handy for protecting against trigger-happy foes. +tips-dataset-35 = As a Traitor, any purchased grenade penguins won't attack you, and will explode if killed. +tips-dataset-36 = As a Traitor, be careful when using vestine from the chemical synthesis kit. If someone checks your station, they could easily out you. +tips-dataset-37 = As a Traitor, remember that power sinks will create a loud sound and alert the crew after running for long enough. Try to hide them in a tricky-to-find spot, or reinforce the area around them so that they're harder to reach. +tips-dataset-38 = As a Traitor, plasma gas is an excellent way to create chaos. It can be ignited to make an area extra-uninhabitable, and can cause toxin damage to those that inhale it. +tips-dataset-39 = As a Traitor, dehydrated carps are useful for killing a large hoard of people. As long as you pat it before rehydrating it, it can be used as a great distraction. +tips-dataset-40 = As a Traitor, have you tried injecting plasma into batteries? In the case of a defibrillator, it explodes on use; hurting the user and the patient! +tips-dataset-41 = As a Nuclear Operative, stick together! While your equipment is robust, your fellow operatives are much better at saving your life: they can drag you away from danger while stunned and provide cover fire. +tips-dataset-42 = As a Nuclear Operative, communication is key! Use your radio to speak to your fellow operatives and coordinate an attack plan. +tips-dataset-43 = As a Nuclear Operative, remember that stealth is an option. It'll be hard for the captain to fight back if he gets caught off guard by what he thinks is just a regular passenger! +tips-dataset-44 = As an antagonist, be mindful of the power of destroying telecommunications. It'll be a lot harder for people to call you out if they can't do so effectively! +tips-dataset-45 = You can examine your headset to see which radio channels you have available and how to speak in them. +tips-dataset-46 = As a Salvage Specialist, always carry a GPS on you and take note of the station's coordinates in case your salvage is lost to space. +tips-dataset-47 = As a Salvage Specialist, you can use your proto-kinetic accelerator to move yourself in space when in a pinch. Just be wary that it isn't very effective. +tips-dataset-48 = As a Salvage Specialist, never forget to mine ore! Ore can be sold to cargo for a pretty penny, be used for construction, and also be used by Scientists for fancy technology. +tips-dataset-49 = As a Salvage Specialist, try asking science for a tethergun. It can be used to grab items off of salvage wrecks extremely efficiently! +tips-dataset-50 = As a Salvage Specialist, try asking science for a grappling hook. It can be used to propel yourself onto wrecks, or if stuck in space you don't have to rely on the proto-kinetic accelerator. +tips-dataset-52 = As a Salvage Specialist, consider cooperating with the Cargo Technicians. They can order you a wide variety of useful items, including ones that may be hard to get otherwise, such laser guns and shuttle building materials. +tips-dataset-53 = As a Cargo Technician, consider asking science for a Ripley APLU. When paired with a hydraulic clamp, you can grab valuable maintenance objects like fuel tanks much more easily, and make deliveries in a swift manner. +tips-dataset-54 = As a Cargo Technician, try to maintain a surplus of materials. They are extremely useful for Scientists and Station Engineers to have immediate access to. +tips-dataset-55 = As a Cargo Technician, if you have a surplus of cash try gambling! Sometimes you gain more money than you begin with. +tips-dataset-56 = As a Cargo Technician, remember that you can order guns in an emergency! The extra firepower can often be the difference between you and your fellow crewmembers living or dying. +tips-dataset-57 = As the Bartender, you can use a circular saw on your shotgun to make it easier to store. +tips-dataset-58 = As the Bartender, try experimenting with unique drinks. Have you tried to make demon's blood yet? +tips-dataset-59 = As a Botanist, you can mutate and crossbreed plants together to create more potent produce that also has higher yields. +tips-dataset-60 = As the Clown, spice your gimmicks up! Nobody likes a one-trick pony. +tips-dataset-61 = As the Clown, if you lose your banana peels and soap, you can still slip people with your PDA! Honk! +tips-dataset-62 = As the Chef, your knife can act as a weapon in an emergency. +tips-dataset-63 = As the Chef, you can sneak liquids into your foods. As a traitor, putting a little bit of amatoxin or other poison can greatly annoy the crew! +tips-dataset-64 = As the Mime, your oath of silence is your source of power. Breaking it robs you of your powers and of your honor. +tips-dataset-65 = As the Lawyer, try to negotiate with the Warden if sentences seem too high for the crime. +tips-dataset-66 = As a Security Officer, communicate and coordinate with your fellow officers using the security radio channel to avoid confusion. +tips-dataset-67 = As a Security Officer, remember that correlation does not equal causation. Someone may have just been at the wrong place at the wrong time! +tips-dataset-68 = As a Detective, you can chase criminals more effectively by using fingerprint fiber data and DNA obtained from forensic scans of objects the perpetrator likely interacted with. +tips-dataset-69 = As an Atmospheric Technician, your ATMOS holofan projector blocks gases while allowing objects to pass through. With it, you can quickly contain gas spills, fires and hull breaches. +tips-dataset-70 = As an Atmospheric Technician, try to resist the temptation of making canister bombs for Nuclear Operatives, unless you're in a last-ditch scenario. They often lead to large amounts of unnecessary friendly fire! +tips-dataset-71 = As an Engineer, you can repair cracked windows by using a lit welding tool on them while not in combat mode. +tips-dataset-72 = As an Engineer, you can electrify grilles by placing powered cables beneath them. +tips-dataset-73 = As an Engineer, always double check when you're setting up the singularity. It is easier than you think to loose it! +tips-dataset-74 = As an Engineer, you can use plasma glass to reinforce an area and prevent radiation. Uranium glass can also be used to prevent radiation. +tips-dataset-75 = As the Captain, you are one of the highest priority targets on the station. Everything from revolutions, to nuclear operatives, to traitors that need to rob you of your unique laser pistol or your life are things to worry about. +tips-dataset-76 = As the Captain, always take the nuclear disk and pinpointer with you every shift. It's a good idea to give one of these to another head you can trust with keeping it safe. +tips-dataset-77 = As the Captain, you have absolute access and control over the station, but this does not mean that being a horrible person won't result in mutiny. +tips-dataset-78 = As the Captain, try to be active and patrol the station. Staying in the bridge might be tempting, but you'll just end up putting a bigger target on your back! +tips-dataset-79 = As a Scientist, you can try random things on an artifact while the scanner is on cooldown to speed up the point extraction process significantly. +tips-dataset-80 = As a Scientist, you can utilize upgraded versions of machines to increase its effectiveness. This can make certain machines significantly better; salvage will love you if you upgrade their ore processor! +tips-dataset-81 = As a Scientist, you can build cyborgs using positronic brains and a chassis, they are just as useful as a new crew member. +tips-dataset-82 = As a Medical Doctor, try to be wary of overdosing your patients, especially if someone else has already been on the scene. Overdoses are often lethal to patients in crit! +tips-dataset-83 = As a Medical Doctor, don't underestimate your cryo pods! They heal almost every type of damage, making them very useful when you are overloaded or need to heal someone in a pinch. +tips-dataset-84 = As a Medical Doctor, exercise caution when putting reptilians in cryopods. They will take a lot of extra cold damage, but you can mitigate this with some burn medicine or leporazine. +tips-dataset-85 = As a Medical Doctor, remember that the health analyzer can be used if you lose your PDA. However it has a battery, and if it drains too quickly for your taste you can ask science to print a better battery for you! +tips-dataset-86 = As a Chemist, once you've made everything you've needed to, don't be afraid to make more silly reagents. Have you tried desoxyephedrine or licoxide? +tips-dataset-87 = As a Medical Doctor, Chemist, or Chief Medical Officer, you can use chloral hydrate to non-lethally sedate unruly patients. +tips-dataset-88 = Don't be afraid to ask for help, whether from your peers in character or through LOOC, or from admins! +tips-dataset-89 = You'll quickly lose your interest in the game if you play to win and kill. If you find yourself doing this, take a step back and talk to people--it's a much better experience! +tips-dataset-90 = If there's something you need from another department, try asking! This game isn't singleplayer and you'd be surprised what you can get accomplished together! +tips-dataset-91 = The station's nuke is invincible. Go find the disk instead of trying to destroy it. +tips-dataset-92 = Maintenance is full of equipment that is randomized every round. Look around and see if anything is worth using. +tips-dataset-93 = We were all new once, be patient and guide new players, especially those playing intern roles, in the right direction. +tips-dataset-94 = Firesuits, winter coats and emergency EVA suits offer mild protection from the cold, allowing you to spend longer periods of time near breaches and space than if wearing nothing at all. +tips-dataset-95 = In an emergency, you can always rely on firesuits and emergency EVA suits; they will always spawn in their respective lockers. They might be awkward to move around in, but can easily save your life in a dangerous situation. +tips-dataset-96 = In an emergency, remember that you can craft improvised weapons! A baseball bat or spear could easily mean the difference between deterring an attacker or perishing from the hands of one. +tips-dataset-97 = Spears can be tipped with chemicals, and will inject a few units every time you hit someone with them directly. +tips-dataset-98 = You can make spears with reinforced glass, plasma glass, or uranium glass shards to enhance their damage. +tips-dataset-99 = Thrown spears deal extra damage! Beware, however, as throwing them too much will end up breaking them. +tips-dataset-100 = All forms of toxin damage are fairly difficult to treat, and usually involve the use of chemicals or other inconvenient methods. You can use this to your advantage in combat. +tips-dataset-101 = You can throw crafted bolas at people to slow them down, letting you follow up on them for an easier kill or getaway. +tips-dataset-102 = You can put napalm in a backpack water tank to make a flamethrower. +tips-dataset-103 = Some jobs have alternate uniforms in their respective drobe vendors. Don't be afraid to try out a new look! +tips-dataset-104 = Speed is almost everything in combat. Using hardsuits just for their armor is usually a terrible idea unless the resistances it provides are geared towards combat, or you're not planning to go head-first into the fray. +tips-dataset-105 = Just because a job can't be a traitor at the beginning of a round doesn't mean that they'll never be a traitor. +tips-dataset-106 = Syndicate gas masks will both provide welding protection and block flashes. Think twice before trying to flash a Nuclear Operative! +tips-dataset-107 = Demoman takes skill. +tips-dataset-108 = You can spray a fire extinguisher, throw items or fire a gun while floating through space to give yourself a minor boost. Simply fire opposite to where you want to go. +tips-dataset-109 = You can drag other players onto yourself to open the strip menu, allowing you to remove their equipment or force them to wear something. Note that exosuits or helmets will block your access to the clothing beneath them, and that certain items take longer to strip or put on than others. +tips-dataset-110 = You can climb onto a table by dragging yourself onto one. +tips-dataset-111 = You can move an item out of the way by dragging it, and then holding CTRL + right click and moving your mouse into the direction you want it to go. +tips-dataset-112 = When dealing with security, you can often get your sentence negated entirely through cooperation and deception. +tips-dataset-113 = Fire can spread to other players through touch! Be careful around flaming bodies or large crowds with people on fire in them. +tips-dataset-114 = Hull breaches take a few seconds to fully space an area. You can use this time to patch up the hole if you're confident enough, or just run away. +tips-dataset-115 = Burn damage, such as that from a welding tool or lightbulb, can be used to cauterize wounds and stop bleeding. +tips-dataset-116 = Bleeding is no joke! If you've been shot or acquired any other major injury, make sure to treat it quickly. +tips-dataset-117 = In an emergency, you can butcher a jumpsuit with a sharp object to get cloth, which can be crafted into gauze. +tips-dataset-118 = You can use sharp objects to butcher clothes or animals in the right click context menu. This includes glass shards. +tips-dataset-119 = Most explosives have an adjustable timer that you can set in the right click menu. This includes grenade penguins! +tips-dataset-120 = You can stun grenade penguins, which can bide valuable time for you to kill them. +tips-dataset-121 = You can click on the names of items to pick them up in the right click menu, instead of hovering over the item and then selecting pick up. +tips-dataset-122 = Space Station 14 is open source! If there's a change you want to make, or a simple item you want to add, then try contributing to the game. It's not as hard as you'd think it is. +tips-dataset-123 = In a pinch, you can throw drinks or other reagent containers behind you to create a spill that can slip people chasing you. +tips-dataset-124 = Some weapons, such as knives & shivs, have a fast attack speed. +tips-dataset-125 = The jaws of life can be used to open powered doors. +tips-dataset-126 = If you're not a human, you can drink blood to heal back some of your blood volume, albeit very inefficiently. +tips-dataset-127 = If you're a human, don't drink blood! It makes you sick and you'll begin to take damage. +tips-dataset-128 = There is a chemical metabolism limit that limits the amount of reagents of a certain type you can digest at once. Certain species have higher metabolism limits, such as slimes. +tips-dataset-129 = Welding without proper eye protection can cause eye damage, which must be cured with oculine. +tips-dataset-130 = Zombies are very vulnerable to heat damage, making welding tools and laser guns extremely effective against them. +tips-dataset-131 = You can weld glass shards into glass sheets. +tips-dataset-132 = By right clicking on a player, and then clicking the heart icon, you can quickly examine them to check for injuries or how badly they're bleeding. You can also do this to yourself. +tips-dataset-133 = Monkeys and kobolds have a rare chance to be sentient. Ook! +tips-dataset-134 = You can tell if an area with firelocks up is spaced by looking to see if the firelocks have lights beside them. +tips-dataset-135 = Instead of picking it up, you can alt-click food to eat it. This also works for mice and other creatures without hands. diff --git a/Resources/Prototypes/Datasets/tips.yml b/Resources/Prototypes/Datasets/tips.yml index 88dc09c220..b710d69fab 100644 --- a/Resources/Prototypes/Datasets/tips.yml +++ b/Resources/Prototypes/Datasets/tips.yml @@ -1,137 +1,5 @@ -- type: dataset +- type: localizedDataset id: Tips values: - - "If you're on fire, you can click the alert on the right of your screen to stop, drop, and roll." - - "You can view and edit all keybindings used in-game at any time through the Options menu." - - "You can access the in-game guidebook through the escape menu, or by pressing Numpad 0 by default." - - "Some entities ingame have guidebook entries associated with them, which you can view by examining the entity and clicking the question mark icon." - - "Artifacts have the ability to gain permanent effects for some triggered nodes, including becoming an intercom or an extremely efficient generator." - - "You can avoid slipping on most puddles by walking. However, some strong chemicals like space lube will slip people anyway." - - "Some plants, such as galaxy thistle, can be ground up into extremely useful and potent medicines." - - "Mopping up puddles and draining them into other containers conserves the reagents found in the puddle." - - "Floor drains, usually found in the chef's freezer or janitor's office, rapidly consume reagent found in puddles around them--including blood." - - "Cognizine, a hard to manufacture chemical, makes animals sentient when they are injected with it." - - "Loaded mousetraps are incredibly effective at dealing with all manner of low-mass mobs--including Rat Servants." - - "Fire extinguishers can be loaded with any reagent in the game." - - "Some reagents, like chlorine trifluoride, have unique effects when applied by touch, such as through a spray bottle or foam." - - "Remember to touch grass in between playing Space Station 14 every once in a while." - - "You can use the Activate in World keybind, E by default, to interact with objects while your hands are full, or without picking them up." - - "Common sense goes a long way to avoiding conflict." - - "Every other player in game is a human being as well." - - "When running the Singularity, make sure to check on it periodically. If sabotaged, it could put the entire station at risk." - - "If the Singularity is up, make sure to refuel the radiation collectors once in a while." - - "Chemicals don't react while inside the ChemMaster's buffer." - - "Don't anger the bartender by throwing their glasses! Politely place them on the table by tapping Q." - - "You can hold SPACE by default to slow the movement of the shuttle when piloting, to allow for precise movements--or even coming to a complete stop." - - "Dexalin, Dexalin Plus, and Epinephrine will all purge heartbreaker toxin from your bloodstream while metabolizing." - - "Every crewmember comes with an emergency medipen in their survival box containing epinephrine and tranexamic acid." - - "The AME is a high-priority target and is easily sabotaged. Make sure to set up the Singularity or Solars so that you don't run out of power if it blows." - - "During a teslaloose, make sure to get rid of all electronic items so you don't become a target." - - "You can add labels to any item, including food or pill canisters, using a hand labeller." - - "Riot armor is significantly more powerful against opponents that aren't using guns compared to regular armor." - - "As a ghost, you can use the Verb Menu to orbit around and follow any entity in game automatically." - - "As a Traitor, you may sometimes be assigned to hunt other traitors, and in turn be hunted by others." - - "As a Traitor, the syndicate encryption key can be used to communicate through a secure channel with any other traitors who have purchased it." - - "As a Traitor, compromising important communications channels like security or engineering can give valuable intelligence. Be aware that this goes in both ways - security can compromise syndicate communications as well!" - - "As a Traitor, the syndicate toolbox is extremely versatile. For only 2 telecrystals, you can get a full set of tools to help you in an emergency, insulated combat gloves and a syndicate gas mask." - - "As a Traitor, never underestimate the web vest. It may not provide space protection, but its cheap cost and robust protection makes it handy for protecting against trigger-happy foes." - - "As a Traitor, any purchased grenade penguins won't attack you, and will explode if killed." - - "As a Traitor, be careful when using vestine from the chemical synthesis kit. If someone checks your station, they could easily out you." - - "As a Traitor, remember that power sinks will create a loud sound and alert the crew after running for long enough. Try to hide them in a tricky-to-find spot, or reinforce the area around them so that they're harder to reach." - - "As a Traitor, plasma gas is an excellent way to create chaos. It can be ignited to make an area extra-uninhabitable, and can cause toxin damage to those that inhale it." - - "As a Traitor, dehydrated carps are useful for killing a large hoard of people. As long as you pat it before rehydrating it, it can be used as a great distraction." - - "As a Traitor, have you tried injecting plasma into batteries? In the case of a defibrillator, it explodes on use; hurting the user and the patient!" - - "As a Nuclear Operative, stick together! While your equipment is robust, your fellow operatives are much better at saving your life: they can drag you away from danger while stunned and provide cover fire." - - "As a Nuclear Operative, communication is key! Use your radio to speak to your fellow operatives and coordinate an attack plan." - - "As a Nuclear Operative, remember that stealth is an option. It'll be hard for the captain to fight back if he gets caught off guard by what he thinks is just a regular passenger!" - - "As an antagonist, be mindful of the power of destroying telecommunications. It'll be a lot harder for people to call you out if they can't do so effectively!" - - "You can examine your headset to see which radio channels you have available and how to speak in them." - - "As a Salvage Specialist, always carry a GPS on you and take note of the station's coordinates in case your salvage is lost to space." - - "As a Salvage Specialist, you can use your proto-kinetic accelerator to move yourself in space when in a pinch. Just be wary that it isn't very effective." - - "As a Salvage Specialist, never forget to mine ore! Ore can be sold to cargo for a pretty penny, be used for construction, and also be used by Scientists for fancy technology." - - "As a Salvage Specialist, try asking science for a tethergun. It can be used to grab items off of salvage wrecks extremely efficiently!" - - "As a Salvage Specialist, try asking science for a grappling hook. It can be used to propel yourself onto wrecks, or if stuck in space you don't have to rely on the proto-kinetic accelerator." - - "As a Salvage Specialist, consider cooperating with the Cargo Technicians. They can order you a wide variety of useful items, including ones that may be hard to get otherwise, such laser guns and shuttle building materials." - - "As a Cargo Technician, consider asking science for a Ripley APLU. When paired with a hydraulic clamp, you can grab valuable maintenance objects like fuel tanks much more easily, and make deliveries in a swift manner." - - "As a Cargo Technician, try to maintain a surplus of materials. They are extremely useful for Scientists and Station Engineers to have immediate access to." - - "As a Cargo Technician, if you have a surplus of cash try gambling! Sometimes you gain more money than you begin with." - - "As a Cargo Technician, remember that you can order guns in an emergency! The extra firepower can often be the difference between you and your fellow crewmembers living or dying." - - "As the Bartender, you can use a circular saw on your shotgun to make it easier to store." - - "As the Bartender, try experimenting with unique drinks. Have you tried to make demon's blood yet?" - - "As a Botanist, you can mutate and crossbreed plants together to create more potent produce that also has higher yields." - - "As the Clown, spice your gimmicks up! Nobody likes a one-trick pony." - - "As the Clown, if you lose your banana peels and soap, you can still slip people with your PDA! Honk!" - - "As the Chef, your knife can act as a weapon in an emergency." - - "As the Chef, you can sneak liquids into your foods. As a traitor, putting a little bit of amatoxin or other poison can greatly annoy the crew!" - - "As the Mime, your oath of silence is your source of power. Breaking it robs you of your powers and of your honor." - - "As the Lawyer, try to negotiate with the Warden if sentences seem too high for the crime." - - "As a Security Officer, communicate and coordinate with your fellow officers using the security radio channel to avoid confusion." - - "As a Security Officer, remember that correlation does not equal causation. Someone may have just been at the wrong place at the wrong time!" - - "As a Detective, you can chase criminals more effectively by using fingerprint fiber data and DNA obtained from forensic scans of objects the perpetrator likely interacted with." - - "As an Atmospheric Technician, your ATMOS holofan projector blocks gases while allowing objects to pass through. With it, you can quickly contain gas spills, fires and hull breaches." - - "As an Atmospheric Technician, try to resist the temptation of making canister bombs for Nuclear Operatives, unless you're in a last-ditch scenario. They often lead to large amounts of unnecessary friendly fire!" - - "As an Engineer, you can repair cracked windows by using a lit welding tool on them while not in combat mode." - - "As an Engineer, you can electrify grilles by placing powered cables beneath them." - - "As an Engineer, always double check when you're setting up the singularity. It is easier than you think to loose it!" - - "As an Engineer, you can use plasma glass to reinforce an area and prevent radiation. Uranium glass can also be used to prevent radiation." - - "As the Captain, you are one of the highest priority targets on the station. Everything from revolutions, to nuclear operatives, to traitors that need to rob you of your unique laser pistol or your life are things to worry about." - - "As the Captain, always take the nuclear disk and pinpointer with you every shift. It's a good idea to give one of these to another head you can trust with keeping it safe." - - "As the Captain, you have absolute access and control over the station, but this does not mean that being a horrible person won't result in mutiny." - - "As the Captain, try to be active and patrol the station. Staying in the bridge might be tempting, but you'll just end up putting a bigger target on your back!" - - "As a Scientist, you can try random things on an artifact while the scanner is on cooldown to speed up the point extraction process significantly." - - "As a Scientist, you can utilize upgraded versions of machines to increase its effectiveness. This can make certain machines significantly better; salvage will love you if you upgrade their ore processor!" - - "As a Scientist, you can build cyborgs using positronic brains and a chassis, they are just as useful as a new crew member." - - "As a Medical Doctor, try to be wary of overdosing your patients, especially if someone else has already been on the scene. Overdoses are often lethal to patients in crit!" - - "As a Medical Doctor, don't underestimate your cryo pods! They heal almost every type of damage, making them very useful when you are overloaded or need to heal someone in a pinch." - - "As a Medical Doctor, exercise caution when putting reptilians in cryopods. They will take a lot of extra cold damage, but you can mitigate this with some burn medicine or leporazine." - - "As a Medical Doctor, remember that the health analyzer can be used if you lose your PDA. However it has a battery, and if it drains too quickly for your taste you can ask science to print a better battery for you!" - - "As a Chemist, once you've made everything you've needed to, don't be afraid to make more silly reagents. Have you tried desoxyephedrine or licoxide?" - - "As a Medical Doctor, Chemist, or Chief Medical Officer, you can use chloral hydrate to non-lethally sedate unruly patients." - - "Don't be afraid to ask for help, whether from your peers in character or through LOOC, or from admins!" - - "You'll quickly lose your interest in the game if you play to win and kill. If you find yourself doing this, take a step back and talk to people--it's a much better experience!" - - "If there's something you need from another department, try asking! This game isn't singleplayer and you'd be surprised what you can get accomplished together!" - - "The station's nuke is invincible. Go find the disk instead of trying to destroy it." - - "Maintenance is full of equipment that is randomized every round. Look around and see if anything is worth using." - - "We were all new once, be patient and guide new players, especially those playing intern roles, in the right direction." - - "Firesuits, winter coats and emergency EVA suits offer mild protection from the cold, allowing you to spend longer periods of time near breaches and space than if wearing nothing at all." - - "In an emergency, you can always rely on firesuits and emergency EVA suits; they will always spawn in their respective lockers. They might be awkward to move around in, but can easily save your life in a dangerous situation." - - "In an emergency, remember that you can craft improvised weapons! A baseball bat or spear could easily mean the difference between deterring an attacker or perishing from the hands of one." - - "Spears can be tipped with chemicals, and will inject a few units every time you hit someone with them directly." - - "You can make spears with reinforced glass, plasma glass, or uranium glass shards to enhance their damage." - - "Thrown spears deal extra damage! Beware, however, as throwing them too much will end up breaking them." - - "All forms of toxin damage are fairly difficult to treat, and usually involve the use of chemicals or other inconvenient methods. You can use this to your advantage in combat." - - "You can throw crafted bolas at people to slow them down, letting you follow up on them for an easier kill or getaway." - - "You can put napalm in a backpack water tank to make a flamethrower." - - "Some jobs have alternate uniforms in their respective drobe vendors. Don't be afraid to try out a new look!" - - "Speed is almost everything in combat. Using hardsuits just for their armor is usually a terrible idea unless the resistances it provides are geared towards combat, or you're not planning to go head-first into the fray." - - "Just because a job can't be a traitor at the beginning of a round doesn't mean that they'll never be a traitor." - - "Syndicate gas masks will both provide welding protection and block flashes. Think twice before trying to flash a Nuclear Operative!" - - "Demoman takes skill." - - "You can spray a fire extinguisher, throw items or fire a gun while floating through space to give yourself a minor boost. Simply fire opposite to where you want to go." - - "You can drag other players onto yourself to open the strip menu, allowing you to remove their equipment or force them to wear something. Note that exosuits or helmets will block your access to the clothing beneath them, and that certain items take longer to strip or put on than others." - - "You can climb onto a table by dragging yourself onto one." - - "You can move an item out of the way by dragging it, and then holding CTRL + right click and moving your mouse into the direction you want it to go." - - "When dealing with security, you can often get your sentence negated entirely through cooperation and deception." - - "Fire can spread to other players through touch! Be careful around flaming bodies or large crowds with people on fire in them." - - "Hull breaches take a few seconds to fully space an area. You can use this time to patch up the hole if you're confident enough, or just run away." - - "Burn damage, such as that from a welding tool or lightbulb, can be used to cauterize wounds and stop bleeding." - - "Bleeding is no joke! If you've been shot or acquired any other major injury, make sure to treat it quickly." - - "In an emergency, you can butcher a jumpsuit with a sharp object to get cloth, which can be crafted into gauze." - - "You can use sharp objects to butcher clothes or animals in the right click context menu. This includes glass shards." - - "Most explosives have an adjustable timer that you can set in the right click menu. This includes grenade penguins!" - - "You can stun grenade penguins, which can bide valuable time for you to kill them." - - "You can click on the names of items to pick them up in the right click menu, instead of hovering over the item and then selecting pick up." - - "Space Station 14 is open source! If there's a change you want to make, or a simple item you want to add, then try contributing to the game. It's not as hard as you'd think it is." - - "In a pinch, you can throw drinks or other reagent containers behind you to create a spill that can slip people chasing you." - - "Some weapons, such as knives & shivs, have a fast attack speed." - - "The jaws of life can be used to open powered doors." - - "If you're not a human, you can drink blood to heal back some of your blood volume, albeit very inefficiently." - - "If you're a human, don't drink blood! It makes you sick and you'll begin to take damage." - - "There is a chemical metabolism limit that limits the amount of reagents of a certain type you can digest at once. Certain species have higher metabolism limits, such as slimes." - - "Welding without proper eye protection can cause eye damage, which must be cured with oculine." - - "Zombies are very vulnerable to heat damage, making welding tools and laser guns extremely effective against them." - - "You can weld glass shards into glass sheets." - - "By right clicking on a player, and then clicking the heart icon, you can quickly examine them to check for injuries or how badly they're bleeding. You can also do this to yourself." - - "Monkeys and kobolds have a rare chance to be sentient. Ook!" - - "You can tell if an area with firelocks up is spaced by looking to see if the firelocks have lights beside them." - - "Instead of picking it up, you can alt-click food to eat it. This also works for mice and other creatures without hands." + prefix: tips-dataset- + count: 135 From d6f1f0ac1cba76e469848026359e24eede7d5602 Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Thu, 30 May 2024 07:49:12 -0400 Subject: [PATCH 11/75] Convert story generation to use LocalizedDatasets (#28402) Converted story generation to use LocalizedDatasets --- .../Prototypes/StoryTemplatePrototype.cs | 4 +- .../Prototypes/Datasets/story_generation.yml | 266 +++--------------- 2 files changed, 35 insertions(+), 235 deletions(-) diff --git a/Content.Shared/StoryGen/Prototypes/StoryTemplatePrototype.cs b/Content.Shared/StoryGen/Prototypes/StoryTemplatePrototype.cs index 7f6afacccc..948c7b2dc0 100644 --- a/Content.Shared/StoryGen/Prototypes/StoryTemplatePrototype.cs +++ b/Content.Shared/StoryGen/Prototypes/StoryTemplatePrototype.cs @@ -24,10 +24,10 @@ public sealed partial class StoryTemplatePrototype : IPrototype /// /// Dictionary containing the name of each variable to pass to the template and the ID of the - /// from which a random entry will be selected as its value. + /// from which a random entry will be selected as its value. /// For example, name: book_character will pick a random entry from the book_character /// dataset which can then be used in the template by {$name}. /// [DataField] - public Dictionary> Variables { get; } = default!; + public Dictionary> Variables { get; } = default!; } diff --git a/Resources/Prototypes/Datasets/story_generation.yml b/Resources/Prototypes/Datasets/story_generation.yml index 1a461c7596..9f5741c433 100644 --- a/Resources/Prototypes/Datasets/story_generation.yml +++ b/Resources/Prototypes/Datasets/story_generation.yml @@ -1,266 +1,66 @@ -- type: dataset +- type: localizedDataset id: BookTypes values: - - story-gen-book-type1 - - story-gen-book-type2 - - story-gen-book-type3 - - story-gen-book-type4 - - story-gen-book-type5 - - story-gen-book-type6 - - story-gen-book-type7 - - story-gen-book-type8 - - story-gen-book-type9 - - story-gen-book-type10 - - story-gen-book-type11 - - story-gen-book-type12 + prefix: story-gen-book-type + count: 12 -- type: dataset +- type: localizedDataset id: BookGenres values: - - story-gen-book-genre1 - - story-gen-book-genre2 - - story-gen-book-genre3 - - story-gen-book-genre4 - - story-gen-book-genre5 - - story-gen-book-genre6 - - story-gen-book-genre7 - - story-gen-book-genre8 - - story-gen-book-genre9 - - story-gen-book-genre10 - - story-gen-book-genre11 - - story-gen-book-genre12 - - story-gen-book-genre13 - - story-gen-book-genre14 + prefix: story-gen-book-genre + count: 14 -- type: dataset +- type: localizedDataset id: BookHintAppearances values: - - story-gen-book-appearance1 - - story-gen-book-appearance2 - - story-gen-book-appearance3 - - story-gen-book-appearance4 - - story-gen-book-appearance5 - - story-gen-book-appearance6 - - story-gen-book-appearance7 - - story-gen-book-appearance8 - - story-gen-book-appearance9 - - story-gen-book-appearance10 - - story-gen-book-appearance11 - - story-gen-book-appearance12 - - story-gen-book-appearance13 - - story-gen-book-appearance14 - - story-gen-book-appearance15 - - story-gen-book-appearance16 - - story-gen-book-appearance17 - - story-gen-book-appearance18 - - story-gen-book-appearance19 - - story-gen-book-appearance20 - - story-gen-book-appearance21 - - story-gen-book-appearance22 - - story-gen-book-appearance23 - - story-gen-book-appearance24 - - story-gen-book-appearance25 - - story-gen-book-appearance26 - - story-gen-book-appearance27 + prefix: story-gen-book-appearance + count: 27 -- type: dataset +- type: localizedDataset id: BookCharacters values: - - story-gen-book-character1 - - story-gen-book-character2 - - story-gen-book-character3 - - story-gen-book-character4 - - story-gen-book-character5 - - story-gen-book-character6 - - story-gen-book-character7 - - story-gen-book-character8 - - story-gen-book-character9 - - story-gen-book-character10 - - story-gen-book-character11 - - story-gen-book-character12 - - story-gen-book-character13 - - story-gen-book-character14 - - story-gen-book-character15 - - story-gen-book-character16 - - story-gen-book-character17 - - story-gen-book-character18 - - story-gen-book-character19 - - story-gen-book-character20 - - story-gen-book-character21 - - story-gen-book-character22 - - story-gen-book-character23 - - story-gen-book-character24 - - story-gen-book-character25 - - story-gen-book-character26 - - story-gen-book-character27 - - story-gen-book-character28 - - story-gen-book-character29 - - story-gen-book-character30 - - story-gen-book-character31 - - story-gen-book-character32 - - story-gen-book-character33 - - story-gen-book-character34 - - story-gen-book-character35 - - story-gen-book-character36 - - story-gen-book-character37 - - story-gen-book-character38 - - story-gen-book-character39 - - story-gen-book-character40 + prefix: story-gen-book-character + count: 40 -- type: dataset +- type: localizedDataset id: BookCharacterTraits values: - - story-gen-book-character-trait1 - - story-gen-book-character-trait2 - - story-gen-book-character-trait3 - - story-gen-book-character-trait4 - - story-gen-book-character-trait5 - - story-gen-book-character-trait6 - - story-gen-book-character-trait7 - - story-gen-book-character-trait8 - - story-gen-book-character-trait9 - - story-gen-book-character-trait10 - - story-gen-book-character-trait11 - - story-gen-book-character-trait12 - - story-gen-book-character-trait13 - - story-gen-book-character-trait14 - - story-gen-book-character-trait15 - - story-gen-book-character-trait16 - - story-gen-book-character-trait17 - - story-gen-book-character-trait18 - - story-gen-book-character-trait19 - - story-gen-book-character-trait20 - - story-gen-book-character-trait21 - - story-gen-book-character-trait22 - - story-gen-book-character-trait23 - - story-gen-book-character-trait24 + prefix: story-gen-book-character-trait + count: 24 -- type: dataset +- type: localizedDataset id: BookEvents values: - - story-gen-book-event1 - - story-gen-book-event2 - - story-gen-book-event3 - - story-gen-book-event4 - - story-gen-book-event5 - - story-gen-book-event6 - - story-gen-book-event7 - - story-gen-book-event8 - - story-gen-book-event9 - - story-gen-book-event10 - - story-gen-book-event11 - - story-gen-book-event12 - - story-gen-book-event13 - - story-gen-book-event14 - - story-gen-book-event15 - - story-gen-book-event16 - - story-gen-book-event17 - - story-gen-book-event18 - - story-gen-book-event19 - - story-gen-book-event20 - - story-gen-book-event21 - - story-gen-book-event22 - - story-gen-book-event23 - - story-gen-book-event24 + prefix: story-gen-book-event + count: 24 -- type: dataset +- type: localizedDataset id: BookActions values: - - story-gen-book-action1 - - story-gen-book-action2 - - story-gen-book-action3 - - story-gen-book-action4 - - story-gen-book-action5 - - story-gen-book-action6 - - story-gen-book-action7 - - story-gen-book-action8 - - story-gen-book-action9 - - story-gen-book-action10 - - story-gen-book-action11 - - story-gen-book-action12 + prefix: story-gen-book-action + count: 12 -- type: dataset +- type: localizedDataset id: BookActionTraits values: - - story-gen-book-action-trait1 - - story-gen-book-action-trait2 - - story-gen-book-action-trait3 - - story-gen-book-action-trait4 - - story-gen-book-action-trait5 - - story-gen-book-action-trait6 - - story-gen-book-action-trait7 - - story-gen-book-action-trait8 - - story-gen-book-action-trait9 - - story-gen-book-action-trait10 - - story-gen-book-action-trait11 - - story-gen-book-action-trait12 - - story-gen-book-action-trait13 + prefix: story-gen-book-action-trait + count: 13 -- type: dataset +- type: localizedDataset id: BookLocations values: - - story-gen-book-location1 - - story-gen-book-location2 - - story-gen-book-location3 - - story-gen-book-location4 - - story-gen-book-location5 - - story-gen-book-location6 - - story-gen-book-location7 - - story-gen-book-location8 - - story-gen-book-location9 - - story-gen-book-location10 - - story-gen-book-location11 - - story-gen-book-location12 - - story-gen-book-location13 - - story-gen-book-location14 - - story-gen-book-location15 - - story-gen-book-location16 - - story-gen-book-location17 - - story-gen-book-location18 - - story-gen-book-location19 - - story-gen-book-location20 - - story-gen-book-location21 - - story-gen-book-location22 - - story-gen-book-location23 - - story-gen-book-location24 - - story-gen-book-location25 - - story-gen-book-location26 - - story-gen-book-location27 - - story-gen-book-location28 - - story-gen-book-location29 - - story-gen-book-location30 - - story-gen-book-location31 - - story-gen-book-location32 - - story-gen-book-location33 - - story-gen-book-location34 + prefix: story-gen-book-location + count: 34 -- type: dataset +- type: localizedDataset id: BookStoryElements values: - - story-gen-book-element1 - - story-gen-book-element2 - - story-gen-book-element3 - - story-gen-book-element4 - - story-gen-book-element5 - - story-gen-book-element6 - - story-gen-book-element7 - - story-gen-book-element8 - - story-gen-book-element9 + prefix: story-gen-book-element + count: 9 -- type: dataset +- type: localizedDataset id: BookStoryElementTraits values: - - story-gen-book-element-trait1 - - story-gen-book-element-trait2 - - story-gen-book-element-trait3 - - story-gen-book-element-trait4 - - story-gen-book-element-trait5 - - story-gen-book-element-trait6 - - story-gen-book-element-trait7 - - story-gen-book-element-trait8 - - story-gen-book-element-trait9 - - story-gen-book-element-trait10 - - story-gen-book-element-trait11 - - story-gen-book-element-trait12 - - story-gen-book-element-trait13 + prefix: story-gen-book-element-trait + count: 13 From 98446cb0616be2b2e89144293122f6ff9bff7d50 Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Thu, 30 May 2024 12:08:42 -0400 Subject: [PATCH 12/75] Convert advertisements to use localized datasets (#28400) * Convert advertisements to use LocalizedDatasets * File consolidation * Arcade machines too --- .../Components/AdvertiseComponent.cs | 8 +- .../Components/SpeakOnUIClosedComponent.cs | 6 +- .../EntitySystems/AdvertiseSystem.cs | 2 +- .../EntitySystems/SpeakOnUIClosedSystem.cs | 7 +- .../Advertise/MessagePackPrototype.cs | 14 - .../vending-machines/vending-machine.ftl | 2 +- .../Arcade/Advertisements/blockgame.yml | 16 -- .../Arcade/Advertisements/spacevillain.yml | 18 -- .../Catalog/Arcade/Goodbyes/blockgame.yml | 15 - .../Catalog/Arcade/Goodbyes/spacevillain.yml | 15 - .../Catalog/Arcade/advertisements.yml | 11 + .../Prototypes/Catalog/Arcade/goodbyes.yml | 11 + .../VendingMachines/Advertisements/ammo.yml | 13 - .../Advertisements/atmosdrobe.yml | 6 - .../Advertisements/bardrobe.yml | 5 - .../Advertisements/boozeomat.yml | 22 -- .../Advertisements/cargodrobe.yml | 6 - .../VendingMachines/Advertisements/chang.yml | 8 - .../Advertisements/chefdrobe.yml | 6 - .../Advertisements/chefvend.yml | 12 - .../Advertisements/chemdrobe.yml | 6 - .../VendingMachines/Advertisements/cigs.yml | 15 - .../Advertisements/clothesmate.yml | 10 - .../VendingMachines/Advertisements/coffee.yml | 17 -- .../VendingMachines/Advertisements/cola.yml | 11 - .../Advertisements/condiments.yml | 9 - .../Advertisements/curadrobe.yml | 6 - .../Advertisements/detdrobe.yml | 6 - .../Advertisements/dinnerware.yml | 13 - .../Advertisements/discount.yml | 12 - .../VendingMachines/Advertisements/donut.yml | 8 - .../Advertisements/engidrobe.yml | 8 - .../Advertisements/fatextractor.yml | 9 - .../VendingMachines/Advertisements/games.yml | 13 - .../Advertisements/genedrobe.yml | 5 - .../Advertisements/happyhonk.yml | 13 - .../Advertisements/hydrobe.yml | 7 - .../Advertisements/janidrobe.yml | 6 - .../Advertisements/lawdrobe.yml | 11 - .../Advertisements/magivend.yml | 14 - .../Advertisements/medidrobe.yml | 6 - .../Advertisements/megaseed.yml | 9 - .../Advertisements/nanomed.yml | 12 - .../Advertisements/nutrimax.yml | 10 - .../Advertisements/robodrobe.yml | 7 - .../Advertisements/scidrobe.yml | 6 - .../Advertisements/secdrobe.yml | 8 - .../Advertisements/sectech.yml | 8 - .../Advertisements/smartfridge.yml | 11 - .../VendingMachines/Advertisements/snack.yml | 18 -- .../Advertisements/sovietsoda.yml | 9 - .../Advertisements/syndiedrobe.yml | 34 --- .../Advertisements/theater.yml | 9 - .../Advertisements/vendomat.yml | 10 - .../Advertisements/virodrobe.yml | 6 - .../VendingMachines/Goodbyes/boozeomat.yml | 7 - .../VendingMachines/Goodbyes/chang.yml | 6 - .../VendingMachines/Goodbyes/chefvend.yml | 8 - .../Catalog/VendingMachines/Goodbyes/cigs.yml | 7 - .../VendingMachines/Goodbyes/coffee.yml | 8 - .../Catalog/VendingMachines/Goodbyes/cola.yml | 8 - .../VendingMachines/Goodbyes/discount.yml | 12 - .../VendingMachines/Goodbyes/donut.yml | 8 - .../VendingMachines/Goodbyes/games.yml | 8 - .../VendingMachines/Goodbyes/generic.yml | 4 - .../VendingMachines/Goodbyes/happyhonk.yml | 8 - .../VendingMachines/Goodbyes/lawdrobe.yml | 8 - .../VendingMachines/Goodbyes/nutrimax.yml | 6 - .../VendingMachines/Goodbyes/sectech.yml | 7 - .../VendingMachines/Goodbyes/snack.yml | 10 - .../VendingMachines/Goodbyes/sovietsoda.yml | 7 - .../VendingMachines/Goodbyes/syndiedrobe.yml | 9 - .../VendingMachines/advertisements.yml | 257 ++++++++++++++++++ .../Catalog/VendingMachines/goodbyes.yml | 101 +++++++ 74 files changed, 392 insertions(+), 662 deletions(-) delete mode 100644 Content.Shared/Advertise/MessagePackPrototype.cs delete mode 100644 Resources/Prototypes/Catalog/Arcade/Advertisements/blockgame.yml delete mode 100644 Resources/Prototypes/Catalog/Arcade/Advertisements/spacevillain.yml delete mode 100644 Resources/Prototypes/Catalog/Arcade/Goodbyes/blockgame.yml delete mode 100644 Resources/Prototypes/Catalog/Arcade/Goodbyes/spacevillain.yml create mode 100644 Resources/Prototypes/Catalog/Arcade/advertisements.yml create mode 100644 Resources/Prototypes/Catalog/Arcade/goodbyes.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/ammo.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/atmosdrobe.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/bardrobe.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/boozeomat.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/cargodrobe.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/chang.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/chefdrobe.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/chefvend.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/chemdrobe.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/cigs.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/clothesmate.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/coffee.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/cola.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/condiments.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/curadrobe.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/detdrobe.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/dinnerware.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/discount.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/donut.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/engidrobe.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/fatextractor.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/games.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/genedrobe.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/happyhonk.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/hydrobe.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/janidrobe.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/lawdrobe.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/magivend.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/medidrobe.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/megaseed.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/nanomed.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/nutrimax.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/robodrobe.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/scidrobe.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/secdrobe.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/sectech.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/smartfridge.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/snack.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/sovietsoda.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/syndiedrobe.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/theater.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/vendomat.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/virodrobe.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/boozeomat.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/chang.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/chefvend.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/cigs.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/coffee.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/cola.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/discount.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/donut.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/games.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/generic.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/happyhonk.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/lawdrobe.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/nutrimax.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/sectech.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/snack.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/sovietsoda.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/syndiedrobe.yml create mode 100644 Resources/Prototypes/Catalog/VendingMachines/advertisements.yml create mode 100644 Resources/Prototypes/Catalog/VendingMachines/goodbyes.yml diff --git a/Content.Server/Advertise/Components/AdvertiseComponent.cs b/Content.Server/Advertise/Components/AdvertiseComponent.cs index 3d617e3a34..94650234bb 100644 --- a/Content.Server/Advertise/Components/AdvertiseComponent.cs +++ b/Content.Server/Advertise/Components/AdvertiseComponent.cs @@ -1,12 +1,12 @@ using Content.Server.Advertise.EntitySystems; -using Content.Shared.Advertise; +using Content.Shared.Dataset; using Robust.Shared.Prototypes; namespace Content.Server.Advertise.Components; /// /// Makes this entity periodically advertise by speaking a randomly selected -/// message from a specified MessagePack into local chat. +/// message from a specified dataset into local chat. /// [RegisterComponent, Access(typeof(AdvertiseSystem))] public sealed partial class AdvertiseComponent : Component @@ -33,10 +33,10 @@ public sealed partial class AdvertiseComponent : Component public bool Prewarm = true; /// - /// The identifier for the advertisements pack prototype. + /// The identifier for the advertisements dataset prototype. /// [DataField(required: true)] - public ProtoId Pack { get; private set; } + public ProtoId Pack { get; private set; } /// /// The next time an advertisement will be said. diff --git a/Content.Server/Advertise/Components/SpeakOnUIClosedComponent.cs b/Content.Server/Advertise/Components/SpeakOnUIClosedComponent.cs index 2a663b7f89..99d0080d7f 100644 --- a/Content.Server/Advertise/Components/SpeakOnUIClosedComponent.cs +++ b/Content.Server/Advertise/Components/SpeakOnUIClosedComponent.cs @@ -1,4 +1,4 @@ -using Content.Shared.Advertise; +using Content.Shared.Dataset; using Robust.Shared.Prototypes; namespace Content.Server.Advertise.Components; @@ -11,10 +11,10 @@ namespace Content.Server.Advertise.Components; public sealed partial class SpeakOnUIClosedComponent : Component { /// - /// The identifier for the message pack prototype containing messages to be spoken by this entity. + /// The identifier for the dataset prototype containing messages to be spoken by this entity. /// [DataField(required: true)] - public ProtoId Pack { get; private set; } + public ProtoId Pack { get; private set; } /// /// Is this component active? If false, no messages will be spoken. diff --git a/Content.Server/Advertise/EntitySystems/AdvertiseSystem.cs b/Content.Server/Advertise/EntitySystems/AdvertiseSystem.cs index 28fa01628f..7f2e128183 100644 --- a/Content.Server/Advertise/EntitySystems/AdvertiseSystem.cs +++ b/Content.Server/Advertise/EntitySystems/AdvertiseSystem.cs @@ -62,7 +62,7 @@ public sealed class AdvertiseSystem : EntitySystem return; if (_prototypeManager.TryIndex(advert.Pack, out var advertisements)) - _chat.TrySendInGameICMessage(uid, Loc.GetString(_random.Pick(advertisements.Messages)), InGameICChatType.Speak, hideChat: true); + _chat.TrySendInGameICMessage(uid, Loc.GetString(_random.Pick(advertisements.Values)), InGameICChatType.Speak, hideChat: true); } public override void Update(float frameTime) diff --git a/Content.Server/Advertise/EntitySystems/SpeakOnUIClosedSystem.cs b/Content.Server/Advertise/EntitySystems/SpeakOnUIClosedSystem.cs index 232b4b7eda..a0a709e5fa 100644 --- a/Content.Server/Advertise/EntitySystems/SpeakOnUIClosedSystem.cs +++ b/Content.Server/Advertise/EntitySystems/SpeakOnUIClosedSystem.cs @@ -1,7 +1,6 @@ using Content.Server.Advertise.Components; using Content.Server.Chat.Systems; -using Content.Server.UserInterface; -using Content.Shared.Advertise; +using Content.Shared.Dataset; using Robust.Shared.Prototypes; using Robust.Shared.Random; using ActivatableUIComponent = Content.Shared.UserInterface.ActivatableUIComponent; @@ -39,10 +38,10 @@ public sealed partial class SpeakOnUIClosedSystem : EntitySystem if (!entity.Comp.Enabled) return false; - if (!_prototypeManager.TryIndex(entity.Comp.Pack, out MessagePackPrototype? messagePack)) + if (!_prototypeManager.TryIndex(entity.Comp.Pack, out var messagePack)) return false; - var message = Loc.GetString(_random.Pick(messagePack.Messages), ("name", Name(entity))); + var message = Loc.GetString(_random.Pick(messagePack.Values), ("name", Name(entity))); _chat.TrySendInGameICMessage(entity, message, InGameICChatType.Speak, true); entity.Comp.Flag = false; return true; diff --git a/Content.Shared/Advertise/MessagePackPrototype.cs b/Content.Shared/Advertise/MessagePackPrototype.cs deleted file mode 100644 index f7495d7e46..0000000000 --- a/Content.Shared/Advertise/MessagePackPrototype.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Robust.Shared.Prototypes; - -namespace Content.Shared.Advertise; - -[Serializable, Prototype("messagePack")] -public sealed partial class MessagePackPrototype : IPrototype -{ - [ViewVariables] - [IdDataField] - public string ID { get; private set; } = default!; - - [DataField] - public List Messages { get; private set; } = []; -} diff --git a/Resources/Locale/en-US/vending-machines/vending-machine.ftl b/Resources/Locale/en-US/vending-machines/vending-machine.ftl index 637a9c7568..cb1dd3502f 100644 --- a/Resources/Locale/en-US/vending-machines/vending-machine.ftl +++ b/Resources/Locale/en-US/vending-machines/vending-machine.ftl @@ -1,3 +1,3 @@ -vending-machine-thanks = Thanks for using { $name }! +vending-machine-thanks-1 = Thanks for using { $name }! vending-machine-flavor-left = Request refills at cargo vending-machine-flavor-right = v1.1 diff --git a/Resources/Prototypes/Catalog/Arcade/Advertisements/blockgame.yml b/Resources/Prototypes/Catalog/Arcade/Advertisements/blockgame.yml deleted file mode 100644 index 7fb0367295..0000000000 --- a/Resources/Prototypes/Catalog/Arcade/Advertisements/blockgame.yml +++ /dev/null @@ -1,16 +0,0 @@ -- type: messagePack - id: BlockGameAds - messages: - - advertisement-block-game-1 - - advertisement-block-game-2 - - advertisement-block-game-3 - - advertisement-block-game-4 - - advertisement-block-game-5 - - advertisement-block-game-6 - - advertisement-block-game-7 - - advertisement-block-game-8 - - advertisement-block-game-9 - - advertisement-block-game-10 - - advertisement-block-game-11 - - advertisement-block-game-12 - - advertisement-block-game-13 diff --git a/Resources/Prototypes/Catalog/Arcade/Advertisements/spacevillain.yml b/Resources/Prototypes/Catalog/Arcade/Advertisements/spacevillain.yml deleted file mode 100644 index 7c94ab94f6..0000000000 --- a/Resources/Prototypes/Catalog/Arcade/Advertisements/spacevillain.yml +++ /dev/null @@ -1,18 +0,0 @@ -- type: messagePack - id: SpaceVillainAds - messages: - - advertisement-space-villain-1 - - advertisement-space-villain-2 - - advertisement-space-villain-3 - - advertisement-space-villain-4 - - advertisement-space-villain-5 - - advertisement-space-villain-6 - - advertisement-space-villain-7 - - advertisement-space-villain-8 - - advertisement-space-villain-9 - - advertisement-space-villain-10 - - advertisement-space-villain-11 - - advertisement-space-villain-12 - - advertisement-space-villain-13 - - advertisement-space-villain-14 - - advertisement-space-villain-15 diff --git a/Resources/Prototypes/Catalog/Arcade/Goodbyes/blockgame.yml b/Resources/Prototypes/Catalog/Arcade/Goodbyes/blockgame.yml deleted file mode 100644 index 460e8d13bf..0000000000 --- a/Resources/Prototypes/Catalog/Arcade/Goodbyes/blockgame.yml +++ /dev/null @@ -1,15 +0,0 @@ -- type: messagePack - id: BlockGameGoodbyes - messages: - - thankyou-block-game-1 - - thankyou-block-game-2 - - thankyou-block-game-3 - - thankyou-block-game-4 - - thankyou-block-game-5 - - thankyou-block-game-6 - - thankyou-block-game-7 - - thankyou-block-game-8 - - thankyou-block-game-9 - - thankyou-block-game-10 - - thankyou-block-game-11 - - thankyou-block-game-12 diff --git a/Resources/Prototypes/Catalog/Arcade/Goodbyes/spacevillain.yml b/Resources/Prototypes/Catalog/Arcade/Goodbyes/spacevillain.yml deleted file mode 100644 index 09016afec3..0000000000 --- a/Resources/Prototypes/Catalog/Arcade/Goodbyes/spacevillain.yml +++ /dev/null @@ -1,15 +0,0 @@ -- type: messagePack - id: SpaceVillainGoodbyes - messages: - - thankyou-space-villain-1 - - thankyou-space-villain-2 - - thankyou-space-villain-3 - - thankyou-space-villain-4 - - thankyou-space-villain-5 - - thankyou-space-villain-6 - - thankyou-space-villain-7 - - thankyou-space-villain-8 - - thankyou-space-villain-9 - - thankyou-space-villain-10 - - thankyou-space-villain-11 - - thankyou-space-villain-12 diff --git a/Resources/Prototypes/Catalog/Arcade/advertisements.yml b/Resources/Prototypes/Catalog/Arcade/advertisements.yml new file mode 100644 index 0000000000..47ecfb60b8 --- /dev/null +++ b/Resources/Prototypes/Catalog/Arcade/advertisements.yml @@ -0,0 +1,11 @@ +- type: localizedDataset + id: BlockGameAds + values: + prefix: advertisement-block-game- + count: 13 + +- type: localizedDataset + id: SpaceVillainAds + values: + prefix: advertisement-space-villain- + count: 15 diff --git a/Resources/Prototypes/Catalog/Arcade/goodbyes.yml b/Resources/Prototypes/Catalog/Arcade/goodbyes.yml new file mode 100644 index 0000000000..48b45eb40e --- /dev/null +++ b/Resources/Prototypes/Catalog/Arcade/goodbyes.yml @@ -0,0 +1,11 @@ +- type: localizedDataset + id: BlockGameGoodbyes + values: + prefix: thankyou-block-game- + count: 12 + +- type: localizedDataset + id: SpaceVillainGoodbyes + values: + prefix: thankyou-space-villain- + count: 12 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/ammo.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/ammo.yml deleted file mode 100644 index 7e089a2825..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/ammo.yml +++ /dev/null @@ -1,13 +0,0 @@ -- type: messagePack - id: AmmoVendAds - messages: - - advertisement-ammo-1 - - advertisement-ammo-2 - - advertisement-ammo-3 - - advertisement-ammo-4 - - advertisement-ammo-5 - - advertisement-ammo-6 - - advertisement-ammo-7 - - advertisement-ammo-8 - - advertisement-ammo-9 - - advertisement-ammo-10 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/atmosdrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/atmosdrobe.yml deleted file mode 100644 index 7946c06310..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/atmosdrobe.yml +++ /dev/null @@ -1,6 +0,0 @@ -- type: messagePack - id: AtmosDrobeAds - messages: - - advertisement-atmosdrobe-1 - - advertisement-atmosdrobe-2 - - advertisement-atmosdrobe-3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/bardrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/bardrobe.yml deleted file mode 100644 index 0a21acf2cb..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/bardrobe.yml +++ /dev/null @@ -1,5 +0,0 @@ -- type: messagePack - id: BarDrobeAds - messages: - - advertisement-bardrobe-1 - - advertisement-bardrobe-2 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/boozeomat.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/boozeomat.yml deleted file mode 100644 index e6bcbbcb9c..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/boozeomat.yml +++ /dev/null @@ -1,22 +0,0 @@ -- type: messagePack - id: BoozeOMatAds - messages: - - advertisement-boozeomat-1 - - advertisement-boozeomat-2 - - advertisement-boozeomat-3 - - advertisement-boozeomat-4 - - advertisement-boozeomat-5 - - advertisement-boozeomat-6 - - advertisement-boozeomat-7 - - advertisement-boozeomat-8 - - advertisement-boozeomat-9 - - advertisement-boozeomat-10 - - advertisement-boozeomat-11 - - advertisement-boozeomat-12 - - advertisement-boozeomat-13 - - advertisement-boozeomat-14 - - advertisement-boozeomat-15 - - advertisement-boozeomat-16 - - advertisement-boozeomat-17 - - advertisement-boozeomat-18 - - advertisement-boozeomat-19 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/cargodrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/cargodrobe.yml deleted file mode 100644 index f2cd38f737..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/cargodrobe.yml +++ /dev/null @@ -1,6 +0,0 @@ -- type: messagePack - id: CargoDrobeAds - messages: - - advertisement-cargodrobe-1 - - advertisement-cargodrobe-2 - - advertisement-cargodrobe-3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/chang.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/chang.yml deleted file mode 100644 index 911d467e7d..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/chang.yml +++ /dev/null @@ -1,8 +0,0 @@ -- type: messagePack - id: ChangAds - messages: - - advertisement-chang-1 - - advertisement-chang-2 - - advertisement-chang-3 - - advertisement-chang-4 - - advertisement-chang-5 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/chefdrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/chefdrobe.yml deleted file mode 100644 index c71d8225e0..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/chefdrobe.yml +++ /dev/null @@ -1,6 +0,0 @@ -- type: messagePack - id: ChefDrobeAds - messages: - - advertisement-chefdrobe-1 - - advertisement-chefdrobe-2 - - advertisement-chefdrobe-3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/chefvend.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/chefvend.yml deleted file mode 100644 index d52fcf9894..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/chefvend.yml +++ /dev/null @@ -1,12 +0,0 @@ -- type: messagePack - id: ChefvendAds - messages: - - advertisement-chefvend-1 - - advertisement-chefvend-2 - - advertisement-chefvend-3 - - advertisement-chefvend-4 - - advertisement-chefvend-5 - - advertisement-chefvend-6 - - advertisement-chefvend-7 - - advertisement-chefvend-8 - - advertisement-chefvend-9 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/chemdrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/chemdrobe.yml deleted file mode 100644 index 69a37de184..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/chemdrobe.yml +++ /dev/null @@ -1,6 +0,0 @@ -- type: messagePack - id: ChemDrobeAds - messages: - - advertisement-chemdrobe-1 - - advertisement-chemdrobe-2 - - advertisement-chemdrobe-3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/cigs.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/cigs.yml deleted file mode 100644 index db3d492c45..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/cigs.yml +++ /dev/null @@ -1,15 +0,0 @@ -- type: messagePack - id: CigaretteMachineAds - messages: - - advertisement-cigs-1 - - advertisement-cigs-2 - - advertisement-cigs-3 - - advertisement-cigs-4 - - advertisement-cigs-5 - - advertisement-cigs-6 - - advertisement-cigs-7 - - advertisement-cigs-8 - - advertisement-cigs-9 - - advertisement-cigs-10 - - advertisement-cigs-11 - - advertisement-cigs-12 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/clothesmate.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/clothesmate.yml deleted file mode 100644 index dc109f8eb9..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/clothesmate.yml +++ /dev/null @@ -1,10 +0,0 @@ -- type: messagePack - id: ClothesMateAds - messages: - - advertisement-clothes-1 - - advertisement-clothes-2 - - advertisement-clothes-3 - - advertisement-clothes-4 - - advertisement-clothes-5 - - advertisement-clothes-6 - - advertisement-clothes-7 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/coffee.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/coffee.yml deleted file mode 100644 index 4781768cf0..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/coffee.yml +++ /dev/null @@ -1,17 +0,0 @@ -- type: messagePack - id: HotDrinksMachineAds - messages: - - advertisement-coffee-1 - - advertisement-coffee-2 - - advertisement-coffee-3 - - advertisement-coffee-4 - - advertisement-coffee-5 - - advertisement-coffee-6 - - advertisement-coffee-7 - - advertisement-coffee-8 - - advertisement-coffee-9 - - advertisement-coffee-10 - - advertisement-coffee-11 - - advertisement-coffee-12 - - advertisement-coffee-13 - - advertisement-coffee-14 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/cola.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/cola.yml deleted file mode 100644 index d12d7eb92f..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/cola.yml +++ /dev/null @@ -1,11 +0,0 @@ -- type: messagePack - id: RobustSoftdrinksAds - messages: - - advertisement-cola-1 - - advertisement-cola-2 - - advertisement-cola-3 - - advertisement-cola-4 - - advertisement-cola-5 - - advertisement-cola-6 - - advertisement-cola-7 - - advertisement-cola-8 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/condiments.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/condiments.yml deleted file mode 100644 index d1d07df0c4..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/condiments.yml +++ /dev/null @@ -1,9 +0,0 @@ -- type: messagePack - id: CondimentVendAds - messages: - - advertisement-condiment-1 - - advertisement-condiment-2 - - advertisement-condiment-3 - - advertisement-condiment-4 - - advertisement-condiment-5 - - advertisement-condiment-6 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/curadrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/curadrobe.yml deleted file mode 100644 index 47523c6e93..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/curadrobe.yml +++ /dev/null @@ -1,6 +0,0 @@ -- type: messagePack - id: CuraDrobeAds - messages: - - advertisement-curadrobe-1 - - advertisement-curadrobe-2 - - advertisement-curadrobe-3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/detdrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/detdrobe.yml deleted file mode 100644 index 50024ce3d9..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/detdrobe.yml +++ /dev/null @@ -1,6 +0,0 @@ -- type: messagePack - id: DetDrobeAds - messages: - - advertisement-detdrobe-1 - - advertisement-detdrobe-2 - - advertisement-detdrobe-3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/dinnerware.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/dinnerware.yml deleted file mode 100644 index ac31dc075b..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/dinnerware.yml +++ /dev/null @@ -1,13 +0,0 @@ -- type: messagePack - id: DinnerwareAds - messages: - - advertisement-dinnerware-1 - - advertisement-dinnerware-2 - - advertisement-dinnerware-3 - - advertisement-dinnerware-4 - - advertisement-dinnerware-5 - - advertisement-dinnerware-6 - - advertisement-dinnerware-7 - - advertisement-dinnerware-8 - - advertisement-dinnerware-9 - - advertisement-dinnerware-10 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/discount.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/discount.yml deleted file mode 100644 index dce8646a60..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/discount.yml +++ /dev/null @@ -1,12 +0,0 @@ -- type: messagePack - id: DiscountDansAds - messages: - - advertisement-discount-1 - - advertisement-discount-2 - - advertisement-discount-3 - - advertisement-discount-4 - - advertisement-discount-5 - - advertisement-discount-6 - - advertisement-discount-7 - - advertisement-discount-8 - - advertisement-discount-9 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/donut.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/donut.yml deleted file mode 100644 index 73766aa749..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/donut.yml +++ /dev/null @@ -1,8 +0,0 @@ -- type: messagePack - id: DonutAds - messages: - - advertisement-donut-1 - - advertisement-donut-2 - - advertisement-donut-3 - - advertisement-donut-4 - - advertisement-donut-5 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/engidrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/engidrobe.yml deleted file mode 100644 index ec92fbe4de..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/engidrobe.yml +++ /dev/null @@ -1,8 +0,0 @@ -- type: messagePack - id: EngiDrobeAds - messages: - - advertisement-engidrobe-1 - - advertisement-engidrobe-2 - - advertisement-engidrobe-3 - - advertisement-engidrobe-4 - - advertisement-engidrobe-5 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/fatextractor.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/fatextractor.yml deleted file mode 100644 index 7619ea1856..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/fatextractor.yml +++ /dev/null @@ -1,9 +0,0 @@ -- type: messagePack - id: FatExtractorFacts - messages: - - fat-extractor-fact-1 - - fat-extractor-fact-2 - - fat-extractor-fact-3 - - fat-extractor-fact-4 - - fat-extractor-fact-5 - - fat-extractor-fact-6 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/games.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/games.yml deleted file mode 100644 index 1348635e1f..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/games.yml +++ /dev/null @@ -1,13 +0,0 @@ -- type: messagePack - id: GoodCleanFunAds - messages: - - advertisement-goodcleanfun-1 - - advertisement-goodcleanfun-2 - - advertisement-goodcleanfun-3 - - advertisement-goodcleanfun-4 - - advertisement-goodcleanfun-5 - - advertisement-goodcleanfun-6 - - advertisement-goodcleanfun-7 - - advertisement-goodcleanfun-8 - - advertisement-goodcleanfun-9 - - advertisement-goodcleanfun-10 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/genedrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/genedrobe.yml deleted file mode 100644 index 722388055b..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/genedrobe.yml +++ /dev/null @@ -1,5 +0,0 @@ -- type: messagePack - id: GeneDrobeAds - messages: - - advertisement-genedrobe-1 - - advertisement-genedrobe-2 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/happyhonk.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/happyhonk.yml deleted file mode 100644 index e145ebcdac..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/happyhonk.yml +++ /dev/null @@ -1,13 +0,0 @@ -- type: messagePack - id: HappyHonkAds - messages: - - advertisement-happyhonk-1 - - advertisement-happyhonk-2 - - advertisement-happyhonk-3 - - advertisement-happyhonk-4 - - advertisement-happyhonk-5 - - advertisement-happyhonk-6 - - advertisement-happyhonk-7 - - advertisement-happyhonk-8 - - advertisement-happyhonk-9 - - advertisement-happyhonk-10 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/hydrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/hydrobe.yml deleted file mode 100644 index 5999c496f5..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/hydrobe.yml +++ /dev/null @@ -1,7 +0,0 @@ -- type: messagePack - id: HyDrobeAds - messages: - - advertisement-hydrobe-1 - - advertisement-hydrobe-2 - - advertisement-hydrobe-3 - - advertisement-hydrobe-4 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/janidrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/janidrobe.yml deleted file mode 100644 index 8310136bf8..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/janidrobe.yml +++ /dev/null @@ -1,6 +0,0 @@ -- type: messagePack - id: JaniDrobeAds - messages: - - advertisement-janidrobe-1 - - advertisement-janidrobe-2 - - advertisement-janidrobe-3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/lawdrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/lawdrobe.yml deleted file mode 100644 index a948413abd..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/lawdrobe.yml +++ /dev/null @@ -1,11 +0,0 @@ -- type: messagePack - id: LawDrobeAds - messages: - - advertisement-lawdrobe-1 - - advertisement-lawdrobe-2 - - advertisement-lawdrobe-3 - - advertisement-lawdrobe-4 - - advertisement-lawdrobe-5 - - advertisement-lawdrobe-6 - - advertisement-lawdrobe-7 - - advertisement-lawdrobe-8 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/magivend.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/magivend.yml deleted file mode 100644 index 896a3853e7..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/magivend.yml +++ /dev/null @@ -1,14 +0,0 @@ -- type: messagePack - id: MagiVendAds - messages: - - advertisement-magivend-1 - - advertisement-magivend-2 - - advertisement-magivend-3 - - advertisement-magivend-4 - - advertisement-magivend-5 - - advertisement-magivend-6 - - advertisement-magivend-7 - - advertisement-magivend-8 - - advertisement-magivend-9 - - advertisement-magivend-10 - - advertisement-magivend-11 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/medidrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/medidrobe.yml deleted file mode 100644 index b7b055231b..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/medidrobe.yml +++ /dev/null @@ -1,6 +0,0 @@ -- type: messagePack - id: MediDrobeAds - messages: - - advertisement-medidrobe-1 - - advertisement-medidrobe-2 - - advertisement-medidrobe-3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/megaseed.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/megaseed.yml deleted file mode 100644 index b6e6ae098e..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/megaseed.yml +++ /dev/null @@ -1,9 +0,0 @@ -- type: messagePack - id: MegaSeedAds - messages: - - advertisement-megaseed-1 - - advertisement-megaseed-2 - - advertisement-megaseed-3 - - advertisement-megaseed-4 - - advertisement-megaseed-5 - - advertisement-megaseed-6 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/nanomed.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/nanomed.yml deleted file mode 100644 index a79a4c8a6c..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/nanomed.yml +++ /dev/null @@ -1,12 +0,0 @@ -- type: messagePack - id: NanoMedAds - messages: - - advertisement-nanomed-1 - - advertisement-nanomed-2 - - advertisement-nanomed-3 - - advertisement-nanomed-4 - - advertisement-nanomed-5 - - advertisement-nanomed-6 - - advertisement-nanomed-7 - - advertisement-nanomed-8 - - advertisement-nanomed-9 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/nutrimax.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/nutrimax.yml deleted file mode 100644 index a3ade34960..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/nutrimax.yml +++ /dev/null @@ -1,10 +0,0 @@ -- type: messagePack - id: NutriMaxAds - messages: - - advertisement-nutrimax-1 - - advertisement-nutrimax-2 - - advertisement-nutrimax-3 - - advertisement-nutrimax-4 - - advertisement-nutrimax-5 - - advertisement-nutrimax-6 - - advertisement-nutrimax-7 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/robodrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/robodrobe.yml deleted file mode 100644 index 82ece7d763..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/robodrobe.yml +++ /dev/null @@ -1,7 +0,0 @@ -- type: messagePack - id: RoboDrobeAds - messages: - - advertisement-robodrobe-1 - - advertisement-robodrobe-2 - - advertisement-robodrobe-3 - - advertisement-robodrobe-4 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/scidrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/scidrobe.yml deleted file mode 100644 index f8b125073b..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/scidrobe.yml +++ /dev/null @@ -1,6 +0,0 @@ -- type: messagePack - id: SciDrobeAds - messages: - - advertisement-scidrobe-1 - - advertisement-scidrobe-2 - - advertisement-scidrobe-3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/secdrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/secdrobe.yml deleted file mode 100644 index 99c2c402de..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/secdrobe.yml +++ /dev/null @@ -1,8 +0,0 @@ -- type: messagePack - id: SecDrobeAds - messages: - - advertisement-secdrobe-1 - - advertisement-secdrobe-2 - - advertisement-secdrobe-3 - - advertisement-secdrobe-4 - - advertisement-secdrobe-5 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/sectech.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/sectech.yml deleted file mode 100644 index 0b32ed166d..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/sectech.yml +++ /dev/null @@ -1,8 +0,0 @@ -- type: messagePack - id: SecTechAds - messages: - - advertisement-sectech-1 - - advertisement-sectech-2 - - advertisement-sectech-3 - - advertisement-sectech-4 - - advertisement-sectech-5 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/smartfridge.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/smartfridge.yml deleted file mode 100644 index d92a95f70f..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/smartfridge.yml +++ /dev/null @@ -1,11 +0,0 @@ -- type: messagePack - id: SmartFridgeAds - messages: - - advertisement-smartfridge-1 - - advertisement-smartfridge-2 - - advertisement-smartfridge-3 - - advertisement-smartfridge-4 - - advertisement-smartfridge-5 - - advertisement-smartfridge-6 - - advertisement-smartfridge-7 - - advertisement-smartfridge-8 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/snack.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/snack.yml deleted file mode 100644 index 3d9b93f9c2..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/snack.yml +++ /dev/null @@ -1,18 +0,0 @@ -- type: messagePack - id: GetmoreChocolateCorpAds - messages: - - advertisement-snack-1 - - advertisement-snack-2 - - advertisement-snack-3 - - advertisement-snack-4 - - advertisement-snack-5 - - advertisement-snack-6 - - advertisement-snack-7 - - advertisement-snack-8 - - advertisement-snack-9 - - advertisement-snack-10 - - advertisement-snack-11 - - advertisement-snack-12 - - advertisement-snack-13 - - advertisement-snack-14 - - advertisement-snack-15 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/sovietsoda.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/sovietsoda.yml deleted file mode 100644 index 630de1e615..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/sovietsoda.yml +++ /dev/null @@ -1,9 +0,0 @@ -- type: messagePack - id: BodaAds - messages: - - advertisement-sovietsoda-1 - - advertisement-sovietsoda-2 - - advertisement-sovietsoda-3 - - advertisement-sovietsoda-4 - - advertisement-sovietsoda-5 - - advertisement-sovietsoda-6 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/syndiedrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/syndiedrobe.yml deleted file mode 100644 index dc89d04aa0..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/syndiedrobe.yml +++ /dev/null @@ -1,34 +0,0 @@ -- type: messagePack - id: SyndieDrobeAds - messages: - - advertisement-syndiedrobe-1 - - advertisement-syndiedrobe-2 - - advertisement-syndiedrobe-3 - - advertisement-syndiedrobe-4 - - advertisement-syndiedrobe-5 - - advertisement-syndiedrobe-6 - - advertisement-syndiedrobe-7 - - advertisement-syndiedrobe-8 - - advertisement-syndiedrobe-9 - - advertisement-syndiedrobe-10 - - advertisement-syndiedrobe-11 - - advertisement-syndiedrobe-12 - - advertisement-syndiedrobe-13 - - advertisement-syndiedrobe-14 - - advertisement-syndiedrobe-15 - - advertisement-syndiedrobe-16 - - advertisement-syndiedrobe-17 - - advertisement-syndiedrobe-18 - - advertisement-syndiedrobe-19 - - advertisement-syndiedrobe-20 - - advertisement-syndiedrobe-21 - - advertisement-syndiedrobe-22 - - advertisement-syndiedrobe-23 - - advertisement-syndiedrobe-24 - - advertisement-syndiedrobe-25 - - advertisement-syndiedrobe-26 - - advertisement-syndiedrobe-27 - - advertisement-syndiedrobe-28 - - advertisement-syndiedrobe-29 - - advertisement-syndiedrobe-30 - - advertisement-syndiedrobe-31 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/theater.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/theater.yml deleted file mode 100644 index 8b06cdb517..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/theater.yml +++ /dev/null @@ -1,9 +0,0 @@ -- type: messagePack - id: AutoDrobeAds - messages: - - advertisement-theater-1 - - advertisement-theater-2 - - advertisement-theater-3 - - advertisement-theater-4 - - advertisement-theater-5 - - advertisement-theater-6 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/vendomat.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/vendomat.yml deleted file mode 100644 index 31c0e0c241..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/vendomat.yml +++ /dev/null @@ -1,10 +0,0 @@ -- type: messagePack - id: VendomatAds - messages: - - advertisement-vendomat-1 - - advertisement-vendomat-2 - - advertisement-vendomat-3 - - advertisement-vendomat-4 - - advertisement-vendomat-5 - - advertisement-vendomat-6 - - advertisement-vendomat-7 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/virodrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/virodrobe.yml deleted file mode 100644 index c48f9e7d2e..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/virodrobe.yml +++ /dev/null @@ -1,6 +0,0 @@ -- type: messagePack - id: ViroDrobeAds - messages: - - advertisement-virodrobe-1 - - advertisement-virodrobe-2 - - advertisement-virodrobe-3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/boozeomat.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/boozeomat.yml deleted file mode 100644 index e5c96887b9..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/boozeomat.yml +++ /dev/null @@ -1,7 +0,0 @@ -- type: messagePack - id: BoozeOMatGoodbyes - messages: - - vending-machine-thanks - - thankyou-boozeomat-1 - - thankyou-boozeomat-2 - - thankyou-boozeomat-3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/chang.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/chang.yml deleted file mode 100644 index a348d8fa9c..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/chang.yml +++ /dev/null @@ -1,6 +0,0 @@ -- type: messagePack - id: ChangGoodbyes - messages: - - vending-machine-thanks - - thankyou-chang-1 - - thankyou-chang-2 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/chefvend.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/chefvend.yml deleted file mode 100644 index 93249586e1..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/chefvend.yml +++ /dev/null @@ -1,8 +0,0 @@ -- type: messagePack - id: ChefvendGoodbyes - messages: - - vending-machine-thanks - - thankyou-chefvend-1 - - thankyou-chefvend-2 - - thankyou-chefvend-3 - - thankyou-chefvend-4 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/cigs.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/cigs.yml deleted file mode 100644 index 108a34a472..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/cigs.yml +++ /dev/null @@ -1,7 +0,0 @@ -- type: messagePack - id: CigaretteMachineGoodbyes - messages: - - vending-machine-thanks - - thankyou-cigs-1 - - thankyou-cigs-2 - - thankyou-cigs-3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/coffee.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/coffee.yml deleted file mode 100644 index 6f2aef01b8..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/coffee.yml +++ /dev/null @@ -1,8 +0,0 @@ -- type: messagePack - id: HotDrinksMachineGoodbyes - messages: - - vending-machine-thanks - - thankyou-coffee-1 - - thankyou-coffee-2 - - thankyou-coffee-3 - - thankyou-coffee-4 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/cola.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/cola.yml deleted file mode 100644 index 76f00fddaf..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/cola.yml +++ /dev/null @@ -1,8 +0,0 @@ -- type: messagePack - id: RobustSoftdrinksGoodbyes - messages: - - vending-machine-thanks - - thankyou-cola-1 - - thankyou-cola-2 - - thankyou-cola-3 - - thankyou-cola-4 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/discount.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/discount.yml deleted file mode 100644 index d3f7d89a93..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/discount.yml +++ /dev/null @@ -1,12 +0,0 @@ -- type: messagePack - id: DiscountDansGoodbyes - messages: - - vending-machine-thanks - - thankyou-discount-1 - - thankyou-discount-2 - - thankyou-discount-3 - - thankyou-discount-4 - - thankyou-discount-5 - - thankyou-discount-6 - - thankyou-discount-7 - - thankyou-discount-8 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/donut.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/donut.yml deleted file mode 100644 index 3c28741e3b..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/donut.yml +++ /dev/null @@ -1,8 +0,0 @@ -- type: messagePack - id: DonutGoodbyes - messages: - - vending-machine-thanks - - thankyou-donut-1 - - thankyou-donut-2 - - thankyou-donut-3 - - thankyou-donut-4 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/games.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/games.yml deleted file mode 100644 index 5d842619bb..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/games.yml +++ /dev/null @@ -1,8 +0,0 @@ -- type: messagePack - id: GoodCleanFunGoodbyes - messages: - - vending-machine-thanks - - thankyou-goodcleanfun-1 - - thankyou-goodcleanfun-2 - - thankyou-goodcleanfun-3 - - thankyou-goodcleanfun-4 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/generic.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/generic.yml deleted file mode 100644 index 98a2d7d17a..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/generic.yml +++ /dev/null @@ -1,4 +0,0 @@ -- type: messagePack - id: GenericVendGoodbyes - messages: - - vending-machine-thanks diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/happyhonk.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/happyhonk.yml deleted file mode 100644 index 7859d55463..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/happyhonk.yml +++ /dev/null @@ -1,8 +0,0 @@ -- type: messagePack - id: HappyHonkGoodbyes - messages: - - vending-machine-thanks - - thankyou-happyhonk-1 - - thankyou-happyhonk-2 - - thankyou-happyhonk-3 - - thankyou-happyhonk-4 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/lawdrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/lawdrobe.yml deleted file mode 100644 index 56678c1017..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/lawdrobe.yml +++ /dev/null @@ -1,8 +0,0 @@ -- type: messagePack - id: LawDrobeGoodbyes - messages: - - thankyou-lawdrobe-1 - - thankyou-lawdrobe-2 - - thankyou-lawdrobe-3 - - thankyou-lawdrobe-4 - - thankyou-lawdrobe-5 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/nutrimax.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/nutrimax.yml deleted file mode 100644 index 3568ae3031..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/nutrimax.yml +++ /dev/null @@ -1,6 +0,0 @@ -- type: messagePack - id: NutriMaxGoodbyes - messages: - - vending-machine-thanks - - thankyou-nutrimax-1 - - thankyou-nutrimax-2 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/sectech.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/sectech.yml deleted file mode 100644 index 8909693473..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/sectech.yml +++ /dev/null @@ -1,7 +0,0 @@ -- type: messagePack - id: SecTechGoodbyes - messages: - - vending-machine-thanks - - thankyou-sectech-1 - - thankyou-sectech-2 - - thankyou-sectech-3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/snack.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/snack.yml deleted file mode 100644 index bd86e074c6..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/snack.yml +++ /dev/null @@ -1,10 +0,0 @@ -- type: messagePack - id: GetmoreChocolateCorpGoodbyes - messages: - - vending-machine-thanks - - thankyou-snack-1 - - thankyou-snack-2 - - thankyou-snack-3 - - thankyou-snack-4 - - thankyou-snack-5 - - thankyou-snack-6 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/sovietsoda.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/sovietsoda.yml deleted file mode 100644 index 01ab25a7ad..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/sovietsoda.yml +++ /dev/null @@ -1,7 +0,0 @@ -- type: messagePack - id: BodaGoodbyes - messages: - - vending-machine-thanks - - thankyou-sovietsoda-1 - - thankyou-sovietsoda-2 - - thankyou-sovietsoda-3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/syndiedrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/syndiedrobe.yml deleted file mode 100644 index 1246eb3089..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/syndiedrobe.yml +++ /dev/null @@ -1,9 +0,0 @@ -- type: messagePack - id: SyndieDrobeGoodbyes - messages: - - vending-machine-thanks - - thankyou-syndiedrobe-1 - - thankyou-syndiedrobe-2 - - thankyou-syndiedrobe-3 - - thankyou-syndiedrobe-4 - - thankyou-syndiedrobe-5 diff --git a/Resources/Prototypes/Catalog/VendingMachines/advertisements.yml b/Resources/Prototypes/Catalog/VendingMachines/advertisements.yml new file mode 100644 index 0000000000..6dbb60af6a --- /dev/null +++ b/Resources/Prototypes/Catalog/VendingMachines/advertisements.yml @@ -0,0 +1,257 @@ +- type: localizedDataset + id: AmmoVendAds + values: + prefix: advertisement-ammo- + count: 10 + +- type: localizedDataset + id: AtmosDrobeAds + values: + prefix: advertisement-atmosdrobe- + count: 3 + +- type: localizedDataset + id: BarDrobeAds + values: + prefix: advertisement-bardrobe- + count: 2 + +- type: localizedDataset + id: BoozeOMatAds + values: + prefix: advertisement-boozeomat- + count: 19 + +- type: localizedDataset + id: CargoDrobeAds + values: + prefix: advertisement-cargodrobe- + count: 3 + +- type: localizedDataset + id: ChangAds + values: + prefix: advertisement-chang- + count: 5 + +- type: localizedDataset + id: ChefDrobeAds + values: + prefix: advertisement-chefdrobe- + count: 3 + +- type: localizedDataset + id: ChefvendAds + values: + prefix: advertisement-chefvend- + count: 9 + +- type: localizedDataset + id: ChemDrobeAds + values: + prefix: advertisement-chemdrobe- + count: 3 + +- type: localizedDataset + id: CigaretteMachineAds + values: + prefix: advertisement-cigs- + count: 12 + +- type: localizedDataset + id: ClothesMateAds + values: + prefix: advertisement-clothes- + count: 7 + +- type: localizedDataset + id: HotDrinksMachineAds + values: + prefix: advertisement-coffee- + count: 14 + +- type: localizedDataset + id: RobustSoftdrinksAds + values: + prefix: advertisement-cola- + count: 8 + +- type: localizedDataset + id: CondimentVendAds + values: + prefix: advertisement-condiment- + count: 6 + +- type: localizedDataset + id: CuraDrobeAds + values: + prefix: advertisement-curadrobe- + count: 3 + +- type: localizedDataset + id: DetDrobeAds + values: + prefix: advertisement-detdrobe- + count: 3 + +- type: localizedDataset + id: DinnerwareAds + values: + prefix: advertisement-dinnerware- + count: 10 + +- type: localizedDataset + id: DiscountDansAds + values: + prefix: advertisement-discount- + count: 9 + +- type: localizedDataset + id: DonutAds + values: + prefix: advertisement-donut- + count: 5 + +- type: localizedDataset + id: EngiDrobeAds + values: + prefix: advertisement-engidrobe- + count: 5 + +- type: localizedDataset + id: FatExtractorFacts + values: + prefix: fat-extractor-fact- + count: 6 + +- type: localizedDataset + id: GoodCleanFunAds + values: + prefix: advertisement-goodcleanfun- + count: 10 + +- type: localizedDataset + id: GeneDrobeAds + values: + prefix: advertisement-genedrobe- + count: 2 + +- type: localizedDataset + id: HappyHonkAds + values: + prefix: advertisement-happyhonk- + count: 10 + +- type: localizedDataset + id: HyDrobeAds + values: + prefix: advertisement-hydrobe- + count: 4 + +- type: localizedDataset + id: JaniDrobeAds + values: + prefix: advertisement-janidrobe- + count: 3 + +- type: localizedDataset + id: LawDrobeAds + values: + prefix: advertisement-lawdrobe- + count: 8 + +- type: localizedDataset + id: MagiVendAds + values: + prefix: advertisement-magivend- + count: 11 + +- type: localizedDataset + id: MediDrobeAds + values: + prefix: advertisement-medidrobe- + count: 3 + +- type: localizedDataset + id: MegaSeedAds + values: + prefix: advertisement-megaseed- + count: 6 + +- type: localizedDataset + id: NanoMedAds + values: + prefix: advertisement-nanomed- + count: 9 + +- type: localizedDataset + id: NutriMaxAds + values: + prefix: advertisement-nutrimax- + count: 7 + +- type: localizedDataset + id: RoboDrobeAds + values: + prefix: advertisement-robodrobe- + count: 4 + +- type: localizedDataset + id: SciDrobeAds + values: + prefix: advertisement-scidrobe- + count: 3 + +- type: localizedDataset + id: SecDrobeAds + values: + prefix: advertisement-secdrobe- + count: 5 + +- type: localizedDataset + id: SecTechAds + values: + prefix: advertisement-sectech- + count: 5 + +- type: localizedDataset + id: SmartFridgeAds + values: + prefix: advertisement-smartfridge- + count: 8 + +- type: localizedDataset + id: GetmoreChocolateCorpAds + values: + prefix: advertisement-snack- + count: 15 + +- type: localizedDataset + id: BodaAds + values: + prefix: advertisement-sovietsoda- + count: 6 + +- type: localizedDataset + id: SyndieDrobeAds + values: + prefix: advertisement-syndiedrobe- + count: 31 + +- type: localizedDataset + id: AutoDrobeAds + values: + prefix: advertisement-theater- + count: 6 + +- type: localizedDataset + id: VendomatAds + values: + prefix: advertisement-vendomat- + count: 7 + +- type: localizedDataset + id: ViroDrobeAds + values: + prefix: advertisement-virodrobe- + count: 3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/goodbyes.yml b/Resources/Prototypes/Catalog/VendingMachines/goodbyes.yml new file mode 100644 index 0000000000..5a3d91db11 --- /dev/null +++ b/Resources/Prototypes/Catalog/VendingMachines/goodbyes.yml @@ -0,0 +1,101 @@ +- type: localizedDataset + id: BoozeOMatGoodbyes + values: + prefix: thankyou-boozeomat- + count: 3 + +- type: localizedDataset + id: ChangGoodbyes + values: + prefix: thankyou-chang- + count: 2 + +- type: localizedDataset + id: ChefvendGoodbyes + values: + prefix: thankyou-chefvend- + count: 4 + +- type: localizedDataset + id: CigaretteMachineGoodbyes + values: + prefix: thankyou-cigs- + count: 3 + +- type: localizedDataset + id: HotDrinksMachineGoodbyes + values: + prefix: thankyou-coffee- + count: 4 + +- type: localizedDataset + id: RobustSoftdrinksGoodbyes + values: + prefix: thankyou-cola- + count: 4 + +- type: localizedDataset + id: DiscountDansGoodbyes + values: + prefix: thankyou-discount- + count: 8 + +- type: localizedDataset + id: DonutGoodbyes + values: + prefix: thankyou-donut- + count: 4 + +- type: localizedDataset + id: GoodCleanFunGoodbyes + values: + prefix: thankyou-goodcleanfun- + count: 4 + +- type: localizedDataset + id: GenericVendGoodbyes + values: + prefix: vending-machine-thanks- + count: 1 + +- type: localizedDataset + id: HappyHonkGoodbyes + values: + prefix: thankyou-happyhonk- + count: 4 + +- type: localizedDataset + id: LawDrobeGoodbyes + values: + prefix: thankyou-lawdrobe- + count: 5 + +- type: localizedDataset + id: NutriMaxGoodbyes + values: + prefix: thankyou-nutrimax- + count: 2 + +- type: localizedDataset + id: SecTechGoodbyes + values: + prefix: thankyou-sectech- + count: 3 + +- type: localizedDataset + id: GetmoreChocolateCorpGoodbyes + values: + prefix: thankyou-snack- + count: 6 + +- type: localizedDataset + id: BodaGoodbyes + values: + prefix: thankyou-sovietsoda- + count: 3 + +- type: localizedDataset + id: SyndieDrobeGoodbyes + values: + prefix: thankyou-syndiedrobe- + count: 5 From 5a25d1cc4b77ed84242075b043ddc6e987dd17f4 Mon Sep 17 00:00:00 2001 From: Kevin Zheng Date: Thu, 30 May 2024 08:09:07 -0800 Subject: [PATCH 13/75] Make mapping actions slightly more useable (#28394) --- Resources/mapping_actions.yml | 157 ++++++++++++++++------------------ 1 file changed, 75 insertions(+), 82 deletions(-) diff --git a/Resources/mapping_actions.yml b/Resources/mapping_actions.yml index 9498c3062a..e7ab1b4bf3 100644 --- a/Resources/mapping_actions.yml +++ b/Resources/mapping_actions.yml @@ -1,6 +1,47 @@ +- action: !type:InstantActionComponent + icon: /Textures/Tiles/cropped_parallax.png + keywords: [] + checkCanInteract: False + checkConsciousness: False + clientExclusive: True + autoPopulate: False + temporary: True + event: !type:StartPlacementActionEvent + placementOption: AlignTileAny + tileId: Space + assignments: + - 0: 8 + name: space +- action: !type:InstantActionComponent + icon: Interface/VerbIcons/delete.svg.192dpi.png + keywords: [] + checkCanInteract: False + checkConsciousness: False + clientExclusive: True + autoPopulate: False + temporary: True + event: !type:StartPlacementActionEvent + eraser: True + assignments: + - 0: 9 + name: action-name-mapping-erase +- action: !type:InstantActionComponent + icon: /Textures/Tiles/plating.png + keywords: [] + checkCanInteract: False + checkConsciousness: False + clientExclusive: True + autoPopulate: False + temporary: True + event: !type:StartPlacementActionEvent + placementOption: AlignTileAny + tileId: Plating + assignments: + - 0: 7 + name: plating - action: !type:InstantActionComponent icon: - entity: ReinforcedWindow + entity: Grille keywords: [] checkCanInteract: False checkConsciousness: False @@ -9,10 +50,10 @@ temporary: True event: !type:StartPlacementActionEvent placementOption: SnapgridCenter - entityType: ReinforcedWindow + entityType: Grille assignments: - - 0: 3 - name: ReinforcedWindow + - 0: 4 + name: Grille - action: !type:InstantActionComponent icon: entity: WallSolid @@ -28,21 +69,6 @@ assignments: - 0: 0 name: WallSolid -- action: !type:InstantActionComponent - icon: - entity: WallReinforced - keywords: [] - checkCanInteract: False - checkConsciousness: False - clientExclusive: True - autoPopulate: False - temporary: True - event: !type:StartPlacementActionEvent - placementOption: SnapgridCenter - entityType: WallReinforced - assignments: - - 0: 1 - name: WallReinforced - action: !type:InstantActionComponent icon: entity: Window @@ -58,6 +84,36 @@ assignments: - 0: 2 name: Window +- action: !type:InstantActionComponent + icon: + entity: WallReinforced + keywords: [] + checkCanInteract: False + checkConsciousness: False + clientExclusive: True + autoPopulate: False + temporary: True + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: WallReinforced + assignments: + - 0: 1 + name: WallReinforced +- action: !type:InstantActionComponent + icon: + entity: ReinforcedWindow + keywords: [] + checkCanInteract: False + checkConsciousness: False + clientExclusive: True + autoPopulate: False + temporary: True + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: ReinforcedWindow + assignments: + - 0: 3 + name: ReinforcedWindow - action: !type:InstantActionComponent icon: entity: Firelock @@ -73,39 +129,6 @@ assignments: - 0: 5 name: Firelock -- action: !type:InstantActionComponent - icon: - entity: Grille - keywords: [] - checkCanInteract: False - checkConsciousness: False - clientExclusive: True - autoPopulate: False - temporary: True - event: !type:StartPlacementActionEvent - placementOption: SnapgridCenter - entityType: Grille - assignments: - - 0: 4 - name: Grille -- action: !type:InstantActionComponent - icon: Interface/VerbIcons/delete.svg.192dpi.png - keywords: [] - checkCanInteract: False - checkConsciousness: False - clientExclusive: True - autoPopulate: False - temporary: True - event: !type:StartPlacementActionEvent - eraser: True - assignments: - - 0: 9 - - 1: 9 - - 2: 9 - - 4: 9 - - 5: 9 - - 6: 9 - name: action-name-mapping-erase - action: !type:InstantActionComponent icon: entity: GasPipeStraight @@ -1110,34 +1133,4 @@ assignments: - 0: 6 name: steel floor -- action: !type:InstantActionComponent - icon: /Textures/Tiles/plating.png - keywords: [] - checkCanInteract: False - checkConsciousness: False - clientExclusive: True - autoPopulate: False - temporary: True - event: !type:StartPlacementActionEvent - placementOption: AlignTileAny - tileId: Plating - assignments: - - 0: 7 - - 1: 8 - - 2: 8 - name: plating -- action: !type:InstantActionComponent - icon: /Textures/Tiles/cropped_parallax.png - keywords: [] - checkCanInteract: False - checkConsciousness: False - clientExclusive: True - autoPopulate: False - temporary: True - event: !type:StartPlacementActionEvent - placementOption: AlignTileAny - tileId: Space - assignments: - - 0: 8 - name: space ... From 34486f7451465acd4d1e22d792f6fc07f2a25d14 Mon Sep 17 00:00:00 2001 From: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> Date: Thu, 30 May 2024 10:25:07 -0700 Subject: [PATCH 14/75] Fix firelocks not requiring engineering access (#28415) --- .../Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml b/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml index c098409f3a..d6815abcdc 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml @@ -105,6 +105,8 @@ arc: 360 - type: StaticPrice price: 150 + - type: AccessReader + access: [ [ "Engineering" ] ] - type: PryUnpowered pryModifier: 0.5 From 9163af46b63be9b697c05824d391e0e3f9ac4e70 Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 30 May 2024 17:26:14 +0000 Subject: [PATCH 15/75] 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 7bbc12d0b9..6614ad8735 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: 778b - changes: - - message: Guns which use battery as magazines now display ammo. Like Svalinn pistol. - type: Fix - id: 6144 - time: '2024-03-13T11:13:12.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26009 - author: icekot8 changes: - message: Foxes are now neutral and deal 8 piercing damage @@ -3863,3 +3856,11 @@ id: 6643 time: '2024-05-30T07:32:16.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28405 +- author: ShadowCommander + changes: + - message: Fixed firelocks with warnings enabled opening immediately for characters + without engineering access. + type: Fix + id: 6644 + time: '2024-05-30T17:25:07.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28415 From 2459572ecc4ecc570ef3aeedf77fe00b93cc8ed6 Mon Sep 17 00:00:00 2001 From: Moony Date: Thu, 30 May 2024 15:45:16 -0500 Subject: [PATCH 16/75] Handheld teleporter portals now must start on the same grid. (#28423) Co-authored-by: moonheart08 --- .../Teleportation/HandTeleporterSystem.cs | 55 +++++++++++++------ .../teleportation/handheld-teleporter.ftl | 1 + 2 files changed, 40 insertions(+), 16 deletions(-) create mode 100644 Resources/Locale/en-US/teleportation/handheld-teleporter.ftl diff --git a/Content.Server/Teleportation/HandTeleporterSystem.cs b/Content.Server/Teleportation/HandTeleporterSystem.cs index 3d988b0916..d4c6753c4b 100644 --- a/Content.Server/Teleportation/HandTeleporterSystem.cs +++ b/Content.Server/Teleportation/HandTeleporterSystem.cs @@ -1,7 +1,9 @@ using Content.Server.Administration.Logs; +using Content.Server.Popups; using Content.Shared.DoAfter; using Content.Shared.Database; using Content.Shared.Interaction.Events; +using Content.Shared.Popups; using Content.Shared.Teleportation.Components; using Content.Shared.Teleportation.Systems; using Robust.Server.Audio; @@ -18,6 +20,7 @@ public sealed class HandTeleporterSystem : EntitySystem [Dependency] private readonly LinkedEntitySystem _link = default!; [Dependency] private readonly AudioSystem _audio = default!; [Dependency] private readonly SharedDoAfterSystem _doafter = default!; + [Dependency] private readonly PopupSystem _popup = default!; /// public override void Initialize() @@ -92,6 +95,16 @@ public sealed class HandTeleporterSystem : EntitySystem } else if (Deleted(component.SecondPortal)) { + if (xform.ParentUid != xform.GridUid) // Still, don't portal. + return; + + if (xform.ParentUid != Transform(component.FirstPortal!.Value).ParentUid) + { + // Whoops. Fizzle time. Crime time too because yippee I'm not refactoring this logic right now (I started to, I'm not going to.) + FizzlePortals(uid, component, user, true); + return; + } + var timeout = EnsureComp(user); timeout.EnteredPortal = null; component.SecondPortal = Spawn(component.SecondPortalPrototype, Transform(user).Coordinates); @@ -101,22 +114,32 @@ public sealed class HandTeleporterSystem : EntitySystem } else { - // Logging - var portalStrings = ""; - portalStrings += ToPrettyString(component.FirstPortal!.Value); - if (portalStrings != "") - portalStrings += " and "; - portalStrings += ToPrettyString(component.SecondPortal!.Value); - if (portalStrings != "") - _adminLogger.Add(LogType.EntityDelete, LogImpact.Low, $"{ToPrettyString(user):player} closed {portalStrings} with {ToPrettyString(uid)}"); - - // Clear both portals - QueueDel(component.FirstPortal!.Value); - QueueDel(component.SecondPortal!.Value); - - component.FirstPortal = null; - component.SecondPortal = null; - _audio.PlayPvs(component.ClearPortalsSound, uid); + FizzlePortals(uid, component, user, false); } } + + private void FizzlePortals(EntityUid uid, HandTeleporterComponent component, EntityUid user, bool instability) + { + // Logging + var portalStrings = ""; + portalStrings += ToPrettyString(component.FirstPortal); + if (portalStrings != "") + portalStrings += " and "; + portalStrings += ToPrettyString(component.SecondPortal); + if (portalStrings != "") + _adminLogger.Add(LogType.EntityDelete, LogImpact.Low, $"{ToPrettyString(user):player} closed {portalStrings} with {ToPrettyString(uid)}"); + + // Clear both portals + if (!Deleted(component.FirstPortal)) + QueueDel(component.FirstPortal.Value); + if (!Deleted(component.SecondPortal)) + QueueDel(component.SecondPortal.Value); + + component.FirstPortal = null; + component.SecondPortal = null; + _audio.PlayPvs(component.ClearPortalsSound, uid); + + if (instability) + _popup.PopupEntity(Loc.GetString("handheld-teleporter-instability-fizzle"), uid, user, PopupType.MediumCaution); + } } diff --git a/Resources/Locale/en-US/teleportation/handheld-teleporter.ftl b/Resources/Locale/en-US/teleportation/handheld-teleporter.ftl new file mode 100644 index 0000000000..28f526f0d5 --- /dev/null +++ b/Resources/Locale/en-US/teleportation/handheld-teleporter.ftl @@ -0,0 +1 @@ +handheld-teleporter-instability-fizzle = The portal fizzles as you try to place it, destroying both ends! From 9df7f5cd2f8739a26f8c51c2bf971aa0e10bbe74 Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 30 May 2024 20:46:22 +0000 Subject: [PATCH 17/75] 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 6614ad8735..12aed60d45 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: icekot8 - changes: - - message: Foxes are now neutral and deal 8 piercing damage - type: Tweak - id: 6145 - time: '2024-03-13T15:02:49.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25992 - author: Mangohydra changes: - message: The lawyer now spawns with their own lawyer stamp for stamping very important @@ -3864,3 +3857,10 @@ id: 6644 time: '2024-05-30T17:25:07.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28415 +- author: moonheart08 + changes: + - message: The hand teleporter can no longer create portal links between grids. + type: Remove + id: 6645 + time: '2024-05-30T20:45:16.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28423 From c6b22fb01c159a3c1612860af49e862ada8f3bda Mon Sep 17 00:00:00 2001 From: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> Date: Thu, 30 May 2024 17:25:13 -0700 Subject: [PATCH 18/75] Fix TryGetActionData error triggering when run on deleting entities (#27839) * Fix TryGetActionData error triggering when run on deleting entities * Only get metadata once --- Content.Shared/Actions/SharedActionsSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Shared/Actions/SharedActionsSystem.cs b/Content.Shared/Actions/SharedActionsSystem.cs index c92d67ba81..bf86d2c1e4 100644 --- a/Content.Shared/Actions/SharedActionsSystem.cs +++ b/Content.Shared/Actions/SharedActionsSystem.cs @@ -112,7 +112,7 @@ public abstract class SharedActionsSystem : EntitySystem bool logError = true) { result = null; - if (!Exists(uid)) + if (uid == null || TerminatingOrDeleted(uid.Value)) return false; var ev = new GetActionDataEvent(); From abfa12e3152173715a369e6efa81c48079e91ded Mon Sep 17 00:00:00 2001 From: Ghagliiarghii <68826635+Ghagliiarghii@users.noreply.github.com> Date: Thu, 30 May 2024 20:28:12 -0400 Subject: [PATCH 19/75] Create sale console computer board (#28425) --- .../Prototypes/Catalog/Fills/Lockers/heads.yml | 1 + .../Objects/Devices/Circuitboards/computer.yml | 13 ++++++++++++- .../Structures/Machines/Computers/computers.yml | 4 ++-- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml b/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml index e38d57e6d8..11863ea51d 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml @@ -7,6 +7,7 @@ contents: - id: BoxFolderQmClipboard - id: CargoRequestComputerCircuitboard + - id: CargoSaleComputerCircuitboard - id: CargoShuttleComputerCircuitboard - id: CargoShuttleConsoleCircuitboard - id: SalvageShuttleConsoleCircuitboard diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml index 93aff069f0..d3c58a0fe7 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml @@ -84,6 +84,17 @@ - type: StaticPrice price: 750 +- type: entity + parent: BaseComputerCircuitboard + id: CargoSaleComputerCircuitboard + name: cargo sale computer board + description: A computer printed circuit board for a cargo sale computer. + components: + - type: Sprite + state: cpu_supply + - type: ComputerBoard + prototype: ComputerPalletConsole + - type: entity id: CargoBountyComputerCircuitboard parent: BaseComputerCircuitboard @@ -394,4 +405,4 @@ - type: Sprite state: cpu_science - type: ComputerBoard - prototype: ComputerRoboticsControl + prototype: ComputerRoboticsControl \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml index b5c7a1a19c..abb12f9313 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml @@ -993,7 +993,7 @@ enum.CargoPalletConsoleUiKey.Sale: type: CargoPalletConsoleBoundUserInterface - type: Computer - board: CargoRequestComputerCircuitboard + board: CargoSaleComputerCircuitboard - type: PointLight radius: 1.5 energy: 1.6 @@ -1114,4 +1114,4 @@ - type: AccessReader # only used for dangerous things access: [["ResearchDirector"]] - type: Lock - unlockOnClick: false + unlockOnClick: false \ No newline at end of file From 75ef8e4fec7cb1e2395fe93138e6ebc592c106e7 Mon Sep 17 00:00:00 2001 From: PJBot Date: Fri, 31 May 2024 00:29:18 +0000 Subject: [PATCH 20/75] 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 12aed60d45..17018395a2 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +1,4 @@ Entries: -- author: Mangohydra - changes: - - message: The lawyer now spawns with their own lawyer stamp for stamping very important - documents. - type: Add - id: 6146 - time: '2024-03-13T15:05:39.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26012 - author: Errant changes: - message: Clothes with alternate sprites for vox once again show the alternate @@ -3864,3 +3856,10 @@ id: 6645 time: '2024-05-30T20:45:16.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28423 +- author: Ghagliiarghii + changes: + - message: Cargo Sale Computer now correctly uses its own computer board + type: Fix + id: 6646 + time: '2024-05-31T00:28:12.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28425 From d90cbee7f21e115b3b364bcb86c0085208c5b0d9 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Fri, 31 May 2024 11:55:01 +1000 Subject: [PATCH 21/75] Fix verb categories shuffling (#28368) If it's an extra category we leave it in its default spot. --- .../Verbs/UI/VerbMenuUIController.cs | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/Content.Client/Verbs/UI/VerbMenuUIController.cs b/Content.Client/Verbs/UI/VerbMenuUIController.cs index c3fc8c8356..e9c3f90641 100644 --- a/Content.Client/Verbs/UI/VerbMenuUIController.cs +++ b/Content.Client/Verbs/UI/VerbMenuUIController.cs @@ -8,6 +8,7 @@ using Content.Shared.Verbs; using Robust.Client.Player; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controllers; +using Robust.Shared.Collections; using Robust.Shared.Input; using Robust.Shared.Utility; @@ -115,6 +116,13 @@ namespace Content.Client.Verbs.UI private void FillVerbPopup(ContextMenuPopup popup) { HashSet listedCategories = new(); + var extras = new ValueList(ExtraCategories.Count); + + foreach (var cat in ExtraCategories) + { + extras.Add(cat.Text); + } + foreach (var verb in CurrentVerbs) { if (verb.Category == null) @@ -122,17 +130,15 @@ namespace Content.Client.Verbs.UI var element = new VerbMenuElement(verb); _context.AddElement(popup, element); } - else if (listedCategories.Add(verb.Category.Text)) + // Add the category if it's not an extra (this is to avoid shuffling if we're filling from server verbs response). + else if (!extras.Contains(verb.Category.Text) && listedCategories.Add(verb.Category.Text)) AddVerbCategory(verb.Category, popup); } - if (ExtraCategories != null) + foreach (var category in ExtraCategories) { - foreach (var category in ExtraCategories) - { - if (listedCategories.Add(category.Text)) - AddVerbCategory(category, popup); - } + if (listedCategories.Add(category.Text)) + AddVerbCategory(category, popup); } popup.InvalidateMeasure(); From 6b19d0a62c415302a2333b0d387529f074da04ad Mon Sep 17 00:00:00 2001 From: PJBot Date: Fri, 31 May 2024 01:56:07 +0000 Subject: [PATCH 22/75] 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 17018395a2..e54095c43c 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +1,4 @@ Entries: -- author: Errant - changes: - - message: Clothes with alternate sprites for vox once again show the alternate - sprites, instead of the defaults. - type: Fix - id: 6147 - time: '2024-03-13T15:06:37.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25989 - author: Gyrandola changes: - message: Fireaxe and Shotgun cabinets can now be destroyed. @@ -3863,3 +3855,10 @@ id: 6646 time: '2024-05-31T00:28:12.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28425 +- author: metalgearsloth + changes: + - message: Fix verb categories shuffling around. + type: Fix + id: 6647 + time: '2024-05-31T01:55:01.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28368 From 7e8e145d075c96705d4678c237b2876225e0da45 Mon Sep 17 00:00:00 2001 From: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> Date: Thu, 30 May 2024 18:59:25 -0700 Subject: [PATCH 23/75] Reflow the player tab to show more of the names (#28029) --- .../Administration/UI/Tabs/PlayerTab/PlayerTabEntry.xaml | 7 ++++--- .../Administration/UI/Tabs/PlayerTab/PlayerTabHeader.xaml | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTabEntry.xaml b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTabEntry.xaml index 8ac90305ca..f9ed57792e 100644 --- a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTabEntry.xaml +++ b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTabEntry.xaml @@ -15,16 +15,17 @@ ClipText="True"/> - public bool TryMakeAntag(Entity ent, ICommonSession? session, AntagSelectionDefinition def, bool ignoreSpawner = false) + public bool TryMakeAntag(Entity ent, ICommonSession? session, AntagSelectionDefinition def, bool ignoreSpawner = false, bool checkPref = true) { - if (!IsSessionValid(ent, session, def) || - !IsEntityValid(session?.AttachedEntity, def)) - { + if (checkPref && !HasPrimaryAntagPreference(session, def)) + return false; + + if (!IsSessionValid(ent, session, def) || !IsEntityValid(session?.AttachedEntity, def)) return false; - } MakeAntag(ent, session, def, ignoreSpawner); return true; @@ -338,16 +342,14 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem(); foreach (var session in sessions) { - if (!IsSessionValid(ent, session, def) || - !IsEntityValid(session.AttachedEntity, def)) + if (!IsSessionValid(ent, session, def) || !IsEntityValid(session.AttachedEntity, def)) continue; - var pref = (HumanoidCharacterProfile) _pref.GetPreferences(session.UserId).SelectedCharacter; - if (def.PrefRoles.Count != 0 && pref.AntagPreferences.Any(p => def.PrefRoles.Contains(p))) + if (HasPrimaryAntagPreference(session, def)) { preferredList.Add(session); } - else if (def.FallbackRoles.Count != 0 && pref.AntagPreferences.Any(p => def.FallbackRoles.Contains(p))) + else if (HasFallbackAntagPreference(session, def)) { fallbackList.Add(session); } @@ -418,13 +420,13 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem Date: Fri, 31 May 2024 02:07:27 +0000 Subject: [PATCH 26/75] Automatic changelog update --- Resources/Changelog/Changelog.yml | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 45634249a7..03741c1714 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,15 +1,4 @@ Entries: -- author: lzk228 - changes: - - message: Surgery tools can be placed in medical belt. - type: Tweak - - message: Lantern can be placed in utility belt. - type: Tweak - - message: Hand labeler can be placed in utility and botanical belt - type: Tweak - id: 6149 - time: '2024-03-13T18:55:27.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26085 - author: wafehling changes: - message: The trading outpost now has dedicated buy-only and sell-only pallets. @@ -3863,3 +3852,11 @@ id: 6648 time: '2024-05-31T01:59:25.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28029 +- author: EmoGarbage404 + changes: + - message: Fixed latejoin antagonists not respecting antag preferences (for real + this time) + type: Fix + id: 6649 + time: '2024-05-31T02:06:21.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28430 From 54337911d3995075caf4bbc6b6d768a93e8c1d0a Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Fri, 31 May 2024 14:28:11 +1200 Subject: [PATCH 27/75] Only auto-enable internals when necessary (#28248) * Only auto-enable internals when necessary * Add toxic gas check * Rename Any -> AnyPositive --- .../AtmosphereSystem.ExcitedGroup.cs | 2 +- .../Body/Systems/InternalsSystem.cs | 25 +++- Content.Server/Body/Systems/LungSystem.cs | 28 +++-- .../Body/Systems/RespiratorSystem.cs | 118 +++++++++++++++++- .../ReagentEffectConditions/OrganType.cs | 12 +- .../Damage/Systems/DamageOnLandSystem.cs | 2 +- .../Electrocution/ElectrocutionSystem.cs | 4 +- .../ExplosionSystem.Processing.cs | 2 +- .../Projectiles/ProjectileSystem.cs | 2 +- .../Station/Systems/StationSpawningSystem.cs | 2 +- .../EntitySystems/BluespaceLockerSystem.cs | 4 +- .../Weapons/Ranged/Systems/GunSystem.cs | 2 +- Content.Shared/Atmos/GasMixture.cs | 12 +- Content.Shared/Damage/DamageSpecifier.cs | 4 +- .../Station/SharedStationSpawningSystem.cs | 2 +- .../Weapons/Melee/SharedMeleeWeaponSystem.cs | 2 +- 16 files changed, 189 insertions(+), 34 deletions(-) diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.ExcitedGroup.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.ExcitedGroup.cs index c1b58f7a77..0d622f3067 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.ExcitedGroup.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.ExcitedGroup.cs @@ -106,7 +106,7 @@ namespace Content.Server.Atmos.EntitySystems if (tile?.Air == null) continue; - tile.Air.CopyFromMutable(combined); + tile.Air.CopyFrom(combined); InvalidateVisuals(ent, tile); } diff --git a/Content.Server/Body/Systems/InternalsSystem.cs b/Content.Server/Body/Systems/InternalsSystem.cs index c1e1de2baa..b79e083bd4 100644 --- a/Content.Server/Body/Systems/InternalsSystem.cs +++ b/Content.Server/Body/Systems/InternalsSystem.cs @@ -23,6 +23,7 @@ public sealed class InternalsSystem : EntitySystem [Dependency] private readonly GasTankSystem _gasTank = default!; [Dependency] private readonly InventorySystem _inventory = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly RespiratorSystem _respirator = default!; private EntityQuery _internalsQuery; @@ -38,15 +39,30 @@ public sealed class InternalsSystem : EntitySystem SubscribeLocalEvent>(OnGetInteractionVerbs); SubscribeLocalEvent(OnDoAfter); - SubscribeLocalEvent(OnStartingGear); + SubscribeLocalEvent(OnStartingGear); } - private void OnStartingGear(ref StartingGearEquippedEvent ev) + private void OnStartingGear(EntityUid uid, InternalsComponent component, ref StartingGearEquippedEvent args) { - if (!_internalsQuery.TryComp(ev.Entity, out var internals) || internals.BreathToolEntity == null) + if (component.BreathToolEntity == null) return; - ToggleInternals(ev.Entity, ev.Entity, force: false, internals); + if (component.GasTankEntity != null) + return; // already connected + + // Can the entity breathe the air it is currently exposed to? + if (_respirator.CanMetabolizeInhaledAir(uid)) + return; + + var tank = FindBestGasTank(uid); + if (tank == null) + return; + + // Could the entity metabolise the air in the linked gas tank? + if (!_respirator.CanMetabolizeGas(uid, tank.Value.Comp.Air)) + return; + + ToggleInternals(uid, uid, force: false, component); } private void OnGetInteractionVerbs( @@ -243,6 +259,7 @@ public sealed class InternalsSystem : EntitySystem public Entity? FindBestGasTank( Entity user) { + // TODO use _respirator.CanMetabolizeGas() to prioritize metabolizable gasses // Prioritise // 1. back equipped tanks // 2. exo-slot tanks diff --git a/Content.Server/Body/Systems/LungSystem.cs b/Content.Server/Body/Systems/LungSystem.cs index e83d3c32a2..7e58c24f7e 100644 --- a/Content.Server/Body/Systems/LungSystem.cs +++ b/Content.Server/Body/Systems/LungSystem.cs @@ -3,6 +3,7 @@ using Content.Server.Atmos.EntitySystems; using Content.Server.Body.Components; using Content.Server.Chemistry.Containers.EntitySystems; using Content.Shared.Atmos; +using Content.Shared.Chemistry.Components; using Content.Shared.Clothing; using Content.Shared.Inventory.Events; @@ -77,23 +78,32 @@ public sealed class LungSystem : EntitySystem if (!_solutionContainerSystem.ResolveSolution(uid, lung.SolutionName, ref lung.Solution, out var solution)) return; - foreach (var gas in Enum.GetValues()) + GasToReagent(lung.Air, solution); + _solutionContainerSystem.UpdateChemicals(lung.Solution.Value); + } + + private void GasToReagent(GasMixture gas, Solution solution) + { + foreach (var gasId in Enum.GetValues()) { - var i = (int) gas; - var moles = lung.Air[i]; + var i = (int) gasId; + var moles = gas[i]; if (moles <= 0) continue; + var reagent = _atmosphereSystem.GasReagents[i]; - if (reagent is null) continue; + if (reagent is null) + continue; var amount = moles * Atmospherics.BreathMolesToReagentMultiplier; solution.AddReagent(reagent, amount); - - // We don't remove the gas from the lung mix, - // that's the responsibility of whatever gas is being metabolized. - // Most things will just want to exhale again. } + } - _solutionContainerSystem.UpdateChemicals(lung.Solution.Value); + public Solution GasToReagent(GasMixture gas) + { + var solution = new Solution(); + GasToReagent(gas, solution); + return solution; } } diff --git a/Content.Server/Body/Systems/RespiratorSystem.cs b/Content.Server/Body/Systems/RespiratorSystem.cs index a46294beb4..4e6c02edbd 100644 --- a/Content.Server/Body/Systems/RespiratorSystem.cs +++ b/Content.Server/Body/Systems/RespiratorSystem.cs @@ -1,16 +1,21 @@ using Content.Server.Administration.Logs; -using Content.Server.Atmos; using Content.Server.Atmos.EntitySystems; using Content.Server.Body.Components; using Content.Server.Chat.Systems; using Content.Server.Chemistry.Containers.EntitySystems; +using Content.Server.Chemistry.ReagentEffectConditions; +using Content.Server.Chemistry.ReagentEffects; using Content.Shared.Alert; using Content.Shared.Atmos; using Content.Shared.Body.Components; +using Content.Shared.Body.Prototypes; +using Content.Shared.Chemistry.Components; +using Content.Shared.Chemistry.Reagent; using Content.Shared.Damage; using Content.Shared.Database; using Content.Shared.Mobs.Systems; using JetBrains.Annotations; +using Robust.Shared.Prototypes; using Robust.Shared.Timing; namespace Content.Server.Body.Systems; @@ -26,9 +31,12 @@ public sealed class RespiratorSystem : EntitySystem [Dependency] private readonly DamageableSystem _damageableSys = default!; [Dependency] private readonly LungSystem _lungSystem = default!; [Dependency] private readonly MobStateSystem _mobState = default!; + [Dependency] private readonly IPrototypeManager _protoMan = default!; [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!; [Dependency] private readonly ChatSystem _chat = default!; + private static readonly ProtoId GasId = new("Gas"); + public override void Initialize() { base.Initialize(); @@ -109,7 +117,7 @@ public sealed class RespiratorSystem : EntitySystem // Inhale gas var ev = new InhaleLocationEvent(); - RaiseLocalEvent(uid, ref ev, broadcast: false); + RaiseLocalEvent(uid, ref ev); ev.Gas ??= _atmosSys.GetContainingMixture(uid, excite: true); @@ -164,6 +172,112 @@ public sealed class RespiratorSystem : EntitySystem _atmosSys.Merge(ev.Gas, outGas); } + /// + /// Check whether or not an entity can metabolize inhaled air without suffocating or taking damage (i.e., no toxic + /// gasses). + /// + public bool CanMetabolizeInhaledAir(Entity ent) + { + if (!Resolve(ent, ref ent.Comp)) + return false; + + var ev = new InhaleLocationEvent(); + RaiseLocalEvent(ent, ref ev); + + var gas = ev.Gas ?? _atmosSys.GetContainingMixture(ent.Owner); + if (gas == null) + return false; + + return CanMetabolizeGas(ent, gas); + } + + /// + /// Check whether or not an entity can metabolize the given gas mixture without suffocating or taking damage + /// (i.e., no toxic gasses). + /// + public bool CanMetabolizeGas(Entity ent, GasMixture gas) + { + if (!Resolve(ent, ref ent.Comp)) + return false; + + var organs = _bodySystem.GetBodyOrganComponents(ent); + if (organs.Count == 0) + return false; + + gas = new GasMixture(gas); + var lungRatio = 1.0f / organs.Count; + gas.Multiply(MathF.Min(lungRatio * gas.Volume/Atmospherics.BreathVolume, lungRatio)); + var solution = _lungSystem.GasToReagent(gas); + + float saturation = 0; + foreach (var organ in organs) + { + saturation += GetSaturation(solution, organ.Comp.Owner, out var toxic); + if (toxic) + return false; + } + + return saturation > ent.Comp.UpdateInterval.TotalSeconds; + } + + /// + /// Get the amount of saturation that would be generated if the lung were to metabolize the given solution. + /// + /// + /// This assumes the metabolism rate is unbounded, which generally should be the case for lungs, otherwise we get + /// back to the old pulmonary edema bug. + /// + /// The reagents to metabolize + /// The entity doing the metabolizing + /// Whether or not any of the reagents would deal damage to the entity + private float GetSaturation(Solution solution, Entity lung, out bool toxic) + { + toxic = false; + if (!Resolve(lung, ref lung.Comp)) + return 0; + + if (lung.Comp.MetabolismGroups == null) + return 0; + + float saturation = 0; + foreach (var (id, quantity) in solution.Contents) + { + var reagent = _protoMan.Index(id.Prototype); + if (reagent.Metabolisms == null) + continue; + + if (!reagent.Metabolisms.TryGetValue(GasId, out var entry)) + continue; + + foreach (var effect in entry.Effects) + { + if (effect is HealthChange health) + toxic |= CanMetabolize(health) && health.Damage.AnyPositive(); + else if (effect is Oxygenate oxy && CanMetabolize(oxy)) + saturation += oxy.Factor * quantity.Float(); + } + } + + // TODO generalize condition checks + // this is pretty janky, but I just want to bodge a method that checks if an entity can breathe a gas mixture + // Applying actual reaction effects require a full ReagentEffectArgs struct. + bool CanMetabolize(ReagentEffect effect) + { + if (effect.Conditions == null) + return true; + + foreach (var cond in effect.Conditions) + { + if (cond is OrganType organ && !organ.Condition(lung, EntityManager)) + return false; + } + + return true; + } + + return saturation; + } + private void TakeSuffocationDamage(Entity ent) { if (ent.Comp.SuffocationCycles == 2) diff --git a/Content.Server/Chemistry/ReagentEffectConditions/OrganType.cs b/Content.Server/Chemistry/ReagentEffectConditions/OrganType.cs index 4ae13b6a6e..986c3d79c8 100644 --- a/Content.Server/Chemistry/ReagentEffectConditions/OrganType.cs +++ b/Content.Server/Chemistry/ReagentEffectConditions/OrganType.cs @@ -25,9 +25,15 @@ namespace Content.Server.Chemistry.ReagentEffectConditions if (args.OrganEntity == null) return false; - if (args.EntityManager.TryGetComponent(args.OrganEntity.Value, out var metabolizer) - && metabolizer.MetabolizerTypes != null - && metabolizer.MetabolizerTypes.Contains(Type)) + return Condition(args.OrganEntity.Value, args.EntityManager); + } + + public bool Condition(Entity metabolizer, IEntityManager entMan) + { + metabolizer.Comp ??= entMan.GetComponentOrNull(metabolizer.Owner); + if (metabolizer.Comp != null + && metabolizer.Comp.MetabolizerTypes != null + && metabolizer.Comp.MetabolizerTypes.Contains(Type)) return ShouldHave; return !ShouldHave; } diff --git a/Content.Server/Damage/Systems/DamageOnLandSystem.cs b/Content.Server/Damage/Systems/DamageOnLandSystem.cs index 8f01e791ea..2e72e76e6d 100644 --- a/Content.Server/Damage/Systems/DamageOnLandSystem.cs +++ b/Content.Server/Damage/Systems/DamageOnLandSystem.cs @@ -22,7 +22,7 @@ namespace Content.Server.Damage.Systems private void OnAttemptPacifiedThrow(Entity ent, ref AttemptPacifiedThrowEvent args) { // Allow healing projectiles, forbid any that do damage: - if (ent.Comp.Damage.Any()) + if (ent.Comp.Damage.AnyPositive()) { args.Cancel("pacified-cannot-throw"); } diff --git a/Content.Server/Electrocution/ElectrocutionSystem.cs b/Content.Server/Electrocution/ElectrocutionSystem.cs index 8291e97efe..67e60c9de4 100644 --- a/Content.Server/Electrocution/ElectrocutionSystem.cs +++ b/Content.Server/Electrocution/ElectrocutionSystem.cs @@ -166,7 +166,7 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem if (!electrified.OnAttacked) return; - if (!_meleeWeapon.GetDamage(args.Used, args.User).Any()) + if (_meleeWeapon.GetDamage(args.Used, args.User).Empty) return; TryDoElectrifiedAct(uid, args.User, 1, electrified); @@ -183,7 +183,7 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem if (!component.CurrentLit || args.Used != args.User) return; - if (!_meleeWeapon.GetDamage(args.Used, args.User).Any()) + if (_meleeWeapon.GetDamage(args.Used, args.User).Empty) return; DoCommonElectrocution(args.User, uid, component.UnarmedHitShock, component.UnarmedHitStun, false); diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs index a93157a175..bce3dc21c2 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs @@ -396,7 +396,7 @@ public sealed partial class ExplosionSystem // don't raise BeforeExplodeEvent if the entity is completely immune to explosions var thisDamage = GetDamage(uid, prototype, originalDamage); - if (!thisDamage.Any()) + if (thisDamage.Empty) return; _toDamage.Add((uid, thisDamage)); diff --git a/Content.Server/Projectiles/ProjectileSystem.cs b/Content.Server/Projectiles/ProjectileSystem.cs index f8c8ef64b7..970536f42b 100644 --- a/Content.Server/Projectiles/ProjectileSystem.cs +++ b/Content.Server/Projectiles/ProjectileSystem.cs @@ -51,7 +51,7 @@ public sealed class ProjectileSystem : SharedProjectileSystem if (modifiedDamage is not null && EntityManager.EntityExists(component.Shooter)) { - if (modifiedDamage.Any() && !deleted) + if (modifiedDamage.AnyPositive() && !deleted) { _color.RaiseEffect(Color.Red, new List { target }, Filter.Pvs(target, entityManager: EntityManager)); } diff --git a/Content.Server/Station/Systems/StationSpawningSystem.cs b/Content.Server/Station/Systems/StationSpawningSystem.cs index 05f5cc58bc..f175565a5a 100644 --- a/Content.Server/Station/Systems/StationSpawningSystem.cs +++ b/Content.Server/Station/Systems/StationSpawningSystem.cs @@ -204,7 +204,7 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem } var gearEquippedEv = new StartingGearEquippedEvent(entity.Value); - RaiseLocalEvent(entity.Value, ref gearEquippedEv, true); + RaiseLocalEvent(entity.Value, ref gearEquippedEv); if (profile != null) { diff --git a/Content.Server/Storage/EntitySystems/BluespaceLockerSystem.cs b/Content.Server/Storage/EntitySystems/BluespaceLockerSystem.cs index 838311c1aa..9da7606bcc 100644 --- a/Content.Server/Storage/EntitySystems/BluespaceLockerSystem.cs +++ b/Content.Server/Storage/EntitySystems/BluespaceLockerSystem.cs @@ -109,7 +109,7 @@ public sealed class BluespaceLockerSystem : EntitySystem // Move contained air if (component.BehaviorProperties.TransportGas) { - entityStorageComponent.Air.CopyFromMutable(target.Value.storageComponent.Air); + entityStorageComponent.Air.CopyFrom(target.Value.storageComponent.Air); target.Value.storageComponent.Air.Clear(); } @@ -326,7 +326,7 @@ public sealed class BluespaceLockerSystem : EntitySystem // Move contained air if (component.BehaviorProperties.TransportGas) { - target.Value.storageComponent.Air.CopyFromMutable(entityStorageComponent.Air); + target.Value.storageComponent.Air.CopyFrom(entityStorageComponent.Air); entityStorageComponent.Air.Clear(); } diff --git a/Content.Server/Weapons/Ranged/Systems/GunSystem.cs b/Content.Server/Weapons/Ranged/Systems/GunSystem.cs index 986cac98dd..7247109e37 100644 --- a/Content.Server/Weapons/Ranged/Systems/GunSystem.cs +++ b/Content.Server/Weapons/Ranged/Systems/GunSystem.cs @@ -237,7 +237,7 @@ public sealed partial class GunSystem : SharedGunSystem { if (!Deleted(hitEntity)) { - if (dmg.Any()) + if (dmg.AnyPositive()) { _color.RaiseEffect(Color.Red, new List() { hitEntity }, Filter.Pvs(hitEntity, entityManager: EntityManager)); } diff --git a/Content.Shared/Atmos/GasMixture.cs b/Content.Shared/Atmos/GasMixture.cs index a676ed6720..0f1efba976 100644 --- a/Content.Shared/Atmos/GasMixture.cs +++ b/Content.Shared/Atmos/GasMixture.cs @@ -96,6 +96,11 @@ namespace Content.Shared.Atmos Volume = volume; } + public GasMixture(GasMixture toClone) + { + CopyFrom(toClone); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void MarkImmutable() { @@ -197,9 +202,12 @@ namespace Content.Shared.Atmos } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void CopyFromMutable(GasMixture sample) + public void CopyFrom(GasMixture sample) { - if (Immutable) return; + if (Immutable) + return; + + Volume = sample.Volume; sample.Moles.CopyTo(Moles, 0); Temperature = sample.Temperature; } diff --git a/Content.Shared/Damage/DamageSpecifier.cs b/Content.Shared/Damage/DamageSpecifier.cs index 8ab9116a97..7f505b807f 100644 --- a/Content.Shared/Damage/DamageSpecifier.cs +++ b/Content.Shared/Damage/DamageSpecifier.cs @@ -43,7 +43,7 @@ namespace Content.Shared.Damage /// /// /// Note that this being zero does not mean this damage has no effect. Healing in one type may cancel damage - /// in another. Consider using or instead. + /// in another. Consider using or instead. /// public FixedPoint2 GetTotal() { @@ -60,7 +60,7 @@ namespace Content.Shared.Damage /// Differs from as a damage specifier might contain entries with zeroes. /// This also returns false if the specifier only contains negative values. /// - public bool Any() + public bool AnyPositive() { foreach (var value in DamageDict.Values) { diff --git a/Content.Shared/Station/SharedStationSpawningSystem.cs b/Content.Shared/Station/SharedStationSpawningSystem.cs index acf00f7c6b..f352c9db63 100644 --- a/Content.Shared/Station/SharedStationSpawningSystem.cs +++ b/Content.Shared/Station/SharedStationSpawningSystem.cs @@ -142,7 +142,7 @@ public abstract class SharedStationSpawningSystem : EntitySystem if (raiseEvent) { var ev = new StartingGearEquippedEvent(entity); - RaiseLocalEvent(entity, ref ev, true); + RaiseLocalEvent(entity, ref ev); } } } diff --git a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs index 7fc440db47..2d9993096b 100644 --- a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs +++ b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs @@ -499,7 +499,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem var modifiedDamage = DamageSpecifier.ApplyModifierSets(damage + hitEvent.BonusDamage + attackedEvent.BonusDamage, hitEvent.ModifiersList); var damageResult = Damageable.TryChangeDamage(target, modifiedDamage, origin:user); - if (damageResult != null && damageResult.Any()) + if (damageResult is {Empty: false}) { // If the target has stamina and is taking blunt damage, they should also take stamina damage based on their blunt to stamina factor if (damageResult.DamageDict.TryGetValue("Blunt", out var bluntDamage)) From 27e63f5919b36102683420cabd0152ef83e18122 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Fri, 31 May 2024 14:44:35 +1200 Subject: [PATCH 28/75] Make tests automatically reset modified cvars (#28219) * Make tests automatically reset modified cvars * Fix bad return * A * Try Fix tests * clarify comment * update eng --- .../Pair/TestPair.Cvars.cs | 69 +++++++++++++++++++ .../Pair/TestPair.Recycle.cs | 3 + Content.IntegrationTests/Pair/TestPair.cs | 4 ++ Content.IntegrationTests/PoolManager.cs | 2 + .../Tests/GameRules/NukeOpsTest.cs | 2 - .../Administration/Systems/AdminSystem.cs | 13 ++++ 6 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 Content.IntegrationTests/Pair/TestPair.Cvars.cs diff --git a/Content.IntegrationTests/Pair/TestPair.Cvars.cs b/Content.IntegrationTests/Pair/TestPair.Cvars.cs new file mode 100644 index 0000000000..81df31fc9a --- /dev/null +++ b/Content.IntegrationTests/Pair/TestPair.Cvars.cs @@ -0,0 +1,69 @@ +#nullable enable +using System.Collections.Generic; +using Content.Shared.CCVar; +using Robust.Shared.Configuration; +using Robust.Shared.Utility; + +namespace Content.IntegrationTests.Pair; + +public sealed partial class TestPair +{ + private readonly Dictionary _modifiedClientCvars = new(); + private readonly Dictionary _modifiedServerCvars = new(); + + private void OnServerCvarChanged(CVarChangeInfo args) + { + _modifiedServerCvars.TryAdd(args.Name, args.OldValue); + } + + private void OnClientCvarChanged(CVarChangeInfo args) + { + _modifiedClientCvars.TryAdd(args.Name, args.OldValue); + } + + internal void ClearModifiedCvars() + { + _modifiedClientCvars.Clear(); + _modifiedServerCvars.Clear(); + } + + /// + /// Reverts any cvars that were modified during a test back to their original values. + /// + public async Task RevertModifiedCvars() + { + await Server.WaitPost(() => + { + foreach (var (name, value) in _modifiedServerCvars) + { + if (Server.CfgMan.GetCVar(name).Equals(value)) + continue; + Server.Log.Info($"Resetting cvar {name} to {value}"); + Server.CfgMan.SetCVar(name, value); + } + + // I just love order dependent cvars + if (_modifiedServerCvars.TryGetValue(CCVars.PanicBunkerEnabled.Name, out var panik)) + Server.CfgMan.SetCVar(CCVars.PanicBunkerEnabled.Name, panik); + + }); + + await Client.WaitPost(() => + { + foreach (var (name, value) in _modifiedClientCvars) + { + if (Client.CfgMan.GetCVar(name).Equals(value)) + continue; + + var flags = Client.CfgMan.GetCVarFlags(name); + if (flags.HasFlag(CVar.REPLICATED) && flags.HasFlag(CVar.SERVER)) + continue; + + Client.Log.Info($"Resetting cvar {name} to {value}"); + Client.CfgMan.SetCVar(name, value); + } + }); + + ClearModifiedCvars(); + } +} diff --git a/Content.IntegrationTests/Pair/TestPair.Recycle.cs b/Content.IntegrationTests/Pair/TestPair.Recycle.cs index c0f4b3b745..8d1e425553 100644 --- a/Content.IntegrationTests/Pair/TestPair.Recycle.cs +++ b/Content.IntegrationTests/Pair/TestPair.Recycle.cs @@ -40,6 +40,8 @@ public sealed partial class TestPair : IAsyncDisposable TestMap = null; } + await RevertModifiedCvars(); + var usageTime = Watch.Elapsed; Watch.Restart(); await _testOut.WriteLineAsync($"{nameof(CleanReturnAsync)}: Test borrowed pair {Id} for {usageTime.TotalMilliseconds} ms"); @@ -132,6 +134,7 @@ public sealed partial class TestPair : IAsyncDisposable if (gameTicker.RunLevel != GameRunLevel.PreRoundLobby) { await testOut.WriteLineAsync($"Recycling: {Watch.Elapsed.TotalMilliseconds} ms: Restarting round."); + Server.CfgMan.SetCVar(CCVars.GameDummyTicker, false); Assert.That(gameTicker.DummyTicker, Is.False); Server.CfgMan.SetCVar(CCVars.GameLobbyEnabled, true); await Server.WaitPost(() => gameTicker.RestartRound()); diff --git a/Content.IntegrationTests/Pair/TestPair.cs b/Content.IntegrationTests/Pair/TestPair.cs index 916a94c9c4..7ee5dbd55c 100644 --- a/Content.IntegrationTests/Pair/TestPair.cs +++ b/Content.IntegrationTests/Pair/TestPair.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using Content.Server.GameTicking; using Content.Shared.Players; +using Robust.Shared.Configuration; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Network; @@ -58,6 +59,9 @@ public sealed partial class TestPair (Server, ServerLogHandler) = await PoolManager.GenerateServer(settings, testOut); ActivateContext(testOut); + Client.CfgMan.OnCVarValueChanged += OnClientCvarChanged; + Server.CfgMan.OnCVarValueChanged += OnServerCvarChanged; + if (!settings.NoLoadTestPrototypes) await LoadPrototypes(testPrototypes!); diff --git a/Content.IntegrationTests/PoolManager.cs b/Content.IntegrationTests/PoolManager.cs index 3b49ffcf84..21c93ea8ae 100644 --- a/Content.IntegrationTests/PoolManager.cs +++ b/Content.IntegrationTests/PoolManager.cs @@ -270,6 +270,8 @@ public static partial class PoolManager $"{nameof(GetServerClientPair)}: Retrieving pair {pair.Id} from pool took {poolRetrieveTime.TotalMilliseconds} ms"); await testOut.WriteLineAsync( $"{nameof(GetServerClientPair)}: Returning pair {pair.Id}"); + + pair.ClearModifiedCvars(); pair.Settings = poolSettings; pair.TestHistory.Add(currentTestName); pair.Watch.Restart(); diff --git a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs index 3a77834b72..ea19a30005 100644 --- a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs @@ -50,7 +50,6 @@ public sealed class NukeOpsTest var invSys = server.System(); var factionSys = server.System(); - Assert.That(server.CfgMan.GetCVar(CCVars.GridFill), Is.False); server.CfgMan.SetCVar(CCVars.GridFill, true); // Initially in the lobby @@ -200,7 +199,6 @@ public sealed class NukeOpsTest } ticker.SetGamePreset((GamePresetPrototype?)null); - server.CfgMan.SetCVar(CCVars.GridFill, false); await pair.SetAntagPref("NukeopsCommander", false); await pair.CleanReturnAsync(); } diff --git a/Content.Server/Administration/Systems/AdminSystem.cs b/Content.Server/Administration/Systems/AdminSystem.cs index b7ca4e915b..a1a6b33ebc 100644 --- a/Content.Server/Administration/Systems/AdminSystem.cs +++ b/Content.Server/Administration/Systems/AdminSystem.cs @@ -292,6 +292,19 @@ namespace Content.Server.Administration.Systems : _adminManager.ActiveAdmins; var hasAdmins = admins.Any(); + // TODO Fix order dependent Cvars + // Please for the sake of my sanity don't make cvars & order dependent. + // Just make a bool field on the system instead of having some cvars automatically modify other cvars. + // + // I.e., this: + // /sudo cvar game.panic_bunker.enabled true + // /sudo cvar game.panic_bunker.disable_with_admins true + // and this: + // /sudo cvar game.panic_bunker.disable_with_admins true + // /sudo cvar game.panic_bunker.enabled true + // + // should have the same effect, but currently setting the disable_with_admins can modify enabled. + if (hasAdmins && PanicBunker.DisableWithAdmins) { _config.SetCVar(CCVars.PanicBunkerEnabled, false); From 07866e47644e010733a2d1be025a27ba4b775bf3 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Fri, 31 May 2024 15:11:21 +1200 Subject: [PATCH 29/75] Prevent Identity.Name NRE exception (#28433) Fix Identity.Name exception --- Content.Client/Inventory/StrippableBoundUserInterface.cs | 5 ++++- Content.Shared/IdentityManagement/Identity.cs | 8 ++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Content.Client/Inventory/StrippableBoundUserInterface.cs b/Content.Client/Inventory/StrippableBoundUserInterface.cs index 33f38688ed..7e50eb1c68 100644 --- a/Content.Client/Inventory/StrippableBoundUserInterface.cs +++ b/Content.Client/Inventory/StrippableBoundUserInterface.cs @@ -21,7 +21,6 @@ using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Shared.Input; using Robust.Shared.Map; -using Robust.Shared.Prototypes; using static Content.Client.Inventory.ClientInventorySystem; using static Robust.Client.UserInterface.Control; @@ -53,9 +52,13 @@ namespace Content.Client.Inventory _inv = EntMan.System(); _cuffable = EntMan.System(); + // TODO update name when identity changes var title = Loc.GetString("strippable-bound-user-interface-stripping-menu-title", ("ownerName", Identity.Name(Owner, EntMan))); _strippingMenu = new StrippingMenu(title, this); _strippingMenu.OnClose += Close; + + // TODO use global entity + // BUIs are opened and closed while applying comp sates, so spawning entities here is probably not the best idea. _virtualHiddenEntity = EntMan.SpawnEntity(HiddenPocketEntityId, MapCoordinates.Nullspace); } diff --git a/Content.Shared/IdentityManagement/Identity.cs b/Content.Shared/IdentityManagement/Identity.cs index 20220e0326..2fb53e3d5a 100644 --- a/Content.Shared/IdentityManagement/Identity.cs +++ b/Content.Shared/IdentityManagement/Identity.cs @@ -15,7 +15,11 @@ public static class Identity /// public static string Name(EntityUid uid, IEntityManager ent, EntityUid? viewer=null) { - var uidName = ent.GetComponent(uid).EntityName; + var meta = ent.GetComponent(uid); + if (meta.EntityLifeStage <= EntityLifeStage.Initializing) + return meta.EntityName; // Identity component and such will not yet have initialized and may throw NREs + + var uidName = meta.EntityName; if (!ent.TryGetComponent(uid, out var identity)) return uidName; @@ -34,7 +38,7 @@ public static class Identity return uidName; } - return uidName + $" ({identName})"; + return $"{uidName} ({identName})"; } /// From dbcdefc5fdf2fba6559619ef0526729bcf73b19c Mon Sep 17 00:00:00 2001 From: Flesh <62557990+PolterTzi@users.noreply.github.com> Date: Fri, 31 May 2024 05:37:04 +0200 Subject: [PATCH 30/75] Fix trailing periods in some plant metabolism descriptions (#28428) fxied tpyos --- Resources/Locale/en-US/guidebook/chemistry/effects.ftl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Resources/Locale/en-US/guidebook/chemistry/effects.ftl b/Resources/Locale/en-US/guidebook/chemistry/effects.ftl index 1dacebd134..ba005e6bf9 100644 --- a/Resources/Locale/en-US/guidebook/chemistry/effects.ftl +++ b/Resources/Locale/en-US/guidebook/chemistry/effects.ftl @@ -373,10 +373,10 @@ reagent-effect-guidebook-plant-diethylamine = { $chance -> [1] Increases *[other] increase - } the plant's lifespan and/or base health with 10% chance for each. + } the plant's lifespan and/or base health with 10% chance for each reagent-effect-guidebook-plant-robust-harvest = { $chance -> [1] Increases *[other] increase - } the plant's potency by {$increase} up to a maximum of {$limit}. Causes the plant to lose its seeds once the potency reaches {$seedlesstreshold}. Trying to add potency over {$limit} may cause decrease in yield at a 10% chance. + } the plant's potency by {$increase} up to a maximum of {$limit}. Causes the plant to lose its seeds once the potency reaches {$seedlesstreshold}. Trying to add potency over {$limit} may cause decrease in yield at a 10% chance From 3ed1ee6a5bd6a7fc3409bdc06079465a3f9d3cbf Mon Sep 17 00:00:00 2001 From: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> Date: Thu, 30 May 2024 23:28:08 -0700 Subject: [PATCH 31/75] Add search filter to the admin menu player tab (#28030) --- .../UI/Bwoink/BwoinkWindow.xaml.cs | 15 ++- .../CustomControls/PlayerListControl.xaml.cs | 9 +- .../UI/Tabs/PlayerTab/PlayerTab.xaml | 26 ++--- .../UI/Tabs/PlayerTab/PlayerTab.xaml.cs | 108 +++++++++++++----- .../UI/Tabs/PlayerTab/PlayerTabEntry.xaml | 8 +- .../UI/Tabs/PlayerTab/PlayerTabEntry.xaml.cs | 26 +++-- .../UI/Tabs/PlayerTab/PlayerTabHeader.xaml | 3 +- .../UserInterface/Controls/ListContainer.cs | 39 +++++-- .../Controls/SearchListContainer.cs | 68 +++++++++++ .../Systems/Admin/AdminUIController.cs | 9 +- .../Locale/en-US/administration/bwoink.ftl | 2 + .../administration/ui/tabs/player-tab.ftl | 3 + 12 files changed, 236 insertions(+), 80 deletions(-) create mode 100644 Content.Client/UserInterface/Controls/SearchListContainer.cs diff --git a/Content.Client/Administration/UI/Bwoink/BwoinkWindow.xaml.cs b/Content.Client/Administration/UI/Bwoink/BwoinkWindow.xaml.cs index f8d06f758f..999eba4d29 100644 --- a/Content.Client/Administration/UI/Bwoink/BwoinkWindow.xaml.cs +++ b/Content.Client/Administration/UI/Bwoink/BwoinkWindow.xaml.cs @@ -16,14 +16,17 @@ namespace Content.Client.Administration.UI.Bwoink Bwoink.ChannelSelector.OnSelectionChanged += sel => { - if (sel is not null) + if (sel is null) { - Title = $"{sel.CharacterName} / {sel.Username}"; + Title = Loc.GetString("bwoink-none-selected"); + return; + } - if (sel.OverallPlaytime != null) - { - Title += $" | {Loc.GetString("generic-playtime-title")}: {sel.PlaytimeString}"; - } + Title = $"{sel.CharacterName} / {sel.Username}"; + + if (sel.OverallPlaytime != null) + { + Title += $" | {Loc.GetString("generic-playtime-title")}: {sel.PlaytimeString}"; } }; diff --git a/Content.Client/Administration/UI/CustomControls/PlayerListControl.xaml.cs b/Content.Client/Administration/UI/CustomControls/PlayerListControl.xaml.cs index fdf935d7c0..12522d552d 100644 --- a/Content.Client/Administration/UI/CustomControls/PlayerListControl.xaml.cs +++ b/Content.Client/Administration/UI/CustomControls/PlayerListControl.xaml.cs @@ -20,7 +20,7 @@ namespace Content.Client.Administration.UI.CustomControls private List _playerList = new(); private readonly List _sortedPlayerList = new(); - public event Action? OnSelectionChanged; + public event Action? OnSelectionChanged; public IReadOnlyList PlayerInfo => _playerList; public Func? OverrideText; @@ -41,12 +41,19 @@ namespace Content.Client.Administration.UI.CustomControls PlayerListContainer.ItemPressed += PlayerListItemPressed; PlayerListContainer.ItemKeyBindDown += PlayerListItemKeyBindDown; PlayerListContainer.GenerateItem += GenerateButton; + PlayerListContainer.NoItemSelected += PlayerListNoItemSelected; PopulateList(_adminSystem.PlayerList); FilterLineEdit.OnTextChanged += _ => FilterList(); _adminSystem.PlayerListChanged += PopulateList; BackgroundPanel.PanelOverride = new StyleBoxFlat {BackgroundColor = new Color(32, 32, 40)}; } + private void PlayerListNoItemSelected() + { + _selectedPlayer = null; + OnSelectionChanged?.Invoke(null); + } + private void PlayerListItemPressed(BaseButton.ButtonEventArgs? args, ListData? data) { if (args == null || data is not PlayerListData {Info: var selectedPlayer}) diff --git a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml index 3071bf8358..25a96df1d3 100644 --- a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml +++ b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml @@ -1,21 +1,19 @@  + xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls" + xmlns:co="clr-namespace:Content.Client.UserInterface.Controls"> - Pii = 1 << 18, + /// + /// Lets you take moderator actions on the game server. + /// + Moderator = 1 << 19, + + /// + /// Lets you check currently online admins. + /// + AdminWho = 1 << 20, + + /// + /// Lets you set the color of your OOC name. + /// + NameColor = 1 << 21, + /// /// Dangerous host permissions like scsi. /// diff --git a/Resources/engineCommandPerms.yml b/Resources/engineCommandPerms.yml index 42cc4668a9..b68a2b22df 100644 --- a/Resources/engineCommandPerms.yml +++ b/Resources/engineCommandPerms.yml @@ -90,12 +90,12 @@ - Flags: ADMIN Commands: - - delete - - kick - listplayers - tp - tpto - - respawn + +- Flags: FUN + Commands: - tippy - tip @@ -111,6 +111,12 @@ Commands: - spawn - cspawn + - delete + +- Flags: MODERATOR + Commands: + - kick + - respawn - Flags: HOST Commands: From 727a176ca80f65221d62dd0f4906120e24f9a4d6 Mon Sep 17 00:00:00 2001 From: PJBot Date: Sat, 1 Jun 2024 08:15:50 +0000 Subject: [PATCH 66/75] Automatic changelog update --- Resources/Changelog/Admin.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Resources/Changelog/Admin.yml b/Resources/Changelog/Admin.yml index 24fc59b3eb..2964e8c11b 100644 --- a/Resources/Changelog/Admin.yml +++ b/Resources/Changelog/Admin.yml @@ -272,5 +272,14 @@ Entries: id: 33 time: '2024-06-01T07:23:54.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/27776 +- author: ShadowCommander, EmoGarbage + changes: + - message: 'Added new permissions: moderator, adminwho, and namecolor' + type: Add + - message: Changed various command perms from admin to moderator & fun. + type: Tweak + id: 34 + time: '2024-06-01T08:14:44.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28451 Name: Admin Order: 1 From e3a66136bfad05d09028fe3196ce7221ba3fb2f1 Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Sat, 1 Jun 2024 02:35:14 -0700 Subject: [PATCH 67/75] Fix the client thinking it cannot shoot after mispredicting when it actually can (#28464) --- .../Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs index 6242312b07..784dd0793a 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs @@ -186,6 +186,7 @@ public abstract partial class SharedGunSystem !Paused(uid)) { gunComp.NextFire = Timing.CurTime + TimeSpan.FromSeconds(1 / gunComp.FireRateModified); + Dirty(uid, gunComp); } Dirty(uid, component); From 19be94c9ea184710218aaddff8c9b91237735042 Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Sat, 1 Jun 2024 05:08:31 -0700 Subject: [PATCH 68/75] Add job whitelist system (#28085) * Add job whitelist system * Address reviews * Fix name * Apply suggestions from code review Co-authored-by: Pieter-Jan Briers * cancinium --------- Co-authored-by: Pieter-Jan Briers --- Content.Client/Lobby/LobbyUIController.cs | 17 +- .../JobRequirementsManager.cs | 28 + Content.IntegrationTests/PoolManager.Cvars.cs | 1 + .../20240531011555_RoleWhitelist.Designer.cs | 1913 +++++++++++++++++ .../Postgres/20240531011555_RoleWhitelist.cs | 40 + .../PostgresServerDbContextModelSnapshot.cs | 31 + .../20240531011549_RoleWhitelist.Designer.cs | 1838 ++++++++++++++++ .../Sqlite/20240531011549_RoleWhitelist.cs | 40 + .../SqliteServerDbContextModelSnapshot.cs | 31 + Content.Server.Database/Model.cs | 20 + .../Commands/JobWhitelistCommands.cs | 214 ++ .../Administration/Managers/BanManager.cs | 8 +- .../Administration/Managers/IBanManager.cs | 4 +- Content.Server/Database/ServerDbBase.cs | 61 + Content.Server/Database/ServerDbManager.cs | 38 + Content.Server/Database/UserDbDataManager.cs | 50 +- Content.Server/Entry/EntryPoint.cs | 2 + .../Events/GetDisallowedJobsEvent.cs | 8 + .../GameTicking/Events/IsJobAllowedEvent.cs | 13 + .../GameTicking/GameTicker.Spawning.cs | 19 +- Content.Server/GameTicking/GameTicker.cs | 1 - Content.Server/IoC/ServerContentIoC.cs | 2 + .../JobWhitelist/JobWhitelistManager.cs | 114 + .../JobWhitelist/JobWhitelistSystem.cs | 83 + .../PlayTimeTrackingManager.cs | 9 +- .../PlayTimeTrackingSystem.cs | 30 +- .../Managers/ServerPreferencesManager.cs | 15 +- .../Events/StationJobsGetCandidatesEvent.cs | 8 + .../Systems/StationJobsSystem.Roundstart.cs | 8 +- .../Station/Systems/StationJobsSystem.cs | 2 +- Content.Shared/CCVar/CCVars.cs | 6 + .../Players/JobWhitelist/MsgJobWhitelist.cs | 33 + Content.Shared/Roles/JobPrototype.cs | 5 +- .../en-US/commands/job-whitelist-command.ftl | 20 + Resources/Locale/en-US/job/role-whitelist.ftl | 1 + 35 files changed, 4666 insertions(+), 47 deletions(-) create mode 100644 Content.Server.Database/Migrations/Postgres/20240531011555_RoleWhitelist.Designer.cs create mode 100644 Content.Server.Database/Migrations/Postgres/20240531011555_RoleWhitelist.cs create mode 100644 Content.Server.Database/Migrations/Sqlite/20240531011549_RoleWhitelist.Designer.cs create mode 100644 Content.Server.Database/Migrations/Sqlite/20240531011549_RoleWhitelist.cs create mode 100644 Content.Server/Administration/Commands/JobWhitelistCommands.cs create mode 100644 Content.Server/GameTicking/Events/GetDisallowedJobsEvent.cs create mode 100644 Content.Server/GameTicking/Events/IsJobAllowedEvent.cs create mode 100644 Content.Server/Players/JobWhitelist/JobWhitelistManager.cs create mode 100644 Content.Server/Players/JobWhitelist/JobWhitelistSystem.cs create mode 100644 Content.Server/Station/Events/StationJobsGetCandidatesEvent.cs create mode 100644 Content.Shared/Players/JobWhitelist/MsgJobWhitelist.cs create mode 100644 Resources/Locale/en-US/commands/job-whitelist-command.ftl create mode 100644 Resources/Locale/en-US/job/role-whitelist.ftl diff --git a/Content.Client/Lobby/LobbyUIController.cs b/Content.Client/Lobby/LobbyUIController.cs index ae9196c110..f6a3eed962 100644 --- a/Content.Client/Lobby/LobbyUIController.cs +++ b/Content.Client/Lobby/LobbyUIController.cs @@ -22,7 +22,6 @@ using Robust.Client.UserInterface.Controllers; using Robust.Shared.Configuration; using Robust.Shared.Map; using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.Manager; using Robust.Shared.Utility; namespace Content.Client.Lobby; @@ -70,12 +69,9 @@ public sealed class LobbyUIController : UIController, IOnStateEntered - { - _profileEditor?.RefreshAntags(); - _profileEditor?.RefreshJobs(); - _profileEditor?.RefreshLoadouts(); - }); + _configurationManager.OnValueChanged(CCVars.GameRoleTimers, _ => RefreshProfileEditor()); + + _configurationManager.OnValueChanged(CCVars.GameRoleWhitelist, _ => RefreshProfileEditor()); } private LobbyCharacterPreviewPanel? GetLobbyPreview() @@ -193,6 +189,13 @@ public sealed class LobbyUIController : UIController, IOnStateEntered _roles = new(); private readonly List _roleBans = new(); + private readonly List _jobWhitelists = new(); private ISawmill _sawmill = default!; @@ -36,6 +38,7 @@ public sealed class JobRequirementsManager : ISharedPlaytimeManager // Yeah the client manager handles role bans and playtime but the server ones are separate DEAL. _net.RegisterNetMessage(RxRoleBans); _net.RegisterNetMessage(RxPlayTime); + _net.RegisterNetMessage(RxJobWhitelist); _client.RunLevelChanged += ClientOnRunLevelChanged; } @@ -79,6 +82,13 @@ public sealed class JobRequirementsManager : ISharedPlaytimeManager Updated?.Invoke(); } + private void RxJobWhitelist(MsgJobWhitelist message) + { + _jobWhitelists.Clear(); + _jobWhitelists.AddRange(message.Whitelist); + Updated?.Invoke(); + } + public bool IsAllowed(JobPrototype job, [NotNullWhen(false)] out FormattedMessage? reason) { reason = null; @@ -89,6 +99,9 @@ public sealed class JobRequirementsManager : ISharedPlaytimeManager return false; } + if (!CheckWhitelist(job, out reason)) + return false; + var player = _playerManager.LocalSession; if (player == null) return true; @@ -116,6 +129,21 @@ public sealed class JobRequirementsManager : ISharedPlaytimeManager return reason == null; } + public bool CheckWhitelist(JobPrototype job, [NotNullWhen(false)] out FormattedMessage? reason) + { + reason = default; + if (!_cfg.GetCVar(CCVars.GameRoleWhitelist)) + return true; + + if (job.Whitelisted && !_jobWhitelists.Contains(job.ID)) + { + reason = FormattedMessage.FromUnformatted(Loc.GetString("role-not-whitelisted")); + return false; + } + + return true; + } + public TimeSpan FetchOverallPlaytime() { return _roles.TryGetValue("Overall", out var overallPlaytime) ? overallPlaytime : TimeSpan.Zero; diff --git a/Content.IntegrationTests/PoolManager.Cvars.cs b/Content.IntegrationTests/PoolManager.Cvars.cs index aa6b4dffdc..5acd9d502c 100644 --- a/Content.IntegrationTests/PoolManager.Cvars.cs +++ b/Content.IntegrationTests/PoolManager.Cvars.cs @@ -21,6 +21,7 @@ public static partial class PoolManager (CCVars.NPCMaxUpdates.Name, "999999"), (CVars.ThreadParallelCount.Name, "1"), (CCVars.GameRoleTimers.Name, "false"), + (CCVars.GameRoleWhitelist.Name, "false"), (CCVars.GridFill.Name, "false"), (CCVars.PreloadGrids.Name, "false"), (CCVars.ArrivalsShuttles.Name, "false"), diff --git a/Content.Server.Database/Migrations/Postgres/20240531011555_RoleWhitelist.Designer.cs b/Content.Server.Database/Migrations/Postgres/20240531011555_RoleWhitelist.Designer.cs new file mode 100644 index 0000000000..5032281dfe --- /dev/null +++ b/Content.Server.Database/Migrations/Postgres/20240531011555_RoleWhitelist.Designer.cs @@ -0,0 +1,1913 @@ +// +using System; +using System.Net; +using System.Text.Json; +using Content.Server.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using NpgsqlTypes; + +#nullable disable + +namespace Content.Server.Database.Migrations.Postgres +{ + [DbContext(typeof(PostgresServerDbContext))] + [Migration("20240531011555_RoleWhitelist")] + partial class RoleWhitelist + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("AdminRankId") + .HasColumnType("integer") + .HasColumnName("admin_rank_id"); + + b.Property("Title") + .HasColumnType("text") + .HasColumnName("title"); + + b.HasKey("UserId") + .HasName("PK_admin"); + + b.HasIndex("AdminRankId") + .HasDatabaseName("IX_admin_admin_rank_id"); + + b.ToTable("admin", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_flag_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdminId") + .HasColumnType("uuid") + .HasColumnName("admin_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("text") + .HasColumnName("flag"); + + b.Property("Negative") + .HasColumnType("boolean") + .HasColumnName("negative"); + + b.HasKey("Id") + .HasName("PK_admin_flag"); + + b.HasIndex("AdminId") + .HasDatabaseName("IX_admin_flag_admin_id"); + + b.HasIndex("Flag", "AdminId") + .IsUnique(); + + b.ToTable("admin_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Id") + .HasColumnType("integer") + .HasColumnName("admin_log_id"); + + b.Property("Date") + .HasColumnType("timestamp with time zone") + .HasColumnName("date"); + + b.Property("Impact") + .HasColumnType("smallint") + .HasColumnName("impact"); + + b.Property("Json") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("json"); + + b.Property("Message") + .IsRequired() + .HasColumnType("text") + .HasColumnName("message"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.HasKey("RoundId", "Id") + .HasName("PK_admin_log"); + + b.HasIndex("Date"); + + b.HasIndex("Message") + .HasAnnotation("Npgsql:TsVectorConfig", "english"); + + NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Message"), "GIN"); + + b.HasIndex("Type") + .HasDatabaseName("IX_admin_log_type"); + + b.ToTable("admin_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("LogId") + .HasColumnType("integer") + .HasColumnName("log_id"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.HasKey("RoundId", "LogId", "PlayerUserId") + .HasName("PK_admin_log_player"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_log_player_player_user_id"); + + b.ToTable("admin_log_player", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_messages_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Dismissed") + .HasColumnType("boolean") + .HasColumnName("dismissed"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Seen") + .HasColumnType("boolean") + .HasColumnName("seen"); + + b.HasKey("Id") + .HasName("PK_admin_messages"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_messages_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_messages_round_id"); + + b.ToTable("admin_messages", null, t => + { + t.HasCheckConstraint("NotDismissedAndSeen", "NOT dismissed OR seen"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.AdminNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_notes_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .IsRequired() + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Secret") + .HasColumnType("boolean") + .HasColumnName("secret"); + + b.Property("Severity") + .HasColumnType("integer") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_admin_notes"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_notes_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_notes_round_id"); + + b.ToTable("admin_notes", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_rank_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_admin_rank"); + + b.ToTable("admin_rank", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_rank_flag_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdminRankId") + .HasColumnType("integer") + .HasColumnName("admin_rank_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("text") + .HasColumnName("flag"); + + b.HasKey("Id") + .HasName("PK_admin_rank_flag"); + + b.HasIndex("AdminRankId"); + + b.HasIndex("Flag", "AdminRankId") + .IsUnique(); + + b.ToTable("admin_rank_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_watchlists_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .IsRequired() + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.HasKey("Id") + .HasName("PK_admin_watchlists"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_watchlists_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_watchlists_round_id"); + + b.ToTable("admin_watchlists", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("antag_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AntagName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("antag_name"); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_antag"); + + b.HasIndex("ProfileId", "AntagName") + .IsUnique(); + + b.ToTable("antag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AssignedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("assigned_user_id_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_assigned_user_id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("UserName") + .IsUnique(); + + b.ToTable("assigned_user_id", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("connection_log_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("Denied") + .HasColumnType("smallint") + .HasColumnName("denied"); + + b.Property("HWId") + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b.Property("ServerId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("server_id"); + + b.Property("Time") + .HasColumnType("timestamp with time zone") + .HasColumnName("time"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_connection_log"); + + b.HasIndex("ServerId") + .HasDatabaseName("IX_connection_log_server_id"); + + b.HasIndex("UserId"); + + b.ToTable("connection_log", null, t => + { + t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("job_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("JobName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("job_name"); + + b.Property("Priority") + .HasColumnType("integer") + .HasColumnName("priority"); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_job"); + + b.HasIndex("ProfileId"); + + b.HasIndex("ProfileId", "JobName") + .IsUnique(); + + b.HasIndex(new[] { "ProfileId" }, "IX_job_one_high_priority") + .IsUnique() + .HasFilter("priority = 3"); + + b.ToTable("job", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.PlayTime", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("play_time_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("PlayerId") + .HasColumnType("uuid") + .HasColumnName("player_id"); + + b.Property("TimeSpent") + .HasColumnType("interval") + .HasColumnName("time_spent"); + + b.Property("Tracker") + .IsRequired() + .HasColumnType("text") + .HasColumnName("tracker"); + + b.HasKey("Id") + .HasName("PK_play_time"); + + b.HasIndex("PlayerId", "Tracker") + .IsUnique(); + + b.ToTable("play_time", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("player_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FirstSeenTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("first_seen_time"); + + b.Property("LastReadRules") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_read_rules"); + + b.Property("LastSeenAddress") + .IsRequired() + .HasColumnType("inet") + .HasColumnName("last_seen_address"); + + b.Property("LastSeenHWId") + .HasColumnType("bytea") + .HasColumnName("last_seen_hwid"); + + b.Property("LastSeenTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_seen_time"); + + b.Property("LastSeenUserName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("last_seen_user_name"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_player"); + + b.HasAlternateKey("UserId") + .HasName("ak_player_user_id"); + + b.HasIndex("LastSeenUserName"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("player", null, t => + { + t.HasCheckConstraint("LastSeenAddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= last_seen_address"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("preference_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdminOOCColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("admin_ooc_color"); + + b.Property("SelectedCharacterSlot") + .HasColumnType("integer") + .HasColumnName("selected_character_slot"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_preference"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("preference", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("profile_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Age") + .HasColumnType("integer") + .HasColumnName("age"); + + b.Property("CharacterName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("char_name"); + + b.Property("EyeColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("eye_color"); + + b.Property("FacialHairColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("facial_hair_color"); + + b.Property("FacialHairName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("facial_hair_name"); + + b.Property("FlavorText") + .IsRequired() + .HasColumnType("text") + .HasColumnName("flavor_text"); + + b.Property("Gender") + .IsRequired() + .HasColumnType("text") + .HasColumnName("gender"); + + b.Property("HairColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("hair_color"); + + b.Property("HairName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("hair_name"); + + b.Property("Markings") + .HasColumnType("jsonb") + .HasColumnName("markings"); + + b.Property("PreferenceId") + .HasColumnType("integer") + .HasColumnName("preference_id"); + + b.Property("PreferenceUnavailable") + .HasColumnType("integer") + .HasColumnName("pref_unavailable"); + + b.Property("Sex") + .IsRequired() + .HasColumnType("text") + .HasColumnName("sex"); + + b.Property("SkinColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("skin_color"); + + b.Property("Slot") + .HasColumnType("integer") + .HasColumnName("slot"); + + b.Property("SpawnPriority") + .HasColumnType("integer") + .HasColumnName("spawn_priority"); + + b.Property("Species") + .IsRequired() + .HasColumnType("text") + .HasColumnName("species"); + + b.HasKey("Id") + .HasName("PK_profile"); + + b.HasIndex("PreferenceId") + .HasDatabaseName("IX_profile_preference_id"); + + b.HasIndex("Slot", "PreferenceId") + .IsUnique(); + + b.ToTable("profile", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("profile_loadout_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("LoadoutName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("loadout_name"); + + b.Property("ProfileLoadoutGroupId") + .HasColumnType("integer") + .HasColumnName("profile_loadout_group_id"); + + b.HasKey("Id") + .HasName("PK_profile_loadout"); + + b.HasIndex("ProfileLoadoutGroupId"); + + b.ToTable("profile_loadout", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("profile_loadout_group_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("GroupName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("group_name"); + + b.Property("ProfileRoleLoadoutId") + .HasColumnType("integer") + .HasColumnName("profile_role_loadout_id"); + + b.HasKey("Id") + .HasName("PK_profile_loadout_group"); + + b.HasIndex("ProfileRoleLoadoutId"); + + b.ToTable("profile_loadout_group", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("profile_role_loadout_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.Property("RoleName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("role_name"); + + b.HasKey("Id") + .HasName("PK_profile_role_loadout"); + + b.HasIndex("ProfileId"); + + b.ToTable("profile_role_loadout", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => + { + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("RoleId") + .HasColumnType("text") + .HasColumnName("role_id"); + + b.HasKey("PlayerUserId", "RoleId") + .HasName("PK_role_whitelists"); + + b.ToTable("role_whitelists", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("round_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ServerId") + .HasColumnType("integer") + .HasColumnName("server_id"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("start_date"); + + b.HasKey("Id") + .HasName("PK_round"); + + b.HasIndex("ServerId") + .HasDatabaseName("IX_round_server_id"); + + b.HasIndex("StartDate"); + + b.ToTable("round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Server", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_server"); + + b.ToTable("server", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_ban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("AutoDelete") + .HasColumnType("boolean") + .HasColumnName("auto_delete"); + + b.Property("BanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("uuid") + .HasColumnName("banning_admin"); + + b.Property("ExemptFlags") + .HasColumnType("integer") + .HasColumnName("exempt_flags"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("HWId") + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b.Property("Hidden") + .HasColumnType("boolean") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Severity") + .HasColumnType("integer") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_server_ban"); + + b.HasIndex("Address"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_server_ban_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_server_ban_round_id"); + + b.ToTable("server_ban", null, t => + { + t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + + t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanExemption", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("Flags") + .HasColumnType("integer") + .HasColumnName("flags"); + + b.HasKey("UserId") + .HasName("PK_server_ban_exemption"); + + b.ToTable("server_ban_exemption", null, t => + { + t.HasCheckConstraint("FlagsNotZero", "flags != 0"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_ban_hit_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("ConnectionId") + .HasColumnType("integer") + .HasColumnName("connection_id"); + + b.HasKey("Id") + .HasName("PK_server_ban_hit"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_server_ban_hit_ban_id"); + + b.HasIndex("ConnectionId") + .HasDatabaseName("IX_server_ban_hit_connection_id"); + + b.ToTable("server_ban_hit", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_role_ban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("BanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("uuid") + .HasColumnName("banning_admin"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("HWId") + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b.Property("Hidden") + .HasColumnType("boolean") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("role_id"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Severity") + .HasColumnType("integer") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_server_role_ban"); + + b.HasIndex("Address"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_server_role_ban_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_server_role_ban_round_id"); + + b.ToTable("server_role_ban", null, t => + { + t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + + t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("role_unban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("uuid") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_role_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_role_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("unban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("uuid") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Trait", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("trait_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.Property("TraitName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trait_name"); + + b.HasKey("Id") + .HasName("PK_trait"); + + b.HasIndex("ProfileId", "TraitName") + .IsUnique(); + + b.ToTable("trait", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("uploaded_resource_log_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Data") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("data"); + + b.Property("Date") + .HasColumnType("timestamp with time zone") + .HasColumnName("date"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text") + .HasColumnName("path"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_uploaded_resource_log"); + + b.ToTable("uploaded_resource_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Whitelist", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("UserId") + .HasName("PK_whitelist"); + + b.ToTable("whitelist", (string)null); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.Property("PlayersId") + .HasColumnType("integer") + .HasColumnName("players_id"); + + b.Property("RoundsId") + .HasColumnType("integer") + .HasColumnName("rounds_id"); + + b.HasKey("PlayersId", "RoundsId") + .HasName("PK_player_round"); + + b.HasIndex("RoundsId") + .HasDatabaseName("IX_player_round_rounds_id"); + + b.ToTable("player_round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.HasOne("Content.Server.Database.AdminRank", "AdminRank") + .WithMany("Admins") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_admin_rank_admin_rank_id"); + + b.Navigation("AdminRank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.HasOne("Content.Server.Database.Admin", "Admin") + .WithMany("Flags") + .HasForeignKey("AdminId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_flag_admin_admin_id"); + + b.Navigation("Admin"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany("AdminLogs") + .HasForeignKey("RoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_round_round_id"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminLogs") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_player_player_user_id"); + + b.HasOne("Content.Server.Database.AdminLog", "Log") + .WithMany("Players") + .HasForeignKey("RoundId", "LogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_admin_log_round_id_log_id"); + + b.Navigation("Log"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminMessage", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminMessagesCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminMessagesDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminMessagesLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminMessagesReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_messages_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_messages_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminNote", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminNotesCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminNotesDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminNotesLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminNotesReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_notes_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_notes_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.HasOne("Content.Server.Database.AdminRank", "Rank") + .WithMany("Flags") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_rank_flag_admin_rank_admin_rank_id"); + + b.Navigation("Rank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminWatchlistsCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminWatchlistsDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminWatchlistsLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminWatchlistsReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_watchlists_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_watchlists_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Antags") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_antag_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.HasOne("Content.Server.Database.Server", "Server") + .WithMany("ConnectionLogs") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.SetNull) + .IsRequired() + .HasConstraintName("FK_connection_log_server_server_id"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Jobs") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_job_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.HasOne("Content.Server.Database.Preference", "Preference") + .WithMany("Profiles") + .HasForeignKey("PreferenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_preference_preference_id"); + + b.Navigation("Preference"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b => + { + b.HasOne("Content.Server.Database.ProfileLoadoutGroup", "ProfileLoadoutGroup") + .WithMany("Loadouts") + .HasForeignKey("ProfileLoadoutGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_loadout_profile_loadout_group_profile_loadout_group~"); + + b.Navigation("ProfileLoadoutGroup"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => + { + b.HasOne("Content.Server.Database.ProfileRoleLoadout", "ProfileRoleLoadout") + .WithMany("Groups") + .HasForeignKey("ProfileRoleLoadoutId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_loadout_group_profile_role_loadout_profile_role_loa~"); + + b.Navigation("ProfileRoleLoadout"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Loadouts") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_role_loadout_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("JobWhitelists") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_role_whitelists_player_player_user_id"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.HasOne("Content.Server.Database.Server", "Server") + .WithMany("Rounds") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_round_server_server_id"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_ban_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_server_ban_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithMany("BanHits") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_server_ban_ban_id"); + + b.HasOne("Content.Server.Database.ConnectionLog", "Connection") + .WithMany("BanHits") + .HasForeignKey("ConnectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_connection_log_connection_id"); + + b.Navigation("Ban"); + + b.Navigation("Connection"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerRoleBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_role_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerRoleBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_role_ban_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_server_role_ban_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.HasOne("Content.Server.Database.ServerRoleBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerRoleUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_role_unban_server_role_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_unban_server_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.Trait", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Traits") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_trait_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.HasOne("Content.Server.Database.Player", null) + .WithMany() + .HasForeignKey("PlayersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_player_players_id"); + + b.HasOne("Content.Server.Database.Round", null) + .WithMany() + .HasForeignKey("RoundsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_round_rounds_id"); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Navigation("Players"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Navigation("Admins"); + + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Navigation("BanHits"); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Navigation("AdminLogs"); + + b.Navigation("AdminMessagesCreated"); + + b.Navigation("AdminMessagesDeleted"); + + b.Navigation("AdminMessagesLastEdited"); + + b.Navigation("AdminMessagesReceived"); + + b.Navigation("AdminNotesCreated"); + + b.Navigation("AdminNotesDeleted"); + + b.Navigation("AdminNotesLastEdited"); + + b.Navigation("AdminNotesReceived"); + + b.Navigation("AdminServerBansCreated"); + + b.Navigation("AdminServerBansLastEdited"); + + b.Navigation("AdminServerRoleBansCreated"); + + b.Navigation("AdminServerRoleBansLastEdited"); + + b.Navigation("AdminWatchlistsCreated"); + + b.Navigation("AdminWatchlistsDeleted"); + + b.Navigation("AdminWatchlistsLastEdited"); + + b.Navigation("AdminWatchlistsReceived"); + + b.Navigation("JobWhitelists"); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Navigation("Profiles"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Navigation("Antags"); + + b.Navigation("Jobs"); + + b.Navigation("Loadouts"); + + b.Navigation("Traits"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => + { + b.Navigation("Loadouts"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => + { + b.Navigation("Groups"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Navigation("AdminLogs"); + }); + + modelBuilder.Entity("Content.Server.Database.Server", b => + { + b.Navigation("ConnectionLogs"); + + b.Navigation("Rounds"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Navigation("BanHits"); + + b.Navigation("Unban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Navigation("Unban"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Content.Server.Database/Migrations/Postgres/20240531011555_RoleWhitelist.cs b/Content.Server.Database/Migrations/Postgres/20240531011555_RoleWhitelist.cs new file mode 100644 index 0000000000..1cd0d8df17 --- /dev/null +++ b/Content.Server.Database/Migrations/Postgres/20240531011555_RoleWhitelist.cs @@ -0,0 +1,40 @@ +#nullable disable + +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Content.Server.Database.Migrations.Postgres +{ + /// + public partial class RoleWhitelist : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "role_whitelists", + columns: table => new + { + player_user_id = table.Column(type: "uuid", nullable: false), + role_id = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_role_whitelists", x => new { x.player_user_id, x.role_id }); + table.ForeignKey( + name: "FK_role_whitelists_player_player_user_id", + column: x => x.player_user_id, + principalTable: "player", + principalColumn: "user_id", + onDelete: ReferentialAction.Cascade); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "role_whitelists"); + } + } +} diff --git a/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs b/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs index bbbd64d874..32a655f7f6 100644 --- a/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs +++ b/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs @@ -900,6 +900,22 @@ namespace Content.Server.Database.Migrations.Postgres b.ToTable("profile_role_loadout", (string)null); }); + modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => + { + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("RoleId") + .HasColumnType("text") + .HasColumnName("role_id"); + + b.HasKey("PlayerUserId", "RoleId") + .HasName("PK_role_whitelists"); + + b.ToTable("role_whitelists", (string)null); + }); + modelBuilder.Entity("Content.Server.Database.Round", b => { b.Property("Id") @@ -1623,6 +1639,19 @@ namespace Content.Server.Database.Migrations.Postgres b.Navigation("Profile"); }); + modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("JobWhitelists") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_role_whitelists_player_player_user_id"); + + b.Navigation("Player"); + }); + modelBuilder.Entity("Content.Server.Database.Round", b => { b.HasOne("Content.Server.Database.Server", "Server") @@ -1822,6 +1851,8 @@ namespace Content.Server.Database.Migrations.Postgres b.Navigation("AdminWatchlistsLastEdited"); b.Navigation("AdminWatchlistsReceived"); + + b.Navigation("JobWhitelists"); }); modelBuilder.Entity("Content.Server.Database.Preference", b => diff --git a/Content.Server.Database/Migrations/Sqlite/20240531011549_RoleWhitelist.Designer.cs b/Content.Server.Database/Migrations/Sqlite/20240531011549_RoleWhitelist.Designer.cs new file mode 100644 index 0000000000..531f013604 --- /dev/null +++ b/Content.Server.Database/Migrations/Sqlite/20240531011549_RoleWhitelist.Designer.cs @@ -0,0 +1,1838 @@ +// +using System; +using Content.Server.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Content.Server.Database.Migrations.Sqlite +{ + [DbContext(typeof(SqliteServerDbContext))] + [Migration("20240531011549_RoleWhitelist")] + partial class RoleWhitelist + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.0"); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("AdminRankId") + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_id"); + + b.Property("Title") + .HasColumnType("TEXT") + .HasColumnName("title"); + + b.HasKey("UserId") + .HasName("PK_admin"); + + b.HasIndex("AdminRankId") + .HasDatabaseName("IX_admin_admin_rank_id"); + + b.ToTable("admin", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_flag_id"); + + b.Property("AdminId") + .HasColumnType("TEXT") + .HasColumnName("admin_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("flag"); + + b.Property("Negative") + .HasColumnType("INTEGER") + .HasColumnName("negative"); + + b.HasKey("Id") + .HasName("PK_admin_flag"); + + b.HasIndex("AdminId") + .HasDatabaseName("IX_admin_flag_admin_id"); + + b.HasIndex("Flag", "AdminId") + .IsUnique(); + + b.ToTable("admin_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Id") + .HasColumnType("INTEGER") + .HasColumnName("admin_log_id"); + + b.Property("Date") + .HasColumnType("TEXT") + .HasColumnName("date"); + + b.Property("Impact") + .HasColumnType("INTEGER") + .HasColumnName("impact"); + + b.Property("Json") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("json"); + + b.Property("Message") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("Type") + .HasColumnType("INTEGER") + .HasColumnName("type"); + + b.HasKey("RoundId", "Id") + .HasName("PK_admin_log"); + + b.HasIndex("Date"); + + b.HasIndex("Type") + .HasDatabaseName("IX_admin_log_type"); + + b.ToTable("admin_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("LogId") + .HasColumnType("INTEGER") + .HasColumnName("log_id"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.HasKey("RoundId", "LogId", "PlayerUserId") + .HasName("PK_admin_log_player"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_log_player_player_user_id"); + + b.ToTable("admin_log_player", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_messages_id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("TEXT") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("INTEGER") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("TEXT") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("TEXT") + .HasColumnName("deleted_by_id"); + + b.Property("Dismissed") + .HasColumnType("INTEGER") + .HasColumnName("dismissed"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Seen") + .HasColumnType("INTEGER") + .HasColumnName("seen"); + + b.HasKey("Id") + .HasName("PK_admin_messages"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_messages_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_messages_round_id"); + + b.ToTable("admin_messages", null, t => + { + t.HasCheckConstraint("NotDismissedAndSeen", "NOT dismissed OR seen"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.AdminNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_notes_id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("TEXT") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("INTEGER") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("TEXT") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("TEXT") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Secret") + .HasColumnType("INTEGER") + .HasColumnName("secret"); + + b.Property("Severity") + .HasColumnType("INTEGER") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_admin_notes"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_notes_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_notes_round_id"); + + b.ToTable("admin_notes", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_id"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_admin_rank"); + + b.ToTable("admin_rank", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_flag_id"); + + b.Property("AdminRankId") + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("flag"); + + b.HasKey("Id") + .HasName("PK_admin_rank_flag"); + + b.HasIndex("AdminRankId"); + + b.HasIndex("Flag", "AdminRankId") + .IsUnique(); + + b.ToTable("admin_rank_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_watchlists_id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("TEXT") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("INTEGER") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("TEXT") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("TEXT") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.HasKey("Id") + .HasName("PK_admin_watchlists"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_watchlists_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_watchlists_round_id"); + + b.ToTable("admin_watchlists", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("antag_id"); + + b.Property("AntagName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("antag_name"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_antag"); + + b.HasIndex("ProfileId", "AntagName") + .IsUnique(); + + b.ToTable("antag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AssignedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("assigned_user_id_id"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_assigned_user_id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("UserName") + .IsUnique(); + + b.ToTable("assigned_user_id", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("connection_log_id"); + + b.Property("Address") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("Denied") + .HasColumnType("INTEGER") + .HasColumnName("denied"); + + b.Property("HWId") + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b.Property("ServerId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0) + .HasColumnName("server_id"); + + b.Property("Time") + .HasColumnType("TEXT") + .HasColumnName("time"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_connection_log"); + + b.HasIndex("ServerId") + .HasDatabaseName("IX_connection_log_server_id"); + + b.HasIndex("UserId"); + + b.ToTable("connection_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("job_id"); + + b.Property("JobName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("job_name"); + + b.Property("Priority") + .HasColumnType("INTEGER") + .HasColumnName("priority"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_job"); + + b.HasIndex("ProfileId"); + + b.HasIndex("ProfileId", "JobName") + .IsUnique(); + + b.HasIndex(new[] { "ProfileId" }, "IX_job_one_high_priority") + .IsUnique() + .HasFilter("priority = 3"); + + b.ToTable("job", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.PlayTime", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("play_time_id"); + + b.Property("PlayerId") + .HasColumnType("TEXT") + .HasColumnName("player_id"); + + b.Property("TimeSpent") + .HasColumnType("TEXT") + .HasColumnName("time_spent"); + + b.Property("Tracker") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("tracker"); + + b.HasKey("Id") + .HasName("PK_play_time"); + + b.HasIndex("PlayerId", "Tracker") + .IsUnique(); + + b.ToTable("play_time", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("player_id"); + + b.Property("FirstSeenTime") + .HasColumnType("TEXT") + .HasColumnName("first_seen_time"); + + b.Property("LastReadRules") + .HasColumnType("TEXT") + .HasColumnName("last_read_rules"); + + b.Property("LastSeenAddress") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_seen_address"); + + b.Property("LastSeenHWId") + .HasColumnType("BLOB") + .HasColumnName("last_seen_hwid"); + + b.Property("LastSeenTime") + .HasColumnType("TEXT") + .HasColumnName("last_seen_time"); + + b.Property("LastSeenUserName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_seen_user_name"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_player"); + + b.HasAlternateKey("UserId") + .HasName("ak_player_user_id"); + + b.HasIndex("LastSeenUserName"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("player", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("preference_id"); + + b.Property("AdminOOCColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("admin_ooc_color"); + + b.Property("SelectedCharacterSlot") + .HasColumnType("INTEGER") + .HasColumnName("selected_character_slot"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_preference"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("preference", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.Property("Age") + .HasColumnType("INTEGER") + .HasColumnName("age"); + + b.Property("CharacterName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("char_name"); + + b.Property("EyeColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("eye_color"); + + b.Property("FacialHairColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("facial_hair_color"); + + b.Property("FacialHairName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("facial_hair_name"); + + b.Property("FlavorText") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("flavor_text"); + + b.Property("Gender") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("gender"); + + b.Property("HairColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("hair_color"); + + b.Property("HairName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("hair_name"); + + b.Property("Markings") + .HasColumnType("jsonb") + .HasColumnName("markings"); + + b.Property("PreferenceId") + .HasColumnType("INTEGER") + .HasColumnName("preference_id"); + + b.Property("PreferenceUnavailable") + .HasColumnType("INTEGER") + .HasColumnName("pref_unavailable"); + + b.Property("Sex") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("sex"); + + b.Property("SkinColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("skin_color"); + + b.Property("Slot") + .HasColumnType("INTEGER") + .HasColumnName("slot"); + + b.Property("SpawnPriority") + .HasColumnType("INTEGER") + .HasColumnName("spawn_priority"); + + b.Property("Species") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("species"); + + b.HasKey("Id") + .HasName("PK_profile"); + + b.HasIndex("PreferenceId") + .HasDatabaseName("IX_profile_preference_id"); + + b.HasIndex("Slot", "PreferenceId") + .IsUnique(); + + b.ToTable("profile", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("profile_loadout_id"); + + b.Property("LoadoutName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("loadout_name"); + + b.Property("ProfileLoadoutGroupId") + .HasColumnType("INTEGER") + .HasColumnName("profile_loadout_group_id"); + + b.HasKey("Id") + .HasName("PK_profile_loadout"); + + b.HasIndex("ProfileLoadoutGroupId"); + + b.ToTable("profile_loadout", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("profile_loadout_group_id"); + + b.Property("GroupName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("group_name"); + + b.Property("ProfileRoleLoadoutId") + .HasColumnType("INTEGER") + .HasColumnName("profile_role_loadout_id"); + + b.HasKey("Id") + .HasName("PK_profile_loadout_group"); + + b.HasIndex("ProfileRoleLoadoutId"); + + b.ToTable("profile_loadout_group", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("profile_role_loadout_id"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.Property("RoleName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("role_name"); + + b.HasKey("Id") + .HasName("PK_profile_role_loadout"); + + b.HasIndex("ProfileId"); + + b.ToTable("profile_role_loadout", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => + { + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("RoleId") + .HasColumnType("TEXT") + .HasColumnName("role_id"); + + b.HasKey("PlayerUserId", "RoleId") + .HasName("PK_role_whitelists"); + + b.ToTable("role_whitelists", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("ServerId") + .HasColumnType("INTEGER") + .HasColumnName("server_id"); + + b.Property("StartDate") + .HasColumnType("TEXT") + .HasColumnName("start_date"); + + b.HasKey("Id") + .HasName("PK_round"); + + b.HasIndex("ServerId") + .HasDatabaseName("IX_round_server_id"); + + b.HasIndex("StartDate"); + + b.ToTable("round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Server", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_id"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_server"); + + b.ToTable("server", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_ban_id"); + + b.Property("Address") + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("AutoDelete") + .HasColumnType("INTEGER") + .HasColumnName("auto_delete"); + + b.Property("BanTime") + .HasColumnType("TEXT") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("banning_admin"); + + b.Property("ExemptFlags") + .HasColumnType("INTEGER") + .HasColumnName("exempt_flags"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("HWId") + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b.Property("Hidden") + .HasColumnType("INTEGER") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("reason"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Severity") + .HasColumnType("INTEGER") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_server_ban"); + + b.HasIndex("Address"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_server_ban_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_server_ban_round_id"); + + b.ToTable("server_ban", null, t => + { + t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanExemption", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("Flags") + .HasColumnType("INTEGER") + .HasColumnName("flags"); + + b.HasKey("UserId") + .HasName("PK_server_ban_exemption"); + + b.ToTable("server_ban_exemption", null, t => + { + t.HasCheckConstraint("FlagsNotZero", "flags != 0"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_ban_hit_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("ConnectionId") + .HasColumnType("INTEGER") + .HasColumnName("connection_id"); + + b.HasKey("Id") + .HasName("PK_server_ban_hit"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_server_ban_hit_ban_id"); + + b.HasIndex("ConnectionId") + .HasDatabaseName("IX_server_ban_hit_connection_id"); + + b.ToTable("server_ban_hit", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_role_ban_id"); + + b.Property("Address") + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("BanTime") + .HasColumnType("TEXT") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("banning_admin"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("HWId") + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b.Property("Hidden") + .HasColumnType("INTEGER") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("reason"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("role_id"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Severity") + .HasColumnType("INTEGER") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_server_role_ban"); + + b.HasIndex("Address"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_server_role_ban_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_server_role_ban_round_id"); + + b.ToTable("server_role_ban", null, t => + { + t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("role_unban_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("TEXT") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_role_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_role_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("unban_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("TEXT") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Trait", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("trait_id"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.Property("TraitName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("trait_name"); + + b.HasKey("Id") + .HasName("PK_trait"); + + b.HasIndex("ProfileId", "TraitName") + .IsUnique(); + + b.ToTable("trait", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("uploaded_resource_log_id"); + + b.Property("Data") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("data"); + + b.Property("Date") + .HasColumnType("TEXT") + .HasColumnName("date"); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("path"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_uploaded_resource_log"); + + b.ToTable("uploaded_resource_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Whitelist", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("UserId") + .HasName("PK_whitelist"); + + b.ToTable("whitelist", (string)null); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.Property("PlayersId") + .HasColumnType("INTEGER") + .HasColumnName("players_id"); + + b.Property("RoundsId") + .HasColumnType("INTEGER") + .HasColumnName("rounds_id"); + + b.HasKey("PlayersId", "RoundsId") + .HasName("PK_player_round"); + + b.HasIndex("RoundsId") + .HasDatabaseName("IX_player_round_rounds_id"); + + b.ToTable("player_round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.HasOne("Content.Server.Database.AdminRank", "AdminRank") + .WithMany("Admins") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_admin_rank_admin_rank_id"); + + b.Navigation("AdminRank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.HasOne("Content.Server.Database.Admin", "Admin") + .WithMany("Flags") + .HasForeignKey("AdminId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_flag_admin_admin_id"); + + b.Navigation("Admin"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany("AdminLogs") + .HasForeignKey("RoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_round_round_id"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminLogs") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_player_player_user_id"); + + b.HasOne("Content.Server.Database.AdminLog", "Log") + .WithMany("Players") + .HasForeignKey("RoundId", "LogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_admin_log_round_id_log_id"); + + b.Navigation("Log"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminMessage", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminMessagesCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminMessagesDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminMessagesLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminMessagesReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_messages_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_messages_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminNote", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminNotesCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminNotesDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminNotesLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminNotesReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_notes_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_notes_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.HasOne("Content.Server.Database.AdminRank", "Rank") + .WithMany("Flags") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_rank_flag_admin_rank_admin_rank_id"); + + b.Navigation("Rank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminWatchlistsCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminWatchlistsDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminWatchlistsLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminWatchlistsReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_watchlists_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_watchlists_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Antags") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_antag_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.HasOne("Content.Server.Database.Server", "Server") + .WithMany("ConnectionLogs") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.SetNull) + .IsRequired() + .HasConstraintName("FK_connection_log_server_server_id"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Jobs") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_job_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.HasOne("Content.Server.Database.Preference", "Preference") + .WithMany("Profiles") + .HasForeignKey("PreferenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_preference_preference_id"); + + b.Navigation("Preference"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b => + { + b.HasOne("Content.Server.Database.ProfileLoadoutGroup", "ProfileLoadoutGroup") + .WithMany("Loadouts") + .HasForeignKey("ProfileLoadoutGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_loadout_profile_loadout_group_profile_loadout_group_id"); + + b.Navigation("ProfileLoadoutGroup"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => + { + b.HasOne("Content.Server.Database.ProfileRoleLoadout", "ProfileRoleLoadout") + .WithMany("Groups") + .HasForeignKey("ProfileRoleLoadoutId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_loadout_group_profile_role_loadout_profile_role_loadout_id"); + + b.Navigation("ProfileRoleLoadout"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Loadouts") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_role_loadout_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("JobWhitelists") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_role_whitelists_player_player_user_id"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.HasOne("Content.Server.Database.Server", "Server") + .WithMany("Rounds") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_round_server_server_id"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_ban_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_server_ban_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithMany("BanHits") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_server_ban_ban_id"); + + b.HasOne("Content.Server.Database.ConnectionLog", "Connection") + .WithMany("BanHits") + .HasForeignKey("ConnectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_connection_log_connection_id"); + + b.Navigation("Ban"); + + b.Navigation("Connection"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerRoleBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_role_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerRoleBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_role_ban_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_server_role_ban_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.HasOne("Content.Server.Database.ServerRoleBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerRoleUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_role_unban_server_role_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_unban_server_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.Trait", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Traits") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_trait_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.HasOne("Content.Server.Database.Player", null) + .WithMany() + .HasForeignKey("PlayersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_player_players_id"); + + b.HasOne("Content.Server.Database.Round", null) + .WithMany() + .HasForeignKey("RoundsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_round_rounds_id"); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Navigation("Players"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Navigation("Admins"); + + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Navigation("BanHits"); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Navigation("AdminLogs"); + + b.Navigation("AdminMessagesCreated"); + + b.Navigation("AdminMessagesDeleted"); + + b.Navigation("AdminMessagesLastEdited"); + + b.Navigation("AdminMessagesReceived"); + + b.Navigation("AdminNotesCreated"); + + b.Navigation("AdminNotesDeleted"); + + b.Navigation("AdminNotesLastEdited"); + + b.Navigation("AdminNotesReceived"); + + b.Navigation("AdminServerBansCreated"); + + b.Navigation("AdminServerBansLastEdited"); + + b.Navigation("AdminServerRoleBansCreated"); + + b.Navigation("AdminServerRoleBansLastEdited"); + + b.Navigation("AdminWatchlistsCreated"); + + b.Navigation("AdminWatchlistsDeleted"); + + b.Navigation("AdminWatchlistsLastEdited"); + + b.Navigation("AdminWatchlistsReceived"); + + b.Navigation("JobWhitelists"); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Navigation("Profiles"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Navigation("Antags"); + + b.Navigation("Jobs"); + + b.Navigation("Loadouts"); + + b.Navigation("Traits"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => + { + b.Navigation("Loadouts"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => + { + b.Navigation("Groups"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Navigation("AdminLogs"); + }); + + modelBuilder.Entity("Content.Server.Database.Server", b => + { + b.Navigation("ConnectionLogs"); + + b.Navigation("Rounds"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Navigation("BanHits"); + + b.Navigation("Unban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Navigation("Unban"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Content.Server.Database/Migrations/Sqlite/20240531011549_RoleWhitelist.cs b/Content.Server.Database/Migrations/Sqlite/20240531011549_RoleWhitelist.cs new file mode 100644 index 0000000000..9d192fc685 --- /dev/null +++ b/Content.Server.Database/Migrations/Sqlite/20240531011549_RoleWhitelist.cs @@ -0,0 +1,40 @@ +#nullable disable + +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Content.Server.Database.Migrations.Sqlite +{ + /// + public partial class RoleWhitelist : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "role_whitelists", + columns: table => new + { + player_user_id = table.Column(type: "TEXT", nullable: false), + role_id = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_role_whitelists", x => new { x.player_user_id, x.role_id }); + table.ForeignKey( + name: "FK_role_whitelists_player_player_user_id", + column: x => x.player_user_id, + principalTable: "player", + principalColumn: "user_id", + onDelete: ReferentialAction.Cascade); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "role_whitelists"); + } + } +} diff --git a/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs b/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs index d343f49f41..2c444c83eb 100644 --- a/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs +++ b/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs @@ -847,6 +847,22 @@ namespace Content.Server.Database.Migrations.Sqlite b.ToTable("profile_role_loadout", (string)null); }); + modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => + { + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("RoleId") + .HasColumnType("TEXT") + .HasColumnName("role_id"); + + b.HasKey("PlayerUserId", "RoleId") + .HasName("PK_role_whitelists"); + + b.ToTable("role_whitelists", (string)null); + }); + modelBuilder.Entity("Content.Server.Database.Round", b => { b.Property("Id") @@ -1548,6 +1564,19 @@ namespace Content.Server.Database.Migrations.Sqlite b.Navigation("Profile"); }); + modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("JobWhitelists") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_role_whitelists_player_player_user_id"); + + b.Navigation("Player"); + }); + modelBuilder.Entity("Content.Server.Database.Round", b => { b.HasOne("Content.Server.Database.Server", "Server") @@ -1747,6 +1776,8 @@ namespace Content.Server.Database.Migrations.Sqlite b.Navigation("AdminWatchlistsLastEdited"); b.Navigation("AdminWatchlistsReceived"); + + b.Navigation("JobWhitelists"); }); modelBuilder.Entity("Content.Server.Database.Preference", b => diff --git a/Content.Server.Database/Model.cs b/Content.Server.Database/Model.cs index 57dcc3fc6d..60935e8f06 100644 --- a/Content.Server.Database/Model.cs +++ b/Content.Server.Database/Model.cs @@ -40,6 +40,7 @@ namespace Content.Server.Database public DbSet AdminNotes { get; set; } = null!; public DbSet AdminWatchlists { get; set; } = null!; public DbSet AdminMessages { get; set; } = null!; + public DbSet RoleWhitelists { get; set; } = null!; protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -314,6 +315,13 @@ namespace Content.Server.Database .HasForeignKey(ban => ban.LastEditedById) .HasPrincipalKey(author => author.UserId) .OnDelete(DeleteBehavior.SetNull); + + modelBuilder.Entity() + .HasOne(w => w.Player) + .WithMany(p => p.JobWhitelists) + .HasForeignKey(w => w.PlayerUserId) + .HasPrincipalKey(p => p.UserId) + .OnDelete(DeleteBehavior.Cascade); } public virtual IQueryable SearchLogs(IQueryable query, string searchText) @@ -530,6 +538,7 @@ namespace Content.Server.Database public List AdminServerBansLastEdited { get; set; } = null!; public List AdminServerRoleBansCreated { get; set; } = null!; public List AdminServerRoleBansLastEdited { get; set; } = null!; + public List JobWhitelists { get; set; } = null!; } [Table("whitelist")] @@ -1099,4 +1108,15 @@ namespace Content.Server.Database /// public bool Dismissed { get; set; } } + + [PrimaryKey(nameof(PlayerUserId), nameof(RoleId))] + public class RoleWhitelist + { + [Required, ForeignKey("Player")] + public Guid PlayerUserId { get; set; } + public Player Player { get; set; } = default!; + + [Required] + public string RoleId { get; set; } = default!; + } } diff --git a/Content.Server/Administration/Commands/JobWhitelistCommands.cs b/Content.Server/Administration/Commands/JobWhitelistCommands.cs new file mode 100644 index 0000000000..a84a11ef84 --- /dev/null +++ b/Content.Server/Administration/Commands/JobWhitelistCommands.cs @@ -0,0 +1,214 @@ +using System.Linq; +using Content.Server.Database; +using Content.Server.Players.JobWhitelist; +using Content.Shared.Administration; +using Content.Shared.Roles; +using Robust.Server.Player; +using Robust.Shared.Console; +using Robust.Shared.Prototypes; + +namespace Content.Server.Administration.Commands; + +[AdminCommand(AdminFlags.Ban)] +public sealed class JobWhitelistAddCommand : LocalizedCommands +{ + [Dependency] private readonly IServerDbManager _db = default!; + [Dependency] private readonly JobWhitelistManager _jobWhitelist = default!; + [Dependency] private readonly IPlayerLocator _playerLocator = default!; + [Dependency] private readonly IPlayerManager _players = default!; + [Dependency] private readonly IPrototypeManager _prototypes = default!; + + public override string Command => "jobwhitelistadd"; + + public override async void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (args.Length != 2) + { + shell.WriteError(Loc.GetString("shell-wrong-arguments-number-need-specific", + ("properAmount", 2), + ("currentAmount", args.Length))); + shell.WriteLine(Help); + return; + } + + var player = args[0].Trim(); + var job = new ProtoId(args[1].Trim()); + if (!_prototypes.TryIndex(job, out var jobPrototype)) + { + shell.WriteError(Loc.GetString("cmd-jobwhitelist-job-does-not-exist", ("job", job.Id))); + shell.WriteLine(Help); + return; + } + + var data = await _playerLocator.LookupIdByNameAsync(player); + if (data != null) + { + var guid = data.UserId; + var isWhitelisted = await _db.IsJobWhitelisted(guid, job); + if (isWhitelisted) + { + shell.WriteLine(Loc.GetString("cmd-jobwhitelist-already-whitelisted", + ("player", player), + ("jobId", job.Id), + ("jobName", jobPrototype.LocalizedName))); + return; + } + + _jobWhitelist.AddWhitelist(guid, job); + shell.WriteLine(Loc.GetString("cmd-jobwhitelistadd-added", + ("player", player), + ("jobId", job.Id), + ("jobName", jobPrototype.LocalizedName))); + return; + } + + shell.WriteError(Loc.GetString("cmd-jobwhitelist-player-not-found", ("player", player))); + } + + public override CompletionResult GetCompletion(IConsoleShell shell, string[] args) + { + if (args.Length == 1) + { + return CompletionResult.FromHintOptions( + _players.Sessions.Select(s => s.Name), + Loc.GetString("cmd-jobwhitelist-hint-player")); + } + + if (args.Length == 2) + { + return CompletionResult.FromHintOptions( + _prototypes.EnumeratePrototypes().Select(p => p.ID), + Loc.GetString("cmd-jobwhitelist-hint-job")); + } + + return CompletionResult.Empty; + } +} + +[AdminCommand(AdminFlags.Ban)] +public sealed class GetJobWhitelistCommand : LocalizedCommands +{ + [Dependency] private readonly IServerDbManager _db = default!; + [Dependency] private readonly IPlayerLocator _playerLocator = default!; + [Dependency] private readonly IPlayerManager _players = default!; + + public override string Command => "jobwhitelistget"; + + public override async void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (args.Length == 0) + { + shell.WriteError("This command needs at least one argument."); + shell.WriteLine(Help); + return; + } + + var player = string.Join(' ', args).Trim(); + var data = await _playerLocator.LookupIdByNameAsync(player); + if (data != null) + { + var guid = data.UserId; + var whitelists = await _db.GetJobWhitelists(guid); + if (whitelists.Count == 0) + { + shell.WriteLine(Loc.GetString("cmd-jobwhitelistget-whitelisted-none", ("player", player))); + return; + } + + shell.WriteLine(Loc.GetString("cmd-jobwhitelistget-whitelisted-for", + ("player", player), + ("jobs", string.Join(", ", whitelists)))); + return; + } + + shell.WriteError(Loc.GetString("cmd-jobwhitelist-player-not-found", ("player", player))); + } + + public override CompletionResult GetCompletion(IConsoleShell shell, string[] args) + { + if (args.Length == 1) + { + return CompletionResult.FromHintOptions( + _players.Sessions.Select(s => s.Name), + Loc.GetString("cmd-jobwhitelist-hint-player")); + } + + return CompletionResult.Empty; + } +} + +[AdminCommand(AdminFlags.Ban)] +public sealed class RemoveJobWhitelistCommand : LocalizedCommands +{ + [Dependency] private readonly IServerDbManager _db = default!; + [Dependency] private readonly JobWhitelistManager _jobWhitelist = default!; + [Dependency] private readonly IPlayerLocator _playerLocator = default!; + [Dependency] private readonly IPlayerManager _players = default!; + [Dependency] private readonly IPrototypeManager _prototypes = default!; + + public override string Command => "jobwhitelistremove"; + + public override async void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (args.Length != 2) + { + shell.WriteError(Loc.GetString("shell-wrong-arguments-number-need-specific", + ("properAmount", 2), + ("currentAmount", args.Length))); + shell.WriteLine(Help); + return; + } + + var player = args[0].Trim(); + var job = new ProtoId(args[1].Trim()); + if (!_prototypes.TryIndex(job, out var jobPrototype)) + { + shell.WriteError(Loc.GetString("cmd-jobwhitelist-job-does-not-exist", ("job", job))); + shell.WriteLine(Help); + return; + } + + var data = await _playerLocator.LookupIdByNameAsync(player); + if (data != null) + { + var guid = data.UserId; + var isWhitelisted = await _db.IsJobWhitelisted(guid, job); + if (!isWhitelisted) + { + shell.WriteError(Loc.GetString("cmd-jobwhitelistremove-was-not-whitelisted", + ("player", player), + ("jobId", job.Id), + ("jobName", jobPrototype.LocalizedName))); + return; + } + + _jobWhitelist.RemoveWhitelist(guid, job); + shell.WriteLine(Loc.GetString("cmd-jobwhitelistremove-removed", + ("player", player), + ("jobId", job.Id), + ("jobName", jobPrototype.LocalizedName))); + return; + } + + shell.WriteError(Loc.GetString("cmd-jobwhitelist-player-not-found", ("player", player))); + } + + public override CompletionResult GetCompletion(IConsoleShell shell, string[] args) + { + if (args.Length == 1) + { + return CompletionResult.FromHintOptions( + _players.Sessions.Select(s => s.Name), + Loc.GetString("cmd-jobwhitelist-hint-player")); + } + + if (args.Length == 2) + { + return CompletionResult.FromHintOptions( + _prototypes.EnumeratePrototypes().Select(p => p.ID), + Loc.GetString("cmd-jobwhitelist-hint-job")); + } + + return CompletionResult.Empty; + } +} diff --git a/Content.Server/Administration/Managers/BanManager.cs b/Content.Server/Administration/Managers/BanManager.cs index 3a05b934b2..68bd817026 100644 --- a/Content.Server/Administration/Managers/BanManager.cs +++ b/Content.Server/Administration/Managers/BanManager.cs @@ -73,7 +73,9 @@ public sealed class BanManager : IBanManager, IPostInjectInit public HashSet? GetRoleBans(NetUserId playerUserId) { - return _cachedRoleBans.TryGetValue(playerUserId, out var roleBans) ? roleBans.Select(banDef => banDef.Role).ToHashSet() : null; + return _cachedRoleBans.TryGetValue(playerUserId, out var roleBans) + ? roleBans.Select(banDef => banDef.Role).ToHashSet() + : null; } private async Task CacheDbRoleBans(NetUserId userId, IPAddress? address = null, ImmutableArray? hwId = null) @@ -263,13 +265,13 @@ public sealed class BanManager : IBanManager, IPostInjectInit return $"Pardoned ban with id {banId}"; } - public HashSet? GetJobBans(NetUserId playerUserId) + public HashSet>? GetJobBans(NetUserId playerUserId) { if (!_cachedRoleBans.TryGetValue(playerUserId, out var roleBans)) return null; return roleBans .Where(ban => ban.Role.StartsWith(JobPrefix, StringComparison.Ordinal)) - .Select(ban => ban.Role[JobPrefix.Length..]) + .Select(ban => new ProtoId(ban.Role[JobPrefix.Length..])) .ToHashSet(); } #endregion diff --git a/Content.Server/Administration/Managers/IBanManager.cs b/Content.Server/Administration/Managers/IBanManager.cs index dafe3d35bd..b60e0a2535 100644 --- a/Content.Server/Administration/Managers/IBanManager.cs +++ b/Content.Server/Administration/Managers/IBanManager.cs @@ -2,8 +2,10 @@ using System.Collections.Immutable; using System.Net; using System.Threading.Tasks; using Content.Shared.Database; +using Content.Shared.Roles; using Robust.Shared.Network; using Robust.Shared.Player; +using Robust.Shared.Prototypes; namespace Content.Server.Administration.Managers; @@ -24,7 +26,7 @@ public interface IBanManager /// Reason for the ban public void CreateServerBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableArray? hwid, uint? minutes, NoteSeverity severity, string reason); public HashSet? GetRoleBans(NetUserId playerUserId); - public HashSet? GetJobBans(NetUserId playerUserId); + public HashSet>? GetJobBans(NetUserId playerUserId); /// /// Creates a job ban for the specified target, username or GUID diff --git a/Content.Server/Database/ServerDbBase.cs b/Content.Server/Database/ServerDbBase.cs index 8743a4b5db..be6c7196d5 100644 --- a/Content.Server/Database/ServerDbBase.cs +++ b/Content.Server/Database/ServerDbBase.cs @@ -14,9 +14,11 @@ using Content.Shared.Humanoid; using Content.Shared.Humanoid.Markings; using Content.Shared.Preferences; using Content.Shared.Preferences.Loadouts; +using Content.Shared.Roles; using Microsoft.EntityFrameworkCore; using Robust.Shared.Enums; using Robust.Shared.Network; +using Robust.Shared.Prototypes; using Robust.Shared.Utility; namespace Content.Server.Database @@ -1579,6 +1581,65 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id} #endregion + #region Job Whitelists + + public async Task AddJobWhitelist(Guid player, ProtoId job) + { + await using var db = await GetDb(); + var exists = await db.DbContext.RoleWhitelists + .Where(w => w.PlayerUserId == player) + .Where(w => w.RoleId == job.Id) + .AnyAsync(); + + if (exists) + return false; + + var whitelist = new RoleWhitelist + { + PlayerUserId = player, + RoleId = job + }; + db.DbContext.RoleWhitelists.Add(whitelist); + await db.DbContext.SaveChangesAsync(); + return true; + } + + public async Task> GetJobWhitelists(Guid player, CancellationToken cancel) + { + await using var db = await GetDb(cancel); + return await db.DbContext.RoleWhitelists + .Where(w => w.PlayerUserId == player) + .Select(w => w.RoleId) + .ToListAsync(cancellationToken: cancel); + } + + public async Task IsJobWhitelisted(Guid player, ProtoId job) + { + await using var db = await GetDb(); + return await db.DbContext.RoleWhitelists + .Where(w => w.PlayerUserId == player) + .Where(w => w.RoleId == job.Id) + .AnyAsync(); + } + + public async Task RemoveJobWhitelist(Guid player, ProtoId job) + { + await using var db = await GetDb(); + var entry = await db.DbContext.RoleWhitelists + .Where(w => w.PlayerUserId == player) + .Where(w => w.RoleId == job.Id) + .SingleOrDefaultAsync(); + + if (entry == null) + return false; + + db.DbContext.RoleWhitelists.Remove(entry); + await db.DbContext.SaveChangesAsync(); + return true; + } + + #endregion + // SQLite returns DateTime as Kind=Unspecified, Npgsql actually knows for sure it's Kind=Utc. // Normalize DateTimes here so they're always Utc. Thanks. protected abstract DateTime NormalizeDatabaseTime(DateTime time); diff --git a/Content.Server/Database/ServerDbManager.cs b/Content.Server/Database/ServerDbManager.cs index 01d1526727..1983fe43d2 100644 --- a/Content.Server/Database/ServerDbManager.cs +++ b/Content.Server/Database/ServerDbManager.cs @@ -9,6 +9,7 @@ using Content.Shared.Administration.Logs; using Content.Shared.CCVar; using Content.Shared.Database; using Content.Shared.Preferences; +using Content.Shared.Roles; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; @@ -17,6 +18,7 @@ using Prometheus; using Robust.Shared.Configuration; using Robust.Shared.ContentPack; using Robust.Shared.Network; +using Robust.Shared.Prototypes; using LogLevel = Robust.Shared.Log.LogLevel; using MSLogLevel = Microsoft.Extensions.Logging.LogLevel; @@ -290,6 +292,18 @@ namespace Content.Server.Database Task MarkMessageAsSeen(int id, bool dismissedToo); #endregion + + #region Job Whitelists + + Task AddJobWhitelist(Guid player, ProtoId job); + + + Task> GetJobWhitelists(Guid player, CancellationToken cancel = default); + Task IsJobWhitelisted(Guid player, ProtoId job); + + Task RemoveJobWhitelist(Guid player, ProtoId job); + + #endregion } public sealed class ServerDbManager : IServerDbManager @@ -869,6 +883,30 @@ namespace Content.Server.Database return RunDbCommand(() => _db.MarkMessageAsSeen(id, dismissedToo)); } + public Task AddJobWhitelist(Guid player, ProtoId job) + { + DbWriteOpsMetric.Inc(); + return RunDbCommand(() => _db.AddJobWhitelist(player, job)); + } + + public Task> GetJobWhitelists(Guid player, CancellationToken cancel = default) + { + DbReadOpsMetric.Inc(); + return RunDbCommand(() => _db.GetJobWhitelists(player, cancel)); + } + + public Task IsJobWhitelisted(Guid player, ProtoId job) + { + DbReadOpsMetric.Inc(); + return RunDbCommand(() => _db.IsJobWhitelisted(player, job)); + } + + public Task RemoveJobWhitelist(Guid player, ProtoId job) + { + DbWriteOpsMetric.Inc(); + return RunDbCommand(() => _db.RemoveJobWhitelist(player, job)); + } + // Wrapper functions to run DB commands from the thread pool. // This will avoid SynchronizationContext capturing and avoid running CPU work on the main thread. // For SQLite, this will also enable read parallelization (within limits). diff --git a/Content.Server/Database/UserDbDataManager.cs b/Content.Server/Database/UserDbDataManager.cs index c58c594dba..1aac86c129 100644 --- a/Content.Server/Database/UserDbDataManager.cs +++ b/Content.Server/Database/UserDbDataManager.cs @@ -1,8 +1,6 @@ using System.Threading; using System.Threading.Tasks; -using Content.Server.Players.PlayTimeTracking; using Content.Server.Preferences.Managers; -using Robust.Server.Player; using Robust.Shared.Network; using Robust.Shared.Player; using Robust.Shared.Utility; @@ -19,11 +17,12 @@ namespace Content.Server.Database; /// public sealed class UserDbDataManager : IPostInjectInit { - [Dependency] private readonly IServerPreferencesManager _prefs = default!; [Dependency] private readonly ILogManager _logManager = default!; - [Dependency] private readonly PlayTimeTrackingManager _playTimeTracking = default!; private readonly Dictionary _users = new(); + private readonly List _onLoadPlayer = []; + private readonly List _onFinishLoad = []; + private readonly List _onPlayerDisconnect = []; private ISawmill _sawmill = default!; @@ -51,8 +50,10 @@ public sealed class UserDbDataManager : IPostInjectInit data.Cancel.Cancel(); data.Cancel.Dispose(); - _prefs.OnClientDisconnected(session); - _playTimeTracking.ClientDisconnected(session); + foreach (var onDisconnect in _onPlayerDisconnect) + { + onDisconnect(session); + } } private async Task Load(ICommonSession session, CancellationToken cancel) @@ -62,12 +63,20 @@ public sealed class UserDbDataManager : IPostInjectInit // As such, this task must NOT throw a non-cancellation error! try { - await Task.WhenAll( - _prefs.LoadData(session, cancel), - _playTimeTracking.LoadData(session, cancel)); + var tasks = new List(); + foreach (var action in _onLoadPlayer) + { + tasks.Add(action(session, cancel)); + } + + await Task.WhenAll(tasks); cancel.ThrowIfCancellationRequested(); - _prefs.FinishLoad(session); + + foreach (var action in _onFinishLoad) + { + action(session); + } _sawmill.Verbose($"Load complete for user {session}"); } @@ -118,10 +127,31 @@ public sealed class UserDbDataManager : IPostInjectInit return _users[session.UserId].Task; } + public void AddOnLoadPlayer(OnLoadPlayer action) + { + _onLoadPlayer.Add(action); + } + + public void AddOnFinishLoad(OnFinishLoad action) + { + _onFinishLoad.Add(action); + } + + public void AddOnPlayerDisconnect(OnPlayerDisconnect action) + { + _onPlayerDisconnect.Add(action); + } + void IPostInjectInit.PostInject() { _sawmill = _logManager.GetSawmill("userdb"); } private sealed record UserData(CancellationTokenSource Cancel, Task Task); + + public delegate Task OnLoadPlayer(ICommonSession player, CancellationToken cancel); + + public delegate void OnFinishLoad(ICommonSession player); + + public delegate void OnPlayerDisconnect(ICommonSession player); } diff --git a/Content.Server/Entry/EntryPoint.cs b/Content.Server/Entry/EntryPoint.cs index 3cdf3bfe8e..234a2e4658 100644 --- a/Content.Server/Entry/EntryPoint.cs +++ b/Content.Server/Entry/EntryPoint.cs @@ -14,6 +14,7 @@ using Content.Server.Info; using Content.Server.IoC; using Content.Server.Maps; using Content.Server.NodeContainer.NodeGroups; +using Content.Server.Players.JobWhitelist; using Content.Server.Players.PlayTimeTracking; using Content.Server.Preferences.Managers; using Content.Server.ServerInfo; @@ -107,6 +108,7 @@ namespace Content.Server.Entry _voteManager.Initialize(); _updateManager.Initialize(); _playTimeTracking.Initialize(); + IoCManager.Resolve().Initialize(); } } diff --git a/Content.Server/GameTicking/Events/GetDisallowedJobsEvent.cs b/Content.Server/GameTicking/Events/GetDisallowedJobsEvent.cs new file mode 100644 index 0000000000..cd15cfb8f8 --- /dev/null +++ b/Content.Server/GameTicking/Events/GetDisallowedJobsEvent.cs @@ -0,0 +1,8 @@ +using Content.Shared.Roles; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; + +namespace Content.Server.GameTicking.Events; + +[ByRefEvent] +public readonly record struct GetDisallowedJobsEvent(ICommonSession Player, HashSet> Jobs); diff --git a/Content.Server/GameTicking/Events/IsJobAllowedEvent.cs b/Content.Server/GameTicking/Events/IsJobAllowedEvent.cs new file mode 100644 index 0000000000..51969d61ea --- /dev/null +++ b/Content.Server/GameTicking/Events/IsJobAllowedEvent.cs @@ -0,0 +1,13 @@ +using Content.Shared.Roles; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; + +namespace Content.Server.GameTicking.Events; + +[ByRefEvent] +public struct IsJobAllowedEvent(ICommonSession player, ProtoId jobId, bool cancelled = false) +{ + public readonly ICommonSession Player = player; + public readonly ProtoId JobId = jobId; + public bool Cancelled = cancelled; +} diff --git a/Content.Server/GameTicking/GameTicker.Spawning.cs b/Content.Server/GameTicking/GameTicker.Spawning.cs index 843122e0fe..22d368e57b 100644 --- a/Content.Server/GameTicking/GameTicker.Spawning.cs +++ b/Content.Server/GameTicking/GameTicker.Spawning.cs @@ -2,11 +2,11 @@ using System.Globalization; using System.Linq; using System.Numerics; using Content.Server.Administration.Managers; +using Content.Server.GameTicking.Events; using Content.Server.Ghost; using Content.Server.Spawners.Components; using Content.Server.Speech.Components; using Content.Server.Station.Components; -using Content.Shared.CCVar; using Content.Shared.Database; using Content.Shared.Mind; using Content.Shared.Players; @@ -137,8 +137,14 @@ namespace Content.Server.GameTicking if (jobBans == null || jobId != null && jobBans.Contains(jobId)) return; - if (jobId != null && !_playTimeTrackings.IsAllowed(player, jobId)) - return; + if (jobId != null) + { + var ev = new IsJobAllowedEvent(player, new ProtoId(jobId)); + RaiseLocalEvent(ref ev); + if (ev.Cancelled) + return; + } + SpawnPlayer(player, character, station, jobId, lateJoin, silent); } @@ -181,10 +187,9 @@ namespace Content.Server.GameTicking } // Figure out job restrictions - var restrictedRoles = new HashSet(); - - var getDisallowed = _playTimeTrackings.GetDisallowedJobs(player); - restrictedRoles.UnionWith(getDisallowed); + var restrictedRoles = new HashSet>(); + var ev = new GetDisallowedJobsEvent(player, restrictedRoles); + RaiseLocalEvent(ref ev); var jobBans = _banManager.GetJobBans(player.UserId); if (jobBans != null) diff --git a/Content.Server/GameTicking/GameTicker.cs b/Content.Server/GameTicking/GameTicker.cs index fa23312268..d9511309b9 100644 --- a/Content.Server/GameTicking/GameTicker.cs +++ b/Content.Server/GameTicking/GameTicker.cs @@ -19,7 +19,6 @@ using Content.Shared.Roles; using Robust.Server; using Robust.Server.GameObjects; using Robust.Server.GameStates; -using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Configuration; using Robust.Shared.Console; diff --git a/Content.Server/IoC/ServerContentIoC.cs b/Content.Server/IoC/ServerContentIoC.cs index f6800f7289..c6dfcadd38 100644 --- a/Content.Server/IoC/ServerContentIoC.cs +++ b/Content.Server/IoC/ServerContentIoC.cs @@ -13,6 +13,7 @@ using Content.Server.Info; using Content.Server.Maps; using Content.Server.MoMMI; using Content.Server.NodeContainer.NodeGroups; +using Content.Server.Players.JobWhitelist; using Content.Server.Players.PlayTimeTracking; using Content.Server.Preferences.Managers; using Content.Server.ServerInfo; @@ -61,6 +62,7 @@ namespace Content.Server.IoC IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); + IoCManager.Register(); } } } diff --git a/Content.Server/Players/JobWhitelist/JobWhitelistManager.cs b/Content.Server/Players/JobWhitelist/JobWhitelistManager.cs new file mode 100644 index 0000000000..04289a4098 --- /dev/null +++ b/Content.Server/Players/JobWhitelist/JobWhitelistManager.cs @@ -0,0 +1,114 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Content.Server.Database; +using Content.Shared.CCVar; +using Content.Shared.Players.JobWhitelist; +using Content.Shared.Roles; +using Robust.Server.Player; +using Robust.Shared.Configuration; +using Robust.Shared.Network; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; +using Serilog; + +namespace Content.Server.Players.JobWhitelist; + +public sealed class JobWhitelistManager : IPostInjectInit +{ + [Dependency] private readonly IConfigurationManager _config = default!; + [Dependency] private readonly IServerDbManager _db = default!; + [Dependency] private readonly INetManager _net = default!; + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IPrototypeManager _prototypes = default!; + [Dependency] private readonly UserDbDataManager _userDb = default!; + + private readonly Dictionary> _whitelists = new(); + + public void Initialize() + { + _net.RegisterNetMessage(); + } + + private async Task LoadData(ICommonSession session, CancellationToken cancel) + { + var whitelists = await _db.GetJobWhitelists(session.UserId, cancel); + cancel.ThrowIfCancellationRequested(); + _whitelists[session.UserId] = whitelists.ToHashSet(); + } + + private void FinishLoad(ICommonSession session) + { + SendJobWhitelist(session); + } + + private void ClientDisconnected(ICommonSession session) + { + _whitelists.Remove(session.UserId); + } + + public async void AddWhitelist(NetUserId player, ProtoId job) + { + if (_whitelists.TryGetValue(player, out var whitelists)) + whitelists.Add(job); + + await _db.AddJobWhitelist(player, job); + + if (_player.TryGetSessionById(player, out var session)) + SendJobWhitelist(session); + } + + public bool IsAllowed(ICommonSession session, ProtoId job) + { + if (!_config.GetCVar(CCVars.GameRoleWhitelist)) + return true; + + if (!_prototypes.TryIndex(job, out var jobPrototype) || + !jobPrototype.Whitelisted) + { + return true; + } + + return IsWhitelisted(session.UserId, job); + } + + public bool IsWhitelisted(NetUserId player, ProtoId job) + { + if (!_whitelists.TryGetValue(player, out var whitelists)) + { + Log.Error("Unable to check if player {Player} is whitelisted for {Job}. Stack trace:\\n{StackTrace}", + player, + job, + Environment.StackTrace); + return false; + } + + return whitelists.Contains(job); + } + + public async void RemoveWhitelist(NetUserId player, ProtoId job) + { + _whitelists.GetValueOrDefault(player)?.Remove(job); + await _db.RemoveJobWhitelist(player, job); + + if (_player.TryGetSessionById(new NetUserId(player), out var session)) + SendJobWhitelist(session); + } + + public void SendJobWhitelist(ICommonSession player) + { + var msg = new MsgJobWhitelist + { + Whitelist = _whitelists.GetValueOrDefault(player.UserId) ?? new HashSet() + }; + + _net.ServerSendMessage(msg, player.Channel); + } + + void IPostInjectInit.PostInject() + { + _userDb.AddOnLoadPlayer(LoadData); + _userDb.AddOnFinishLoad(FinishLoad); + _userDb.AddOnPlayerDisconnect(ClientDisconnected); + } +} diff --git a/Content.Server/Players/JobWhitelist/JobWhitelistSystem.cs b/Content.Server/Players/JobWhitelist/JobWhitelistSystem.cs new file mode 100644 index 0000000000..aaada99dea --- /dev/null +++ b/Content.Server/Players/JobWhitelist/JobWhitelistSystem.cs @@ -0,0 +1,83 @@ +using System.Collections.Immutable; +using Content.Server.GameTicking.Events; +using Content.Server.Station.Events; +using Content.Shared.CCVar; +using Content.Shared.Roles; +using Robust.Server.Player; +using Robust.Shared.Configuration; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Server.Players.JobWhitelist; + +public sealed class JobWhitelistSystem : EntitySystem +{ + [Dependency] private readonly IConfigurationManager _config = default!; + [Dependency] private readonly JobWhitelistManager _manager = default!; + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IPrototypeManager _prototypes = default!; + + private ImmutableArray> _whitelistedJobs = []; + + public override void Initialize() + { + SubscribeLocalEvent(OnPrototypesReloaded); + SubscribeLocalEvent(OnStationJobsGetCandidates); + SubscribeLocalEvent(OnIsJobAllowed); + SubscribeLocalEvent(OnGetDisallowedJobs); + + CacheJobs(); + } + + private void OnPrototypesReloaded(PrototypesReloadedEventArgs ev) + { + if (ev.WasModified()) + CacheJobs(); + } + + private void OnStationJobsGetCandidates(ref StationJobsGetCandidatesEvent ev) + { + if (!_config.GetCVar(CCVars.GameRoleWhitelist)) + return; + + for (var i = ev.Jobs.Count - 1; i >= 0; i--) + { + var jobId = ev.Jobs[i]; + if (_player.TryGetSessionById(ev.Player, out var player) && + !_manager.IsAllowed(player, jobId)) + { + ev.Jobs.RemoveSwap(i); + } + } + } + + private void OnIsJobAllowed(ref IsJobAllowedEvent ev) + { + if (!_manager.IsAllowed(ev.Player, ev.JobId)) + ev.Cancelled = true; + } + + private void OnGetDisallowedJobs(ref GetDisallowedJobsEvent ev) + { + if (!_config.GetCVar(CCVars.GameRoleWhitelist)) + return; + + foreach (var job in _whitelistedJobs) + { + if (!_manager.IsAllowed(ev.Player, job)) + ev.Jobs.Add(job); + } + } + + private void CacheJobs() + { + var builder = ImmutableArray.CreateBuilder>(); + foreach (var job in _prototypes.EnumeratePrototypes()) + { + if (job.Whitelisted) + builder.Add(job.ID); + } + + _whitelistedJobs = builder.ToImmutable(); + } +} diff --git a/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingManager.cs b/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingManager.cs index c638c2f240..bfd6172f4c 100644 --- a/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingManager.cs +++ b/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingManager.cs @@ -54,7 +54,7 @@ public delegate void CalcPlayTimeTrackersCallback(ICommonSession player, HashSet /// Operations like refreshing and sending play time info to clients are deferred until the next frame (note: not tick). /// /// -public sealed class PlayTimeTrackingManager : ISharedPlaytimeManager +public sealed class PlayTimeTrackingManager : ISharedPlaytimeManager, IPostInjectInit { [Dependency] private readonly IServerDbManager _db = default!; [Dependency] private readonly IServerNetManager _net = default!; @@ -62,6 +62,7 @@ public sealed class PlayTimeTrackingManager : ISharedPlaytimeManager [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly ITaskManager _task = default!; [Dependency] private readonly IRuntimeLog _runtimeLog = default!; + [Dependency] private readonly UserDbDataManager _userDb = default!; private ISawmill _sawmill = default!; @@ -445,4 +446,10 @@ public sealed class PlayTimeTrackingManager : ISharedPlaytimeManager /// public readonly HashSet DbTrackersDirty = new(); } + + void IPostInjectInit.PostInject() + { + _userDb.AddOnLoadPlayer(LoadData); + _userDb.AddOnPlayerDisconnect(ClientDisconnected); + } } diff --git a/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs b/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs index e594e4de26..c3142a709a 100644 --- a/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs +++ b/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs @@ -4,7 +4,9 @@ using Content.Server.Administration.Managers; using Content.Server.Afk; using Content.Server.Afk.Events; using Content.Server.GameTicking; +using Content.Server.GameTicking.Events; using Content.Server.Mind; +using Content.Server.Station.Events; using Content.Shared.CCVar; using Content.Shared.GameTicking; using Content.Shared.Mobs; @@ -12,7 +14,6 @@ using Content.Shared.Mobs.Components; using Content.Shared.Players; using Content.Shared.Players.PlayTimeTracking; using Content.Shared.Roles; -using Robust.Server.GameObjects; using Robust.Server.Player; using Robust.Shared.Configuration; using Robust.Shared.Network; @@ -50,6 +51,9 @@ public sealed class PlayTimeTrackingSystem : EntitySystem SubscribeLocalEvent(OnUnAFK); SubscribeLocalEvent(OnMobStateChanged); SubscribeLocalEvent(OnPlayerJoinedLobby); + SubscribeLocalEvent(OnStationJobsGetCandidates); + SubscribeLocalEvent(OnIsJobAllowed); + SubscribeLocalEvent(OnGetDisallowedJobs); _adminManager.OnPermsChanged += AdminPermsChanged; } @@ -174,6 +178,22 @@ public sealed class PlayTimeTrackingSystem : EntitySystem _tracking.QueueSendTimers(ev.PlayerSession); } + private void OnStationJobsGetCandidates(ref StationJobsGetCandidatesEvent ev) + { + RemoveDisallowedJobs(ev.Player, ev.Jobs); + } + + private void OnIsJobAllowed(ref IsJobAllowedEvent ev) + { + if (!IsAllowed(ev.Player, ev.JobId)) + ev.Cancelled = true; + } + + private void OnGetDisallowedJobs(ref GetDisallowedJobsEvent ev) + { + ev.Jobs.UnionWith(GetDisallowedJobs(ev.Player)); + } + public bool IsAllowed(ICommonSession player, string role) { if (!_prototypes.TryIndex(role, out var job) || @@ -190,9 +210,9 @@ public sealed class PlayTimeTrackingSystem : EntitySystem return JobRequirements.TryRequirementsMet(job, playTimes, out _, EntityManager, _prototypes); } - public HashSet GetDisallowedJobs(ICommonSession player) + public HashSet> GetDisallowedJobs(ICommonSession player) { - var roles = new HashSet(); + var roles = new HashSet>(); if (!_cfg.GetCVar(CCVars.GameRoleTimers)) return roles; @@ -222,7 +242,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem return roles; } - public void RemoveDisallowedJobs(NetUserId userId, ref List jobs) + public void RemoveDisallowedJobs(NetUserId userId, List> jobs) { if (!_cfg.GetCVar(CCVars.GameRoleTimers)) return; @@ -239,7 +259,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem { var job = jobs[i]; - if (!_prototypes.TryIndex(job, out var jobber) || + if (!_prototypes.TryIndex(job, out var jobber) || jobber.Requirements == null || jobber.Requirements.Count == 0) continue; diff --git a/Content.Server/Preferences/Managers/ServerPreferencesManager.cs b/Content.Server/Preferences/Managers/ServerPreferencesManager.cs index e32af589e9..f7c15a2340 100644 --- a/Content.Server/Preferences/Managers/ServerPreferencesManager.cs +++ b/Content.Server/Preferences/Managers/ServerPreferencesManager.cs @@ -3,26 +3,21 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Content.Server.Database; -using Content.Server.Humanoid; using Content.Shared.CCVar; -using Content.Shared.Humanoid.Prototypes; using Content.Shared.Preferences; -using Content.Shared.Roles; using Robust.Server.Player; using Robust.Shared.Configuration; using Robust.Shared.Network; using Robust.Shared.Player; -using Robust.Shared.Prototypes; using Robust.Shared.Utility; - namespace Content.Server.Preferences.Managers { /// /// Sends before the client joins the lobby. /// Receives and at any time. /// - public sealed class ServerPreferencesManager : IServerPreferencesManager + public sealed class ServerPreferencesManager : IServerPreferencesManager, IPostInjectInit { [Dependency] private readonly IServerNetManager _netManager = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; @@ -30,6 +25,7 @@ namespace Content.Server.Preferences.Managers [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IDependencyCollection _dependencies = default!; [Dependency] private readonly ILogManager _log = default!; + [Dependency] private readonly UserDbDataManager _userDb = default!; // Cache player prefs on the server so we don't need as much async hell related to them. private readonly Dictionary _cachedPlayerPrefs = @@ -326,5 +322,12 @@ namespace Content.Server.Preferences.Managers public bool PrefsLoaded; public PlayerPreferences? Prefs; } + + void IPostInjectInit.PostInject() + { + _userDb.AddOnLoadPlayer(LoadData); + _userDb.AddOnFinishLoad(FinishLoad); + _userDb.AddOnPlayerDisconnect(OnClientDisconnected); + } } } diff --git a/Content.Server/Station/Events/StationJobsGetCandidatesEvent.cs b/Content.Server/Station/Events/StationJobsGetCandidatesEvent.cs new file mode 100644 index 0000000000..58d860b1e1 --- /dev/null +++ b/Content.Server/Station/Events/StationJobsGetCandidatesEvent.cs @@ -0,0 +1,8 @@ +using Content.Shared.Roles; +using Robust.Shared.Network; +using Robust.Shared.Prototypes; + +namespace Content.Server.Station.Events; + +[ByRefEvent] +public readonly record struct StationJobsGetCandidatesEvent(NetUserId Player, List> Jobs); diff --git a/Content.Server/Station/Systems/StationJobsSystem.Roundstart.cs b/Content.Server/Station/Systems/StationJobsSystem.Roundstart.cs index 4b7cd64961..c3c3865c7b 100644 --- a/Content.Server/Station/Systems/StationJobsSystem.Roundstart.cs +++ b/Content.Server/Station/Systems/StationJobsSystem.Roundstart.cs @@ -2,6 +2,7 @@ using System.Linq; using Content.Server.Administration.Managers; using Content.Server.Players.PlayTimeTracking; using Content.Server.Station.Components; +using Content.Server.Station.Events; using Content.Shared.Preferences; using Content.Shared.Roles; using Robust.Shared.Network; @@ -342,8 +343,9 @@ public sealed partial class StationJobsSystem foreach (var (player, profile) in profiles) { var roleBans = _banManager.GetJobBans(player); - var profileJobs = profile.JobPriorities.Keys.ToList(); - _playTime.RemoveDisallowedJobs(player, ref profileJobs); + var profileJobs = profile.JobPriorities.Keys.Select(k => new ProtoId(k)).ToList(); + var ev = new StationJobsGetCandidatesEvent(player, profileJobs); + RaiseLocalEvent(ref ev); List? availableJobs = null; @@ -354,7 +356,7 @@ public sealed partial class StationJobsSystem if (!(priority == selectedPriority || selectedPriority is null)) continue; - if (!_prototypeManager.TryIndex(jobId, out JobPrototype? job)) + if (!_prototypeManager.TryIndex(jobId, out var job)) continue; if (weight is not null && job.Weight != weight.Value) diff --git a/Content.Server/Station/Systems/StationJobsSystem.cs b/Content.Server/Station/Systems/StationJobsSystem.cs index debac8902e..3bfa815af1 100644 --- a/Content.Server/Station/Systems/StationJobsSystem.cs +++ b/Content.Server/Station/Systems/StationJobsSystem.cs @@ -428,7 +428,7 @@ public sealed partial class StationJobsSystem : EntitySystem /// Whether or not to pick from the overflow list. /// A set of disallowed jobs, if any. /// The selected job, if any. - public string? PickBestAvailableJobWithPriority(EntityUid station, IReadOnlyDictionary jobPriorities, bool pickOverflows, IReadOnlySet? disallowedJobs = null) + public string? PickBestAvailableJobWithPriority(EntityUid station, IReadOnlyDictionary jobPriorities, bool pickOverflows, IReadOnlySet>? disallowedJobs = null) { if (station == EntityUid.Invalid) return null; diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index 5ca34945d8..f41e0a1e6f 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -225,6 +225,12 @@ namespace Content.Shared.CCVar public static readonly CVarDef GameRoleTimers = CVarDef.Create("game.role_timers", true, CVar.SERVER | CVar.REPLICATED); + /// + /// If roles should be restricted based on whether or not they are whitelisted. + /// + public static readonly CVarDef + GameRoleWhitelist = CVarDef.Create("game.role_whitelist", true, CVar.SERVER | CVar.REPLICATED); + /// /// Whether or not disconnecting inside of a cryopod should remove the character or just store them until they reconnect. /// diff --git a/Content.Shared/Players/JobWhitelist/MsgJobWhitelist.cs b/Content.Shared/Players/JobWhitelist/MsgJobWhitelist.cs new file mode 100644 index 0000000000..8347e2d706 --- /dev/null +++ b/Content.Shared/Players/JobWhitelist/MsgJobWhitelist.cs @@ -0,0 +1,33 @@ +using Lidgren.Network; +using Robust.Shared.Network; +using Robust.Shared.Serialization; + +namespace Content.Shared.Players.JobWhitelist; + +public sealed class MsgJobWhitelist : NetMessage +{ + public override MsgGroups MsgGroup => MsgGroups.EntityEvent; + + public HashSet Whitelist = new(); + + public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer) + { + var count = buffer.ReadVariableInt32(); + Whitelist.EnsureCapacity(count); + + for (var i = 0; i < count; i++) + { + Whitelist.Add(buffer.ReadString()); + } + } + + public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer) + { + buffer.WriteVariableInt32(Whitelist.Count); + + foreach (var ban in Whitelist) + { + buffer.Write(ban); + } + } +} diff --git a/Content.Shared/Roles/JobPrototype.cs b/Content.Shared/Roles/JobPrototype.cs index 34a8ce64bf..2959b56497 100644 --- a/Content.Shared/Roles/JobPrototype.cs +++ b/Content.Shared/Roles/JobPrototype.cs @@ -1,10 +1,8 @@ using Content.Shared.Access; using Content.Shared.Players.PlayTimeTracking; -using Content.Shared.Roles; using Content.Shared.StatusIcon; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; namespace Content.Shared.Roles { @@ -116,6 +114,9 @@ namespace Content.Shared.Roles [DataField("extendedAccessGroups")] public IReadOnlyCollection> ExtendedAccessGroups { get; private set; } = Array.Empty>(); + + [DataField] + public bool Whitelisted; } /// diff --git a/Resources/Locale/en-US/commands/job-whitelist-command.ftl b/Resources/Locale/en-US/commands/job-whitelist-command.ftl new file mode 100644 index 0000000000..7fa20f03de --- /dev/null +++ b/Resources/Locale/en-US/commands/job-whitelist-command.ftl @@ -0,0 +1,20 @@ +cmd-jobwhitelist-job-does-not-exist = Job {$job} does not exist. +cmd-jobwhitelist-player-not-found = Player {$player} not found. +cmd-jobwhitelist-hint-player = [player] +cmd-jobwhitelist-hint-job = [job] + +cmd-jobwhitelistadd-desc = Lets a player play a whitelisted job. +cmd-jobwhitelistadd-help = Usage: jobwhitelistadd +cmd-jobwhitelistadd-already-whitelisted = {$player} is already whitelisted to play as {$jobId} .({$jobName}). +cmd-jobwhitelistadd-added = Added {$player} to the {$jobId} ({$jobName}) whitelist. + +cmd-jobwhitelistget-desc = Gets all the jobs that a player has been whitelisted for. +cmd-jobwhitelistget-help = Usage: jobwhitelistadd +cmd-jobwhitelistget-whitelisted-none = Player {$player} is not whitelisted for any jobs. +cmd-jobwhitelistget-whitelisted-for = "Player {$player} is whitelisted for: +{$jobs}" + +cmd-jobwhitelistremove-desc = Removes a player's ability to play a whitelisted job. +cmd-jobwhitelistremove-help = Usage: jobwhitelistadd +cmd-jobwhitelistremove-was-not-whitelisted = {$player} was not whitelisted to play as {$jobId} ({$jobName}). +cmd-jobwhitelistremove-removed = Removed {$player} from the whitelist for {$jobId} ({$jobName}). diff --git a/Resources/Locale/en-US/job/role-whitelist.ftl b/Resources/Locale/en-US/job/role-whitelist.ftl new file mode 100644 index 0000000000..3149f182b6 --- /dev/null +++ b/Resources/Locale/en-US/job/role-whitelist.ftl @@ -0,0 +1 @@ +role-not-whitelisted = You are not whitelisted to play this role. From be6f55a09013ce88d7b57518ff283df0977aded9 Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Sat, 1 Jun 2024 11:29:17 -0400 Subject: [PATCH 69/75] Clean up store system (#28463) --- .../Store/Ui/StoreBoundUserInterface.cs | 24 +++----- Content.Client/Store/Ui/StoreMenu.xaml.cs | 5 +- .../GameTicking/Rules/NukeopsRuleSystem.cs | 1 + .../Implants/SubdermalImplantSystem.cs | 1 + Content.Server/PDA/PdaSystem.cs | 8 ++- Content.Server/PDA/Ringer/RingerSystem.cs | 1 + .../Revenant/EntitySystems/RevenantSystem.cs | 1 + .../Store/Conditions/BuyBeforeCondition.cs | 1 + .../Store/Systems/StoreSystem.Command.cs | 4 +- .../Store/Systems/StoreSystem.Listings.cs | 11 +++- .../Store/Systems/StoreSystem.Refund.cs | 1 + .../Store/Systems/StoreSystem.Ui.cs | 9 +-- Content.Server/Store/Systems/StoreSystem.cs | 42 +------------- .../SurplusBundle/SurplusBundleComponent.cs | 13 +---- .../SurplusBundle/SurplusBundleSystem.cs | 57 ++++++++---------- .../Traitor/Uplink/UplinkComponent.cs | 7 +++ Content.Server/Traitor/Uplink/UplinkSystem.cs | 18 +----- .../Store/Components/StoreComponent.cs | 34 +++++------ Content.Shared/Store/StoreUi.cs | 14 ----- Resources/Locale/en-US/store/store.ftl | 3 + .../Catalog/Fills/Crates/syndicate.yml | 4 +- .../Entities/Objects/Devices/pda.yml | 2 +- .../Entities/Objects/Magic/books.yml | 6 +- .../Objects/Misc/subdermal_implants.yml | 3 +- .../Entities/Objects/Specific/syndicate.yml | 8 +-- Resources/Prototypes/Store/presets.yml | 58 +++++++++++-------- 26 files changed, 127 insertions(+), 209 deletions(-) create mode 100644 Content.Server/Traitor/Uplink/UplinkComponent.cs rename {Content.Server => Content.Shared}/Store/Components/StoreComponent.cs (72%) diff --git a/Content.Client/Store/Ui/StoreBoundUserInterface.cs b/Content.Client/Store/Ui/StoreBoundUserInterface.cs index 88ad0e3de8..0010aedd96 100644 --- a/Content.Client/Store/Ui/StoreBoundUserInterface.cs +++ b/Content.Client/Store/Ui/StoreBoundUserInterface.cs @@ -1,6 +1,7 @@ using Content.Shared.Store; using JetBrains.Annotations; using System.Linq; +using Content.Shared.Store.Components; using Robust.Shared.Prototypes; namespace Content.Client.Store.Ui; @@ -13,9 +14,6 @@ public sealed class StoreBoundUserInterface : BoundUserInterface [ViewVariables] private StoreMenu? _menu; - [ViewVariables] - private string _windowName = Loc.GetString("store-ui-default-title"); - [ViewVariables] private string _search = string.Empty; @@ -28,7 +26,9 @@ public sealed class StoreBoundUserInterface : BoundUserInterface protected override void Open() { - _menu = new StoreMenu(_windowName); + _menu = new StoreMenu(); + if (EntMan.TryGetComponent(Owner, out var store)) + _menu.Title = Loc.GetString(store.Name); _menu.OpenCentered(); _menu.OnClose += Close; @@ -64,25 +64,15 @@ public sealed class StoreBoundUserInterface : BoundUserInterface { base.UpdateState(state); - if (_menu == null) - return; - switch (state) { case StoreUpdateState msg: _listings = msg.Listings; - _menu.UpdateBalance(msg.Balance); + _menu?.UpdateBalance(msg.Balance); UpdateListingsWithSearchFilter(); - _menu.SetFooterVisibility(msg.ShowFooter); - _menu.UpdateRefund(msg.AllowRefund); - break; - case StoreInitializeState msg: - _windowName = msg.Name; - if (_menu != null && _menu.Window != null) - { - _menu.Window.Title = msg.Name; - } + _menu?.SetFooterVisibility(msg.ShowFooter); + _menu?.UpdateRefund(msg.AllowRefund); break; } } diff --git a/Content.Client/Store/Ui/StoreMenu.xaml.cs b/Content.Client/Store/Ui/StoreMenu.xaml.cs index b7a2c285fe..388b31291c 100644 --- a/Content.Client/Store/Ui/StoreMenu.xaml.cs +++ b/Content.Client/Store/Ui/StoreMenu.xaml.cs @@ -32,7 +32,7 @@ public sealed partial class StoreMenu : DefaultWindow private List _cachedListings = new(); - public StoreMenu(string name) + public StoreMenu() { RobustXamlLoader.Load(this); IoCManager.InjectDependencies(this); @@ -40,9 +40,6 @@ public sealed partial class StoreMenu : DefaultWindow WithdrawButton.OnButtonDown += OnWithdrawButtonDown; RefundButton.OnButtonDown += OnRefundButtonDown; SearchBar.OnTextChanged += _ => SearchTextUpdated?.Invoke(this, SearchBar.Text); - - if (Window != null) - Window.Title = name; } public void UpdateBalance(Dictionary, FixedPoint2> balance) diff --git a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs index d6f1c3c619..1b62778d75 100644 --- a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs @@ -25,6 +25,7 @@ using Robust.Shared.Random; using Robust.Shared.Utility; using System.Linq; using Content.Server.GameTicking.Components; +using Content.Shared.Store.Components; namespace Content.Server.GameTicking.Rules; diff --git a/Content.Server/Implants/SubdermalImplantSystem.cs b/Content.Server/Implants/SubdermalImplantSystem.cs index e8af08b2eb..88c5fb9459 100644 --- a/Content.Server/Implants/SubdermalImplantSystem.cs +++ b/Content.Server/Implants/SubdermalImplantSystem.cs @@ -20,6 +20,7 @@ using Robust.Shared.Random; using System.Numerics; using Content.Shared.Movement.Pulling.Components; using Content.Shared.Movement.Pulling.Systems; +using Content.Shared.Store.Components; using Robust.Shared.Collections; using Robust.Shared.Map.Components; diff --git a/Content.Server/PDA/PdaSystem.cs b/Content.Server/PDA/PdaSystem.cs index d4934ee24e..43bf571eb4 100644 --- a/Content.Server/PDA/PdaSystem.cs +++ b/Content.Server/PDA/PdaSystem.cs @@ -8,6 +8,7 @@ using Content.Server.PDA.Ringer; using Content.Server.Station.Systems; using Content.Server.Store.Components; using Content.Server.Store.Systems; +using Content.Server.Traitor.Uplink; using Content.Shared.Access.Components; using Content.Shared.CartridgeLoader; using Content.Shared.Chat; @@ -15,6 +16,7 @@ using Content.Shared.Light; using Content.Shared.Light.Components; using Content.Shared.Light.EntitySystems; using Content.Shared.PDA; +using Content.Shared.Store.Components; using Robust.Server.Containers; using Robust.Server.GameObjects; using Robust.Shared.Containers; @@ -152,7 +154,7 @@ namespace Content.Server.PDA var address = GetDeviceNetAddress(uid); var hasInstrument = HasComp(uid); - var showUplink = HasComp(uid) && IsUnlocked(uid); + var showUplink = HasComp(uid) && IsUnlocked(uid); UpdateStationName(uid, pda); UpdateAlertLevel(uid, pda); @@ -237,8 +239,8 @@ namespace Content.Server.PDA return; // check if its locked again to prevent malicious clients opening locked uplinks - if (TryComp(uid, out var store) && IsUnlocked(uid)) - _store.ToggleUi(msg.Actor, uid, store); + if (HasComp(uid) && IsUnlocked(uid)) + _store.ToggleUi(msg.Actor, uid); } private void OnUiMessage(EntityUid uid, PdaComponent pda, PdaLockUplinkMessage msg) diff --git a/Content.Server/PDA/Ringer/RingerSystem.cs b/Content.Server/PDA/Ringer/RingerSystem.cs index 47ae41896e..e15dcfaa2b 100644 --- a/Content.Server/PDA/Ringer/RingerSystem.cs +++ b/Content.Server/PDA/Ringer/RingerSystem.cs @@ -6,6 +6,7 @@ using Content.Shared.PDA; using Content.Shared.PDA.Ringer; using Content.Shared.Popups; using Content.Shared.Store; +using Content.Shared.Store.Components; using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.Network; diff --git a/Content.Server/Revenant/EntitySystems/RevenantSystem.cs b/Content.Server/Revenant/EntitySystems/RevenantSystem.cs index c390432f3a..a05105662d 100644 --- a/Content.Server/Revenant/EntitySystems/RevenantSystem.cs +++ b/Content.Server/Revenant/EntitySystems/RevenantSystem.cs @@ -17,6 +17,7 @@ using Content.Shared.Popups; using Content.Shared.Revenant; using Content.Shared.Revenant.Components; using Content.Shared.StatusEffect; +using Content.Shared.Store.Components; using Content.Shared.Stunnable; using Content.Shared.Tag; using Robust.Server.GameObjects; diff --git a/Content.Server/Store/Conditions/BuyBeforeCondition.cs b/Content.Server/Store/Conditions/BuyBeforeCondition.cs index 132f353439..3f0c2de2e1 100644 --- a/Content.Server/Store/Conditions/BuyBeforeCondition.cs +++ b/Content.Server/Store/Conditions/BuyBeforeCondition.cs @@ -1,6 +1,7 @@ using Content.Server.Store.Components; using Content.Server.Store.Systems; using Content.Shared.Store; +using Content.Shared.Store.Components; using Robust.Shared.Prototypes; namespace Content.Server.Store.Conditions; diff --git a/Content.Server/Store/Systems/StoreSystem.Command.cs b/Content.Server/Store/Systems/StoreSystem.Command.cs index d259da2c95..5ad361eb42 100644 --- a/Content.Server/Store/Systems/StoreSystem.Command.cs +++ b/Content.Server/Store/Systems/StoreSystem.Command.cs @@ -1,7 +1,9 @@ +using System.Linq; using Content.Server.Store.Components; using Content.Shared.FixedPoint; using Content.Server.Administration; using Content.Shared.Administration; +using Content.Shared.Store.Components; using Robust.Shared.Console; namespace Content.Server.Store.Systems; @@ -58,7 +60,7 @@ public sealed partial class StoreSystem if (args.Length == 2 && NetEntity.TryParse(args[0], out var uidNet) && TryGetEntity(uidNet, out var uid)) { if (TryComp(uid, out var store)) - return CompletionResult.FromHintOptions(store.CurrencyWhitelist, ""); + return CompletionResult.FromHintOptions(store.CurrencyWhitelist.Select(p => p.ToString()), ""); } return CompletionResult.Empty; diff --git a/Content.Server/Store/Systems/StoreSystem.Listings.cs b/Content.Server/Store/Systems/StoreSystem.Listings.cs index a56d9640d3..10b53a7c94 100644 --- a/Content.Server/Store/Systems/StoreSystem.Listings.cs +++ b/Content.Server/Store/Systems/StoreSystem.Listings.cs @@ -1,5 +1,6 @@ -using Content.Server.Store.Components; using Content.Shared.Store; +using Content.Shared.Store.Components; +using Robust.Shared.Prototypes; namespace Content.Server.Store.Systems; @@ -80,7 +81,11 @@ public sealed partial class StoreSystem /// What categories to filter by. /// The physial entity of the store. Can be null. /// The available listings. - public IEnumerable GetAvailableListings(EntityUid buyer, HashSet? listings, HashSet categories, EntityUid? storeEntity = null) + public IEnumerable GetAvailableListings( + EntityUid buyer, + HashSet? listings, + HashSet> categories, + EntityUid? storeEntity = null) { listings ??= GetAllListings(); @@ -117,7 +122,7 @@ public sealed partial class StoreSystem /// The listing itself. /// The categories to check through. /// If the listing was present in one of the categories. - public bool ListingHasCategory(ListingData listing, HashSet categories) + public bool ListingHasCategory(ListingData listing, HashSet> categories) { foreach (var cat in categories) { diff --git a/Content.Server/Store/Systems/StoreSystem.Refund.cs b/Content.Server/Store/Systems/StoreSystem.Refund.cs index 5a8be4be2b..4e823582e6 100644 --- a/Content.Server/Store/Systems/StoreSystem.Refund.cs +++ b/Content.Server/Store/Systems/StoreSystem.Refund.cs @@ -1,4 +1,5 @@ using Content.Server.Store.Components; +using Content.Shared.Store.Components; using Robust.Shared.Containers; namespace Content.Server.Store.Systems; diff --git a/Content.Server/Store/Systems/StoreSystem.Ui.cs b/Content.Server/Store/Systems/StoreSystem.Ui.cs index 0a1a8d19f3..983d68d0af 100644 --- a/Content.Server/Store/Systems/StoreSystem.Ui.cs +++ b/Content.Server/Store/Systems/StoreSystem.Ui.cs @@ -10,6 +10,7 @@ using Content.Shared.FixedPoint; using Content.Shared.Hands.EntitySystems; using Content.Shared.Mind; using Content.Shared.Store; +using Content.Shared.Store.Components; using Content.Shared.UserInterface; using Robust.Server.GameObjects; using Robust.Shared.Audio.Systems; @@ -82,16 +83,11 @@ public sealed partial class StoreSystem /// The person who if opening the store ui. Listings are filtered based on this. /// The store entity itself /// The store component being refreshed. - /// public void UpdateUserInterface(EntityUid? user, EntityUid store, StoreComponent? component = null) { if (!Resolve(store, ref component)) return; - // TODO: Why is the state not being set unless this? - if (!_ui.HasUi(store, StoreUiKey.Key)) - return; - //this is the person who will be passed into logic for all listing filtering. if (user != null) //if we have no "buyer" for this update, then don't update the listings { @@ -259,7 +255,8 @@ public sealed partial class StoreSystem } //log dat shit. - _admin.Add(LogType.StorePurchase, LogImpact.Low, + _admin.Add(LogType.StorePurchase, + LogImpact.Low, $"{ToPrettyString(buyer):player} purchased listing \"{ListingLocalisationHelpers.GetLocalisedNameOrEntityName(listing, _prototypeManager)}\" from {ToPrettyString(uid)}"); listing.PurchaseAmount++; //track how many times something has been purchased diff --git a/Content.Server/Store/Systems/StoreSystem.cs b/Content.Server/Store/Systems/StoreSystem.cs index e310194778..0fd92cfb96 100644 --- a/Content.Server/Store/Systems/StoreSystem.cs +++ b/Content.Server/Store/Systems/StoreSystem.cs @@ -5,11 +5,10 @@ using Content.Shared.Implants.Components; using Content.Shared.Interaction; using Content.Shared.Popups; using Content.Shared.Stacks; -using Content.Shared.Store; using JetBrains.Annotations; -using Robust.Server.GameObjects; using Robust.Shared.Prototypes; using System.Linq; +using Content.Shared.Store.Components; using Robust.Shared.Utility; namespace Content.Server.Store.Systems; @@ -44,7 +43,6 @@ public sealed partial class StoreSystem : EntitySystem private void OnMapInit(EntityUid uid, StoreComponent component, MapInitEvent args) { RefreshAllListings(component); - InitializeFromPreset(component.Preset, uid, component); component.StartingMap = Transform(uid).MapUid; } @@ -54,7 +52,6 @@ public sealed partial class StoreSystem : EntitySystem if (MetaData(uid).EntityLifeStage == EntityLifeStage.MapInitialized) { RefreshAllListings(component); - InitializeFromPreset(component.Preset, uid, component); } var ev = new StoreAddedEvent(); @@ -167,43 +164,6 @@ public sealed partial class StoreSystem : EntitySystem UpdateUserInterface(null, uid, store); return true; } - - /// - /// Initializes a store based on a preset ID - /// - /// The ID of a store preset prototype - /// - /// The store being initialized - public void InitializeFromPreset(string? preset, EntityUid uid, StoreComponent component) - { - if (preset == null) - return; - - if (!_proto.TryIndex(preset, out var proto)) - return; - - InitializeFromPreset(proto, uid, component); - } - - /// - /// Initializes a store based on a given preset - /// - /// The StorePresetPrototype - /// - /// The store being initialized - public void InitializeFromPreset(StorePresetPrototype preset, EntityUid uid, StoreComponent component) - { - component.Preset = preset.ID; - component.CurrencyWhitelist.UnionWith(preset.CurrencyWhitelist); - component.Categories.UnionWith(preset.Categories); - if (component.Balance == new Dictionary() && preset.InitialBalance != null) //if we don't have a value stored, use the preset - TryAddCurrency(preset.InitialBalance, uid, component); - - if (_ui.HasUi(uid, StoreUiKey.Key)) - { - _ui.SetUiState(uid, StoreUiKey.Key, new StoreInitializeState(preset.StoreName)); - } - } } public sealed class CurrencyInsertAttemptEvent : CancellableEntityEventArgs diff --git a/Content.Server/Traitor/Uplink/SurplusBundle/SurplusBundleComponent.cs b/Content.Server/Traitor/Uplink/SurplusBundle/SurplusBundleComponent.cs index 47ce68625a..120581cf82 100644 --- a/Content.Server/Traitor/Uplink/SurplusBundle/SurplusBundleComponent.cs +++ b/Content.Server/Traitor/Uplink/SurplusBundle/SurplusBundleComponent.cs @@ -1,6 +1,3 @@ -using Content.Shared.Store; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - namespace Content.Server.Traitor.Uplink.SurplusBundle; /// @@ -12,14 +9,6 @@ public sealed partial class SurplusBundleComponent : Component /// /// Total price of all content inside bundle. /// - [ViewVariables(VVAccess.ReadOnly)] - [DataField("totalPrice")] + [DataField] public int TotalPrice = 20; - - /// - /// The preset that will be used to get all the listings. - /// Currently just defaults to the basic uplink. - /// - [DataField("storePreset", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string StorePreset = "StorePresetUplink"; } diff --git a/Content.Server/Traitor/Uplink/SurplusBundle/SurplusBundleSystem.cs b/Content.Server/Traitor/Uplink/SurplusBundle/SurplusBundleSystem.cs index 5c0a56d346..759cad5ded 100644 --- a/Content.Server/Traitor/Uplink/SurplusBundle/SurplusBundleSystem.cs +++ b/Content.Server/Traitor/Uplink/SurplusBundle/SurplusBundleSystem.cs @@ -3,76 +3,67 @@ using Content.Server.Storage.EntitySystems; using Content.Server.Store.Systems; using Content.Shared.FixedPoint; using Content.Shared.Store; -using Robust.Shared.Prototypes; +using Content.Shared.Store.Components; using Robust.Shared.Random; namespace Content.Server.Traitor.Uplink.SurplusBundle; public sealed class SurplusBundleSystem : EntitySystem { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly EntityStorageSystem _entityStorage = default!; [Dependency] private readonly StoreSystem _store = default!; - private ListingData[] _listings = default!; - public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(OnMapInit); - - SubscribeLocalEvent(OnInit); - } - - private void OnInit(EntityUid uid, SurplusBundleComponent component, ComponentInit args) - { - var storePreset = _prototypeManager.Index(component.StorePreset); - - _listings = _store.GetAvailableListings(uid, null, storePreset.Categories).ToArray(); - - Array.Sort(_listings, (a, b) => (int) (b.Cost.Values.Sum() - a.Cost.Values.Sum())); //this might get weird with multicurrency but don't think about it } private void OnMapInit(EntityUid uid, SurplusBundleComponent component, MapInitEvent args) { - FillStorage(uid, component); - } - - private void FillStorage(EntityUid uid, SurplusBundleComponent? component = null) - { - if (!Resolve(uid, ref component)) + if (!TryComp(uid, out var store)) return; - var cords = Transform(uid).Coordinates; + FillStorage((uid, component, store)); + } - var content = GetRandomContent(component.TotalPrice); + private void FillStorage(Entity ent) + { + var cords = Transform(ent).Coordinates; + var content = GetRandomContent(ent); foreach (var item in content) { - var ent = EntityManager.SpawnEntity(item.ProductEntity, cords); - _entityStorage.Insert(ent, uid); + var dode = Spawn(item.ProductEntity, cords); + _entityStorage.Insert(dode, ent); } } // wow, is this leetcode reference? - private List GetRandomContent(FixedPoint2 targetCost) + private List GetRandomContent(Entity ent) { var ret = new List(); - if (_listings.Length == 0) + + var listings = _store.GetAvailableListings(ent, null, ent.Comp2.Categories) + .OrderBy(p => p.Cost.Values.Sum()) + .ToList(); + + if (listings.Count == 0) return ret; var totalCost = FixedPoint2.Zero; var index = 0; - while (totalCost < targetCost) + while (totalCost < ent.Comp1.TotalPrice) { // All data is sorted in price descending order // Find new item with the lowest acceptable price // All expansive items will be before index, all acceptable after - var remainingBudget = targetCost - totalCost; - while (_listings[index].Cost.Values.Sum() > remainingBudget) + var remainingBudget = ent.Comp1.TotalPrice - totalCost; + while (listings[index].Cost.Values.Sum() > remainingBudget) { index++; - if (index >= _listings.Length) + if (index >= listings.Count) { // Looks like no cheap items left // It shouldn't be case for ss14 content @@ -82,8 +73,8 @@ public sealed class SurplusBundleSystem : EntitySystem } // Select random listing and add into crate - var randomIndex = _random.Next(index, _listings.Length); - var randomItem = _listings[randomIndex]; + var randomIndex = _random.Next(index, listings.Count); + var randomItem = listings[randomIndex]; ret.Add(randomItem); totalCost += randomItem.Cost.Values.Sum(); } diff --git a/Content.Server/Traitor/Uplink/UplinkComponent.cs b/Content.Server/Traitor/Uplink/UplinkComponent.cs new file mode 100644 index 0000000000..35f11ce9ef --- /dev/null +++ b/Content.Server/Traitor/Uplink/UplinkComponent.cs @@ -0,0 +1,7 @@ +namespace Content.Server.Traitor.Uplink; + +/// +/// This is used for identifying something as a hidden uplink and showing the UI. +/// +[RegisterComponent] +public sealed partial class UplinkComponent : Component; diff --git a/Content.Server/Traitor/Uplink/UplinkSystem.cs b/Content.Server/Traitor/Uplink/UplinkSystem.cs index 5670e28ec9..7c39f1ed66 100644 --- a/Content.Server/Traitor/Uplink/UplinkSystem.cs +++ b/Content.Server/Traitor/Uplink/UplinkSystem.cs @@ -5,6 +5,7 @@ using Content.Shared.PDA; using Content.Server.Store.Components; using Content.Shared.FixedPoint; using Content.Shared.Store; +using Content.Shared.Store.Components; namespace Content.Server.Traitor.Uplink { @@ -17,18 +18,6 @@ namespace Content.Server.Traitor.Uplink [ValidatePrototypeId] public const string TelecrystalCurrencyPrototype = "Telecrystal"; - /// - /// Gets the amount of TC on an "uplink" - /// Mostly just here for legacy systems based on uplink. - /// - /// - /// the amount of TC - public int GetTCBalance(StoreComponent component) - { - FixedPoint2? tcBalance = component.Balance.GetValueOrDefault(TelecrystalCurrencyPrototype); - return tcBalance?.Int() ?? 0; - } - /// /// Adds an uplink to the target /// @@ -37,7 +26,7 @@ namespace Content.Server.Traitor.Uplink /// The id of the storepreset /// The entity that will actually have the uplink functionality. Defaults to the PDA if null. /// Whether or not the uplink was added successfully - public bool AddUplink(EntityUid user, FixedPoint2? balance, string uplinkPresetId = "StorePresetUplink", EntityUid? uplinkEntity = null) + public bool AddUplink(EntityUid user, FixedPoint2? balance, EntityUid? uplinkEntity = null) { // Try to find target item if (uplinkEntity == null) @@ -47,11 +36,10 @@ namespace Content.Server.Traitor.Uplink return false; } + EnsureComp(uplinkEntity.Value); var store = EnsureComp(uplinkEntity.Value); - _store.InitializeFromPreset(uplinkPresetId, uplinkEntity.Value, store); store.AccountOwner = user; store.Balance.Clear(); - if (balance != null) { store.Balance.Clear(); diff --git a/Content.Server/Store/Components/StoreComponent.cs b/Content.Shared/Store/Components/StoreComponent.cs similarity index 72% rename from Content.Server/Store/Components/StoreComponent.cs rename to Content.Shared/Store/Components/StoreComponent.cs index 0b7dbbea09..223f507971 100644 --- a/Content.Server/Store/Components/StoreComponent.cs +++ b/Content.Shared/Store/Components/StoreComponent.cs @@ -1,46 +1,41 @@ using Content.Shared.FixedPoint; -using Content.Shared.Store; using Robust.Shared.Audio; -using Robust.Shared.Map; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set; -namespace Content.Server.Store.Components; +namespace Content.Shared.Store.Components; /// /// This component manages a store which players can use to purchase different listings /// through the ui. The currency, listings, and categories are defined in yaml. /// -[RegisterComponent] +[RegisterComponent, NetworkedComponent] public sealed partial class StoreComponent : Component { - /// - /// The default preset for the store. Is overriden by default values specified on the component. - /// - [DataField("preset", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? Preset; + [DataField] + public LocId Name = "store-ui-default-title"; /// /// All the listing categories that are available on this store. /// The available listings are partially based on the categories. /// - [DataField("categories", customTypeSerializer: typeof(PrototypeIdHashSetSerializer))] - public HashSet Categories = new(); + [DataField] + public HashSet> Categories = new(); /// /// The total amount of currency that can be used in the store. /// The string represents the ID of te currency prototype, where the /// float is that amount. /// - [ViewVariables(VVAccess.ReadWrite), DataField("balance", customTypeSerializer: typeof(PrototypeIdDictionarySerializer))] - public Dictionary Balance = new(); + [DataField] + public Dictionary, FixedPoint2> Balance = new(); /// /// The list of currencies that can be inserted into this store. /// - [ViewVariables(VVAccess.ReadOnly), DataField("currencyWhitelist", customTypeSerializer: typeof(PrototypeIdHashSetSerializer))] - public HashSet CurrencyWhitelist = new(); + [DataField] + public HashSet> CurrencyWhitelist = new(); /// /// The person who "owns" the store/account. Used if you want the listings to be fixed @@ -52,6 +47,7 @@ public sealed partial class StoreComponent : Component /// /// All listings, including those that aren't available to the buyer /// + [DataField] public HashSet Listings = new(); /// @@ -70,7 +66,7 @@ public sealed partial class StoreComponent : Component /// The total balance spent in this store. Used for refunds. /// [ViewVariables, DataField] - public Dictionary BalanceSpent = new(); + public Dictionary, FixedPoint2> BalanceSpent = new(); /// /// Controls if the store allows refunds @@ -95,7 +91,7 @@ public sealed partial class StoreComponent : Component /// /// The sound played to the buyer when a purchase is succesfully made. /// - [DataField("buySuccessSound")] + [DataField] public SoundSpecifier BuySuccessSound = new SoundPathSpecifier("/Audio/Effects/kaching.ogg"); #endregion } diff --git a/Content.Shared/Store/StoreUi.cs b/Content.Shared/Store/StoreUi.cs index ee4da6991f..59cf1bbbc8 100644 --- a/Content.Shared/Store/StoreUi.cs +++ b/Content.Shared/Store/StoreUi.cs @@ -30,20 +30,6 @@ public sealed class StoreUpdateState : BoundUserInterfaceState } } -/// -/// initializes miscellaneous data about the store. -/// -[Serializable, NetSerializable] -public sealed class StoreInitializeState : BoundUserInterfaceState -{ - public readonly string Name; - - public StoreInitializeState(string name) - { - Name = name; - } -} - [Serializable, NetSerializable] public sealed class StoreRequestUpdateInterfaceMessage : BoundUserInterfaceMessage { diff --git a/Resources/Locale/en-US/store/store.ftl b/Resources/Locale/en-US/store/store.ftl index 997afedfc0..5c1a46339e 100644 --- a/Resources/Locale/en-US/store/store.ftl +++ b/Resources/Locale/en-US/store/store.ftl @@ -8,3 +8,6 @@ store-ui-traitor-warning = Operatives must lock their uplinks after use to avoid store-withdraw-button-ui = Withdraw {$currency} store-ui-button-out-of-stock = {""} (Out of Stock) store-not-account-owner = This {$store} is not bound to you! + +store-preset-name-uplink = Uplink +store-preset-name-spellbook = Spellbook diff --git a/Resources/Prototypes/Catalog/Fills/Crates/syndicate.yml b/Resources/Prototypes/Catalog/Fills/Crates/syndicate.yml index 3f9e909c80..ba97af3925 100644 --- a/Resources/Prototypes/Catalog/Fills/Crates/syndicate.yml +++ b/Resources/Prototypes/Catalog/Fills/Crates/syndicate.yml @@ -1,6 +1,6 @@ - type: entity id: CrateSyndicateSurplusBundle - parent: CrateSyndicate + parent: [ CrateSyndicate, StorePresetUplink ] name: Syndicate surplus crate description: Contains 50 telecrystals worth of completely random Syndicate items. It can be useless junk or really good. components: @@ -24,7 +24,7 @@ - type: entity id: CrateSyndicateSuperSurplusBundle - parent: CrateSyndicate + parent: [ CrateSyndicate, StorePresetUplink ] name: Syndicate super surplus crate description: Contains 125 telecrystals worth of completely random Syndicate items. components: diff --git a/Resources/Prototypes/Entities/Objects/Devices/pda.yml b/Resources/Prototypes/Entities/Objects/Devices/pda.yml index b76ca6a14a..b1a6ab0b8f 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/pda.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/pda.yml @@ -1,6 +1,6 @@ - type: entity abstract: true - parent: BaseItem + parent: [ BaseItem, StorePresetUplink ] #PDA's have uplinks so they have to inherit the data. id: BasePDA name: PDA description: Personal Data Assistant. diff --git a/Resources/Prototypes/Entities/Objects/Magic/books.yml b/Resources/Prototypes/Entities/Objects/Magic/books.yml index 554c5214c1..e47fa00c45 100644 --- a/Resources/Prototypes/Entities/Objects/Magic/books.yml +++ b/Resources/Prototypes/Entities/Objects/Magic/books.yml @@ -25,7 +25,7 @@ id: WizardsGrimoire name: wizards grimoire suffix: Wizard - parent: BaseItem + parent: [ BaseItem, StorePresetSpellbook ] components: - type: Sprite sprite: Objects/Misc/books.rsi @@ -46,7 +46,6 @@ - type: Store refundAllowed: true ownerOnly: true # get your own tome! - preset: StorePresetSpellbook balance: WizCoin: 10 # prices are balanced around this 10 point maximum and how strong the spells are @@ -55,12 +54,11 @@ id: WizardsGrimoireNoRefund name: wizards grimoire suffix: Wizard, No Refund - parent: WizardsGrimoire + parent: [ WizardsGrimoire, StorePresetSpellbook ] components: - type: Store refundAllowed: false ownerOnly: true # get your own tome! - preset: StorePresetSpellbook balance: WizCoin: 10 # prices are balanced around this 10 point maximum and how strong the spells are diff --git a/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml b/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml index c92985c2cb..9690d0bdfe 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml @@ -143,7 +143,7 @@ - Cuffable # useless if you cant be cuffed - type: entity - parent: BaseSubdermalImplant + parent: [ BaseSubdermalImplant, StorePresetUplink ] id: UplinkImplant name: uplink implant description: This implant lets the user access a hidden Syndicate uplink at will. @@ -155,7 +155,6 @@ components: - Hands # prevent mouse buying grenade penguin since its not telepathic - type: Store - preset: StorePresetUplink balance: Telecrystal: 0 - type: UserInterface diff --git a/Resources/Prototypes/Entities/Objects/Specific/syndicate.yml b/Resources/Prototypes/Entities/Objects/Specific/syndicate.yml index 459beeef18..53d4f7953b 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/syndicate.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/syndicate.yml @@ -48,7 +48,7 @@ # Uplinks - type: entity - parent: BaseItem + parent: [ BaseItem, StorePresetUplink ] id: BaseUplinkRadio name: syndicate uplink description: Suspiciously looking old radio... @@ -68,7 +68,6 @@ - type: ActivatableUI key: enum.StoreUiKey.Key - type: Store - preset: StorePresetUplink balance: Telecrystal: 0 @@ -78,7 +77,6 @@ suffix: 20 TC components: - type: Store - preset: StorePresetUplink balance: Telecrystal: 20 @@ -88,7 +86,6 @@ suffix: 25 TC components: - type: Store - preset: StorePresetUplink balance: Telecrystal: 25 @@ -99,7 +96,6 @@ suffix: 40 TC, NukeOps components: - type: Store - preset: StorePresetUplink balance: Telecrystal: 40 - type: Tag @@ -112,7 +108,6 @@ suffix: 60 TC, LoneOps components: - type: Store - preset: StorePresetUplink balance: Telecrystal: 60 - type: Tag @@ -125,6 +120,5 @@ suffix: DEBUG components: - type: Store - preset: StorePresetUplink balance: Telecrystal: 99999 diff --git a/Resources/Prototypes/Store/presets.yml b/Resources/Prototypes/Store/presets.yml index 166c29fe41..762ed68921 100644 --- a/Resources/Prototypes/Store/presets.yml +++ b/Resources/Prototypes/Store/presets.yml @@ -1,29 +1,37 @@ -- type: storePreset +- type: entity id: StorePresetUplink - storeName: Uplink - categories: - - UplinkWeaponry - - UplinkAmmo - - UplinkExplosives - - UplinkChemicals - - UplinkDeception - - UplinkDisruption - - UplinkImplants - - UplinkAllies - - UplinkWearables - - UplinkJob - - UplinkPointless - currencyWhitelist: - - Telecrystal + abstract: true + components: + - type: Store + name: store-preset-name-uplink + categories: + - UplinkWeaponry + - UplinkAmmo + - UplinkExplosives + - UplinkChemicals + - UplinkDeception + - UplinkDisruption + - UplinkImplants + - UplinkAllies + - UplinkWearables + - UplinkJob + - UplinkPointless + currencyWhitelist: + - Telecrystal + balance: + Telecrystal: 0 -- type: storePreset +- type: entity id: StorePresetSpellbook - storeName: Spellbook - categories: - - SpellbookOffensive #Fireball, Rod Form - - SpellbookDefensive #Magic Missile, Wall of Force - - SpellbookUtility #Body Swap, Lich, Teleport, Knock, Polymorph - - SpellbookEquipment #Battlemage Robes, Staff of Locker - - SpellbookEvents #Summon Weapons, Summon Ghosts - currencyWhitelist: + abstract: true + components: + - type: Store + name: store-preset-name-spellbook + categories: + - SpellbookOffensive #Fireball, Rod Form + - SpellbookDefensive #Magic Missile, Wall of Force + - SpellbookUtility #Body Swap, Lich, Teleport, Knock, Polymorph + - SpellbookEquipment #Battlemage Robes, Staff of Locker + - SpellbookEvents #Summon Weapons, Summon Ghosts + currencyWhitelist: - WizCoin From 5bb0c4d6a23232103d9da97e7df9c4bbfce467cc Mon Sep 17 00:00:00 2001 From: beck-thompson <107373427+beck-thompson@users.noreply.github.com> Date: Sat, 1 Jun 2024 10:29:46 -0700 Subject: [PATCH 70/75] Fixed bug where ID card computer defaulted to the atmos as the job icon. (#28462) Fixed ID atmos bug! --- Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs b/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs index 298912e7d5..82f6ebd8b5 100644 --- a/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs +++ b/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs @@ -27,6 +27,9 @@ namespace Content.Client.Access.UI private string? _lastJobTitle; private string? _lastJobProto; + // The job that will be picked if the ID doesn't have a job on the station. + private static ProtoId _defaultJob = "Passenger"; + public IdCardConsoleWindow(IdCardConsoleBoundUserInterface owner, IPrototypeManager prototypeManager, List> accessLevels) { @@ -65,7 +68,6 @@ namespace Content.Client.Access.UI } JobPresetOptionButton.OnItemSelected += SelectJobPreset; - _accessButtons.Populate(accessLevels, prototypeManager); AccessLevelControlContainer.AddChild(_accessButtons); @@ -172,11 +174,15 @@ namespace Content.Client.Access.UI new List>()); var jobIndex = _jobPrototypeIds.IndexOf(state.TargetIdJobPrototype); - if (jobIndex >= 0) + // If the job index is < 0 that means they don't have a job registered in the station records. + // For example, a new ID from a box would have no job index. + if (jobIndex < 0) { - JobPresetOptionButton.SelectId(jobIndex); + jobIndex = _jobPrototypeIds.IndexOf(_defaultJob); } + JobPresetOptionButton.SelectId(jobIndex); + _lastFullName = state.TargetIdFullName; _lastJobTitle = state.TargetIdJobTitle; _lastJobProto = state.TargetIdJobPrototype; From 81a328c543d9d7194570f2d176354aea9554e2a5 Mon Sep 17 00:00:00 2001 From: PJBot Date: Sat, 1 Jun 2024 17:30:52 +0000 Subject: [PATCH 71/75] 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 2a808f3282..449f9bf43b 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +1,4 @@ Entries: -- author: Tayrtahn - changes: - - message: Reflected tranquilizer rounds no longer inject the character who reflected - them. - type: Fix - id: 6160 - time: '2024-03-15T13:57:15.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26141 - author: lzk228 changes: - message: Refill light replacer from box popup now shows properly. @@ -3853,3 +3845,11 @@ id: 6659 time: '2024-06-01T05:51:16.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28424 +- author: Beck Thompson + changes: + - message: ID computer will now select passenger as the default id icon instead + of atmospheric technician. + type: Fix + id: 6660 + time: '2024-06-01T17:29:46.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28462 From a29b6a6894f8197be392bfd1c90085b8ea80f86c Mon Sep 17 00:00:00 2001 From: Tornado Tech <54727692+Tornado-Technology@users.noreply.github.com> Date: Sun, 2 Jun 2024 03:46:35 +1000 Subject: [PATCH 72/75] Clean up new HTNs tasks (#28469) * Clean up new HTNs tasks * Added docs to math operations --- .../NPC/HTN/Preconditions/KeyExistsPrecondition.cs | 7 ++++++- .../NPC/HTN/Preconditions/KeyNotExistsPrecondition.cs | 6 +++++- .../HTN/Preconditions/Math/KeyBoolEqualsPrecondition.cs | 7 ++++--- .../HTN/Preconditions/Math/KeyFloatEqualsPrecondition.cs | 8 ++++++-- .../Preconditions/Math/KeyFloatGreaterPrecondition.cs | 8 ++++++-- .../HTN/Preconditions/Math/KeyFloatLessPrecondition.cs | 8 ++++++-- .../PrimitiveTasks/Operators/Math/AddFloatOperator.cs | 5 +++-- .../HTN/PrimitiveTasks/Operators/Math/SetBoolOperator.cs | 5 +++-- .../PrimitiveTasks/Operators/Math/SetFloatOperator.cs | 5 +++-- .../Operators/Math/SetRandomFloatOperator.cs | 8 +++++--- .../NPC/HTN/PrimitiveTasks/Operators/SayKeyOperator.cs | 9 +++++++-- .../NPC/HTN/PrimitiveTasks/Operators/SpeakOperator.cs | 7 ++++--- 12 files changed, 58 insertions(+), 25 deletions(-) diff --git a/Content.Server/NPC/HTN/Preconditions/KeyExistsPrecondition.cs b/Content.Server/NPC/HTN/Preconditions/KeyExistsPrecondition.cs index 72c4e6367f..69e265f276 100644 --- a/Content.Server/NPC/HTN/Preconditions/KeyExistsPrecondition.cs +++ b/Content.Server/NPC/HTN/Preconditions/KeyExistsPrecondition.cs @@ -1,8 +1,13 @@ namespace Content.Server.NPC.HTN.Preconditions; +/// +/// Checks for the presence of the value by the specified in the . +/// Returns true if there is a value. +/// public sealed partial class KeyExistsPrecondition : HTNPrecondition { - [DataField("key", required: true)] public string Key = string.Empty; + [DataField(required: true), ViewVariables] + public string Key = string.Empty; public override bool IsMet(NPCBlackboard blackboard) { diff --git a/Content.Server/NPC/HTN/Preconditions/KeyNotExistsPrecondition.cs b/Content.Server/NPC/HTN/Preconditions/KeyNotExistsPrecondition.cs index c12663901c..8dc38e442a 100644 --- a/Content.Server/NPC/HTN/Preconditions/KeyNotExistsPrecondition.cs +++ b/Content.Server/NPC/HTN/Preconditions/KeyNotExistsPrecondition.cs @@ -1,8 +1,12 @@ namespace Content.Server.NPC.HTN.Preconditions; +/// +/// Checks if there is no value at the specified in the . +/// Returns true if there is no value. +/// public sealed partial class KeyNotExistsPrecondition : HTNPrecondition { - [DataField(required: true)] + [DataField(required: true), ViewVariables] public string Key = string.Empty; public override bool IsMet(NPCBlackboard blackboard) diff --git a/Content.Server/NPC/HTN/Preconditions/Math/KeyBoolEqualsPrecondition.cs b/Content.Server/NPC/HTN/Preconditions/Math/KeyBoolEqualsPrecondition.cs index 8c7920e8be..2abb351272 100644 --- a/Content.Server/NPC/HTN/Preconditions/Math/KeyBoolEqualsPrecondition.cs +++ b/Content.Server/NPC/HTN/Preconditions/Math/KeyBoolEqualsPrecondition.cs @@ -1,16 +1,17 @@ namespace Content.Server.NPC.HTN.Preconditions.Math; /// -/// Checks for the presence of data in the blackboard and makes a comparison with the specified boolean +/// Checks if there is a bool value for the specified +/// in the and the specified value is equal to the . /// public sealed partial class KeyBoolEqualsPrecondition : HTNPrecondition { [Dependency] private readonly IEntityManager _entManager = default!; - [DataField(required: true)] + [DataField(required: true), ViewVariables] public string Key = string.Empty; - [DataField(required: true)] + [DataField(required: true), ViewVariables(VVAccess.ReadWrite)] public bool Value; public override bool IsMet(NPCBlackboard blackboard) diff --git a/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatEqualsPrecondition.cs b/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatEqualsPrecondition.cs index 802fdaf2b9..0f7e2cca2a 100644 --- a/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatEqualsPrecondition.cs +++ b/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatEqualsPrecondition.cs @@ -1,13 +1,17 @@ namespace Content.Server.NPC.HTN.Preconditions.Math; +/// +/// Checks if there is a float value for the specified +/// in the and the specified value is equal to the . +/// public sealed partial class KeyFloatEqualsPrecondition : HTNPrecondition { [Dependency] private readonly IEntityManager _entManager = default!; - [DataField(required: true)] + [DataField(required: true), ViewVariables] public string Key = string.Empty; - [DataField(required: true)] + [DataField(required: true), ViewVariables(VVAccess.ReadWrite)] public float Value; public override bool IsMet(NPCBlackboard blackboard) diff --git a/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatGreaterPrecondition.cs b/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatGreaterPrecondition.cs index 3a9ac36698..b3a27a7d5d 100644 --- a/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatGreaterPrecondition.cs +++ b/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatGreaterPrecondition.cs @@ -1,13 +1,17 @@ namespace Content.Server.NPC.HTN.Preconditions.Math; +/// +/// Checks if there is a float value for the specified +/// in the and the specified value is greater then . +/// public sealed partial class KeyFloatGreaterPrecondition : HTNPrecondition { [Dependency] private readonly IEntityManager _entManager = default!; - [DataField(required: true)] + [DataField(required: true), ViewVariables] public string Key = string.Empty; - [DataField(required: true)] + [DataField(required: true), ViewVariables(VVAccess.ReadWrite)] public float Value; public override bool IsMet(NPCBlackboard blackboard) diff --git a/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatLessPrecondition.cs b/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatLessPrecondition.cs index 5cd51d7a7c..c5eb35eebc 100644 --- a/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatLessPrecondition.cs +++ b/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatLessPrecondition.cs @@ -1,13 +1,17 @@ namespace Content.Server.NPC.HTN.Preconditions.Math; +/// +/// Checks if there is a float value for the specified +/// in the and the specified value is less then . +/// public sealed partial class KeyFloatLessPrecondition : HTNPrecondition { [Dependency] private readonly IEntityManager _entManager = default!; - [DataField(required: true)] + [DataField(required: true), ViewVariables] public string Key = string.Empty; - [DataField(required: true)] + [DataField(required: true), ViewVariables(VVAccess.ReadWrite)] public float Value; public override bool IsMet(NPCBlackboard blackboard) diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/AddFloatOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/AddFloatOperator.cs index 00404517c9..f8ed20544e 100644 --- a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/AddFloatOperator.cs +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/AddFloatOperator.cs @@ -4,13 +4,14 @@ using System.Threading.Tasks; namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Math; /// -/// Gets the key, and adds the value to that float +/// Added to float value for the +/// specified in the . /// public sealed partial class AddFloatOperator : HTNOperator { [Dependency] private readonly IEntityManager _entManager = default!; - [DataField(required: true)] + [DataField(required: true), ViewVariables] public string TargetKey = string.Empty; [DataField, ViewVariables(VVAccess.ReadWrite)] diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetBoolOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetBoolOperator.cs index a40b96798d..f168326af7 100644 --- a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetBoolOperator.cs +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetBoolOperator.cs @@ -4,11 +4,12 @@ using System.Threading.Tasks; namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Math; /// -/// Just sets a blackboard key to a bool +/// Set to bool value for the +/// specified in the . /// public sealed partial class SetBoolOperator : HTNOperator { - [DataField(required: true)] + [DataField(required: true), ViewVariables] public string TargetKey = string.Empty; [DataField, ViewVariables(VVAccess.ReadWrite)] diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetFloatOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetFloatOperator.cs index 76842b431f..f9b5e54fe3 100644 --- a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetFloatOperator.cs +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetFloatOperator.cs @@ -4,11 +4,12 @@ using System.Threading.Tasks; namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Math; /// -/// Just sets a blackboard key to a float +/// Set to float value for the +/// specified in the . /// public sealed partial class SetFloatOperator : HTNOperator { - [DataField(required: true)] + [DataField(required: true), ViewVariables] public string TargetKey = string.Empty; [DataField, ViewVariables(VVAccess.ReadWrite)] diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetRandomFloatOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetRandomFloatOperator.cs index 999756f1f7..edcab6baff 100644 --- a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetRandomFloatOperator.cs +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetRandomFloatOperator.cs @@ -5,20 +5,22 @@ using Robust.Shared.Random; namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Math; /// -/// Sets a random float from MinAmount to MaxAmount to blackboard +/// Set random float value between and +/// specified +/// in the . /// public sealed partial class SetRandomFloatOperator : HTNOperator { [Dependency] private readonly IRobustRandom _random = default!; - [DataField(required: true)] + [DataField(required: true), ViewVariables] public string TargetKey = string.Empty; [DataField, ViewVariables(VVAccess.ReadWrite)] public float MaxAmount = 1f; [DataField, ViewVariables(VVAccess.ReadWrite)] - public float MinAmount = 0f; + public float MinAmount; public override async Task<(bool Valid, Dictionary? Effects)> Plan(NPCBlackboard blackboard, CancellationToken cancelToken) diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SayKeyOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SayKeyOperator.cs index d1c7d61915..558b1fc04d 100644 --- a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SayKeyOperator.cs +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SayKeyOperator.cs @@ -20,7 +20,8 @@ public sealed partial class SayKeyOperator : HTNOperator public override void Initialize(IEntitySystemManager sysManager) { base.Initialize(sysManager); - _chat = IoCManager.Resolve().GetEntitySystem(); + + _chat = sysManager.GetEntitySystem(); } public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime) @@ -28,8 +29,12 @@ public sealed partial class SayKeyOperator : HTNOperator if (!blackboard.TryGetValue(Key, out var value, _entManager)) return HTNOperatorStatus.Failed; + var @string = value.ToString(); + if (@string is not { }) + return HTNOperatorStatus.Failed; + var speaker = blackboard.GetValue(NPCBlackboard.Owner); - _chat.TrySendInGameICMessage(speaker, value.ToString() ?? "Oh no...", InGameICChatType.Speak, hideChat: Hidden, hideLog: Hidden); + _chat.TrySendInGameICMessage(speaker, @string, InGameICChatType.Speak, hideChat: Hidden, hideLog: Hidden); return base.Update(blackboard, frameTime); } diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SpeakOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SpeakOperator.cs index cf07831959..8a4c655a39 100644 --- a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SpeakOperator.cs +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SpeakOperator.cs @@ -6,7 +6,7 @@ public sealed partial class SpeakOperator : HTNOperator { private ChatSystem _chat = default!; - [DataField("speech", required: true)] + [DataField(required: true)] public string Speech = string.Empty; /// @@ -18,14 +18,15 @@ public sealed partial class SpeakOperator : HTNOperator public override void Initialize(IEntitySystemManager sysManager) { base.Initialize(sysManager); - _chat = IoCManager.Resolve().GetEntitySystem(); + + _chat = sysManager.GetEntitySystem(); } public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime) { var speaker = blackboard.GetValue(NPCBlackboard.Owner); - _chat.TrySendInGameICMessage(speaker, Loc.GetString(Speech), InGameICChatType.Speak, hideChat: Hidden, hideLog: Hidden); + return base.Update(blackboard, frameTime); } } From 09256cfaa572d326840edaea1dc0268cd9def5e3 Mon Sep 17 00:00:00 2001 From: AJCM-git <60196617+AJCM-git@users.noreply.github.com> Date: Sat, 1 Jun 2024 13:49:28 -0400 Subject: [PATCH 73/75] Makes machine parts stackable, removes unused field in stack prototypes (#28434) * Makes machine parts stacks, removes unused field in stack prototypes * forgor * Fix tests * Fixes lathe construction. Yes. This sucks but there's no better way that doesnt involve refactoring machine parts completely * detail * a --- .../Tests/MaterialArbitrageTest.cs | 2 +- .../Construction/MachineFrameSystem.cs | 59 +++++++++++--- Content.Server/Stack/StackSystem.cs | 2 +- .../Construction/MachinePartSystem.cs | 4 +- Content.Shared/Materials/MaterialPrototype.cs | 2 +- Content.Shared/Stacks/StackPrototype.cs | 19 ++--- .../Entities/Objects/Misc/machine_parts.yml | 8 ++ .../Entities/Objects/Tools/fulton.yml | 1 - .../Stacks/Materials/Sheets/glass.yml | 7 -- .../Stacks/Materials/Sheets/metal.yml | 3 - .../Stacks/Materials/Sheets/other.yml | 4 - .../Prototypes/Stacks/Materials/crystals.yml | 1 - .../Prototypes/Stacks/Materials/ingots.yml | 2 - .../Prototypes/Stacks/Materials/materials.yml | 13 ---- Resources/Prototypes/Stacks/Materials/ore.yml | 9 --- .../Prototypes/Stacks/Materials/parts.yml | 1 - .../Prototypes/Stacks/consumable_stacks.yml | 11 --- .../Prototypes/Stacks/engineering_stacks.yml | 2 - .../Prototypes/Stacks/floor_tile_stacks.yml | 76 +------------------ .../Prototypes/Stacks/medical_stacks.yml | 8 -- Resources/Prototypes/Stacks/power_stacks.yml | 3 - .../Prototypes/Stacks/science_stacks.yml | 19 ++++- 22 files changed, 88 insertions(+), 168 deletions(-) diff --git a/Content.IntegrationTests/Tests/MaterialArbitrageTest.cs b/Content.IntegrationTests/Tests/MaterialArbitrageTest.cs index 7f9c02fc13..ed1e554943 100644 --- a/Content.IntegrationTests/Tests/MaterialArbitrageTest.cs +++ b/Content.IntegrationTests/Tests/MaterialArbitrageTest.cs @@ -103,7 +103,7 @@ public sealed class MaterialArbitrageTest continue; var stackProto = protoManager.Index(materialStep.MaterialPrototypeId); - var spawnProto = protoManager.Index(stackProto.Spawn); + var spawnProto = protoManager.Index(stackProto.Spawn); if (!spawnProto.Components.ContainsKey(materialName) || !spawnProto.Components.TryGetValue(compositionName, out var compositionReg)) diff --git a/Content.Server/Construction/MachineFrameSystem.cs b/Content.Server/Construction/MachineFrameSystem.cs index 09d8d413ec..e20c36d849 100644 --- a/Content.Server/Construction/MachineFrameSystem.cs +++ b/Content.Server/Construction/MachineFrameSystem.cs @@ -59,23 +59,27 @@ public sealed class MachineFrameSystem : EntitySystem return; } - // Machine parts cannot currently satisfy stack/component/tag restrictions. Similarly stacks cannot satisfy - // component/tag restrictions. However, there is no reason this cannot be supported in the future. If this - // changes, then RegenerateProgress() also needs to be updated. - // + // If this changes in the future, then RegenerateProgress() also needs to be updated. // Note that one entity is ALLOWED to satisfy more than one kind of component or tag requirements. This is // necessary in order to avoid weird entity-ordering shenanigans in RegenerateProgress(). + var stack = CompOrNull(args.Used); + var machinePart = CompOrNull(args.Used); + if (stack != null && machinePart != null) + { + if (TryInsertPartStack(uid, args.Used, component, machinePart, stack)) + args.Handled = true; + return; + } // Handle parts - if (TryComp(args.Used, out var machinePart)) + if (machinePart != null) { if (TryInsertPart(uid, args.Used, component, machinePart)) args.Handled = true; return; } - // Handle stacks - if (TryComp(args.Used, out var stack)) + if (stack != null) { if (TryInsertStack(uid, args.Used, component, stack)) args.Handled = true; @@ -191,6 +195,44 @@ public sealed class MachineFrameSystem : EntitySystem return true; } + /// Whether or not the function had any effect. Does not indicate success. + private bool TryInsertPartStack(EntityUid uid, EntityUid used, MachineFrameComponent component, MachinePartComponent machinePart, StackComponent stack) + { + if (!component.Requirements.ContainsKey(machinePart.PartType)) + return false; + + var progress = component.Progress[machinePart.PartType]; + var requirement = component.Requirements[machinePart.PartType]; + + var needed = requirement - progress; + if (needed <= 0) + return false; + + var count = stack.Count; + if (count < needed) + { + if (!_container.Insert(used, component.PartContainer)) + return true; + + component.Progress[machinePart.PartType] += count; + return true; + } + + var splitStack = _stack.Split(used, needed, Transform(uid).Coordinates, stack); + + if (splitStack == null) + return false; + + if (!_container.Insert(splitStack.Value, component.PartContainer)) + return true; + + component.Progress[machinePart.PartType] += needed; + if (IsComplete(component)) + _popupSystem.PopupEntity(Loc.GetString("machine-frame-component-on-complete"), uid); + + return true; + } + /// Whether or not the function had any effect. Does not indicate success. private bool TryInsertStack(EntityUid uid, EntityUid used, MachineFrameComponent component, StackComponent stack) { @@ -328,8 +370,6 @@ public sealed class MachineFrameSystem : EntitySystem { if (TryComp(part, out var machinePart)) { - DebugTools.Assert(!HasComp(part)); - // Check this is part of the requirements... if (!component.Requirements.ContainsKey(machinePart.PartType)) continue; @@ -338,7 +378,6 @@ public sealed class MachineFrameSystem : EntitySystem component.Progress[machinePart.PartType] = 1; else component.Progress[machinePart.PartType]++; - continue; } diff --git a/Content.Server/Stack/StackSystem.cs b/Content.Server/Stack/StackSystem.cs index 001093a8dd..e34592b45f 100644 --- a/Content.Server/Stack/StackSystem.cs +++ b/Content.Server/Stack/StackSystem.cs @@ -51,7 +51,7 @@ namespace Content.Server.Stack // Get a prototype ID to spawn the new entity. Null is also valid, although it should rarely be picked... var prototype = _prototypeManager.TryIndex(stack.StackTypeId, out var stackType) - ? stackType.Spawn + ? stackType.Spawn.ToString() : Prototype(uid)?.ID; // Set the output parameter in the event instance to the newly split stack. diff --git a/Content.Shared/Construction/MachinePartSystem.cs b/Content.Shared/Construction/MachinePartSystem.cs index 1a19040b41..359b58c881 100644 --- a/Content.Shared/Construction/MachinePartSystem.cs +++ b/Content.Shared/Construction/MachinePartSystem.cs @@ -87,9 +87,9 @@ namespace Content.Shared.Construction foreach (var (stackId, amount) in comp.MaterialIdRequirements) { var stackProto = _prototype.Index(stackId); + var defaultProto = _prototype.Index(stackProto.Spawn); - if (_prototype.TryIndex(stackProto.Spawn, out var defaultProto) && - defaultProto.TryGetComponent(out var physComp)) + if (defaultProto.TryGetComponent(out var physComp)) { foreach (var (mat, matAmount) in physComp.MaterialComposition) { diff --git a/Content.Shared/Materials/MaterialPrototype.cs b/Content.Shared/Materials/MaterialPrototype.cs index 905a2359d3..5adf13213e 100644 --- a/Content.Shared/Materials/MaterialPrototype.cs +++ b/Content.Shared/Materials/MaterialPrototype.cs @@ -29,7 +29,7 @@ namespace Content.Shared.Materials /// include which stack we should spawn by default. /// [DataField] - public ProtoId? StackEntity; + public EntProtoId? StackEntity; [DataField] public string Name = string.Empty; diff --git a/Content.Shared/Stacks/StackPrototype.cs b/Content.Shared/Stacks/StackPrototype.cs index 28b7da8f2a..f108419a9e 100644 --- a/Content.Shared/Stacks/StackPrototype.cs +++ b/Content.Shared/Stacks/StackPrototype.cs @@ -4,7 +4,7 @@ using Robust.Shared.Utility; namespace Content.Shared.Stacks; -[Prototype("stack")] +[Prototype] public sealed partial class StackPrototype : IPrototype { [ViewVariables] @@ -15,33 +15,26 @@ public sealed partial class StackPrototype : IPrototype /// Human-readable name for this stack type e.g. "Steel" /// /// This is a localization string ID. - [DataField("name")] + [DataField] public string Name { get; private set; } = string.Empty; /// /// An icon that will be used to represent this stack type. /// - [DataField("icon")] + [DataField] public SpriteSpecifier? Icon { get; private set; } /// /// The entity id that will be spawned by default from this stack. /// - [DataField("spawn", required: true, customTypeSerializer:typeof(PrototypeIdSerializer))] - public string Spawn { get; private set; } = string.Empty; + [DataField(required: true)] + public EntProtoId Spawn { get; private set; } = string.Empty; /// /// The maximum amount of things that can be in a stack. /// Can be overriden on /// if null, simply has unlimited max count. /// - [DataField("maxCount")] + [DataField] public int? MaxCount { get; private set; } - - /// - /// The size of an individual unit of this stack. - /// - [DataField("itemSize")] - public int? ItemSize; } - diff --git a/Resources/Prototypes/Entities/Objects/Misc/machine_parts.yml b/Resources/Prototypes/Entities/Objects/Misc/machine_parts.yml index 62a63c80c3..37de294cce 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/machine_parts.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/machine_parts.yml @@ -9,6 +9,8 @@ sprite: Objects/Misc/stock_parts.rsi - type: Item size: Tiny + - type: Stack + count: 1 - type: entity id: CapacitorStockPart @@ -25,6 +27,8 @@ - type: Tag tags: - CapacitorStockPart + - type: Stack + stackType: Capacitor - type: entity id: MicroManipulatorStockPart @@ -38,6 +42,8 @@ - type: MachinePart part: Manipulator rating: 1 + - type: Stack + stackType: MicroManipulator - type: entity id: MatterBinStockPart @@ -51,3 +57,5 @@ - type: MachinePart part: MatterBin rating: 1 + - type: Stack + stackType: MatterBin diff --git a/Resources/Prototypes/Entities/Objects/Tools/fulton.yml b/Resources/Prototypes/Entities/Objects/Tools/fulton.yml index 5255e5f303..cfd0b8f770 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/fulton.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/fulton.yml @@ -7,7 +7,6 @@ state: extraction_pack spawn: Fulton1 maxCount: 10 - itemSize: 2 # Entities - type: entity diff --git a/Resources/Prototypes/Stacks/Materials/Sheets/glass.yml b/Resources/Prototypes/Stacks/Materials/Sheets/glass.yml index 0caffb301f..cd6aed7cdf 100644 --- a/Resources/Prototypes/Stacks/Materials/Sheets/glass.yml +++ b/Resources/Prototypes/Stacks/Materials/Sheets/glass.yml @@ -4,7 +4,6 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/glass.rsi, state: glass } spawn: SheetGlass1 maxCount: 30 - itemSize: 1 - type: stack id: ReinforcedGlass @@ -12,7 +11,6 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/glass.rsi, state: rglass } spawn: SheetRGlass1 maxCount: 30 - itemSize: 1 - type: stack id: PlasmaGlass @@ -20,7 +18,6 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/glass.rsi, state: pglass } spawn: SheetPGlass1 maxCount: 30 - itemSize: 1 - type: stack id: ReinforcedPlasmaGlass @@ -28,7 +25,6 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/glass.rsi, state: rpglass } spawn: SheetRPGlass1 maxCount: 30 - itemSize: 1 - type: stack id: UraniumGlass @@ -36,7 +32,6 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/glass.rsi, state: uglass } spawn: SheetUGlass1 maxCount: 30 - itemSize: 1 - type: stack id: ReinforcedUraniumGlass @@ -44,7 +39,6 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/glass.rsi, state: ruglass } spawn: SheetRUGlass1 maxCount: 30 - itemSize: 1 - type: stack id: ClockworkGlass @@ -52,4 +46,3 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/glass.rsi, state: cglass } spawn: SheetClockworkGlass1 maxCount: 30 - itemSize: 1 diff --git a/Resources/Prototypes/Stacks/Materials/Sheets/metal.yml b/Resources/Prototypes/Stacks/Materials/Sheets/metal.yml index 77f750c205..7520130b9b 100644 --- a/Resources/Prototypes/Stacks/Materials/Sheets/metal.yml +++ b/Resources/Prototypes/Stacks/Materials/Sheets/metal.yml @@ -4,7 +4,6 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/metal.rsi, state: steel } spawn: SheetSteel1 maxCount: 30 - itemSize: 1 - type: stack id: Plasteel @@ -12,7 +11,6 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/metal.rsi, state: plasteel } spawn: SheetPlasteel1 maxCount: 30 - itemSize: 1 - type: stack id: Brass @@ -20,4 +18,3 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/metal.rsi, state: brass } spawn: SheetBrass1 maxCount: 30 - itemSize: 1 diff --git a/Resources/Prototypes/Stacks/Materials/Sheets/other.yml b/Resources/Prototypes/Stacks/Materials/Sheets/other.yml index 96f22ae656..565b1fc1ae 100644 --- a/Resources/Prototypes/Stacks/Materials/Sheets/other.yml +++ b/Resources/Prototypes/Stacks/Materials/Sheets/other.yml @@ -4,7 +4,6 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/other.rsi, state: paper } spawn: SheetPaper1 maxCount: 30 - itemSize: 1 - type: stack id: Plasma @@ -12,7 +11,6 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/other.rsi, state: plasma } spawn: SheetPlasma1 maxCount: 30 - itemSize: 1 - type: stack id: Plastic @@ -20,7 +18,6 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/other.rsi, state: plastic } spawn: SheetPlastic1 maxCount: 30 - itemSize: 1 - type: stack id: Uranium @@ -28,4 +25,3 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/other.rsi, state: uranium } spawn: SheetUranium1 maxCount: 30 - itemSize: 1 diff --git a/Resources/Prototypes/Stacks/Materials/crystals.yml b/Resources/Prototypes/Stacks/Materials/crystals.yml index 274f9c10ea..28f4ed70ca 100644 --- a/Resources/Prototypes/Stacks/Materials/crystals.yml +++ b/Resources/Prototypes/Stacks/Materials/crystals.yml @@ -3,4 +3,3 @@ name: telecrystal icon: Objects/Specific/Syndicate/telecrystal.rsi spawn: Telecrystal1 - itemSize: 1 diff --git a/Resources/Prototypes/Stacks/Materials/ingots.yml b/Resources/Prototypes/Stacks/Materials/ingots.yml index 956523c92b..1fd67a096d 100644 --- a/Resources/Prototypes/Stacks/Materials/ingots.yml +++ b/Resources/Prototypes/Stacks/Materials/ingots.yml @@ -4,7 +4,6 @@ icon: { sprite: "/Textures/Objects/Materials/ingots.rsi", state: gold } spawn: IngotGold1 maxCount: 30 - itemSize: 1 - type: stack id: Silver @@ -12,4 +11,3 @@ icon: { sprite: "/Textures/Objects/Materials/ingots.rsi", state: silver } spawn: IngotSilver1 maxCount: 30 - itemSize: 1 diff --git a/Resources/Prototypes/Stacks/Materials/materials.yml b/Resources/Prototypes/Stacks/Materials/materials.yml index cc963dde59..1157dc3f00 100644 --- a/Resources/Prototypes/Stacks/Materials/materials.yml +++ b/Resources/Prototypes/Stacks/Materials/materials.yml @@ -4,7 +4,6 @@ icon: { sprite: /Textures/Objects/Misc/monkeycube.rsi, state: cube } spawn: MaterialBiomass1 maxCount: 100 - itemSize: 1 - type: stack id: WoodPlank @@ -12,7 +11,6 @@ icon: { sprite: /Textures/Objects/Materials/materials.rsi, state: wood } spawn: MaterialWoodPlank1 maxCount: 30 - itemSize: 1 - type: stack id: Cardboard @@ -20,7 +18,6 @@ icon: { sprite: /Textures/Objects/Materials/materials.rsi, state: cardboard } spawn: MaterialCardboard1 maxCount: 30 - itemSize: 1 - type: stack id: Cloth @@ -28,7 +25,6 @@ icon: { sprite: /Textures/Objects/Materials/materials.rsi, state: cloth } spawn: MaterialCloth1 maxCount: 30 - itemSize: 1 - type: stack id: Durathread @@ -36,7 +32,6 @@ icon: { sprite: /Textures/Objects/Materials/materials.rsi, state: durathread } spawn: MaterialDurathread1 maxCount: 30 - itemSize: 1 - type: stack id: Diamond @@ -44,7 +39,6 @@ icon: { sprite: /Textures/Objects/Materials/materials.rsi, state: diamond } spawn: MaterialDiamond1 maxCount: 30 - itemSize: 2 - type: stack id: Cotton @@ -52,7 +46,6 @@ icon: { sprite: /Textures/Objects/Materials/materials.rsi, state: cotton } spawn: MaterialCotton1 maxCount: 30 - itemSize: 1 - type: stack id: Pyrotton @@ -60,7 +53,6 @@ icon: { sprite: /Textures/Objects/Materials/materials.rsi, state: pyrotton } spawn: MaterialPyrotton1 maxCount: 30 - itemSize: 1 - type: stack id: Bananium @@ -68,7 +60,6 @@ icon: { sprite: /Textures/Objects/Materials/materials.rsi, state: bananium } spawn: MaterialBananium1 maxCount: 10 - itemSize: 2 - type: stack id: MeatSheets @@ -76,7 +67,6 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/meaterial.rsi, state: meat } spawn: MaterialSheetMeat1 maxCount: 30 - itemSize: 1 - type: stack id: WebSilk @@ -84,7 +74,6 @@ icon: { sprite: /Textures/Objects/Materials/silk.rsi, state: icon } spawn: MaterialWebSilk1 maxCount: 50 - itemSize: 1 - type: stack id: Bones @@ -92,7 +81,6 @@ icon: { sprite: /Textures/Objects/Materials/materials.rsi, state: bones} spawn: MaterialBones1 maxCount: 30 - itemSize: 1 - type: stack id: Gunpowder @@ -100,4 +88,3 @@ icon: { sprite: /Textures/Objects/Misc/reagent_fillings.rsi, state: powderpile } spawn: MaterialGunpowder maxCount: 60 - itemSize: 1 diff --git a/Resources/Prototypes/Stacks/Materials/ore.yml b/Resources/Prototypes/Stacks/Materials/ore.yml index 2a95393c27..51254b5a7a 100644 --- a/Resources/Prototypes/Stacks/Materials/ore.yml +++ b/Resources/Prototypes/Stacks/Materials/ore.yml @@ -4,7 +4,6 @@ icon: { sprite: /Textures/Objects/Materials/ore.rsi, state: gold } spawn: GoldOre1 maxCount: 30 - itemSize: 2 - type: stack id: SteelOre @@ -12,7 +11,6 @@ icon: { sprite: /Textures/Objects/Materials/ore.rsi, state: iron } spawn: SteelOre1 maxCount: 30 - itemSize: 2 - type: stack id: PlasmaOre @@ -20,7 +18,6 @@ icon: { sprite: /Textures/Objects/Materials/ore.rsi, state: plasma } spawn: PlasmaOre1 maxCount: 30 - itemSize: 2 - type: stack id: SilverOre @@ -28,7 +25,6 @@ icon: { sprite: /Textures/Objects/Materials/ore.rsi, state: silver } spawn: SilverOre1 maxCount: 30 - itemSize: 2 - type: stack id: SpaceQuartz @@ -36,7 +32,6 @@ icon: { sprite: /Textures/Objects/Materials/ore.rsi, state: spacequartz } spawn: SpaceQuartz1 maxCount: 30 - itemSize: 2 - type: stack id: UraniumOre @@ -44,7 +39,6 @@ icon: { sprite: /Textures/Objects/Materials/ore.rsi, state: uranium } spawn: UraniumOre1 maxCount: 30 - itemSize: 2 - type: stack @@ -53,7 +47,6 @@ icon: { sprite: /Textures/Objects/Materials/ore.rsi, state: bananium } spawn: BananiumOre1 maxCount: 30 - itemSize: 2 - type: stack id: Coal @@ -61,7 +54,6 @@ icon: { sprite: /Textures/Objects/Materials/ore.rsi, state: coal } spawn: Coal1 maxCount: 30 - itemSize: 2 - type: stack id: SaltOre @@ -69,4 +61,3 @@ icon: { sprite: /Textures/Objects/Materials/ore.rsi, state: salt } spawn: Salt1 maxCount: 30 - itemSize: 2 diff --git a/Resources/Prototypes/Stacks/Materials/parts.yml b/Resources/Prototypes/Stacks/Materials/parts.yml index 947bbb1bf2..50ceb28757 100644 --- a/Resources/Prototypes/Stacks/Materials/parts.yml +++ b/Resources/Prototypes/Stacks/Materials/parts.yml @@ -4,4 +4,3 @@ icon: { sprite: /Textures/Objects/Materials/parts.rsi, state: rods } spawn: PartRodMetal1 maxCount: 30 - itemSize: 1 diff --git a/Resources/Prototypes/Stacks/consumable_stacks.yml b/Resources/Prototypes/Stacks/consumable_stacks.yml index 2936772f08..e7feab7b52 100644 --- a/Resources/Prototypes/Stacks/consumable_stacks.yml +++ b/Resources/Prototypes/Stacks/consumable_stacks.yml @@ -5,7 +5,6 @@ name: pancake spawn: FoodBakedPancake maxCount: 3 - itemSize: 1 # Food Containers @@ -15,7 +14,6 @@ icon: { sprite: Objects/Consumable/Food/Baked/pizza.rsi, state: box } spawn: FoodBoxPizza maxCount: 30 - itemSize: 10 # Smokeables @@ -25,7 +23,6 @@ icon: { sprite: /Textures/Objects/Consumable/Smokeables/Cigarettes/paper.rsi, state: cigpaper } spawn: PaperRolling maxCount: 5 - itemSize: 1 - type: stack id: CigaretteFilter @@ -33,7 +30,6 @@ icon: { sprite: /Textures/Objects/Consumable/Smokeables/Cigarettes/paper.rsi, state: cigfilter } spawn: CigaretteFilter maxCount: 5 - itemSize: 2 - type: stack id: GroundTobacco @@ -41,7 +37,6 @@ icon: { sprite: /Textures/Objects/Misc/reagent_fillings.rsi, state: powderpile } spawn: GroundTobacco maxCount: 5 - itemSize: 1 - type: stack id: GroundCannabis @@ -49,7 +44,6 @@ icon: { sprite: /Textures/Objects/Misc/reagent_fillings.rsi, state: powderpile } spawn: GroundCannabis maxCount: - itemSize: 1 - type: stack id: GroundCannabisRainbow @@ -57,7 +51,6 @@ icon: { sprite: /Textures/Objects/Specific/Hydroponics/rainbow_cannabis.rsi, state: powderpile_rainbow } spawn: GroundCannabisRainbow maxCount: - itemSize: 1 - type: stack id: LeavesTobaccoDried @@ -65,7 +58,6 @@ icon: { sprite: /Textures/Objects/Specific/Hydroponics/tobacco.rsi, state: dried } spawn: LeavesTobaccoDried maxCount: 5 - itemSize: 5 - type: stack id: LeavesCannabisDried @@ -73,12 +65,9 @@ icon: { sprite: /Textures/Objects/Specific/Hydroponics/tobacco.rsi, state: dried } spawn: LeavesCannabisDried maxCount: 5 - itemSize: 5 - type: stack id: LeavesCannabisRainbowDried name: dried rainbow cannabis leaves icon: { sprite: /Textures/Objects/Specific/Hydroponics/rainbow_cannabis.rsi, state: dried } spawn: LeavesCannabisRainbowDried - maxCount: 5 - itemSize: 5 diff --git a/Resources/Prototypes/Stacks/engineering_stacks.yml b/Resources/Prototypes/Stacks/engineering_stacks.yml index 77cc620402..b4550015dc 100644 --- a/Resources/Prototypes/Stacks/engineering_stacks.yml +++ b/Resources/Prototypes/Stacks/engineering_stacks.yml @@ -4,11 +4,9 @@ name: inflatable wall spawn: InflatableWallStack1 maxCount: 10 - itemSize: 1 - type: stack id: InflatableDoor name: inflatable door spawn: InflatableDoorStack1 maxCount: 4 - itemSize: 1 diff --git a/Resources/Prototypes/Stacks/floor_tile_stacks.yml b/Resources/Prototypes/Stacks/floor_tile_stacks.yml index c5e37013b8..c88786f0dc 100644 --- a/Resources/Prototypes/Stacks/floor_tile_stacks.yml +++ b/Resources/Prototypes/Stacks/floor_tile_stacks.yml @@ -3,469 +3,402 @@ name: steel tile spawn: FloorTileItemSteel maxCount: 30 - itemSize: 5 - type: stack id: FloorTileMetalDiamond name: steel tile spawn: FloorTileItemMetalDiamond maxCount: 30 - itemSize: 5 - type: stack id: FloorTileWood name: wood floor spawn: FloorTileItemWood maxCount: 30 - itemSize: 5 - type: stack id: FloorTileWhite name: white tile spawn: FloorTileItemWhite maxCount: 30 - itemSize: 5 - type: stack id: FloorTileDark name: dark tile spawn: FloorTileItemDark maxCount: 30 - itemSize: 5 - type: stack id: FloorTileTechmaint name: techmaint floor spawn: FloorTileItemTechmaint maxCount: 30 - itemSize: 5 - type: stack id: FloorTileFreezer name: freezer tile spawn: FloorTileItemFreezer maxCount: 30 - itemSize: 5 - type: stack id: FloorTileShowroom name: showroom tile spawn: FloorTileItemShowroom maxCount: 30 - itemSize: 5 - type: stack id: FloorTileGCircuit name: green-circuit floor spawn: FloorTileItemGCircuit maxCount: 30 - itemSize: 5 - type: stack id: FloorTileGold name: gold floor spawn: FloorTileItemGold maxCount: 30 - itemSize: 5 - type: stack id: FloorTileReinforced name: reinforced tile spawn: FloorTileItemReinforced maxCount: 30 - itemSize: 5 - type: stack id: FloorTileMono name: mono tile spawn: FloorTileItemMono maxCount: 30 - itemSize: 5 - type: stack id: FloorTileBrassFilled name: filled brass plate spawn: FloorTileItemBrassFilled maxCount: 30 - itemSize: 5 - + - type: stack id: FloorTileBrassReebe name: smooth brass plate spawn: FloorTileItemBrassReebe maxCount: 30 - itemSize: 5 - type: stack id: FloorTileLino name: linoleum floor spawn: FloorTileItemLino maxCount: 30 - itemSize: 5 - type: stack id: FloorTileHydro name: hydro tile spawn: FloorTileItemHydro maxCount: 30 - itemSize: 5 - type: stack id: FloorTileLime name: lime tile spawn: FloorTileItemLime maxCount: 30 - itemSize: 5 - type: stack id: FloorTileDirty name: dirty tile spawn: FloorTileItemDirty maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackShuttleWhite name: white shuttle tile spawn: FloorTileItemShuttleWhite maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackShuttleBlue name: blue shuttle tile spawn: FloorTileItemShuttleBlue maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackShuttleOrange name: orange shuttle tile spawn: FloorTileItemShuttleOrange maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackShuttlePurple name: purple shuttle tile spawn: FloorTileItemShuttlePurple maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackShuttleRed name: red shuttle tile spawn: FloorTileItemShuttleRed maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackShuttleGrey name: grey shuttle tile spawn: FloorTileItemShuttleGrey maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackShuttleBlack name: black shuttle tile spawn: FloorTileItemShuttleBlack maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackEighties name: eighties floor tile spawn: FloorTileItemEighties maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackArcadeBlue name: blue arcade tile spawn: FloorTileItemArcadeBlue maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackArcadeBlue2 name: blue arcade tile spawn: FloorTileItemArcadeBlue2 maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackArcadeRed name: red arcade tile spawn: FloorTileItemArcadeRed maxCount: 30 - itemSize: 5 - type: stack id: FloorCarpetRed name: red carpet tile spawn: FloorCarpetItemRed maxCount: 30 - itemSize: 5 - type: stack id: FloorCarpetBlack name: block carpet tile spawn: FloorCarpetItemBlack maxCount: 30 - itemSize: 5 - type: stack id: FloorCarpetBlue name: blue carpet tile spawn: FloorCarpetItemBlue maxCount: 30 - itemSize: 5 - type: stack id: FloorCarpetGreen name: green carpet tile spawn: FloorCarpetItemGreen maxCount: 30 - itemSize: 5 - type: stack id: FloorCarpetOrange name: orange carpet tile spawn: FloorCarpetItemOrange maxCount: 30 - itemSize: 5 - type: stack id: FloorCarpetSkyBlue name: skyblue carpet tile spawn: FloorCarpetItemSkyBlue maxCount: 30 - itemSize: 5 - type: stack id: FloorCarpetPurple name: purple carpet tile spawn: FloorCarpetItemPurple maxCount: 30 - itemSize: 5 - type: stack id: FloorCarpetPink name: pink carpet tile spawn: FloorCarpetItemPink maxCount: 30 - itemSize: 5 - type: stack id: FloorCarpetCyan name: cyan carpet tile spawn: FloorCarpetItemCyan maxCount: 30 - itemSize: 5 - type: stack id: FloorCarpetWhite name: white carpet tile spawn: FloorCarpetItemWhite maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackCarpetClown name: clown carpet tile spawn: FloorTileItemCarpetClown maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackCarpetOffice name: office carpet tile spawn: FloorTileItemCarpetOffice maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackBoxing name: boxing ring tile spawn: FloorTileItemBoxing maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackGym name: gym floor tile spawn: FloorTileItemGym maxCount: 30 - itemSize: 5 - type: stack id: FloorTileElevatorShaft name: elevator shaft tile spawn: FloorTileItemElevatorShaft maxCount: 30 - itemSize: 5 - type: stack id: FloorTileRockVault name: rock vault tile spawn: FloorTileItemRockVault maxCount: 30 - itemSize: 5 - type: stack id: FloorTileBlue name: blue floor tile spawn: FloorTileItemBlue maxCount: 30 - itemSize: 5 - type: stack id: FloorTileMining name: mining floor tile spawn: FloorTileItemMining maxCount: 30 - itemSize: 5 - type: stack id: FloorTileMiningDark name: dark mining floor tile spawn: FloorTileItemMiningDark maxCount: 30 - itemSize: 5 - type: stack id: FloorTileMiningLight name: light mining floor tile spawn: FloorTileItemMiningLight maxCount: 30 - itemSize: 5 - type: stack id: FloorTileBar name: item bar floor tile spawn: FloorTileItemBar maxCount: 30 - itemSize: 5 - type: stack id: FloorTileClown name: clown floor tile spawn: FloorTileItemClown maxCount: 30 - itemSize: 5 - type: stack id: FloorTileMime name: mime floor tile spawn: FloorTileItemMime maxCount: 30 - itemSize: 5 - type: stack id: FloorTileKitchen name: kitchen floor tile spawn: FloorTileItemKitchen maxCount: 30 - itemSize: 5 - type: stack id: FloorTileLaundry name: laundry floor tile spawn: FloorTileItemLaundry maxCount: 30 - itemSize: 5 - type: stack id: FloorTileConcrete name: concrete tile spawn: FloorTileItemGrayConcrete maxCount: 30 - itemSize: 5 - type: stack id: FloorTileGrayConcrete name: gray concrete tile spawn: FloorTileItemLaundry maxCount: 30 - itemSize: 5 - type: stack id: FloorTileOldConcrete name: old concrete tile spawn: FloorTileItemOldConcrete maxCount: 30 - itemSize: 5 - type: stack id: FloorTileSilver name: silver floor tile spawn: FloorTileItemSilver maxCount: 30 - itemSize: 5 - type: stack id: FloorTileBCircuit name: bcircuit floor tile spawn: FloorTileItemBCircuit maxCount: 30 - itemSize: 5 - type: stack id: FloorTileGrass name: grass floor tile spawn: FloorTileItemGrass maxCount: 30 - itemSize: 5 - type: stack id: FloorTileGrassJungle name: grass jungle floor tile spawn: FloorTileItemGrassJungle maxCount: 30 - itemSize: 5 - type: stack id: FloorTileSnow name: snow floor tile spawn: FloorTileItemSnow maxCount: 30 - itemSize: 5 - type: stack id: FloorTileWoodPattern name: wood pattern floor spawn: FloorTileItemWoodPattern maxCount: 30 - itemSize: 5 - type: stack id: FloorTileFlesh name: flesh floor spawn: FloorTileItemFlesh maxCount: 30 - itemSize: 5 - type: stack id: FloorTileSteelMaint name: steel maint floor spawn: FloorTileItemSteelMaint maxCount: 30 - itemSize: 5 - type: stack id: FloorTileGratingMaint name: grating maint floor spawn: FloorTileItemGratingMaint maxCount: 30 - itemSize: 5 - type: stack id: FloorTileWeb name: web tile spawn: FloorTileItemWeb maxCount: 30 - itemSize: 5 # Faux science tiles - type: stack @@ -473,38 +406,33 @@ name: astro-grass floor spawn: FloorTileItemAstroGrass maxCount: 30 - itemSize: 5 - type: stack id: FloorTileMowedAstroGrass name: mowed astro-grass floor spawn: FloorTileItemMowedAstroGrass maxCount: 30 - itemSize: 5 - type: stack id: FloorTileJungleAstroGrass name: jungle astro-grass floor spawn: FloorTileItemJungleAstroGrass maxCount: 30 - itemSize: 5 - type: stack id: FloorTileAstroIce name: astro-ice floor spawn: FloorTileItemAstroIce maxCount: 30 - itemSize: 5 - type: stack id: FloorTileAstroSnow name: astro-snow floor spawn: FloorTileItemAstroSnow maxCount: 30 - itemSize: 5 - type: stack id: FloorTileWoodLarge name: large wood floor spawn: FloorTileItemWoodLarge - maxCount: 30 \ No newline at end of file + maxCount: 30 diff --git a/Resources/Prototypes/Stacks/medical_stacks.yml b/Resources/Prototypes/Stacks/medical_stacks.yml index 9d2b77ec93..7ad3c21634 100644 --- a/Resources/Prototypes/Stacks/medical_stacks.yml +++ b/Resources/Prototypes/Stacks/medical_stacks.yml @@ -4,7 +4,6 @@ icon: { sprite: "/Textures/Objects/Specific/Medical/medical.rsi", state: ointment } spawn: Ointment maxCount: 10 - itemSize: 1 - type: stack id: AloeCream @@ -12,7 +11,6 @@ icon: { sprite: "/Textures/Objects/Specific/Hydroponics/aloe.rsi", state: cream } spawn: AloeCream maxCount: 10 - itemSize: 1 - type: stack id: Gauze @@ -20,7 +18,6 @@ icon: { sprite: "/Textures/Objects/Specific/Medical/medical.rsi", state: gauze } spawn: Gauze maxCount: 10 - itemSize: 1 - type: stack id: Brutepack @@ -28,7 +25,6 @@ icon: { sprite: "/Textures/Objects/Specific/Medical/medical.rsi", state: gauze } spawn: Brutepack maxCount: 10 - itemSize: 1 - type: stack id: Bloodpack @@ -36,7 +32,6 @@ icon: { sprite: "/Textures/Objects/Specific/Medical/medical.rsi", state: bloodpack } spawn: Bloodpack maxCount: 10 - itemSize: 1 - type: stack id: MedicatedSuture @@ -44,7 +39,6 @@ icon: {sprite: "/Textures/Objects/Specific/Medical/medical.rsi", state: medicated-suture } spawn: MedicatedSuture maxCount: 10 - itemSize: 1 - type: stack id: RegenerativeMesh @@ -52,6 +46,4 @@ icon: {sprite: "/Textures/Objects/Specific/Medical/medical.rsi", state: regenerative-mesh} spawn: RegenerativeMesh maxCount: 10 - itemSize: 1 - diff --git a/Resources/Prototypes/Stacks/power_stacks.yml b/Resources/Prototypes/Stacks/power_stacks.yml index 5acf4819de..7f015659e9 100644 --- a/Resources/Prototypes/Stacks/power_stacks.yml +++ b/Resources/Prototypes/Stacks/power_stacks.yml @@ -4,7 +4,6 @@ icon: { sprite: "/Textures/Objects/Tools/cable-coils.rsi", state: coil-30 } spawn: CableApcStack1 maxCount: 30 - itemSize: 1 - type: stack id: CableMV @@ -12,7 +11,6 @@ icon: { sprite: "/Textures/Objects/Tools/cable-coils.rsi", state: coilmv-30 } spawn: CableMVStack1 maxCount: 30 - itemSize: 1 - type: stack id: CableHV @@ -20,4 +18,3 @@ icon: { sprite: "/Textures/Objects/Tools/cable-coils.rsi", state: coilhv-30 } spawn: CableHVStack1 maxCount: 30 - itemSize: 1 diff --git a/Resources/Prototypes/Stacks/science_stacks.yml b/Resources/Prototypes/Stacks/science_stacks.yml index 010a5dc659..bf58fad915 100644 --- a/Resources/Prototypes/Stacks/science_stacks.yml +++ b/Resources/Prototypes/Stacks/science_stacks.yml @@ -3,4 +3,21 @@ name: artifact fragment spawn: ArtifactFragment1 maxCount: 30 - itemSize: 5 \ No newline at end of file + +- type: stack + id: Capacitor + name: capacitor + spawn: CapacitorStockPart + maxCount: 10 + +- type: stack + id: MicroManipulator + name: micro manipulator + spawn: MicroManipulatorStockPart + maxCount: 10 + +- type: stack + id: MatterBin + name: matter bin + spawn: MatterBinStockPart + maxCount: 10 From 6501a5b180f61b4e5e5f05f7238a8f7a093dfe2d Mon Sep 17 00:00:00 2001 From: PJBot Date: Sat, 1 Jun 2024 17:50:34 +0000 Subject: [PATCH 74/75] 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 449f9bf43b..535a93e87e 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: lzk228 - changes: - - message: Refill light replacer from box popup now shows properly. - type: Fix - id: 6161 - time: '2024-03-15T15:23:58.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26136 - author: FairlySadPanda changes: - message: Food and drink stocks in vending machines has been reduced to encourage @@ -3853,3 +3846,10 @@ id: 6660 time: '2024-06-01T17:29:46.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28462 +- author: AJCM-git + changes: + - message: Machine parts are now stackable + type: Tweak + id: 6661 + time: '2024-06-01T17:49:28.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28434 From 8c32072f67cbab2f69647ff1e78c3f62e705639a Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Sat, 1 Jun 2024 12:00:31 -0700 Subject: [PATCH 75/75] Fix error when removing chasm falling component on a terminating entity (#28471) --- Content.Client/Chasm/ChasmFallingVisualsSystem.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Content.Client/Chasm/ChasmFallingVisualsSystem.cs b/Content.Client/Chasm/ChasmFallingVisualsSystem.cs index 4b04aa9dd7..ddcd509cb3 100644 --- a/Content.Client/Chasm/ChasmFallingVisualsSystem.cs +++ b/Content.Client/Chasm/ChasmFallingVisualsSystem.cs @@ -24,8 +24,11 @@ public sealed class ChasmFallingVisualsSystem : EntitySystem private void OnComponentInit(EntityUid uid, ChasmFallingComponent component, ComponentInit args) { - if (!TryComp(uid, out var sprite)) + if (!TryComp(uid, out var sprite) || + TerminatingOrDeleted(uid)) + { return; + } component.OriginalScale = sprite.Scale;