From b174af7661cbfaf564292caabfccca18619bda23 Mon Sep 17 00:00:00 2001 From: Jacquerel Date: Mon, 26 Dec 2022 02:24:18 +0000 Subject: [PATCH] Basic Mob Carp Part VIII: Basic Mob Carp (#72073) ## About The Pull Request Wow we're finally here. This turns carp into Basic Mobs instead of Simple Animals. They use a variety of behaviours added in previous PRs to act in a marginally more interesting way than they used to. But don't worry there's still 2 or 3 PRs to follow this one until I'm done with space fish. Changes in this PR: Carp will try to run away if they get below 50% health, to make use of their "regenerate if not attacked" component. Magicarp have different targetting behaviour for spells depending on their spell; - Ressurecting Carp will try to ressurect allied mobs. - Animating Carp will try to animate nearby objects. - Door-creating Carp will try to turn nearby walls into doors. You can order Magicarp to cast their spell on something if you happen to manage to tame one. The eating element now has support for "getting hurt" when you eat something. Carp eating can rings and hating it was too soulful not to continue supporting. ## Why It's Good For The Game Carp are iconic beasts and I think they should be more interesting. Also we just want to turn mobs into basic mobs anyway. ## Changelog :cl: add: Carp will now run away if their health gets low, meaning they may have a chance to regenerate. add: Lia will now fight back if attacked instead of letting herself get killed, watch out! balance: Magicarp will now aim their spells more intelligently. add: Tame Magicarp can be ordered to use their spells on things. refactor: Carp are now "Basic Mobs" instead of "Simple Mobs" fix: Dehydrated carp no longer give you a bad feeling when they're your friend and a good feeling when they're going to attack you. balance: Tamed carp are now friendly only to their tamer rather than their whole faction, which should make dehydrated carp more active. Order them to stay or follow you if you want them to behave around your friends. /:cl: --- _maps/RandomRuins/SpaceRuins/abandonedzoo.dmm | 2 +- .../RandomRuins/SpaceRuins/caravanambush.dmm | 2 +- _maps/RandomRuins/SpaceRuins/oldstation.dmm | 2 +- .../map_files/Deltastation/DeltaStation2.dmm | 2 +- .../map_files/IceBoxStation/IceBoxStation.dmm | 2 +- _maps/map_files/KiloStation/KiloStation.dmm | 4 +- _maps/map_files/MetaStation/MetaStation.dmm | 104 ++++--- _maps/templates/lazy_templates/ninja_den.dmm | 3 +- _maps/templates/lazy_templates/nukie_base.dmm | 2 +- code/__DEFINES/ai_carp.dm | 9 + .../configuration/configuration.dm | 2 +- .../basic_ai_behaviors/try_mob_ability.dm | 33 ++- .../basic_subtrees/targetted_mob_ability.dm | 26 ++ code/datums/ai/generic/find_and_set.dm | 60 ++++ .../pet_commands/pet_commands_basic.dm | 2 +- code/datums/components/spawner.dm | 2 +- code/datums/elements/basic_eating.dm | 35 ++- code/datums/memory/memory.dm | 6 +- .../machinery/dna_infuser/infuser_entries.dm | 2 +- code/game/objects/items/dehy_carp.dm | 4 +- code/game/objects/items/dna_probe.dm | 2 +- .../objects/items/grenades/spawnergrenade.dm | 2 +- code/game/objects/structures/spawner.dm | 2 +- .../heretic/magic/eldritch_shapeshift.dm | 2 +- .../nukeop/equipment/nuclear_challenge.dm | 2 +- .../antagonists/space_dragon/space_dragon.dm | 4 +- .../traitor/objectives/kill_pet.dm | 2 +- code/modules/events/carp_migration.dm | 4 +- code/modules/events/ghost_role/sentience.dm | 2 +- code/modules/events/scrubber_clog.dm | 2 +- code/modules/events/wizard/magicarp.dm | 4 +- .../experisci/experiment/experiments.dm | 2 +- code/modules/holodeck/holo_effect.dm | 2 +- code/modules/meteors/meteors.dm | 4 +- .../mob/living/basic/farm_animals/cow/_cow.dm | 2 +- .../basic/farm_animals/cow/cow_moonicorn.dm | 2 +- .../mob/living/basic/space_fauna/carp/carp.dm | 215 +++++++++++++++ .../basic/space_fauna/carp/carp_abilities.dm | 29 ++ .../basic/space_fauna/carp/carp_ai_actions.dm | 74 +++++ .../space_fauna/carp/carp_controllers.dm | 62 +++++ .../living/basic/space_fauna/carp/magicarp.dm | 136 +++++++++ .../living/basic/space_fauna/carp/megacarp.dm | 34 +++ code/modules/mob/living/living.dm | 6 +- .../mob/living/simple_animal/hostile/carp.dm | 257 ------------------ .../simple_animal/hostile/carp_magic.dm | 97 ------- .../mob/living/simple_animal/hostile/mimic.dm | 4 +- code/modules/mob/transform_procs.dm | 6 +- code/modules/projectiles/projectile/magic.dm | 2 +- code/modules/research/experimentor.dm | 2 +- .../vatgrowing/samples/cell_lines/common.dm | 4 +- .../spells/spell_types/conjure/carp.dm | 2 +- .../spell_types/shapeshift/shapechange.dm | 2 +- .../unit_tests/simple_animal_freeze.dm | 9 - tgstation.dme | 10 +- .../Scripts/71421_simple_carp_to_basic.txt | 8 + 55 files changed, 815 insertions(+), 488 deletions(-) create mode 100644 code/__DEFINES/ai_carp.dm create mode 100644 code/datums/ai/basic_mobs/basic_subtrees/targetted_mob_ability.dm create mode 100644 code/modules/mob/living/basic/space_fauna/carp/carp.dm create mode 100644 code/modules/mob/living/basic/space_fauna/carp/carp_abilities.dm create mode 100644 code/modules/mob/living/basic/space_fauna/carp/carp_ai_actions.dm create mode 100644 code/modules/mob/living/basic/space_fauna/carp/carp_controllers.dm create mode 100644 code/modules/mob/living/basic/space_fauna/carp/magicarp.dm create mode 100644 code/modules/mob/living/basic/space_fauna/carp/megacarp.dm delete mode 100644 code/modules/mob/living/simple_animal/hostile/carp.dm delete mode 100644 code/modules/mob/living/simple_animal/hostile/carp_magic.dm create mode 100644 tools/UpdatePaths/Scripts/71421_simple_carp_to_basic.txt diff --git a/_maps/RandomRuins/SpaceRuins/abandonedzoo.dmm b/_maps/RandomRuins/SpaceRuins/abandonedzoo.dmm index c8179a4468f..4cf3084e4a8 100644 --- a/_maps/RandomRuins/SpaceRuins/abandonedzoo.dmm +++ b/_maps/RandomRuins/SpaceRuins/abandonedzoo.dmm @@ -471,7 +471,7 @@ /area/ruin/space/has_grav/abandonedzoo) "Ig" = ( /obj/machinery/light/directional/north, -/mob/living/simple_animal/hostile/carp, +/mob/living/basic/carp, /turf/open/floor/plating, /area/ruin/space/has_grav/abandonedzoo) "LD" = ( diff --git a/_maps/RandomRuins/SpaceRuins/caravanambush.dmm b/_maps/RandomRuins/SpaceRuins/caravanambush.dmm index cd42b018d94..a44d289abf1 100644 --- a/_maps/RandomRuins/SpaceRuins/caravanambush.dmm +++ b/_maps/RandomRuins/SpaceRuins/caravanambush.dmm @@ -102,7 +102,7 @@ /turf/template_noop, /area/template_noop) "bl" = ( -/mob/living/simple_animal/hostile/carp, +/mob/living/basic/carp, /turf/template_noop, /area/template_noop) "br" = ( diff --git a/_maps/RandomRuins/SpaceRuins/oldstation.dmm b/_maps/RandomRuins/SpaceRuins/oldstation.dmm index 8186191d5c7..48eda5d8e1a 100644 --- a/_maps/RandomRuins/SpaceRuins/oldstation.dmm +++ b/_maps/RandomRuins/SpaceRuins/oldstation.dmm @@ -3,7 +3,7 @@ /turf/template_noop, /area/template_noop) "ab" = ( -/mob/living/simple_animal/hostile/carp, +/mob/living/basic/carp, /turf/template_noop, /area/template_noop) "ad" = ( diff --git a/_maps/map_files/Deltastation/DeltaStation2.dmm b/_maps/map_files/Deltastation/DeltaStation2.dmm index 63b792cf07c..ced537f5ee5 100644 --- a/_maps/map_files/Deltastation/DeltaStation2.dmm +++ b/_maps/map_files/Deltastation/DeltaStation2.dmm @@ -98434,7 +98434,7 @@ /obj/effect/turf_decal/siding/dark_red{ dir = 1 }, -/mob/living/simple_animal/hostile/carp/lia, +/mob/living/basic/carp/pet/lia, /turf/open/floor/iron/grimy, /area/station/command/heads_quarters/hos) "xXt" = ( diff --git a/_maps/map_files/IceBoxStation/IceBoxStation.dmm b/_maps/map_files/IceBoxStation/IceBoxStation.dmm index 7644a4ecd72..9f080b7f627 100644 --- a/_maps/map_files/IceBoxStation/IceBoxStation.dmm +++ b/_maps/map_files/IceBoxStation/IceBoxStation.dmm @@ -47276,7 +47276,7 @@ /area/station/maintenance/port/aft) "oxJ" = ( /obj/structure/bed/dogbed/lia, -/mob/living/simple_animal/hostile/carp/lia, +/mob/living/basic/carp/pet/lia, /obj/structure/cable, /turf/open/floor/carpet/royalblue, /area/station/command/heads_quarters/hos) diff --git a/_maps/map_files/KiloStation/KiloStation.dmm b/_maps/map_files/KiloStation/KiloStation.dmm index 4b437cf022d..b9d214a05f7 100644 --- a/_maps/map_files/KiloStation/KiloStation.dmm +++ b/_maps/map_files/KiloStation/KiloStation.dmm @@ -76845,7 +76845,7 @@ /obj/structure/bed/dogbed/cayenne{ name = "Lia's bed" }, -/mob/living/simple_animal/hostile/carp/lia, +/mob/living/basic/carp/pet/lia, /turf/open/floor/iron/dark, /area/station/command/heads_quarters/hos) "vPd" = ( @@ -78742,7 +78742,7 @@ /turf/open/floor/iron, /area/station/security/prison) "woH" = ( -/mob/living/simple_animal/hostile/carp{ +/mob/living/basic/carp{ environment_smash = 0; name = "Tuna"; real_name = "Tuna" diff --git a/_maps/map_files/MetaStation/MetaStation.dmm b/_maps/map_files/MetaStation/MetaStation.dmm index 48ecf6e8b91..e238fc79989 100644 --- a/_maps/map_files/MetaStation/MetaStation.dmm +++ b/_maps/map_files/MetaStation/MetaStation.dmm @@ -12789,9 +12789,6 @@ }, /turf/open/floor/engine/co2, /area/station/engineering/atmos) -"eJX" = ( -/turf/open/floor/iron, -/area/station/engineering/atmos) "eJZ" = ( /obj/effect/spawner/random/structure/chair_comfy{ dir = 4 @@ -18064,13 +18061,6 @@ /obj/effect/mapping_helpers/airlock/access/all/security/general, /turf/open/floor/plating, /area/station/maintenance/fore) -"gKt" = ( -/obj/structure/disposalpipe/segment, -/obj/structure/cable, -/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, -/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, -/turf/open/floor/iron, -/area/station/engineering/main) "gKw" = ( /obj/effect/turf_decal/delivery, /obj/effect/turf_decal/tile/yellow{ @@ -109844,16 +109834,16 @@ tCS xzs vQV lnT -gKt -gKt -gKt -gKt +fbf +fbf +fbf +fbf rPe rPe -gKt -gKt -gKt -gKt +fbf +fbf +fbf +fbf dhi rPe vjB @@ -110148,8 +110138,8 @@ gnT lgL lrK heV -eJX -eJX +cyW +cyW ffk hHt lRS @@ -110869,7 +110859,7 @@ xID hTV brE vtp -gKt +fbf shV sGC stl @@ -111681,11 +111671,11 @@ the wDG udD sCN -eJX +cyW ppD -eJX -eJX -eJX +cyW +cyW +cyW iQB tds nzo @@ -111939,12 +111929,12 @@ hdp htY ruP gSn -eJX -eJX -eJX -eJX -eJX -eJX +cyW +cyW +cyW +cyW +cyW +cyW nzo mTI jvj @@ -112195,12 +112185,12 @@ gto aej ahV ruP -eJX -eJX -eJX +cyW +cyW +cyW vlq -eJX -eJX +cyW +cyW lxt nzo vuU @@ -112452,13 +112442,13 @@ gto aej qDt ruP -eJX -eJX cyW cyW cyW -eJX -eJX +cyW +cyW +cyW +cyW nzo vuU dwf @@ -112713,12 +112703,12 @@ ruP ruP ruP ruP -eJX -eJX +cyW +cyW dfS nzo vuU -eJX +cyW kEe fPg jie @@ -113994,7 +113984,7 @@ lAh kvK iPx qHh -eJX +cyW fBc dGq uQe @@ -114251,7 +114241,7 @@ lAh dHG iui vlq -eJX +cyW jKG gpQ uQe @@ -114508,7 +114498,7 @@ lAh htD ukq sdE -eJX +cyW jKG gpQ uQe @@ -114774,7 +114764,7 @@ nzo fiE hur uel -eJX +cyW nnD lRA xXG @@ -115031,7 +115021,7 @@ mDA jZz vlq gTC -eJX +cyW qzK vdf eSZ @@ -115288,7 +115278,7 @@ nzo fzM rZt jZz -eJX +cyW ccK lqS kBh @@ -115802,7 +115792,7 @@ nzo qVi uhx psl -eJX +cyW nnD aIz xXG @@ -116059,7 +116049,7 @@ xRc qTA jZz nnD -eJX +cyW nnD nMe oIM @@ -116572,8 +116562,8 @@ trM qwi jvj pul -eJX -eJX +cyW +cyW plD wGB oIM @@ -116826,7 +116816,7 @@ vTX uIe dLl boD -eJX +cyW uDH xEN mei @@ -117083,11 +117073,11 @@ vTX jTS sCp qFA -eJX +cyW jvj pul -eJX -eJX +cyW +cyW ewU tcA oIM diff --git a/_maps/templates/lazy_templates/ninja_den.dmm b/_maps/templates/lazy_templates/ninja_den.dmm index f4d096c81e4..052e37c3c86 100644 --- a/_maps/templates/lazy_templates/ninja_den.dmm +++ b/_maps/templates/lazy_templates/ninja_den.dmm @@ -769,8 +769,7 @@ /obj/structure/bed/dogbed/cayenne{ name = "Paprika's bed" }, -/mob/living/simple_animal/hostile/carp/cayenne{ - aggro_vision_range = 1; +/mob/living/basic/carp/pet/cayenne{ desc = "It's Paprika! One of the Spider Clan's lovable Space Carp!"; faction = list("neutral"); name = "Paprika"; diff --git a/_maps/templates/lazy_templates/nukie_base.dmm b/_maps/templates/lazy_templates/nukie_base.dmm index 69487830920..95f212174b1 100644 --- a/_maps/templates/lazy_templates/nukie_base.dmm +++ b/_maps/templates/lazy_templates/nukie_base.dmm @@ -1619,7 +1619,7 @@ pixel_y = 27 }, /obj/structure/bed/dogbed/cayenne, -/mob/living/simple_animal/hostile/carp/cayenne, +/mob/living/basic/carp/pet/cayenne, /obj/machinery/atmospherics/pipe/smart/manifold4w/general/hidden, /obj/machinery/atmospherics/pipe/smart/manifold4w/yellow/hidden/layer2, /obj/machinery/atmospherics/pipe/smart/manifold4w/orange/hidden/layer5, diff --git a/code/__DEFINES/ai_carp.dm b/code/__DEFINES/ai_carp.dm new file mode 100644 index 00000000000..575d18939d2 --- /dev/null +++ b/code/__DEFINES/ai_carp.dm @@ -0,0 +1,9 @@ +///Carp AI blackboard keys +#define BB_MAGICARP_SPELL "BB_magicarp_spell" + +/// Targetting keys for magicarp spells +#define BB_MAGICARP_SPELL_TARGET "BB_magicarp_spell_target" +#define BB_MAGICARP_SPELL_SPECIAL_TARGETTING "BB_magicarp_spell_special_targetting" +#define MAGICARP_SPELL_CORPSES "magicarp_spell_corpses" +#define MAGICARP_SPELL_WALLS "magicarp_spell_walls" +#define MAGICARP_SPELL_OBJECTS "magicarp_spell_objects" diff --git a/code/controllers/configuration/configuration.dm b/code/controllers/configuration/configuration.dm index 01719807f5d..0ca9e039e4e 100644 --- a/code/controllers/configuration/configuration.dm +++ b/code/controllers/configuration/configuration.dm @@ -294,7 +294,7 @@ Value is raw html. Possible keywords : Job titles / Assigned roles (ghost spawners for example) : Assistant , Captain , Ash Walker -Mob types : /mob/living/simple_animal/hostile/carp +Mob types : /mob/living/basic/carp Antagonist types : /datum/antagonist/highlander Species types : /datum/species/lizard special keywords defined in _DEFINES/admin.dm diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/try_mob_ability.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/try_mob_ability.dm index 34f89591593..aad52b924bb 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/try_mob_ability.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/try_mob_ability.dm @@ -1,12 +1,16 @@ +/** + * # Try Mob Ability + * Attempts to use a mob's cooldown ability on a target + */ /datum/ai_behavior/try_mob_ability /datum/ai_behavior/try_mob_ability/perform(delta_time, datum/ai_controller/controller, ability_key, target_key) - var/datum/action/cooldown/mob_cooldown/ability = controller.blackboard[ability_key] var/datum/weakref/weak_target = controller.blackboard[target_key] var/mob/living/target = weak_target?.resolve() if(!ability || QDELETED(target)) finish_action(controller, FALSE, ability_key, target_key) + return var/mob/pawn = controller.pawn var/result = ability.InterceptClickOn(pawn, null, target) finish_action(controller, result, ability_key, target_key) @@ -14,14 +18,33 @@ /datum/ai_behavior/try_mob_ability/finish_action(datum/ai_controller/controller, succeeded, ability_key, target_key) . = ..() var/datum/weakref/weak_target = controller.blackboard[target_key] - var/mob/living/target = weak_target?.resolve() - if(QDELETED(target) || target.stat >= UNCONSCIOUS) + var/atom/target = weak_target?.resolve() + if (QDELETED(target)) + controller.blackboard[target_key] = null + return + if (!isliving(target)) + return + var/mob/living/living_target = target + if(living_target.stat >= UNCONSCIOUS) controller.blackboard[target_key] = null -///subtype of normal mob ability that moves the target into a special execution targetting. -///doesn't need another subtype to clear BB_BASIC_MOB_EXECUTION_TARGET because it will be the target key for above type +/** + * # Try Mob Ability and plan execute + * Attempts to use a mob's cooldown ability on a target and then move the target into a special target blackboard datum + * Doesn't need another subtype to clear BB_BASIC_MOB_EXECUTION_TARGET because it will be the target key for the normal action + */ /datum/ai_behavior/try_mob_ability/and_plan_execute /datum/ai_behavior/try_mob_ability/and_plan_execute/finish_action(datum/ai_controller/controller, succeeded, ability_key, target_key) controller.blackboard[BB_BASIC_MOB_EXECUTION_TARGET] = controller.blackboard[target_key] return ..() + +/** + * # Try Mob Ability and clear target + * Attempts to use a mob's cooldown ability on a target and releases the target when the action completes + */ +/datum/ai_behavior/try_mob_ability/and_clear_target + +/datum/ai_behavior/try_mob_ability/and_clear_target/finish_action(datum/ai_controller/controller, succeeded, ability_key, target_key) + . = ..() + controller.blackboard[target_key] = null diff --git a/code/datums/ai/basic_mobs/basic_subtrees/targetted_mob_ability.dm b/code/datums/ai/basic_mobs/basic_subtrees/targetted_mob_ability.dm new file mode 100644 index 00000000000..4450f9fa331 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/targetted_mob_ability.dm @@ -0,0 +1,26 @@ +/// Attempts to use a mob ability on a target +/datum/ai_planning_subtree/targetted_mob_ability + /// Blackboard key for the ability + var/ability_key + /// Blackboard key for where the target ref is stored + var/target_key = BB_BASIC_MOB_CURRENT_TARGET + /// Behaviour to perform using ability + var/use_ability_behaviour = /datum/ai_behavior/try_mob_ability + +/datum/ai_planning_subtree/targetted_mob_ability/SelectBehaviors(datum/ai_controller/controller, delta_time) + if (!ability_key) + CRASH("You forgot to tell this mob where to find its ability") + + var/datum/weakref/weak_target = controller.blackboard[target_key] + var/mob/living/target = weak_target?.resolve() + if (QDELETED(target)) + return + + var/datum/action/cooldown/using_action = controller.blackboard[ability_key] + if (QDELETED(using_action)) + return + if (!using_action.IsAvailable()) + return + + controller.queue_behavior(use_ability_behaviour, ability_key, target_key) + return SUBTREE_RETURN_FINISH_PLANNING diff --git a/code/datums/ai/generic/find_and_set.dm b/code/datums/ai/generic/find_and_set.dm index 693d43d70d3..b2897f58276 100644 --- a/code/datums/ai/generic/find_and_set.dm +++ b/code/datums/ai/generic/find_and_set.dm @@ -72,3 +72,63 @@ found += single_locate if(found.len) return pick(found) + +/** + * Variant of find and set which returns an object which can be animated with a staff of change + */ +/datum/ai_behavior/find_and_set/animatable + +/datum/ai_behavior/find_and_set/animatable/search_tactic(datum/ai_controller/controller, locate_path, search_range) + var/mob/living/living_pawn = controller.pawn + + var/list/nearby_items = list() + for (var/obj/new_friend as anything in oview(search_range, controller.pawn)) + if (!isitem(new_friend) && !isstructure(new_friend)) + continue + if (is_type_in_list(new_friend, GLOB.animatable_blacklist)) + continue + if (living_pawn.see_invisible < new_friend.invisibility) + continue + nearby_items += new_friend + + if(nearby_items.len) + return pick(nearby_items) + +/** + * Variant of find and set which returns the nearest wall which isn't invulnerable + */ +/datum/ai_behavior/find_and_set/nearest_wall + +/datum/ai_behavior/find_and_set/nearest_wall/search_tactic(datum/ai_controller/controller, locate_path, search_range) + var/mob/living/living_pawn = controller.pawn + + var/list/nearby_walls = list() + for (var/turf/closed/new_wall in oview(search_range, controller.pawn)) + if (isindestructiblewall(new_wall)) + continue + nearby_walls += new_wall + + if(nearby_walls.len) + return get_closest_atom(/turf/closed/, nearby_walls, living_pawn) + +/** + * Variant of find and set which returns corpses who share your faction + */ +/datum/ai_behavior/find_and_set/friendly_corpses + +/datum/ai_behavior/find_and_set/friendly_corpses/search_tactic(datum/ai_controller/controller, locate_path, search_range) + var/mob/living/living_pawn = controller.pawn + var/list/nearby_bodies = list() + for (var/mob/living/dead_pal in oview(search_range, controller.pawn)) + if (!isturf(dead_pal.loc)) + continue + if (!dead_pal.stat || dead_pal.health > 0) + continue + if (living_pawn.see_invisible < dead_pal.invisibility) + continue + if (!living_pawn.faction_check_mob(dead_pal)) + continue + nearby_bodies += dead_pal + + if (nearby_bodies.len) + return pick(nearby_bodies) diff --git a/code/datums/components/pet_commands/pet_commands_basic.dm b/code/datums/components/pet_commands/pet_commands_basic.dm index 7caad390e06..641e3a68748 100644 --- a/code/datums/components/pet_commands/pet_commands_basic.dm +++ b/code/datums/components/pet_commands/pet_commands_basic.dm @@ -120,5 +120,5 @@ return // We don't check if the target exists because we want to 'sit attentively' if we've been instructed to attack but not given one yet // We also don't check if the cooldown is over because there's no way a pet owner can know that, the behaviour will handle it - controller.queue_behavior(/datum/ai_behavior/pet_use_ability, pet_ability_key, targetting_datum_key) + controller.queue_behavior(/datum/ai_behavior/pet_use_ability, pet_ability_key, BB_CURRENT_PET_TARGET) return SUBTREE_RETURN_FINISH_PLANNING diff --git a/code/datums/components/spawner.dm b/code/datums/components/spawner.dm index d3e824ae0e1..7cc18937127 100644 --- a/code/datums/components/spawner.dm +++ b/code/datums/components/spawner.dm @@ -1,5 +1,5 @@ /datum/component/spawner - var/mob_types = list(/mob/living/simple_animal/hostile/carp) + var/mob_types = list(/mob/living/basic/carp) var/spawn_time = 300 //30 seconds default var/list/spawned_mobs = list() var/spawn_delay = 0 diff --git a/code/datums/elements/basic_eating.dm b/code/datums/elements/basic_eating.dm index 593aa24adee..86f4be63cac 100644 --- a/code/datums/elements/basic_eating.dm +++ b/code/datums/elements/basic_eating.dm @@ -6,18 +6,24 @@ /datum/element/basic_eating element_flags = ELEMENT_BESPOKE argument_hash_start_idx = 2 - ///Path of the reagent added + /// Amount to heal var/heal_amt + /// Amount to hurt + var/damage_amount + /// Type of hurt to apply + var/damage_type /// Types the animal can eat. var/list/food_types -/datum/element/basic_eating/Attach(datum/target, heal_amt = 0, food_types = list()) +/datum/element/basic_eating/Attach(datum/target, heal_amt = 0, damage_amount = 0, damage_type = null, food_types = list()) . = ..() if(!isliving(target)) return ELEMENT_INCOMPATIBLE src.heal_amt = heal_amt + src.damage_amount = damage_amount + src.damage_type = damage_type src.food_types = food_types //this lets players eat @@ -38,14 +44,27 @@ try_eating(eater, target) /datum/element/basic_eating/proc/try_eating(mob/living/eater, atom/target) - if(eater.combat_mode) - return if(!is_type_in_list(target, food_types)) return var/eat_verb = pick("bite","chew","nibble","gnaw","gobble","chomp") - var/healed = heal_amt && eater.health < eater.maxHealth - if(heal_amt) - eater.heal_overall_damage(heal_amt) - eater.visible_message(span_notice("[eater] [eat_verb]s [target]."), span_notice("You [eat_verb] [target][healed ? ", restoring some health" : ""].")) + + if (heal_amt > 0) + var/healed = heal_amt && eater.health < eater.maxHealth + if(heal_amt) + eater.heal_overall_damage(heal_amt) + eater.visible_message(span_notice("[eater] [eat_verb]s [target]."), span_notice("You [eat_verb] [target][healed ? ", restoring some health" : ""].")) + finish_eating(eater, target) + return + + if (damage_amount > 0 && damage_type) + eater.apply_damage(damage_amount, damage_type) + eater.visible_message(span_notice("[eater] [eat_verb]s [target], and seems to hurt itself."), span_notice("You [eat_verb] [target], hurting yourself in the process.")) + finish_eating(eater, target) + return + + eater.visible_message(span_notice("[eater] [eat_verb]s [target]."), span_notice("You [eat_verb] [target].")) + finish_eating(eater, target) + +/datum/element/basic_eating/proc/finish_eating(mob/living/eater, atom/target) playsound(eater.loc,'sound/items/eatfood.ogg', rand(10,50), TRUE) qdel(target) diff --git a/code/datums/memory/memory.dm b/code/datums/memory/memory.dm index a77e82d280b..adebd193b73 100644 --- a/code/datums/memory/memory.dm +++ b/code/datums/memory/memory.dm @@ -45,7 +45,7 @@ //entirely independent vars (not related to the action or story type) var/static/list/something_pool = list( - /mob/living/simple_animal/hostile/carp, + /mob/living/basic/carp, /mob/living/simple_animal/hostile/bear, /mob/living/simple_animal/hostile/mushroom, /mob/living/simple_animal/hostile/netherworld/statue, @@ -55,8 +55,8 @@ /mob/living/simple_animal/hostile/giant_spider, /mob/living/simple_animal/hostile/giant_spider/hunter, /mob/living/simple_animal/hostile/blob/blobbernaut/independent, - /mob/living/simple_animal/hostile/carp/ranged, - /mob/living/simple_animal/hostile/carp/ranged/chaos, + /mob/living/basic/carp/magic, + /mob/living/basic/carp/magic/chaos, /mob/living/simple_animal/hostile/asteroid/basilisk/watcher, /mob/living/simple_animal/hostile/asteroid/goliath/beast, /mob/living/simple_animal/hostile/headcrab, diff --git a/code/game/machinery/dna_infuser/infuser_entries.dm b/code/game/machinery/dna_infuser/infuser_entries.dm index 1f9309585c1..f8c080dd280 100644 --- a/code/game/machinery/dna_infuser/infuser_entries.dm +++ b/code/game/machinery/dna_infuser/infuser_entries.dm @@ -89,7 +89,7 @@ GLOBAL_LIST_INIT(infuser_entries, prepare_entries()) "always wants to travel", ) input_obj_or_mob = list( - /mob/living/simple_animal/hostile/carp, + /mob/living/basic/carp, ) output_organs = list( /obj/item/organ/internal/lungs/carp, diff --git a/code/game/objects/items/dehy_carp.dm b/code/game/objects/items/dehy_carp.dm index cf59cd214ab..17b26159dfd 100644 --- a/code/game/objects/items/dehy_carp.dm +++ b/code/game/objects/items/dehy_carp.dm @@ -6,7 +6,7 @@ //Child of carpplushie because this should do everything the toy does and more /obj/item/toy/plush/carpplushie/dehy_carp var/mob/owner = null //Carp doesn't attack owner, set when using in hand - var/mobtype = /mob/living/simple_animal/hostile/carp //So admins can change what mob spawns via var fuckery + var/mobtype = /mob/living/basic/carp //So admins can change what mob spawns via var fuckery var/swelling = FALSE //Attack self @@ -63,7 +63,7 @@ if(owner) spawned_mob.faction = list("[REF(owner)]") for(var/mob/living/viewer in viewers(5, get_turf(src))) - to_chat(viewer, viewer == owner ? span_warning("You have a bad feeling about this.") : span_notice("The newly grown [spawned_mob.name] looks up at you with friendly eyes.")) + to_chat(viewer, viewer == owner ? span_notice("The newly grown [spawned_mob.name] looks up at you with friendly eyes.") : span_warning("You have a bad feeling about this.")) qdel(src) /obj/item/toy/plush/carpplushie/dehy_carp/proc/owner_deleted(datum/source) diff --git a/code/game/objects/items/dna_probe.dm b/code/game/objects/items/dna_probe.dm index 876de726f5b..e150e6128ca 100644 --- a/code/game/objects/items/dna_probe.dm +++ b/code/game/objects/items/dna_probe.dm @@ -49,7 +49,7 @@ if(allowed_scans & DNA_PROBE_SCAN_ANIMALS) var/static/list/non_simple_animals = typecacheof(list(/mob/living/carbon/alien)) if(isanimal_or_basicmob(target) || is_type_in_typecache(target, non_simple_animals) || ismonkey(target)) - if(istype(target, /mob/living/simple_animal/hostile/carp)) + if(istype(target, /mob/living/basic/carp)) carp_dna_loaded = TRUE var/mob/living/living_target = target if(stored_dna_animal[living_target.type]) diff --git a/code/game/objects/items/grenades/spawnergrenade.dm b/code/game/objects/items/grenades/spawnergrenade.dm index 94533025a61..cdd2b14cedf 100644 --- a/code/game/objects/items/grenades/spawnergrenade.dm +++ b/code/game/objects/items/grenades/spawnergrenade.dm @@ -36,7 +36,7 @@ /obj/item/grenade/spawnergrenade/spesscarp name = "carp delivery grenade" - spawner_type = /mob/living/simple_animal/hostile/carp + spawner_type = /mob/living/basic/carp deliveryamt = 5 /obj/item/grenade/spawnergrenade/syndiesoap diff --git a/code/game/objects/structures/spawner.dm b/code/game/objects/structures/spawner.dm index 61baa0a5297..e6b9f344d72 100644 --- a/code/game/objects/structures/spawner.dm +++ b/code/game/objects/structures/spawner.dm @@ -10,7 +10,7 @@ var/max_mobs = 5 var/spawn_time = 30 SECONDS - var/mob_types = list(/mob/living/simple_animal/hostile/carp) + var/mob_types = list(/mob/living/basic/carp) var/spawn_text = "emerges from" var/faction = list("hostile") var/spawner_type = /datum/component/spawner diff --git a/code/modules/antagonists/heretic/magic/eldritch_shapeshift.dm b/code/modules/antagonists/heretic/magic/eldritch_shapeshift.dm index 18f5a981999..aed5cb78c92 100644 --- a/code/modules/antagonists/heretic/magic/eldritch_shapeshift.dm +++ b/code/modules/antagonists/heretic/magic/eldritch_shapeshift.dm @@ -14,7 +14,7 @@ possible_shapes = list( /mob/living/basic/mouse, /mob/living/basic/pet/dog/corgi, - /mob/living/simple_animal/hostile/carp, + /mob/living/basic/carp, /mob/living/simple_animal/bot/secbot, /mob/living/simple_animal/pet/fox, /mob/living/simple_animal/pet/cat, diff --git a/code/modules/antagonists/nukeop/equipment/nuclear_challenge.dm b/code/modules/antagonists/nukeop/equipment/nuclear_challenge.dm index 318c4600cdf..12f296b8477 100644 --- a/code/modules/antagonists/nukeop/equipment/nuclear_challenge.dm +++ b/code/modules/antagonists/nukeop/equipment/nuclear_challenge.dm @@ -127,7 +127,7 @@ GLOBAL_LIST_EMPTY(jam_on_wardec) tc_to_distribute -= tc_per_nukie if (tc_to_distribute > 0) // What shall we do with the remainder... - for (var/mob/living/simple_animal/hostile/carp/cayenne/C in GLOB.mob_living_list) + for (var/mob/living/basic/carp/pet/cayenne/C in GLOB.mob_living_list) if (C.stat != DEAD) var/obj/item/stack/telecrystal/TC = new(C.drop_location(), tc_to_distribute) TC.throw_at(get_step(C, C.dir), 3, 3) diff --git a/code/modules/antagonists/space_dragon/space_dragon.dm b/code/modules/antagonists/space_dragon/space_dragon.dm index 38d8abeee46..f208d4c7f27 100644 --- a/code/modules/antagonists/space_dragon/space_dragon.dm +++ b/code/modules/antagonists/space_dragon/space_dragon.dm @@ -21,9 +21,9 @@ /// Whether or not Space Dragon has completed their objective, and thus triggered the ending sequence. var/objective_complete = FALSE /// What mob to spawn from ghosts using this dragon's rifts - var/minion_to_spawn = /mob/living/simple_animal/hostile/carp + var/minion_to_spawn = /mob/living/basic/carp /// What AI mobs to spawn from this dragon's rifts - var/ai_to_spawn = /mob/living/simple_animal/hostile/carp + var/ai_to_spawn = /mob/living/basic/carp /datum/antagonist/space_dragon/greet() . = ..() diff --git a/code/modules/antagonists/traitor/objectives/kill_pet.dm b/code/modules/antagonists/traitor/objectives/kill_pet.dm index f62a823d1c4..2d3c1c7f3c8 100644 --- a/code/modules/antagonists/traitor/objectives/kill_pet.dm +++ b/code/modules/antagonists/traitor/objectives/kill_pet.dm @@ -53,7 +53,7 @@ limited_to_department_head = FALSE possible_heads = list( JOB_HEAD_OF_SECURITY = list( - /mob/living/simple_animal/hostile/carp/lia, + /mob/living/basic/carp/pet/lia, /mob/living/simple_animal/hostile/retaliate/bat/sgt_araneus ), JOB_WARDEN = list( diff --git a/code/modules/events/carp_migration.dm b/code/modules/events/carp_migration.dm index d93d244fb0a..2ce29cdbd47 100644 --- a/code/modules/events/carp_migration.dm +++ b/code/modules/events/carp_migration.dm @@ -30,12 +30,12 @@ /datum/round_event/carp_migration/start() - var/mob/living/simple_animal/hostile/carp/fish + var/mob/living/basic/carp/fish for(var/obj/effect/landmark/carpspawn/C in GLOB.landmarks_list) if(prob(95)) fish = new (C.loc) else - fish = new /mob/living/simple_animal/hostile/carp/megacarp(C.loc) + fish = new /mob/living/basic/carp/mega(C.loc) fishannounce(fish) //Prefer to announce the megacarps over the regular fishies fishannounce(fish) diff --git a/code/modules/events/ghost_role/sentience.dm b/code/modules/events/ghost_role/sentience.dm index a26a787ff84..ca4bb0fc969 100644 --- a/code/modules/events/ghost_role/sentience.dm +++ b/code/modules/events/ghost_role/sentience.dm @@ -9,7 +9,7 @@ GLOBAL_LIST_INIT(high_priority_sentience, typecacheof(list( /mob/living/simple_animal/chicken, /mob/living/basic/cow, /mob/living/simple_animal/hostile/retaliate/bat, - /mob/living/simple_animal/hostile/carp/cayenne, + /mob/living/basic/carp/pet/cayenne, /mob/living/simple_animal/butterfly, /mob/living/simple_animal/hostile/retaliate/snake, /mob/living/simple_animal/hostile/retaliate/goose/vomit, diff --git a/code/modules/events/scrubber_clog.dm b/code/modules/events/scrubber_clog.dm index 12d3cbc838f..ecbd334cb63 100644 --- a/code/modules/events/scrubber_clog.dm +++ b/code/modules/events/scrubber_clog.dm @@ -175,7 +175,7 @@ /datum/round_event/scrubber_clog/critical/get_mob() var/static/list/mob_list = list( - /mob/living/simple_animal/hostile/carp, + /mob/living/basic/carp, /mob/living/simple_animal/hostile/bee/toxin, /mob/living/basic/cockroach/glockroach, ) diff --git a/code/modules/events/wizard/magicarp.dm b/code/modules/events/wizard/magicarp.dm index 98c168907e7..5a49a441fb9 100644 --- a/code/modules/events/wizard/magicarp.dm +++ b/code/modules/events/wizard/magicarp.dm @@ -19,6 +19,6 @@ /datum/round_event/wizard/magicarp/start() for(var/obj/effect/landmark/carpspawn/C in GLOB.landmarks_list) if(prob(5)) - new /mob/living/simple_animal/hostile/carp/ranged/chaos(C.loc) + new /mob/living/basic/carp/magic/chaos(C.loc) else - new /mob/living/simple_animal/hostile/carp/ranged(C.loc) + new /mob/living/basic/carp/magic(C.loc) diff --git a/code/modules/experisci/experiment/experiments.dm b/code/modules/experisci/experiment/experiments.dm index e41f1764021..849899d6773 100644 --- a/code/modules/experisci/experiment/experiments.dm +++ b/code/modules/experisci/experiment/experiments.dm @@ -39,7 +39,7 @@ description = "We need to see how the body functions from the earliest moments. Some cytology experiments will help us gain this understanding." total_requirement = 3 max_requirement_per_type = 2 - possible_types = list(/mob/living/simple_animal/hostile/carp, /mob/living/simple_animal/hostile/retaliate/snake, /mob/living/simple_animal/pet/cat, /mob/living/basic/pet/dog/corgi, /mob/living/basic/cow, /mob/living/simple_animal/chicken) + possible_types = list(/mob/living/basic/carp, /mob/living/simple_animal/hostile/retaliate/snake, /mob/living/simple_animal/pet/cat, /mob/living/basic/pet/dog/corgi, /mob/living/basic/cow, /mob/living/simple_animal/chicken) /datum/experiment/scanning/random/cytology/medium/one name = "Advanced Cytology Scanning Experiment One" diff --git a/code/modules/holodeck/holo_effect.dm b/code/modules/holodeck/holo_effect.dm index 4d14fc929cf..cb2853bd810 100644 --- a/code/modules/holodeck/holo_effect.dm +++ b/code/modules/holodeck/holo_effect.dm @@ -53,7 +53,7 @@ return to_spawn /obj/effect/holodeck_effect/mobspawner - var/mobtype = /mob/living/simple_animal/hostile/carp/holocarp + var/mobtype = /mob/living/basic/carp/holographic var/mob/our_mob = null /obj/effect/holodeck_effect/mobspawner/activate(obj/machinery/computer/holodeck/HC) diff --git a/code/modules/meteors/meteors.dm b/code/modules/meteors/meteors.dm index 1cbdcb2fb45..800915df346 100644 --- a/code/modules/meteors/meteors.dm +++ b/code/modules/meteors/meteors.dm @@ -387,14 +387,14 @@ GLOBAL_LIST_INIT(meteors_sandstorm, list(/obj/effect/meteor/sand=45, /obj/effect desc = "Am I glad he's frozen in there, and that we're out here." hits = 4 meteorsound = 'sound/effects/ethereal_revive_fail.ogg' - meteordrop = list(/mob/living/simple_animal/hostile/carp) + meteordrop = list(/mob/living/basic/carp) dropamt = 1 threat = 5 signature = "fishing and trawling" /obj/effect/meteor/carp/Initialize(mapload) if(prob(2)) - meteordrop = list(/mob/living/simple_animal/hostile/carp/megacarp) //hehe + meteordrop = list(/mob/living/basic/carp/mega) //hehe return ..() //bluespace meteor diff --git a/code/modules/mob/living/basic/farm_animals/cow/_cow.dm b/code/modules/mob/living/basic/farm_animals/cow/_cow.dm index 2efe81029ad..f4e4aa908e0 100644 --- a/code/modules/mob/living/basic/farm_animals/cow/_cow.dm +++ b/code/modules/mob/living/basic/farm_animals/cow/_cow.dm @@ -63,7 +63,7 @@ if(!food_types) food_types = src.food_types.Copy() AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 25, bonus_tame_chance = 15, after_tame = CALLBACK(src, PROC_REF(tamed))) - AddElement(/datum/element/basic_eating, 10, food_types) + AddElement(/datum/element/basic_eating, 10, 0, null, food_types) /mob/living/basic/cow/proc/tamed(mob/living/tamer) buckle_lying = 0 diff --git a/code/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.dm b/code/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.dm index d5a1250144d..0c0c7047bdd 100644 --- a/code/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.dm +++ b/code/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.dm @@ -34,7 +34,7 @@ var/static/list/food_types if(!food_types) food_types = src.food_types.Copy() - AddElement(/datum/element/basic_eating, 10, food_types) + AddElement(/datum/element/basic_eating, 10, 0, null, food_types) AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 25, bonus_tame_chance = 15, after_tame = CALLBACK(src, PROC_REF(tamed))) /mob/living/basic/cow/moonicorn/tamed(mob/living/tamer) diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp.dm b/code/modules/mob/living/basic/space_fauna/carp/carp.dm new file mode 100644 index 00000000000..12a60a7bad4 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/carp/carp.dm @@ -0,0 +1,215 @@ +/** + * ## Space Carp + * + * A migratory dwarf fortress reference who swim through space and sometimes bump into the space station. + * Can be created in dehydrated form by traitors, and are also summoned through rifts by space dragons. + * + * Begin regenerating their health after a short time without taking any damage, and will try to run away to do this if they get hurt. + * Lethally attracted to loose plastic. + * + * Tameable by feeding them meat, and can follow basic instructions. Rideable. + * Owned as a pet both by the HoS (sometimes) and also the Nuclear Operatives. + */ +/mob/living/basic/carp + name = "space carp" + desc = "A ferocious, fang-bearing creature that resembles a fish." + icon = 'icons/mob/simple/carp.dmi' + icon_state = "base" + icon_living = "base" + icon_dead = "base_dead" + icon_gib = "carp_gib" + gold_core_spawnable = HOSTILE_SPAWN + mob_biotypes = MOB_ORGANIC | MOB_BEAST + movement_type = FLYING + health = 25 + maxHealth = 25 + pressure_resistance = 200 + combat_mode = TRUE + obj_damage = 50 + melee_damage_lower = 20 + melee_damage_upper = 20 + attack_sound = 'sound/weapons/bite.ogg' + attack_vis_effect = ATTACK_EFFECT_BITE + attack_verb_continuous = "bites" + attack_verb_simple = "bite" + response_help_continuous = "pets" + response_help_simple = "pet" + response_disarm_continuous = "gently pushes aside" + response_disarm_simple = "gently push aside" + faction = list("carp") + butcher_results = list(/obj/item/food/fishmeat/carp = 2, /obj/item/stack/sheet/animalhide/carp = 1) + greyscale_config = /datum/greyscale_config/carp + ai_controller = /datum/ai_controller/basic_controller/carp + + /// Cytology cells you can swab from this creature + var/cell_line = CELL_LINE_TABLE_CARP + /// What colour is our 'healing' outline? + var/regenerate_colour = COLOR_PALE_GREEN + /// Information to apply when treating this carp as a vehicle + var/ridable_data = /datum/component/riding/creature/carp + /// Commands you can give this carp once it is tamed, not static because subtypes can modify it + var/tamed_commands = list( + /datum/pet_command/idle, + /datum/pet_command/free, + /datum/pet_command/follow, + /datum/pet_command/point_targetting/attack/carp + ) + /// Carp want to eat raw meat + var/static/list/desired_food = list(/obj/item/food/meat/slab, /obj/item/food/meat/rawcutlet) + /// Carp want to eat delicious six pack plastic rings + var/static/list/desired_trash = list(/obj/item/storage/cans) + /// Weighted list of colours a carp can be + /// Weighted list of usual carp colors + var/static/list/carp_colors = list( + COLOR_CARP_PURPLE = 7, + COLOR_CARP_PINK = 7, + COLOR_CARP_GREEN = 7, + COLOR_CARP_GRAPE = 7, + COLOR_CARP_SWAMP = 7, + COLOR_CARP_TURQUOISE = 7, + COLOR_CARP_BROWN = 7, + COLOR_CARP_TEAL = 7, + COLOR_CARP_LIGHT_BLUE = 7, + COLOR_CARP_RUSTY = 7, + COLOR_CARP_RED = 7, + COLOR_CARP_YELLOW = 7, + COLOR_CARP_BLUE = 7, + COLOR_CARP_PALE_GREEN = 7, + COLOR_CARP_SILVER = 1, // The rare silver carp + ) + +/mob/living/basic/carp/Initialize(mapload, mob/tamer) + . = ..() + apply_colour() + ADD_TRAIT(src, TRAIT_HEALS_FROM_CARP_RIFTS, INNATE_TRAIT) + ADD_TRAIT(src, TRAIT_SPACEWALK, INNATE_TRAIT) + + if (cell_line) + AddElement(/datum/element/swabable, cell_line, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) + AddElement(/datum/element/simple_flying) + AddElement(/datum/element/ai_flee_while_injured) + setup_eating() + + AddComponent(/datum/component/regenerator, outline_colour = regenerate_colour) + if (tamer) + befriend(tamer) + on_tamed(tamer, FALSE) + else + AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/meat), tame_chance = 10, bonus_tame_chance = 5, after_tame = CALLBACK(src, PROC_REF(on_tamed))) + +/// Tell the elements and the blackboard what food we want to eat +/mob/living/basic/carp/proc/setup_eating() + AddElement(/datum/element/basic_eating, 10, 0, null, desired_food) + AddElement(/datum/element/basic_eating, 0, 10, BRUTE, desired_trash) // We are killing our planet + ai_controller.blackboard[BB_BASIC_FOODS] = desired_food + desired_trash + +/// Set a random colour on the carp, override to do something else +/mob/living/basic/carp/proc/apply_colour() + if (!greyscale_config) + return + set_greyscale(colors = list(pick_weight(carp_colors))) + +/// Called when another mob has forged a bond of friendship with this one, passed the taming mob as 'tamer' +/mob/living/basic/carp/proc/on_tamed(mob/tamer, feedback = TRUE) + buckle_lying = 0 + AddElement(/datum/element/ridable, ridable_data) + AddComponent(/datum/component/obeys_commands, tamed_commands) + if (!feedback) + return + spin(spintime = 10, speed = 1) + visible_message("[src] spins in a circle as it seems to bond with [tamer].") + +/** + * Holographic carp from the holodeck + */ +/mob/living/basic/carp/holographic + icon_state = "holocarp" + icon_living = "holocarp" + gold_core_spawnable = NO_SPAWN + greyscale_config = NONE + basic_mob_flags = DEL_ON_DEATH + cell_line = NONE + regenerate_colour = "#ffffff" + +/// Holocarp don't eat food +/mob/living/basic/carp/holographic/setup_eating() + return FALSE + +/** + * Pet carp, abstract carp which just holds some shared properties. + */ +/mob/living/basic/carp/pet + speak_emote = list("squeaks") + gold_core_spawnable = NO_SPAWN + gender = FEMALE // Both current existing pet carp are female but you can remove this if someone else gets a male one? + ai_controller = /datum/ai_controller/basic_controller/carp/pet + +/mob/living/basic/carp/pet/Initialize(mapload) + . = ..() + AddElement(/datum/element/ai_retaliate) + AddElement(/datum/element/pet_bonus, "bloops happily!") + +/** + * Lia - Sometimes the pet of the Head of Security. + * Has a lot more health than a normal carp because she's meant to be a mildly more threatening pet to have to assassinate than an aging corgi. + */ +/mob/living/basic/carp/pet/lia + name = "Lia" + real_name = "Lia" + desc = "A failed experiment of Nanotrasen to create weaponised carp technology. This less than intimidating carp now serves as the Head of Security's pet." + faction = list("neutral") + maxHealth = 200 + health = 200 + icon_dead = "magicarp_dead" + icon_gib = "magicarp_gib" + icon_living = "magicarp" + icon_state = "magicarp" + greyscale_config = NONE + +/// Boosted chance for Cayenne to be silver +#define RARE_CAYENNE_CHANCE 10 + +/** + * Cayenne - Loyal member of the nuclear operatives. + * Spawns in the nuke op shuttle, can be made sapient if they want to do that for some reason. + * Is very talented and also capable of holding the nuclear disk. + */ +/mob/living/basic/carp/pet/cayenne + name = "Cayenne" + real_name = "Cayenne" + desc = "A failed Syndicate experiment in weaponized space carp technology, it now serves as a lovable mascot." + faction = list(ROLE_SYNDICATE) + /// Overlay to apply to display the disk + var/mutable_appearance/disk_overlay + /// Overlay to apply over the disk so it looks like cayenne is holding it + var/mutable_appearance/mouth_overlay + +/mob/living/basic/carp/pet/cayenne/Initialize(mapload) + . = ..() + var/datum/callback/got_disk = CALLBACK(src, PROC_REF(got_disk)) + var/datum/callback/display_disk = CALLBACK(src, PROC_REF(display_disk)) + AddComponent(/datum/component/nuclear_bomb_operator, got_disk, display_disk) + +/mob/living/basic/carp/pet/cayenne/apply_colour() + if (prob(RARE_CAYENNE_CHANCE)) + set_greyscale(colors = list(COLOR_CARP_SILVER)) + else + return ..() + +/// She did it! Treats for Cayenne! +/mob/living/basic/carp/pet/cayenne/proc/got_disk(obj/item/disk/nuclear/disky) + if (disky.fake) // Never mind she didn't do it + return + client.give_award(/datum/award/achievement/misc/cayenne_disk, src) + +/// Adds an overlay to show the disk on Cayenne +/mob/living/basic/carp/pet/cayenne/proc/display_disk(list/new_overlays) + if (!mouth_overlay) + mouth_overlay = mutable_appearance(SSgreyscale.GetColoredIconByType(/datum/greyscale_config/carp/disk_mouth, greyscale_colors), "disk_mouth") + new_overlays += mouth_overlay + + if (!disk_overlay) + disk_overlay = mutable_appearance('icons/mob/simple/carp.dmi', "disk_overlay") + new_overlays += disk_overlay + +#undef RARE_CAYENNE_CHANCE diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_abilities.dm b/code/modules/mob/living/basic/space_fauna/carp/carp_abilities.dm new file mode 100644 index 00000000000..abb24df73b4 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_abilities.dm @@ -0,0 +1,29 @@ +/** + * # Magicarp Bolt + * Holder ability simply for "firing a projectile with a cooldown". + * Probably won't do anything if assigned via VV unless you also VV in a projectile for it. + */ +/datum/action/cooldown/mob_cooldown/projectile_attack/magicarp_bolt + name = "Magicarp Blast" + desc = "Unleash a bolt of magical force at a target you click on." + button_icon = 'icons/obj/weapons/guns/projectiles.dmi' + button_icon_state = "arcane_barrage" + cooldown_time = 5 SECONDS + projectile_sound = 'sound/weapons/emitter.ogg' + melee_cooldown_time = 0 SECONDS // Without this they become extremely hesitant to bite anyone ever + shared_cooldown = MOB_SHARED_COOLDOWN_2 + +/datum/action/cooldown/mob_cooldown/projectile_attack/magicarp_bolt/chaos/attack_sequence(mob/living/firer, atom/target) + playsound(get_turf(firer), projectile_sound, 100, vary = TRUE) + return ..() + +/// Chaos variant picks one from a list +/datum/action/cooldown/mob_cooldown/projectile_attack/magicarp_bolt/chaos + /// List of things we can cast + var/list/permitted_projectiles = list() + +/datum/action/cooldown/mob_cooldown/projectile_attack/magicarp_bolt/chaos/attack_sequence(mob/living/firer, atom/target) + if (!length(permitted_projectiles)) + return + projectile_type = pick(permitted_projectiles) + return ..() diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_ai_actions.dm b/code/modules/mob/living/basic/space_fauna/carp/carp_ai_actions.dm new file mode 100644 index 00000000000..abda57a55ed --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_ai_actions.dm @@ -0,0 +1,74 @@ +#define MAGICARP_SPELL_TARGET_SEEK_RANGE 4 + +/datum/pet_command/point_targetting/attack/carp + attack_behaviour = /datum/ai_behavior/basic_melee_attack/carp + +/datum/pet_command/point_targetting/use_ability/magicarp + pet_ability_key = BB_MAGICARP_SPELL + +/datum/ai_planning_subtree/basic_melee_attack_subtree/carp + melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/carp + +/datum/ai_behavior/basic_melee_attack/carp + action_cooldown = 1.5 SECONDS + +/datum/ai_planning_subtree/attack_obstacle_in_path/carp + attack_behaviour = /datum/ai_behavior/attack_obstructions/carp + +/datum/ai_behavior/attack_obstructions/carp + action_cooldown = 1.5 SECONDS + +/// As basic attack tree but interrupt if your health gets low or if your spell is off cooldown +/datum/ai_planning_subtree/basic_melee_attack_subtree/magicarp + melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/carp/magic + +/// Interrupt your attack chain if: you have a spell, it's not on cooldown, and it has a target +/datum/ai_behavior/basic_melee_attack/carp/magic + +/datum/ai_behavior/basic_melee_attack/carp/magic/perform(delta_time, datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key, health_ratio_key) + var/datum/action/cooldown/using_action = controller.blackboard[BB_MAGICARP_SPELL] + if (QDELETED(using_action)) + return ..() + if (!controller.blackboard[BB_MAGICARP_SPELL_SPECIAL_TARGETTING] && using_action.IsAvailable()) + finish_action(controller, succeeded = FALSE) + return + return ..() + +/** + * Find a target for the magicarp's spell + * This gets weird because different spells want different targetting + * but I didn't want a new ai controller for every different spell + */ +/datum/ai_planning_subtree/find_nearest_magicarp_spell_target + +/datum/ai_planning_subtree/find_nearest_magicarp_spell_target/SelectBehaviors(datum/ai_controller/controller, delta_time) + var/datum/action/cooldown/using_action = controller.blackboard[BB_MAGICARP_SPELL] + if (QDELETED(using_action)) + return + if (!using_action.IsAvailable()) + return + + var/spell_targetting = controller.blackboard[BB_MAGICARP_SPELL_SPECIAL_TARGETTING] + if (!spell_targetting) + controller.queue_behavior(/datum/ai_behavior/find_potential_targets/nearest/magicarp, BB_MAGICARP_SPELL_TARGET, BB_TARGETTING_DATUM, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) + return + + switch(spell_targetting) + if (MAGICARP_SPELL_CORPSES) + controller.queue_behavior(/datum/ai_behavior/find_and_set/friendly_corpses, BB_MAGICARP_SPELL_TARGET, MAGICARP_SPELL_TARGET_SEEK_RANGE) + return + if (MAGICARP_SPELL_OBJECTS) + controller.queue_behavior(/datum/ai_behavior/find_and_set/animatable, BB_MAGICARP_SPELL_TARGET, MAGICARP_SPELL_TARGET_SEEK_RANGE) + return + if (MAGICARP_SPELL_WALLS) + controller.queue_behavior(/datum/ai_behavior/find_and_set/nearest_wall, BB_MAGICARP_SPELL_TARGET, MAGICARP_SPELL_TARGET_SEEK_RANGE) + return + +/// This subtype only exists because if you queue multiple of the same action with different arguments it deletes their stored arguments +/datum/ai_behavior/find_potential_targets/nearest/magicarp + +/// Then use it on that target +/datum/ai_planning_subtree/targetted_mob_ability/magicarp + ability_key = BB_MAGICARP_SPELL + target_key = BB_MAGICARP_SPELL_TARGET + use_ability_behaviour = /datum/ai_behavior/try_mob_ability/and_clear_target diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_controllers.dm b/code/modules/mob/living/basic/space_fauna/carp/carp_controllers.dm new file mode 100644 index 00000000000..4cb0ea4f476 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_controllers.dm @@ -0,0 +1,62 @@ +/** + * AI controller for carp + * Expected flow is: + * * If health is low, mark that we want to run away. + * * If we want to run away, find nearest target and run out of view of it. + * * Look for anything we want to eat in the area and target it. + * * If we don't have a target already, find something to attack. + * * Go and attack our target (which might be food, or might be a mob). + */ +/datum/ai_controller/basic_controller/carp + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items(), + BB_PET_TARGETTING_DATUM = new /datum/targetting_datum/not_friends() + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/pet_planning, + /datum/ai_planning_subtree/simple_find_nearest_target_to_flee, + /datum/ai_planning_subtree/flee_target, + /datum/ai_planning_subtree/find_food, + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/attack_obstacle_in_path/carp, + /datum/ai_planning_subtree/basic_melee_attack_subtree/carp, + ) + +/** + * Carp which bites back, but doesn't look for targets. + * 'Not hunting targets' includes food (and can rings), because they have been well trained. + */ +/datum/ai_controller/basic_controller/carp/pet + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/ignore_faction(), + BB_PET_TARGETTING_DATUM = new /datum/targetting_datum/not_friends() + ) + ai_traits = STOP_MOVING_WHEN_PULLED + planning_subtrees = list( + /datum/ai_planning_subtree/pet_planning, + /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee, + /datum/ai_planning_subtree/flee_target, + /datum/ai_planning_subtree/target_retaliate, + /datum/ai_planning_subtree/attack_obstacle_in_path/carp, + /datum/ai_planning_subtree/basic_melee_attack_subtree/carp, + ) + +/** + * AI for carp with a spell. + * Flow is basically the same as regular carp, except it will try and cast a spell at its target whenever possible and not fleeing. + */ +/datum/ai_controller/basic_controller/carp/ranged + planning_subtrees = list( + /datum/ai_planning_subtree/pet_planning, + /datum/ai_planning_subtree/simple_find_nearest_target_to_flee, + /datum/ai_planning_subtree/flee_target, + /datum/ai_planning_subtree/find_food, + /datum/ai_planning_subtree/find_nearest_magicarp_spell_target, + /datum/ai_planning_subtree/targetted_mob_ability/magicarp, + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/attack_obstacle_in_path/carp, + /datum/ai_planning_subtree/basic_melee_attack_subtree/magicarp, + ) diff --git a/code/modules/mob/living/basic/space_fauna/carp/magicarp.dm b/code/modules/mob/living/basic/space_fauna/carp/magicarp.dm new file mode 100644 index 00000000000..57c13488679 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/carp/magicarp.dm @@ -0,0 +1,136 @@ +/// A list of spells magicarp can use and a corresponding name prefix to apply +GLOBAL_LIST_INIT(magicarp_spell_types, list( + /obj/projectile/magic/animate = "dancing", + /obj/projectile/magic/arcane_barrage = "arcane", + /obj/projectile/magic/change = "transforming", + /obj/projectile/magic/death = "grim", + /obj/projectile/magic/door = "unbarred", + /obj/projectile/magic/fireball = "blazing", + /obj/projectile/magic/resurrection = "vital", + /obj/projectile/magic/spellblade = "vorpal", + /obj/projectile/magic/teleport = "warping", + /obj/projectile/magic/babel = "babbling", +)) + +/// A reduced list of spells for magicarp spawned in xenobiology, less disruptive +GLOBAL_LIST_INIT(xenobiology_magicarp_spell_types, list( + /obj/projectile/magic/animate = "dancing", + /obj/projectile/magic/teleport = "warping", + /obj/projectile/magic/door = "unbarred", + /obj/projectile/magic/fireball = "blazing", + /obj/projectile/magic/spellblade = "vorpal", + /obj/projectile/magic/arcane_barrage = "arcane", +)) + +/// Associative list of magicarp spells to colours, expand this list if you expand the other lists +GLOBAL_LIST_INIT(magicarp_spell_colours, list( + /obj/projectile/magic/animate = COLOR_CARP_RUSTY, + /obj/projectile/magic/arcane_barrage = COLOR_CARP_PURPLE, + /obj/projectile/magic/change = COLOR_CARP_PINK, + /obj/projectile/magic/death = COLOR_CARP_DARK_BLUE, + /obj/projectile/magic/door = COLOR_CARP_GREEN, + /obj/projectile/magic/fireball = COLOR_CARP_RED, + /obj/projectile/magic/resurrection = COLOR_CARP_PALE_GREEN, + /obj/projectile/magic/spellblade = COLOR_CARP_SILVER, + /obj/projectile/magic/teleport = COLOR_CARP_GRAPE, + /obj/projectile/magic/babel = COLOR_CARP_BROWN, +)) + +/** + * # Magicarp + * + * Carp who can cast spells! + * Mostly created via wizard event or transformation. + * Come in 'does one thing' and 'does random things' varieties. + */ +/mob/living/basic/carp/magic + name = "magicarp" + desc = "50% magic, 50% carp, 100% horrible." + icon_gib = "magicarp_gib" + maxHealth = 50 + health = 50 + gold_core_spawnable = NO_SPAWN + greyscale_config = /datum/greyscale_config/carp_magic + ai_controller = /datum/ai_controller/basic_controller/carp/ranged + tamed_commands = list( + /datum/pet_command/idle, + /datum/pet_command/free, + /datum/pet_command/follow, + /datum/pet_command/point_targetting/attack/carp, + /datum/pet_command/point_targetting/use_ability/magicarp, + ) + /// List of all projectiles we can fire. + /// Non-static, because subtypes can have their own lists. + var/list/allowed_projectile_types + /// Our magic attack + var/datum/action/cooldown/mob_cooldown/projectile_attack/magicarp_bolt/spell + +/mob/living/basic/carp/magic/Initialize(mapload) + . = ..() + allowed_projectile_types = spell_list() + assign_spell() + +/// Returns the list of spells we are allowed to cast +/mob/living/basic/carp/magic/proc/spell_list() + return GLOB.magicarp_spell_types + +/// Updates name based on chosen spell +/mob/living/basic/carp/magic/proc/assign_spell() + var/obj/projectile/spell_type = pick(allowed_projectile_types) + name = "[GLOB.magicarp_spell_types[spell_type]] [name]" + set_greyscale(colors = list(GLOB.magicarp_spell_colours[spell_type])) + + spell = new (src) + spell.projectile_type = spell_type + spell.button_icon_state = initial(spell_type.icon_state) + spell.Grant(src) + ai_controller.blackboard[BB_MAGICARP_SPELL] = spell + assign_spell_ai(spell_type) + +/// If you have certain spells, use a different targetting datum +/mob/living/basic/carp/magic/proc/assign_spell_ai(spell_type) + var/static/list/spell_special_targetting = list( + /obj/projectile/magic/animate = MAGICARP_SPELL_OBJECTS, + /obj/projectile/magic/door = MAGICARP_SPELL_WALLS, + /obj/projectile/magic/resurrection = MAGICARP_SPELL_CORPSES, + ) + + ai_controller.blackboard[BB_MAGICARP_SPELL_SPECIAL_TARGETTING] = spell_special_targetting[spell_type] + +/// Shoot when you click away from you +/mob/living/basic/carp/magic/RangedAttack(atom/atom_target, modifiers) + spell.Trigger(target = atom_target) + +/*** + * # Chaos Magicarp + * + * Fires a random spell (and changes colour) every time, also beefier. + * Sometimes actually more durable than the much larger megacarp. That's magic for you. + * They trade off for this with a tendency to fireball themselves. + */ +/mob/living/basic/carp/magic/chaos + name = "chaos magicarp" + desc = "50% carp, 100% magic, 150% horrible." + maxHealth = 75 + health = 75 + +/mob/living/basic/carp/magic/chaos/assign_spell() + var/datum/action/cooldown/mob_cooldown/projectile_attack/magicarp_bolt/chaos/chaos_bolt = new(src) + chaos_bolt.permitted_projectiles = allowed_projectile_types + chaos_bolt.Grant(src) + spell = chaos_bolt + ai_controller.blackboard[BB_MAGICARP_SPELL] = spell + RegisterSignal(spell, COMSIG_ACTION_TRIGGER, PROC_REF(apply_colour)) + +/// Has a more limited spell pool but can appear from gold slime cores +/mob/living/basic/carp/magic/xenobiology + gold_core_spawnable = HOSTILE_SPAWN + +/mob/living/basic/carp/magic/xenobiology/spell_list() + return GLOB.xenobiology_magicarp_spell_types + +/mob/living/basic/carp/magic/chaos/xenobiology + gold_core_spawnable = HOSTILE_SPAWN + +/mob/living/basic/carp/magic/chaos/xenobiology/spell_list() + return GLOB.xenobiology_magicarp_spell_types diff --git a/code/modules/mob/living/basic/space_fauna/carp/megacarp.dm b/code/modules/mob/living/basic/space_fauna/carp/megacarp.dm new file mode 100644 index 00000000000..deadeaaa540 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/carp/megacarp.dm @@ -0,0 +1,34 @@ +/** + * ## Mega Space Carp + * + * A bigger space carp with more health and greater ability to smash objects. + * Brings in some buddies when it starts fleeing as a distraction. + * Has mildly randomised stats for some inexplicable reason, makes it somewhat more like a randomised Diablo mob. + */ +/mob/living/basic/carp/mega + icon = 'icons/mob/simple/broadMobs.dmi' + name = "Mega Space Carp" + desc = "A ferocious, fang bearing creature that resembles a shark. This one seems especially ticked off." + icon_state = "megacarp_greyscale" + icon_living = "megacarp_greyscale" + icon_dead = "megacarp_dead_greyscale" + icon_gib = "megacarp_gib" + health_doll_icon = "megacarp" + maxHealth = 20 + health = 20 + pixel_x = -16 + base_pixel_x = -16 + mob_size = MOB_SIZE_LARGE + obj_damage = 80 + cell_line = CELL_LINE_TABLE_MEGACARP + ridable_data = /datum/component/riding/creature/megacarp + greyscale_config = /datum/greyscale_config/carp_mega + butcher_results = list(/obj/item/food/fishmeat/carp = 2, /obj/item/stack/sheet/animalhide/carp = 3) + +/mob/living/basic/carp/mega/Initialize(mapload) + . = ..() + name = "[pick(GLOB.megacarp_first_names)] [pick(GLOB.megacarp_last_names)]" + melee_damage_lower += rand(2, 10) + melee_damage_upper += rand(10,20) + maxHealth += rand(30,60) + health = maxHealth diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index b3bc3ab148f..c063af96390 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -1344,7 +1344,7 @@ if(WABBAJACK_ANIMAL) var/picked_animal = pick( - /mob/living/simple_animal/hostile/carp, + /mob/living/basic/carp, /mob/living/simple_animal/hostile/bear, /mob/living/simple_animal/hostile/mushroom, /mob/living/simple_animal/hostile/netherworld/statue, @@ -1354,8 +1354,8 @@ /mob/living/simple_animal/hostile/giant_spider, /mob/living/simple_animal/hostile/giant_spider/hunter, /mob/living/simple_animal/hostile/blob/blobbernaut/independent, - /mob/living/simple_animal/hostile/carp/ranged, - /mob/living/simple_animal/hostile/carp/ranged/chaos, + /mob/living/basic/carp/magic, + /mob/living/basic/carp/magic/chaos, /mob/living/simple_animal/hostile/asteroid/basilisk/watcher, /mob/living/simple_animal/hostile/asteroid/goliath/beast, /mob/living/simple_animal/hostile/headcrab, diff --git a/code/modules/mob/living/simple_animal/hostile/carp.dm b/code/modules/mob/living/simple_animal/hostile/carp.dm deleted file mode 100644 index c59903c31fd..00000000000 --- a/code/modules/mob/living/simple_animal/hostile/carp.dm +++ /dev/null @@ -1,257 +0,0 @@ -/mob/living/simple_animal/hostile/carp - name = "space carp" - desc = "A ferocious, fang-bearing creature that resembles a fish." - icon = 'icons/mob/simple/carp.dmi' - icon_state = "base" - icon_living = "base" - icon_dead = "base_dead" - icon_gib = "carp_gib" - mob_biotypes = MOB_ORGANIC|MOB_BEAST - movement_type = FLYING - ai_controller = /datum/ai_controller/hostile_friend - speak_chance = 0 - turns_per_move = 5 - butcher_results = list(/obj/item/food/fishmeat/carp = 2, /obj/item/stack/sheet/animalhide/carp = 1) - response_help_continuous = "pets" - response_help_simple = "pet" - response_disarm_continuous = "gently pushes aside" - response_disarm_simple = "gently push aside" - emote_taunt = list("gnashes") - taunt_chance = 30 - speed = 0 - maxHealth = 25 - health = 25 - search_objects = 1 - wanted_objects = list(/obj/item/storage/cans) - harm_intent_damage = 8 - obj_damage = 50 - melee_damage_lower = 20 - melee_damage_upper = 20 - attack_verb_continuous = "bites" - attack_verb_simple = "bite" - attack_sound = 'sound/weapons/bite.ogg' - attack_vis_effect = ATTACK_EFFECT_BITE - speak_emote = list("gnashes") - //Space carp aren't affected by cold. - atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - minbodytemp = 0 - maxbodytemp = 1500 - faction = list("carp") - pressure_resistance = 200 - gold_core_spawnable = HOSTILE_SPAWN - greyscale_config = /datum/greyscale_config/carp - /// Weighted list of usual carp colors - var/static/list/carp_colors = list( - COLOR_CARP_PURPLE = 7, - COLOR_CARP_PINK = 7, - COLOR_CARP_GREEN = 7, - COLOR_CARP_GRAPE = 7, - COLOR_CARP_SWAMP = 7, - COLOR_CARP_TURQUOISE = 7, - COLOR_CARP_BROWN = 7, - COLOR_CARP_TEAL = 7, - COLOR_CARP_LIGHT_BLUE = 7, - COLOR_CARP_RUSTY = 7, - COLOR_CARP_RED = 7, - COLOR_CARP_YELLOW = 7, - COLOR_CARP_BLUE = 7, - COLOR_CARP_PALE_GREEN = 7, - COLOR_CARP_SILVER = 1, // The rare silver carp - ) - /// Is the carp tamed? - var/tamed = FALSE - /// What colour is our 'healing' outline? - var/regenerate_colour = COLOR_PALE_GREEN - -/mob/living/simple_animal/hostile/carp/Initialize(mapload, mob/tamer) - AddElement(/datum/element/simple_flying) - apply_colour() - . = ..() - ADD_TRAIT(src, TRAIT_HEALS_FROM_CARP_RIFTS, INNATE_TRAIT) - ADD_TRAIT(src, TRAIT_SPACEWALK, INNATE_TRAIT) - AddComponent(/datum/component/regenerator, outline_colour = regenerate_colour) - add_cell_sample() - if(ai_controller) - ai_controller.blackboard[BB_HOSTILE_ATTACK_WORD] = pick(speak_emote) - if(tamer) - tamed(tamer) - else - make_tameable() - -/// Set a random colour on the carp, override to do something else -/mob/living/simple_animal/hostile/carp/proc/apply_colour() - if (!greyscale_config) - return - set_greyscale(colors = list(pick_weight(carp_colors))) - -/mob/living/simple_animal/hostile/carp/revive(full_heal_flags = NONE, excess_healing = 0, force_grab_ghost = FALSE) - . = ..() - if(!. || !tamed) - return - - var/datum/weakref/friendref = ai_controller.blackboard[BB_HOSTILE_FRIEND] - var/mob/living/friend = friendref?.resolve() - if(friend) - tamed(friend) - - update_icon() - -/mob/living/simple_animal/hostile/carp/death(gibbed) - if (tamed) - can_buckle = FALSE - return ..() - -/mob/living/simple_animal/hostile/carp/proc/make_tameable() - AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/meat), tame_chance = 10, bonus_tame_chance = 5, after_tame = CALLBACK(src, PROC_REF(tamed))) - -/mob/living/simple_animal/hostile/carp/proc/tamed(mob/living/tamer) - tamed = TRUE - buckle_lying = 0 - AddElement(/datum/element/ridable, /datum/component/riding/creature/carp) - if(ai_controller) - var/datum/ai_controller/hostile_friend/ai_current_controller = ai_controller - ai_current_controller.befriend(tamer) - can_have_ai = FALSE - toggle_ai(AI_OFF) - -/mob/living/simple_animal/hostile/carp/add_cell_sample() - AddElement(/datum/element/swabable, CELL_LINE_TABLE_CARP, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) - -/mob/living/simple_animal/hostile/carp/proc/chomp_plastic() - var/obj/item/storage/cans/tasty_plastic = locate(/obj/item/storage/cans) in view(1, src) - if(tasty_plastic && Adjacent(tasty_plastic)) - visible_message(span_notice("[src] gets its head stuck in [tasty_plastic], and gets cut breaking free from it!"), span_notice("You try to avoid [tasty_plastic], but it looks so... delicious... Ow! It cuts the inside of your mouth!")) - - new /obj/effect/decal/cleanable/plastic(loc) - - adjustBruteLoss(5) - qdel(tasty_plastic) - -/mob/living/simple_animal/hostile/carp/Life(delta_time = SSMOBS_DT, times_fired) - . = ..() - if(stat == CONSCIOUS) - chomp_plastic() - -/mob/living/simple_animal/hostile/carp/holocarp - icon_state = "holocarp" - icon_living = "holocarp" - maxbodytemp = INFINITY - ai_controller = null - gold_core_spawnable = NO_SPAWN - del_on_death = 1 - greyscale_config = null - regenerate_colour = COLOR_WHITE - -/mob/living/simple_animal/hostile/carp/holocarp/add_cell_sample() - return - -/mob/living/simple_animal/hostile/carp/megacarp - icon = 'icons/mob/simple/broadMobs.dmi' - name = "Mega Space Carp" - desc = "A ferocious, fang bearing creature that resembles a shark. This one seems especially ticked off." - icon_state = "megacarp_greyscale" - icon_living = "megacarp_greyscale" - icon_dead = "megacarp_dead_greyscale" - icon_gib = "megacarp_gib" - health_doll_icon = "megacarp" - ai_controller = null - maxHealth = 20 - health = 20 - pixel_x = -16 - base_pixel_x = -16 - mob_size = MOB_SIZE_LARGE - greyscale_config = /datum/greyscale_config/carp_mega - - obj_damage = 80 - melee_damage_lower = 20 - melee_damage_upper = 20 - butcher_results = list(/obj/item/food/fishmeat/carp = 2, /obj/item/stack/sheet/animalhide/carp = 3) - -/mob/living/simple_animal/hostile/carp/megacarp/Initialize(mapload) - . = ..() - name = "[pick(GLOB.megacarp_first_names)] [pick(GLOB.megacarp_last_names)]" - melee_damage_lower += rand(2, 10) - melee_damage_upper += rand(10,20) - maxHealth += rand(30,60) - move_to_delay = rand(3,7) - -/mob/living/simple_animal/hostile/carp/megacarp/add_cell_sample() - AddElement(/datum/element/swabable, CELL_LINE_TABLE_MEGACARP, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) - -/mob/living/simple_animal/hostile/carp/megacarp/Login() - . = ..() - if(!. || !client) - return FALSE - - AddElement(/datum/element/ridable, /datum/component/riding/creature/megacarp) - can_buckle = TRUE - buckle_lying = 0 - -/mob/living/simple_animal/hostile/carp/lia - name = "Lia" - real_name = "Lia" - desc = "A failed experiment of Nanotrasen to create weaponised carp technology. This less than intimidating carp now serves as the Head of Security's pet." - gender = FEMALE - speak_emote = list("squeaks") - ai_controller = null - gold_core_spawnable = NO_SPAWN - faction = list(FACTION_NEUTRAL) - health = 200 - icon_dead = "magicarp_dead" - icon_gib = "magicarp_gib" - icon_living = "magicarp" - icon_state = "magicarp" - maxHealth = 200 - greyscale_config = null - -/mob/living/simple_animal/hostile/carp/lia/Initialize(mapload) - . = ..() - AddElement(/datum/element/pet_bonus, "bloops happily!") - -/// Boosted chance for Cayenne to be silver -#define RARE_CAYENNE_CHANCE 10 - -/mob/living/simple_animal/hostile/carp/cayenne - name = "Cayenne" - real_name = "Cayenne" - desc = "A failed Syndicate experiment in weaponized space carp technology, it now serves as a lovable mascot." - gender = FEMALE - speak_emote = list("squeaks") - ai_controller = null - gold_core_spawnable = NO_SPAWN - faction = list(ROLE_SYNDICATE) - /// Overlay to apply to display the disk - var/mutable_appearance/disk_overlay - /// Overlay to apply over the disk so it looks like cayenne is holding it - var/mutable_appearance/mouth_overlay - -/mob/living/simple_animal/hostile/carp/cayenne/Initialize(mapload) - . = ..() - AddElement(/datum/element/pet_bonus, "bloops happily!") - var/datum/callback/got_disk = CALLBACK(src, PROC_REF(got_disk)) - var/datum/callback/display_disk = CALLBACK(src, PROC_REF(display_disk)) - AddComponent(/datum/component/nuclear_bomb_operator, got_disk, display_disk) - -/mob/living/simple_animal/hostile/carp/cayenne/apply_colour() - if (prob(RARE_CAYENNE_CHANCE)) - set_greyscale(colors = list(COLOR_CARP_SILVER)) - else - return ..() - -/// She did it! Treats for Cayenne! -/mob/living/simple_animal/hostile/carp/cayenne/proc/got_disk(obj/item/disk/nuclear/disky) - if (disky.fake) // Never mind she didn't do it - return - client.give_award(/datum/award/achievement/misc/cayenne_disk, src) - -/// Adds an overlay to show the disk on Cayenne -/mob/living/simple_animal/hostile/carp/cayenne/proc/display_disk(list/new_overlays) - if (!mouth_overlay) - mouth_overlay = mutable_appearance(SSgreyscale.GetColoredIconByType(/datum/greyscale_config/carp/disk_mouth, greyscale_colors), "disk_mouth") - new_overlays += mouth_overlay - - if (!disk_overlay) - disk_overlay = mutable_appearance('icons/mob/simple/carp.dmi', "disk_overlay") - new_overlays += disk_overlay - -#undef RARE_CAYENNE_CHANCE diff --git a/code/modules/mob/living/simple_animal/hostile/carp_magic.dm b/code/modules/mob/living/simple_animal/hostile/carp_magic.dm deleted file mode 100644 index bab4d58d16b..00000000000 --- a/code/modules/mob/living/simple_animal/hostile/carp_magic.dm +++ /dev/null @@ -1,97 +0,0 @@ -/// A list of spells magicarp can use and a corresponding name prefix to apply -GLOBAL_LIST_INIT(magicarp_spell_types, list( - /obj/projectile/magic/animate = "dancing", - /obj/projectile/magic/arcane_barrage = "arcane", - /obj/projectile/magic/change = "transforming", - /obj/projectile/magic/death = "grim", - /obj/projectile/magic/door = "unbarred", - /obj/projectile/magic/fireball = "blazing", - /obj/projectile/magic/resurrection = "vital", - /obj/projectile/magic/spellblade = "vorpal", - /obj/projectile/magic/teleport = "warping", - /obj/projectile/magic/babel = "babbling", -)) - -/// A reduced list of spells for magicarp spawned in xenobiology, less disruptive -GLOBAL_LIST_INIT(xenobiology_magicarp_spell_types, list( - /obj/projectile/magic/animate = "dancing", - /obj/projectile/magic/teleport = "warping", - /obj/projectile/magic/door = "unbarred", - /obj/projectile/magic/fireball = "blazing", - /obj/projectile/magic/spellblade = "vorpal", - /obj/projectile/magic/arcane_barrage = "arcane", -)) - -/// Associative list of magicarp spells to colours, expand this list if you expand the other lists -GLOBAL_LIST_INIT(magicarp_spell_colours, list( - /obj/projectile/magic/animate = COLOR_CARP_RUSTY, - /obj/projectile/magic/arcane_barrage = COLOR_CARP_PURPLE, - /obj/projectile/magic/change = COLOR_CARP_PINK, - /obj/projectile/magic/death = COLOR_CARP_DARK_BLUE, - /obj/projectile/magic/door = COLOR_CARP_GREEN, - /obj/projectile/magic/fireball = COLOR_CARP_RED, - /obj/projectile/magic/resurrection = COLOR_CARP_PALE_GREEN, - /obj/projectile/magic/spellblade = COLOR_CARP_SILVER, - /obj/projectile/magic/teleport = COLOR_CARP_GRAPE, - /obj/projectile/magic/babel = COLOR_CARP_BROWN, -)) - -/mob/living/simple_animal/hostile/carp/ranged - name = "magicarp" - desc = "50% magic, 50% carp, 100% horrible." - ranged = 1 - retreat_distance = 2 - minimum_distance = 0 //Between shots they can and will close in to nash - projectiletype = /obj/projectile/magic - projectilesound = 'sound/weapons/emitter.ogg' - maxHealth = 50 - health = 50 - gold_core_spawnable = NO_SPAWN - greyscale_config = /datum/greyscale_config/carp_magic - /// List of all projectiles we can fire. - /// Non-static, because subtypes can have their own lists. - var/list/allowed_projectile_types - -/mob/living/simple_animal/hostile/carp/ranged/Initialize(mapload) - . = ..() - allowed_projectile_types = spell_list() - assign_spell() - -/// Returns the list of spells we are allowed to cast -/mob/living/simple_animal/hostile/carp/ranged/proc/spell_list() - return GLOB.magicarp_spell_types - -/// Pick a random spell then update name and colour based on which one we picked -/mob/living/simple_animal/hostile/carp/ranged/proc/assign_spell() - projectiletype = pick(allowed_projectile_types) - name = "[GLOB.magicarp_spell_types[projectiletype]] [name]" - set_greyscale(colors = list(GLOB.magicarp_spell_colours[projectiletype])) - - -/mob/living/simple_animal/hostile/carp/ranged/chaos - name = "chaos magicarp" - desc = "50% carp, 100% magic, 150% horrible." - color = "#00FFFF" - maxHealth = 75 - health = 75 - gold_core_spawnable = NO_SPAWN - -/mob/living/simple_animal/hostile/carp/ranged/chaos/assign_spell() - return - -/mob/living/simple_animal/hostile/carp/ranged/chaos/Shoot() - projectiletype = pick(allowed_projectile_types) - apply_colour() - return ..() - -/mob/living/simple_animal/hostile/carp/ranged/xenobiology // these are for the xenobio gold slime pool - gold_core_spawnable = HOSTILE_SPAWN - -/mob/living/simple_animal/hostile/carp/ranged/xenobiology/spell_list() - return GLOB.xenobiology_magicarp_spell_types - -/mob/living/simple_animal/hostile/carp/ranged/chaos/xenobiology - gold_core_spawnable = HOSTILE_SPAWN - -/mob/living/simple_animal/hostile/carp/ranged/chaos/xenobiology/spell_list() - return GLOB.xenobiology_magicarp_spell_types diff --git a/code/modules/mob/living/simple_animal/hostile/mimic.dm b/code/modules/mob/living/simple_animal/hostile/mimic.dm index 5693a2c305e..53e53b87a3f 100644 --- a/code/modules/mob/living/simple_animal/hostile/mimic.dm +++ b/code/modules/mob/living/simple_animal/hostile/mimic.dm @@ -99,7 +99,7 @@ ..() /// Mimics can't be made out of these objects -GLOBAL_LIST_INIT(mimic_blacklist, list(/obj/structure/table, /obj/structure/cable, /obj/structure/window, /obj/structure/blob)) +GLOBAL_LIST_INIT(animatable_blacklist, list(/obj/structure/table, /obj/structure/cable, /obj/structure/window, /obj/structure/blob)) /mob/living/simple_animal/hostile/mimic/copy health = 100 @@ -143,7 +143,7 @@ GLOBAL_LIST_INIT(mimic_blacklist, list(/obj/structure/table, /obj/structure/cabl faction |= "[REF(owner)]" /mob/living/simple_animal/hostile/mimic/copy/proc/CheckObject(obj/O) - if((isitem(O) || isstructure(O)) && !is_type_in_list(O, GLOB.mimic_blacklist)) + if((isitem(O) || isstructure(O)) && !is_type_in_list(O, GLOB.animatable_blacklist)) return TRUE return FALSE diff --git a/code/modules/mob/transform_procs.dm b/code/modules/mob/transform_procs.dm index b8102bb60ce..e7a4fc3027e 100644 --- a/code/modules/mob/transform_procs.dm +++ b/code/modules/mob/transform_procs.dm @@ -300,7 +300,7 @@ /mob/living/carbon/human/Animalize() - var/list/mobtypes = typesof(/mob/living/simple_animal) + var/list/mobtypes = typesof(/mob/living/simple_animal) + typesof(/mob/living/basic) var/mobpath = tgui_input_list(usr, "Which type of mob should [src] turn into?", "Choose a type", sort_list(mobtypes, GLOBAL_PROC_REF(cmp_typepaths_asc))) if(isnull(mobpath)) return @@ -334,7 +334,7 @@ /mob/proc/Animalize() - var/list/mobtypes = typesof(/mob/living/simple_animal) + var/list/mobtypes = typesof(/mob/living/simple_animal) + typesof(/mob/living/basic) var/mobpath = tgui_input_list(usr, "Which type of mob should [src] turn into?", "Choose a type", sort_list(mobtypes, GLOBAL_PROC_REF(cmp_typepaths_asc))) if(isnull(mobpath)) return @@ -372,7 +372,7 @@ return TRUE if(ispath(MP, /mob/living/simple_animal/crab)) return TRUE - if(ispath(MP, /mob/living/simple_animal/hostile/carp)) + if(ispath(MP, /mob/living/basic/carp)) return TRUE if(ispath(MP, /mob/living/simple_animal/hostile/mushroom)) return TRUE diff --git a/code/modules/projectiles/projectile/magic.dm b/code/modules/projectiles/projectile/magic.dm index a2a5cfa5910..b1b826c11b5 100644 --- a/code/modules/projectiles/projectile/magic.dm +++ b/code/modules/projectiles/projectile/magic.dm @@ -193,7 +193,7 @@ target.animate_atom_living(firer) /atom/proc/animate_atom_living(mob/living/owner = null) - if((isitem(src) || isstructure(src)) && !is_type_in_list(src, GLOB.mimic_blacklist)) + if((isitem(src) || isstructure(src)) && !is_type_in_list(src, GLOB.animatable_blacklist)) if(istype(src, /obj/structure/statue/petrified)) var/obj/structure/statue/petrified/P = src if(P.petrified_mob) diff --git a/code/modules/research/experimentor.dm b/code/modules/research/experimentor.dm index 162b3b42f73..d5dd752ea0e 100644 --- a/code/modules/research/experimentor.dm +++ b/code/modules/research/experimentor.dm @@ -650,7 +650,7 @@ /mob/living/basic/mouse, /mob/living/simple_animal/hostile/bear, /mob/living/simple_animal/hostile/bee, - /mob/living/simple_animal/hostile/carp, + /mob/living/basic/carp, ) for(var/counter in 1 to rand(1, 25)) var/mobType = pick(valid_animals) diff --git a/code/modules/research/xenobiology/vatgrowing/samples/cell_lines/common.dm b/code/modules/research/xenobiology/vatgrowing/samples/cell_lines/common.dm index 0b3cf60d648..19d1943526e 100644 --- a/code/modules/research/xenobiology/vatgrowing/samples/cell_lines/common.dm +++ b/code/modules/research/xenobiology/vatgrowing/samples/cell_lines/common.dm @@ -187,7 +187,7 @@ /datum/reagent/oxygen = -3) virus_suspectibility = 2 - resulting_atoms = list(/mob/living/simple_animal/hostile/carp = 1) + resulting_atoms = list(/mob/living/basic/carp = 1) /datum/micro_organism/cell_line/megacarp desc = "Cartilaginous cyprinid cells" @@ -208,7 +208,7 @@ /datum/reagent/oxygen = -3) virus_suspectibility = 1 - resulting_atoms = list(/mob/living/simple_animal/hostile/carp/megacarp = 1) + resulting_atoms = list(/mob/living/basic/carp/mega = 1) /datum/micro_organism/cell_line/snake desc = "Ophidic cells" diff --git a/code/modules/spells/spell_types/conjure/carp.dm b/code/modules/spells/spell_types/conjure/carp.dm index 45007ee8503..5411030e75c 100644 --- a/code/modules/spells/spell_types/conjure/carp.dm +++ b/code/modules/spells/spell_types/conjure/carp.dm @@ -10,4 +10,4 @@ invocation_type = INVOCATION_SHOUT summon_radius = 1 - summon_type = list(/mob/living/simple_animal/hostile/carp) + summon_type = list(/mob/living/basic/carp) diff --git a/code/modules/spells/spell_types/shapeshift/shapechange.dm b/code/modules/spells/spell_types/shapeshift/shapechange.dm index d7b2f36da64..3079a7d5b86 100644 --- a/code/modules/spells/spell_types/shapeshift/shapechange.dm +++ b/code/modules/spells/spell_types/shapeshift/shapechange.dm @@ -13,7 +13,7 @@ possible_shapes = list( /mob/living/basic/mouse, /mob/living/basic/pet/dog/corgi, - /mob/living/simple_animal/hostile/carp/ranged/chaos, + /mob/living/basic/carp/magic/chaos, /mob/living/simple_animal/bot/secbot/ed209, /mob/living/simple_animal/hostile/giant_spider/viper/wizard, /mob/living/simple_animal/hostile/construct/juggernaut/mystic, diff --git a/code/modules/unit_tests/simple_animal_freeze.dm b/code/modules/unit_tests/simple_animal_freeze.dm index 8d60e1df2b0..85313847053 100644 --- a/code/modules/unit_tests/simple_animal_freeze.dm +++ b/code/modules/unit_tests/simple_animal_freeze.dm @@ -127,15 +127,6 @@ /mob/living/simple_animal/hostile/boss, /mob/living/simple_animal/hostile/boss/paper_wizard, /mob/living/simple_animal/hostile/boss/paper_wizard/copy, - /mob/living/simple_animal/hostile/carp, - /mob/living/simple_animal/hostile/carp/cayenne, - /mob/living/simple_animal/hostile/carp/holocarp, - /mob/living/simple_animal/hostile/carp/lia, - /mob/living/simple_animal/hostile/carp/megacarp, - /mob/living/simple_animal/hostile/carp/ranged, - /mob/living/simple_animal/hostile/carp/ranged/chaos, - /mob/living/simple_animal/hostile/carp/ranged/chaos/xenobiology, - /mob/living/simple_animal/hostile/carp/ranged/xenobiology, /mob/living/simple_animal/hostile/cat_butcherer, /mob/living/simple_animal/hostile/construct, /mob/living/simple_animal/hostile/construct/artificer, diff --git a/tgstation.dme b/tgstation.dme index 165837286b9..b96765fc646 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -33,6 +33,7 @@ #include "code\__DEFINES\admin.dm" #include "code\__DEFINES\adventure.dm" #include "code\__DEFINES\ai.dm" +#include "code\__DEFINES\ai_carp.dm" #include "code\__DEFINES\ai_pet_commands.dm" #include "code\__DEFINES\alarm.dm" #include "code\__DEFINES\alerts.dm" @@ -734,6 +735,7 @@ #include "code\datums\ai\basic_mobs\basic_subtrees\simple_find_target.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\speech_subtree.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\target_retaliate.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\targetted_mob_ability.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\tipped_subtree.dm" #include "code\datums\ai\basic_mobs\pet_commands\pet_command_planning.dm" #include "code\datums\ai\basic_mobs\pet_commands\pet_follow_friend.dm" @@ -3643,6 +3645,12 @@ #include "code\modules\mob\living\basic\pets\dog.dm" #include "code\modules\mob\living\basic\pets\pet.dm" #include "code\modules\mob\living\basic\ruin_defender\stickman.dm" +#include "code\modules\mob\living\basic\space_fauna\carp\carp.dm" +#include "code\modules\mob\living\basic\space_fauna\carp\carp_abilities.dm" +#include "code\modules\mob\living\basic\space_fauna\carp\carp_ai_actions.dm" +#include "code\modules\mob\living\basic\space_fauna\carp\carp_controllers.dm" +#include "code\modules\mob\living\basic\space_fauna\carp\magicarp.dm" +#include "code\modules\mob\living\basic\space_fauna\carp\megacarp.dm" #include "code\modules\mob\living\basic\syndicate\syndicate.dm" #include "code\modules\mob\living\basic\syndicate\syndicate_ai.dm" #include "code\modules\mob\living\basic\vermin\axolotl.dm" @@ -3851,8 +3859,6 @@ #include "code\modules\mob\living\simple_animal\hostile\ant.dm" #include "code\modules\mob\living\simple_animal\hostile\bear.dm" #include "code\modules\mob\living\simple_animal\hostile\bees.dm" -#include "code\modules\mob\living\simple_animal\hostile\carp.dm" -#include "code\modules\mob\living\simple_animal\hostile\carp_magic.dm" #include "code\modules\mob\living\simple_animal\hostile\cat_butcher.dm" #include "code\modules\mob\living\simple_animal\hostile\dark_wizard.dm" #include "code\modules\mob\living\simple_animal\hostile\eyeballs.dm" diff --git a/tools/UpdatePaths/Scripts/71421_simple_carp_to_basic.txt b/tools/UpdatePaths/Scripts/71421_simple_carp_to_basic.txt new file mode 100644 index 00000000000..9555176bc50 --- /dev/null +++ b/tools/UpdatePaths/Scripts/71421_simple_carp_to_basic.txt @@ -0,0 +1,8 @@ +# transforms simple carp to the new basic carp +# I'm not using {@subtypes} because the paths aren't exactly the same + +/mob/living/simple_animal/hostile/carp : /mob/living/basic/carp{@OLD} +/mob/living/simple_animal/hostile/carp/lia : /mob/living/basic/carp/pet/lia{@OLD} +/mob/living/simple_animal/hostile/carp/cayenne : /mob/living/basic/carp/pet/cayenne{@OLD} +/mob/living/simple_animal/hostile/carp/megacarp : /mob/living/basic/carp/mega{@OLD} +/mob/living/simple_animal/hostile/carp/ranged/@SUBTYPES : /mob/living/basic/carp/magic/@SUBTYPES{@OLD}