From 5e2c8459dd04a7bf840beb76118706c26013bf5d Mon Sep 17 00:00:00 2001 From: Jacquerel Date: Fri, 6 Oct 2023 23:06:22 +0100 Subject: [PATCH] Basic Heretic Mobs: The Rest of Them (#78757) ## About The Pull Request Raw Prophet and Armsy had fun stuff going on and merited their own PRs, but the rest of these guys are basically just statblocks with abilities so I converted them all at once. Rust Walkers are present in a ruin and so have new AI which will actually use their abilities. They rust the area around where they spawn and throw their rust blast at people. I also gave Flesh Stalkers AI even though nobody has put them in a map because I thought it would be cool. This adds an AI behaviour where if they're not doing anything else they will turn into an animal and chill until someone's been around them for a bit, before attacking. They will also use EMP almost immediately upon performing their ambush, which kills the lights in that room. Spooky! To support this I needed to make some changes to let AI continue processing and targetting correctly while shapeshifted. I didn't give Maids or Ash Spirits AI because they'd be really boring. Other changes: I made the maid in the mirror flicker when it takes examine damage because the `visible_message` says it does but despite having the power, nobody made it actually flicker... ## Why It's Good For The Game No more simple mob heretic summons. ## Changelog :cl: refactor: Rust Walkers, Ash Spirits, Flesh Stalkers, and The Maid in the Mirror now use the basic mob framework. Please report any unusual behaviour. /:cl: --------- Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com> --- .../SpaceRuins/dangerous_research.dmm | 15 +-- code/__DEFINES/ai/ai.dm | 2 + code/__DEFINES/ai/ai_blackboard.dm | 3 + code/__DEFINES/footsteps.dm | 1 + code/_globalvars/phobias.dm | 2 - .../ai/basic_mobs/base_basic_controller.dm | 5 +- .../targeted_mob_ability.dm | 3 +- .../basic_ai_behaviors/targetting.dm | 3 +- .../basic_subtrees/shapechange_ambush.dm | 41 ++++++ .../basic_targetting_datum.dm | 6 +- code/datums/elements/footstep.dm | 2 + .../heretic/heretic_living_heart.dm | 2 +- .../heretic/knowledge/flesh_lore.dm | 2 +- .../heretic/knowledge/side_ash_flesh.dm | 2 +- .../heretic/knowledge/side_rust_cosmos.dm | 2 +- .../heretic/knowledge/side_void_blade.dm | 2 +- .../heretic/magic/ascended_shapeshift.dm | 6 +- .../heretic/structures/knock_final.dm | 2 +- .../mob/living/basic/heretic/ash_spirit.dm | 25 ++++ .../mob/living/basic/heretic/flesh_stalker.dm | 46 +++++++ .../basic/heretic/maid_in_the_mirror.dm} | 52 ++++---- .../mob/living/basic/heretic/rust_walker.dm | 87 +++++++++++++ .../simple_animal/hostile/heretic_monsters.dm | 120 ------------------ .../unit_tests/simple_animal_freeze.dm | 5 - tgstation.dme | 7 +- .../78757_simple_to_basic_heretic_summons.txt | 4 + 26 files changed, 266 insertions(+), 181 deletions(-) create mode 100644 code/datums/ai/basic_mobs/basic_subtrees/shapechange_ambush.dm create mode 100644 code/modules/mob/living/basic/heretic/ash_spirit.dm create mode 100644 code/modules/mob/living/basic/heretic/flesh_stalker.dm rename code/modules/{antagonists/heretic/mobs/maid_in_mirror.dm => mob/living/basic/heretic/maid_in_the_mirror.dm} (60%) create mode 100644 code/modules/mob/living/basic/heretic/rust_walker.dm delete mode 100644 code/modules/mob/living/simple_animal/hostile/heretic_monsters.dm create mode 100644 tools/UpdatePaths/Scripts/78757_simple_to_basic_heretic_summons.txt diff --git a/_maps/RandomRuins/SpaceRuins/dangerous_research.dmm b/_maps/RandomRuins/SpaceRuins/dangerous_research.dmm index b621fef4d28..2383c4688e3 100644 --- a/_maps/RandomRuins/SpaceRuins/dangerous_research.dmm +++ b/_maps/RandomRuins/SpaceRuins/dangerous_research.dmm @@ -1984,10 +1984,7 @@ /obj/effect/decal/cleanable/glass, /obj/item/shard, /obj/item/stack/sheet/iron, -/mob/living/simple_animal/hostile/heretic_summon/rust_spirit{ - AIStatus = 1; - stop_automated_movement = 0 - }, +/mob/living/basic/heretic_summon/rust_walker, /turf/open/floor/plating/rust, /area/ruin/space/has_grav/dangerous_research/lab) "zR" = ( @@ -3717,10 +3714,7 @@ /turf/open/floor/engine/airless, /area/ruin/space/has_grav/dangerous_research/maint) "Xh" = ( -/mob/living/simple_animal/hostile/heretic_summon/rust_spirit{ - AIStatus = 1; - stop_automated_movement = 0 - }, +/mob/living/basic/heretic_summon/rust_walker, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/structure/cable, @@ -3862,10 +3856,7 @@ /turf/open/floor/iron/white, /area/ruin/space/has_grav/dangerous_research/medical) "YZ" = ( -/mob/living/simple_animal/hostile/heretic_summon/rust_spirit{ - AIStatus = 1; - stop_automated_movement = 0 - }, +/mob/living/basic/heretic_summon/rust_walker, /turf/open/floor/plating/rust, /area/ruin/space/has_grav/dangerous_research/lab) "Zc" = ( diff --git a/code/__DEFINES/ai/ai.dm b/code/__DEFINES/ai/ai.dm index 467fc9d7c0f..83d7e7f6a5f 100644 --- a/code/__DEFINES/ai/ai.dm +++ b/code/__DEFINES/ai/ai.dm @@ -32,6 +32,8 @@ #define CAN_ACT_WHILE_DEAD (1<<1) /// Stop processing while in a progress bar #define PAUSE_DURING_DO_AFTER (1<<2) +/// Continue processing while in stasis +#define CAN_ACT_IN_STASIS (1<<3) //Base Subtree defines diff --git a/code/__DEFINES/ai/ai_blackboard.dm b/code/__DEFINES/ai/ai_blackboard.dm index 24ecf0a19f6..8e10eb07d3f 100644 --- a/code/__DEFINES/ai/ai_blackboard.dm +++ b/code/__DEFINES/ai/ai_blackboard.dm @@ -54,6 +54,9 @@ /// Generic key for a non-specific action #define BB_GENERIC_ACTION "BB_generic_action" +/// Generic key for a shapeshifting action +#define BB_SHAPESHIFT_ACTION "BB_shapeshift_action" + ///How long have we spent with no target? #define BB_TARGETLESS_TIME "BB_targetless_time" diff --git a/code/__DEFINES/footsteps.dm b/code/__DEFINES/footsteps.dm index d77a0e1cab3..c8b89b082a4 100644 --- a/code/__DEFINES/footsteps.dm +++ b/code/__DEFINES/footsteps.dm @@ -25,6 +25,7 @@ #define FOOTSTEP_MOB_SHOE "footstep_shoe" #define FOOTSTEP_MOB_HUMAN "footstep_human" //Warning: Only works on /mob/living/carbon/human #define FOOTSTEP_MOB_SLIME "footstep_slime" +#define FOOTSTEP_MOB_RUST "footstep_rust" #define FOOTSTEP_OBJ_MACHINE "footstep_machine" #define FOOTSTEP_OBJ_ROBOT "footstep_robot" diff --git a/code/_globalvars/phobias.dm b/code/_globalvars/phobias.dm index a913410214d..f9a878c3da9 100644 --- a/code/_globalvars/phobias.dm +++ b/code/_globalvars/phobias.dm @@ -72,7 +72,6 @@ GLOBAL_LIST_INIT(phobia_mobs, list( "doctors" = typecacheof(list(/mob/living/simple_animal/bot/medbot)), "heresy" = typecacheof(list( /mob/living/basic/heretic_summon, - /mob/living/simple_animal/hostile/heretic_summon, )), "insects" = typecacheof(list( /mob/living/basic/cockroach, @@ -99,7 +98,6 @@ GLOBAL_LIST_INIT(phobia_mobs, list( /mob/living/simple_animal/bot/mulebot/paranormal, /mob/living/simple_animal/hostile/construct, /mob/living/simple_animal/hostile/dark_wizard, - /mob/living/simple_animal/hostile/heretic_summon, /mob/living/simple_animal/hostile/skeleton, /mob/living/simple_animal/hostile/wizard, /mob/living/simple_animal/hostile/zombie, diff --git a/code/datums/ai/basic_mobs/base_basic_controller.dm b/code/datums/ai/basic_mobs/base_basic_controller.dm index 3eb79a815ad..cd025b28bcb 100644 --- a/code/datums/ai/basic_mobs/base_basic_controller.dm +++ b/code/datums/ai/basic_mobs/base_basic_controller.dm @@ -18,7 +18,10 @@ if(!isliving(pawn)) return var/mob/living/living_pawn = pawn - if(!(ai_traits & CAN_ACT_WHILE_DEAD) && IS_DEAD_OR_INCAP(living_pawn)) + var/incap_flags = NONE + if (ai_traits & CAN_ACT_IN_STASIS) + incap_flags |= IGNORE_STASIS + if(!(ai_traits & CAN_ACT_WHILE_DEAD) && (living_pawn.incapacitated(incap_flags) || living_pawn.stat)) return FALSE if(ai_traits & PAUSE_DURING_DO_AFTER && LAZYLEN(living_pawn.do_afters)) return FALSE diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/targeted_mob_ability.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/targeted_mob_ability.dm index 04cb9b171dd..ba167b34f29 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/targeted_mob_ability.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/targeted_mob_ability.dm @@ -11,7 +11,8 @@ finish_action(controller, FALSE, ability_key, target_key) return var/mob/pawn = controller.pawn - var/result = ability.InterceptClickOn(pawn, null, target) + pawn.face_atom(target) + var/result = ability.Trigger(target = target) finish_action(controller, result, ability_key, target_key) /datum/ai_behavior/targeted_mob_ability/finish_action(datum/ai_controller/controller, succeeded, ability_key, target_key) diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/targetting.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/targetting.dm index 0b9e31db667..376f62a5855 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/targetting.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/targetting.dm @@ -23,7 +23,8 @@ var/aggro_range = controller.blackboard[aggro_range_key] || vision_range controller.clear_blackboard_key(target_key) - var/list/potential_targets = hearers(aggro_range, controller.pawn) - living_mob //Remove self, so we don't suicide + + var/list/potential_targets = hearers(aggro_range, get_turf(controller.pawn)) - living_mob //Remove self, so we don't suicide for(var/HM in typecache_filter_list(range(aggro_range, living_mob), hostile_machines)) //Can we see any hostile machines? if(can_see(living_mob, HM, aggro_range)) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/shapechange_ambush.dm b/code/datums/ai/basic_mobs/basic_subtrees/shapechange_ambush.dm new file mode 100644 index 00000000000..ff01eb804ff --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/shapechange_ambush.dm @@ -0,0 +1,41 @@ +/// Shapeshift when we have no target, until someone has been nearby for long enough +/datum/ai_planning_subtree/shapechange_ambush + operational_datums = list(/datum/component/ai_target_timer) + /// Key where we keep our ability + var/ability_key = BB_SHAPESHIFT_ACTION + /// Key where we keep our target + var/target_key = BB_BASIC_MOB_CURRENT_TARGET + /// How long to lull our target into a false sense of security + var/minimum_target_time = 8 SECONDS + +/datum/ai_planning_subtree/shapechange_ambush/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/mob/living/living_pawn = controller.pawn + var/is_shifted = ismob(living_pawn.loc) + var/has_target = controller.blackboard_key_exists(target_key) + var/datum/action/cooldown/using_action = controller.blackboard[ability_key] + + if (!is_shifted) + if (has_target) + return // We're busy + + if (using_action?.IsAvailable()) + controller.queue_behavior(/datum/ai_behavior/use_mob_ability/shapeshift, BB_SHAPESHIFT_ACTION) // Shift + return SUBTREE_RETURN_FINISH_PLANNING + + if (!has_target || !using_action?.IsAvailable()) + return SUBTREE_RETURN_FINISH_PLANNING // Lie in wait + var/time_on_target = controller.blackboard[BB_BASIC_MOB_HAS_TARGET_TIME] || 0 + if (time_on_target < minimum_target_time) + return // Wait a bit longer + controller.queue_behavior(/datum/ai_behavior/use_mob_ability/shapeshift, BB_SHAPESHIFT_ACTION) // Surprise! + +/// Selects a random shapeshift ability before shifting +/datum/ai_behavior/use_mob_ability/shapeshift + +/datum/ai_behavior/use_mob_ability/shapeshift/setup(datum/ai_controller/controller, ability_key) + var/datum/action/cooldown/spell/shapeshift/using_action = controller.blackboard[ability_key] + if (!using_action?.IsAvailable()) + return FALSE + if (isnull(using_action.shapeshift_type)) // If we don't have a shape then pick one, AI can't use context wheels + using_action.shapeshift_type = pick(using_action.possible_shapes) + return ..() diff --git a/code/datums/ai/basic_mobs/targetting_datums/basic_targetting_datum.dm b/code/datums/ai/basic_mobs/targetting_datums/basic_targetting_datum.dm index 570088ce4d6..978d7b7f8a7 100644 --- a/code/datums/ai/basic_mobs/targetting_datums/basic_targetting_datum.dm +++ b/code/datums/ai/basic_mobs/targetting_datums/basic_targetting_datum.dm @@ -26,7 +26,7 @@ if(isnull(our_controller)) return FALSE - if(isturf(the_target) || !the_target) // bail out on invalids + if(isturf(the_target) || isnull(the_target)) // bail out on invalids return FALSE if(isobj(the_target.loc)) @@ -35,6 +35,8 @@ return FALSE if(ismob(the_target)) //Target is in godmode, ignore it. + if(living_mob.loc == the_target) + return FALSE // We've either been eaten or are shapeshifted, let's assume the latter because we're still alive var/mob/M = the_target if(M.status_flags & GODMODE) return FALSE @@ -45,7 +47,7 @@ if(living_mob.see_invisible < the_target.invisibility) //Target's invisible to us, forget it return FALSE - if(isturf(the_target.loc) && living_mob.z != the_target.z) // z check will always fail if target is in a mech + if(isturf(living_mob.loc) && isturf(the_target.loc) && living_mob.z != the_target.z) // z check will always fail if target is in a mech or pawn is shapeshifted or jaunting return FALSE if(isliving(the_target)) //Targeting vs living mobs diff --git a/code/datums/elements/footstep.dm b/code/datums/elements/footstep.dm index c9623c9ed4a..3a0a9961843 100644 --- a/code/datums/elements/footstep.dm +++ b/code/datums/elements/footstep.dm @@ -40,6 +40,8 @@ footstep_sounds = GLOB.heavyfootstep if(FOOTSTEP_MOB_SHOE) footstep_sounds = GLOB.footstep + if(FOOTSTEP_MOB_RUST) + footstep_sounds = 'sound/effects/footstep/rustystep1.ogg' if(FOOTSTEP_MOB_SLIME) footstep_sounds = 'sound/effects/footstep/slime1.ogg' if(FOOTSTEP_OBJ_MACHINE) diff --git a/code/modules/antagonists/heretic/heretic_living_heart.dm b/code/modules/antagonists/heretic/heretic_living_heart.dm index 4af93c0da68..1766cb4cd76 100644 --- a/code/modules/antagonists/heretic/heretic_living_heart.dm +++ b/code/modules/antagonists/heretic/heretic_living_heart.dm @@ -99,7 +99,7 @@ return TRUE -/datum/action/cooldown/track_target/Trigger(trigger_flags) +/datum/action/cooldown/track_target/Trigger(trigger_flags, atom/target) right_clicked = !!(trigger_flags & TRIGGER_SECONDARY_ACTION) return ..() diff --git a/code/modules/antagonists/heretic/knowledge/flesh_lore.dm b/code/modules/antagonists/heretic/knowledge/flesh_lore.dm index cd1a7ace540..c38fcc1a856 100644 --- a/code/modules/antagonists/heretic/knowledge/flesh_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/flesh_lore.dm @@ -294,7 +294,7 @@ /obj/item/pen = 1, /obj/item/paper = 1, ) - mob_to_summon = /mob/living/simple_animal/hostile/heretic_summon/stalker + mob_to_summon = /mob/living/basic/heretic_summon/stalker cost = 1 route = PATH_FLESH poll_ignore_define = POLL_IGNORE_STALKER diff --git a/code/modules/antagonists/heretic/knowledge/side_ash_flesh.dm b/code/modules/antagonists/heretic/knowledge/side_ash_flesh.dm index f7f3c175b2f..bf840d6ed27 100644 --- a/code/modules/antagonists/heretic/knowledge/side_ash_flesh.dm +++ b/code/modules/antagonists/heretic/knowledge/side_ash_flesh.dm @@ -71,7 +71,7 @@ /obj/item/bodypart/head = 1, /obj/item/book = 1, ) - mob_to_summon = /mob/living/simple_animal/hostile/heretic_summon/ash_spirit + mob_to_summon = /mob/living/basic/heretic_summon/ash_spirit cost = 1 route = PATH_SIDE poll_ignore_define = POLL_IGNORE_ASH_SPIRIT diff --git a/code/modules/antagonists/heretic/knowledge/side_rust_cosmos.dm b/code/modules/antagonists/heretic/knowledge/side_rust_cosmos.dm index 8aecfe06e1f..2dbb44ea4eb 100644 --- a/code/modules/antagonists/heretic/knowledge/side_rust_cosmos.dm +++ b/code/modules/antagonists/heretic/knowledge/side_rust_cosmos.dm @@ -88,7 +88,7 @@ /obj/item/book = 1, /obj/item/bodypart/head = 1, ) - mob_to_summon = /mob/living/simple_animal/hostile/heretic_summon/rust_spirit + mob_to_summon = /mob/living/basic/heretic_summon/rust_walker cost = 1 route = PATH_SIDE poll_ignore_define = POLL_IGNORE_RUST_SPIRIT diff --git a/code/modules/antagonists/heretic/knowledge/side_void_blade.dm b/code/modules/antagonists/heretic/knowledge/side_void_blade.dm index 92e6e381222..643fd434af7 100644 --- a/code/modules/antagonists/heretic/knowledge/side_void_blade.dm +++ b/code/modules/antagonists/heretic/knowledge/side_void_blade.dm @@ -159,5 +159,5 @@ ) cost = 1 route = PATH_SIDE - mob_to_summon = /mob/living/simple_animal/hostile/heretic_summon/maid_in_the_mirror + mob_to_summon = /mob/living/basic/heretic_summon/maid_in_the_mirror poll_ignore_define = POLL_IGNORE_MAID_IN_MIRROR diff --git a/code/modules/antagonists/heretic/magic/ascended_shapeshift.dm b/code/modules/antagonists/heretic/magic/ascended_shapeshift.dm index 4848635b443..f1d6de56e39 100644 --- a/code/modules/antagonists/heretic/magic/ascended_shapeshift.dm +++ b/code/modules/antagonists/heretic/magic/ascended_shapeshift.dm @@ -6,10 +6,10 @@ cooldown_time = 20 SECONDS die_with_shapeshifted_form = FALSE possible_shapes = list( + /mob/living/basic/heretic_summon/ash_spirit, /mob/living/basic/heretic_summon/raw_prophet/ascended, - /mob/living/simple_animal/hostile/heretic_summon/rust_spirit, - /mob/living/simple_animal/hostile/heretic_summon/ash_spirit, - /mob/living/simple_animal/hostile/heretic_summon/stalker, + /mob/living/basic/heretic_summon/rust_walker, + /mob/living/basic/heretic_summon/stalker, ) /datum/action/cooldown/spell/shapeshift/eldritch/ascension/do_shapeshift(mob/living/caster) diff --git a/code/modules/antagonists/heretic/structures/knock_final.dm b/code/modules/antagonists/heretic/structures/knock_final.dm index 28a8f90b9f3..f7d0d1a9ea7 100644 --- a/code/modules/antagonists/heretic/structures/knock_final.dm +++ b/code/modules/antagonists/heretic/structures/knock_final.dm @@ -28,7 +28,7 @@ . = ..() transform *= 3 if(isnull(monster_types)) - monster_types = subtypesof(/mob/living/simple_animal/hostile/heretic_summon) + subtypesof(/mob/living/basic/heretic_summon) - monster_types_blacklist + monster_types = subtypesof(/mob/living/basic/heretic_summon) - monster_types_blacklist if(!isnull(ascendant_mind)) ascendee = ascendant_mind RegisterSignals(ascendant_mind.current, list(COMSIG_LIVING_DEATH, COMSIG_QDELETING), PROC_REF(end_madness)) diff --git a/code/modules/mob/living/basic/heretic/ash_spirit.dm b/code/modules/mob/living/basic/heretic/ash_spirit.dm new file mode 100644 index 00000000000..b2d4d8b4d29 --- /dev/null +++ b/code/modules/mob/living/basic/heretic/ash_spirit.dm @@ -0,0 +1,25 @@ +/** + * Player-only mob which is fast, can jaunt a short distance, and is dangerous at close range + */ +/mob/living/basic/heretic_summon/ash_spirit + name = "Ash Spirit" + real_name = "Ashy" + desc = "A manifestation of ash, trailing a perpetual cloud of short-lived cinders." + icon_state = "ash_walker" + icon_living = "ash_walker" + maxHealth = 75 + health = 75 + melee_damage_lower = 15 + melee_damage_upper = 20 + sight = SEE_TURFS + +/mob/living/basic/heretic_summon/ash_spirit/Initialize(mapload) + . = ..() + var/static/list/actions_to_add = list( + /datum/action/cooldown/spell/fire_sworn, + /datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash, + /datum/action/cooldown/spell/pointed/cleave, + ) + for (var/action in actions_to_add) + var/datum/action/cooldown/new_action = new action(src) + new_action.Grant(src) diff --git a/code/modules/mob/living/basic/heretic/flesh_stalker.dm b/code/modules/mob/living/basic/heretic/flesh_stalker.dm new file mode 100644 index 00000000000..6f31b3ce7c5 --- /dev/null +++ b/code/modules/mob/living/basic/heretic/flesh_stalker.dm @@ -0,0 +1,46 @@ +/// Durable ambush mob with an EMP ability +/mob/living/basic/heretic_summon/stalker + name = "Flesh Stalker" + real_name = "Flesh Stalker" + desc = "An abomination cobbled together from varied remains. Its appearance changes slightly every time you blink." + icon_state = "stalker" + icon_living = "stalker" + maxHealth = 150 + health = 150 + melee_damage_lower = 15 + melee_damage_upper = 20 + sight = SEE_MOBS + ai_controller = /datum/ai_controller/basic_controller/stalker + /// Associative list of action types we would like to have, and what blackboard key (if any) to put it in + var/static/list/actions_to_add = list( + /datum/action/cooldown/spell/emp/eldritch = BB_GENERIC_ACTION, + /datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash = null, + /datum/action/cooldown/spell/shapeshift/eldritch = BB_SHAPESHIFT_ACTION, + ) + +/mob/living/basic/heretic_summon/stalker/Initialize(mapload) + . = ..() + AddComponent(/datum/component/ai_target_timer) + for (var/action_type in actions_to_add) + var/datum/action/new_action = new action_type(src) + new_action.Grant(src) + var/blackboard_key = actions_to_add[action_type] + if (!isnull(blackboard_key)) + ai_controller?.set_blackboard_key(blackboard_key, new_action) + +/// Changes shape and lies in wait when it has no target, uses EMP and attacks once it does +/datum/ai_controller/basic_controller/stalker + ai_traits = CAN_ACT_IN_STASIS + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/shapechange_ambush, + /datum/ai_planning_subtree/use_mob_ability, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) diff --git a/code/modules/antagonists/heretic/mobs/maid_in_mirror.dm b/code/modules/mob/living/basic/heretic/maid_in_the_mirror.dm similarity index 60% rename from code/modules/antagonists/heretic/mobs/maid_in_mirror.dm rename to code/modules/mob/living/basic/heretic/maid_in_the_mirror.dm index b53bbe147d3..edc5148b3cc 100644 --- a/code/modules/antagonists/heretic/mobs/maid_in_mirror.dm +++ b/code/modules/mob/living/basic/heretic/maid_in_the_mirror.dm @@ -1,11 +1,11 @@ -// A summon which floats around the station incorporeally, and can appear in any mirror -/mob/living/simple_animal/hostile/heretic_summon/maid_in_the_mirror +/// Scout and assassin who can appear and disappear from glass surfaces. Damaged by being examined. +/mob/living/basic/heretic_summon/maid_in_the_mirror name = "Maid in the Mirror" real_name = "Maid in the Mirror" desc = "A floating and flowing wisp of chilled air. Glancing at it causes it to shimmer slightly." icon = 'icons/mob/simple/mob.dmi' icon_state = "stand" - icon_living = "stand" // Placeholder sprite + icon_living = "stand" // Placeholder sprite... still speak_emote = list("whispers") movement_type = FLOATING status_flags = CANSTUN | CANPUSH @@ -16,36 +16,34 @@ melee_damage_upper = 16 sight = SEE_MOBS | SEE_OBJS | SEE_TURFS death_message = "shatters and vanishes, releasing a gust of cold air." - loot = list( - /obj/item/shard, - /obj/effect/decal/cleanable/ash, - /obj/item/clothing/suit/armor/vest, - /obj/item/organ/internal/lungs, - ) - actions_to_add = list(/datum/action/cooldown/spell/jaunt/mirror_walk) - - /// Whether we take damage when we're examined - var/weak_on_examine = TRUE - /// The cooldown after being examined that the same mob cannot trigger it again + /// Whether we take damage when someone looks at us + var/harmed_by_examine = TRUE + /// How often being examined by a specific mob can hurt us var/recent_examine_damage_cooldown = 10 SECONDS /// A list of REFs to people who recently examined us var/list/recent_examiner_refs = list() -/mob/living/simple_animal/hostile/heretic_summon/maid_in_the_mirror/death(gibbed) +/mob/living/basic/heretic_summon/Initialize(mapload) + . = ..() + var/static/list/loot = list( + /obj/effect/decal/cleanable/ash, + /obj/item/clothing/suit/armor/vest, + /obj/item/organ/internal/lungs, + /obj/item/shard, + ) + AddElement(/datum/element/death_drops, loot) + var/datum/action/cooldown/spell/jaunt/mirror_walk/jaunt = new (src) + jaunt.Grant(src) + +/mob/living/basic/heretic_summon/maid_in_the_mirror/death(gibbed) var/turf/death_turf = get_turf(src) - death_turf.TakeTemperature(-40) + death_turf.TakeTemperature(-40) // Spooky return ..() // Examining them will harm them, on a cooldown. -/mob/living/simple_animal/hostile/heretic_summon/maid_in_the_mirror/examine(mob/user) +/mob/living/basic/heretic_summon/maid_in_the_mirror/examine(mob/user) . = ..() - if(!weak_on_examine) - return - - if(!isliving(user) || user.stat == DEAD) - return - - if(IS_HERETIC_OR_MONSTER(user) || user == src) + if(!harmed_by_examine || user == src || user.stat == DEAD || !isliving(user) || IS_HERETIC_OR_MONSTER(user)) return var/user_ref = REF(user) @@ -62,7 +60,9 @@ recent_examiner_refs += user_ref apply_damage(maxHealth * 0.1) // We take 10% of our health as damage upon being examined playsound(src, 'sound/effects/ghost2.ogg', 40, TRUE) - addtimer(CALLBACK(src, PROC_REF(clear_recent_examiner), user_ref), recent_examine_damage_cooldown) + addtimer(CALLBACK(src, PROC_REF(clear_recent_examiner), user_ref), recent_examine_damage_cooldown, TIMER_DELETE_ME) + animate(src, alpha = 120, time = 0.5 SECONDS, easing = ELASTIC_EASING, loop = 2, flags = ANIMATION_PARALLEL) + animate(alpha = 255, time = 0.5 SECONDS, easing = ELASTIC_EASING) // If we're examined on low enough health we die straight up else @@ -73,7 +73,7 @@ death() -/mob/living/simple_animal/hostile/heretic_summon/maid_in_the_mirror/proc/clear_recent_examiner(mob_ref) +/mob/living/basic/heretic_summon/maid_in_the_mirror/proc/clear_recent_examiner(mob_ref) if(!(mob_ref in recent_examiner_refs)) return diff --git a/code/modules/mob/living/basic/heretic/rust_walker.dm b/code/modules/mob/living/basic/heretic/rust_walker.dm new file mode 100644 index 00000000000..070326c0281 --- /dev/null +++ b/code/modules/mob/living/basic/heretic/rust_walker.dm @@ -0,0 +1,87 @@ +/// Pretty simple mob which creates areas of rust and has a rust-creating projectile spell +/mob/living/basic/heretic_summon/rust_walker + name = "Rust Walker" + real_name = "Rusty" + desc = "A grinding, clanking construct which leaches life from its surroundings with every armoured step." + icon_state = "rust_walker_s" + base_icon_state = "rust_walker" + icon_living = "rust_walker_s" + maxHealth = 75 + health = 75 + melee_damage_lower = 15 + melee_damage_upper = 20 + sight = SEE_TURFS + speed = 1 + ai_controller = /datum/ai_controller/basic_controller/rust_walker + +/mob/living/basic/heretic_summon/rust_walker/Initialize(mapload) + . = ..() + AddElement(/datum/element/footstep, FOOTSTEP_MOB_RUST) + var/datum/action/cooldown/spell/aoe/rust_conversion/small/conversion = new(src) + conversion.Grant(src) + ai_controller?.set_blackboard_key(BB_GENERIC_ACTION, conversion) + + var/datum/action/cooldown/spell/basic_projectile/rust_wave/short/wave = new(src) + wave.Grant(src) + ai_controller?.set_blackboard_key(BB_TARGETTED_ACTION, wave) + +/mob/living/basic/heretic_summon/rust_walker/setDir(newdir) + . = ..() + update_appearance(UPDATE_ICON_STATE) + +/mob/living/basic/heretic_summon/rust_walker/update_icon_state() + . = ..() + if(stat == DEAD) // We usually delete on death but just in case + return + if(dir & NORTH) + icon_state = "[base_icon_state]_n" + else if(dir & SOUTH) + icon_state = "[base_icon_state]_s" + icon_living = icon_state + +/mob/living/basic/heretic_summon/rust_walker/Life(seconds_per_tick = SSMOBS_DT, times_fired) + if(stat == DEAD) + return ..() + var/turf/our_turf = get_turf(src) + if(HAS_TRAIT(our_turf, TRAIT_RUSTY)) + adjustBruteLoss(-3 * seconds_per_tick) + + return ..() + +/// Converts unconverted terrain, sprays pocket sand around +/datum/ai_controller/basic_controller/rust_walker + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk/rust + planning_subtrees = list( + /datum/ai_planning_subtree/use_mob_ability/rust_walker, + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/targeted_mob_ability, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) + +/// Moves a lot if healthy and on rust (to find more tiles to rust) or unhealthy and not on rust (to find healing rust) +/// Still moving in random directions though we're not really seeking it out +/datum/idle_behavior/idle_random_walk/rust + +/datum/idle_behavior/idle_random_walk/rust/perform_idle_behavior(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/our_mob = controller.pawn + var/turf/our_turf = get_turf(our_mob) + if (HAS_TRAIT(our_turf, TRAIT_RUSTY)) + walk_chance = (our_mob.health < our_mob.maxHealth) ? 10 : 50 + else + walk_chance = (our_mob.health < our_mob.maxHealth) ? 50 : 10 + return ..() + +/// Use if we're not stood on rust right now +/datum/ai_planning_subtree/use_mob_ability/rust_walker + +/datum/ai_planning_subtree/use_mob_ability/rust_walker/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/turf/our_turf = get_turf(controller.pawn) + if (HAS_TRAIT(our_turf, TRAIT_RUSTY)) + return + return ..() diff --git a/code/modules/mob/living/simple_animal/hostile/heretic_monsters.dm b/code/modules/mob/living/simple_animal/hostile/heretic_monsters.dm deleted file mode 100644 index 090709d0527..00000000000 --- a/code/modules/mob/living/simple_animal/hostile/heretic_monsters.dm +++ /dev/null @@ -1,120 +0,0 @@ -/mob/living/simple_animal/hostile/heretic_summon - name = "Eldritch Demon" - real_name = "Eldritch Demon" - desc = "A horror from beyond this realm." - icon = 'icons/mob/nonhuman-player/eldritch_mobs.dmi' - gender = NEUTER - mob_biotypes = NONE - attack_sound = 'sound/weapons/punch1.ogg' - response_help_continuous = "thinks better of touching" - response_help_simple = "think better of touching" - response_disarm_continuous = "flails at" - response_disarm_simple = "flail at" - response_harm_continuous = "reaps" - response_harm_simple = "tears" - speak_emote = list("screams") - speak_chance = 1 - speed = 0 - combat_mode = TRUE - stop_automated_movement = TRUE - AIStatus = AI_OFF - // Sort of greenish brown, to match the vibeTM - lighting_cutoff_red = 20 - lighting_cutoff_green = 25 - lighting_cutoff_blue = 5 - damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0) - 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 = INFINITY - movement_type = GROUND - pressure_resistance = 100 - del_on_death = TRUE - death_message = "implodes into itself." - loot = list(/obj/effect/gibspawner/human) - faction = list(FACTION_HERETIC) - simple_mob_flags = SILENCE_RANGED_MESSAGE - - /// Innate spells that are added when a beast is created. - var/list/actions_to_add - -/mob/living/simple_animal/hostile/heretic_summon/Initialize(mapload) - . = ..() - for(var/spell in actions_to_add) - var/datum/action/cooldown/spell/new_spell = new spell(src) - new_spell.Grant(src) - -/mob/living/simple_animal/hostile/heretic_summon/rust_spirit - name = "Rust Walker" - real_name = "Rusty" - desc = "An incomprehensible abomination. Everywhere it steps, it appears to be actively seeping life out of its surroundings." - icon_state = "rust_walker_s" - icon_living = "rust_walker_s" - status_flags = CANPUSH - maxHealth = 75 - health = 75 - melee_damage_lower = 15 - melee_damage_upper = 20 - sight = SEE_TURFS - actions_to_add = list( - /datum/action/cooldown/spell/aoe/rust_conversion/small, - /datum/action/cooldown/spell/basic_projectile/rust_wave/short, - ) - -/mob/living/simple_animal/hostile/heretic_summon/rust_spirit/setDir(newdir) - . = ..() - if(newdir == NORTH) - icon_state = "rust_walker_n" - else if(newdir == SOUTH) - icon_state = "rust_walker_s" - update_appearance(UPDATE_ICON_STATE) - -/mob/living/simple_animal/hostile/heretic_summon/rust_spirit/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE) - . = ..() - playsound(src, 'sound/effects/footstep/rustystep1.ogg', 100, TRUE) - -/mob/living/simple_animal/hostile/heretic_summon/rust_spirit/Life(seconds_per_tick = SSMOBS_DT, times_fired) - if(stat == DEAD) - return ..() - - var/turf/our_turf = get_turf(src) - if(HAS_TRAIT(our_turf, TRAIT_RUSTY)) - adjustBruteLoss(-1.5 * seconds_per_tick, FALSE) - adjustFireLoss(-1.5 * seconds_per_tick, FALSE) - - return ..() - -/mob/living/simple_animal/hostile/heretic_summon/ash_spirit - name = "Ash Man" - real_name = "Ashy" - desc = "An incomprehensible abomination. As it moves, a thin trail of ash follows, appearing from seemingly nowhere." - icon_state = "ash_walker" - icon_living = "ash_walker" - status_flags = CANPUSH - maxHealth = 75 - health = 75 - melee_damage_lower = 15 - melee_damage_upper = 20 - sight = SEE_TURFS - actions_to_add = list( - /datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash, - /datum/action/cooldown/spell/pointed/cleave, - /datum/action/cooldown/spell/fire_sworn, - ) - -/mob/living/simple_animal/hostile/heretic_summon/stalker - name = "Flesh Stalker" - real_name = "Flesh Stalker" - desc = "An abomination made from several limbs and organs. Every moment you stare at it, it appears to shift and change unnaturally." - icon_state = "stalker" - icon_living = "stalker" - status_flags = CANPUSH - maxHealth = 150 - health = 150 - melee_damage_lower = 15 - melee_damage_upper = 20 - sight = SEE_MOBS - actions_to_add = list( - /datum/action/cooldown/spell/shapeshift/eldritch, - /datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash, - /datum/action/cooldown/spell/emp/eldritch, - ) diff --git a/code/modules/unit_tests/simple_animal_freeze.dm b/code/modules/unit_tests/simple_animal_freeze.dm index 4e9b10a80ee..eec2ab9adcf 100644 --- a/code/modules/unit_tests/simple_animal_freeze.dm +++ b/code/modules/unit_tests/simple_animal_freeze.dm @@ -103,11 +103,6 @@ /mob/living/simple_animal/hostile/guardian/ranged, /mob/living/simple_animal/hostile/guardian/standard, /mob/living/simple_animal/hostile/guardian/support, - /mob/living/simple_animal/hostile/heretic_summon, - /mob/living/simple_animal/hostile/heretic_summon/ash_spirit, - /mob/living/simple_animal/hostile/heretic_summon/maid_in_the_mirror, - /mob/living/simple_animal/hostile/heretic_summon/rust_spirit, - /mob/living/simple_animal/hostile/heretic_summon/stalker, /mob/living/simple_animal/hostile/illusion, /mob/living/simple_animal/hostile/illusion/escape, /mob/living/simple_animal/hostile/illusion/mirage, diff --git a/tgstation.dme b/tgstation.dme index 96b0f791a98..a7de438df15 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -827,6 +827,7 @@ #include "code\datums\ai\basic_mobs\basic_subtrees\move_to_cardinal.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\opportunistic_ventcrawler.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\ranged_skirmish.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\shapechange_ambush.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\simple_attack_target.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\simple_find_nearest_target_to_flee.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\simple_find_target.dm" @@ -2938,7 +2939,6 @@ #include "code\modules\antagonists\heretic\magic\void_phase.dm" #include "code\modules\antagonists\heretic\magic\void_pull.dm" #include "code\modules\antagonists\heretic\magic\wave_of_desperation.dm" -#include "code\modules\antagonists\heretic\mobs\maid_in_mirror.dm" #include "code\modules\antagonists\heretic\status_effects\buffs.dm" #include "code\modules\antagonists\heretic\status_effects\debuffs.dm" #include "code\modules\antagonists\heretic\status_effects\ghoul.dm" @@ -4368,10 +4368,14 @@ #include "code\modules\mob\living\basic\farm_animals\goat\_goat.dm" #include "code\modules\mob\living\basic\farm_animals\goat\goat_ai.dm" #include "code\modules\mob\living\basic\farm_animals\goat\goat_subtypes.dm" +#include "code\modules\mob\living\basic\heretic\ash_spirit.dm" #include "code\modules\mob\living\basic\heretic\fire_shark.dm" +#include "code\modules\mob\living\basic\heretic\flesh_stalker.dm" #include "code\modules\mob\living\basic\heretic\flesh_worm.dm" #include "code\modules\mob\living\basic\heretic\heretic_summon.dm" +#include "code\modules\mob\living\basic\heretic\maid_in_the_mirror.dm" #include "code\modules\mob\living\basic\heretic\raw_prophet.dm" +#include "code\modules\mob\living\basic\heretic\rust_walker.dm" #include "code\modules\mob\living\basic\heretic\star_gazer.dm" #include "code\modules\mob\living\basic\icemoon\ice_whelp\ice_whelp.dm" #include "code\modules\mob\living\basic\icemoon\ice_whelp\ice_whelp_abilities.dm" @@ -4701,7 +4705,6 @@ #include "code\modules\mob\living\simple_animal\guardian\types\support.dm" #include "code\modules\mob\living\simple_animal\hostile\alien.dm" #include "code\modules\mob\living\simple_animal\hostile\dark_wizard.dm" -#include "code\modules\mob\living\simple_animal\hostile\heretic_monsters.dm" #include "code\modules\mob\living\simple_animal\hostile\hostile.dm" #include "code\modules\mob\living\simple_animal\hostile\illusion.dm" #include "code\modules\mob\living\simple_animal\hostile\mimic.dm" diff --git a/tools/UpdatePaths/Scripts/78757_simple_to_basic_heretic_summons.txt b/tools/UpdatePaths/Scripts/78757_simple_to_basic_heretic_summons.txt new file mode 100644 index 00000000000..470eb429db7 --- /dev/null +++ b/tools/UpdatePaths/Scripts/78757_simple_to_basic_heretic_summons.txt @@ -0,0 +1,4 @@ +/mob/living/simple_animal/hostile/heretic_summon/ash_spirit : /mob/living/basic/heretic_summon/ash_spirit{@OLD} +/mob/living/simple_animal/hostile/heretic_summon/maid_in_the_mirror : /mob/living/basic/heretic_summon/maid_in_the_mirror{@OLD} +/mob/living/simple_animal/hostile/heretic_summon/rust_spirit : /mob/living/basic/heretic_summon/rust_walker{@OLD;AIStatus=@SKIP;stop_automated_movement=@SKIP} +/mob/living/simple_animal/hostile/heretic_summon/stalker : /mob/living/basic/heretic_summon/stalker{@OLD}