From 311b86e012ac7d40363b5027ab51b09c9d2285e2 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Sun, 22 Dec 2024 05:26:41 -0500 Subject: [PATCH] Loadout Modular Functions (And Loadout Pets) (#1366) # Description This PR implements a reflection based system for applying functions directly to entities spawned by loadouts. In order to provide an "Example" use of this system, I have created a "LoadoutMakeFollower" function, which can be applied to a loadout entity that happens to be an NPC with the Follower blackboard, making it follow the player who purchased that loadout. Basically. Pet mouse. The pet mouse will follow its owner. Yes I actually have tested this ingame, and it works great. The longest part about coding this was me spending almost 30 minutes straight wondering why the mouse wasn't following my character, until I remembered that I had to make a special "Pet" mouse variant that had the right HTN root task. This could be extended to other things. I happen to know that Nuclear14 wanted something like this for a Pet Dog.

Media

![image](https://github.com/user-attachments/assets/a18b026b-07a3-4ad7-8cce-4ea4dc4c3036)

# Changelog :cl: - add: Loadouts can now apply modular functions to items upon spawning in. - add: A new LoadoutMakeFollower function, which lets you buy NPC followers in loadouts. - add: added Pet Mice, Cockroach, Mothroach, and Hamster to Loadouts. All of which use the new LoadoutMakeFollower function. Co-authored-by: sleepyyapril <123355664+sleepyyapril@users.noreply.github.com> --- .../Systems/LoadoutSystem.Functions.cs | 30 ++++++++++ .../Clothing/Systems/LoadoutSystem.cs | 5 +- .../Loadouts/Prototypes/LoadoutPrototype.cs | 16 +++++ .../Locale/en-US/loadouts/generic/items.ftl | 5 ++ .../Locale/en-US/loadouts/itemgroups.ftl | 1 + .../Generic/miscItemGroups.yml | 13 ++++ .../Prototypes/Entities/Mobs/NPCs/animals.yml | 60 +++++++++++++++++++ .../Prototypes/Loadouts/Generic/items.yml | 53 ++++++++++++++++ 8 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 Content.Server/Clothing/Systems/LoadoutSystem.Functions.cs diff --git a/Content.Server/Clothing/Systems/LoadoutSystem.Functions.cs b/Content.Server/Clothing/Systems/LoadoutSystem.Functions.cs new file mode 100644 index 00000000000..99ca8b15c28 --- /dev/null +++ b/Content.Server/Clothing/Systems/LoadoutSystem.Functions.cs @@ -0,0 +1,30 @@ +using JetBrains.Annotations; +using Robust.Shared.Serialization.Manager; +using Content.Shared.Clothing.Loadouts.Prototypes; +using Content.Server.NPC.Components; +using Content.Server.NPC.Systems; +using Content.Server.NPC.HTN; +using Content.Server.NPC; +using Robust.Shared.Map; +using System.Numerics; + +namespace Content.Server.Clothing.Systems; + +[UsedImplicitly] +public sealed partial class LoadoutMakeFollower : LoadoutFunction +{ + public override void OnPlayerSpawn(EntityUid character, + EntityUid loadoutEntity, + IComponentFactory factory, + IEntityManager entityManager, + ISerializationManager serializationManager) + { + var npc = entityManager.System(); + var htn = entityManager.System(); + if (!entityManager.TryGetComponent(loadoutEntity, out var hTNComponent)) + return; + + npc.SetBlackboard(loadoutEntity, NPCBlackboard.FollowTarget, new EntityCoordinates(character, Vector2.Zero), hTNComponent); + htn.Replan(hTNComponent); + } +} diff --git a/Content.Server/Clothing/Systems/LoadoutSystem.cs b/Content.Server/Clothing/Systems/LoadoutSystem.cs index 1527db0be85..4c357c58642 100644 --- a/Content.Server/Clothing/Systems/LoadoutSystem.cs +++ b/Content.Server/Clothing/Systems/LoadoutSystem.cs @@ -13,7 +13,6 @@ using Content.Shared.Storage; using Content.Shared.Storage.EntitySystems; using Content.Shared.Traits.Assorted.Components; -using Content.Shared.Whitelist; using Robust.Shared.Configuration; using Robust.Shared.Prototypes; using Robust.Shared.Random; @@ -33,6 +32,7 @@ public sealed class LoadoutSystem : EntitySystem [Dependency] private readonly IPrototypeManager _protoMan = default!; [Dependency] private readonly ISerializationManager _serialization = default!; [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IComponentFactory _componentFactory = default!; public override void Initialize() @@ -103,6 +103,9 @@ public void ApplyCharacterLoadout( comp.Owner = loadout.Item1; EntityManager.AddComponent(loadout.Item1, comp); } + + foreach (var function in loadoutProto.Functions) + function.OnPlayerSpawn(uid, loadout.Item1, _componentFactory, EntityManager, _serialization); } diff --git a/Content.Shared/Clothing/Loadouts/Prototypes/LoadoutPrototype.cs b/Content.Shared/Clothing/Loadouts/Prototypes/LoadoutPrototype.cs index 38a356b1e0d..ecb3c3fd6c5 100644 --- a/Content.Shared/Clothing/Loadouts/Prototypes/LoadoutPrototype.cs +++ b/Content.Shared/Clothing/Loadouts/Prototypes/LoadoutPrototype.cs @@ -1,5 +1,6 @@ using Content.Shared.Customization.Systems; using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.Manager; namespace Content.Shared.Clothing.Loadouts.Prototypes; @@ -45,4 +46,19 @@ public sealed partial class LoadoutPrototype : IPrototype [DataField] public string GuideEntry { get; } = ""; + + [DataField(serverOnly: true)] + public LoadoutFunction[] Functions { get; private set; } = Array.Empty(); +} + +/// This serves as a hook for loadout functions to modify one or more entities upon spawning in. +[ImplicitDataDefinitionForInheritors] +public abstract partial class LoadoutFunction +{ + public abstract void OnPlayerSpawn( + EntityUid character, + EntityUid loadoutEntity, + IComponentFactory factory, + IEntityManager entityManager, + ISerializationManager serializationManager); } diff --git a/Resources/Locale/en-US/loadouts/generic/items.ftl b/Resources/Locale/en-US/loadouts/generic/items.ftl index ac52b8be226..37ca4f91fe3 100644 --- a/Resources/Locale/en-US/loadouts/generic/items.ftl +++ b/Resources/Locale/en-US/loadouts/generic/items.ftl @@ -42,3 +42,8 @@ loadout-name-LoadoutItemLighterFlippo = flippo lighter (colorable) loadout-name-LoadoutItemDrinkShinyFlask = shiny flask (colorable) loadout-name-LoadoutItemDrinkLithiumFlask = lithium flask (colorable) loadout-name-LoadoutItemDrinkVacuumFlask = vacuum flask (colorable) + +loadout-name-LoadoutItemPetMouse = pet mouse +loadout-name-LoadoutItemPetHamster = pet hamster +loadout-name-LoadoutItemPetMothroach = pet mothroach +loadout-name-LoadoutItemPetCockroach = pet cockroach diff --git a/Resources/Locale/en-US/loadouts/itemgroups.ftl b/Resources/Locale/en-US/loadouts/itemgroups.ftl index b6221e85850..8af87679c45 100644 --- a/Resources/Locale/en-US/loadouts/itemgroups.ftl +++ b/Resources/Locale/en-US/loadouts/itemgroups.ftl @@ -17,6 +17,7 @@ character-item-group-LoadoutInstrumentsAny = Musical Instruments (Non-Musician) character-item-group-LoadoutSmokes = Smokeables character-item-group-LoadoutBoxKits = Survival Kits character-item-group-LoadoutWritables = Writing Tools +character-item-group-LoadoutPets = Pets # Job Specific Template character-item-group-LoadoutJOBBackpacks = JOB Backpacks diff --git a/Resources/Prototypes/CharacterItemGroups/Generic/miscItemGroups.yml b/Resources/Prototypes/CharacterItemGroups/Generic/miscItemGroups.yml index 3cad4423f52..cc1e82ceb69 100644 --- a/Resources/Prototypes/CharacterItemGroups/Generic/miscItemGroups.yml +++ b/Resources/Prototypes/CharacterItemGroups/Generic/miscItemGroups.yml @@ -121,3 +121,16 @@ id: LoadoutBookRandom - type: loadout id: LoadoutPen + +- type: characterItemGroup + id: LoadoutPets + maxItems: 1 + items: + - type: loadout + id: LoadoutItemPetMouse + - type: loadout + id: LoadoutItemPetHamster + - type: loadout + id: LoadoutItemPetMothroach + - type: loadout + id: LoadoutItemPetCockroach diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index 6e369389a90..1ee20cdcb61 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -404,6 +404,21 @@ speaks: [Hissing] understands: [Hissing] +- type: entity + parent: MobCockroach + id: MobCockroachPet + components: + - type: HTN + rootTask: + task: FollowCompound + blackboard: + IdleRange: !type:Single + 1.5 + FollowCloseRange: !type:Single + 1.0 + FollowRange: !type:Single + 2.0 + - type: entity name: glockroach parent: MobCockroach @@ -575,6 +590,21 @@ enum.SurgeryUIKey.Key: type: SurgeryBui +- type: entity + parent: MobMothroach + id: MobMothroachPet + components: + - type: HTN + rootTask: + task: FollowCompound + blackboard: + IdleRange: !type:Single + 1.5 + FollowCloseRange: !type:Single + 1.0 + FollowRange: !type:Single + 2.0 + # Note that the mallard duck is actually a male drake mallard, with the brown duck being the female variant of the same species, however ss14 lacks sex specific textures # The white duck is more akin to a pekin or call duck. @@ -1769,6 +1799,21 @@ enum.SurgeryUIKey.Key: type: SurgeryBui +- type: entity + parent: MobMouse + id: MobMousePet + components: + - type: HTN + rootTask: + task: FollowCompound + blackboard: + IdleRange: !type:Single + 1.5 + FollowCloseRange: !type:Single + 1.0 + FollowRange: !type:Single + 2.0 + - type: entity parent: MobMouse suffix: Dead @@ -3418,6 +3463,21 @@ sprite: Mobs/Effects/onfire.rsi normalState: Mouse_burning +- type: entity + parent: MobHamster + id: MobHamsterPet + components: + - type: HTN + rootTask: + task: FollowCompound + blackboard: + IdleRange: !type:Single + 1.5 + FollowCloseRange: !type:Single + 1.0 + FollowRange: !type:Single + 2.0 + - type: entity name: pig parent: SimpleMobBase diff --git a/Resources/Prototypes/Loadouts/Generic/items.yml b/Resources/Prototypes/Loadouts/Generic/items.yml index dafe7c9ffa5..2c42955c08f 100644 --- a/Resources/Prototypes/Loadouts/Generic/items.yml +++ b/Resources/Prototypes/Loadouts/Generic/items.yml @@ -766,3 +766,56 @@ customColorTint: true items: - DrinkVacuumFlask + +# Pets +- type: loadout + id: LoadoutItemPetMouse + category: Items + cost: 2 + canBeHeirloom: true + items: + - MobMousePet + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutPets + functions: + - !type:LoadoutMakeFollower + +- type: loadout + id: LoadoutItemPetHamster + category: Items + cost: 2 + canBeHeirloom: true + items: + - MobHamsterPet + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutPets + functions: + - !type:LoadoutMakeFollower + +- type: loadout + id: LoadoutItemPetMothroach + category: Items + cost: 2 + canBeHeirloom: true + items: + - MobMothroachPet + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutPets + functions: + - !type:LoadoutMakeFollower + +- type: loadout + id: LoadoutItemPetCockroach + category: Items + cost: 2 + canBeHeirloom: true + items: + - MobCockroachPet + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutPets + functions: + - !type:LoadoutMakeFollower