mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-01-28 18:11:16 +00:00
## About The Pull Request [Implements the backend required to make targeting datums global](6901ead12e) It's inconsistent with the rest of basic ai for these to have a high degree of state, plus like, such a waste yaknow? [Implements GET_TARGETING_STRATEGY](d79c29134d) Regexes used: new.*(/datum/targetting_datum[^,(]*)\(*\)* -> GET_TARGETING_STRATEGY($1) Renamed all instances of targetting to targeting (also targetting datum -> targeting strategy) I've used GET_TARGETING_STRATEGY at the source where the keys are actually used, rather then in the listing. This works out just fine. ## Why It's Good For The Game Not a misspelled name through the whole codebase, very slightly less memory load for basically no downside (slight cpu cost maybe but not a significant one. --------- Co-authored-by: John Willard <53777086+JohnFulpWillard@users.noreply.github.com>
215 lines
7.6 KiB
Plaintext
215 lines
7.6 KiB
Plaintext
/// The classic morph, Corpus Accipientis (or "The body of the recipient"). It's a blob that can disguise itself as other things simply put.
|
|
/mob/living/basic/morph
|
|
name = "morph"
|
|
real_name = "morph"
|
|
desc = "A revolting, pulsating pile of flesh."
|
|
speak_emote = list("gurgles")
|
|
icon = 'icons/mob/simple/animal.dmi'
|
|
icon_state = "morph"
|
|
icon_living = "morph"
|
|
icon_dead = "morph_dead"
|
|
combat_mode = TRUE
|
|
|
|
mob_biotypes = MOB_BEAST
|
|
pass_flags = PASSTABLE
|
|
|
|
maxHealth = 150
|
|
health = 150
|
|
habitable_atmos = 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)
|
|
minimum_survivable_temperature = TCMB
|
|
|
|
obj_damage = 50
|
|
melee_damage_lower = 20
|
|
melee_damage_upper = 20
|
|
melee_attack_cooldown = CLICK_CD_MELEE
|
|
|
|
// Oh you KNOW it's gonna be real green
|
|
lighting_cutoff_red = 10
|
|
lighting_cutoff_green = 35
|
|
lighting_cutoff_blue = 15
|
|
|
|
attack_verb_continuous = "glomps"
|
|
attack_verb_simple = "glomp"
|
|
attack_sound = 'sound/effects/blobattack.ogg'
|
|
attack_vis_effect = ATTACK_EFFECT_BITE //nom nom nom
|
|
butcher_results = list(/obj/item/food/meat/slab = 2)
|
|
|
|
ai_controller = /datum/ai_controller/basic_controller/morph
|
|
|
|
/// A weakref pointing to the form we are currently assumed as.
|
|
var/datum/weakref/form_weakref = null
|
|
/// A typepath pointing of the form we are currently assumed as. Remember, TYPEPATH!!!
|
|
var/atom/movable/form_typepath = null
|
|
/// The ability that allows us to disguise ourselves.
|
|
var/datum/action/cooldown/mob_cooldown/assume_form/disguise_ability = null
|
|
|
|
/// How much damage are we doing while disguised?
|
|
var/melee_damage_disguised = 0
|
|
/// Can we eat while disguised?
|
|
var/eat_while_disguised = FALSE
|
|
|
|
/mob/living/basic/morph/Initialize(mapload)
|
|
. = ..()
|
|
ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT)
|
|
RegisterSignal(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(pre_attack))
|
|
RegisterSignal(src, COMSIG_CLICK_SHIFT, PROC_REF(trigger_ability))
|
|
RegisterSignal(src, COMSIG_ACTION_DISGUISED_APPEARANCE, PROC_REF(on_disguise))
|
|
RegisterSignal(src, SIGNAL_REMOVETRAIT(TRAIT_DISGUISED), PROC_REF(on_undisguise))
|
|
|
|
AddElement(/datum/element/ai_retaliate)
|
|
AddElement(/datum/element/content_barfer)
|
|
|
|
disguise_ability = new(src)
|
|
disguise_ability.Grant(src)
|
|
|
|
/mob/living/basic/morph/examine(mob/user)
|
|
if(!HAS_TRAIT(src, TRAIT_DISGUISED))
|
|
return ..()
|
|
|
|
var/atom/movable/form_reference = form_weakref.resolve()
|
|
if(!isnull(form_reference))
|
|
. = form_reference.examine(user)
|
|
|
|
if(get_dist(user, src) <= 3) // always add this because if the form_reference somehow nulls out we still want to have something look "weird" about an item when someone is close
|
|
. += span_warning("It doesn't look quite right...")
|
|
|
|
/mob/living/basic/morph/med_hud_set_health()
|
|
if(isliving(form_typepath))
|
|
return ..()
|
|
|
|
//we hide medical hud while in regular state or an item
|
|
var/image/holder = hud_list[HEALTH_HUD]
|
|
holder.icon_state = null
|
|
|
|
/mob/living/basic/morph/med_hud_set_status()
|
|
if(isliving(form_typepath))
|
|
return ..()
|
|
|
|
//we hide medical hud while in regular state or an item
|
|
var/image/holder = hud_list[STATUS_HUD]
|
|
holder.icon_state = null
|
|
|
|
/mob/living/basic/morph/death(gibbed)
|
|
if(HAS_TRAIT(src, TRAIT_DISGUISED))
|
|
visible_message(
|
|
span_warning("[src] twists and dissolves into a pile of green flesh!"),
|
|
span_userdanger("Your skin ruptures! Your flesh breaks apart! No disguise can ward off de--"),
|
|
)
|
|
|
|
return ..()
|
|
|
|
/mob/living/basic/morph/can_track(mob/living/user)
|
|
if(!HAS_TRAIT(src, TRAIT_DISGUISED))
|
|
return FALSE
|
|
return ..()
|
|
|
|
/// Do some more logic for the morph when we disguise through the action.
|
|
/mob/living/basic/morph/proc/on_disguise(mob/living/basic/user, atom/movable/target)
|
|
SIGNAL_HANDLER
|
|
// We are now weaker
|
|
melee_damage_lower = melee_damage_disguised
|
|
melee_damage_upper = melee_damage_disguised
|
|
add_movespeed_modifier(/datum/movespeed_modifier/morph_disguised)
|
|
|
|
med_hud_set_health()
|
|
med_hud_set_status() //we're an object honest
|
|
|
|
visible_message(
|
|
span_warning("[src] suddenly twists and changes shape, becoming a copy of [target]!"),
|
|
span_notice("You twist your body and assume the form of [target]."),
|
|
)
|
|
|
|
form_weakref = WEAKREF(target)
|
|
form_typepath = target.type
|
|
|
|
/// Do some more logic for the morph when we undisguise through the action.
|
|
/mob/living/basic/morph/proc/on_undisguise()
|
|
SIGNAL_HANDLER
|
|
visible_message(
|
|
span_warning("[src] suddenly collapses in on itself, dissolving into a pile of green flesh!"),
|
|
span_notice("You reform to your normal body."),
|
|
)
|
|
|
|
//Baseline stats
|
|
melee_damage_lower = initial(melee_damage_lower)
|
|
melee_damage_upper = initial(melee_damage_upper)
|
|
remove_movespeed_modifier(/datum/movespeed_modifier/morph_disguised)
|
|
|
|
med_hud_set_health()
|
|
med_hud_set_status() //we are no longer an object
|
|
|
|
form_weakref = null
|
|
form_typepath = null
|
|
|
|
/// Alias for the disguise ability to be used as a keybind.
|
|
/mob/living/basic/morph/proc/trigger_ability(mob/living/basic/source, atom/target)
|
|
SIGNAL_HANDLER
|
|
|
|
// linters hate this if it's not async for some reason even though nothing blocks
|
|
INVOKE_ASYNC(disguise_ability, TYPE_PROC_REF(/datum/action/cooldown, InterceptClickOn), caller = source, target = target)
|
|
return COMSIG_MOB_CANCEL_CLICKON
|
|
|
|
/// Handles the logic for attacking anything.
|
|
/mob/living/basic/morph/proc/pre_attack(mob/living/basic/source, atom/target)
|
|
SIGNAL_HANDLER
|
|
|
|
if(HAS_TRAIT(src, TRAIT_DISGUISED) && (melee_damage_disguised <= 0))
|
|
balloon_alert(src, "can't attack while disguised!")
|
|
return COMPONENT_HOSTILE_NO_ATTACK
|
|
|
|
if(isliving(target)) //Eat Corpses to regen health
|
|
var/mob/living/living_target = target
|
|
if(living_target.stat != DEAD)
|
|
return
|
|
|
|
INVOKE_ASYNC(source, PROC_REF(eat), eatable = living_target, delay = 3 SECONDS, update_health = -50)
|
|
return COMPONENT_HOSTILE_NO_ATTACK
|
|
|
|
if(isitem(target)) //Eat items just to be annoying
|
|
var/obj/item/item_target = target
|
|
if(item_target.anchored)
|
|
return
|
|
|
|
INVOKE_ASYNC(source, PROC_REF(eat), eatable = item_target, delay = 2 SECONDS)
|
|
return COMPONENT_HOSTILE_NO_ATTACK
|
|
|
|
/// Eat stuff. Delicious. Return TRUE if we ate something, FALSE otherwise.
|
|
/// Required: `eatable` is the thing (item or mob) that we are going to eat.
|
|
/// Optional: `delay` is the applicable time-based delay to pass into `do_after()` before the logic is ran.
|
|
/// Optional: `update_health` is an integer that will be added (or maybe subtracted if you're cruel) to our health after we eat something. Passed into `adjust_health()` so make sure what you pass in is accurate.
|
|
/mob/living/basic/morph/proc/eat(atom/movable/eatable, delay = 0 SECONDS, update_health = 0)
|
|
if(QDELETED(eatable) || eatable.loc == src)
|
|
return FALSE
|
|
|
|
if(HAS_TRAIT(src, TRAIT_DISGUISED) && !eat_while_disguised)
|
|
balloon_alert(src, "can't eat while disguised!")
|
|
return FALSE
|
|
|
|
balloon_alert(src, "eating...")
|
|
if((delay > 0 SECONDS) && !do_after(src, delay, target = eatable))
|
|
return FALSE
|
|
|
|
visible_message(span_warning("[src] swallows [eatable] whole!"))
|
|
eatable.forceMove(src)
|
|
if(update_health != 0)
|
|
adjust_health(update_health)
|
|
|
|
return TRUE
|
|
|
|
/// No fleshed out AI implementation, just something that make these fellers seem lively if they're just dropped into a station.
|
|
/// Only real human-powered intelligence is capable of playing prop hunt in SS13 (until further notice).
|
|
/datum/ai_controller/basic_controller/morph
|
|
blackboard = list(
|
|
BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
|
|
)
|
|
|
|
ai_movement = /datum/ai_movement/basic_avoidance
|
|
idle_behavior = /datum/idle_behavior/idle_random_walk
|
|
|
|
planning_subtrees = list(
|
|
/datum/ai_planning_subtree/target_retaliate,
|
|
/datum/ai_planning_subtree/simple_find_target,
|
|
/datum/ai_planning_subtree/attack_obstacle_in_path,
|
|
/datum/ai_planning_subtree/basic_melee_attack_subtree,
|
|
)
|