mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-26 06:29:23 +01:00
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 🆑 refactor: Rust Walkers, Ash Spirits, Flesh Stalkers, and The Maid in the Mirror now use the basic mob framework. Please report any unusual behaviour. /🆑 --------- Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
This commit is contained in:
@@ -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)
|
||||
@@ -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,
|
||||
)
|
||||
@@ -0,0 +1,81 @@
|
||||
/// 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... still
|
||||
speak_emote = list("whispers")
|
||||
movement_type = FLOATING
|
||||
status_flags = CANSTUN | CANPUSH
|
||||
attack_sound = SFX_SHATTER
|
||||
maxHealth = 80
|
||||
health = 80
|
||||
melee_damage_lower = 12
|
||||
melee_damage_upper = 16
|
||||
sight = SEE_MOBS | SEE_OBJS | SEE_TURFS
|
||||
death_message = "shatters and vanishes, releasing a gust of cold air."
|
||||
/// 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/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) // Spooky
|
||||
return ..()
|
||||
|
||||
// Examining them will harm them, on a cooldown.
|
||||
/mob/living/basic/heretic_summon/maid_in_the_mirror/examine(mob/user)
|
||||
. = ..()
|
||||
if(!harmed_by_examine || user == src || user.stat == DEAD || !isliving(user) || IS_HERETIC_OR_MONSTER(user))
|
||||
return
|
||||
|
||||
var/user_ref = REF(user)
|
||||
if(user_ref in recent_examiner_refs)
|
||||
return
|
||||
|
||||
// If we have health, we take some damage
|
||||
if(health > (maxHealth * 0.125))
|
||||
visible_message(
|
||||
span_warning("[src] seems to fade in and out slightly."),
|
||||
span_userdanger("[user]'s gaze pierces your every being!"),
|
||||
)
|
||||
|
||||
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, 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
|
||||
visible_message(
|
||||
span_danger("[src] vanishes from existence!"),
|
||||
span_userdanger("[user]'s gaze shatters your form, destroying you!"),
|
||||
)
|
||||
|
||||
death()
|
||||
|
||||
/mob/living/basic/heretic_summon/maid_in_the_mirror/proc/clear_recent_examiner(mob_ref)
|
||||
if(!(mob_ref in recent_examiner_refs))
|
||||
return
|
||||
|
||||
recent_examiner_refs -= mob_ref
|
||||
heal_overall_damage(5)
|
||||
@@ -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 ..()
|
||||
Reference in New Issue
Block a user