Files
Bubberstation/code/modules/mob/living/basic/space_fauna/morph.dm
T
SkyratBot 94c3d31fab [MIRROR] Refactors Morphs into Basic Mobs (there is now a swag action for morphification) [MDB IGNORE] (#23046)
* Refactors Morphs into Basic Mobs (there is now a swag action for morphification) (#77503)

## About The Pull Request

I was bored, so did this. Probably one of the neatest refactors I've
done, sorry if there's some oddities because I was experimenting with
some other stuff in this so just tell me to clean them up whenever I
can.

Anyways, morphs are basic mobs now. We are able to easily refactor the
whole "eat items and corpses" stuff in the basic mob framework, but the
whole "morph into objects and people" turned out to be a bit trickier.
That was easily rectified with a datum mob cooldown action and
copy-pasting the old code into that code, as well as doing some nice
stuff with traits and signals to ensure the one-way communication from
the action to the mob.

Old Morph AI didn't seem to be existant whatsoever, they inappropriately
leveraged some old procs and I have no idea how to make it work with new
AI. They DEFINITELY don't spawn outside of admin interference/ the event
anymore, and will always be controlled by a player, so this shouldn't be
too bad of an issue. I gave them something to seem alive just in case
though, but I think adding legitimate prop-hunt AI would be such a
laborious task that I am unwilling to do it in this PR.
## Why It's Good For The Game

If admins want to add the ability for Ian to assume the form of the HoP,
they can do that now! The datum action cooldown is quite nice for simple
and basic mobs... but it is currently not compatible with carbons. That
is not within scope for this PR, but I am dwelling on ways to extend it
to carbon but they all sound really awfully bad.

Also morphs are smarter, and we tick another simple animal in need of
refactoring off the list.
## Changelog
🆑
refactor: Morphs are now basic mobs with a nice new ability to help you
change forms rather than the old shift-click method, much more
intuitive.
admin: With the morph rework comes a new ability you can add to mobs,
"Assume Form". Feel free to add that to any simple or basic mob for le
funnies as Runtime turns into a pen or something.
/🆑

~~Does anyone know if there's a (sane) way to alias a cooldown action as
a keypress? I can't think of a good way to retain the old shift-click
functionality, because that does feel _kinda_ nice, but I think it can
be lived without.~~ I added it. Kinda fugly but whatever.

* Refactors Morphs into Basic Mobs (there is now a swag action for morphification)

---------

Co-authored-by: san7890 <the@san7890.com>
2023-08-12 17:58:09 -04:00

214 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
// 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_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/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,
)