From 16032cf421a43608d5c04dad04a3fd43290240f4 Mon Sep 17 00:00:00 2001 From: Skarletto <122584947+Skarletto@users.noreply.github.com> Date: Wed, 11 Oct 2023 11:20:21 -0400 Subject: [PATCH 01/29] today i will not stack 50 scrubbers (#20917) --- .../Structures/Piping/Atmospherics/unary.yml | 5 +++++ .../Prototypes/Recipes/Construction/utilities.yml | 12 ++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml index e9d1c4a30d0..1d08bd8c6e5 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml @@ -6,6 +6,9 @@ mode: SnapgridCenter components: - type: AtmosDevice + - type: Tag + tags: + - Unstackable - type: SubFloorHide blockInteractions: false blockAmbience: false @@ -53,6 +56,7 @@ - type: Tag tags: - GasVent + - Unstackable - type: Sprite drawdepth: FloorObjects sprite: Structures/Piping/Atmospherics/vent.rsi @@ -143,6 +147,7 @@ - type: Tag tags: - GasScrubber + - Unstackable - type: Sprite drawdepth: FloorObjects sprite: Structures/Piping/Atmospherics/scrubber.rsi diff --git a/Resources/Prototypes/Recipes/Construction/utilities.yml b/Resources/Prototypes/Recipes/Construction/utilities.yml index 40fe9b68830..d1dac1d1b05 100644 --- a/Resources/Prototypes/Recipes/Construction/utilities.yml +++ b/Resources/Prototypes/Recipes/Construction/utilities.yml @@ -445,7 +445,8 @@ state: vent_off conditions: - !type:TileNotBlocked {} - + - !type:NoUnstackableInTile + - type: construction name: passive vent description: Unpowered vent that equalises gases on both sides. @@ -466,7 +467,8 @@ state: vent_off conditions: - !type:TileNotBlocked {} - + - !type:NoUnstackableInTile + - type: construction name: air scrubber description: Sucks gas into connected pipes. @@ -487,7 +489,8 @@ state: scrub_off conditions: - !type:TileNotBlocked {} - + - !type:NoUnstackableInTile + - type: construction name: air injector description: Injects air into the atmosphere. @@ -508,7 +511,8 @@ state: injector conditions: - !type:TileNotBlocked {} - + - !type:NoUnstackableInTile + # ATMOS BINARY - type: construction name: gas pump From 6d34f19030a4559a425ebc44757dca0289be4dc6 Mon Sep 17 00:00:00 2001 From: PJBot Date: Wed, 11 Oct 2023 11:21:26 -0400 Subject: [PATCH 02/29] Automatic changelog update --- Resources/Changelog/Changelog.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index f84ac42a72a..9071419f44d 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,9 +1,4 @@ Entries: -- author: metalgearsloth - changes: - - {message: Added toggle to hide clothing in the character editor., type: Add} - id: 4493 - time: '2023-08-09T06:26:34.0000000+00:00' - author: Vordenburg changes: - {message: Diona are no longer inhibited by kudzu., type: Tweak} @@ -2956,3 +2951,9 @@ Entries: inside., type: Tweak} id: 4992 time: '2023-10-11T09:22:09.0000000+00:00' +- author: Skarletto + changes: + - {message: Changed unary devices such as scrubbers and vents to not be able to + stack with each other, type: Tweak} + id: 4993 + time: '2023-10-11T15:20:21.0000000+00:00' From 1f21826c21006cee1c4724e99e24d8faff0484d2 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Thu, 12 Oct 2023 03:31:10 +1100 Subject: [PATCH 03/29] Fix inventory relay by-ref events (#20816) * Fix inventory relay ref events * this works too (avoid duplication) --------- Co-authored-by: Slava0135 --- .../EntitySystems/ExplosionSystem.cs | 3 +-- .../Inventory/InventorySystem.Relay.cs | 24 +++++++++++-------- Content.Shared/Verbs/Verb.cs | 8 ++++--- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs index aa007c61c05..5e5af03c17b 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs @@ -116,8 +116,7 @@ public override void Shutdown() private void RelayedResistance(EntityUid uid, ExplosionResistanceComponent component, InventoryRelayedEvent args) { - var a = args.Args; - OnGetResistance(uid, component, ref a); + OnGetResistance(uid, component, ref args.Args); } private void OnGetResistance(EntityUid uid, ExplosionResistanceComponent component, ref GetExplosionResistanceEvent args) diff --git a/Content.Shared/Inventory/InventorySystem.Relay.cs b/Content.Shared/Inventory/InventorySystem.Relay.cs index 83b47542e29..fbe744911fd 100644 --- a/Content.Shared/Inventory/InventorySystem.Relay.cs +++ b/Content.Shared/Inventory/InventorySystem.Relay.cs @@ -24,12 +24,14 @@ public void InitializeRelay() SubscribeLocalEvent(RelayInventoryEvent); SubscribeLocalEvent(RelayInventoryEvent); SubscribeLocalEvent(RelayInventoryEvent); - SubscribeLocalEvent(RefRelayInventoryEvent); SubscribeLocalEvent(RelayInventoryEvent); SubscribeLocalEvent(RelayInventoryEvent); SubscribeLocalEvent(RelayInventoryEvent); SubscribeLocalEvent(RelayInventoryEvent); + // by-ref events + SubscribeLocalEvent(RefRelayInventoryEvent); + // Eye/vision events SubscribeLocalEvent(RelayInventoryEvent); SubscribeLocalEvent(RelayInventoryEvent); @@ -41,22 +43,24 @@ public void InitializeRelay() SubscribeLocalEvent>(RelayInventoryEvent); SubscribeLocalEvent>(RelayInventoryEvent); - SubscribeLocalEvent>(OnGetStrippingVerbs); + SubscribeLocalEvent>(OnGetEquipmentVerbs); } protected void RefRelayInventoryEvent(EntityUid uid, InventoryComponent component, ref T args) where T : IInventoryRelayEvent { - // Just so I don't have to update 20 morbillion events at once. - if (args.TargetSlots == SlotFlags.NONE) - return; - var containerEnumerator = new ContainerSlotEnumerator(uid, component.TemplateId, _prototypeManager, this, args.TargetSlots); + + // this copies the by-ref event var ev = new InventoryRelayedEvent(args); + while (containerEnumerator.MoveNext(out var container)) { if (!container.ContainedEntity.HasValue) continue; - RaiseLocalEvent(container.ContainedEntity.Value, ev, broadcast: false); + RaiseLocalEvent(container.ContainedEntity.Value, ev); } + + // and now we copy it back + args = ev.Args; } protected void RelayInventoryEvent(EntityUid uid, InventoryComponent component, T args) where T : IInventoryRelayEvent @@ -69,11 +73,11 @@ protected void RelayInventoryEvent(EntityUid uid, InventoryComponent componen while (containerEnumerator.MoveNext(out var container)) { if (!container.ContainedEntity.HasValue) continue; - RaiseLocalEvent(container.ContainedEntity.Value, ev, broadcast: false); + RaiseLocalEvent(container.ContainedEntity.Value, ev); } } - private void OnGetStrippingVerbs(EntityUid uid, InventoryComponent component, GetVerbsEvent args) + private void OnGetEquipmentVerbs(EntityUid uid, InventoryComponent component, GetVerbsEvent args) { // Automatically relay stripping related verbs to all equipped clothing. @@ -112,7 +116,7 @@ private void OnGetStrippingVerbs(EntityUid uid, InventoryComponent component, Ge /// public sealed class InventoryRelayedEvent : EntityEventArgs { - public readonly TEvent Args; + public TEvent Args; public InventoryRelayedEvent(TEvent args) { diff --git a/Content.Shared/Verbs/Verb.cs b/Content.Shared/Verbs/Verb.cs index 76ed2073075..660a3bdf948 100644 --- a/Content.Shared/Verbs/Verb.cs +++ b/Content.Shared/Verbs/Verb.cs @@ -1,5 +1,6 @@ using Content.Shared.Database; using Content.Shared.Interaction.Events; +using Content.Shared.Inventory; using Robust.Shared.Serialization; using Robust.Shared.Utility; @@ -349,9 +350,10 @@ public sealed class ExamineVerb : Verb } /// - /// Verbs specifically for interactions that occur with equipped entities. These verbs should be accessible via - /// the stripping UI, and may optionally also be accessible via a verb on the equipee if the via inventory relay - /// events.get-verbs event. + /// Verbs specifically for interactions that occur with equipped entities. These verbs are unique in that they + /// can be used via the stripping UI. Additionally, when getting verbs on an entity with an inventory it will + /// these automatically relay the event to all equipped items via a + /// . /// [Serializable, NetSerializable] public sealed class EquipmentVerb : Verb From 9ea55c5417ff6e60d887e71b6722f763ac61038f Mon Sep 17 00:00:00 2001 From: PJBot Date: Wed, 11 Oct 2023 12:32:14 -0400 Subject: [PATCH 04/29] Automatic changelog update --- Resources/Changelog/Changelog.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 9071419f44d..6ed28abb162 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,9 +1,4 @@ Entries: -- author: Vordenburg - changes: - - {message: Diona are no longer inhibited by kudzu., type: Tweak} - id: 4494 - time: '2023-08-09T15:24:41.0000000+00:00' - author: themias changes: - {message: Taxibots have robot speech bubbles, type: Tweak} @@ -2957,3 +2952,9 @@ Entries: stack with each other, type: Tweak} id: 4993 time: '2023-10-11T15:20:21.0000000+00:00' +- author: ElectroJr + changes: + - {message: Fixed explosion resistance from clothing/equipment not being applied., + type: Fix} + id: 4994 + time: '2023-10-11T16:31:10.0000000+00:00' From b7d96c6538ef4ab0f00c6441ba42b0c8d34bbb19 Mon Sep 17 00:00:00 2001 From: brainfood1183 <113240905+brainfood1183@users.noreply.github.com> Date: Wed, 11 Oct 2023 17:43:54 +0100 Subject: [PATCH 05/29] Honk reagent now makes you Honk. (#20838) --- Resources/Prototypes/Reagents/toxins.yml | 7 ++++++- Resources/Prototypes/Voice/disease_emotes.yml | 5 ----- Resources/Prototypes/Voice/speech_emotes.yml | 5 +++++ 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/Resources/Prototypes/Reagents/toxins.yml b/Resources/Prototypes/Reagents/toxins.yml index e2a40ffbb31..7e206cc9d8f 100644 --- a/Resources/Prototypes/Reagents/toxins.yml +++ b/Resources/Prototypes/Reagents/toxins.yml @@ -508,6 +508,10 @@ Poison: metabolismRate: 0.05 effects: + - !type:Emote + emote: Honk + showInChat: true + probability: 0.2 - !type:HealthChange conditions: - !type:ReagentThreshold @@ -517,6 +521,7 @@ damage: types: Poison: 0.06 + - type: reagent id: Lead @@ -548,4 +553,4 @@ - !type:HealthChange damage: types: - Poison: 1.8 \ No newline at end of file + Poison: 1.8 diff --git a/Resources/Prototypes/Voice/disease_emotes.yml b/Resources/Prototypes/Voice/disease_emotes.yml index fa5f1cd6109..af93025cae0 100644 --- a/Resources/Prototypes/Voice/disease_emotes.yml +++ b/Resources/Prototypes/Voice/disease_emotes.yml @@ -43,8 +43,3 @@ id: Snore category: Vocal chatMessages: [snores] - -- type: emote - id: Honk - category: Vocal - chatMessages: [honks] diff --git a/Resources/Prototypes/Voice/speech_emotes.yml b/Resources/Prototypes/Voice/speech_emotes.yml index ca8b9d55021..133c9249190 100644 --- a/Resources/Prototypes/Voice/speech_emotes.yml +++ b/Resources/Prototypes/Voice/speech_emotes.yml @@ -53,6 +53,11 @@ - giggling - giggled +- type: emote + id: Honk + category: Vocal + chatMessages: [honks] + - type: emote id: Sigh category: Vocal From 0cb46632fd0d38ab2d026c30cb11a7217520eb9e Mon Sep 17 00:00:00 2001 From: JoeHammad1844 <130668733+JoeHammad1844@users.noreply.github.com> Date: Thu, 12 Oct 2023 03:47:25 +1100 Subject: [PATCH 06/29] nukie shuttle gets syndicate airlocks (#20417) --- Resources/Maps/infiltrator.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Resources/Maps/infiltrator.yml b/Resources/Maps/infiltrator.yml index 0cba6c828b2..6670301e909 100644 --- a/Resources/Maps/infiltrator.yml +++ b/Resources/Maps/infiltrator.yml @@ -574,7 +574,7 @@ entities: - type: GasTileOverlay - type: SpreaderGrid - type: GridPathfinding -- proto: AirlockExternal +- proto: AirlockSyndicateLocked entities: - uid: 69 components: @@ -616,7 +616,7 @@ entities: - pos: -4.5,-10.5 parent: 73 type: Transform -- proto: AirlockSecurity +- proto: AirlockSyndicateLocked entities: - uid: 201 components: @@ -637,7 +637,7 @@ entities: - pos: -0.5,-14.5 parent: 73 type: Transform -- proto: AirlockSecurityGlass +- proto: AirlockSyndicateGlassLocked entities: - uid: 371 components: From 8177c406f6fa5a9fb1e01ee330f2ac9026bc2f79 Mon Sep 17 00:00:00 2001 From: PJBot Date: Wed, 11 Oct 2023 12:48:31 -0400 Subject: [PATCH 07/29] Automatic changelog update --- Resources/Changelog/Changelog.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 6ed28abb162..a21254b1320 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,9 +1,4 @@ Entries: -- author: themias - changes: - - {message: Taxibots have robot speech bubbles, type: Tweak} - id: 4495 - time: '2023-08-09T16:48:08.0000000+00:00' - author: Ilya246 changes: - {message: The air alarm UI now allows copying settings of one device to all similar @@ -2958,3 +2953,8 @@ Entries: type: Fix} id: 4994 time: '2023-10-11T16:31:10.0000000+00:00' +- author: JoeHammad + changes: + - {message: The nukie ship now has syndicate access airlocks, type: Add} + id: 4995 + time: '2023-10-11T16:47:25.0000000+00:00' From fcd0d9ef0f4d3ce43fd97ac204f7e3b0275a79cd Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Thu, 12 Oct 2023 04:50:10 +1100 Subject: [PATCH 08/29] Add methods to transfer actions between containers (#20901) --- .../Systems/Actions/ActionUIController.cs | 5 +- .../Actions/ActionContainerSystem.cs | 100 +++++++++++++++--- Content.Shared/Actions/SharedActionsSystem.cs | 10 +- 3 files changed, 98 insertions(+), 17 deletions(-) diff --git a/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs b/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs index b2ff36d05c3..e2de86201e7 100644 --- a/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs +++ b/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs @@ -769,10 +769,9 @@ private bool OnMenuBeginDrag() { if (_actionsSystem != null && _actionsSystem.TryGetActionData(_menuDragHelper.Dragged?.ActionId, out var action)) { - if (action.EntityIcon is {} entIcon) + if (EntityManager.TryGetComponent(action.EntityIcon, out SpriteComponent? sprite)) { - _dragShadow.Texture = EntityManager.GetComponent(entIcon).Icon? - .GetFrame(RsiDirection.South, 0); + _dragShadow.Texture = sprite.Icon?.GetFrame(RsiDirection.South, 0); } else if (action.Icon != null) { diff --git a/Content.Shared/Actions/ActionContainerSystem.cs b/Content.Shared/Actions/ActionContainerSystem.cs index 27cd8dcce68..bce0836efb6 100644 --- a/Content.Shared/Actions/ActionContainerSystem.cs +++ b/Content.Shared/Actions/ActionContainerSystem.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using System.Linq; using Robust.Shared.Containers; using Robust.Shared.Network; using Robust.Shared.Timing; @@ -15,6 +16,7 @@ public sealed class ActionContainerSystem : EntitySystem [Dependency] private readonly SharedContainerSystem _container = default!; [Dependency] private readonly SharedActionsSystem _actions = default!; [Dependency] private readonly INetManager _netMan = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; public override void Initialize() { @@ -96,7 +98,61 @@ public bool EnsureAction(EntityUid uid, } /// - /// Adds a pre-existing action to an action container. + /// Transfers an action from one container to another, while keeping the attached entity the same. + /// + /// + /// While the attached entity should be the same at the end, this will actually remove and then re-grant the action. + /// + public void TransferAction( + EntityUid actionId, + EntityUid newContainer, + BaseActionComponent? action = null, + ActionsContainerComponent? container = null) + { + if (!_actions.ResolveActionData(actionId, ref action)) + return; + + if (action.Container == newContainer) + return; + + var attached = action.AttachedEntity; + if (!AddAction(newContainer, actionId, action, container)) + return; + + DebugTools.AssertEqual(action.Container, newContainer); + DebugTools.AssertNull(action.AttachedEntity); + + if (attached != null) + _actions.AddActionDirect(attached.Value, actionId, action: action); + + DebugTools.AssertEqual(action.AttachedEntity, attached); + } + + /// + /// Transfers all actions from one container to another, while keeping the attached entity the same. + /// + /// <remarks> + /// While the attached entity should be the same at the end, this will actually remove and then re-grant the action. + /// </remarks> + public void TransferAllActions( + EntityUid from, + EntityUid to, + ActionsContainerComponent? oldContainer = null, + ActionsContainerComponent? newContainer = null) + { + if (!Resolve(from, ref oldContainer) || !Resolve(to, ref newContainer)) + return; + + foreach (var action in oldContainer.Container.ContainedEntities.ToArray()) + { + TransferAction(action, to, container: newContainer); + } + + DebugTools.AssertEqual(oldContainer.Container.Count, 0); + } + + /// + /// Adds a pre-existing action to an action container. If the action is already in some container it will first remove it. /// public bool AddAction(EntityUid uid, EntityUid actionId, BaseActionComponent? action = null, ActionsContainerComponent? comp = null) { @@ -104,10 +160,7 @@ public bool AddAction(EntityUid uid, EntityUid actionId, BaseActionComponent? ac return false; if (action.Container != null) - { - Log.Error($"Attempted to insert an action {ToPrettyString(actionId)} that was already in a container {ToPrettyString(action.Container.Value)}"); - return false; - } + RemoveAction(actionId, action); DebugTools.Assert(comp == null || comp.Owner == uid); comp ??= EnsureComp(uid); @@ -124,6 +177,35 @@ public bool AddAction(EntityUid uid, EntityUid actionId, BaseActionComponent? ac return true; } + /// + /// Removes an action from its container and any action-performer and moves the action to null-space + /// + public void RemoveAction(EntityUid actionId, BaseActionComponent? action = null) + { + if (!_actions.ResolveActionData(actionId, ref action)) + return; + + if (action.Container == null) + return; + + _transform.DetachParentToNull(actionId, Transform(actionId)); + + // Container removal events should have removed the action from the action container. + // However, just in case the container was already deleted we will still manually clear the container field + if (action.Container != null) + { + if (Exists(action.Container)) + Log.Error($"Failed to remove action {ToPrettyString(actionId)} from its container {ToPrettyString(action.Container)}?"); + action.Container = null; + } + + // If the action was granted to some entity, then the removal from the container should have automatically removed it. + // However, if the action was granted without ever being placed in an action container, it will not have been removed. + // Therefore, to ensure that the behaviour of the method is consistent we will also explicitly remove the action. + if (action.AttachedEntity != null) + _actions.RemoveAction(action.AttachedEntity.Value, actionId, action: action); + } + private void OnInit(EntityUid uid, ActionsContainerComponent component, ComponentInit args) { component.Container = _container.EnsureContainer(uid, ActionsContainerComponent.ContainerId); @@ -171,13 +253,7 @@ private void OnEntityRemoved(EntityUid uid, ActionsContainerComponent component, var ev = new ActionRemovedEvent(args.Entity, data); RaiseLocalEvent(uid, ref ev); - - if (_netMan.IsServer) - { - // TODO Actions - // log an error or warning here once gibbing code is fixed. - QueueDel(args.Entity); - } + data.Container = null; } } diff --git a/Content.Shared/Actions/SharedActionsSystem.cs b/Content.Shared/Actions/SharedActionsSystem.cs index 86379277e23..8d2c8c28e3e 100644 --- a/Content.Shared/Actions/SharedActionsSystem.cs +++ b/Content.Shared/Actions/SharedActionsSystem.cs @@ -586,9 +586,16 @@ public void RemoveAction(EntityUid performer, EntityUid? actionId, ActionsCompon if (!ResolveActionData(actionId, ref action)) return; + if (action.AttachedEntity != performer) + { + Log.Error($"Attempted to remove an action {ToPrettyString(actionId)} from an entity that it was never attached to: {ToPrettyString(performer)}"); + return; + } + if (!Resolve(performer, ref comp, false)) { - DebugTools.AssertNull(action.AttachedEntity); + DebugTools.Assert(action.AttachedEntity == null || TerminatingOrDeleted(action.AttachedEntity.Value)); + action.AttachedEntity = null; return; } @@ -599,7 +606,6 @@ public void RemoveAction(EntityUid performer, EntityUid? actionId, ActionsCompon return; } - DebugTools.Assert(action.AttachedEntity == performer); comp.Actions.Remove(actionId.Value); action.AttachedEntity = null; Dirty(actionId.Value, action); From 0afc03558508060a7de6361b6a513e1441049d3c Mon Sep 17 00:00:00 2001 From: Colin-Tel <113523727+Colin-Tel@users.noreply.github.com> Date: Wed, 11 Oct 2023 16:49:49 -0500 Subject: [PATCH 09/29] Prettified human_hair.rsi meta.json (#20873) * Prettified human_hair.rsi meta.json * Update meta.json removed delay field --- .../Customization/human_hair.rsi/meta.json | 748 +++++++++++++++++- 1 file changed, 747 insertions(+), 1 deletion(-) diff --git a/Resources/Textures/Mobs/Customization/human_hair.rsi/meta.json b/Resources/Textures/Mobs/Customization/human_hair.rsi/meta.json index 142fc9c1aa3..9b7d68a1671 100644 --- a/Resources/Textures/Mobs/Customization/human_hair.rsi/meta.json +++ b/Resources/Textures/Mobs/Customization/human_hair.rsi/meta.json @@ -1 +1,747 @@ -{"version":1,"size":{"x":32,"y":32},"copyright":"Taken from https://github.com/tgstation/tgstation/blob/05ec94e46349c35e29ca91e5e97d0c88ae26ad44/icons/mob/species/human/human_face.dmi , resprited by Alekshhh, a modified by potato1234x","license":"CC-BY-SA-3.0","states":[{"name":"80s","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"a","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"afro","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"afro2","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"antenna","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"b","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"baldfade","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"bedhead","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"bedheadv2","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"bedheadv3","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"beehive","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"beehivev2","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"bigafro","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"bigflattop","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"bigpompadour","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"bob","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"bob2","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"bob4","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"bobcurl","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"bobcut","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"boddicker","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"bowlcut","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"bowlcut2","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"braid","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"braid2","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"braided","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"braidfront","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"braidtail","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"bun","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"bun3","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"bunhead2","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"business","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"business2","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"business3","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"business4","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"buzzcut","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"c","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"classicafro","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"classicbigafro","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"classiccia","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"classicfloorlength_bedhead","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"classicmodern","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"classicmulder","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"classicwisp","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"cia","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"coffeehouse","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"combover","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"cornrowbraid","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"cornrowbun","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"cornrows","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"cornrows2","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"cornrowtail","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"country","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"crewcut","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"curls","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"d","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"dandypompadour","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"devilock","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"doublebun","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"dreads","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"drillhair","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"drillhairextended","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"drillruru","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"e","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"emo","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"emo2","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"emofringe","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"f","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"father","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"feather","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"flair","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"floorlength_bedhead","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"fringetail","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"gelled","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"gentle","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"halfbang","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"halfbang2","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"halfshaved","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"hbraid","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"hedgehog","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"highfade","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"highponytail","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"himecut","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"himecut2","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"himeup","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"hitop","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"jade","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"jensen","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"joestar","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"kagami","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"keanu","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"kusanagi","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"largebun","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"lbangs","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"long","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"long2","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"long3","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"long_bedhead","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"longemo","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"longest","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"longest2","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"longfringe","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"longovereye","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"longsidepart","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"longstraightponytail","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"lowfade","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"manbun","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"medfade","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"megaeyebrows","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"messy","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"modern","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"mulder","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"nitori","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"nofade","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"odango","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"ombre","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"oneshoulder","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"oxton","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"part","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"parted","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"pigtails","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"pigtails2","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"pixie","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"pompadour","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"ponytail","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"ponytail2","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"ponytail3","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"ponytail4","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"ponytail5","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"ponytail6","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"ponytail7","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"poofy","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"protagonist","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"quiff","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"reversemohawk","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"ronin","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"rosa","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"sargeant","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"shaved","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"shavedmohawk","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"shavedpart","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"shortbangs","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"shortbraid","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"shorthair2","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"shorthair3","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"shorthairg","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"shorthime","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"shortovereye","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"sidecut","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"sidetail","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"sidetail2","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"sidetail3","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"sidetail4","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"skinhead","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"spikey","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"spiky","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"spiky2","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"spikyponytail","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"stail","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"swept","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"swept2","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"shoulderlengthovereye","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"thinning","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"thinningfront","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"thinningrear","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"tightbun","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"topknot","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"tressshoulder","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"trimflat","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"trimmed","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"twintail","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"twostrands","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"undercut","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"undercutleft","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"undercutright","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"unkept","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"unshaven_mohawk","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"updo","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"veryshortovereyealternate","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"vlong","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"vlongfringe","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"volaju","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"wisp","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]},{"name":"bald","directions":4,"delays":[[1.0],[1.0],[1.0],[1.0]]}]} +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "copyright": "Taken from https://github.com/tgstation/tgstation/blob/05ec94e46349c35e29ca91e5e97d0c88ae26ad44/icons/mob/species/human/human_face.dmi , resprited by Alekshhh, a modified by potato1234x", + "license": "CC-BY-SA-3.0", + "states": [ + { + "name": "80s", + "directions": 4 + }, + { + "name": "a", + "directions": 4 + }, + { + "name": "afro", + "directions": 4 + }, + { + "name": "afro2", + "directions": 4 + }, + { + "name": "antenna", + "directions": 4 + }, + { + "name": "b", + "directions": 4 + }, + { + "name": "baldfade", + "directions": 4 + }, + { + "name": "bedhead", + "directions": 4 + }, + { + "name": "bedheadv2", + "directions": 4 + }, + { + "name": "bedheadv3", + "directions": 4 + }, + { + "name": "beehive", + "directions": 4 + }, + { + "name": "beehivev2", + "directions": 4 + }, + { + "name": "bigafro", + "directions": 4 + }, + { + "name": "bigflattop", + "directions": 4 + }, + { + "name": "bigpompadour", + "directions": 4 + }, + { + "name": "bob", + "directions": 4 + }, + { + "name": "bob2", + "directions": 4 + }, + { + "name": "bob4", + "directions": 4 + }, + { + "name": "bobcurl", + "directions": 4 + }, + { + "name": "bobcut", + "directions": 4 + }, + { + "name": "boddicker", + "directions": 4 + }, + { + "name": "bowlcut", + "directions": 4 + }, + { + "name": "bowlcut2", + "directions": 4 + }, + { + "name": "braid", + "directions": 4 + }, + { + "name": "braid2", + "directions": 4 + }, + { + "name": "braided", + "directions": 4 + }, + { + "name": "braidfront", + "directions": 4 + }, + { + "name": "braidtail", + "directions": 4 + }, + { + "name": "bun", + "directions": 4 + }, + { + "name": "bun3", + "directions": 4 + }, + { + "name": "bunhead2", + "directions": 4 + }, + { + "name": "business", + "directions": 4 + }, + { + "name": "business2", + "directions": 4 + }, + { + "name": "business3", + "directions": 4 + }, + { + "name": "business4", + "directions": 4 + }, + { + "name": "buzzcut", + "directions": 4 + }, + { + "name": "c", + "directions": 4 + }, + { + "name": "classicafro", + "directions": 4 + }, + { + "name": "classicbigafro", + "directions": 4 + }, + { + "name": "classiccia", + "directions": 4 + }, + { + "name": "classicfloorlength_bedhead", + "directions": 4 + }, + { + "name": "classicmodern", + "directions": 4 + }, + { + "name": "classicmulder", + "directions": 4 + }, + { + "name": "classicwisp", + "directions": 4 + }, + { + "name": "cia", + "directions": 4 + }, + { + "name": "coffeehouse", + "directions": 4 + }, + { + "name": "combover", + "directions": 4 + }, + { + "name": "cornrowbraid", + "directions": 4 + }, + { + "name": "cornrowbun", + "directions": 4 + }, + { + "name": "cornrows", + "directions": 4 + }, + { + "name": "cornrows2", + "directions": 4 + }, + { + "name": "cornrowtail", + "directions": 4 + }, + { + "name": "country", + "directions": 4 + }, + { + "name": "crewcut", + "directions": 4 + }, + { + "name": "curls", + "directions": 4 + }, + { + "name": "d", + "directions": 4 + }, + { + "name": "dandypompadour", + "directions": 4 + }, + { + "name": "devilock", + "directions": 4 + }, + { + "name": "doublebun", + "directions": 4 + }, + { + "name": "dreads", + "directions": 4 + }, + { + "name": "drillhair", + "directions": 4 + }, + { + "name": "drillhairextended", + "directions": 4 + }, + { + "name": "drillruru", + "directions": 4 + }, + { + "name": "e", + "directions": 4 + }, + { + "name": "emo", + "directions": 4 + }, + { + "name": "emo2", + "directions": 4 + }, + { + "name": "emofringe", + "directions": 4 + }, + { + "name": "f", + "directions": 4 + }, + { + "name": "father", + "directions": 4 + }, + { + "name": "feather", + "directions": 4 + }, + { + "name": "flair", + "directions": 4 + }, + { + "name": "floorlength_bedhead", + "directions": 4 + }, + { + "name": "fringetail", + "directions": 4 + }, + { + "name": "gelled", + "directions": 4 + }, + { + "name": "gentle", + "directions": 4 + }, + { + "name": "halfbang", + "directions": 4 + }, + { + "name": "halfbang2", + "directions": 4 + }, + { + "name": "halfshaved", + "directions": 4 + }, + { + "name": "hbraid", + "directions": 4 + }, + { + "name": "hedgehog", + "directions": 4 + }, + { + "name": "highfade", + "directions": 4 + }, + { + "name": "highponytail", + "directions": 4 + }, + { + "name": "himecut", + "directions": 4 + }, + { + "name": "himecut2", + "directions": 4 + }, + { + "name": "himeup", + "directions": 4 + }, + { + "name": "hitop", + "directions": 4 + }, + { + "name": "jade", + "directions": 4 + }, + { + "name": "jensen", + "directions": 4 + }, + { + "name": "joestar", + "directions": 4 + }, + { + "name": "kagami", + "directions": 4 + }, + { + "name": "keanu", + "directions": 4 + }, + { + "name": "kusanagi", + "directions": 4 + }, + { + "name": "largebun", + "directions": 4 + }, + { + "name": "lbangs", + "directions": 4 + }, + { + "name": "long", + "directions": 4 + }, + { + "name": "long2", + "directions": 4 + }, + { + "name": "long3", + "directions": 4 + }, + { + "name": "long_bedhead", + "directions": 4 + }, + { + "name": "longemo", + "directions": 4 + }, + { + "name": "longest", + "directions": 4 + }, + { + "name": "longest2", + "directions": 4 + }, + { + "name": "longfringe", + "directions": 4 + }, + { + "name": "longovereye", + "directions": 4 + }, + { + "name": "longsidepart", + "directions": 4 + }, + { + "name": "longstraightponytail", + "directions": 4 + }, + { + "name": "lowfade", + "directions": 4 + }, + { + "name": "manbun", + "directions": 4 + }, + { + "name": "medfade", + "directions": 4 + }, + { + "name": "megaeyebrows", + "directions": 4 + }, + { + "name": "messy", + "directions": 4 + }, + { + "name": "modern", + "directions": 4 + }, + { + "name": "mulder", + "directions": 4 + }, + { + "name": "nitori", + "directions": 4 + }, + { + "name": "nofade", + "directions": 4 + }, + { + "name": "odango", + "directions": 4 + }, + { + "name": "ombre", + "directions": 4 + }, + { + "name": "oneshoulder", + "directions": 4 + }, + { + "name": "oxton", + "directions": 4 + }, + { + "name": "part", + "directions": 4 + }, + { + "name": "parted", + "directions": 4 + }, + { + "name": "pigtails", + "directions": 4 + }, + { + "name": "pigtails2", + "directions": 4 + }, + { + "name": "pixie", + "directions": 4 + }, + { + "name": "pompadour", + "directions": 4 + }, + { + "name": "ponytail", + "directions": 4 + }, + { + "name": "ponytail2", + "directions": 4 + }, + { + "name": "ponytail3", + "directions": 4 + }, + { + "name": "ponytail4", + "directions": 4 + }, + { + "name": "ponytail5", + "directions": 4 + }, + { + "name": "ponytail6", + "directions": 4 + }, + { + "name": "ponytail7", + "directions": 4 + }, + { + "name": "poofy", + "directions": 4 + }, + { + "name": "protagonist", + "directions": 4 + }, + { + "name": "quiff", + "directions": 4 + }, + { + "name": "reversemohawk", + "directions": 4 + }, + { + "name": "ronin", + "directions": 4 + }, + { + "name": "rosa", + "directions": 4 + }, + { + "name": "sargeant", + "directions": 4 + }, + { + "name": "shaved", + "directions": 4 + }, + { + "name": "shavedmohawk", + "directions": 4 + }, + { + "name": "shavedpart", + "directions": 4 + }, + { + "name": "shortbangs", + "directions": 4 + }, + { + "name": "shortbraid", + "directions": 4 + }, + { + "name": "shorthair2", + "directions": 4 + }, + { + "name": "shorthair3", + "directions": 4 + }, + { + "name": "shorthairg", + "directions": 4 + }, + { + "name": "shorthime", + "directions": 4 + }, + { + "name": "shortovereye", + "directions": 4 + }, + { + "name": "sidecut", + "directions": 4 + }, + { + "name": "sidetail", + "directions": 4 + }, + { + "name": "sidetail2", + "directions": 4 + }, + { + "name": "sidetail3", + "directions": 4 + }, + { + "name": "sidetail4", + "directions": 4 + }, + { + "name": "skinhead", + "directions": 4 + }, + { + "name": "spikey", + "directions": 4 + }, + { + "name": "spiky", + "directions": 4 + }, + { + "name": "spiky2", + "directions": 4 + }, + { + "name": "spikyponytail", + "directions": 4 + }, + { + "name": "stail", + "directions": 4 + }, + { + "name": "swept", + "directions": 4 + }, + { + "name": "swept2", + "directions": 4 + }, + { + "name": "shoulderlengthovereye", + "directions": 4 + }, + { + "name": "thinning", + "directions": 4 + }, + { + "name": "thinningfront", + "directions": 4 + }, + { + "name": "thinningrear", + "directions": 4 + }, + { + "name": "tightbun", + "directions": 4 + }, + { + "name": "topknot", + "directions": 4 + }, + { + "name": "tressshoulder", + "directions": 4 + }, + { + "name": "trimflat", + "directions": 4 + }, + { + "name": "trimmed", + "directions": 4 + }, + { + "name": "twintail", + "directions": 4 + }, + { + "name": "twostrands", + "directions": 4 + }, + { + "name": "undercut", + "directions": 4 + }, + { + "name": "undercutleft", + "directions": 4 + }, + { + "name": "undercutright", + "directions": 4 + }, + { + "name": "unkept", + "directions": 4 + }, + { + "name": "unshaven_mohawk", + "directions": 4 + }, + { + "name": "updo", + "directions": 4 + }, + { + "name": "veryshortovereyealternate", + "directions": 4 + }, + { + "name": "vlong", + "directions": 4 + }, + { + "name": "vlongfringe", + "directions": 4 + }, + { + "name": "volaju", + "directions": 4 + }, + { + "name": "wisp", + "directions": 4 + }, + { + "name": "bald", + "directions": 4 + } + ] + } \ No newline at end of file From b53f10fbb268bb1cdd7e3a2256cd8cc424485cd3 Mon Sep 17 00:00:00 2001 From: DrSmugleaf Date: Wed, 11 Oct 2023 15:14:00 -0700 Subject: [PATCH 10/29] Fix not removing RevolutionaryRoleComponent from minds on mindshield application (#20832) * Fix not removing RevolutionaryRoleComponent from minds on mindshield application * Simplify other part of the mindshield code * Fix being able to mindshield head revs * Other way around --- Content.Server/Mindshield/MindShieldSystem.cs | 31 ++++++++++--------- .../SharedRevolutionarySystem.cs | 14 +++++---- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/Content.Server/Mindshield/MindShieldSystem.cs b/Content.Server/Mindshield/MindShieldSystem.cs index 714055bd942..bfca6c008ea 100644 --- a/Content.Server/Mindshield/MindShieldSystem.cs +++ b/Content.Server/Mindshield/MindShieldSystem.cs @@ -1,13 +1,13 @@ -using Content.Shared.Mindshield.Components; -using Content.Shared.Revolutionary.Components; -using Content.Server.Popups; -using Content.Shared.Database; using Content.Server.Administration.Logs; using Content.Server.Mind; -using Content.Shared.Implants; -using Content.Shared.Tag; +using Content.Server.Popups; using Content.Server.Roles; +using Content.Shared.Database; +using Content.Shared.Implants; using Content.Shared.Implants.Components; +using Content.Shared.Mindshield.Components; +using Content.Shared.Revolutionary.Components; +using Content.Shared.Tag; namespace Content.Server.Mindshield; @@ -39,25 +39,26 @@ public void ImplantCheck(EntityUid uid, SubdermalImplantComponent comp, ref Impl if (_tag.HasTag(ev.Implant, MindShieldTag) && ev.Implanted != null) { EnsureComp(ev.Implanted.Value); - MindShieldRemovalCheck(ev.Implanted, ev.Implant); + MindShieldRemovalCheck(ev.Implanted.Value, ev.Implant); } } /// /// Checks if the implanted person was a Rev or Head Rev and remove role or destroy mindshield respectively. /// - public void MindShieldRemovalCheck(EntityUid? implanted, EntityUid implant) + public void MindShieldRemovalCheck(EntityUid implanted, EntityUid implant) { - if (HasComp(implanted) && !HasComp(implanted)) + if (HasComp(implanted)) { - _mindSystem.TryGetMind(implanted.Value, out var mindId, out _); - _adminLogManager.Add(LogType.Mind, LogImpact.Medium, $"{ToPrettyString(implanted.Value)} was deconverted due to being implanted with a Mindshield."); - _roleSystem.MindTryRemoveRole(mindId); + _popupSystem.PopupEntity(Loc.GetString("head-rev-break-mindshield"), implanted); + QueueDel(implant); + return; } - else if (HasComp(implanted)) + + if (_mindSystem.TryGetMind(implanted, out var mindId, out _) && + _roleSystem.MindTryRemoveRole(mindId)) { - _popupSystem.PopupEntity(Loc.GetString("head-rev-break-mindshield"), implanted.Value); - QueueDel(implant); + _adminLogManager.Add(LogType.Mind, LogImpact.Medium, $"{ToPrettyString(implanted)} was deconverted due to being implanted with a Mindshield."); } } } diff --git a/Content.Shared/Revolutionary/SharedRevolutionarySystem.cs b/Content.Shared/Revolutionary/SharedRevolutionarySystem.cs index e2a8192716c..1399b116e0f 100644 --- a/Content.Shared/Revolutionary/SharedRevolutionarySystem.cs +++ b/Content.Shared/Revolutionary/SharedRevolutionarySystem.cs @@ -1,7 +1,7 @@ -using Content.Shared.Revolutionary.Components; using Content.Shared.IdentityManagement; using Content.Shared.Mindshield.Components; using Content.Shared.Popups; +using Content.Shared.Revolutionary.Components; using Content.Shared.Stunnable; namespace Content.Shared.Revolutionary; @@ -22,7 +22,13 @@ public override void Initialize() /// private void MindShieldImplanted(EntityUid uid, MindShieldComponent comp, MapInitEvent init) { - if (HasComp(uid) && !HasComp(uid)) + if (HasComp(uid)) + { + RemCompDeferred(uid); + return; + } + + if (HasComp(uid)) { var stunTime = TimeSpan.FromSeconds(4); var name = Identity.Entity(uid, EntityManager); @@ -30,9 +36,5 @@ private void MindShieldImplanted(EntityUid uid, MindShieldComponent comp, MapIni _sharedStun.TryParalyze(uid, stunTime, true); _popupSystem.PopupEntity(Loc.GetString("rev-break-control", ("name", name)), uid); } - else if (HasComp(uid)) - { - RemCompDeferred(uid); - } } } From 1508f513bf883ad8047dd952a0571258c539b9a1 Mon Sep 17 00:00:00 2001 From: Psychpsyo <60073468+Psychpsyo@users.noreply.github.com> Date: Thu, 12 Oct 2023 00:57:09 +0200 Subject: [PATCH 11/29] Jittering System Fix + Cleanup (#20921) --- Content.Client/Jittering/JitteringSystem.cs | 21 ++++++++----------- .../Jittering/SharedJitteringSystem.cs | 2 +- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/Content.Client/Jittering/JitteringSystem.cs b/Content.Client/Jittering/JitteringSystem.cs index 032eb3e18f2..41f20634ab5 100644 --- a/Content.Client/Jittering/JitteringSystem.cs +++ b/Content.Client/Jittering/JitteringSystem.cs @@ -1,13 +1,7 @@ -using System; -using System.Collections.Immutable; using System.Numerics; using Content.Shared.Jittering; using Robust.Client.Animations; using Robust.Client.GameObjects; -using Robust.Shared.Animations; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Maths; using Robust.Shared.Random; namespace Content.Client.Jittering @@ -15,6 +9,7 @@ namespace Content.Client.Jittering public sealed class JitteringSystem : SharedJitteringSystem { [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly AnimationPlayerSystem _animationPlayer = default!; private readonly float[] _sign = { -1, 1 }; private readonly string _jitterAnimationKey = "jittering"; @@ -35,13 +30,13 @@ private void OnStartup(EntityUid uid, JitteringComponent jittering, ComponentSta var animationPlayer = EntityManager.EnsureComponent(uid); - animationPlayer.Play(GetAnimation(jittering, sprite), _jitterAnimationKey); + _animationPlayer.Play(animationPlayer, GetAnimation(jittering, sprite), _jitterAnimationKey); } private void OnShutdown(EntityUid uid, JitteringComponent jittering, ComponentShutdown args) { if (EntityManager.TryGetComponent(uid, out AnimationPlayerComponent? animationPlayer)) - animationPlayer.Stop(_jitterAnimationKey); + _animationPlayer.Stop(animationPlayer, _jitterAnimationKey); if (EntityManager.TryGetComponent(uid, out SpriteComponent? sprite)) sprite.Offset = Vector2.Zero; @@ -52,9 +47,9 @@ private void OnAnimationCompleted(EntityUid uid, JitteringComponent jittering, A if(args.Key != _jitterAnimationKey) return; - if(EntityManager.TryGetComponent(uid, out AnimationPlayerComponent? animationPlayer) + if (EntityManager.TryGetComponent(uid, out AnimationPlayerComponent? animationPlayer) && EntityManager.TryGetComponent(uid, out SpriteComponent? sprite)) - animationPlayer.Play(GetAnimation(jittering, sprite), _jitterAnimationKey); + _animationPlayer.Play(animationPlayer, GetAnimation(jittering, sprite), _jitterAnimationKey); } private Animation GetAnimation(JitteringComponent jittering, SpriteComponent sprite) @@ -77,8 +72,10 @@ private Animation GetAnimation(JitteringComponent jittering, SpriteComponent spr offset.Y *= -1; } - // Animation length shouldn't be too high so we will cap it at 2 seconds... - var length = Math.Min((1f/jittering.Frequency), 2f); + var length = 0f; + // avoid dividing by 0 so animations don't try to be infinitely long + if (jittering.Frequency > 0) + length = 1f / jittering.Frequency; jittering.LastJitter = offset; diff --git a/Content.Shared/Jittering/SharedJitteringSystem.cs b/Content.Shared/Jittering/SharedJitteringSystem.cs index 327a4521752..1ac8413375e 100644 --- a/Content.Shared/Jittering/SharedJitteringSystem.cs +++ b/Content.Shared/Jittering/SharedJitteringSystem.cs @@ -72,7 +72,7 @@ public void AddJitter(EntityUid uid, float amplitude = 10f, float frequency = 4f var jitter = EnsureComp(uid); jitter.Amplitude = amplitude; jitter.Frequency = frequency; - Dirty(jitter); + Dirty(uid, jitter); } } } From 1e1433374a30baf48b3d4082f2d3cefb300c6468 Mon Sep 17 00:00:00 2001 From: DrSmugleaf Date: Wed, 11 Oct 2023 23:30:52 -0700 Subject: [PATCH 12/29] Update Patrons list in the credits (#20937) --- Resources/Credits/Patrons.yml | 226 +++++++++++++++++++--------------- 1 file changed, 128 insertions(+), 98 deletions(-) diff --git a/Resources/Credits/Patrons.yml b/Resources/Credits/Patrons.yml index 99f51c15676..acb9eb229a5 100644 --- a/Resources/Credits/Patrons.yml +++ b/Resources/Credits/Patrons.yml @@ -1,160 +1,190 @@ -- Name: AAbcoi - Tier: Syndicate Agent -- Name: tomhendo - Tier: Syndicate Agent -- Name: M4shr00m - Tier: Syndicate Agent -- Name: Aleksey - Tier: Syndicate Agent -- Name: Skarlet - Tier: Revolutionary -- Name: March - Tier: Syndicate Agent -- Name: Александр Белошапка - Tier: Revolutionary -- Name: Dave +- Name: "Tomeno" Tier: Revolutionary -- Name: Akira Shiroyanagi [無情な男] +- Name: "Daniel Thompson" Tier: Revolutionary -- Name: Alix Shepard +- Name: "Farewell Fire" + Tier: Syndicate Agent +- Name: "MetalClone" Tier: Nuclear Operative -- Name: JhenMaster +- Name: "CPM311" Tier: Revolutionary -- Name: Lieutenant Colonel Orangejuice - Tier: Syndicate Agent -- Name: TheGoldElite +- Name: "Bobberunio" Tier: Revolutionary -- Name: Gnomo - Tier: Nuclear Operative -- Name: Never Solus +- Name: "vifs_vestige" Tier: Syndicate Agent -- Name: Ari. - Tier: Revolutionary -- Name: russell +- Name: "Anthony Fleck" Tier: Nuclear Operative -- Name: Kevin +- Name: "Nico Thate" + Tier: Revolutionary +- Name: "Zandario" Tier: Nuclear Operative -- Name: DubzyVEVO +- Name: "Darren Brady" Tier: Revolutionary -- Name: ikamuse johnson +- Name: "DramaBuns" Tier: Revolutionary -- Name: Saphire +- Name: "Ethan Keller" Tier: Revolutionary -- Name: Gavin Simmons +- Name: "Eric VW" Tier: Revolutionary -- Name: Tamora Droppa +- Name: "Joshington Awesomahee" Tier: Revolutionary -- Name: Gaxeer - Tier: Syndicate Agent -- Name: rosysyntax +- Name: "Altana" Tier: Revolutionary -- Name: Scott MacCombie +- Name: "clyf" Tier: Nuclear Operative -- Name: Jeremy Hernandez +- Name: "spinnermaster" Tier: Syndicate Agent -- Name: Dan - Tier: Syndicate Agent -- Name: Carbonhell +- Name: "Will M." + Tier: Revolutionary +- Name: "The Hateful Flesh" + Tier: Revolutionary +- Name: "Viridian" + Tier: Revolutionary +- Name: "Nicholas Hillblom" + Tier: Revolutionary +- Name: "Austin Nelson" Tier: Syndicate Agent -- Name: Rasmus Cedergren +- Name: "John Edward Hamilton Barchard" + Tier: Revolutionary +- Name: "Cormos Lemming" + Tier: Nuclear Operative +- Name: "Hamcha" Tier: Revolutionary -- Name: Pasemi +- Name: "Peter \"Azmond\" Newhouse" Tier: Revolutionary -- Name: Unknown Kiwi +- Name: "Zakanater 19" Tier: Revolutionary -- Name: Mitchell Marry +- Name: "Kris Piper" Tier: Revolutionary -- Name: Matouš Hrdlička +- Name: "Mikhail" + Tier: Revolutionary +- Name: "osborn" + Tier: Syndicate Agent +- Name: "Uinseann" + Tier: Revolutionary +- Name: "Brandon Campbell" Tier: Nuclear Operative -- Name: BokChoy +- Name: "KevKev" + Tier: Revolutionary +- Name: "Jacob Schramm" + Tier: Revolutionary +- Name: "Matouš Hrdlička" Tier: Nuclear Operative -- Name: KevKev +- Name: "Pasemi" + Tier: Revolutionary +- Name: "Late Fox" Tier: Revolutionary -- Name: Gothryd +- Name: "Dan" Tier: Syndicate Agent -- Name: Brandon Campbell +- Name: "Scott MacCombie" Tier: Nuclear Operative -- Name: Matthew C Miklaucic +- Name: "Gaxeer" + Tier: Syndicate Agent +- Name: "Tamora Droppa" Tier: Revolutionary -- Name: lapatison +- Name: "Gavin Simmons" + Tier: Syndicate Agent +- Name: "Saphire" Tier: Revolutionary -- Name: Uinseann +- Name: "DubzyVEVO" Tier: Revolutionary -- Name: osborn +- Name: "Never Solus" Tier: Syndicate Agent -- Name: Ramiro Agis +- Name: "Gnomo" + Tier: Nuclear Operative +- Name: "TheGoldElite" Tier: Revolutionary -- Name: Mikhail +- Name: "JhenMaster" Tier: Revolutionary -- Name: liltenhead +- Name: "Akira" Tier: Revolutionary -- Name: Kris Piper +- Name: "Dave" Tier: Revolutionary -- Name: Peter "Azmond" Newhouse +- Name: "Александр Белошапка" Tier: Revolutionary -- Name: Hamcha +- Name: "Gordod" + Tier: Syndicate Agent +- Name: "tomhendo" + Tier: Syndicate Agent +- Name: "Odin The Wanderer" Tier: Revolutionary -- Name: Oxyclean114 +- Name: "Wallace Megas" Tier: Revolutionary -- Name: Cormos Lemming - Tier: Nuclear Operative -- Name: John Edward Hamilton Barchard +- Name: "Vandell" Tier: Revolutionary -- Name: Wrexbe +- Name: "Enricoc3l" Tier: Revolutionary -- Name: Austin Nelson +- Name: "DadNotTheBelt" + Tier: Nuclear Operative +- Name: "David" + Tier: Revolutionary +- Name: "Watson Whittington" Tier: Syndicate Agent -- Name: AquaDraco +- Name: "Raw Toast" Tier: Revolutionary -- Name: Nicholas Hillblom +- Name: "Tim Foley" + Tier: Syndicate Agent +- Name: "Blight" + Tier: Syndicate Agent +- Name: "Katarn" Tier: Revolutionary -- Name: Florian +- Name: "eric156" Tier: Revolutionary -- Name: Viridian +- Name: "Glenn Olsen" Tier: Syndicate Agent -- Name: Daskata - Tier: Nuclear Operative -- Name: The Hateful Flesh +- Name: "Constellations" + Tier: Syndicate Agent +- Name: "Charles Baron" + Tier: Syndicate Agent +- Name: "Shaina Gibson" Tier: Revolutionary -- Name: Will M. +- Name: "Wolfie" Tier: Revolutionary -- Name: spinnermaster - Tier: Nuclear Operative -- Name: clyf +- Name: "Alexandre Courtin" + Tier: Revolutionary +- Name: "Geekyhobo2" + Tier: Revolutionary +- Name: "Nicholas" Tier: Nuclear Operative -- Name: Robin Rottstock +- Name: "GeneralMarty" Tier: Revolutionary -- Name: Altana +- Name: "HCG" Tier: Revolutionary -- Name: Durp +- Name: "ShaunTexas" + Tier: Syndicate Agent +- Name: "Fallcon" Tier: Revolutionary -- Name: Joshington Awesomahee +- Name: "Malachi Housewright" Tier: Revolutionary -- Name: Eric VW +- Name: "Petalmeat" + Tier: Syndicate Agent +- Name: "Jakub Kędziora" + Tier: Syndicate Agent +- Name: "Adam Smedstad" Tier: Revolutionary -- Name: Evan Armstrong +- Name: "oBerry" + Tier: Nuclear Operative +- Name: "DireBoar" Tier: Revolutionary -- Name: Mono +- Name: "Ignoramis" Tier: Revolutionary -- Name: Ethan Keller +- Name: "Repo" + Tier: Nuclear Operative +- Name: "612" Tier: Revolutionary -- Name: DramaBuns +- Name: "Higgtastic" Tier: Revolutionary -- Name: Darren Brady +- Name: "Sandvich enjoyer" Tier: Revolutionary -- Name: Zandario - Tier: Nuclear Operative -- Name: Anthony Fleck - Tier: Nuclear Operative -- Name: vifs_vestige +- Name: "Mihailo Trickovic" Tier: Syndicate Agent -- Name: Bobberunio +- Name: "Vice Emargo" Tier: Revolutionary -- Name: CPM311 +- Name: "awndrssk" Tier: Revolutionary -- Name: Farewell Fire - Tier: Syndicate Agent -- Name: Daniel Thompson +- Name: "François Desautels" + Tier: Revolutionary +- Name: "Christian Hicks" Tier: Revolutionary -- Name: Tomeno +- Name: "MasterFurret" Tier: Revolutionary From 388e424a17f8c964fb5696377c6bfc58e99998bc Mon Sep 17 00:00:00 2001 From: DrSmugleaf Date: Thu, 12 Oct 2023 00:33:30 -0700 Subject: [PATCH 13/29] Add project to update Patrons.yml from a csv file containing Patreon webhooks, add missing Patrons (#20942) --- Content.PatreonParser/Attributes.cs | 15 +++ .../Content.PatreonParser.csproj | 14 ++ .../CurrentlyEntitledTiers.cs | 9 ++ Content.PatreonParser/Data.cs | 18 +++ Content.PatreonParser/Included.cs | 15 +++ Content.PatreonParser/Patron.cs | 3 + Content.PatreonParser/Program.cs | 125 ++++++++++++++++++ Content.PatreonParser/Relationships.cs | 9 ++ Content.PatreonParser/Root.cs | 12 ++ Content.PatreonParser/Row.cs | 19 +++ Content.PatreonParser/TierData.cs | 12 ++ Resources/Credits/Patrons.yml | 6 + SpaceStation14.sln | 10 ++ 13 files changed, 267 insertions(+) create mode 100644 Content.PatreonParser/Attributes.cs create mode 100644 Content.PatreonParser/Content.PatreonParser.csproj create mode 100644 Content.PatreonParser/CurrentlyEntitledTiers.cs create mode 100644 Content.PatreonParser/Data.cs create mode 100644 Content.PatreonParser/Included.cs create mode 100644 Content.PatreonParser/Patron.cs create mode 100644 Content.PatreonParser/Program.cs create mode 100644 Content.PatreonParser/Relationships.cs create mode 100644 Content.PatreonParser/Root.cs create mode 100644 Content.PatreonParser/Row.cs create mode 100644 Content.PatreonParser/TierData.cs diff --git a/Content.PatreonParser/Attributes.cs b/Content.PatreonParser/Attributes.cs new file mode 100644 index 00000000000..d3ebeb5f7c8 --- /dev/null +++ b/Content.PatreonParser/Attributes.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Serialization; + +namespace Content.PatreonParser; + +public sealed class Attributes +{ + [JsonPropertyName("full_name")] + public string FullName = default!; + + [JsonPropertyName("pledge_relationship_start")] + public DateTime? PledgeRelationshipStart; + + [JsonPropertyName("title")] + public string Title = default!; +} diff --git a/Content.PatreonParser/Content.PatreonParser.csproj b/Content.PatreonParser/Content.PatreonParser.csproj new file mode 100644 index 00000000000..53b06b265b6 --- /dev/null +++ b/Content.PatreonParser/Content.PatreonParser.csproj @@ -0,0 +1,14 @@ + + + + Exe + net7.0 + enable + enable + + + + + + + diff --git a/Content.PatreonParser/CurrentlyEntitledTiers.cs b/Content.PatreonParser/CurrentlyEntitledTiers.cs new file mode 100644 index 00000000000..fd1747efda9 --- /dev/null +++ b/Content.PatreonParser/CurrentlyEntitledTiers.cs @@ -0,0 +1,9 @@ +using System.Text.Json.Serialization; + +namespace Content.PatreonParser; + +public sealed class CurrentlyEntitledTiers +{ + [JsonPropertyName("data")] + public List Data = default!; +} diff --git a/Content.PatreonParser/Data.cs b/Content.PatreonParser/Data.cs new file mode 100644 index 00000000000..cdc7d79bff5 --- /dev/null +++ b/Content.PatreonParser/Data.cs @@ -0,0 +1,18 @@ +using System.Text.Json.Serialization; + +namespace Content.PatreonParser; + +public sealed class Data +{ + [JsonPropertyName("id")] + public string Id = default!; + + [JsonPropertyName("type")] + public string Type = default!; + + [JsonPropertyName("attributes")] + public Attributes Attributes = default!; + + [JsonPropertyName("relationships")] + public Relationships Relationships = default!; +} diff --git a/Content.PatreonParser/Included.cs b/Content.PatreonParser/Included.cs new file mode 100644 index 00000000000..ec3363579b9 --- /dev/null +++ b/Content.PatreonParser/Included.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Serialization; + +namespace Content.PatreonParser; + +public sealed class Included +{ + [JsonPropertyName("id")] + public int Id; + + [JsonPropertyName("type")] + public string Type = default!; + + [JsonPropertyName("attributes")] + public Attributes Attributes = default!; +} diff --git a/Content.PatreonParser/Patron.cs b/Content.PatreonParser/Patron.cs new file mode 100644 index 00000000000..d9943a6a265 --- /dev/null +++ b/Content.PatreonParser/Patron.cs @@ -0,0 +1,3 @@ +namespace Content.PatreonParser; + +public readonly record struct Patron(string FullName, string TierName, DateTime Start); diff --git a/Content.PatreonParser/Program.cs b/Content.PatreonParser/Program.cs new file mode 100644 index 00000000000..60a320006fe --- /dev/null +++ b/Content.PatreonParser/Program.cs @@ -0,0 +1,125 @@ +using System.Globalization; +using System.Text.Json; +using System.Text.Json.Serialization; +using Content.PatreonParser; +using CsvHelper; +using CsvHelper.Configuration; +using static System.Environment; + +var repository = new DirectoryInfo(Directory.GetCurrentDirectory()).Parent!.Parent!.Parent!.Parent!; +var patronsPath = Path.Combine(repository.FullName, "Resources/Credits/Patrons.yml"); +if (!File.Exists(patronsPath)) +{ + Console.WriteLine($"File {patronsPath} not found."); + return; +} + +Console.WriteLine($"Updating {patronsPath}"); +Console.WriteLine("Is this correct? [Y/N]"); +var response = Console.ReadLine()?.ToUpper(); +if (response != "Y") +{ + Console.WriteLine("Exiting"); + return; +} + +var delimiter = ","; +var hasHeaderRecord = false; +var mode = CsvMode.RFC4180; +var escape = '\''; +Console.WriteLine($""" +Delimiter: {delimiter} +HasHeaderRecord: {hasHeaderRecord} +Mode: {mode} +Escape Character: {escape} +"""); + +Console.WriteLine("Enter the full path to the .csv file containing the Patreon webhook data:"); +var filePath = Console.ReadLine(); +if (filePath == null) +{ + Console.Write("No path given."); + return; +} + +var file = File.OpenRead(filePath); +var csvConfig = new CsvConfiguration(CultureInfo.InvariantCulture) +{ + Delimiter = delimiter, + HasHeaderRecord = hasHeaderRecord, + Mode = mode, + Escape = escape, +}; + +using var reader = new CsvReader(new StreamReader(file), csvConfig); + +// This does not handle tier name changes, but we haven't had any yet +var patrons = new Dictionary(); +var jsonOptions = new JsonSerializerOptions +{ + IncludeFields = true, + NumberHandling = JsonNumberHandling.AllowReadingFromString +}; + +// This assumes that the rows are already sorted by id +foreach (var record in reader.GetRecords()) +{ + if (record.Trigger == "members:create") + continue; + + var content = JsonSerializer.Deserialize(record.ContentJson, jsonOptions)!; + + var id = Guid.Parse(content.Data.Id); + patrons.Remove(id); + + var tiers = content.Data.Relationships.CurrentlyEntitledTiers.Data; + if (tiers.Count == 0) + continue; + else if (tiers.Count > 1) + throw new ArgumentException("Found more than one tier"); + + var tier = tiers[0]; + var tierName = content.Included.SingleOrDefault(i => i.Id == tier.Id && i.Type == tier.Type)?.Attributes.Title; + if (tierName == null) + continue; + + if (record.Trigger == "members:delete") + continue; + + var fullName = content.Data.Attributes.FullName.Trim(); + var pledgeStart = content.Data.Attributes.PledgeRelationshipStart; + + switch (record.Trigger) + { + case "members:create": + break; + case "members:delete": + break; + case "members:update": + patrons.Add(id, new Patron(fullName, tierName, pledgeStart!.Value)); + break; + case "members:pledge:create": + if (pledgeStart == null) + continue; + + patrons.Add(id, new Patron(fullName, tierName, pledgeStart.Value)); + break; + case "members:pledge:delete": + // Deleted pledge but still not expired, expired is handled earlier + patrons.Add(id, new Patron(fullName, tierName, pledgeStart!.Value)); + break; + case "members:pledge:update": + patrons.Add(id, new Patron(fullName, tierName, pledgeStart!.Value)); + break; + } +} + +var patronList = patrons.Values.ToList(); +patronList.Sort((a, b) => a.Start.CompareTo(b.Start)); +var yaml = patronList.Select(p => $""" +- Name: "{p.FullName.Replace("\"", "\\\"")}" + Tier: {p.TierName} +"""); +var output = string.Join(NewLine, yaml) + NewLine; +File.WriteAllText(patronsPath, output); +Console.WriteLine($"Updated {patronsPath} with {patronList.Count} patrons."); diff --git a/Content.PatreonParser/Relationships.cs b/Content.PatreonParser/Relationships.cs new file mode 100644 index 00000000000..f919f812f62 --- /dev/null +++ b/Content.PatreonParser/Relationships.cs @@ -0,0 +1,9 @@ +using System.Text.Json.Serialization; + +namespace Content.PatreonParser; + +public sealed class Relationships +{ + [JsonPropertyName("currently_entitled_tiers")] + public CurrentlyEntitledTiers CurrentlyEntitledTiers = default!; +} diff --git a/Content.PatreonParser/Root.cs b/Content.PatreonParser/Root.cs new file mode 100644 index 00000000000..2a772a12143 --- /dev/null +++ b/Content.PatreonParser/Root.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; + +namespace Content.PatreonParser; + +public sealed class Root +{ + [JsonPropertyName("data")] + public Data Data = default!; + + [JsonPropertyName("included")] + public List Included = default!; +} diff --git a/Content.PatreonParser/Row.cs b/Content.PatreonParser/Row.cs new file mode 100644 index 00000000000..f4d25046165 --- /dev/null +++ b/Content.PatreonParser/Row.cs @@ -0,0 +1,19 @@ +using CsvHelper.Configuration.Attributes; + +namespace Content.PatreonParser; + +// These need to be properties or CSVHelper will not find them +public sealed class Row +{ + [Name("Id"), Index(0)] + public int Id { get; set; } + + [Name("Trigger"), Index(1)] + public string Trigger { get; set; } = default!; + + [Name("Time"), Index(2)] + public DateTime Time { get; set; } + + [Name("Content"), Index(3)] + public string ContentJson { get; set; } = default!; +} diff --git a/Content.PatreonParser/TierData.cs b/Content.PatreonParser/TierData.cs new file mode 100644 index 00000000000..a840b21d359 --- /dev/null +++ b/Content.PatreonParser/TierData.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; + +namespace Content.PatreonParser; + +public sealed class TierData +{ + [JsonPropertyName("id")] + public int Id; + + [JsonPropertyName("type")] + public string Type = default!; +} diff --git a/Resources/Credits/Patrons.yml b/Resources/Credits/Patrons.yml index acb9eb229a5..427a8f62f09 100644 --- a/Resources/Credits/Patrons.yml +++ b/Resources/Credits/Patrons.yml @@ -58,6 +58,8 @@ Tier: Revolutionary - Name: "Mikhail" Tier: Revolutionary +- Name: "Ramiro Agis" + Tier: Revolutionary - Name: "osborn" Tier: Syndicate Agent - Name: "Uinseann" @@ -108,6 +110,8 @@ Tier: Syndicate Agent - Name: "Odin The Wanderer" Tier: Revolutionary +- Name: "tokie" + Tier: Nuclear Operative - Name: "Wallace Megas" Tier: Revolutionary - Name: "Vandell" @@ -130,6 +134,8 @@ Tier: Revolutionary - Name: "eric156" Tier: Revolutionary +- Name: "SHANE ALAN ZINDA" + Tier: Nuclear Operative - Name: "Glenn Olsen" Tier: Syndicate Agent - Name: "Constellations" diff --git a/SpaceStation14.sln b/SpaceStation14.sln index 2dc4c95508d..a94daa316dd 100644 --- a/SpaceStation14.sln +++ b/SpaceStation14.sln @@ -127,6 +127,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Robust.Shared.CompNetworkGe EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Robust.Serialization.Generator", "RobustToolbox\Robust.Serialization.Generator\Robust.Serialization.Generator.csproj", "{6FBF108E-5CB5-47DE-8D7E-B496ABA9E3E2}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Content.PatreonParser", "Content.PatreonParser\Content.PatreonParser.csproj", "{D97D8258-D915-4D1D-B1E3-1A8D00CF9EB5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -430,6 +432,14 @@ Global {6FBF108E-5CB5-47DE-8D7E-B496ABA9E3E2}.DebugOpt|Any CPU.Build.0 = Debug|Any CPU {6FBF108E-5CB5-47DE-8D7E-B496ABA9E3E2}.Tools|Any CPU.ActiveCfg = Debug|Any CPU {6FBF108E-5CB5-47DE-8D7E-B496ABA9E3E2}.Tools|Any CPU.Build.0 = Debug|Any CPU + {D97D8258-D915-4D1D-B1E3-1A8D00CF9EB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D97D8258-D915-4D1D-B1E3-1A8D00CF9EB5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D97D8258-D915-4D1D-B1E3-1A8D00CF9EB5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D97D8258-D915-4D1D-B1E3-1A8D00CF9EB5}.Release|Any CPU.Build.0 = Release|Any CPU + {D97D8258-D915-4D1D-B1E3-1A8D00CF9EB5}.DebugOpt|Any CPU.ActiveCfg = Debug|Any CPU + {D97D8258-D915-4D1D-B1E3-1A8D00CF9EB5}.DebugOpt|Any CPU.Build.0 = Debug|Any CPU + {D97D8258-D915-4D1D-B1E3-1A8D00CF9EB5}.Tools|Any CPU.ActiveCfg = Debug|Any CPU + {D97D8258-D915-4D1D-B1E3-1A8D00CF9EB5}.Tools|Any CPU.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 99f777ca9d98494f6da4f219c50e4e16473740e6 Mon Sep 17 00:00:00 2001 From: Kevin Zheng Date: Wed, 11 Oct 2023 23:35:31 -0800 Subject: [PATCH 14/29] Increase carbon dioxide poisoning damage (#20939) * Increase CO2 poisoning damage * Fix --- Resources/Prototypes/Reagents/gases.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Resources/Prototypes/Reagents/gases.yml b/Resources/Prototypes/Reagents/gases.yml index 9fa06f632c6..367ffa0894b 100644 --- a/Resources/Prototypes/Reagents/gases.yml +++ b/Resources/Prototypes/Reagents/gases.yml @@ -135,7 +135,13 @@ damage: types: Poison: - 0.2 + 0.8 + - !type:Oxygenate # carbon dioxide displaces oxygen from the bloodstream, causing asphyxiation + conditions: + - !type:OrganType + type: Plant + shouldHave: false + factor: -4 # Cant be added until I add metabolism effects on reagent removal #- !type:AdjustAlert # alertType: CarbonDioxide From 3323b617dd671bc14289d40c0c4f308d623bf397 Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 12 Oct 2023 03:36:37 -0400 Subject: [PATCH 15/29] Automatic changelog update --- Resources/Changelog/Changelog.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index a21254b1320..820b9a0a138 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,10 +1,4 @@ Entries: -- author: Ilya246 - changes: - - {message: The air alarm UI now allows copying settings of one device to all similar - devices., type: Add} - id: 4496 - time: '2023-08-09T18:20:20.0000000+00:00' - author: Nairodian changes: - {message: 'Changed Dark & Bushy, Death''s-Head, Firewatch, Moffra, Plasmafire, @@ -2958,3 +2952,9 @@ Entries: - {message: The nukie ship now has syndicate access airlocks, type: Add} id: 4995 time: '2023-10-11T16:47:25.0000000+00:00' +- author: notafet + changes: + - {message: Carbon dioxide poisoning is now more deadly. Victims of carbon dioxide + poisoning now gasp visibly., type: Tweak} + id: 4996 + time: '2023-10-12T07:35:31.0000000+00:00' From 535b013f63e3628ad690f446462afe32160bce54 Mon Sep 17 00:00:00 2001 From: chromiumboy <50505512+chromiumboy@users.noreply.github.com> Date: Thu, 12 Oct 2023 11:06:03 -0500 Subject: [PATCH 16/29] SMES and substation require power cells as machine parts (#20344) * Initial commit * Balancing and tweaks --- .../Components/UpgradeBatteryComponent.cs | 2 +- .../UpgradePowerSupplyRampingComponent.cs | 38 ++++++++++++++++ .../EntitySystems/UpgradeBatterySystem.cs | 6 ++- .../Power/EntitySystems/UpgradePowerSystem.cs | 43 ++++++++++++++++++- Resources/Locale/en-US/machine/machine.ftl | 1 + .../Circuitboards/Machine/production.yml | 8 +++- .../Entities/Objects/Power/powercells.yml | 16 ++++++- .../Entities/Structures/Power/smes.yml | 3 ++ .../Entities/Structures/Power/substation.yml | 3 ++ .../Prototypes/MachineParts/machine_parts.yml | 6 +++ 10 files changed, 117 insertions(+), 9 deletions(-) create mode 100644 Content.Server/Power/Components/UpgradePowerSupplyRampingComponent.cs diff --git a/Content.Server/Power/Components/UpgradeBatteryComponent.cs b/Content.Server/Power/Components/UpgradeBatteryComponent.cs index e1fc4d2bb82..ff91cfa7559 100644 --- a/Content.Server/Power/Components/UpgradeBatteryComponent.cs +++ b/Content.Server/Power/Components/UpgradeBatteryComponent.cs @@ -11,7 +11,7 @@ public sealed partial class UpgradeBatteryComponent : Component /// The machine part that affects the power capacity. /// [DataField("machinePartPowerCapacity", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string MachinePartPowerCapacity = "Capacitor"; + public string MachinePartPowerCapacity = "PowerCell"; /// /// The machine part rating is raised to this power when calculating power gain diff --git a/Content.Server/Power/Components/UpgradePowerSupplyRampingComponent.cs b/Content.Server/Power/Components/UpgradePowerSupplyRampingComponent.cs new file mode 100644 index 00000000000..7bd29f2de71 --- /dev/null +++ b/Content.Server/Power/Components/UpgradePowerSupplyRampingComponent.cs @@ -0,0 +1,38 @@ +using Content.Server.Construction.Components; +using Content.Shared.Construction.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Server.Power.Components +{ + + [RegisterComponent] + public sealed partial class UpgradePowerSupplyRampingComponent : Component + { + [ViewVariables(VVAccess.ReadWrite)] + public float BaseRampRate; + + /// + /// The machine part that affects the power supply ramping + /// + [DataField("machinePartPowerCapacity", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartRampRate = "Capacitor"; + + /// + /// The multiplier used for scaling the power supply ramping + /// + [DataField("supplyRampingMultiplier")] + public float SupplyRampingMultiplier = 1f; + + /// + /// What type of scaling is being used? + /// + [DataField("scaling", required: true), ViewVariables(VVAccess.ReadWrite)] + public MachineUpgradeScalingType Scaling; + + /// + /// The current value that the power supply is being scaled by + /// + [DataField("actualScalar"), ViewVariables(VVAccess.ReadWrite)] + public float ActualScalar = 1f; + } +} diff --git a/Content.Server/Power/EntitySystems/UpgradeBatterySystem.cs b/Content.Server/Power/EntitySystems/UpgradeBatterySystem.cs index 6571e1cf097..734cf9d89ce 100644 --- a/Content.Server/Power/EntitySystems/UpgradeBatterySystem.cs +++ b/Content.Server/Power/EntitySystems/UpgradeBatterySystem.cs @@ -7,6 +7,8 @@ namespace Content.Server.Power.EntitySystems [UsedImplicitly] public sealed class UpgradeBatterySystem : EntitySystem { + [Dependency] private readonly BatterySystem _batterySystem = default!; + public override void Initialize() { base.Initialize(); @@ -17,11 +19,11 @@ public override void Initialize() public void OnRefreshParts(EntityUid uid, UpgradeBatteryComponent component, RefreshPartsEvent args) { - var capacitorRating = args.PartRatings[component.MachinePartPowerCapacity]; + var powerCellRating = args.PartRatings[component.MachinePartPowerCapacity]; if (TryComp(uid, out var batteryComp)) { - batteryComp.MaxCharge = MathF.Pow(component.MaxChargeMultiplier, capacitorRating - 1) * component.BaseMaxCharge; + _batterySystem.SetMaxCharge(uid, MathF.Pow(component.MaxChargeMultiplier, powerCellRating - 1) * component.BaseMaxCharge, batteryComp); } } diff --git a/Content.Server/Power/EntitySystems/UpgradePowerSystem.cs b/Content.Server/Power/EntitySystems/UpgradePowerSystem.cs index 0d43f60a2bf..9cf28c386a6 100644 --- a/Content.Server/Power/EntitySystems/UpgradePowerSystem.cs +++ b/Content.Server/Power/EntitySystems/UpgradePowerSystem.cs @@ -1,4 +1,4 @@ -using Content.Server.Construction; +using Content.Server.Construction; using Content.Server.Construction.Components; using Content.Server.Power.Components; @@ -20,6 +20,10 @@ public override void Initialize() SubscribeLocalEvent(OnSupplierMapInit); SubscribeLocalEvent(OnSupplierRefreshParts); SubscribeLocalEvent(OnSupplierUpgradeExamine); + + SubscribeLocalEvent(OnSupplyRampingMapInit); + SubscribeLocalEvent(OnSupplyRampingRefreshParts); + SubscribeLocalEvent(OnSupplyRampingUpgradeExamine); } private void OnMapInit(EntityUid uid, UpgradePowerDrawComponent component, MapInitEvent args) @@ -76,7 +80,7 @@ private void OnSupplierRefreshParts(EntityUid uid, UpgradePowerSupplierComponent switch (component.Scaling) { case MachineUpgradeScalingType.Linear: - supply += component.BaseSupplyRate * (rating - 1); + supply += component.PowerSupplyMultiplier * component.BaseSupplyRate * (rating - 1); break; case MachineUpgradeScalingType.Exponential: supply *= MathF.Pow(component.PowerSupplyMultiplier, rating - 1); @@ -97,4 +101,39 @@ private void OnSupplierUpgradeExamine(EntityUid uid, UpgradePowerSupplierCompone { args.AddPercentageUpgrade("upgrade-power-supply", component.ActualScalar); } + + private void OnSupplyRampingMapInit(EntityUid uid, UpgradePowerSupplyRampingComponent component, MapInitEvent args) + { + if (TryComp(uid, out var battery)) + component.BaseRampRate = battery.SupplyRampRate; + } + + private void OnSupplyRampingRefreshParts(EntityUid uid, UpgradePowerSupplyRampingComponent component, RefreshPartsEvent args) + { + var rampRate = component.BaseRampRate; + var rating = args.PartRatings[component.MachinePartRampRate]; + switch (component.Scaling) + { + case MachineUpgradeScalingType.Linear: + rampRate += component.SupplyRampingMultiplier * component.BaseRampRate * (rating - 1); + break; + case MachineUpgradeScalingType.Exponential: + rampRate *= MathF.Pow(component.SupplyRampingMultiplier, rating - 1); + break; + default: + Log.Error($"invalid power supply ramping type for {ToPrettyString(uid)}."); + rampRate = component.BaseRampRate; + break; + } + + component.ActualScalar = rampRate / component.BaseRampRate; + + if (TryComp(uid, out var battery)) + battery.SupplyRampRate = rampRate; + } + + private void OnSupplyRampingUpgradeExamine(EntityUid uid, UpgradePowerSupplyRampingComponent component, UpgradeExamineEvent args) + { + args.AddPercentageUpgrade("upgrade-power-supply-ramping", component.ActualScalar); + } } diff --git a/Resources/Locale/en-US/machine/machine.ftl b/Resources/Locale/en-US/machine/machine.ftl index 1d086e4fdb5..20a7eb440ff 100644 --- a/Resources/Locale/en-US/machine/machine.ftl +++ b/Resources/Locale/en-US/machine/machine.ftl @@ -15,6 +15,7 @@ machine-part-name-matter-bin = Matter Bin upgrade-power-draw = power draw upgrade-max-charge = max charge upgrade-power-supply = power supply +upgrade-power-supply-ramping = power ramp rate two-way-lever-left = push left two-way-lever-right = push right diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml index 9c60e725350..a25e5b7069d 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml @@ -476,7 +476,10 @@ - type: MachineBoard prototype: SMESBasicEmpty requirements: - Capacitor: 5 + Capacitor: 1 + PowerCell: 4 + materialRequirements: + CableHV: 10 - type: entity id: CellRechargerCircuitboard @@ -553,7 +556,8 @@ - type: MachineBoard prototype: SubstationBasicEmpty requirements: - Capacitor: 3 + Capacitor: 1 + PowerCell: 1 materialRequirements: CableMV: 5 CableHV: 5 diff --git a/Resources/Prototypes/Entities/Objects/Power/powercells.yml b/Resources/Prototypes/Entities/Objects/Power/powercells.yml index 4b1c9104285..82c1bb37e96 100644 --- a/Resources/Prototypes/Entities/Objects/Power/powercells.yml +++ b/Resources/Prototypes/Entities/Objects/Power/powercells.yml @@ -62,6 +62,9 @@ - type: Battery maxCharge: 360 startingCharge: 360 + - type: MachinePart + part: PowerCell + rating: 1 - type: Tag tags: - PowerCellSmall @@ -100,6 +103,9 @@ - type: Battery maxCharge: 720 startingCharge: 720 + - type: MachinePart + part: PowerCell + rating: 2 - type: entity id: PowerCellMediumPrinted @@ -135,7 +141,10 @@ - type: Battery maxCharge: 1080 startingCharge: 1080 - + - type: MachinePart + part: PowerCell + rating: 3 + - type: entity id: PowerCellHighPrinted suffix: Empty @@ -170,7 +179,10 @@ - type: Battery maxCharge: 1800 startingCharge: 1800 - + - type: MachinePart + part: PowerCell + rating: 4 + - type: entity id: PowerCellHyperPrinted suffix: Empty diff --git a/Resources/Prototypes/Entities/Structures/Power/smes.yml b/Resources/Prototypes/Entities/Structures/Power/smes.yml index 68d72f64f89..92451918c7b 100644 --- a/Resources/Prototypes/Entities/Structures/Power/smes.yml +++ b/Resources/Prototypes/Entities/Structures/Power/smes.yml @@ -32,6 +32,9 @@ - type: UpgradeBattery maxChargeMultiplier: 2 baseMaxCharge: 8000000 + - type: UpgradePowerSupplyRamping + scaling: Linear + supplyRampingMultiplier: 1 - type: Appearance - type: Battery startingCharge: 0 diff --git a/Resources/Prototypes/Entities/Structures/Power/substation.yml b/Resources/Prototypes/Entities/Structures/Power/substation.yml index 6e3ef2f7f1a..bd3dcc4b8ca 100644 --- a/Resources/Prototypes/Entities/Structures/Power/substation.yml +++ b/Resources/Prototypes/Entities/Structures/Power/substation.yml @@ -20,6 +20,9 @@ - type: UpgradeBattery maxChargeMultiplier: 2 baseMaxCharge: 2500000 + - type: UpgradePowerSupplyRamping + scaling: Linear + supplyRampingMultiplier: 1 - type: Battery startingCharge: 0 - type: ExaminableBattery diff --git a/Resources/Prototypes/MachineParts/machine_parts.yml b/Resources/Prototypes/MachineParts/machine_parts.yml index 444bc37356c..317e4b80866 100644 --- a/Resources/Prototypes/MachineParts/machine_parts.yml +++ b/Resources/Prototypes/MachineParts/machine_parts.yml @@ -12,3 +12,9 @@ id: MatterBin name: machine-part-name-matter-bin stockPartPrototype: MatterBinStockPart + +- type: machinePart + id: PowerCell + name: machine-part-name-power-cell + stockPartPrototype: PowerCellSmall + From 1b46acac6428c920d70942a1650a375cbfc4ab7d Mon Sep 17 00:00:00 2001 From: Ubaser <134914314+UbaserB@users.noreply.github.com> Date: Fri, 13 Oct 2023 09:20:23 +1100 Subject: [PATCH 17/29] Paramedic Suit [RESPRITE] (#20619) * add sprites * fix copyright * update sprites --- .../paramedhelm.rsi/equipped-HELMET.png | Bin 1097 -> 2321 bytes .../Head/Helmets/paramedhelm.rsi/icon.png | Bin 716 -> 1724 bytes .../Helmets/paramedhelm.rsi/inhand-left.png | Bin 1332 -> 2082 bytes .../Helmets/paramedhelm.rsi/inhand-right.png | Bin 1314 -> 2097 bytes .../Head/Helmets/paramedhelm.rsi/meta.json | 2 +- .../paramed.rsi/equipped-OUTERCLOTHING.png | Bin 1884 -> 3568 bytes .../Hardsuits/paramed.rsi/icon.png | Bin 808 -> 1888 bytes .../Hardsuits/paramed.rsi/inhand-left.png | Bin 1037 -> 1932 bytes .../Hardsuits/paramed.rsi/inhand-right.png | Bin 1037 -> 1907 bytes .../Hardsuits/paramed.rsi/meta.json | 2 +- 10 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Resources/Textures/Clothing/Head/Helmets/paramedhelm.rsi/equipped-HELMET.png b/Resources/Textures/Clothing/Head/Helmets/paramedhelm.rsi/equipped-HELMET.png index d09e0347f5010eb891e24d1a5a7fa93f344e729c..e6810d74524c335fd9232b1a3cdca9184d481fca 100644 GIT binary patch literal 2321 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 a4Zv4G$t4JU$3tNN0000G?W zUP)qwZeFo6#1NP{E~&-IMVSR9nfZANAafIw@=Hr>m6Sjh!2!gbDamkq3QCJ|z_z3$ z>!;?V=BDPA6zd!68R}!xSCW~AaA96CG&q0(qYsh+YBRv9&9k5+*#sC;t`$J{K>Y`F zXfoK|;*u17BnA3L1_l&2TID3>rQ0f1=%%EmC6?xtDA{F{<|gLZ=tGpCYK4fRnrNes#c~^vm#rd$Qj7C* zN?N>Ymoihv0VY@ZR-I@~JI z%|h56nwME(2QvUo7)cjW#8^2Nm6YcfWru(x0UClh<)LOkLK7)vLXtf=|?L zo`>wX%#G9R7#LWrJzX3_Dj46+^39NT7difaQ<7(&N_cPY?Pl5SeVIwgleV!7GbtG^ z)m@tTVx>e>mF|?fxmjN4Tdmq-iacMkzdbwMiDOEgL*WVyT~3vis=EsdE^!=HEK_}b zdQ;dXugjH--$ZS@#v7-v=ymTvM%w*7Gb`V3H{N{gGlQQ`1E&F_+5wgs48jRaD4c#V z#_7WEp6T~=KX@_4^4jV2I^kTs&w;)lA6q}{ZTdgc;?GUXhwpf1NnMSRo9OR9#iC#*J{i}b3@8*zPNnl>D}|Em-Y1Ae`8}hY0pK@&4-trYE19%zO(banP=nQ_Bhec z^?w)4srx@gdEaWwTeH$`H7?lweZzuxM>pM@;+eN4_^y`i&yt9Wy>S_{+G_0b)2hDx ztXWXDy3Tr{B~wD?&v>5NFTEdr{n0pbFGOXx{?Tf=@glt_Q&M(cnaI+4 zp_fz^yjv#_v~o(;zcPV}>V^BRa561@w@xo5Kkne!nB`SvkB$p6xUOxQ_tg4C_-YTg zbIOzB9LxRtGvkXgIe+Dd?0#ZY_vTz~@Vy8{%e_yJJ=s+~q3_IG_mUSGwWUj*1!RWY zO1wK!_d?|#Ulk$F6eGzOLQQhB4<_ifiOZc|J!y`*Y-@hHlAFn!- zws}WUsp!ewR$*;YxqF+BU%6MQ|L~ynLXnsv?cTp7CtW`sZH>Pwv-tcRkX%p z*6+O7Ty0>w_1|~5$%}XoPCIr(!rSWS9EIcG-@ZQn^>v{L*Xj?ynV)T5HdUZEuBgP% zy3Fib;S(MMww|MU+RtAYXp3;IxZ3hlSA8z~*~-ItdNVOnAWCxdc@U`h!QpTJzqkwW zKb02c^}hIJYxc8qW!@dFnN7JNGCQSiAFJ6G%KN;RvFiJ`B;R{Z*Dog~iZ?A5S8glx zn||ESzx&vN?kKh8bJoB3q_i!{*+61X`iG-oW?Rp2Wt1d@-ZKs2Vqsd*vv0+#r}OuR zKP{_|Vq(}^csR4DAoX2&MaoRSSjOO8bzjDdot}ps?Wr^SA?_|tY7c{)6BY> z_hyjC)t9xx3|1_fU2hit;|ln}>$u>+YUT6mkIMx4FCWD delta 1314 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 GLSTZx&WL&d diff --git a/Resources/Textures/Clothing/Head/Helmets/paramedhelm.rsi/meta.json b/Resources/Textures/Clothing/Head/Helmets/paramedhelm.rsi/meta.json index c402402b13b..4ec7339a888 100644 --- a/Resources/Textures/Clothing/Head/Helmets/paramedhelm.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Helmets/paramedhelm.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from paradise station at commit https://github.com/ParadiseSS13/Paradise/commit/e5e584804b4b0b373a6a69d23afb73fd3c094365", + "copyright": "Taken from paradise station at commit https://github.com/ParadiseSS13/Paradise/commit/e5e584804b4b0b373a6a69d23afb73fd3c094365, redrawn by Ubaser", "size": { "x": 32, "y": 32 diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/paramed.rsi/equipped-OUTERCLOTHING.png b/Resources/Textures/Clothing/OuterClothing/Hardsuits/paramed.rsi/equipped-OUTERCLOTHING.png index da0a69c9c4dbd45072654bb7d9e4b6dfaafc3c68..24837f4d9aa989b61f0838d5e0ee0ce7330c1017 100644 GIT binary patch literal 3568 zcmZ`+2{@G78=oP2k}M^WOQme1u@6R;Fok5uuErP)W-&&_R+h3a*()(_Zl>WaINi37d_^+>jV_RX-MCd;b{-gd0_0z>bbO}U19Kq9a=Mug%_SI741ClC>&)^H-s}yCCg!jKvqDefNNRsjv4& z{mBb%sF)4Hr-LfD(y7NZKBH!cYOGLkHqmhdreG0g~F>8SSPXtiP&&AM?Tq^L+CHKX9r7wYogw6=03mG%31Y_dIPM&A3) zokAe~ko!X0+OnE8d?&hCOrIPWQNALVrI=q^viZEc{6c%Wv#cuWiHf#cbkM4a)b5ur z`j>9gr=3Kp;MA1#d6hb@2D=Us^o(tF!x>Z4(lsmtZjrvIe+ACCA(i931yz=0pH}#6 zn0wn3^%4*WJ$8(pU$!P3t)dc@kdU5KTdsa@J~&mm5n#G**ms#aVC%%5NDD71DX|~T zq6;H(btn|VGp(e^0AC_8XL=&Zel;?X%OeEVdAOu0@i55u>@vAnH$vdaCl}2esI9H- z#Psy@(!Y)rjMdl7lscCMJRcLdI@}QL9Mackhd6uYjGlwTq1ght>we|?A2_lNS7uth z6IWI%=s#}|bXGn+{mUg|M$Y@eO7B9OY_V7k7=~V&MgKtVmz6jlYmFFxyMI^Qi2<&5 z!h+E1bn&L~1Z?hz+>1DtF?3Mt;w8Y=7lryvQE?Rw5E`9b9&WzN&OytNTaOG|^1&o)CHudd2N?CtQYqp+n!QE5W(vw_;$TEN=K$Wijx zsFcn&1;#yYNaNItdGyDNJc=hUEHPj$lKZ$xP~ds~N8NUHgpU)VYS;rq99b;zbh$?T z%xrzk$l!3jc(ds_BP3iPATx00$vu-Oqbsg?qe>}^7f)ttL#;1fa2n#ud^O2BpP{p^ z)TQEhdLE0c4S; zia(gWog^6rkFtnNX^_Z#*n|`qW^1eY^36!`g||@J@`{gy4aE zDN%BAau|1Mnd=FuE}2@9%_b%CC{GoAkB}$BjMX|slc%Bg!}1;(N^HQSmOq|;FW$}a zx~ELlBb)9;nxJCg%T%e*uSip`<6=@($J42kh9=cY7e(u%#S?a$-w()AHUqL_74814 z9hx-Yg7qSmR}R&%=;Gzd!)M13)lsjkoCM}4Jvv{@ zjY!^~Z|Uso^N7PKvy^Rl)HulU-+Xgm?J4Qfrn@%B^Bxp3TE}jN^99$h-)SlvRI}t5 zeLvmtV@$%<-zsmrS9=CSikqMHsm`Vaeh%gE5^yQ(^EeblOmufj;h?P1Q%hNVd5<}CX;br6gH@0EY>>b3h(FVa#qUb_|F+sFzho6ugnECn51X|qi(A9#JpS4JOoiX zKvV9h>jhkWpB~P3E+e*WD&ONIOQ?t#Npi?vM69|o5%DUo(Xd9*H1(O-t6-iBw&nf= zF4h^1ua`%tDWYH}*5PrCuAiS8Yrg<=&j;9 zevt>x8F?;VA!};^&jS6A(P~GVvI384wckHEVBdcteYNsL8629uRa(7~uk3)tGS(v{ zBOpm-<{VQpw{)xHcoi+1q^`=;wP#$3mlG0oamdd7LseCH%$?M^~VRcT@ zu6Z$*^U(Sjj2g%c!sg=@a@wAp(pyulM7I*kcg5WxK?<_=QDSb=;ZOF2^QZJ42|gTJ zp>Z_OCe!i`bi;|qTWPOKV8TQDZpw|fSDj|rg7U-uLW&&Z>q<15uS{9G)#?@BdMzyj z)EF$Cz^eTTKUDv6@}SM!*hNvaN!_vS1|-W_-LUVquvCMb5F$Cr>B-fv+VhgzH{j1(W-e|ind;_L42 zjxUTD;m=80>bC0SiLU5(zgO_-Ui6~AzukVdM~?9!b-k5*@Hg-VAVO}|tkd2ovteVb z>RQt=Uy!uC$lT_Oa^WIFKvmtNahuQ7+4{JbIy9RBo-)VB`?&W@3mkHX|B(dRnFqe{F_`HYQ|?tpihwkPCgX$ku19X2S~!CA%*j zQ+?{xuRkaT+y_^K0;Vet0zYh(F6#erWW9k%_id}jMQZPRxW(E8bFGmze37>Ow*@BPeDeQl_Uq6xn delta 1857 zcmV-H2fq058{7_%Fnp0Kbwi{e1w-7ZexRQ0a@Hzt-2=%*nEFT2hB&gp)CL;= z`6_|HXLqdGiuJUw;FE)Jyzv>Hv>5_c1^JplO42>V&GbDn!E}3Cjp^3rGNMsB0A1}fNI3)y z1J{5SRWP*;DPIOa$_`){DTnB4pK;kuXZp$p#ZOx{@qg{DH2}mv{Xp9=96`#L5#k6P!4Wq3eDt0A9^l6?cFA=5hefXix%&OX z&6%?J&6}BRH3Loq(AhTSO2423Zl601oaScgHon#i0=^}MXabT*32+sFs;ZX|qKU!L z6!2S-9DjL1ZmdyFi-5~s+en-}$;z!a*l?er^_w%(dO4NKbitef0R6+0B02JcT)CCj z=M~l{=z!`czd|~#MgYD6fRxt}Vv}h`rmmFNGuyt_AD~8H95}yLKUc-S=K$&S6S|!X zS!KVPwM@6qfVIxc#qnLBad2ABcKj0fyB_@-;D1v9r2HVK-rt-y>xgu^3Bx!l)9FiG zy3~Xap{(*95B*5r0nqiD9JlSr0gYZ&LQCnL}U7eq| z{C*;1L5}VI(6JGPP@|}bh8Kv(6ZDwjkElebAE63lkImMJ?Fd^@v!V)QB7Ip4(Z_p{wsP3+1wW;=Eky$G(RZ`aPX!L9z9 z#x)L3OF%ai(Th+6<^rhL0?g^{VTcVMVY?*U4`ci{7SFI!)gFSyos@qZ``y#o&e(AoAr{lnIAb?@D)_lE91v4nKB z&*(KMTferp7Vwu<8#M)gEFmHf1s|0F>Nlc~J9%6C@6vblC~P7p-_u(SoEY5=pnn^3 z%F9DJ^j%#7BG7`Ie3P50+W`1lAvtmmILF4CYDA!g^*s383w>7(h=v!mh{*+ft$$Bu zmG=&~(sxBU^j$dstVL)!nc~DK914zczptiJi%`k+*YQaV)JKQaj+Yvx)73sN=meGU8t9JG=yzk+RSW&YTu@ z)|z|nwZ4akhlhuUhlj`i9)2{Ic7Pd!e|F!QJdu|KkkB(DQpR_(h)k!OkTOJW#||p!_aehf_OZ^Z-$=E)QLYbUZ90+96P|A0g1CG0oYhmP1UYv zbZLxAblyaWR{;3Q?^HbS&VR&f{Nnq{F8`hbw%mcWaWw%HI74=Z*fEnJRj$G=?DkY> z5V|FF%g(ldG`A}tT7MQBT(1FO<>>SeKgH@PE0tgi{lj}R_M8}X-$Ieo#q{OSAeqS z1?0ojwcAq6AQZg@R5B-^5ZU2!_vaVo023=75Ad+#e^OXK-8#}f<2u{kFS@05FM?3c vyny`LcjpRJcoDR?sts9RD{fJaE)D+yLs~f*E%MI%00000NkvXXu0mjfl991T diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/paramed.rsi/icon.png b/Resources/Textures/Clothing/OuterClothing/Hardsuits/paramed.rsi/icon.png index 5a1e8bf19485767bd80c3cccc213507c5d8c427b..31f9e2f0e943f1d0edb1ff212b90dc9d49070869 100644 GIT binary patch literal 1888 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}e5nzhX}-P; zT0k}j5QD&_;K@Lev%n*=7^q+l2s5%z3BJp~z?_pA5>XQ2>tmIipR1RclAn~SSCLx) z)@4&+1!U%?mLw`v zE(HYzo1&C7s~{IQsCFRFRw<*Tq`*pFzr4I$uiRKKzbIYb(9+UU-@r)U$VeBcLbtdw zuOzWTH?LS3VhGF}m(=3qqRfJl%=|nBkhzIT`K2YcN=hJ$-~i&zlw`O)1*JtfU|Uj> z^;2_Fb5rw5iuDck4E3?;E6GelxG=968XUlY(Fe%@wHaX5=2=jZYyu1^*9xF}p#B3o zG#PAfaY>3kk^+4r0|N_P10!7{OMSTifX=r`NwzA8(5=(PRl4Skm)1?YK5yc?zx=n$j+)xO2+92O z@2N49zE#=H?}ty`I=>;<|HG>6wE=w<0vtyRw?6G-xm;tx-y#&?r5$kf4%e@2v&DwT zPb4m{O)3#OZ5t~g=G7FnHf+thbx9Qw8Aps0ceS3nb!x6++kal;19vNG@*l|0J`nRg z_{r0!Cy&4W>Q&G;tMA*%iG>&J@9IdTDZG$%5a7C%RxP;3u4SRXfw|gUO>3Ub*7*8+ z;_TVlCr!AkJwJ$AJr_-=J5cbw_0BJ?dFx%n~ryzuGlthp5_{(X}z7@2HEeNGd}4CX;pyGqlbGGltz160 zF7%|uG3m}cHn&B7e|U~;+_59}N-tB&p7qknwGLyIr}-o>)x!mnRe zdEfWAp5J^tA~NED`h0%QZr62tFaDN^j*j+t^4#0@?U%*&k0tU|cCmQr#kvK%i!WEZlhGO%ub{Ex|9&gWbxj61_@?;otWPn^zNVEcInrW#Fv+ zUiD&6noAlV_m%_hH{1RnlMuXc&EV7Ivdw>|WNp5A;8j}VCPvrAfmfdXpH#eTPQGJQ zx9Z%IeuYxSrrU4JIG>5BRGd?v^x(?nc`IMYIlf9dv-)I8Q{7rhU@@&Bgg%H_7fCp#Pt z?)>~hd~TsmQu~|VOudW!8G`lSZ1mw=61wqG_VebTs!bWE{yz@L$yJ^ho3PS6Wo2e_{FeVwW02xme?LTM7YoNC#${|p=d@>> gZ7f(PxPh`m@5#8yias9>N6QHv7dp$EyOA|8u}dMUk_gNS(W&|Zp%A_Nh{QbCA6 zhoq!vYz-EbcnB&K3sIYGw!wz+&CG7PwI#br8$`&1&CGoB_J4ifyf=B)Iv z-{+2_+6ovMJj23MIVj3+FjgSMPh?TK$%POxW8<_Rp@l}Ho7Oqwy&2mqILg4ab02W; ziMi@Bo;O6kpMS`%3Y4)Rkzb)v`U4&(q00+!xkl*}C&5n^^54R$t|JmRMR5CRkdBKn z=#8OSRY~l1A;9H2!HUJPvRNoAU_CKSztdPNtV8uJP@v09Q923J#Yusz@M~ze3yw$P zyhQgEJ$94ram&z3_YN}9*mD-eqRaqrNnnhv5?Qn1k$)@sa@;i4cLOp5a}cGfR)Ezy zBJ(UAyhF23#_8Sk{4&Vj{^j@4X;^0hhGxB(`%$2qy^NI=4f%W?lTrQ`kf#p$-=nFi z2`w!iJoR{l|3vS*oeMbl-~u}L?G@yjrXf6axU?7WJ}b>9qy43M$AAW&`13E0U~#z( z>EKxP0)NuMV+tsxr8$O-?$?fo@qUYED?ORgJG=~wn)yz7vn_!&$jIPVQ3BZ$+AUrg z@!#hq2v0d9b}YbZW(h+6SCsY*JmgQ3s8hyjrjn4=e2!bi1q^s+sSGJ4;62JFqrH%2 z$W|P!A_466OKL0^A@2Z3QZ)9$EQj2$p#Y`3i+@NSiO$GP^-)iZC1{oa?|gjUuJP>sRO72j=Fxwiqe;X`? zYRpE!Fn#?Fh#6yR2A0KEEQ4yzMnH~8wtiT)!)hd8!f{8dk$~D;UBDj$8X)jK&JAq< P015yANkvXXu0mjf4N!8C diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/paramed.rsi/inhand-left.png b/Resources/Textures/Clothing/OuterClothing/Hardsuits/paramed.rsi/inhand-left.png index 1a4f03fa5d11ccd5ab45640ff423052937c89cc3..02bb28f88c61f89274c6fe62222f900cb5e86d96 100644 GIT binary patch literal 1932 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D$|_);T0(|mmy zw18|5AO?X;!IOa`XMsm#F;KxA5N2eb5`33|fjKQRB%&n3*T*V3KUXg?B|j-uuOhbq ztjngt3dqb&ElE_U$j!+swyLmI0;{kBvO&W7N(x{lCE2!05xxNm&iO^D3TAo+dIm~% zTnY*bHbp6ERzWUqQ0+jTtx`rwNr9EVetCJhUb(Seeo?xG?W zUP)qwZeFo6#1NP{E~&-IMVSR9nfZANAafIw@=Hr>m6Sjh!2!gbDamkq3QCJ|z_z3$ z>!;?V=BDPA6zd!68R}!xSCW~AaA96CG&q0(qYsh+YBRv9&9k5+*#sC;t`$J{K>Y`F zXfoK|;*u17BnA3L1_l&2TID3>rQ0f1=%%EmC6?xtDA{F{<|gLZ=tGpCYK4fRnrNes#c~^vm#rd$Qj7C* zN?N>Ymoihv0VY@ZR-I@~JI z%|h56nwME(2QvUo7)cjW#8^2Nm6YcfWru(x0UClh<)LOkLK7)vLXtf=|?L zo`>wX%#G9RfH`idr;B4q1>@UUwm!juBFFu^oh1d@mUVLWGF#nvrO@fd)^toFM|mwz zMvp}Yv!J+otjeq%3tnh&c5aXm?=?Kik(9)AkR`c=QA6*jt5Boa#Que;2cG}F|7~yA z#+Q5bzdt-+=~;XJcj39c`^(SoeezdcP+B;F$)|zSfKlxL%M1o|&hE|z_vuCc5;ykXWtd!MWASL2_(nwqI{dD*8N>&nH`xqNv)x#>ZzwielZ^31FNi-?s@bw+-+Sz*^`^w)MhaB?q77}j`d56gU!o( za?HG?rKL-LCC>j~e&?pj-TK=MD*raSFY>*qrn_+OmM>2=&zhBA$Vzh9vc5BZvGV2S zE$j6Xm^5A=%4;}zxaqC=%g=vrKl%Lgjg@)cmL&&f|16!gR^LyKIaXRqB=xfdsH+%4DLX|&oeHQ|HcTY2*dQ9o9G z3C&%d%Gy(6)+G~rZpqsp-Y@^`4NVU}ac9Qrg#WjXw_GXu%yJ-L3&VzICh5xv-|oV?+4;%zueo}HFf=o|7YK^|E!zrd+Kkf zRfcuku1AFvSL-jE=K5#buZ9TD+Rm1uo@;Gv$&Yp``Y~(|cDT(Y;k7PMZSq2o#Orc0 z@e3Rj7JM>riO#7y{5kVg#;ViIKAO89@ZpX`kWAwwmMQmdO#8$VC|tTwK;_e(UbT4{ zpYwnI`P?PG;$guJy+f~m?0gcgYUn!KfGJQo%OEB9{miiHfV_JOajr|w@-ceGXSKIr)n7Rv5z+g%dX?Y)#Co zzahXKe)-`_#^1_ZM-D$xJn0-f#Wa_}04%MLzdKhT$;lYU$$niux3MrfybvCJafH1-ST7PNqe2epF8tr`34CFYbb zS2aUfvby)iIq&PQMo+Q{;$!oaTc&g}JC;%RndgIrSyB?-nsbjPUE%w8&~t)WuwInK mH-(eG|JJe3+j07x>IdnbQ-<#r_r1*o)r6j|elF{r5}E+~N$1S~ delta 1016 zcmVEX>4Tx04R}tkv&MmKpe$iQ%j3f9oj*} zAwzYtAS&XhRVYG*P%E_RU~>J0CJjl7i=*ILaPVWX>fqw6tAnc`2!4RLxj8AiNQwVT z3N2zhIPS;0dyl(!fY7Wm)$ADuRLwHd$%L5At%|`{gb_hMLVpOz%+%*3DFx5*bq^n3 z@8Uem``n+SFJCYj;1h{wnQmCb8^qI_md<&fILgX$p7@-2%%BSrKXP4h`HgeQVS#5x zjZAu;I7%#*x>)IARyI`PDdJdO)hJ)cx}4{{#aXS^S^J*+h2es>vdndw!$@KgOOPN! zK^+xTVIfYdMt_QlH0>um{6mgkB9}t03K%&SP=gBD@q_=t?{3ZF;acMz~GZE8?qz$X$r+6 z@P0<$lmjBSK<}E{TYDd;4?u>xO5Felhrnowve!M{9e?WX?cX!){(b=d19EuqpJ*!p z00KlwL_t(|obB1MYZGA{#_?}@j-6W2p^zXt2`-WbEQrubw+@w(rHdkF>3CCJM6e(Q z@h=c@vV`K+p@o!A6{H5C;O5XkC!tG%gK)eK?~=12J5RI0XRLq zZ!^RMW-H*^uRlPkucyTI^1=v}P5{{4nk9-V7Jow<$ZP^8-h5_lX}7n(wzSLc!{@2% ztgn6rO5FUk48Y9H7poy==XU*jyv%D3!g#B>i=U+tI@oaj*l>j7|C#QE*Tx)p=$>N-~(A|fIpA|fIp zA|mOSy>TrT7hS9M444G=34%$Y=vAg~bS)xz(_FyE*&Uz~$K2Vnh~&*Cpc`ZRSi6Cb zxwB*w$(yc#D5|izH5>1Zo(v7kB9b?ofN^e&?PKjy9CK&ODv~!6#&PyWE~&14ByS?W zSiAq6<3#et=`eg~EaQ$2tji8CmCzoJ`2@gO9B=1kyhz^kL1ks`uJeg5j24ThHGU*- mx&qEG9`d;lv=cUwVf_XCRMK?$_*;Mg0000NNK`9AI2oR36fCy2d(3X&FAdqBZvY7;QDxjz+ zI$VlWWS|uh2C$q(lyZ~-ff^AJtO_Yt056;oMRCAx0_7^cnYZt~|NY;8eEX)I0-gZSOmR3IMatuo0~ucPaP&k4A`wJF z20?{Ffmb-DKq8Sq0udwi!@*I z

o3w2l_WT%0`af**-oD%5%5aY0B3i6Ai|10C=V;A%KTz+a3|ESskTB?i>qpfJL! zFs20I?m(IHRHE_B=Z6N$)%lenJaCBuNF)%P?FmGCA_-i`4-KEf@7J zTZ_K;hy@7kKLaboOHfM+Lw1uQ3RoJ=(iLHz!4KZg+n8f>NoUTry^eXb?`ftKaN!Nn zOuupD)6f@}%w)K+l-OrKyZWx*ZQXj9r5ye2D{>&HxUEhVQo+77$r%^CUBqcsHR@(8UwSs`YE`w^gWv)pueQe!Ct>$0oLV ztJUgvGqkCG7pdB2u-V`|W__5RZ=4AMYum8m(D0r?e;a(HMD~u=ta%k%wJtN^ZmqJT zswdgpFk%fO$l3bxt+8?yy@e10rMGR_BUk6%9vSJDJn(S!xN0%uXdtPP#chD~3s1cq zcY9VhOzB~4_}*zpcx!t_`r5>h30p70!MwC=|AM>=wJwjvPg#~~Xu9kTF&dMb`#iT; zuQVO2Jqc_&yyw;jicuziBj_+&u8x6kPLCWeth$Gojcqa`NbIU$$<%0X z_0hW7bNdq#v%)%ms~_Y2>FTWiPtQ|nMpRUk`IiI7u<@ZlYHn%zS)e#z_J8%rYVy`a9}D*mt?elmI_oQF}qnl%uqZg(b~3)+%D z;r@L!Vs7urFE|uh+{`kyj#Vl2`y(BItgVJ9~D@a%E zHX_xsO#q9@y5TOn%`qNLAgdfP9>foYVBa=kv*ToYX59ut%l2Y4@y#RG6`6ZCQN7B- zj_2lEcJ82g-WliQgImwGb@v`(b?24bFCJ~5j)W&dI% zI(zs~VmWLQmp#e7jO4Uu0-T4WV|S&BuQeScBbDAwO@4-I^MN7jlwH4Muh}12i5NGP zW@M|2TbmE_>LQBT1|*&7z_+8uFLiXbq`DU7hbgMZ2$lO delta 1016 zcmVEX>4Tx04R}tkv&MmKpe$iQ%j3f9oj*} zAwzYtAS&XhRVYG*P%E_RU~>J0CJjl7i=*ILaPVWX>fqw6tAnc`2!4RLxj8AiNQwVT z3N2zhIPS;0dyl(!fY7Wm)$ADuRLwHd$%L5At%|`{gb_hMLVpOz%+%*3DFx5*bq^n3 z@8Uem``n+SFJCYj;1h{wnQmCb8^qI_md<&fILgX$p7@-2%%BSrKXP4h`HgeQVS#5x zjZAu;I7%#*x>)IARyI`PDdJdO)hJ)cx}4{{#aXS^S^J*+h2es>vdndw!$@KgOOPN! zK^+xTVIfYdMt_QlH0>um{6mgkB9}t03K%&SP=gBD@q_=t?{3ZF;acMz~GZE8?qz$X$r+6 z@P0<$lmjBSK<}E{TYDd;4?u>xO5Felhrnowve!M{9e?WX?cX!){(b=d19EuqpJ*!p z00KlwL_t(|obB1aYZGA{$MJVO$GWtjLm@$Q(Wz(}QLsY`ZXFcq(nS%nbiAouM6d`2 z@h@<25X%j2=}<^lLE7NdsSXWv60$UO5FWk`&&B?F?%Etr?mYay;Nb4bJ(uTs^4yc- zKA@CRN`EP(lu}A5rIh-gREloEs8p*@q}zQ3i~(B&!5GH8IoIE-?&_g%?-q1^+QYy5 z?@KjcL9mN4cYy}RxX-!bDy&oo-t4QGZh7dVS7s>qcyH@*4m*zdQwMEH8e) zY}J-bZS2X?{ER?Tpeal9GcvWYC*_&}q1=vs2&@W(mv6VN1h(^Gyfx+YXJrw}%ei{$ z0Hmzyq1Z(b;2LH72e3kcab)`|mHLje{mwPO8c*0Sn;t74ZX|x1G64Gt`{nWc(Ix=z zK7ZdyJSSmVC*3n;yA96v<9z!Qo&*ow#U&F(4^DM>VWFs&D5aEAN-3q3Qc5ZH&nU%9 z&LDU##`J+%pii~>k#2X>OWPA(il)gwI=2xwK%XGk^?L1;T)?fE===z1$5Hoj^zFlt z15i|SZiAV%^=XW0c)jw_GoXm*+(z5AIC1Jej=l>}WON=1tj3Y|5OKFp#YX2BN8MRu zT)Fd)&Sm?)RO-)wp4aaidJVAhkIrqx-Sd9kj02E=bUui<2a&fg8KzPZ(fP$OcUBpm m1oMr~qbPziKa|GY9{mAXnbuT~1Aq7c0000 Date: Thu, 12 Oct 2023 18:21:28 -0400 Subject: [PATCH 18/29] Automatic changelog update --- Resources/Changelog/Changelog.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 820b9a0a138..661b3e08389 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,10 +1,4 @@ Entries: -- author: Nairodian - changes: - - {message: 'Changed Dark & Bushy, Death''s-Head, Firewatch, Moffra, Plasmafire, - and Royal moth wings to now have two separate color layers.', type: Tweak} - id: 4497 - time: '2023-08-10T01:32:01.0000000+00:00' - author: Interrobang01 changes: - {message: Added Mustard. It can be found in condiment stations., type: Add} @@ -2958,3 +2952,8 @@ Entries: poisoning now gasp visibly., type: Tweak} id: 4996 time: '2023-10-12T07:35:31.0000000+00:00' +- author: Ubaser + changes: + - {message: Revamped the Paramedic Void Suit sprite., type: Tweak} + id: 4997 + time: '2023-10-12T22:20:24.0000000+00:00' From d6575eb556ecf407964f37194916ec7540ad2e9d Mon Sep 17 00:00:00 2001 From: DrSmugleaf Date: Thu, 12 Oct 2023 15:45:04 -0700 Subject: [PATCH 19/29] Add support for multiple changelog files, add admin changelog (#20849) --- .../Managers/ClientAdminManager.cs | 8 + .../Managers/IClientAdminManager.cs | 24 ++- Content.Client/Changelog/ChangelogManager.cs | 112 ++++++++-- Content.Client/Changelog/ChangelogTab.xaml | 9 + Content.Client/Changelog/ChangelogTab.xaml.cs | 175 +++++++++++++++ Content.Client/Changelog/ChangelogWindow.xaml | 9 +- .../Changelog/ChangelogWindow.xaml.cs | 204 ++++++------------ Content.Client/Stylesheets/StyleSpace.cs | 17 +- Resources/Changelog/Admin.yml | 9 + .../en-US/changelog/changelog-window.ftl | 3 + 10 files changed, 402 insertions(+), 168 deletions(-) create mode 100644 Content.Client/Changelog/ChangelogTab.xaml create mode 100644 Content.Client/Changelog/ChangelogTab.xaml.cs create mode 100644 Resources/Changelog/Admin.yml diff --git a/Content.Client/Administration/Managers/ClientAdminManager.cs b/Content.Client/Administration/Managers/ClientAdminManager.cs index 66c8b8a0630..8978e2fd6dd 100644 --- a/Content.Client/Administration/Managers/ClientAdminManager.cs +++ b/Content.Client/Administration/Managers/ClientAdminManager.cs @@ -130,5 +130,13 @@ void IPostInjectInit.PostInject() return null; } + + public AdminData? GetAdminData(bool includeDeAdmin = false) + { + if (_player.LocalPlayer is { Session: { } session }) + return GetAdminData(session, includeDeAdmin); + + return null; + } } } diff --git a/Content.Client/Administration/Managers/IClientAdminManager.cs b/Content.Client/Administration/Managers/IClientAdminManager.cs index 46e3a01537b..b4b5b48b814 100644 --- a/Content.Client/Administration/Managers/IClientAdminManager.cs +++ b/Content.Client/Administration/Managers/IClientAdminManager.cs @@ -1,5 +1,4 @@ -using System; -using Content.Shared.Administration; +using Content.Shared.Administration; namespace Content.Client.Administration.Managers { @@ -13,6 +12,15 @@ public interface IClientAdminManager ///

event Action AdminStatusUpdated; + /// + /// Gets the admin data for the client, if they are an admin. + /// + /// + /// Whether to return admin data for admins that are current de-adminned. + /// + /// if the player is not an admin. + AdminData? GetAdminData(bool includeDeAdmin = false); + /// /// Checks whether the local player is an admin. /// @@ -52,5 +60,17 @@ public interface IClientAdminManager bool CanAdminMenu(); void Initialize(); + + /// + /// Checks if the client is an admin. + /// + /// + /// Whether to return admin data for admins that are current de-adminned. + /// + /// true if the player is an admin, false otherwise. + bool IsAdmin(bool includeDeAdmin = false) + { + return GetAdminData(includeDeAdmin) != null; + } } } diff --git a/Content.Client/Changelog/ChangelogManager.cs b/Content.Client/Changelog/ChangelogManager.cs index 249332c337f..396af99d2cf 100644 --- a/Content.Client/Changelog/ChangelogManager.cs +++ b/Content.Client/Changelog/ChangelogManager.cs @@ -1,29 +1,29 @@ -using System; -using System.Collections.Generic; using System.Globalization; -using System.IO; using System.Linq; using System.Threading.Tasks; using Content.Shared.CCVar; using Robust.Shared.Configuration; using Robust.Shared.ContentPack; -using Robust.Shared.IoC; using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager; -using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Markdown; using Robust.Shared.Serialization.Markdown.Mapping; using Robust.Shared.Utility; - namespace Content.Client.Changelog { - public sealed partial class ChangelogManager + public sealed partial class ChangelogManager : IPostInjectInit { + [Dependency] private readonly ILogManager _logManager = default!; [Dependency] private readonly IResourceManager _resource = default!; [Dependency] private readonly ISerializationManager _serialization = default!; [Dependency] private readonly IConfigurationManager _configManager = default!; + private const string SawmillName = "changelog"; + public const string MainChangelogName = "Changelog"; + + private ISawmill _sawmill = default!; + public bool NewChangelogEntries { get; private set; } public int LastReadId { get; private set; } public int MaxId { get; private set; } @@ -51,17 +51,39 @@ public void SaveNewReadId() public async void Initialize() { // Open changelog purely to compare to the last viewed date. - var changelog = await LoadChangelog(); + var changelogs = await LoadChangelog(); + UpdateChangelogs(changelogs); + } + + private void UpdateChangelogs(List changelogs) + { + if (changelogs.Count == 0) + { + return; + } + + var mainChangelogs = changelogs.Where(c => c.Name == MainChangelogName).ToArray(); + if (mainChangelogs.Length == 0) + { + _sawmill.Error($"No changelog file found in Resources/Changelog with name {MainChangelogName}"); + return; + } - if (changelog.Count == 0) + var changelog = changelogs[0]; + if (mainChangelogs.Length > 1) + { + _sawmill.Error($"More than one file found in Resource/Changelog with name {MainChangelogName}"); + } + + if (changelog.Entries.Count == 0) { return; } - MaxId = changelog.Max(c => c.Id); + MaxId = changelog.Entries.Max(c => c.Id); var path = new ResPath($"/changelog_last_seen_{_configManager.GetCVar(CCVars.ServerId)}"); - if(_resource.UserData.TryReadAllText(path, out var lastReadIdText)) + if (_resource.UserData.TryReadAllText(path, out var lastReadIdText)) { LastReadId = int.Parse(lastReadIdText); } @@ -71,20 +93,74 @@ public async void Initialize() NewChangelogEntriesChanged?.Invoke(); } - public Task> LoadChangelog() + public Task> LoadChangelog() { return Task.Run(() => { - var yamlData = _resource.ContentFileReadYaml(new ("/Changelog/Changelog.yml")); + var changelogs = new List(); + var directory = new ResPath("/Changelog"); + foreach (var file in _resource.ContentFindFiles(new ResPath("/Changelog/"))) + { + if (file.Directory != directory || file.Extension != "yml") + continue; + + var yamlData = _resource.ContentFileReadYaml(file); - if (yamlData.Documents.Count == 0) - return new List(); + if (yamlData.Documents.Count == 0) + continue; - var node = (MappingDataNode)yamlData.Documents[0].RootNode.ToDataNode(); - return _serialization.Read>(node["Entries"], notNullableOverride: true); + var node = yamlData.Documents[0].RootNode.ToDataNodeCast(); + var changelog = _serialization.Read(node, notNullableOverride: true); + if (string.IsNullOrWhiteSpace(changelog.Name)) + changelog.Name = file.FilenameWithoutExtension; + + changelogs.Add(changelog); + } + + changelogs.Sort((a, b) => a.Order.CompareTo(b.Order)); + return changelogs; }); } + public void PostInject() + { + _sawmill = _logManager.GetSawmill(SawmillName); + } + + [DataDefinition] + public sealed partial class Changelog + { + /// + /// The name to use for this changelog. + /// If left unspecified, the name of the file is used instead. + /// Used during localization to find the user-displayed name of this changelog. + /// + [DataField("Name")] + public string Name = string.Empty; + + /// + /// The individual entries in this changelog. + /// These are not kept around in memory in the changelog manager. + /// + [DataField("Entries")] + public List Entries = new(); + + /// + /// Whether or not this changelog will be displayed as a tab to non-admins. + /// These are still loaded by all clients, but not shown if they aren't an admin, + /// as they do not contain sensitive data and are publicly visible on GitHub. + /// + [DataField("AdminOnly")] + public bool AdminOnly; + + /// + /// Used when ordering the changelog tabs for the user to see. + /// Larger numbers are displayed later, smaller numbers are displayed earlier. + /// + [DataField("Order")] + public int Order; + } + [DataDefinition] public sealed partial class ChangelogEntry : ISerializationHooks { @@ -108,7 +184,7 @@ void ISerializationHooks.AfterDeserialization() } [DataDefinition] - public sealed partial class ChangelogChange : ISerializationHooks + public sealed partial class ChangelogChange { [DataField("type")] public ChangelogLineType Type { get; private set; } diff --git a/Content.Client/Changelog/ChangelogTab.xaml b/Content.Client/Changelog/ChangelogTab.xaml new file mode 100644 index 00000000000..7c049efacc7 --- /dev/null +++ b/Content.Client/Changelog/ChangelogTab.xaml @@ -0,0 +1,9 @@ + + + + + + + diff --git a/Content.Client/Changelog/ChangelogTab.xaml.cs b/Content.Client/Changelog/ChangelogTab.xaml.cs new file mode 100644 index 00000000000..d1e2bc7533e --- /dev/null +++ b/Content.Client/Changelog/ChangelogTab.xaml.cs @@ -0,0 +1,175 @@ +using System.Linq; +using System.Numerics; +using Content.Client.Resources; +using Content.Client.Stylesheets; +using Robust.Client.AutoGenerated; +using Robust.Client.ResourceManagement; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Utility; +using static Content.Client.Changelog.ChangelogManager; +using static Robust.Client.UserInterface.Controls.BoxContainer; + +namespace Content.Client.Changelog; + +[GenerateTypedNameReferences] +public sealed partial class ChangelogTab : Control +{ + [Dependency] private readonly ChangelogManager _changelog = default!; + [Dependency] private readonly IResourceCache _resourceCache = default!; + + public bool AdminOnly; + + public ChangelogTab() + { + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + } + + public void PopulateChangelog(ChangelogManager.Changelog changelog) + { + var byDay = changelog.Entries + .GroupBy(e => e.Time.ToLocalTime().Date) + .OrderByDescending(c => c.Key); + + var hasRead = changelog.Name != MainChangelogName || + _changelog.MaxId <= _changelog.LastReadId; + + foreach (var dayEntries in byDay) + { + var day = dayEntries.Key; + + var groupedEntries = dayEntries + .GroupBy(c => (c.Author, Read: c.Id <= _changelog.LastReadId)) + .OrderBy(c => c.Key.Read) + .ThenBy(c => c.Key.Author); + + string dayNice; + var today = DateTime.Today; + if (day == today) + dayNice = Loc.GetString("changelog-today"); + else if (day == today.AddDays(-1)) + dayNice = Loc.GetString("changelog-yesterday"); + else + dayNice = day.ToShortDateString(); + + ChangelogBody.AddChild(new Label + { + Text = dayNice, + StyleClasses = { StyleBase.StyleClassLabelHeading }, + Margin = new Thickness(4, 6, 0, 0) + }); + + var first = true; + + foreach (var groupedEntry in groupedEntries) + { + var (author, read) = groupedEntry.Key; + + if (!first) + { + ChangelogBody.AddChild(new Control { Margin = new Thickness(4) }); + } + + if (read && !hasRead) + { + hasRead = true; + + var upArrow = + _resourceCache.GetTexture("/Textures/Interface/Changelog/up_arrow.svg.192dpi.png"); + + var readDivider = new BoxContainer + { + Orientation = LayoutOrientation.Vertical + }; + + var hBox = new BoxContainer + { + Orientation = LayoutOrientation.Horizontal, + HorizontalAlignment = HAlignment.Center, + Children = + { + new TextureRect + { + Texture = upArrow, + ModulateSelfOverride = Color.FromHex("#888"), + TextureScale = new Vector2(0.5f, 0.5f), + Margin = new Thickness(4, 3), + VerticalAlignment = VAlignment.Bottom + }, + new Label + { + Align = Label.AlignMode.Center, + Text = Loc.GetString("changelog-new-changes"), + FontColorOverride = Color.FromHex("#888"), + }, + new TextureRect + { + Texture = upArrow, + ModulateSelfOverride = Color.FromHex("#888"), + TextureScale = new Vector2(0.5f, 0.5f), + Margin = new Thickness(4, 3), + VerticalAlignment = VAlignment.Bottom + } + } + }; + + readDivider.AddChild(hBox); + readDivider.AddChild(new PanelContainer { StyleClasses = { StyleBase.ClassLowDivider } }); + ChangelogBody.AddChild(readDivider); + + if (first) + readDivider.SetPositionInParent(ChangelogBody.ChildCount - 2); + } + + first = false; + + var authorLabel = new RichTextLabel + { + Margin = new Thickness(6, 0, 0, 0), + }; + authorLabel.SetMessage( + FormattedMessage.FromMarkup(Loc.GetString("changelog-author-changed", ("author", author)))); + ChangelogBody.AddChild(authorLabel); + + foreach (var change in groupedEntry.SelectMany(c => c.Changes)) + { + var text = new RichTextLabel(); + text.SetMessage(FormattedMessage.FromMarkup(change.Message)); + ChangelogBody.AddChild(new BoxContainer + { + Orientation = LayoutOrientation.Horizontal, + Margin = new Thickness(14, 1, 10, 2), + Children = + { + GetIcon(change.Type), + text + } + }); + } + } + } + } + + private TextureRect GetIcon(ChangelogLineType type) + { + var (file, color) = type switch + { + ChangelogLineType.Add => ("plus.svg.192dpi.png", "#6ED18D"), + ChangelogLineType.Remove => ("minus.svg.192dpi.png", "#D16E6E"), + ChangelogLineType.Fix => ("bug.svg.192dpi.png", "#D1BA6E"), + ChangelogLineType.Tweak => ("wrench.svg.192dpi.png", "#6E96D1"), + _ => throw new ArgumentOutOfRangeException(nameof(type), type, null) + }; + + return new TextureRect + { + Texture = _resourceCache.GetTexture(new ResPath($"/Textures/Interface/Changelog/{file}")), + VerticalAlignment = VAlignment.Top, + TextureScale = new Vector2(0.5f, 0.5f), + Margin = new Thickness(2, 4, 6, 2), + ModulateSelfOverride = Color.FromHex(color) + }; + } +} diff --git a/Content.Client/Changelog/ChangelogWindow.xaml b/Content.Client/Changelog/ChangelogWindow.xaml index 888a8528d91..355452dbfad 100644 --- a/Content.Client/Changelog/ChangelogWindow.xaml +++ b/Content.Client/Changelog/ChangelogWindow.xaml @@ -3,15 +3,10 @@ Title="{Loc 'changelog-window-title'}" MinSize="500 400" SetSize="500 400"> - - - - - - + - diff --git a/Content.Client/Changelog/ChangelogWindow.xaml.cs b/Content.Client/Changelog/ChangelogWindow.xaml.cs index cea5bd9e7c2..e5f492900c2 100644 --- a/Content.Client/Changelog/ChangelogWindow.xaml.cs +++ b/Content.Client/Changelog/ChangelogWindow.xaml.cs @@ -1,28 +1,22 @@ using System.Linq; -using System.Numerics; -using Content.Client.Resources; +using Content.Client.Administration.Managers; using Content.Client.Stylesheets; using Content.Client.UserInterface.Controls; using Content.Client.UserInterface.Systems.EscapeMenu; using Content.Shared.Administration; using JetBrains.Annotations; using Robust.Client.AutoGenerated; -using Robust.Client.ResourceManagement; using Robust.Client.UserInterface; -using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; using Robust.Shared.Console; -using Robust.Shared.Utility; -using static Content.Client.Changelog.ChangelogManager; -using static Robust.Client.UserInterface.Controls.BoxContainer; namespace Content.Client.Changelog { [GenerateTypedNameReferences] public sealed partial class ChangelogWindow : FancyWindow { + [Dependency] private readonly IClientAdminManager _adminManager = default!; [Dependency] private readonly ChangelogManager _changelog = default!; - [Dependency] private readonly IResourceCache _resourceCache = default!; public ChangelogWindow() { @@ -39,154 +33,84 @@ protected override void Opened() PopulateChangelog(); } + protected override void EnteredTree() + { + base.EnteredTree(); + _adminManager.AdminStatusUpdated += OnAdminStatusUpdated; + } + + protected override void ExitedTree() + { + base.ExitedTree(); + _adminManager.AdminStatusUpdated -= OnAdminStatusUpdated; + } + + private void OnAdminStatusUpdated() + { + TabsUpdated(); + } + private async void PopulateChangelog() { // Changelog is not kept in memory so load it again. - var changelog = await _changelog.LoadChangelog(); + var changelogs = await _changelog.LoadChangelog(); - var byDay = changelog - .GroupBy(e => e.Time.ToLocalTime().Date) - .OrderByDescending(c => c.Key); + Tabs.DisposeAllChildren(); - var hasRead = _changelog.MaxId <= _changelog.LastReadId; - foreach (var dayEntries in byDay) + var i = 0; + foreach (var changelog in changelogs) { - var day = dayEntries.Key; - - var groupedEntries = dayEntries - .GroupBy(c => (c.Author, Read: c.Id <= _changelog.LastReadId)) - .OrderBy(c => c.Key.Read) - .ThenBy(c => c.Key.Author); - - string dayNice; - var today = DateTime.Today; - if (day == today) - dayNice = Loc.GetString("changelog-today"); - else if (day == today.AddDays(-1)) - dayNice = Loc.GetString("changelog-yesterday"); - else - dayNice = day.ToShortDateString(); - - ChangelogBody.AddChild(new Label - { - Text = dayNice, - StyleClasses = { StyleBase.StyleClassLabelHeading }, - Margin = new Thickness(4, 6, 0, 0) - }); + var tab = new ChangelogTab { AdminOnly = changelog.AdminOnly }; + tab.PopulateChangelog(changelog); - var first = true; - - foreach (var groupedEntry in groupedEntries) - { - var (author, read) = groupedEntry.Key; - - if (!first) - { - ChangelogBody.AddChild(new Control { Margin = new Thickness(4) }); - } - - if (read && !hasRead) - { - hasRead = true; - - var upArrow = - _resourceCache.GetTexture("/Textures/Interface/Changelog/up_arrow.svg.192dpi.png"); - - var readDivider = new BoxContainer - { - Orientation = LayoutOrientation.Vertical - }; - - var hBox = new BoxContainer - { - Orientation = LayoutOrientation.Horizontal, - HorizontalAlignment = HAlignment.Center, - Children = - { - new TextureRect - { - Texture = upArrow, - ModulateSelfOverride = Color.FromHex("#888"), - TextureScale = new Vector2(0.5f, 0.5f), - Margin = new Thickness(4, 3), - VerticalAlignment = VAlignment.Bottom - }, - new Label - { - Align = Label.AlignMode.Center, - Text = Loc.GetString("changelog-new-changes"), - FontColorOverride = Color.FromHex("#888"), - }, - new TextureRect - { - Texture = upArrow, - ModulateSelfOverride = Color.FromHex("#888"), - TextureScale = new Vector2(0.5f, 0.5f), - Margin = new Thickness(4, 3), - VerticalAlignment = VAlignment.Bottom - } - } - }; - - readDivider.AddChild(hBox); - readDivider.AddChild(new PanelContainer { StyleClasses = { StyleBase.ClassLowDivider } }); - ChangelogBody.AddChild(readDivider); - - if (first) - readDivider.SetPositionInParent(ChangelogBody.ChildCount - 2); - } - - first = false; - - var authorLabel = new RichTextLabel - { - Margin = new Thickness(6, 0, 0, 0), - }; - authorLabel.SetMessage( - FormattedMessage.FromMarkup(Loc.GetString("changelog-author-changed", ("author", author)))); - ChangelogBody.AddChild(authorLabel); - - foreach (var change in groupedEntry.SelectMany(c => c.Changes)) - { - var text = new RichTextLabel(); - text.SetMessage(FormattedMessage.FromMarkup(change.Message)); - ChangelogBody.AddChild(new BoxContainer - { - Orientation = LayoutOrientation.Horizontal, - Margin = new Thickness(14, 1, 10, 2), - Children = - { - GetIcon(change.Type), - text - } - }); - } - } + Tabs.AddChild(tab); + Tabs.SetTabTitle(i++, Loc.GetString($"changelog-tab-title-{changelog.Name}")); } var version = typeof(ChangelogWindow).Assembly.GetName().Version ?? new Version(1, 0); VersionLabel.Text = Loc.GetString("changelog-version-tag", ("version", version.ToString())); + + TabsUpdated(); } - private TextureRect GetIcon(ChangelogLineType type) + private void TabsUpdated() { - var (file, color) = type switch + var tabs = Tabs.Children.OfType().ToArray(); + var isAdmin = _adminManager.IsAdmin(true); + + var visibleTabs = 0; + int? firstVisible = null; + for (var i = 0; i < tabs.Length; i++) { - ChangelogLineType.Add => ("plus.svg.192dpi.png", "#6ED18D"), - ChangelogLineType.Remove => ("minus.svg.192dpi.png", "#D16E6E"), - ChangelogLineType.Fix => ("bug.svg.192dpi.png", "#D1BA6E"), - ChangelogLineType.Tweak => ("wrench.svg.192dpi.png", "#6E96D1"), - _ => throw new ArgumentOutOfRangeException(nameof(type), type, null) - }; - - return new TextureRect + var tab = tabs[i]; + + if (!tab.AdminOnly || isAdmin) + { + Tabs.SetTabVisible(i, true); + tab.Visible = true; + visibleTabs++; + firstVisible ??= i; + } + else + { + Tabs.SetTabVisible(i, false); + tab.Visible = false; + } + } + + Tabs.TabsVisible = visibleTabs > 1; + + // Current tab became invisible, select the first one that is visible + if (!Tabs.GetTabVisible(Tabs.CurrentTab) && firstVisible != null) { - Texture = _resourceCache.GetTexture(new ResPath($"/Textures/Interface/Changelog/{file}")), - VerticalAlignment = VAlignment.Top, - TextureScale = new Vector2(0.5f, 0.5f), - Margin = new Thickness(2, 4, 6, 2), - ModulateSelfOverride = Color.FromHex(color) - }; + Tabs.CurrentTab = firstVisible.Value; + } + + // We are only displaying one tab, hide its header + if (!Tabs.TabsVisible && firstVisible != null) + { + Tabs.SetTabVisible(firstVisible.Value, false); + } } } diff --git a/Content.Client/Stylesheets/StyleSpace.cs b/Content.Client/Stylesheets/StyleSpace.cs index a82dba65bcc..3bb4e986af5 100644 --- a/Content.Client/Stylesheets/StyleSpace.cs +++ b/Content.Client/Stylesheets/StyleSpace.cs @@ -4,7 +4,6 @@ using Robust.Client.ResourceManagement; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; -using Robust.Shared.Maths; using static Robust.Client.UserInterface.StylesheetHelpers; namespace Content.Client.Stylesheets @@ -62,6 +61,14 @@ public StyleSpace(IResourceCache resCache) : base(resCache) var textureInvertedTriangle = resCache.GetTexture("/Textures/Interface/Nano/inverted_triangle.svg.png"); + var tabContainerPanel = new StyleBoxTexture(); + tabContainerPanel.SetPatchMargin(StyleBox.Margin.All, 2); + + var tabContainerBoxActive = new StyleBoxFlat {BackgroundColor = new Color(64, 64, 64)}; + tabContainerBoxActive.SetContentMarginOverride(StyleBox.Margin.Horizontal, 5); + var tabContainerBoxInactive = new StyleBoxFlat {BackgroundColor = new Color(32, 32, 32)}; + tabContainerBoxInactive.SetContentMarginOverride(StyleBox.Margin.Horizontal, 5); + Stylesheet = new Stylesheet(BaseRules.Concat(new StyleRule[] { Element