diff --git a/Content.Server/NPC/Components/NPCImprintingOnSpawnBehaviourComponent.cs b/Content.Server/NPC/Components/NPCImprintingOnSpawnBehaviourComponent.cs new file mode 100644 index 0000000000..26439d2b30 --- /dev/null +++ b/Content.Server/NPC/Components/NPCImprintingOnSpawnBehaviourComponent.cs @@ -0,0 +1,34 @@ +using Content.Shared.Whitelist; +using Robust.Shared.Collections; + +namespace Content.Server.NPC.Components; +/// +/// A component that makes the entity friendly to nearby creatures it sees on init. +/// +[RegisterComponent] +public sealed partial class NPCImprintingOnSpawnBehaviourComponent : Component +{ + /// + /// filter who can be a friend to this creature + /// + [DataField] + public EntityWhitelist? Whitelist; + + /// + /// when a creature appears, it will memorize all creatures in the radius to remember them as friends + /// + [DataField] + public float SpawnFriendsSearchRadius = 3f; + + /// + /// if there is a FollowCompound in HTN, the target of the following will be selected from random nearby targets when it appears + /// + [DataField] + public bool Follow = true; + + /// + /// is used to determine who became a friend from this component + /// + [DataField] + public List Friends = new(); +} diff --git a/Content.Server/NPC/Systems/NPCImprintingOnSpawnBehaviourSystem.cs b/Content.Server/NPC/Systems/NPCImprintingOnSpawnBehaviourSystem.cs new file mode 100644 index 0000000000..cfd3b08c61 --- /dev/null +++ b/Content.Server/NPC/Systems/NPCImprintingOnSpawnBehaviourSystem.cs @@ -0,0 +1,49 @@ +using System.Numerics; +using Content.Shared.NPC.Components; +using Content.Shared.NPC.Systems; +using Robust.Shared.Collections; +using Robust.Shared.Map; +using Robust.Shared.Random; +using NPCImprintingOnSpawnBehaviourComponent = Content.Server.NPC.Components.NPCImprintingOnSpawnBehaviourComponent; + +namespace Content.Server.NPC.Systems; + +public sealed partial class NPCImprintingOnSpawnBehaviourSystem : SharedNPCImprintingOnSpawnBehaviourSystem +{ + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly NPCSystem _npc = default!; + [Dependency] private readonly IRobustRandom _random = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnMapInit); + } + + private void OnMapInit(Entity imprinting, ref MapInitEvent args) + { + HashSet friends = new(); + _lookup.GetEntitiesInRange(imprinting, imprinting.Comp.SpawnFriendsSearchRadius, friends); + + foreach (var friend in friends) + { + if (imprinting.Comp.Whitelist?.IsValid(friend) != false) + { + AddImprintingTarget(imprinting, friend, imprinting.Comp); + } + } + + if (imprinting.Comp.Follow && imprinting.Comp.Friends.Count > 0) + { + var mommy = _random.Pick(imprinting.Comp.Friends); + _npc.SetBlackboard(imprinting, NPCBlackboard.FollowTarget, new EntityCoordinates(mommy, Vector2.Zero)); + } + } + + public void AddImprintingTarget(EntityUid entity, EntityUid friend, NPCImprintingOnSpawnBehaviourComponent component) + { + component.Friends.Add(friend); + var exception = EnsureComp(entity); + exception.Ignored.Add(friend); + } +} diff --git a/Content.Server/NPC/Systems/NPCSystem.cs b/Content.Server/NPC/Systems/NPCSystem.cs index 8abe0f7f54..9108629435 100644 --- a/Content.Server/NPC/Systems/NPCSystem.cs +++ b/Content.Server/NPC/Systems/NPCSystem.cs @@ -5,6 +5,7 @@ using Content.Shared.CCVar; using Content.Shared.Mobs; using Content.Shared.Mobs.Systems; using Content.Shared.NPC; +using Content.Shared.NPC.Systems; using Robust.Server.GameObjects; using Robust.Shared.Configuration; using Robust.Shared.Player; diff --git a/Content.Shared/NPC/Components/FactionExceptionComponent.cs b/Content.Shared/NPC/Components/FactionExceptionComponent.cs index 54de0404c2..ba7940d502 100644 --- a/Content.Shared/NPC/Components/FactionExceptionComponent.cs +++ b/Content.Shared/NPC/Components/FactionExceptionComponent.cs @@ -7,7 +7,7 @@ namespace Content.Shared.NPC.Components; /// Prevents an NPC from attacking ignored entities from enemy factions. /// Can be added to if pettable, see PettableFriendComponent. /// -[RegisterComponent, NetworkedComponent, Access(typeof(NpcFactionSystem))] +[RegisterComponent, NetworkedComponent, Access(typeof(NpcFactionSystem), typeof(SharedNPCImprintingOnSpawnBehaviourSystem))] // TO DO (Metalgearsloth): If we start adding a billion access overrides they should be going through a system as then there's no reason to have access, but I'll fix this when I rework npcs. public sealed partial class FactionExceptionComponent : Component { /// diff --git a/Content.Shared/NPC/Systems/SharedNPCImprintingOnSpawnBehaviourSystem.cs b/Content.Shared/NPC/Systems/SharedNPCImprintingOnSpawnBehaviourSystem.cs new file mode 100644 index 0000000000..f06d496e9e --- /dev/null +++ b/Content.Shared/NPC/Systems/SharedNPCImprintingOnSpawnBehaviourSystem.cs @@ -0,0 +1,5 @@ +namespace Content.Shared.NPC.Systems; + +public abstract partial class SharedNPCImprintingOnSpawnBehaviourSystem : EntitySystem +{ +} diff --git a/Content.Shared/NPC/Systems/SharedNPCSystem.cs b/Content.Shared/NPC/Systems/SharedNPCSystem.cs new file mode 100644 index 0000000000..247ab478a1 --- /dev/null +++ b/Content.Shared/NPC/Systems/SharedNPCSystem.cs @@ -0,0 +1,5 @@ +namespace Content.Shared.NPC.Systems; + +public abstract partial class SharedNPCSystem : EntitySystem +{ +} diff --git a/Resources/Locale/en-US/seeds/seeds.ftl b/Resources/Locale/en-US/seeds/seeds.ftl index b398378288..10eb533770 100644 --- a/Resources/Locale/en-US/seeds/seeds.ftl +++ b/Resources/Locale/en-US/seeds/seeds.ftl @@ -41,6 +41,8 @@ seeds-bluetomato-name = blue tomato seeds-bluetomato-display-name = blue tomato plant seeds-bloodtomato-name = blood tomato seeds-bloodtomato-display-name = blood tomato plant +seeds-killertomato-name = tomato killer +seeds-killertomato-display-name = tomato killer plant seeds-eggplant-name = eggplant seeds-eggplant-display-name = eggplants seeds-apple-name = apple diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/miscellaneous.yml b/Resources/Prototypes/Entities/Mobs/NPCs/miscellaneous.yml index 633a4ff3ca..6fca4e6a63 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/miscellaneous.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/miscellaneous.yml @@ -66,3 +66,98 @@ interactFailureString: petting-failure-generic interactSuccessSound: path: /Audio/Animals/lizard_happy.ogg + +- type: entity + id: MobTomatoKiller + parent: + - BaseSimpleMob + - MobDamageable + - MobBloodstream + - MobFlammable + - MobCombat + name: tomato killer + description: it seems today it's not you eating tomatoes, it's the tomatoes eating you. + components: + - type: Item + size: Huge + - type: NpcFactionMember + factions: + - SimpleHostile + - type: HTN + rootTask: + task: KillerTomatoCompound + - type: NPCImprintingOnSpawnBehaviour + whitelist: + components: + - HumanoidAppearance + - type: Sprite + sprite: Mobs/Demons/tomatokiller.rsi + noRot: true + layers: + - map: [ "enum.DamageStateVisualLayers.Base" ] + state: alive + - type: Bloodstream + bloodReagent: JuiceTomato + bloodMaxVolume: 50 + chemicalMaxVolume: 30 + - type: DamageStateVisuals + states: + Alive: + Base: alive + Dead: + Base: dead + - type: Butcherable + spawned: + - id: FoodMeatTomato + amount: 2 + - type: Destructible + thresholds: + - trigger: + !type:DamageTypeTrigger + damageType: Blunt + damage: 100 + behaviors: + - !type:GibBehavior { } + - type: MobThresholds + thresholds: + 0: Alive + 24: Dead + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeCircle + radius: 0.30 + density: 80 + mask: + - MobMask + layer: + - MobLayer + - type: MeleeWeapon + hidden: true + damage: + groups: + Brute: 4 + animation: WeaponArcBite + - type: Climbing + - type: NameIdentifier + group: GenericNumber + - type: SlowOnDamage + speedModifierThresholds: + 60: 0.7 + 80: 0.5 + - type: FootstepModifier + footstepSoundCollection: + path: /Audio/Effects/Footsteps/slime1.ogg + params: + volume: 3 + - type: Tag + tags: + - FootstepSound + - Fruit + - type: Extractable + grindableSolutionName: bloodstream + - type: PotencyVisuals + - type: Appearance + - type: Produce + seedId: killerTomato diff --git a/Resources/Prototypes/Entities/Objects/Specific/Hydroponics/seeds.yml b/Resources/Prototypes/Entities/Objects/Specific/Hydroponics/seeds.yml index 2b232d643d..0a084dc246 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Hydroponics/seeds.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Hydroponics/seeds.yml @@ -213,6 +213,16 @@ - type: Sprite sprite: Objects/Specific/Hydroponics/blood_tomato.rsi +- type: entity + parent: SeedBase + name: packet of killer tomato seeds + id: KillerTomatoSeeds + components: + - type: Seed + seedId: killerTomato + - type: Sprite + sprite: Objects/Specific/Hydroponics/tomatokiller.rsi + - type: entity parent: SeedBase name: packet of eggplant seeds diff --git a/Resources/Prototypes/Hydroponics/seeds.yml b/Resources/Prototypes/Hydroponics/seeds.yml index 71b20440f5..8bbbed6135 100644 --- a/Resources/Prototypes/Hydroponics/seeds.yml +++ b/Resources/Prototypes/Hydroponics/seeds.yml @@ -479,8 +479,10 @@ packetPrototype: BloodTomatoSeeds productPrototypes: - FoodBloodTomato + mutationPrototypes: + - killerTomato harvestRepeat: Repeat - lifespan: 25 + lifespan: 60 maturation: 8 production: 6 yield: 2 @@ -500,6 +502,38 @@ Max: 4 PotencyDivisor: 25 +- type: seed + id: killerTomato + name: seeds-killertomato-name + noun: seeds-noun-seeds + displayName: seeds-killertomato-display-name + plantRsi: Objects/Specific/Hydroponics/tomatokiller.rsi + packetPrototype: KillerTomatoSeeds + productPrototypes: + - MobTomatoKiller + harvestRepeat: Repeat + lifespan: 25 + maturation: 15 + production: 6 + yield: 2 + potency: 10 + waterConsumption: 0.60 + nutrientConsumption: 0.70 + idealLight: 8 + idealHeat: 298 + juicy: true + growthStages: 2 + splatPrototype: PuddleSplatter + chemicals: + Blood: + Min: 1 + Max: 10 + PotencyDivisor: 10 + JuiceTomato: + Min: 1 + Max: 4 + PotencyDivisor: 25 + - type: seed id: eggplant name: seeds-eggplant-name diff --git a/Resources/Prototypes/NPCs/mob.yml b/Resources/Prototypes/NPCs/mob.yml index 740f7ca576..b0e1c8ae9b 100644 --- a/Resources/Prototypes/NPCs/mob.yml +++ b/Resources/Prototypes/NPCs/mob.yml @@ -77,3 +77,16 @@ - tasks: - !type:HTNCompoundTask task: IdleCompound + +- type: htnCompound + id: KillerTomatoCompound + branches: + - tasks: + - !type:HTNCompoundTask + task: MeleeCombatCompound + - tasks: + - !type:HTNCompoundTask + task: FollowCompound + - tasks: + - !type:HTNCompoundTask + task: IdleCompound diff --git a/Resources/Textures/Mobs/Demons/tomatokiller.rsi/alive.png b/Resources/Textures/Mobs/Demons/tomatokiller.rsi/alive.png new file mode 100644 index 0000000000..5f459d518b Binary files /dev/null and b/Resources/Textures/Mobs/Demons/tomatokiller.rsi/alive.png differ diff --git a/Resources/Textures/Mobs/Demons/tomatokiller.rsi/dead.png b/Resources/Textures/Mobs/Demons/tomatokiller.rsi/dead.png new file mode 100644 index 0000000000..4b3ee970a0 Binary files /dev/null and b/Resources/Textures/Mobs/Demons/tomatokiller.rsi/dead.png differ diff --git a/Resources/Textures/Mobs/Demons/tomatokiller.rsi/meta.json b/Resources/Textures/Mobs/Demons/tomatokiller.rsi/meta.json new file mode 100644 index 0000000000..57e4c7351f --- /dev/null +++ b/Resources/Textures/Mobs/Demons/tomatokiller.rsi/meta.json @@ -0,0 +1,40 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": " taken from TG on commit https://github.com/tgstation/tgstation/commit/7e5f13f558253e76865e81c9641b7ec68e57754b", + "states": [ + { + "name": "alive", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "dead" + } + ] +} diff --git a/Resources/Textures/Objects/Specific/Hydroponics/tomatokiller.rsi/dead.png b/Resources/Textures/Objects/Specific/Hydroponics/tomatokiller.rsi/dead.png new file mode 100644 index 0000000000..0051c4dc73 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Hydroponics/tomatokiller.rsi/dead.png differ diff --git a/Resources/Textures/Objects/Specific/Hydroponics/tomatokiller.rsi/harvest.png b/Resources/Textures/Objects/Specific/Hydroponics/tomatokiller.rsi/harvest.png new file mode 100644 index 0000000000..46a3b38982 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Hydroponics/tomatokiller.rsi/harvest.png differ diff --git a/Resources/Textures/Objects/Specific/Hydroponics/tomatokiller.rsi/meta.json b/Resources/Textures/Objects/Specific/Hydroponics/tomatokiller.rsi/meta.json new file mode 100644 index 0000000000..84a4237a60 --- /dev/null +++ b/Resources/Textures/Objects/Specific/Hydroponics/tomatokiller.rsi/meta.json @@ -0,0 +1,26 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/vgstation-coders/vgstation13 at 1dbcf389b0ec6b2c51b002df5fef8dd1519f8068", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "dead" + }, + { + "name": "harvest" + }, + { + "name": "seed" + }, + { + "name": "stage-1" + }, + { + "name": "stage-2" + } + ] +} diff --git a/Resources/Textures/Objects/Specific/Hydroponics/tomatokiller.rsi/seed.png b/Resources/Textures/Objects/Specific/Hydroponics/tomatokiller.rsi/seed.png new file mode 100644 index 0000000000..110dc64a4f Binary files /dev/null and b/Resources/Textures/Objects/Specific/Hydroponics/tomatokiller.rsi/seed.png differ diff --git a/Resources/Textures/Objects/Specific/Hydroponics/tomatokiller.rsi/stage-1.png b/Resources/Textures/Objects/Specific/Hydroponics/tomatokiller.rsi/stage-1.png new file mode 100644 index 0000000000..0b1d58de9d Binary files /dev/null and b/Resources/Textures/Objects/Specific/Hydroponics/tomatokiller.rsi/stage-1.png differ diff --git a/Resources/Textures/Objects/Specific/Hydroponics/tomatokiller.rsi/stage-2.png b/Resources/Textures/Objects/Specific/Hydroponics/tomatokiller.rsi/stage-2.png new file mode 100644 index 0000000000..6225f0c62d Binary files /dev/null and b/Resources/Textures/Objects/Specific/Hydroponics/tomatokiller.rsi/stage-2.png differ