Skip to content

Commit

Permalink
Basic Heretic Mobs: The Rest of Them (tgstation#78757)
Browse files Browse the repository at this point in the history
## 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 <[email protected]>
  • Loading branch information
Jacquerel and MrMelbert authored Oct 6, 2023
1 parent c3446fa commit 5e2c845
Show file tree
Hide file tree
Showing 26 changed files with 265 additions and 180 deletions.
15 changes: 3 additions & 12 deletions _maps/RandomRuins/SpaceRuins/dangerous_research.dmm
Original file line number Diff line number Diff line change
Expand Up @@ -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" = (
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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" = (
Expand Down
2 changes: 2 additions & 0 deletions code/__DEFINES/ai/ai.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 3 additions & 0 deletions code/__DEFINES/ai/ai_blackboard.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
1 change: 1 addition & 0 deletions code/__DEFINES/footsteps.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
2 changes: 0 additions & 2 deletions code/_globalvars/phobias.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down
5 changes: 4 additions & 1 deletion code/datums/ai/basic_mobs/base_basic_controller.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion code/datums/ai/basic_mobs/basic_ai_behaviors/targetting.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
41 changes: 41 additions & 0 deletions code/datums/ai/basic_mobs/basic_subtrees/shapechange_ambush.dm
Original file line number Diff line number Diff line change
@@ -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 ..()
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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
Expand All @@ -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
Expand Down
2 changes: 2 additions & 0 deletions code/datums/elements/footstep.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion code/modules/antagonists/heretic/heretic_living_heart.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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 ..()

Expand Down
2 changes: 1 addition & 1 deletion code/modules/antagonists/heretic/knowledge/flesh_lore.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
6 changes: 3 additions & 3 deletions code/modules/antagonists/heretic/magic/ascended_shapeshift.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion code/modules/antagonists/heretic/structures/knock_final.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
25 changes: 25 additions & 0 deletions code/modules/mob/living/basic/heretic/ash_spirit.dm
Original file line number Diff line number Diff line change
@@ -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)
46 changes: 46 additions & 0 deletions code/modules/mob/living/basic/heretic/flesh_stalker.dm
Original file line number Diff line number Diff line change
@@ -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,
)
Loading

0 comments on commit 5e2c845

Please sign in to comment.