[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>
This commit is contained in:
SkyratBot
2023-08-12 17:58:09 -04:00
committed by GitHub
co-authored by san7890
parent 1584a285d0
commit 94c3d31fab
16 changed files with 337 additions and 219 deletions
@@ -42,3 +42,6 @@
///From /datum/action/vehicle/sealed/mecha/mech_toggle_safeties/proc/update_action_icon(): ()
#define COMSIG_MECH_SAFETIES_TOGGLE "mech_safeties_toggle"
/// From /datum/action/cooldown/mob_cooldown/assume_form/proc/assume_appearances(), sent to the action owner: (atom/movable/target)
#define COMSIG_ACTION_DISGUISED_APPEARANCE "mob_ability_disguise_appearance"
@@ -8,7 +8,7 @@
#define COMSIG_CLICK "atom_click"
///from base of atom/ShiftClick(): (/mob)
#define COMSIG_CLICK_SHIFT "shift_click"
#define COMPONENT_ALLOW_EXAMINATE (1<<0) //Allows the user to examinate regardless of client.eye.
#define COMPONENT_ALLOW_EXAMINATE (1<<0) //! Allows the user to examinate regardless of client.eye.
///from base of atom/CtrlClickOn(): (/mob)
#define COMSIG_CLICK_CTRL "ctrl_click"
///from base of atom/AltClick(): (/mob)
+2
View File
@@ -282,6 +282,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_TOXIMMUNE "toxin_immune"
/// Mob is immune to oxygen damage, does not need to breathe
#define TRAIT_NOBREATH "no_breath"
/// Mob is currently disguised as something else (like a morph being another mob or an object). Holds a reference to the thing that applied the trait.
#define TRAIT_DISGUISED "disguised"
/// Use when you want a mob to be able to metabolize plasma temporarily (e.g. plasma fixation disease symptom)
#define TRAIT_PLASMA_LOVER_METABOLISM "plasma_lover_metabolism"
#define TRAIT_EASYDISMEMBER "easy_dismember"
+2 -1
View File
@@ -339,9 +339,10 @@
/atom/proc/ShiftClick(mob/user)
var/flags = SEND_SIGNAL(user, COMSIG_CLICK_SHIFT, src)
if(flags & COMSIG_MOB_CANCEL_CLICKON)
return
if(user.client && (user.client.eye == user || user.client.eye == user.loc || flags & COMPONENT_ALLOW_EXAMINATE))
user.examinate(src)
return
/**
* Ctrl click
+87
View File
@@ -0,0 +1,87 @@
/// Allows a mob to assume the form of another item or mob.
/// Warning, this will likely shit the bricks if you add this action to anything more sophisticated than a basic mob- this isn't built for anything carbon-wise.
/datum/action/cooldown/mob_cooldown/assume_form
name = "Assume Form"
desc = "Choose something that you wish to blend into the environment as. Click on yourself to reset your appearance."
button_icon_state = "sniper_zoom"
background_icon_state = "bg_alien"
overlay_icon_state = "bg_alien_border"
ranged_mousepointer = 'icons/effects/mouse_pointers/supplypod_target.dmi'
check_flags = AB_CHECK_CONSCIOUS
cooldown_time = 1.5 SECONDS
/// Stuff that we can not disguise as.
var/static/list/blacklist_typecache = typecacheof(list(
/atom/movable/screen,
/obj/effect,
/obj/energy_ball,
/obj/narsie,
/obj/singularity,
))
/datum/action/cooldown/mob_cooldown/assume_form/Grant(mob/grant_to)
. = ..()
RegisterSignal(owner, COMSIG_LIVING_DEATH, PROC_REF(reset_appearances))
/datum/action/cooldown/mob_cooldown/assume_form/Remove(mob/remove_from)
reset_appearances()
UnregisterSignal(owner, COMSIG_LIVING_DEATH)
return ..()
/datum/action/cooldown/mob_cooldown/assume_form/Activate(atom/target_atom)
StartCooldown(360 SECONDS, 360 SECONDS)
determine_intent(target_atom)
StartCooldown()
return TRUE
/// Rapid proc to test if we can assume the form of a given atom. Returns TRUE if we can, FALSE if we can't. Done like this so we can be nice and explicit.
/datum/action/cooldown/mob_cooldown/assume_form/proc/can_assume_form(atom/target_atom)
if(is_type_in_typecache(target_atom, blacklist_typecache) || (!isobj(target_atom) && !ismob(target_atom)))
return FALSE
return TRUE
/// Determines what our user meant by their action. If they clicked on themselves, we reset our appearance. Otherwise, we assume the appearance of the clicked-on item.
/datum/action/cooldown/mob_cooldown/assume_form/proc/determine_intent(atom/target_atom)
if(!can_assume_form(target_atom))
return
if(target_atom == owner)
reset_appearances()
return
assume_appearances(target_atom)
/// Assumes the appearance of a desired movable and applies it to our mob. Target is the movable in question.
/datum/action/cooldown/mob_cooldown/assume_form/proc/assume_appearances(atom/movable/target_atom)
owner.appearance = target_atom.appearance
owner.copy_overlays(target_atom)
owner.alpha = max(target_atom.alpha, 150) //fucking chameleons
owner.transform = initial(target_atom.transform)
owner.pixel_x = target_atom.base_pixel_x
owner.pixel_y = target_atom.base_pixel_y
// important: do this at the very end because we might have SIGNAL_ADDTRAIT for this on the mob that's dependent on the above logic
SEND_SIGNAL(owner, COMSIG_ACTION_DISGUISED_APPEARANCE, target_atom)
ADD_TRAIT(owner, TRAIT_DISGUISED, REF(src))
/// Resets the appearances of the mob to the default.
/datum/action/cooldown/mob_cooldown/assume_form/proc/reset_appearances()
SIGNAL_HANDLER
if(!HAS_TRAIT(owner, TRAIT_DISGUISED))
return // in case we're being invoked on death and we aren't disguised (or we just click on ourselves randomly), no need to do this additional work.
owner.animate_movement = SLIDE_STEPS
owner.maptext = null
owner.alpha = initial(owner.alpha)
owner.color = initial(owner.color)
owner.desc = initial(owner.desc)
owner.name = initial(owner.name)
owner.icon = initial(owner.icon)
owner.icon_state = initial(owner.icon_state)
owner.cut_overlays()
// important: do this very end because we might have SIGNAL_REMOVETRAIT for this on the mob that's dependent on the above logic
REMOVE_TRAIT(owner, TRAIT_DISGUISED, REF(src))
+1 -1
View File
@@ -314,7 +314,7 @@
stage3 = list(span_danger("Your appendages are melting away."), span_danger("Your limbs begin to lose their shape."))
stage4 = list(span_danger("You're ravenous."))
stage5 = list(span_danger("You have become a morph."))
new_form = /mob/living/simple_animal/hostile/morph
new_form = /mob/living/basic/morph
infectable_biotypes = MOB_ORGANIC|MOB_MINERAL|MOB_UNDEAD //magic!
transformed_antag_datum = /datum/antagonist/morph
+4 -4
View File
@@ -255,18 +255,19 @@
/mob/living/basic/cow,
/mob/living/basic/cow/wisdom,
/mob/living/basic/crab,
/mob/living/basic/spider/giant,
/mob/living/basic/spider/giant/hunter,
/mob/living/basic/mining/goliath,
/mob/living/basic/headslug,
/mob/living/basic/killer_tomato,
/mob/living/basic/lizard,
/mob/living/basic/mining/goliath,
/mob/living/basic/morph,
/mob/living/basic/mouse,
/mob/living/basic/mushroom,
/mob/living/basic/pet/dog/breaddog,
/mob/living/basic/pet/dog/corgi,
/mob/living/basic/pet/dog/pug,
/mob/living/basic/pet/fox,
/mob/living/basic/spider/giant,
/mob/living/basic/spider/giant/hunter,
/mob/living/basic/statue,
/mob/living/basic/stickman,
/mob/living/basic/stickman/dog,
@@ -274,7 +275,6 @@
/mob/living/simple_animal/hostile/blob/blobbernaut/independent,
/mob/living/simple_animal/hostile/gorilla,
/mob/living/simple_animal/hostile/megafauna/dragon/lesser,
/mob/living/simple_animal/hostile/morph,
/mob/living/simple_animal/hostile/retaliate/goat,
/mob/living/simple_animal/parrot,
/mob/living/simple_animal/pet/cat,
@@ -26,13 +26,13 @@
if(isnull(spawn_loc))
return MAP_ERROR
var/mob/living/simple_animal/hostile/morph/S = new /mob/living/simple_animal/hostile/morph(spawn_loc)
player_mind.transfer_to(S)
var/mob/living/basic/morph/corpus_accipientis = new(spawn_loc)
player_mind.transfer_to(corpus_accipientis)
player_mind.set_assigned_role(SSjob.GetJobType(/datum/job/morph))
player_mind.special_role = ROLE_MORPH
player_mind.add_antag_datum(/datum/antagonist/morph)
SEND_SOUND(S, sound('sound/magic/mutate.ogg'))
message_admins("[ADMIN_LOOKUPFLW(S)] has been made into a morph by an event.")
S.log_message("was spawned as a morph by an event.", LOG_GAME)
spawned_mobs += S
SEND_SOUND(corpus_accipientis, sound('sound/magic/mutate.ogg'))
message_admins("[ADMIN_LOOKUPFLW(corpus_accipientis)] has been made into a morph by an event.")
corpus_accipientis.log_message("was spawned as a morph by an event.", LOG_GAME)
spawned_mobs += corpus_accipientis
return SUCCESSFUL_SPAWN
@@ -108,7 +108,7 @@
return TRUE
else
to_chat(H, span_warning("You're repulsed by even looking at [src]. Only a pig could force themselves to go through it."))
if(istype(mover, /mob/living/simple_animal/hostile/morph))
if(istype(mover, /mob/living/basic/morph))
return TRUE
//can't be bothered to do sloth right now, will make later
@@ -0,0 +1,213 @@
/// 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,
)
+4 -4
View File
@@ -1492,18 +1492,19 @@
/mob/living/basic/chicken,
/mob/living/basic/cow,
/mob/living/basic/crab,
/mob/living/basic/spider/giant,
/mob/living/basic/spider/giant/hunter,
/mob/living/basic/mining/goliath,
/mob/living/basic/headslug,
/mob/living/basic/killer_tomato,
/mob/living/basic/lizard,
/mob/living/basic/mining/goliath,
/mob/living/basic/morph,
/mob/living/basic/mouse,
/mob/living/basic/mushroom,
/mob/living/basic/pet/dog/breaddog,
/mob/living/basic/pet/dog/corgi,
/mob/living/basic/pet/dog/pug,
/mob/living/basic/pet/fox,
/mob/living/basic/spider/giant,
/mob/living/basic/spider/giant/hunter,
/mob/living/basic/statue,
/mob/living/basic/stickman,
/mob/living/basic/stickman/dog,
@@ -1511,7 +1512,6 @@
/mob/living/simple_animal/hostile/blob/blobbernaut/independent,
/mob/living/simple_animal/hostile/gorilla,
/mob/living/simple_animal/hostile/megafauna/dragon/lesser,
/mob/living/simple_animal/hostile/morph,
/mob/living/simple_animal/hostile/retaliate/goat,
/mob/living/simple_animal/parrot,
/mob/living/simple_animal/pet/cat,
@@ -1,195 +0,0 @@
/mob/living/simple_animal/hostile/morph
name = "morph"
real_name = "morph"
desc = "A revolting, pulsating pile of flesh."
speak_emote = list("gurgles")
emote_hear = list("gurgles")
icon = 'icons/mob/simple/animal.dmi'
icon_state = "morph"
icon_living = "morph"
icon_dead = "morph_dead"
combat_mode = TRUE
stop_automated_movement = 1
status_flags = CANPUSH
pass_flags = PASSTABLE
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
maxHealth = 150
health = 150
mob_biotypes = MOB_BEAST
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
vision_range = 1 // Only attack when target is close
wander = FALSE
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)
var/morphed = FALSE
var/melee_damage_disguised = 0
var/eat_while_disguised = FALSE
var/atom/movable/form = null
var/static/list/blacklist_typecache = typecacheof(list(
/atom/movable/screen,
/obj/singularity,
/obj/energy_ball,
/obj/narsie,
/mob/living/simple_animal/hostile/morph,
/obj/effect,
))
/mob/living/simple_animal/hostile/morph/Initialize(mapload)
. = ..()
ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT)
AddElement(/datum/element/content_barfer)
/mob/living/simple_animal/hostile/morph/examine(mob/user)
if(morphed)
. = form.examine(user)
if(get_dist(user,src) <= 3)
. += span_warning("It doesn't look quite right...")
else
. = ..()
/mob/living/simple_animal/hostile/morph/med_hud_set_health()
if(morphed && !isliving(form))
var/image/holder = hud_list[HEALTH_HUD]
holder.icon_state = null
return //we hide medical hud while morphed
..()
/mob/living/simple_animal/hostile/morph/med_hud_set_status()
if(morphed && !isliving(form))
var/image/holder = hud_list[STATUS_HUD]
holder.icon_state = null
return //we hide medical hud while morphed
..()
/mob/living/simple_animal/hostile/morph/proc/allowed(atom/movable/A) // make it into property/proc ? not sure if worth it
return !is_type_in_typecache(A, blacklist_typecache) && (isobj(A) || ismob(A))
/mob/living/simple_animal/hostile/morph/proc/eat(atom/movable/A)
if(morphed && !eat_while_disguised)
to_chat(src, span_warning("You cannot eat anything while you are disguised!"))
return FALSE
if(A && A.loc != src)
visible_message(span_warning("[src] swallows [A] whole!"))
A.forceMove(src)
return TRUE
return FALSE
/mob/living/simple_animal/hostile/morph/ShiftClickOn(atom/movable/A)
if(!stat)
if(A == src)
restore()
return
if(istype(A) && allowed(A))
assume(A)
else
to_chat(src, span_warning("You need to be conscious to transform!"))
..()
/mob/living/simple_animal/hostile/morph/proc/assume(atom/movable/target)
morphed = TRUE
form = target
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]."))
appearance = target.appearance
copy_overlays(target)
alpha = max(alpha, 150) //fucking chameleons
transform = initial(transform)
pixel_y = base_pixel_y
pixel_x = base_pixel_x
//Morphed is 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
return
/mob/living/simple_animal/hostile/morph/proc/restore()
if(!morphed)
to_chat(src, span_warning("You're already in your normal form!"))
return
morphed = FALSE
form = null
alpha = initial(alpha)
color = initial(color)
desc = initial(desc)
animate_movement = SLIDE_STEPS
maptext = null
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."))
name = initial(name)
icon = initial(icon)
icon_state = initial(icon_state)
cut_overlays()
//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 not an object
/mob/living/simple_animal/hostile/morph/death(gibbed)
if(morphed)
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--"))
restore()
..()
/mob/living/simple_animal/hostile/morph/Aggro() // automated only
..()
if(morphed)
restore()
/mob/living/simple_animal/hostile/morph/LoseAggro()
vision_range = initial(vision_range)
/mob/living/simple_animal/hostile/morph/AIShouldSleep(list/possible_targets)
. = ..()
if(.)
var/list/things = list()
for(var/atom/movable/A in view(src))
if(allowed(A))
things += A
var/atom/movable/T = pick(things)
assume(T)
/mob/living/simple_animal/hostile/morph/can_track(mob/living/user)
if(morphed)
return FALSE
return ..()
/mob/living/simple_animal/hostile/morph/AttackingTarget()
if(morphed && !melee_damage_disguised)
to_chat(src, span_warning("You can not attack while disguised!"))
return
if(isliving(target)) //Eat Corpses to regen health
var/mob/living/L = target
if(L.stat == DEAD)
if(do_after(src, 30, target = L))
if(eat(L))
adjustHealth(-50)
return
else if(isitem(target)) //Eat items just to be annoying
var/obj/item/I = target
if(!I.anchored)
if(do_after(src, 20, target = I))
eat(I)
return
return ..()
@@ -174,7 +174,6 @@
/mob/living/simple_animal/hostile/mimic/crate,
/mob/living/simple_animal/hostile/mimic/xenobio,
/mob/living/simple_animal/hostile/mining_drone,
/mob/living/simple_animal/hostile/morph,
/mob/living/simple_animal/hostile/nanotrasen,
/mob/living/simple_animal/hostile/nanotrasen/elite,
/mob/living/simple_animal/hostile/nanotrasen/ranged,
+2 -1
View File
@@ -842,6 +842,7 @@
#include "code\datums\actions\items\unzip.dm"
#include "code\datums\actions\items\vortex_recall.dm"
#include "code\datums\actions\mobs\adjust_vision.dm"
#include "code\datums\actions\mobs\assume_form.dm"
#include "code\datums\actions\mobs\blood_warp.dm"
#include "code\datums\actions\mobs\charge.dm"
#include "code\datums\actions\mobs\charge_apc.dm"
@@ -4238,6 +4239,7 @@
#include "code\modules\mob\living\basic\space_fauna\headslug.dm"
#include "code\modules\mob\living\basic\space_fauna\killer_tomato.dm"
#include "code\modules\mob\living\basic\space_fauna\lightgeist.dm"
#include "code\modules\mob\living\basic\space_fauna\morph.dm"
#include "code\modules\mob\living\basic\space_fauna\mushroom.dm"
#include "code\modules\mob\living\basic\space_fauna\spaceman.dm"
#include "code\modules\mob\living\basic\space_fauna\bear\_bear.dm"
@@ -4501,7 +4503,6 @@
#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"
#include "code\modules\mob\living\simple_animal\hostile\morph.dm"
#include "code\modules\mob\living\simple_animal\hostile\nanotrasen.dm"
#include "code\modules\mob\living\simple_animal\hostile\ooze.dm"
#include "code\modules\mob\living\simple_animal\hostile\pirate.dm"
@@ -23,7 +23,10 @@ export const AntagInfoMorph = (props, context) => {
<BlockQuote>
...a shapeshifting abomination that can eat almost anything. You
may take the form of anything you can see by{' '}
<span style={noticestyle}>shift-clicking</span> it.
<span style={noticestyle}>
using your &quot;Assume Form&quot; ability on it. Shift-clicking
the object in question will also work.
</span>{' '}
<span style={badstyle}>
&ensp;This process will alert any nearby observers.
</span>{' '}
@@ -35,11 +38,14 @@ export const AntagInfoMorph = (props, context) => {
</span>{' '}
You can attack any item or dead creature to consume it -
<span style={goodstyle}>
&ensp;creatures will restore your health.
&ensp;corpses will restore your health.
</span>{' '}
Finally, you can restore yourself to your original form while
morphed by <span style={noticestyle}>shift-clicking</span>{' '}
yourself.
morphed by{' '}
<span style={noticestyle}>
using the &quot;Assume Form&quot; ability on yourself. You can
also shift-click yourself.
</span>{' '}
</BlockQuote>
</Stack.Item>
</Stack>
@@ -0,0 +1 @@
/mob/living/simple_animal/hostile/morph : /mob/living/basic/morph{@OLD}