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}