[MIRROR] Makes Lesser Form into one ability & unit tests it [MDB IGNORE] (#19521)

* Makes Lesser Form into one ability & unit tests it

* Update headcrab.dm

---------

Co-authored-by: Jacquerel <hnevard@gmail.com>
Co-authored-by: Gandalf <9026500+Gandalf2k15@users.noreply.github.com>
This commit is contained in:
SkyratBot
2023-03-10 06:08:19 +00:00
committed by GitHub
co-authored by Jacquerel Gandalf
parent cba75a0e0b
commit 37953d4bcf
19 changed files with 178 additions and 98 deletions
+4 -3
View File
@@ -531,11 +531,12 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block())
return
death_sound = new_race.death_sound
if (dna.species.properly_gained)
dna.species.on_species_loss(src, new_race, pref_load)
var/datum/species/old_species = dna.species
dna.species = new_race
if (old_species.properly_gained)
old_species.on_species_loss(src, new_race, pref_load)
dna.species.on_species_gain(src, old_species, pref_load)
if(ishuman(src))
qdel(language_holder)
@@ -343,7 +343,7 @@
var/cap_to = isnum(override_cap) ? override_cap : total_chem_storage
chem_charges = clamp(chem_charges + amount, 0, cap_to)
lingchemdisplay.maptext = FORMAT_CHEM_CHARGES_TEXT(chem_charges)
lingchemdisplay?.maptext = FORMAT_CHEM_CHARGES_TEXT(chem_charges)
/*
* Remove changeling powers from the current Changeling's purchased_powers list.
@@ -69,21 +69,21 @@ the same goes for Remove(). if you override Remove(), call parent or else your p
/datum/action/changeling/proc/can_sting(mob/living/user, mob/living/target)
if(!can_be_used_by(user))
return FALSE
var/datum/antagonist/changeling/c = user.mind.has_antag_datum(/datum/antagonist/changeling)
if(c.chem_charges < chemical_cost)
to_chat(user, span_warning("We require at least [chemical_cost] unit\s of chemicals to do that!"))
var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling)
if(changeling.chem_charges < chemical_cost)
user.balloon_alert(user, "needs [chemical_cost] chemicals!")
return FALSE
if(c.absorbed_count < req_dna)
to_chat(user, span_warning("We require at least [req_dna] sample\s of compatible DNA."))
if(changeling.absorbed_count < req_dna)
user.balloon_alert(user, "needs [req_dna] dna sample\s!")
return FALSE
if(c.true_absorbs < req_absorbs)
to_chat(user, span_warning("We require at least [req_absorbs] sample\s of DNA gained through our Absorb ability."))
if(changeling.true_absorbs < req_absorbs)
user.balloon_alert(user, "needs [req_absorbs] absorption\s!")
return FALSE
if(req_stat < user.stat)
to_chat(user, span_warning("We are incapacitated."))
user.balloon_alert(user, "incapacitated!")
return FALSE
if((HAS_TRAIT(user, TRAIT_DEATHCOMA)) && (!ignores_fakedeath))
to_chat(user, span_warning("We are incapacitated."))
user.balloon_alert(user, "playing dead!")
return FALSE
return TRUE
@@ -93,6 +93,6 @@ the same goes for Remove(). if you override Remove(), call parent or else your p
if(!ishuman(user))
return FALSE
if(req_human && ismonkey(user))
to_chat(user, span_warning("This ability requires you be in human form!"))
user.balloon_alert(user, "become human!")
return FALSE
return TRUE
@@ -13,14 +13,14 @@
return
if(is_absorbing)
to_chat(owner, span_warning("We are already absorbing!"))
owner.balloon_alert(owner, "already absorbing!")
return
if(!owner.pulling || !iscarbon(owner.pulling))
to_chat(owner, span_warning("We must be grabbing a creature to absorb them!"))
owner.balloon_alert(owner, "needs grab!")
return
if(owner.grab_state <= GRAB_NECK)
to_chat(owner, span_warning("We must have a tighter grip to absorb this creature!"))
owner.balloon_alert(owner, "needs tighter grip!")
return
var/mob/living/carbon/target = owner.pulling
@@ -161,7 +161,7 @@
SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("Absorb DNA", "[absorbing_iteration]"))
if(!do_after(owner, 15 SECONDS, target))
to_chat(owner, span_warning("Our absorption of [target] has been interrupted!"))
owner.balloon_alert(owner, "interrupted!")
is_absorbing = FALSE
return FALSE
return TRUE
@@ -37,8 +37,8 @@
active = FALSE
user.update_sight()
else
to_chat(user, "We can't adjust our eyes if we don't have any!")
return 1
user.balloon_alert(user, "no eyes!")
return TRUE
/datum/action/changeling/augmented_eyesight/Remove(mob/user) //Get rid of x-ray vision and flash protection when the user refunds this ability
@@ -10,7 +10,7 @@
/datum/action/changeling/biodegrade/sting_action(mob/living/carbon/human/user)
var/used = FALSE // only one form of shackles removed per use
if(!HAS_TRAIT(user, TRAIT_RESTRAINED) && !user.legcuffed && isopenturf(user.loc))
to_chat(user, span_warning("We are already free!"))
user.balloon_alert(user, "already free!")
return FALSE
if(user.handcuffed)
@@ -63,7 +63,7 @@
/datum/action/changeling/fakedeath/can_sting(mob/living/user)
if(HAS_TRAIT_FROM(user, TRAIT_DEATHCOMA, "changeling") && !revive_ready)
to_chat(user, span_warning("We are already reviving."))
user.balloon_alert(user, "already reviving!")
return
if(!user.stat && !revive_ready) //Confirmation for living changelings if they want to fake their death
switch(tgui_alert(usr,"Are we sure we wish to fake our own death?", "Feign Death", list("Yes", "No")))
@@ -11,7 +11,7 @@
//Can be used whilst unconscious.
/datum/action/changeling/fleshmend/sting_action(mob/living/user)
if(user.has_status_effect(/datum/status_effect/fleshmend))
to_chat(user, span_warning("We are already fleshmending!"))
user.balloon_alert(user, "already fleshmending!")
return
..()
to_chat(user, span_notice("We begin to heal rapidly."))
@@ -5,14 +5,17 @@
button_icon_state = "lesser_form"
chemical_cost = 5
dna_cost = 1
req_human = TRUE
/// Whether to allow the transformation animation to play
var/transform_instantly = FALSE
/datum/action/changeling/lesserform/Grant(mob/granted_to)
. = ..()
RegisterSignal(granted_to, COMSIG_HUMAN_MONKEYIZE, PROC_REF(swap_powers))
if (!owner)
return
RegisterSignals(granted_to, list(COMSIG_HUMAN_MONKEYIZE, COMSIG_MONKEY_HUMANIZE), PROC_REF(changed_form))
/datum/action/changeling/lesserform/Remove(mob/remove_from)
UnregisterSignal(remove_from, COMSIG_HUMAN_MONKEYIZE)
UnregisterSignal(remove_from, list(COMSIG_HUMAN_MONKEYIZE, COMSIG_MONKEY_HUMANIZE))
return ..()
//Transform into a monkey.
@@ -20,34 +23,54 @@
if(!user || user.notransform)
return FALSE
..()
to_chat(user, span_warning("Our genes cry out!"))
user.monkeyize()
return ismonkey(user) ? unmonkey(user) : become_monkey(user)
/// Stop being a monkey
/datum/action/changeling/lesserform/proc/unmonkey(mob/living/carbon/human/user)
if(user.movement_type & VENTCRAWLING)
user.balloon_alert(user, "can't transform in pipes!")
return FALSE
var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling)
var/datum/changeling_profile/chosen_form = select_form(changeling, user)
if(!chosen_form)
return FALSE
to_chat(user, span_notice("We transform our appearance."))
var/datum/dna/chosen_dna = chosen_form.dna
var/datum/species/chosen_species = chosen_dna.species
user.humanize(species = chosen_species, instant = transform_instantly)
changeling.transform(user, chosen_form)
user.regenerate_icons()
return TRUE
/**
* Called on COMSIG_HUMAN_MONKEYIZE
* Handles giving the new human force ability and removing ourselves
*
* Args:
* source - Human user who is now turning into a monkey
*/
/datum/action/changeling/lesserform/proc/swap_powers(mob/living/carbon/source)
/// Returns the form to transform back into, automatically selects your only profile if you only have one
/datum/action/changeling/lesserform/proc/select_form(datum/antagonist/changeling/changeling, mob/living/carbon/human/user)
if (!changeling)
return
if (length(changeling.stored_profiles) == 1)
return changeling.first_profile
return changeling?.select_dna()
/// Become a monkey
/datum/action/changeling/lesserform/proc/become_monkey(mob/living/carbon/human/user)
to_chat(user, span_warning("Our genes cry out!"))
user.monkeyize(instant = transform_instantly)
return TRUE
/// Called when you become a human or monkey, whether or not it was voluntary
/datum/action/changeling/lesserform/proc/changed_form()
SIGNAL_HANDLER
build_all_button_icons(update_flags = UPDATE_BUTTON_NAME | UPDATE_BUTTON_ICON)
var/datum/antagonist/changeling/changeling = source.mind.has_antag_datum(/datum/antagonist/changeling)
// Drops all flesh disguise items after monkeyizing, because they don't drop automatically like real clothing.
for(var/slot in changeling.slot2type)
if(istype(source.vars[slot], changeling.slot2type[slot]))
qdel(source.vars[slot])
for(var/datum/scar/iter_scar as anything in source.all_scars)
if(iter_scar.fake)
qdel(iter_scar)
source.regenerate_icons()
/datum/action/changeling/lesserform/update_button_name(atom/movable/screen/movable/action_button/button, force)
if (ismonkey(owner))
name = "Human Form"
desc = "We change back into a human. Costs 5 chemicals."
else
name = initial(name)
desc = initial(desc)
return ..()
var/datum/action/changeling/humanform/from_monkey/human_form_ability = new()
changeling.purchased_powers += human_form_ability
changeling.purchased_powers -= src
human_form_ability.Grant(source)
Remove(source)
qdel(src)
/datum/action/changeling/lesserform/apply_button_icon(atom/movable/screen/movable/action_button/current_button, force)
button_icon_state = ismonkey(owner) ? "human_form" : initial(button_icon_state)
return ..()
@@ -21,10 +21,32 @@
var/weapon_type
var/weapon_name_simple
/datum/action/changeling/weapon/Grant(mob/granted_to)
. = ..()
if (!owner || !req_human)
return
RegisterSignal(granted_to, COMSIG_HUMAN_MONKEYIZE, PROC_REF(became_monkey))
/datum/action/changeling/weapon/Remove(mob/remove_from)
UnregisterSignal(remove_from, COMSIG_HUMAN_MONKEYIZE)
unequip_held(remove_from)
return ..()
/// Remove weapons if we become a monkey
/datum/action/changeling/weapon/proc/became_monkey(mob/source)
SIGNAL_HANDLER
unequip_held(source)
/// Removes weapon if it exists, returns true if we removed something
/datum/action/changeling/weapon/proc/unequip_held(mob/user)
var/found_weapon = FALSE
for(var/obj/item/held in user.held_items)
found_weapon = check_weapon(user, held) || found_weapon
return found_weapon
/datum/action/changeling/weapon/try_to_sting(mob/user, mob/target)
for(var/obj/item/I in user.held_items)
if(check_weapon(user, I))
return
if (unequip_held(user))
return
..(user, target)
/datum/action/changeling/weapon/proc/check_weapon(mob/user, obj/item/hand_item)
@@ -34,15 +56,15 @@
playsound(user, 'sound/effects/blobattack.ogg', 30, TRUE)
user.visible_message(span_warning("With a sickening crunch, [user] reforms [user.p_their()] [weapon_name_simple] into an arm!"), span_notice("We assimilate the [weapon_name_simple] back into our body."), "<span class='italics>You hear organic matter ripping and tearing!</span>")
user.update_held_items()
return 1
return TRUE
/datum/action/changeling/weapon/sting_action(mob/living/carbon/user)
var/obj/item/held = user.get_active_held_item()
if(held && !user.dropItemToGround(held))
to_chat(user, span_warning("[held] is stuck to your hand, you cannot grow a [weapon_name_simple] over it!"))
user.balloon_alert(user, "hand occupied!")
return
if(!istype(user))
to_chat(user, span_warning("You can't do that in this state!"))
user.balloon_alert(user, "wrong shape!")
return
..()
var/limb_regen = 0
@@ -59,11 +81,6 @@
playsound(user, 'sound/effects/blobattack.ogg', 30, TRUE)
return W
/datum/action/changeling/weapon/Remove(mob/user)
for(var/obj/item/I in user.held_items)
check_weapon(user, I)
..()
//Parent to space suits and armor.
/datum/action/changeling/suit
@@ -80,6 +97,22 @@
var/recharge_slowdown = 0
var/blood_on_castoff = 0
/datum/action/changeling/suit/Grant(mob/granted_to)
. = ..()
if (!owner || !req_human)
return
RegisterSignal(granted_to, COMSIG_HUMAN_MONKEYIZE, PROC_REF(became_monkey))
/datum/action/changeling/suit/Remove(mob/remove_from)
UnregisterSignal(remove_from, COMSIG_HUMAN_MONKEYIZE)
check_suit(remove_from)
return ..()
/// Remove suit if we become a monkey
/datum/action/changeling/suit/proc/became_monkey()
SIGNAL_HANDLER
check_suit(owner)
/datum/action/changeling/suit/try_to_sting(mob/user, mob/target)
if(check_suit(user))
return
@@ -107,19 +140,12 @@
changeling.chem_recharge_slowdown -= recharge_slowdown
return 1
/datum/action/changeling/suit/Remove(mob/user)
if(!ishuman(user))
return
var/mob/living/carbon/human/H = user
check_suit(H)
..()
/datum/action/changeling/suit/sting_action(mob/living/carbon/human/user)
if(!user.canUnEquip(user.wear_suit))
to_chat(user, span_warning("\the [user.wear_suit] is stuck to your body, you cannot grow a [suit_name_simple] over it!"))
user.balloon_alert(user, "body occupied!")
return
if(!user.canUnEquip(user.head))
to_chat(user, span_warning("\the [user.head] is stuck on your head, you cannot grow a [helmet_name_simple] over it!"))
user.balloon_alert(user, "head occupied!")
return
..()
user.dropItemToGround(user.head)
@@ -196,24 +222,24 @@
C.attack_alien(user) //muh copypasta
else if(istype(target, /obj/machinery/door/airlock))
var/obj/machinery/door/airlock/A = target
var/obj/machinery/door/airlock/opening = target
if((!A.requiresID() || A.allowed(user)) && A.hasPower()) //This is to prevent stupid shit like hitting a door with an arm blade, the door opening because you have acces and still getting a "the airlocks motors resist our efforts to force it" message, power requirement is so this doesn't stop unpowered doors from being pried open if you have access
if((!opening.requiresID() || opening.allowed(user)) && opening.hasPower()) //This is to prevent stupid shit like hitting a door with an arm blade, the door opening because you have acces and still getting a "the airlocks motors resist our efforts to force it" message, power requirement is so this doesn't stop unpowered doors from being pried open if you have access
return
if(A.locked)
to_chat(user, span_warning("The airlock's bolts prevent it from being forced!"))
if(opening.locked)
opening.balloon_alert(user, "bolted!")
return
if(A.hasPower())
user.visible_message(span_warning("[user] jams [src] into the airlock and starts prying it open!"), span_warning("We start forcing the [A] open."), \
if(opening.hasPower())
user.visible_message(span_warning("[user] jams [src] into the airlock and starts prying it open!"), span_warning("We start forcing the [opening] open."), \
span_hear("You hear a metal screeching sound."))
playsound(A, 'sound/machines/airlock_alien_prying.ogg', 100, TRUE)
if(!do_after(user, 100, target = A))
playsound(opening, 'sound/machines/airlock_alien_prying.ogg', 100, TRUE)
if(!do_after(user, 10 SECONDS, target = opening))
return
//user.say("Heeeeeeeeeerrre's Johnny!")
user.visible_message(span_warning("[user] forces the airlock to open with [user.p_their()] [src]!"), span_warning("We force the [A] to open."), \
user.visible_message(span_warning("[user] forces the airlock to open with [user.p_their()] [src]!"), span_warning("We force the [opening] to open."), \
span_hear("You hear a metal screeching sound."))
A.open(2)
opening.open(2)
/obj/item/melee/arm_blade/dropped(mob/user)
..()
@@ -273,7 +299,7 @@
/obj/item/gun/magic/tentacle/shoot_with_empty_chamber(mob/living/user as mob|obj)
to_chat(user, span_warning("The [name] is not ready yet."))
user.balloon_alert(user, "not ready!")
/obj/item/gun/magic/tentacle/process_fire(atom/target, mob/living/user, message, params, zone_override, bonus_spread)
var/obj/projectile/tentacle/tentacle_shot = chambered.loaded_projectile //Gets the actual projectile we will fire
@@ -9,7 +9,7 @@
/datum/action/changeling/regenerate/sting_action(mob/living/user)
if(!iscarbon(user))
to_chat(user, span_notice("You have nothing to regenerate in this state!"))
user.balloon_alert(user, "nothing missing!")
return FALSE
..()
@@ -87,7 +87,7 @@
if(!selected_dna)
return
if(NOTRANSSTING in selected_dna.dna.species.species_traits)
to_chat(user, span_notice("That DNA is not compatible with changeling retrovirus!"))
user.balloon_alert(user, "incompatible DNA!")
return
..()
@@ -96,7 +96,7 @@
if(!.)
return
if((HAS_TRAIT(target, TRAIT_HUSK)) || !iscarbon(target) || (NOTRANSSTING in target.dna.species.species_traits))
to_chat(user, span_warning("Our sting appears ineffective against its DNA."))
user.balloon_alert(user, "incompatible DNA!")
return FALSE
return TRUE
@@ -131,7 +131,7 @@
if(isliving(target))
var/mob/living/L = target
if((HAS_TRAIT(L, TRAIT_HUSK)) || !L.has_dna())
to_chat(user, span_warning("Our sting appears ineffective against its DNA."))
user.balloon_alert(user, "incompatible DNA!")
return FALSE
return TRUE
@@ -208,7 +208,7 @@
/datum/action/changeling/sting/blind/sting_action(mob/user, mob/living/carbon/target)
var/obj/item/organ/internal/eyes/eyes = target.getorganslot(ORGAN_SLOT_EYES)
if(!eyes)
to_chat(user, span_notice("You prepare to sting [target], but one of the voices in your hivemind points out they have no eyes."))
user.balloon_alert(user, "no eyes!")
return FALSE
log_combat(user, target, "stung", "blind sting")
@@ -118,6 +118,9 @@ INITIALIZE_IMMEDIATE(/mob/living/carbon/human/dummy)
if(is_creating)
fully_replace_character_name(real_name, "John Doe")
/mob/living/carbon/human/consistent/domutcheck()
return // We skipped adding any mutations so this runtimes
//Inefficient pooling/caching way.
GLOBAL_LIST_EMPTY(human_dummy_list)
GLOBAL_LIST_EMPTY(dummy_mob_list)
@@ -426,7 +426,7 @@ GLOBAL_LIST_EMPTY(features_by_species)
for(var/obj/item/equipped_item in wearer.get_all_worn_items())
var/equipped_item_slot = wearer.get_slot_by_item(equipped_item)
if(!equipped_item.mob_can_equip(wearer, equipped_item_slot, bypass_equip_delay_self = TRUE, ignore_equipped = TRUE))
wearer.dropItemToGround(equipped_item)
wearer.dropItemToGround(equipped_item, force = TRUE)
/datum/species/proc/update_no_equip_flags(mob/living/carbon/wearer, new_flags)
no_equip_flags = new_flags
@@ -100,12 +100,9 @@
if(changeling_datum.can_absorb_dna(owner))
changeling_datum.add_new_profile(owner)
// SKYRAT EDIT START
var/datum/action/changeling/humanform/hf = new()
changeling_datum.purchased_powers += hf
hf.Grant(origin.current)
var/datum/action/changeling/lesserform/transform = new()
changeling_datum.purchased_powers[transform.type] = transform
changeling_datum.regain_powers()
// SKYRAT EDIT END
owner.investigate_log("has been gibbed by a changeling egg burst.", INVESTIGATE_DEATHS)
owner.gib()
+10 -3
View File
@@ -1,12 +1,16 @@
#define TRANSFORMATION_DURATION 22
/mob/living/carbon/proc/monkeyize()
/mob/living/carbon/proc/monkeyize(instant = FALSE)
if (notransform || transformation_timer)
return
if(ismonkey(src))
return
if(instant)
finish_monkeyize()
return
//Make mob invisible and spawn animation
notransform = TRUE
Paralyze(TRANSFORMATION_DURATION, ignore_canstun = TRUE)
@@ -15,7 +19,6 @@
invisibility = INVISIBILITY_MAXIMUM
new /obj/effect/temp_visual/monkeyify(loc)
transformation_timer = addtimer(CALLBACK(src, PROC_REF(finish_monkeyize)), TRANSFORMATION_DURATION, TIMER_UNIQUE)
/mob/living/carbon/proc/finish_monkeyize()
@@ -33,13 +36,17 @@
////////////////////////// Humanize //////////////////////////////
//Could probably be merged with monkeyize but other transformations got their own procs, too
/mob/living/carbon/proc/humanize(species = /datum/species/human)
/mob/living/carbon/proc/humanize(species = /datum/species/human, instant = FALSE)
if (notransform || transformation_timer)
return
if(!ismonkey(src))
return
if(instant)
finish_humanize(species)
return
//Make mob invisible and spawn animation
notransform = TRUE
Paralyze(TRANSFORMATION_DURATION, ignore_canstun = TRUE)
+1
View File
@@ -137,6 +137,7 @@
#include "json_savefile_importing.dm"
#include "keybinding_init.dm"
#include "knockoff_component.dm"
#include "lesserform.dm"
#include "limbsanity.dm"
#include "load_map_security.dm"
#include "machine_disassembly.dm"
+23
View File
@@ -0,0 +1,23 @@
/// Unit test to ensure that changelings can consistently turn from monkeys to humans and back
/datum/unit_test/lesserform
/datum/unit_test/lesserform/Run()
var/mob/living/carbon/human/changeling = allocate(/mob/living/carbon/human/consistent)
changeling.mind_initialize()
var/datum/mind/mind = changeling.mind
var/datum/antagonist/changeling/changeling_datum = mind.add_antag_datum(/datum/antagonist/changeling)
changeling_datum.adjust_chemicals(INFINITY) // We don't care about how many chemicals they have
var/datum/action/changeling/lesserform/transform_ability = new(changeling)
transform_ability.transform_instantly = TRUE
transform_ability.Grant(changeling)
transform_ability.Trigger()
TEST_ASSERT(ismonkey(changeling), "Changeling failed to turn into a monkey after voluntarily transforming using lesser form.")
changeling.humanize(instant = TRUE)
transform_ability.Trigger()
TEST_ASSERT(ismonkey(changeling), "Changeling failed to turn into a monkey after involuntarily being made into a human.")
transform_ability.Trigger()
TEST_ASSERT(!ismonkey(changeling), "Changeling failed to stop being a monkey after voluntarily transforming using lesser form.")
changeling.monkeyize(instant = TRUE)
transform_ability.Trigger()
TEST_ASSERT(!ismonkey(changeling), "Changeling failed to stop being a monkey after being involuntarily turned into one.")
-1
View File
@@ -2506,7 +2506,6 @@
#include "code\modules\antagonists\changeling\powers\fakedeath.dm"
#include "code\modules\antagonists\changeling\powers\fleshmend.dm"
#include "code\modules\antagonists\changeling\powers\headcrab.dm"
#include "code\modules\antagonists\changeling\powers\humanform.dm"
#include "code\modules\antagonists\changeling\powers\lesserform.dm"
#include "code\modules\antagonists\changeling\powers\mimic_voice.dm"
#include "code\modules\antagonists\changeling\powers\mutations.dm"