Datumizes changelings and refactors related code (#17774)

* it's finally here

unfucks tgui

* oops, I don't want to change the mode

* charlie and moxian review

* charlie review II + some GC fixes

* rebase to fix conflicts + tgui bundle deconflict

* charlie review III + affected review

* final? tweaks
This commit is contained in:
SteelSlayer
2022-06-18 20:55:49 +01:00
committed by GitHub
parent 0dc7d74cdc
commit c3fcf3e256
62 changed files with 1100 additions and 1129 deletions
+2
View File
@@ -184,6 +184,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_NOEXAMINE "no_examine"
#define TRAIT_NOPAIN "no_pain"
#define TRAIT_FORCE_DOORS "force_doors"
#define TRAIT_AI_UNTRACKABLE "AI_untrackable"
//***** ITEM TRAITS *****//
/// Show what machine/door wires do when held.
@@ -204,6 +205,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define CULT_TRAIT "cult"
#define INNATE_TRAIT "innate"
#define VAMPIRE_TRAIT "vampire"
#define CHANGELING_TRAIT "changeling"
// unique trait sources
#define STATUE_MUTE "statue"
+13 -1
View File
@@ -57,7 +57,19 @@ GLOBAL_LIST_EMPTY(uid_log)
return null
/**
* Opens a lof of UIDs
* If the list `L` contains a datum UID who's type matches `D`'s type, returns the UID of that datum in the list. Otherwise returns null.
*/
/proc/is_type_in_UID_list(datum/D, list/L)
if(!length(L))
return
for(var/datum_UID in L)
var/datum/A = locateUID(datum_UID)
if(istype(D, A))
return datum_UID
/**
* Opens a log of UIDs
*
* In-round ability to view what has created a UID, and how many times a UID for that path has been declared
*/
+1
View File
@@ -66,6 +66,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_NOPAIN" = TRAIT_NOPAIN,
"TRAIT_FORCE_DOORS" = TRAIT_FORCE_DOORS,
"TRAIT_EMOTE_MUTE" = TRAIT_EMOTE_MUTE,
"TRAIT_AI_UNTRACKABLE" = TRAIT_AI_UNTRACKABLE
),
/obj/item = list(
"TRAIT_SHOW_WIRE_INFO" = TRAIT_SHOW_WIRE_INFO,
+2 -2
View File
@@ -33,8 +33,8 @@
screen_loc = ui_lingstingdisplay
/obj/screen/ling/sting/Click()
var/mob/living/carbon/U = usr
U.unset_sting()
var/datum/antagonist/changeling/cling = usr.mind.has_antag_datum(/datum/antagonist/changeling)
cling?.chosen_sting?.unset_sting()
/obj/screen/ling/chems
name = "chemical storage"
+17 -35
View File
@@ -51,7 +51,6 @@
var/miming = 0 // Mime's vow of silence
var/list/antag_datums
var/datum/changeling/changeling //changeling holder
var/linglink
var/antag_hud_icon_state = null //this mind's ANTAG_HUD should have this icon_state
@@ -278,12 +277,15 @@
/datum/mind/proc/memory_edit_changeling(mob/living/carbon/human/H)
. = _memory_edit_header("changeling", list("traitorchan"))
if(src in SSticker.mode.changelings)
var/datum/antagonist/changeling/cling = has_antag_datum(/datum/antagonist/changeling)
if(cling)
. += "<b><font color='red'>CHANGELING</font></b>|<a href='?src=[UID()];changeling=clear'>no</a>"
if(objectives.len==0)
if(!length(cling.objectives))
. += "<br>Objectives are empty! <a href='?src=[UID()];changeling=autoobjectives'>Randomize!</a>"
if(changeling && changeling.absorbed_dna.len && (current.real_name != changeling.absorbed_dna[1]))
. += "<br><a href='?src=[UID()];changeling=initialdna'>Transform to initial appearance.</a>"
if(length(cling.absorbed_dna))
var/datum/dna/DNA = cling.absorbed_dna[1]
if(current.real_name != DNA.real_name)
. += "<br><a href='?src=[UID()];changeling=initialdna'>Transform to initial appearance.</a>"
else
. += "<a href='?src=[UID()];changeling=changeling'>changeling</a>|<b>NO</b>"
@@ -952,41 +954,30 @@
else if(href_list["changeling"])
switch(href_list["changeling"])
if("clear")
if(src in SSticker.mode.changelings)
SSticker.mode.changelings -= src
special_role = null
if(changeling)
current.remove_changeling_powers()
qdel(current.middleClickOverride) // In case the old changeling has a targeted sting prepared (`datum/middleClickOverride`), delete it.
current.middleClickOverride = null
qdel(changeling)
changeling = null
SSticker.mode.update_change_icons_removed(src)
to_chat(current, "<FONT color='red' size = 3><B>You grow weak and lose your powers! You are no longer a changeling and are stuck in your current form!</B></FONT>")
if(ischangeling(current))
remove_antag_datum(/datum/antagonist/changeling)
log_admin("[key_name(usr)] has de-changelinged [key_name(current)]")
message_admins("[key_name_admin(usr)] has de-changelinged [key_name_admin(current)]")
if("changeling")
if(!(src in SSticker.mode.changelings))
SSticker.mode.changelings += src
SSticker.mode.grant_changeling_powers(current)
SSticker.mode.update_change_icons_added(src)
special_role = SPECIAL_ROLE_CHANGELING
SEND_SOUND(current, sound('sound/ambience/antag/ling_aler.ogg'))
to_chat(current, "<B><font color='red'>Your powers have awoken. A flash of memory returns to us... we are a changeling!</font></B>")
if(!ischangeling(current))
add_antag_datum(/datum/antagonist/changeling)
to_chat(current, "<span class='biggerdanger'>Your powers have awoken. A flash of memory returns to us... we are a changeling!</span>")
log_admin("[key_name(usr)] has changelinged [key_name(current)]")
message_admins("[key_name_admin(usr)] has changelinged [key_name_admin(current)]")
if("autoobjectives")
SSticker.mode.forge_changeling_objectives(src)
var/datum/antagonist/changeling/cling = has_antag_datum(/datum/antagonist/changeling)
cling.give_objectives()
to_chat(usr, "<span class='notice'>The objectives for changeling [key] have been generated. You can edit them and announce manually.</span>")
log_admin("[key_name(usr)] has automatically forged objectives for [key_name(current)]")
message_admins("[key_name_admin(usr)] has automatically forged objectives for [key_name_admin(current)]")
if("initialdna")
if(!changeling || !changeling.absorbed_dna.len)
var/datum/antagonist/changeling/cling = has_antag_datum(/datum/antagonist/changeling)
if(!cling || !length(cling.absorbed_dna))
to_chat(usr, "<span class='warning'>Resetting DNA failed!</span>")
else
current.dna = changeling.absorbed_dna[1]
current.dna = cling.absorbed_dna[1]
current.real_name = current.dna.real_name
current.UpdateAppearance()
domutcheck(current)
@@ -1608,15 +1599,6 @@
if(!has_antag_datum(/datum/antagonist/vampire))
add_antag_datum(/datum/antagonist/vampire)
/datum/mind/proc/make_Changeling()
if(!(src in SSticker.mode.changelings))
SSticker.mode.changelings += src
SSticker.mode.grant_changeling_powers(current)
special_role = SPECIAL_ROLE_CHANGELING
SSticker.mode.forge_changeling_objectives(src)
SSticker.mode.greet_changeling(src)
SSticker.mode.update_change_icons_added(src)
/datum/mind/proc/make_Overmind()
if(!(src in SSticker.mode.blob_overminds))
SSticker.mode.blob_overminds += src
+11 -7
View File
@@ -358,34 +358,38 @@
tick_interval = 4 SECONDS
alert_type = null
var/stacks = 0
/// A reference to the changeling's changeling antag datum.
var/datum/antagonist/changeling/cling
/datum/status_effect/speedlegs/on_apply()
ADD_TRAIT(owner, TRAIT_GOTTAGOFAST, "changeling")
cling = owner.mind.has_antag_datum(/datum/antagonist/changeling)
ADD_TRAIT(owner, TRAIT_GOTTAGOFAST, CHANGELING_TRAIT)
return TRUE
/datum/status_effect/speedlegs/tick()
if(owner.stat || owner.staminaloss >= 90 || owner.mind.changeling.chem_charges <= (stacks + 1) * 3)
if(owner.stat || owner.staminaloss >= 90 || cling.chem_charges <= (stacks + 1) * 3)
to_chat(owner, "<span class='danger'>Our muscles relax without the energy to strengthen them.</span>")
owner.Weaken(6 SECONDS)
owner.remove_status_effect(STATUS_EFFECT_SPEEDLEGS)
qdel(src)
else
stacks++
owner.mind.changeling.chem_charges -= stacks * 3 //At first the changeling may regenerate chemicals fast enough to nullify fatigue, but it will stack
cling.chem_charges -= stacks * 3 //At first the changeling may regenerate chemicals fast enough to nullify fatigue, but it will stack
if(stacks == 7) //Warning message that the stacks are getting too high
to_chat(owner, "<span class='warning'>Our legs are really starting to hurt...</span>")
/datum/status_effect/speedlegs/before_remove()
if(stacks < 3 && !(owner.stat || owner.staminaloss >= 90 || owner.mind.changeling.chem_charges <= (stacks + 1) * 3)) //We don't want people to turn it on and off fast, however, we need it forced off if the 3 later conditions are met.
if(stacks < 3 && !(owner.stat || owner.staminaloss >= 90 || cling.chem_charges <= (stacks + 1) * 3)) //We don't want people to turn it on and off fast, however, we need it forced off if the 3 later conditions are met.
to_chat(owner, "<span class='notice'>Our muscles just tensed up, they will not relax so fast.</span>")
return FALSE
return TRUE
/datum/status_effect/speedlegs/on_remove()
REMOVE_TRAIT(owner, TRAIT_GOTTAGOFAST, "changeling")
REMOVE_TRAIT(owner, TRAIT_GOTTAGOFAST, CHANGELING_TRAIT)
if(!owner.IsWeakened())
to_chat(owner, "<span class='notice'>Our muscles relax.</span>")
if(stacks >= 7)
to_chat(owner, "<span class='danger'>We collapse in exhaustion.</span>")
owner.Weaken(6 SECONDS)
owner.emote("gasp")
owner.mind.changeling.geneticdamage += stacks
cling.genetic_damage += stacks
cling = null
+1 -1
View File
@@ -1755,7 +1755,7 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
else if(!AT)
to_chat(usr, "<span class='warning'>Error: Embedded Syndicate credentials not found.</span>")
return
else if(mind.changeling || mind.has_antag_datum(/datum/antagonist/vampire))
else if(ischangeling(usr) || mind.has_antag_datum(/datum/antagonist/vampire))
to_chat(usr, "<span class='warning'>Error: Embedded Syndicate credentials contain an abnormal signature. Aborting.</span>")
return
+31 -258
View File
@@ -1,5 +1,5 @@
#define LING_FAKEDEATH_TIME 400 //40 seconds
#define LING_DEAD_GENETICDAMAGE_HEAL_CAP 50 //The lowest value of geneticdamage handle_changeling() can take it to while dead.
#define LING_FAKEDEATH_TIME 40 SECONDS
#define LING_DEAD_GENETIC_DAMAGE_HEAL_CAP 50 //The lowest value of genetic_damage handle_changeling() can take it to while dead.
#define LING_ABSORB_RECENT_SPEECH 8 //The amount of recent spoken lines to gain on absorbing a mob
GLOBAL_LIST_INIT(possible_changeling_IDs, list("Alpha","Beta","Gamma","Delta","Epsilon","Zeta","Eta","Theta","Iota","Kappa","Lambda","Mu","Nu","Xi","Omicron","Pi","Rho","Sigma","Tau","Upsilon","Phi","Chi","Psi","Omega"))
@@ -16,25 +16,14 @@ GLOBAL_LIST_INIT(possible_changeling_IDs, list("Alpha","Beta","Gamma","Delta","E
required_players = 15
required_enemies = 1
recommended_enemies = 4
var/const/prob_int_murder_target = 50 // intercept names the assassination target half the time
var/const/prob_right_murder_target_l = 25 // lower bound on probability of naming right assassination target
var/const/prob_right_murder_target_h = 50 // upper bound on probability of naimg the right assassination target
var/const/prob_int_item = 50 // intercept names the theft target half the time
var/const/prob_right_item_l = 25 // lower bound on probability of naming right theft target
var/const/prob_right_item_h = 50 // upper bound on probability of naming the right theft target
var/const/prob_int_sab_target = 50 // intercept names the sabotage target half the time
var/const/prob_right_sab_target_l = 25 // lower bound on probability of naming right sabotage target
var/const/prob_right_sab_target_h = 50 // upper bound on probability of naming right sabotage target
var/const/prob_right_killer_l = 25 //lower bound on probability of naming the right operative
var/const/prob_right_killer_h = 50 //upper bound on probability of naming the right operative
var/const/prob_right_objective_l = 25 //lower bound on probability of determining the objective correctly
var/const/prob_right_objective_h = 50 //upper bound on probability of determining the objective correctly
/// The total number of changelings allowed to be picked.
var/changeling_amount = 4
/// A list containing references to the minds of soon-to-be changelings. This is seperate to avoid duplicate entries in the `changelings` list.
var/list/datum/mind/pre_changelings = list()
/datum/game_mode/changeling/Destroy(force, ...)
pre_changelings.Cut()
return ..()
/datum/game_mode/changeling/announce()
to_chat(world, "<B>The current game mode is - Changeling!</B>")
@@ -48,141 +37,30 @@ GLOBAL_LIST_INIT(possible_changeling_IDs, list("Alpha","Beta","Gamma","Delta","E
changeling_amount = 1 + round(num_players() / 10)
if(possible_changelings.len>0)
for(var/i = 0, i < changeling_amount, i++)
if(!possible_changelings.len) break
var/datum/mind/changeling = pick(possible_changelings)
possible_changelings -= changeling
changelings += changeling
changeling.restricted_roles = restricted_jobs
modePlayer += changelings
changeling.special_role = SPECIAL_ROLE_CHANGELING
..()
return 1
else
return 0
for(var/i in 1 to changeling_amount)
if(!length(possible_changelings))
break
var/datum/mind/changeling = pick_n_take(possible_changelings)
pre_changelings += changeling
changeling.restricted_roles = restricted_jobs
changeling.special_role = SPECIAL_ROLE_CHANGELING
if(!length(pre_changelings))
return FALSE
return TRUE
/datum/game_mode/changeling/post_setup()
for(var/datum/mind/changeling in changelings)
grant_changeling_powers(changeling.current)
forge_changeling_objectives(changeling)
greet_changeling(changeling)
update_change_icons_added(changeling)
for(var/datum/mind/changeling as anything in pre_changelings)
changeling.add_antag_datum(/datum/antagonist/changeling)
pre_changelings -= changeling
..()
/datum/game_mode/proc/forge_changeling_objectives(datum/mind/changeling)
//OBJECTIVES - Always absorb 5 genomes, plus random traitor objectives.
//If they have two objectives as well as absorb, they must survive rather than escape
//No escape alone because changelings aren't suited for it and it'd probably just lead to rampant robusting
//If it seems like they'd be able to do it in play, add a 10% chance to have to escape alone
var/datum/objective/absorb/absorb_objective = new
absorb_objective.owner = changeling
absorb_objective.gen_amount_goal(6, 8)
changeling.objectives += absorb_objective
if(prob(60))
var/datum/objective/steal/steal_objective = new
steal_objective.owner = changeling
steal_objective.find_target()
changeling.objectives += steal_objective
else
var/datum/objective/debrain/debrain_objective = new
debrain_objective.owner = changeling
debrain_objective.find_target()
changeling.objectives += debrain_objective
var/list/active_ais = active_ais()
if(active_ais.len && prob(4)) // Leaving this at a flat chance for now, problems with the num_players() proc due to latejoin antags.
var/datum/objective/destroy/destroy_objective = new
destroy_objective.owner = changeling
destroy_objective.find_target()
changeling.objectives += destroy_objective
else
var/datum/objective/assassinate/kill_objective = new
kill_objective.owner = changeling
kill_objective.find_target()
changeling.objectives += kill_objective
if(!(locate(/datum/objective/escape) in changeling.objectives))
var/datum/objective/escape/escape_with_identity/identity_theft = new
identity_theft.owner = changeling
identity_theft.target = kill_objective.target
if(identity_theft.target && identity_theft.target.current)
identity_theft.target_real_name = kill_objective.target.current.real_name //Whoops, forgot this.
var/mob/living/carbon/human/H = identity_theft.target.current
if(!HAS_TRAIT(H, TRAIT_GENELESS)) // For species that can't be absorbed - should default to an escape objective
identity_theft.explanation_text = "Escape on the shuttle or an escape pod with the identity of [identity_theft.target_real_name], the [identity_theft.target.assigned_role] while wearing [identity_theft.target.p_their()] identification card."
changeling.objectives += identity_theft
else
qdel(identity_theft)
if(!(locate(/datum/objective/escape) in changeling.objectives))
if(prob(70))
var/datum/objective/escape/escape_objective = new
escape_objective.owner = changeling
changeling.objectives += escape_objective
else
var/datum/objective/escape/escape_with_identity/identity_theft = new
identity_theft.owner = changeling
identity_theft.find_target()
changeling.objectives += identity_theft
return
/datum/game_mode/proc/greet_changeling(datum/mind/changeling, you_are=1)
SEND_SOUND(changeling.current, sound('sound/ambience/antag/ling_aler.ogg'))
if(you_are)
to_chat(changeling.current, "<span class='danger'>You are a changeling!</span>")
to_chat(changeling.current, "<span class='danger'>Use say \":g message\" to communicate with your fellow changelings. Remember: you get all of their absorbed DNA if you absorb them.</span>")
to_chat(changeling.current, "<B>You must complete the following tasks:</B>")
if(changeling.current.mind)
if(changeling.current.mind.assigned_role == "Clown")
to_chat(changeling.current, "You have evolved beyond your clownish nature, allowing you to wield weapons without harming yourself.")
changeling.current.dna.SetSEState(GLOB.clumsyblock, FALSE)
singlemutcheck(changeling.current, GLOB.clumsyblock, MUTCHK_FORCED)
var/datum/action/innate/toggle_clumsy/A = new
A.Grant(changeling.current)
var/obj_count = 1
for(var/datum/objective/objective in changeling.objectives)
to_chat(changeling.current, "<B>Objective #[obj_count]</B>: [objective.explanation_text]")
obj_count++
to_chat(changeling.current, "<span class='motd'>For more information, check the wiki page: ([GLOB.configuration.url.wiki_url]/index.php/Changeling)</span>")
return
/datum/game_mode/proc/remove_changeling(datum/mind/changeling_mind)
if(changeling_mind in changelings)
changelings -= changeling_mind
changeling_mind.current.remove_changeling_powers()
changeling_mind.memory = ""
changeling_mind.special_role = null
if(issilicon(changeling_mind.current))
to_chat(changeling_mind.current, "<span class='userdanger'>You have been robotized!</span>")
to_chat(changeling_mind.current, "<span class='danger'>You must obey your silicon laws and master AI above all else. Your objectives will consider you to be dead.</span>")
else
to_chat(changeling_mind.current, "<FONT color='red' size = 3><B>You lose your powers! You are no longer a changeling and are stuck in your current form!</B></FONT>")
update_change_icons_removed(changeling_mind)
/datum/game_mode/proc/update_change_icons_added(datum/mind/changeling)
var/datum/atom_hud/antag/linghud = GLOB.huds[ANTAG_HUD_CHANGELING]
linghud.join_hud(changeling.current)
set_antag_hud(changeling.current, "hudchangeling")
/datum/game_mode/proc/update_change_icons_removed(datum/mind/changeling)
var/datum/atom_hud/antag/linghud = GLOB.huds[ANTAG_HUD_CHANGELING]
linghud.leave_hud(changeling.current)
set_antag_hud(changeling.current, null)
/datum/game_mode/proc/grant_changeling_powers(mob/living/carbon/changeling_mob)
if(!istype(changeling_mob))
return
changeling_mob.make_changeling()
/datum/game_mode/proc/auto_declare_completion_changeling()
if(changelings.len)
if(length(changelings))
var/text = "<FONT size = 3><B>The changelings were:</B></FONT>"
for(var/datum/mind/changeling in changelings)
var/changelingwin = 1
var/changelingwin = TRUE
text += "<br>[changeling.key] was [changeling.name] ("
if(changeling.current)
@@ -194,12 +72,13 @@ GLOBAL_LIST_INIT(possible_changeling_IDs, list("Alpha","Beta","Gamma","Delta","E
text += " as [changeling.current.real_name]"
else
text += "body destroyed"
changelingwin = 0
changelingwin = FALSE
text += ")"
//Removed sanity if(changeling) because we -want- a runtime to inform us that the changelings list is incorrect and needs to be fixed.
text += "<br><b>Changeling ID:</b> [changeling.changeling.changelingID]."
text += "<br><b>Genomes Extracted:</b> [changeling.changeling.absorbedcount]"
var/datum/antagonist/changeling/cling = changeling.has_antag_datum(/datum/antagonist/changeling)
text += "<br><b>Changeling ID:</b> [cling.changelingID]."
text += "<br><b>Genomes Extracted:</b> [cling.absorbed_count]"
var/list/all_objectives = changeling.get_all_objectives()
@@ -232,110 +111,4 @@ GLOBAL_LIST_INIT(possible_changeling_IDs, list("Alpha","Beta","Gamma","Delta","E
to_chat(world, text)
return 1
/datum/changeling //stores changeling powers, changeling recharge thingie, changeling absorbed DNA and changeling ID (for changeling hivemind)
var/list/absorbed_dna = list()
var/list/absorbed_languages = list()
var/list/protected_dna = list() //DNA that is not lost when capacity is otherwise full.
var/dna_max = 5 //How many total DNA strands the changeling can store for transformation.
var/absorbedcount = 1 //Would require at least 1 sample to take on the form of a human
var/chem_charges = 20
var/chem_recharge_rate = 1
var/chem_recharge_slowdown = 0
var/chem_storage = 75
var/sting_range = 2
var/changelingID = "Changeling"
var/geneticdamage = 0
var/isabsorbing = 0
var/islinking = 0
var/geneticpoints = 10
var/purchasedpowers = list()
var/mimicing = ""
var/canrespec = FALSE //set to TRUE in absorb.dm
var/changeling_speak = 0
var/datum/dna/chosen_dna
var/datum/action/changeling/sting/chosen_sting
var/regenerating = FALSE
/datum/changeling/New(gender=FEMALE)
..()
var/honorific
if(gender == FEMALE)
honorific = "Ms."
else
honorific = "Mr."
if(GLOB.possible_changeling_IDs.len)
changelingID = pick(GLOB.possible_changeling_IDs)
GLOB.possible_changeling_IDs -= changelingID
changelingID = "[honorific] [changelingID]"
else
changelingID = "[honorific] [rand(1,999)]"
/datum/changeling/proc/regenerate(mob/living/carbon/the_ling)
if(istype(the_ling))
if(the_ling.stat == DEAD)
chem_charges = min(max(0, chem_charges + chem_recharge_rate - chem_recharge_slowdown), (chem_storage*0.5))
geneticdamage = max(LING_DEAD_GENETICDAMAGE_HEAL_CAP,geneticdamage-1)
else //not dead? no chem/geneticdamage caps.
chem_charges = min(max(0, chem_charges + chem_recharge_rate - chem_recharge_slowdown), chem_storage)
geneticdamage = max(0, geneticdamage-1)
/datum/changeling/proc/GetDNA(dna_owner)
for(var/datum/dna/DNA in (absorbed_dna + protected_dna))
if(dna_owner == DNA.real_name)
return DNA
/datum/changeling/proc/find_dna(datum/dna/tDNA)
for(var/datum/dna/D in (absorbed_dna + protected_dna))
if(tDNA.unique_enzymes == D.unique_enzymes && tDNA.uni_identity == D.uni_identity && tDNA.species.type == D.species.type)
return D
return null
/datum/changeling/proc/has_dna(datum/dna/tDNA)
if(find_dna(tDNA))
return 1
return 0
// A changeling's DNA is "stale" if their current form's DNA is the oldest DNA in a full list
/datum/changeling/proc/using_stale_dna(mob/living/carbon/user)
var/current_dna = find_dna(user.dna)
if(absorbed_dna.len < dna_max)
return 0 // Still more room for DNA
if(!current_dna || !(current_dna in absorbed_dna))
return 1 // Oops, our current DNA was somehow not absorbed; force a transformation
if(absorbed_dna[1] == current_dna)
return 1 // Oldest DNA is the current DNA
return 0
/datum/changeling/proc/trim_dna()
absorbed_dna -= null
if(absorbed_dna.len > dna_max)
absorbed_dna.Cut(1, (absorbed_dna.len - dna_max) + 1)
/datum/changeling/proc/can_absorb_dna(mob/living/carbon/user, mob/living/carbon/target)
if(using_stale_dna(user))//If our current DNA is the stalest, we gotta ditch it.
to_chat(user, "<span class='warning'>The DNA we are wearing is stale. Transform and try again.</span>")
return
if(!target || !target.dna)
to_chat(user, "<span class='warning'>This creature does not have any DNA.</span>")
return
var/mob/living/carbon/human/T = target
if(!istype(T) || issmall(T))
to_chat(user, "<span class='warning'>[T] is not compatible with our biology.</span>")
return
if(HAS_TRAIT(T, TRAIT_BADDNA) || HAS_TRAIT(T, TRAIT_HUSK) || HAS_TRAIT(T, TRAIT_SKELETONIZED))
to_chat(user, "<span class='warning'>DNA of [target] is ruined beyond usability!</span>")
return
if(HAS_TRAIT(T, TRAIT_GENELESS))
to_chat(user, "<span class='warning'>This creature does not have DNA!</span>")
return
if(has_dna(target.dna))
to_chat(user, "<span class='warning'>We already have this DNA in storage!</span>")
return 1
return TRUE
@@ -1,99 +0,0 @@
/*
* Don't use the apostrophe in name or desc. Causes script errors.
* TODO: combine atleast some of the functionality with /proc_holder/spell
*/
/datum/action/changeling
name = "Prototype Sting"
desc = "" // Fluff
background_icon_state = "bg_changeling"
var/helptext = "" // Details
var/chemical_cost = 0 // negative chemical cost is for passive abilities (chemical glands)
var/dna_cost = -1 //cost of the sting in dna points. 0 = auto-purchase, -1 = cannot be purchased
var/req_dna = 0 //amount of dna needed to use this ability. Changelings always have atleast 1
var/req_human = 0 //if you need to be human to use this ability
var/req_stat = CONSCIOUS // CONSCIOUS, UNCONSCIOUS or DEAD
var/genetic_damage = 0 // genetic damage caused by using the sting. Nothing to do with cloneloss.
var/max_genetic_damage = 100 // hard counter for spamming abilities. Not used/balanced much yet.
var/always_keep = 0 // important for abilities like regenerate that screw you if you lose them.
var/needs_button = TRUE // for passive abilities like hivemind that dont need a button
var/active = FALSE // used by a few powers that toggle
/*
changeling code now relies on on_purchase to grant powers.
if you override it, MAKE SURE you call parent or it will not be usable
the same goes for Remove(). if you override Remove(), call parent or else your power wont be removed on respec
*/
/datum/action/changeling/proc/on_purchase(mob/user)
if(needs_button)
Grant(user)
/datum/action/changeling/Trigger()
var/mob/user = owner
if(!user || !user.mind || !user.mind.changeling)
return
try_to_sting(user)
/datum/action/changeling/proc/try_to_sting(mob/user, mob/target)
user.changeNext_click(5)
if(!user.mind || !user.mind.changeling)
return
if(!can_sting(user, target))
return
var/datum/changeling/c = user.mind.changeling
if(sting_action(user, target))
sting_feedback(user, target)
take_chemical_cost(c)
/datum/action/changeling/proc/sting_action(mob/user, mob/target)
return 0
/datum/action/changeling/proc/sting_feedback(mob/user, mob/target)
return 0
/datum/action/changeling/proc/take_chemical_cost(datum/changeling/changeling)
changeling.chem_charges -= chemical_cost
changeling.geneticdamage += genetic_damage
//Fairly important to remember to return 1 on success >.<
/datum/action/changeling/proc/can_sting(mob/user, mob/target)
if(!ishuman(user)) //typecast everything from mob to carbon from this point onwards
return 0
if(req_human && (!ishuman(user) || issmall(user)))
to_chat(user, "<span class='warning'>We cannot do that in this form!</span>")
return 0
var/datum/changeling/c = user.mind.changeling
if(c.chem_charges<chemical_cost)
to_chat(user, "<span class='warning'>We require at least [chemical_cost] unit\s of chemicals to do that!</span>")
return 0
if(c.absorbedcount<req_dna)
to_chat(user, "<span class='warning'>We require at least [req_dna] sample\s of compatible DNA.</span>")
return 0
if(req_stat < user.stat)
to_chat(user, "<span class='warning'>We are incapacitated.</span>")
return 0
if(HAS_TRAIT(user, TRAIT_FAKEDEATH) && name != "Regenerate")
to_chat(user, "<span class='warning'>We are incapacitated.</span>")
return 0
if(c.geneticdamage > max_genetic_damage)
to_chat(user, "<span class='warning'>Our genomes are still reassembling. We need time to recover first.</span>")
return 0
return 1
//used in /mob/Stat()
/datum/action/changeling/proc/can_be_used_by(mob/user)
if(!ishuman(user))
return 0
if(req_human && !ishuman(user))
return 0
return 1
// Transform the target to the chosen dna. Used in transform.dm and tiny_prick.dm (handy for changes since it's the same thing done twice)
/datum/action/changeling/proc/transform_dna(mob/living/carbon/human/H, datum/dna/D)
if(!D)
return
var/changesSpecies = TRUE
if (H.dna.species.name == D.species.name)
changesSpecies = FALSE
H.change_dna(D, changesSpecies)
@@ -1,213 +0,0 @@
/// The evolution menu will be shown in the compact mode, with only powers and costs being displayed.
#define COMPACT_MODE 0
/// The evolution menu will be shown in the expanded mode, with powers, costs, and ability descriptions being displayed.
#define EXPANDED_MODE 1
/datum/action/changeling/evolution_menu
name = "-Evolution Menu-" //Dashes are so it's listed before all the other abilities.
desc = "Choose our method of subjugation."
button_icon_state = "changelingsting"
dna_cost = 0
/// Which UI view will be displayed. Compact mode will show only ability names, and will leave out their descriptions and helptext.
var/view_mode = EXPANDED_MODE
/// A list containing the names of bought changeling abilities. For use with the UI.
var/list/purchased_abilities = list()
/// A list containing every purchasable changeling ability. Includes its name, description, helptext and cost.
var/static/list/ability_list = list()
/datum/action/changeling/evolution_menu/Grant(mob/M)
. = ..()
if(length(ability_list))
return // List is already populated.
for(var/action in subtypesof(/datum/action/changeling))
var/datum/action/changeling/C = action
if(initial(C.dna_cost) <= 0) // Filter out innate abilities like DNA sting, Evolution menu, etc.
continue
ability_list += list(list(
"name" = initial(C.name),
"description" = initial(C.desc),
"helptext" = initial(C.helptext),
"cost" = initial(C.dna_cost)
))
/datum/action/changeling/evolution_menu/try_to_sting(mob/user, mob/target)
ui_interact(user)
/datum/action/changeling/evolution_menu/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.always_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "EvolutionMenu", "Evolution Menu", 480, 574, master_ui, state)
ui.set_autoupdate(FALSE)
ui.open()
/datum/action/changeling/evolution_menu/ui_data(mob/user)
var/datum/changeling/cling = owner.mind.changeling
var/list/data = list(
"can_respec" = cling.canrespec,
"evo_points" = cling.geneticpoints,
"purchsed_abilities" = purchased_abilities,
"view_mode" = view_mode
)
return data
/datum/action/changeling/evolution_menu/ui_static_data(mob/user)
var/list/data = list(
"ability_list" = ability_list
)
return data
/datum/action/changeling/evolution_menu/ui_act(action, list/params)
if(..())
return
var/datum/changeling/cling = owner.mind.changeling
switch(action)
if("readapt")
if(!cling.lingRespec(owner))
return FALSE
purchased_abilities.Cut()
return TRUE
if("purchase")
var/power_name = params["power_name"]
if(!cling.purchasePower(owner, power_name))
return FALSE
purchased_abilities |= power_name
return TRUE
if("set_view_mode")
var/new_view_mode = text2num(params["mode"])
if(!(new_view_mode in list(COMPACT_MODE, EXPANDED_MODE)))
return FALSE
view_mode = new_view_mode
return TRUE
/datum/changeling/proc/purchasePower(mob/living/carbon/user, sting_name)
var/datum/action/changeling/thepower = null
var/list/all_powers = init_subtypes(/datum/action/changeling)
for(var/datum/action/changeling/cling_sting in all_powers)
if(cling_sting.name == sting_name)
thepower = cling_sting
if(thepower == null)
to_chat(user, "This is awkward. Changeling power purchase failed, please report this bug to a coder!")
return FALSE
if(absorbedcount < thepower.req_dna)
to_chat(user, "We lack the energy to evolve this ability!")
return FALSE
if(has_sting(thepower))
to_chat(user, "We have already evolved this ability!")
return FALSE
if(thepower.dna_cost < 0)
to_chat(user, "We cannot evolve this ability.")
return FALSE
if(geneticpoints < thepower.dna_cost)
to_chat(user, "We have reached our capacity for abilities.")
return FALSE
if(HAS_TRAIT(user, TRAIT_FAKEDEATH)) //To avoid potential exploits by buying new powers while in stasis, which clears your verblist.
to_chat(user, "We lack the energy to evolve new abilities right now.")
return FALSE
geneticpoints -= thepower.dna_cost
purchasedpowers += thepower
thepower.on_purchase(user)
return TRUE
//Reselect powers
/datum/changeling/proc/lingRespec(mob/user)
if(!ishuman(user) || issmall(user))
to_chat(user, "<span class='danger'>We can't remove our evolutions in this form!</span>")
return FALSE
if(canrespec)
to_chat(user, "<span class='notice'>We have removed our evolutions from this form, and are now ready to readapt.</span>")
user.remove_changeling_powers(1)
canrespec = 0
user.make_changeling(FALSE)
return TRUE
else
to_chat(user, "<span class='danger'>You lack the power to readapt your evolutions!</span>")
return FALSE
/mob/proc/make_changeling(get_free_powers = TRUE)
if(!mind)
return
if(!ishuman(src))
return
if(!mind.changeling)
mind.changeling = new /datum/changeling(gender)
if(mind.changeling.purchasedpowers)
remove_changeling_powers(1)
add_language("Changeling")
for(var/language in languages)
mind.changeling.absorbed_languages |= language
if(get_free_powers)
var/list/all_powers = init_subtypes(/datum/action/changeling)
for(var/datum/action/changeling/path in all_powers) // purchase free powers.
if(!path.dna_cost)
if(!mind.changeling.has_sting(path))
mind.changeling.purchasedpowers += path
path.on_purchase(src)
else //for respec
var/datum/action/changeling/hivemind_pick/S1 = new
if(!mind.changeling.has_sting(S1))
mind.changeling.purchasedpowers+=S1
S1.Grant(src)
var/mob/living/carbon/C = src //only carbons have dna now, so we have to typecaste
mind.changeling.absorbed_dna |= C.dna.Clone()
mind.changeling.trim_dna()
return 1
//Used to dump the languages from the changeling datum into the actual mob.
/mob/proc/changeling_update_languages(updated_languages)
for(var/datum/language/L in updated_languages)
add_language("L.name")
//This isn't strictly necessary but just to be safe...
add_language("Changeling")
return
/datum/changeling/proc/reset()
chosen_sting = null
geneticpoints = initial(geneticpoints)
sting_range = initial(sting_range)
chem_storage = initial(chem_storage)
chem_recharge_rate = initial(chem_recharge_rate)
chem_charges = min(chem_charges, chem_storage)
chem_recharge_slowdown = initial(chem_recharge_slowdown)
mimicing = ""
/mob/proc/remove_changeling_powers(keep_free_powers=0)
if(ishuman(src))
if(mind && mind.changeling)
digitalcamo = 0
mind.changeling.changeling_speak = 0
mind.changeling.reset()
for(var/datum/action/changeling/p in mind.changeling.purchasedpowers)
if((p.dna_cost == 0 && keep_free_powers) || p.always_keep)
continue
mind.changeling.purchasedpowers -= p
p.Remove(src)
remove_language("Changeling")
if(hud_used)
hud_used.lingstingdisplay.icon_state = null
hud_used.lingstingdisplay.invisibility = 101
/datum/changeling/proc/has_sting(datum/action/power)
for(var/datum/action/P in purchasedpowers)
if(power.name == P.name)
return 1
return 0
@@ -1,88 +0,0 @@
//HIVEMIND COMMUNICATION (:g)
/datum/action/changeling/hivemind_comms
name = "Hivemind Communication"
desc = "We tune our senses to the airwaves to allow us to discreetly communicate and exchange DNA with other changelings."
helptext = "We will be able to talk with other changelings with :g. Exchanged DNA do not count towards absorb objectives."
dna_cost = 0
chemical_cost = -1
needs_button = FALSE
/datum/action/changeling/hivemind_comms/on_purchase(mob/user)
..()
var/datum/changeling/changeling=user.mind.changeling
changeling.changeling_speak = 1
to_chat(user, "<i><font color=#800080>Use say \":g message\" to communicate with the other changelings.</font></i>")
var/datum/action/changeling/hivemind_pick/S1 = new
if(!changeling.has_sting(S1))
changeling.purchasedpowers+=S1
S1.Grant(user)
return
// HIVE MIND UPLOAD/DOWNLOAD DNA
GLOBAL_LIST_EMPTY(hivemind_bank)
/datum/action/changeling/hivemind_pick
name = "Hive Channel DNA"
desc = "Allows us to upload or absorb DNA in the airwaves. Does not count towards absorb objectives. Costs 10 chemicals."
button_icon_state = "hive_absorb"
chemical_cost = 10
dna_cost = -1
/datum/action/changeling/hivemind_pick/sting_action(mob/user)
var/datum/changeling/changeling = user.mind.changeling
var/channel_pick = alert("Upload or Absorb DNA?", "Channel Select", "Upload", "Absorb")
if(channel_pick == "Upload")
dna_upload(user)
if(channel_pick == "Absorb")
if(changeling.using_stale_dna(user))//If our current DNA is the stalest, we gotta ditch it.
to_chat(user, "<span class='warning'>We have reached our capacity to store genetic information! We must transform before absorbing more.</span>")
return
else
dna_absorb(user)
/datum/action/changeling/proc/dna_upload(mob/user)
var/datum/changeling/changeling = user.mind.changeling
var/list/names = list()
for(var/datum/dna/DNA in (changeling.absorbed_dna+changeling.protected_dna))
if(!(DNA in GLOB.hivemind_bank))
names += DNA.real_name
if(names.len <= 0)
to_chat(user, "<span class='notice'>The airwaves already have all of our DNA.</span>")
return
var/chosen_name = input("Select a DNA to channel: ", "Channel DNA", null) as null|anything in names
if(!chosen_name)
return
var/datum/dna/chosen_dna = changeling.GetDNA(chosen_name)
if(!chosen_dna)
return
GLOB.hivemind_bank += chosen_dna
to_chat(user, "<span class='notice'>We channel the DNA of [chosen_name] to the air.</span>")
SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("[name]"))
return 1
/datum/action/changeling/proc/dna_absorb(mob/user)
var/datum/changeling/changeling = user.mind.changeling
var/list/names = list()
for(var/datum/dna/DNA in GLOB.hivemind_bank)
if(!(DNA in changeling.absorbed_dna))
names[DNA.real_name] = DNA
if(names.len <= 0)
to_chat(user, "<span class='notice'>There's no new DNA to absorb from the air.</span>")
return
var/S = input("Select a DNA absorb from the air: ", "Absorb DNA", null) as null|anything in names
if(!S) return
var/datum/dna/chosen_dna = names[S]
if(!chosen_dna)
return
changeling.store_dna(chosen_dna, user)
to_chat(user, "<span class='notice'>We absorb the DNA of [S] from the air.</span>")
SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("[name]"))
return 1
@@ -1,35 +0,0 @@
/datum/action/changeling/transform
name = "Transform"
desc = "We take on the appearance and voice of one we have absorbed. Costs 5 chemicals."
button_icon_state = "transform"
chemical_cost = 5
dna_cost = 0
req_dna = 1
req_human = 1
max_genetic_damage = 3
//Change our DNA to that of somebody we've absorbed.
/datum/action/changeling/transform/sting_action(mob/living/carbon/human/user)
var/datum/changeling/changeling = user.mind.changeling
var/datum/dna/chosen_dna = changeling.select_dna("Select the target DNA: ", "Target DNA")
if(!chosen_dna)
return
transform_dna(user,chosen_dna)
user.changeling_update_languages(changeling.absorbed_languages)
SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("[name]"))
return 1
/datum/changeling/proc/select_dna(prompt, title)
var/list/names = list()
for(var/datum/dna/DNA in (absorbed_dna+protected_dna))
names += "[DNA.real_name]"
var/chosen_name = input(prompt, title, null) as null|anything in names
if(!chosen_name)
return
var/datum/dna/chosen_dna = GetDNA(chosen_name)
return chosen_dna
+15 -20
View File
@@ -9,6 +9,8 @@
recommended_enemies = 3
secondary_enemies_scaling = 0.025
secondary_protected_species = list("Machine")
/// A list containing references to the minds of soon-to-be changelings. This is seperate to avoid duplicate entries in the `changelings` list.
var/list/datum/mind/pre_changelings = list()
/datum/game_mode/traitor/changeling/announce()
to_chat(world, "<B>The current game mode is - Traitor+Changeling!</B>")
@@ -26,27 +28,20 @@
if((player.mind in possible_changelings) && (player.client.prefs.active_character.species in secondary_protected_species))
possible_changelings -= player.mind
if(possible_changelings.len > 0)
for(var/I in possible_changelings)
if(length(changelings) >= secondary_enemies)
break
var/datum/mind/changeling = pick(possible_changelings)
changelings += changeling
modePlayer += changelings
possible_changelings -= changeling
changeling.restricted_roles = (restricted_jobs + secondary_restricted_jobs)
changeling.special_role = SPECIAL_ROLE_CHANGELING
if(!length(possible_changelings))
return ..()
else
return 0
for(var/I in possible_changelings)
if(length(pre_changelings) >= secondary_enemies)
break
var/datum/mind/changeling = pick_n_take(possible_changelings)
pre_changelings += changeling
changeling.restricted_roles = (restricted_jobs + secondary_restricted_jobs)
changeling.special_role = SPECIAL_ROLE_CHANGELING
return ..()
/datum/game_mode/traitor/changeling/post_setup()
for(var/datum/mind/changeling in changelings)
grant_changeling_powers(changeling.current)
changeling.special_role = SPECIAL_ROLE_CHANGELING
forge_changeling_objectives(changeling)
greet_changeling(changeling)
update_change_icons_added(changeling)
for(var/datum/mind/changeling as anything in pre_changelings)
changeling.add_antag_datum(/datum/antagonist/changeling)
..()
return
@@ -538,7 +538,7 @@ Congratulations! You are now trained for invasive xenobiology research!"}
if(ishuman(L))
var/mob/living/carbon/human/H = L
species = "<span clas=='notice'>[H.dna.species.name]</span>"
if(L.mind && L.mind.changeling)
if(ischangeling(L))
species = "<span class='warning'>Changeling lifeform</span>"
var/obj/item/organ/internal/heart/gland/temp = locate() in H.internal_organs
if(temp)
@@ -253,7 +253,7 @@
if(has_guardian(user))
to_chat(user, "You already have a [mob_name]!")
return
if(user.mind && (user.mind.changeling || user.mind.has_antag_datum(/datum/antagonist/vampire)))
if(user.mind && (ischangeling(user) || user.mind.has_antag_datum(/datum/antagonist/vampire)))
to_chat(user, "[ling_failure]")
return
if(used == TRUE)
+4 -4
View File
@@ -556,10 +556,10 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective)
return target_amount
/datum/objective/absorb/check_completion()
if(owner && owner.changeling && owner.changeling.absorbed_dna && (owner.changeling.absorbedcount >= target_amount))
return 1
else
return 0
var/datum/antagonist/changeling/cling = owner?.has_antag_datum(/datum/antagonist/changeling)
if(cling?.absorbed_dna && (cling.absorbed_count >= target_amount))
return TRUE
return FALSE
/datum/objective/destroy
martyr_compatible = 1
-2
View File
@@ -444,8 +444,6 @@ GLOBAL_LIST_INIT(cloner_biomass_items, list(\
SSticker.mode.rise(H)
if(SSticker.mode.cult_ascendant)
SSticker.mode.ascend(H)
if(H.mind in SSticker.mode.changelings)
SSticker.mode.update_change_icons_added(H.mind)
//Put messages in the connected computer's temp var for display.
/obj/machinery/clonepod/proc/connected_message(message)
+1 -1
View File
@@ -96,7 +96,7 @@
for(var/i = 0, i<numChangelings, i++)
H = pick(candidates)
H.mind.make_Changeling()
H.mind.add_antag_datum(/datum/antagonist/changeling)
candidates.Remove(H)
return 1
@@ -249,6 +249,7 @@ GLOBAL_LIST_EMPTY(antagonists)
assigned_targets += "[O.target]"
objectives += O
return O
/**
* Announces all objectives of this datum, and only this datum.
@@ -0,0 +1,106 @@
// Defines below to be used with the `power_type` var.
/// Denotes that this power is free and should be given to all changelings by default.
#define CHANGELING_INNATE_POWER 1
/// Denotes that this power can only be obtained by purchasing it.
#define CHANGELING_PURCHASABLE_POWER 2
/// Denotes that this power can not be obtained normally. Primarily used for base types such as [/datum/action/changeling/weapon].
#define CHANGELING_UNOBTAINABLE_POWER 3
/datum/action/changeling
name = "Prototype Sting"
desc = "" // Fluff
background_icon_state = "bg_changeling"
/// A reference to the changeling's changeling antag datum.
var/datum/antagonist/changeling/cling
/// Determines whether the power is always given to the changeling or if it must be purchased.
var/power_type = CHANGELING_UNOBTAINABLE_POWER
/// A description of what the power does.
var/helptext = ""
/// Chemical cost to use this power.
var/chemical_cost = 0
/// The cost of purchasing the power.
var/dna_cost = 0
/// The amount of victims the changeling needs to absorb before they can use this power. Changelings always start with a value of 1.
var/req_dna = 0
/// If you need to be in human form to activate this power.
var/req_human = FALSE
/// What `stat` value the changeling needs to have to use this power. Will be CONSCIOUS, UNCONSCIOUS or DEAD.
var/req_stat = CONSCIOUS
/// The amount of genetic damage caused by using this power. Is not related to the mob's `cloneloss`.
var/genetic_damage = 0
/// The max `genetic_damage` the changeling is allowed to have before they cannot use this power anymore.
var/max_genetic_damage = 100
/// If this power is active or not. Used for toggleable abilities.
var/active = FALSE
/// If this power can be used while the changeling has the `TRAIT_FAKE_DEATH` trait.
var/bypass_fake_death = FALSE
/*
* Changeling code relies on on_purchase to grant powers.
* The same goes for Remove(). if you override Remove(), call parent or else your power wont be removed on respec
*/
/datum/action/changeling/proc/on_purchase(mob/user, datum/antagonist/changeling/C)
SHOULD_CALL_PARENT(TRUE)
if(!user || !user.mind || !C)
qdel(src)
return
cling = C
Grant(user)
/datum/action/changeling/Destroy(force, ...)
cling.acquired_powers -= src
cling = null
return ..()
/datum/action/changeling/Trigger()
try_to_sting(owner)
/datum/action/changeling/proc/try_to_sting(mob/user, mob/target)
user.changeNext_click(5)
if(!can_sting(user, target))
return
if(sting_action(user, target))
sting_feedback(user, target)
take_chemical_cost()
/datum/action/changeling/proc/sting_action(mob/user, mob/target)
return FALSE
/datum/action/changeling/proc/sting_feedback(mob/user, mob/target)
return FALSE
/datum/action/changeling/proc/take_chemical_cost()
cling.chem_charges -= chemical_cost
cling.genetic_damage += genetic_damage
/datum/action/changeling/proc/can_sting(mob/user, mob/target)
SHOULD_CALL_PARENT(TRUE)
if(req_human && (!ishuman(user) || issmall(user)))
to_chat(user, "<span class='warning'>We cannot do that in this form!</span>")
return FALSE
if(cling.chem_charges < chemical_cost)
to_chat(user, "<span class='warning'>We require at least [chemical_cost] unit\s of chemicals to do that!</span>")
return FALSE
if(cling.absorbed_count < req_dna)
to_chat(user, "<span class='warning'>We require at least [req_dna] sample\s of compatible DNA.</span>")
return FALSE
if(req_stat < user.stat)
to_chat(user, "<span class='warning'>We are incapacitated.</span>")
return FALSE
if(HAS_TRAIT(user, TRAIT_FAKEDEATH) && !bypass_fake_death)
to_chat(user, "<span class='warning'>We are incapacitated.</span>")
return FALSE
if(cling.genetic_damage > max_genetic_damage)
to_chat(user, "<span class='warning'>Our genomes are still reassembling. We need time to recover first.</span>")
return FALSE
return TRUE
// Transform the target to the chosen dna. Used in transform.dm and tiny_prick.dm (handy for changes since it's the same thing done twice)
/datum/action/changeling/proc/transform_dna(mob/living/carbon/human/H, datum/dna/D)
if(!D)
return
var/changes_species = TRUE
if(H.dna.species.name == D.species.name)
changes_species = FALSE
H.change_dna(D, changes_species)
@@ -0,0 +1,407 @@
/datum/antagonist/changeling
name = "Changeling"
roundend_category = "changelings"
job_rank = ROLE_CHANGELING
special_role = SPECIAL_ROLE_CHANGELING
antag_hud_name = "hudchangeling"
antag_hud_type = ANTAG_HUD_CHANGELING
clown_gain_text = "You have evolved beyond your clownish nature, allowing you to wield weapons without harming yourself."
clown_removal_text = "As your changeling nature fades, you return to your own clumsy, clownish self."
wiki_page_name = "Changeling"
/// List of [/datum/dna] which have been absorbed through the DNA sting or absorb power.
var/list/absorbed_dna
/// List of [/datum/dna] which are not lost when the changeling has no more room for DNA.
var/list/protected_dna
/// List of [/datum/language] UIDs, learned from absorbed victims.
var/list/absorbed_languages
/// A list of instanced [/datum/action/changeling] the changeling has aquired.
var/list/acquired_powers
/// A list of [/datum/action/changeling] typepaths with a `power_type` of `CHANGELING_INNATE_POWER`.
var/static/list/innate_powers
/// A list of [/datum/action/changeling] typepaths with a `power_type` of `CHANGELING_PURCHASABLE_POWER`.
var/static/list/purchaseable_powers
/// How many total DNA strands the changeling can store for transformation.
var/dna_max = 5
/// Number of victims the changeling has absorbed.
var/absorbed_count = 1
/// The current amount of chemicals the changeling has stored.
var/chem_charges = 20
/// The amount of chemicals that recharges per `Life()` call.
var/chem_recharge_rate = 1
/// Amount of chemical recharge slowdown, calculated as `chem_recharge_rate - chem_recharge_slowdown`
var/chem_recharge_slowdown = 0
/// The total amount of chemicals able to be stored.
var/chem_storage = 75
/// The range of changeling stings.
var/sting_range = 2
/// The changeling's identifier when speaking in the hivemind, i.e. "Mr. Delta 123".
var/changelingID = "Changeling"
/// The current amount of genetic damage incurred from power use.
var/genetic_damage = 0
/// If the changeling is in the process of absorbing someone.
var/is_absorbing = FALSE
/// If the changeling is in the process of linking with someone.
var/is_linking = FALSE
/// The amount of points available to purchase changeling abilities.
var/genetic_points = 10
/// A name that will display in place of the changeling's real name when speaking.
var/mimicing = ""
/// If the changeling can respec their purchased abilities.
var/can_respec = FALSE
/// The current sting power the changeling has active.
var/datum/action/changeling/sting/chosen_sting
/// If the changeling is in the process of regenerating from their fake death.
var/regenerating = FALSE
/datum/antagonist/changeling/New()
..()
if(!length(innate_powers))
innate_powers = get_powers_of_type(CHANGELING_INNATE_POWER)
if(!length(purchaseable_powers))
purchaseable_powers = get_powers_of_type(CHANGELING_PURCHASABLE_POWER)
/datum/antagonist/changeling/on_gain()
SSticker.mode.changelings |= owner
var/honorific = owner.current.gender == FEMALE ? "Ms." : "Mr."
if(length(GLOB.possible_changeling_IDs))
changelingID = pick(GLOB.possible_changeling_IDs)
GLOB.possible_changeling_IDs -= changelingID
changelingID = "[honorific] [changelingID]"
else
changelingID = "[honorific] [rand(1,999)]"
absorbed_dna = list()
protected_dna = list()
acquired_powers = list()
absorbed_languages = list()
var/mob/living/carbon/human/H = owner.current
absorbed_dna += H.dna.Clone()
..()
/datum/antagonist/changeling/Destroy()
SSticker.mode.changelings -= owner
chosen_sting = null
QDEL_LIST(acquired_powers)
STOP_PROCESSING(SSobj, src)
return ..()
/datum/antagonist/changeling/greet()
..()
SEND_SOUND(owner.current, sound('sound/ambience/antag/ling_alert.ogg'))
to_chat(owner.current, "<span class='danger'>Use say \":g message\" to communicate with your fellow changelings. Remember: you get all of their absorbed DNA if you absorb them.</span>")
/datum/antagonist/changeling/farewell()
to_chat(owner.current, "<span class='biggerdanger'><B>You grow weak and lose your powers! You are no longer a changeling and are stuck in your current form!</span>")
/datum/antagonist/changeling/apply_innate_effects(mob/living/mob_override)
var/mob/living/L = ..()
if(ishuman(L))
START_PROCESSING(SSobj, src)
add_new_languages(L.languages) // Absorb the languages of the new body.
update_languages() // But also, give the changeling the languages they've already absorbed before this.
L.add_language("Changeling")
// If there's a mob_override, this is a body transfer, and therefore we should give them back their powers they had while in the old body.
if(mob_override)
for(var/datum/action/changeling/power in acquired_powers)
power.Grant(L)
// Else, this is their first time gaining the datum.
else
for(var/power_type in innate_powers)
give_power(new power_type, L)
/datum/antagonist/changeling/remove_innate_effects(mob/living/mob_override)
var/mob/living/L = ..()
if(!ishuman(L))
STOP_PROCESSING(SSobj, src) // This is to handle when they transfer into a headslug (simple animal). We shouldn't process in that case.
if(L.hud_used?.lingstingdisplay)
L.hud_used.lingstingdisplay.invisibility = 101
L.hud_used.lingchemdisplay.invisibility = 101
L.remove_language("Changeling")
remove_unnatural_languages(L)
// If there's a mob_override, this is a body transfer, and therefore we should only remove their powers from the old body.
if(mob_override)
for(var/datum/action/changeling/power in acquired_powers)
power.Remove(L)
// Else, they're losing the datum.
else
respec(FALSE)
/*
* Always absorb X amount of genomes, plus random traitor objectives.
* If they have two objectives as well as absorb, they must survive rather than escape.
*/
/datum/antagonist/changeling/give_objectives()
var/datum/objective/absorb/absorb = new
absorb.gen_amount_goal(6, 8)
absorb.owner = owner
objectives += absorb
if(prob(60))
add_objective(/datum/objective/steal)
else
add_objective(/datum/objective/debrain)
var/list/active_ais = active_ais()
if(length(active_ais) && prob(4)) // Leaving this at a flat chance for now, problems with the num_players() proc due to latejoin antags.
add_objective(/datum/objective/destroy)
else
var/datum/objective/assassinate/kill_objective = add_objective(/datum/objective/assassinate)
var/mob/living/carbon/human/H = kill_objective.target?.current
if(!(locate(/datum/objective/escape) in owner.get_all_objectives()) && H && !HAS_TRAIT(H, TRAIT_GENELESS))
var/datum/objective/escape/escape_with_identity/identity_theft = new
identity_theft.owner = owner
identity_theft.target = kill_objective.target
identity_theft.target_real_name = kill_objective.target.current.real_name
identity_theft.explanation_text = "Escape on the shuttle or an escape pod with the identity of [identity_theft.target_real_name], the [identity_theft.target.assigned_role] while wearing [identity_theft.target.p_their()] identification card."
objectives += identity_theft
if(!(locate(/datum/objective/escape) in owner.get_all_objectives()))
if(prob(70))
add_objective(/datum/objective/escape)
else
add_objective(/datum/objective/escape/escape_with_identity)
/datum/antagonist/changeling/process()
if(!owner || !owner.current)
return PROCESS_KILL
var/mob/living/carbon/human/H = owner.current
if(H.hud_used?.lingchemdisplay)
H.hud_used.lingchemdisplay.invisibility = 0
H.hud_used.lingchemdisplay.maptext = "<div align='center' valign='middle' style='position:relative; top:0px; left:6px'><font face='Small Fonts' color='#dd66dd'>[round(chem_charges)]</font></div>"
if(H.stat == DEAD)
chem_charges = clamp(0, chem_charges + chem_recharge_rate - chem_recharge_slowdown, chem_storage * 0.5)
genetic_damage = directional_bounded_sum(genetic_damage, -1, LING_DEAD_GENETIC_DAMAGE_HEAL_CAP, 0)
else // Not dead? no chem/genetic_damage caps.
chem_charges = clamp(0, chem_charges + chem_recharge_rate - chem_recharge_slowdown, chem_storage)
genetic_damage = max(0, genetic_damage - 1)
/**
* Respec the changeling's powers after first checking if they're able to respec.
*/
/datum/antagonist/changeling/proc/try_respec()
var/mob/living/carbon/human/H = owner.current
if(!ishuman(H) || issmall(H))
to_chat(H, "<span class='danger'>We can't readapt our evolutions in this form!</span>")
return FALSE
if(can_respec)
to_chat(H, "<span class='notice'>We have removed our evolutions from this form, and are now ready to readapt.</span>")
respec()
can_respec = FALSE
return TRUE
else
to_chat(H, "<span class='danger'>You lack the power to readapt your evolutions!</span>")
return FALSE
/**
* Resets a changeling to the point they were when they first became a changeling, i.e no genetic points to spend, no non-innate powers, etc.
*/
/datum/antagonist/changeling/proc/respec(keep_innate_powers = TRUE)
remove_changeling_powers(keep_innate_powers)
chosen_sting = null
genetic_points = initial(genetic_points)
sting_range = initial(sting_range)
chem_storage = initial(chem_storage)
chem_recharge_rate = initial(chem_recharge_rate)
chem_charges = min(chem_charges, chem_storage)
chem_recharge_slowdown = initial(chem_recharge_slowdown)
mimicing = null
/**
* Removes a changeling's abilities.
*
* Arguments:
* * keep_innate_powers - set to TRUE if changeling actions with a `power_type` of `CHANGELING_INNATE_POWER` should be kept.
*/
/datum/antagonist/changeling/proc/remove_changeling_powers(keep_innate_powers = FALSE)
for(var/datum/action/changeling/power in acquired_powers)
if(keep_innate_powers && (power.power_type == CHANGELING_INNATE_POWER))
continue
acquired_powers -= power
qdel(power)
/**
* Gets a list of changeling action typepaths based on the passed in `power_type`.
*
* Arguments:
* * power_type - should be a define related to [/datum/action/changeling/var/power_type].
*/
/datum/antagonist/changeling/proc/get_powers_of_type(power_type)
var/list/powers = list()
for(var/power_path in subtypesof(/datum/action/changeling))
var/datum/action/changeling/power = power_path
if(initial(power.power_type) != power_type)
continue
powers += power_path
return powers
/**
* Gives the changeling the passed in `power`. Subtracts the cost of the power from our genetic points.
*
* Arugments:
* * datum/action/changeling/power - the power to give to the changeling.
* * mob/living/changeling - the changeling who owns this datum. Optional argument.
* * take_cost - if we should spend genetic points when giving the power
*/
/datum/antagonist/changeling/proc/give_power(datum/action/changeling/power, mob/living/changeling, take_cost = TRUE)
if(take_cost)
genetic_points -= power.dna_cost
acquired_powers += power
power.on_purchase(changeling || owner.current, src)
/**
* Store the languages from the `new_languages` list into the `absorbed_languages` list. Teaches the changeling the new languages.
*
* Arguments:
* * list/new_languages - a list of [/datum/language] to be added
*/
/datum/antagonist/changeling/proc/add_new_languages(list/new_languages)
for(var/datum/language/L in new_languages)
if(is_type_in_UID_list(L, absorbed_languages))
continue
owner.current.add_language("[L.name]")
absorbed_languages += L.UID()
/**
* Teach the changeling every language in the `absorbed_language` list. Already known languages will be ignored.
*/
/datum/antagonist/changeling/proc/update_languages()
for(var/lang_UID in absorbed_languages)
var/datum/language/lang = locateUID(lang_UID)
owner.current.add_language("[lang.name]")
/**
* Removes all the languages the mob `L` has absorbed throughout their life as a changeling and should no longer have.
*
* Ignores languages the player has chosen from character creation, and species languages from the changeling mob's current species.
*
* Arguments:
* * mob/living/L - the changeling mob to remove languages from
*/
/datum/antagonist/changeling/proc/remove_unnatural_languages(mob/living/L)
var/list/ignored_languages = list()
if(L.client?.prefs.active_character.real_name == L.real_name) // Check if L is actually that player's character or just someone they transformed into.
ignored_languages += L.client.prefs.active_character.language
if(ishuman(L))
var/mob/living/carbon/human/H = L
ignored_languages += H.dna.species.default_language
ignored_languages += H.dna.species.language
ignored_languages += H.dna.species.secondary_langs
for(var/lang_UID in absorbed_languages)
var/datum/language/lang = locateUID(lang_UID)
if(lang.name in ignored_languages)
continue
L.remove_language("[lang.name]")
/**
* Absorb the the target's DNA and their languages.
*
* Arguments:
* * mob/living/carbon/C - the mob to absorb DNA from
*/
/datum/antagonist/changeling/proc/absorb_dna(mob/living/carbon/C)
C.dna.real_name = C.real_name // Set this again, just to be sure that it's properly set.
store_dna(C.dna.Clone())
add_new_languages(C.languages)
absorbed_count++
/**
* Store the target DNA. If the DNA belongs to one of the changeling's "escape with identity" objectives, make the DNA protected.
*
* Arguments:
* * datum/dna/new_dna - the DNA to store
*/
/datum/antagonist/changeling/proc/store_dna(datum/dna/new_dna)
for(var/datum/objective/escape/escape_with_identity/E in objectives)
if(E.target_real_name == new_dna.real_name)
protected_dna |= new_dna
return
absorbed_dna |= new_dna
trim_dna()
/**
* Prompt the changeling with a list of names associated with their stored DNA. Return a [/datum/dna] based on the name chosen.
*
* Arguments:
* * message - the message of the `input()` window
* * title - the title of the `input()` window
* * not_in_bank - if we should filter out DNA that's already in the hivemind bank
*/
/datum/antagonist/changeling/proc/select_dna(message, title, not_in_bank = FALSE)
var/list/names = list()
for(var/datum/dna/DNA in (absorbed_dna + protected_dna))
if(not_in_bank && (DNA in GLOB.hivemind_bank))
continue
names[DNA.real_name] = DNA
var/chosen_name = input(message, title, null) as null|anything in names
if(!chosen_name)
return
return names[chosen_name]
/**
* Gets a [/datum/dna] that matches the passed in `tDNA`. Also used as a check to see if the changeling has this DNA already stored.
*
* Arguments:
* * datum/dna/tDNA - a reference to a DNA datum that we want to find
*/
/datum/antagonist/changeling/proc/get_dna(datum/dna/tDNA)
for(var/datum/dna/DNA in (absorbed_dna + protected_dna))
if(tDNA.unique_enzymes == DNA.unique_enzymes && tDNA.uni_identity == DNA.uni_identity && tDNA.species.type == DNA.species.type)
return DNA
/**
* Determines if the changeling's current DNA is stale.
*/
/datum/antagonist/changeling/proc/using_stale_dna()
var/datum/dna/current_dna = get_dna(owner.current.dna)
if(length(absorbed_dna) < dna_max)
return FALSE // Still more room for DNA.
if(!current_dna || !(current_dna in absorbed_dna))
return TRUE // Oops, our current DNA was somehow not absorbed; force a transformation.
if(absorbed_dna[1] == current_dna)
return TRUE // The oldest DNA is the current DNA, which means it's "stale".
return FALSE
/**
* Clears the most "stale" DNA from the `absorbed_dna` list.
*/
/datum/antagonist/changeling/proc/trim_dna()
listclearnulls(absorbed_dna)
if(length(absorbed_dna) > dna_max)
absorbed_dna.Cut(1, 2)
/**
* Returns TRUE if the changeling can absorb the target mob's DNA.
*
* Arguments:
* * mob/living/carbon/target - the mob's DNA we're trying to absorb
*/
/datum/antagonist/changeling/proc/can_absorb_dna(mob/living/carbon/target)
var/mob/living/carbon/user = owner.current
if(using_stale_dna())//If our current DNA is the stalest, we gotta ditch it.
to_chat(user, "<span class='warning'>The DNA we are wearing is stale. Transform and try again.</span>")
return FALSE
if(!target || !target.dna)
to_chat(user, "<span class='warning'>This creature does not have any DNA.</span>")
return FALSE
var/mob/living/carbon/human/T = target
if(!istype(T) || issmall(T))
to_chat(user, "<span class='warning'>[T] is not compatible with our biology.</span>")
return FALSE
if(HAS_TRAIT(T, TRAIT_BADDNA) || HAS_TRAIT(T, TRAIT_HUSK) || HAS_TRAIT(T, TRAIT_SKELETONIZED))
to_chat(user, "<span class='warning'>DNA of [target] is ruined beyond usability!</span>")
return FALSE
if(HAS_TRAIT(T, TRAIT_GENELESS))
to_chat(user, "<span class='warning'>This creature does not have DNA!</span>")
return FALSE
if(get_dna(target.dna))
to_chat(user, "<span class='warning'>We already have this DNA in storage!</span>")
return FALSE
return TRUE
/proc/ischangeling(mob/M)
return M.mind?.has_antag_datum(/datum/antagonist/changeling)
@@ -0,0 +1,99 @@
/// The evolution menu will be shown in the compact mode, with only powers and costs being displayed.
#define COMPACT_MODE 0
/// The evolution menu will be shown in the expanded mode, with powers, costs, and power descriptions being displayed.
#define EXPANDED_MODE 1
/datum/action/changeling/evolution_menu
name = "Evolution Menu"
desc = "Choose our method of subjugation."
button_icon_state = "changelingsting"
power_type = CHANGELING_INNATE_POWER
/// Which UI view will be displayed. Compact mode will show only power names, and will leave out their descriptions and helptext.
var/view_mode = EXPANDED_MODE
/// A list containing the typepaths of bought changeling abilities. For use with the UI.
var/list/purchased_abilities = list()
/// A list containing every purchasable changeling power. Includes its name, description, helptext and cost.
var/static/list/ability_list = list()
/datum/action/changeling/evolution_menu/Grant(mob/M)
..()
if(length(ability_list))
return // List is already populated.
for(var/power_path in cling.purchaseable_powers)
var/datum/action/changeling/C = power_path
ability_list += list(list(
"name" = initial(C.name),
"description" = initial(C.desc),
"helptext" = initial(C.helptext),
"cost" = initial(C.dna_cost),
"power_path" = power_path
))
/datum/action/changeling/evolution_menu/try_to_sting(mob/user, mob/target)
ui_interact(user)
/datum/action/changeling/evolution_menu/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.always_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "EvolutionMenu", "Evolution Menu", 480, 574, master_ui, state)
ui.set_autoupdate(FALSE)
ui.open()
/datum/action/changeling/evolution_menu/ui_data(mob/user)
var/list/data = list(
"can_respec" = cling.can_respec,
"evo_points" = cling.genetic_points,
"purchased_abilities" = purchased_abilities,
"view_mode" = view_mode
)
return data
/datum/action/changeling/evolution_menu/ui_static_data(mob/user)
var/list/data = list(
"ability_list" = ability_list
)
return data
/datum/action/changeling/evolution_menu/ui_act(action, list/params)
if(..())
return
switch(action)
if("readapt")
if(!cling.try_respec())
return FALSE
purchased_abilities.Cut()
return TRUE
if("purchase")
var/power_path = text2path(params["power_path"])
if(!ispath(power_path) || !try_purchase_power(power_path))
return FALSE
purchased_abilities |= power_path
return TRUE
if("set_view_mode")
var/new_view_mode = text2num(params["mode"])
if(!(new_view_mode in list(COMPACT_MODE, EXPANDED_MODE)))
return FALSE
view_mode = new_view_mode
return TRUE
/datum/action/changeling/evolution_menu/proc/try_purchase_power(power_type)
if(power_type in purchased_abilities)
to_chat(owner, "<span class='warning'>We have already evolved this ability!</span>")
return FALSE
var/datum/action/changeling/power = power_type
if(cling.absorbed_count < initial(power.req_dna))
to_chat(owner, "<span class='warning'>We must absorb more victims before we can evolve this ability!</span>")
return FALSE
if(cling.genetic_points < initial(power.dna_cost))
to_chat(owner, "<span class='warning'>We cannot afford to evolve this ability!</span>")
return FALSE
if(HAS_TRAIT(owner, TRAIT_FAKEDEATH)) // To avoid potential exploits by buying new powers while in stasis, which clears your verblist.
to_chat(owner, "<span class='warning'>We lack the energy to evolve new abilities right now.</span>")
return FALSE
cling.give_power(new power_type)
return TRUE
@@ -3,36 +3,33 @@
desc = "Absorb the DNA of our victim. Requires us to strangle them."
button_icon_state = "absorb_dna"
chemical_cost = 0
dna_cost = 0
req_human = 1
power_type = CHANGELING_INNATE_POWER
req_human = TRUE
max_genetic_damage = 100
/datum/action/changeling/absorbDNA/can_sting(mob/living/carbon/user)
if(!..())
return
return FALSE
var/datum/changeling/changeling = user.mind.changeling
if(changeling.isabsorbing)
if(cling.is_absorbing)
to_chat(user, "<span class='warning'>We are already absorbing!</span>")
return
return FALSE
var/obj/item/grab/G = user.get_active_hand()
if(!istype(G))
to_chat(user, "<span class='warning'>We must be grabbing a creature in our active hand to absorb them.</span>")
return
return FALSE
if(G.state <= GRAB_NECK)
to_chat(user, "<span class='warning'>We must have a tighter grip to absorb this creature.</span>")
return
return FALSE
var/mob/living/carbon/target = G.affecting
return changeling.can_absorb_dna(user,target)
return cling.can_absorb_dna(G.affecting)
/datum/action/changeling/absorbDNA/sting_action(mob/user)
var/datum/changeling/changeling = user.mind.changeling
var/obj/item/grab/G = user.get_active_hand()
var/mob/living/carbon/human/target = G.affecting
changeling.isabsorbing = 1
for(var/stage = 1, stage<=3, stage++)
cling.is_absorbing = TRUE
for(var/stage in 1 to 3)
switch(stage)
if(1)
to_chat(user, "<span class='notice'>This creature is compatible. We must hold still...</span>")
@@ -47,15 +44,15 @@
SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("Absorb DNA", "[stage]"))
if(!do_mob(user, target, 150))
to_chat(user, "<span class='warning'>Our absorption of [target] has been interrupted!</span>")
changeling.isabsorbing = 0
return
cling.is_absorbing = FALSE
return FALSE
to_chat(user, "<span class='notice'>We have absorbed [target]!</span>")
user.visible_message("<span class='danger'>[user] sucks the fluids from [target]!</span>")
to_chat(target, "<span class='danger'>You have been absorbed by the changeling!</span>")
if(!changeling.has_dna(target.dna))
changeling.absorb_dna(target, user)
if(!cling.get_dna(target.dna))
cling.absorb_dna(target)
if(user.nutrition < NUTRITION_LEVEL_WELL_FED)
user.set_nutrition(min((user.nutrition + target.nutrition), NUTRITION_LEVEL_WELL_FED))
@@ -73,7 +70,7 @@
else
recent_speech = target.say_log.Copy()
if(recent_speech.len)
if(length(recent_speech))
user.mind.store_memory("<B>Some of [target]'s speech patterns. We should study these to better impersonate [target.p_them()]!</B>")
to_chat(user, "<span class='boldnotice'>Some of [target]'s speech patterns. We should study these to better impersonate [target.p_them()]!</span>")
for(var/spoken_memory in recent_speech)
@@ -82,43 +79,23 @@
user.mind.store_memory("<B>We have no more knowledge of [target]'s speech patterns.</B>")
to_chat(user, "<span class='boldnotice'>We have no more knowledge of [target]'s speech patterns.</span>")
if(target.mind.changeling)//If the target was a changeling, suck out their extra juice and objective points!
changeling.chem_charges += min(target.mind.changeling.chem_charges, changeling.chem_storage)
changeling.absorbedcount += (target.mind.changeling.absorbedcount)
var/datum/antagonist/changeling/target_cling = target.mind.has_antag_datum(/datum/antagonist/changeling)
if(target_cling)//If the target was a changeling, suck out their extra juice and objective points!
cling.chem_charges += min(target_cling.chem_charges, cling.chem_storage)
cling.absorbed_count += (target_cling.absorbed_count)
target.mind.changeling.absorbed_dna.len = 1
target.mind.changeling.absorbedcount = 0
target_cling.absorbed_dna.len = 1
target_cling.absorbed_count = 0
changeling.chem_charges=min(changeling.chem_charges+10, changeling.chem_storage)
cling.chem_charges = min(cling.chem_charges + 10, cling.chem_storage)
changeling.isabsorbing = 0
changeling.canrespec = 1
cling.is_absorbing = FALSE
cling.can_respec = TRUE
var/datum/action/changeling/evolution_menu/E = locate() in user.actions
SStgui.update_uis(E)
target.death(0)
target.death(FALSE)
target.Drain()
return 1
//Absorbs the target DNA.
/datum/changeling/proc/absorb_dna(mob/living/carbon/T, mob/user)
T.dna.real_name = T.real_name //Set this again, just to be sure that it's properly set.
var/datum/dna/new_dna = T.dna.Clone()
//Steal all of their languages!
for(var/language in T.languages)
if(!(language in absorbed_languages))
absorbed_languages += language
user.changeling_update_languages(absorbed_languages)
absorbedcount++
store_dna(new_dna, user)
/datum/changeling/proc/store_dna(datum/dna/new_dna, mob/user)
for(var/datum/objective/escape/escape_with_identity/E in user.mind.objectives)
if(E.target_real_name == new_dna.real_name)
protected_dna |= new_dna
return
absorbed_dna |= new_dna
trim_dna()
return TRUE
#undef LING_ABSORB_RECENT_SPEECH
@@ -1,19 +1,16 @@
//Augmented Eyesight: Gives you thermal and night vision - bye bye, flashlights. Also, high DNA cost because of how powerful it is.
//Possible todo: make a custom message for directing a penlight/flashlight at the eyes - not sure what would display though.
/datum/action/changeling/augmented_eyesight
name = "Augmented Eyesight"
desc = "Creates more light sensing rods in our eyes, allowing our vision to penetrate most blocking objects. Protects our vision from flashes while inactive."
helptext = "Grants us x-ray vision or flash protection. We will become a lot more vulnerable to flash-based devices while x-ray vision is active."
button_icon_state = "augmented_eyesight"
chemical_cost = 0
dna_cost = 2 //Would be 1 without thermal vision
dna_cost = 2
active = FALSE
power_type = CHANGELING_PURCHASABLE_POWER
/datum/action/changeling/augmented_eyesight/on_purchase(mob/user) //The ability starts inactive, so we should be protected from flashes.
if(!istype(user))
/datum/action/changeling/augmented_eyesight/on_purchase(mob/user, /datum/antagonist/changeling/C) //The ability starts inactive, so we should be protected from flashes.
if(!..())
return
..()
var/obj/item/organ/internal/eyes/E = user.get_organ_slot("eyes")
if(E)
E.flash_protect = FLASH_PROTECTION_WELDER //Adjust the user's eyes' flash protection
@@ -23,7 +20,7 @@
/datum/action/changeling/augmented_eyesight/sting_action(mob/living/carbon/user)
if(!istype(user))
return
return FALSE
..()
var/obj/item/organ/internal/eyes/E = user.get_organ_slot("eyes")
if(E)
@@ -5,7 +5,8 @@
button_icon_state = "biodegrade"
chemical_cost = 30 //High cost to prevent spam
dna_cost = 2
req_human = 1
req_human = TRUE
power_type = CHANGELING_PURCHASABLE_POWER
/datum/action/changeling/biodegrade/sting_action(mob/living/carbon/human/user)
var/used = FALSE // only one form of shackles removed per use
@@ -5,12 +5,11 @@
button_icon_state = "chameleon_skin"
dna_cost = 2
chemical_cost = 25
req_human = 1
req_human = TRUE
power_type = CHANGELING_PURCHASABLE_POWER
/datum/action/changeling/chameleon_skin/sting_action(mob/user)
var/mob/living/carbon/human/H = user //SHOULD always be human, because req_human = 1
if(!istype(H)) // req_human could be done in can_sting stuff.
return
var/mob/living/carbon/human/H = user //SHOULD always be human, because req_human = TRUE
if(H.dna.GetSEState(GLOB.chameleonblock))
H.dna.SetSEState(GLOB.chameleonblock, 0)
singlemutcheck(H, GLOB.chameleonblock, MUTCHK_FORCED)
@@ -4,15 +4,20 @@
helptext = "We cannot be tracked by camera while using this skill."
button_icon_state = "digital_camo"
dna_cost = 1
power_type = CHANGELING_PURCHASABLE_POWER
/datum/action/changeling/digitalcamo/Remove(mob/M)
REMOVE_TRAIT(M, TRAIT_AI_UNTRACKABLE, CHANGELING_TRAIT)
..()
//Prevents AIs tracking you.
/datum/action/changeling/digitalcamo/sting_action(mob/user)
if(user.digitalcamo)
if(HAS_TRAIT_FROM(user, TRAIT_AI_UNTRACKABLE, CHANGELING_TRAIT))
REMOVE_TRAIT(user, TRAIT_AI_UNTRACKABLE, CHANGELING_TRAIT)
to_chat(user, "<span class='notice'>We return to normal.</span>")
else
ADD_TRAIT(user, TRAIT_AI_UNTRACKABLE, CHANGELING_TRAIT)
to_chat(user, "<span class='notice'>We distort our form to prevent AI-tracking.</span>")
user.digitalcamo = !user.digitalcamo
SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("[name]"))
return 1
return TRUE
@@ -5,8 +5,9 @@
button_icon_state = "adrenaline"
chemical_cost = 30
dna_cost = 2
req_human = 1
req_human = TRUE
req_stat = UNCONSCIOUS
power_type = CHANGELING_PURCHASABLE_POWER
//Recover from stuns.
/datum/action/changeling/epinephrine/sting_action(mob/living/user)
@@ -3,7 +3,7 @@
desc = "We fall into a stasis, allowing us to regenerate and trick our enemies. Costs 15 chemicals."
button_icon_state = "fake_death"
chemical_cost = 15
dna_cost = 0
power_type = CHANGELING_INNATE_POWER
req_dna = 1
req_stat = DEAD
max_genetic_damage = 100
@@ -15,27 +15,26 @@
if(user.stat != DEAD)
user.emote("deathgasp")
user.timeofdeath = world.time
ADD_TRAIT(user, TRAIT_FAKEDEATH, "changeling") //play dead
ADD_TRAIT(user, TRAIT_FAKEDEATH, CHANGELING_TRAIT) //play dead
user.updatehealth("fakedeath sting")
user.update_canmove()
user.mind.changeling.regenerating = TRUE
cling.regenerating = TRUE
addtimer(CALLBACK(src, .proc/ready_to_regenerate, user), LING_FAKEDEATH_TIME)
SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("[name]"))
return 1
return TRUE
/datum/action/changeling/fakedeath/proc/ready_to_regenerate(mob/user)
if(user && user.mind && user.mind.changeling && user.mind.changeling.purchasedpowers)
if(user?.mind && cling?.acquired_powers)
to_chat(user, "<span class='notice'>We are ready to regenerate.</span>")
var/datum/action/changeling/revive/R = new
R.Grant(user)
cling.give_power(new /datum/action/changeling/revive)
/datum/action/changeling/fakedeath/can_sting(mob/user)
if(HAS_TRAIT(user, TRAIT_FAKEDEATH))
to_chat(user, "<span class='warning'>We are already regenerating.</span>")
return
return FALSE
if(!user.stat)//Confirmation for living changelings if they want to fake their death
switch(alert("Are we sure we wish to fake our death?",,"Yes","No"))
if("No")
return
return FALSE
return ..()
@@ -6,6 +6,7 @@
chemical_cost = 20
dna_cost = 2
req_stat = UNCONSCIOUS
power_type = CHANGELING_PURCHASABLE_POWER
var/recent_uses = 1 //The factor of which the healing should be divided by
var/healing_ticks = 10
// The ideal total healing amount,
@@ -5,7 +5,8 @@
button_icon_state = "last_resort"
chemical_cost = 20
dna_cost = 1
req_human = 1
req_human = TRUE
power_type = CHANGELING_PURCHASABLE_POWER
/datum/action/changeling/headslug/try_to_sting(mob/user, mob/target)
if(alert("Are you sure you wish to do this? This action cannot be undone.",,"Yes","No")=="No")
@@ -41,6 +42,12 @@
crab.origin.active = 1
crab.origin.transfer_to(crab)
to_chat(crab, "<span class='warning'>You burst out of the remains of your former body in a shower of gore!</span>")
// This is done because after the original changeling gibs below, ALL of their actions are qdeleted
// We need to store their power types so we can re-create them later.
for(var/datum/action/changeling/power in cling.acquired_powers)
cling.acquired_powers += power.type
user.gib()
SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("[name]"))
return 1
return TRUE
@@ -0,0 +1,52 @@
// HIVE MIND UPLOAD/DOWNLOAD DNA
GLOBAL_LIST_EMPTY(hivemind_bank)
/datum/action/changeling/hivemind_pick
name = "Hive Channel DNA"
desc = "Allows us to upload or absorb DNA in the airwaves. Does not count towards absorb objectives. Costs 10 chemicals."
button_icon_state = "hive_absorb"
chemical_cost = 10
power_type = CHANGELING_INNATE_POWER
/datum/action/changeling/hivemind_pick/sting_action(mob/user)
var/channel_pick = alert("Upload or Absorb DNA?", "Channel Select", "Upload", "Absorb")
if(channel_pick == "Upload")
dna_upload(user)
if(channel_pick == "Absorb")
if(cling.using_stale_dna())//If our current DNA is the stalest, we gotta ditch it.
to_chat(user, "<span class='warning'>We have reached our capacity to store genetic information! We must transform before absorbing more.</span>")
return FALSE
else
dna_absorb(user)
/datum/action/changeling/proc/dna_upload(mob/user)
var/datum/dna/chosen_dna = cling.select_dna("Select a DNA to channel: ", "Channel DNA", TRUE)
if(!chosen_dna)
to_chat(user, "<span class='notice'>The airwaves already have all of our DNA.</span>")
return FALSE
GLOB.hivemind_bank += chosen_dna
to_chat(user, "<span class='notice'>We channel the DNA of [chosen_dna.real_name] to the air.</span>")
SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("[name]"))
return TRUE
/datum/action/changeling/proc/dna_absorb(mob/user)
var/list/names = list()
for(var/datum/dna/DNA in GLOB.hivemind_bank)
if(!(DNA in cling.absorbed_dna))
names[DNA.real_name] = DNA
if(!length(names))
to_chat(user, "<span class='notice'>There's no new DNA to absorb from the air.</span>")
return
var/S = input("Select a DNA absorb from the air: ", "Absorb DNA", null) as null|anything in names
if(!S)
return
var/datum/dna/chosen_dna = names[S]
cling.store_dna(chosen_dna)
to_chat(user, "<span class='notice'>We absorb the DNA of [S] from the air.</span>")
SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("[name]"))
return TRUE
@@ -9,20 +9,9 @@
//Transform into a human.
/datum/action/changeling/humanform/sting_action(mob/living/carbon/human/user)
var/datum/changeling/changeling = user.mind.changeling
var/list/names = list()
for(var/datum/dna/DNA in (changeling.absorbed_dna+changeling.protected_dna))
names += "[DNA.real_name]"
var/chosen_name = input("Select the target DNA: ", "Target DNA", null) as null|anything in names
if(!chosen_name)
return
var/datum/dna/chosen_dna = changeling.GetDNA(chosen_name)
if(!chosen_dna)
return
if(!user)
return 0
var/datum/dna/chosen_dna = cling.select_dna("Select the target DNA: ", "Target DNA")
if(!chosen_dna || !user)
return FALSE
to_chat(user, "<span class='notice'>We transform our appearance.</span>")
user.dna.SetSEState(GLOB.monkeyblock,0,1)
singlemutcheck(user,GLOB.monkeyblock, MUTCHK_FORCED)
@@ -37,8 +26,7 @@
user.sync_organ_dna(1)
user.UpdateAppearance()
changeling.purchasedpowers -= src
//O.mind.changeling.purchasedpowers += new /datum/action/changeling/lesserform(null)
src.Remove(user)
cling.acquired_powers -= src
Remove(user)
SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("[name]"))
return 1
return TRUE
@@ -6,31 +6,29 @@
chemical_cost = 5
dna_cost = 1
genetic_damage = 3
req_human = 1
req_human = TRUE
power_type = CHANGELING_PURCHASABLE_POWER
//Transform into a monkey.
/datum/action/changeling/lesserform/sting_action(mob/living/carbon/human/user)
var/datum/changeling/changeling = user.mind.changeling
if(!user)
return 0
return FALSE
if(user.has_brain_worms())
to_chat(user, "<span class='warning'>We cannot perform this ability at the present time!</span>")
return
return FALSE
var/mob/living/carbon/human/H = user
if(!istype(H) || !H.dna.species.primitive_form)
to_chat(H, "<span class='warning'>We cannot perform this ability in this form!</span>")
return
return FALSE
H.visible_message("<span class='warning'>[H] transforms!</span>")
changeling.geneticdamage = 30
cling.genetic_damage = 30
to_chat(H, "<span class='warning'>Our genes cry out!</span>")
H.monkeyize()
var/datum/action/changeling/humanform/HF = new
changeling.purchasedpowers += HF
HF.Grant(user)
cling.give_power(new /datum/action/changeling/humanform)
SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("[name]"))
return 1
return TRUE
@@ -4,44 +4,44 @@
helptext = "If we find a human mad enough to support our cause, this can be a helpful tool to stay in touch."
button_icon_state = "hivemind_link"
chemical_cost = 0
dna_cost = 0
req_human = 1
power_type = CHANGELING_INNATE_POWER
req_human = TRUE
max_genetic_damage = 100
/datum/action/changeling/linglink/can_sting(mob/living/carbon/user)
if(!..())
return
var/datum/changeling/changeling = user.mind.changeling
return FALSE
var/obj/item/grab/G = user.get_active_hand()
if(changeling.islinking)
if(cling.is_linking)
to_chat(user, "<span class='warning'>We have already formed a link with the victim!</span>")
return
return FALSE
if(!istype(G))
to_chat(user, "<span class='warning'>We must be tightly grabbing a creature in our active hand to link with them!</span>")
return
return FALSE
if(G.state <= GRAB_AGGRESSIVE)
to_chat(user, "<span class='warning'>We must have a tighter grip to link with this creature!</span>")
return
return FALSE
if(iscarbon(G.affecting))
var/mob/living/carbon/target = G.affecting
if(!target.mind)
to_chat(user, "<span class='warning'>The victim has no mind to link to!</span>")
return
return FALSE
if(target.stat == DEAD)
to_chat(user, "<span class='warning'>The victim is dead, you cannot link to a dead mind!</span>")
return
if(target.mind.changeling)
return FALSE
if(target.mind.has_antag_datum(/datum/antagonist/changeling))
to_chat(user, "<span class='warning'>The victim is already a part of the hivemind!</span>")
return
return changeling.can_absorb_dna(user,target)
return FALSE
return cling.can_absorb_dna(target)
/datum/action/changeling/linglink/sting_action(mob/user)
var/datum/changeling/changeling = user.mind.changeling
var/obj/item/grab/G = user.get_active_hand()
var/mob/living/carbon/target = G.affecting
changeling.islinking = 1
for(var/stage = 1, stage<=3, stage++)
cling.is_linking = TRUE
SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("[name]"))
for(var/stage in 1 to 3)
switch(stage)
if(1)
to_chat(user, "<span class='notice'>This creature is compatible. We must hold still...</span>")
@@ -53,23 +53,25 @@
to_chat(target, "<span class='userdanger'>A migraine throbs behind your eyes, you hear yourself screaming - but your mouth has not opened!</span>")
for(var/mob/M in GLOB.mob_list)
if(GLOB.all_languages["Changeling"] in M.languages)
to_chat(M, "<i><font color=#800080>We can sense a foreign presence in the hivemind...</font></i>")
target.mind.linglink = 1
to_chat(M, "<span class='changeling'>We can sense a foreign presence in the hivemind...</span>")
target.mind.linglink = TRUE
target.add_language("Changeling")
target.say(":g AAAAARRRRGGGGGHHHHH!!")
to_chat(target, "<font color=#800040><span class='boldannounce'>You can now communicate in the changeling hivemind, say \":g message\" to communicate!</span>")
target.reagents.add_reagent("salbutamol", 40) // So they don't choke to death while you interrogate them
sleep(1800)
SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("[name]"))
if(!do_mob(user, target, 20))
addtimer(CALLBACK(src, .proc/end_link, user, target), 180 SECONDS)
if(!do_mob(user, target, 2 SECONDS))
to_chat(user, "<span class='warning'>Our link with [target] has ended!</span>")
target.remove_language("Changeling")
changeling.islinking = 0
target.mind.linglink = 0
return
cling.is_linking = FALSE
target.mind.linglink = FALSE
return FALSE
/datum/action/changeling/linglink/proc/end_link(mob/living/user, mob/living/carbon/target)
target.remove_language("Changeling")
changeling.islinking = 0
target.mind.linglink = 0
cling.is_linking = FALSE
target.mind.linglink = FALSE
to_chat(user, "<span class='notice'>You cannot sustain the connection any longer, your victim fades from the hivemind</span>")
to_chat(target, "<span class='userdanger'>The link cannot be sustained any longer, your connection to the hivemind has faded!</span>")
return TRUE
@@ -5,25 +5,26 @@
button_icon_state = "mimic_voice"
chemical_cost = 0 //constant chemical drain hardcoded
dna_cost = 1
req_human = 1
req_human = TRUE
power_type = CHANGELING_PURCHASABLE_POWER
// Fake Voice
/datum/action/changeling/mimicvoice/sting_action(mob/user)
var/datum/changeling/changeling=user.mind.changeling
if(changeling.mimicing)
changeling.mimicing = ""
changeling.chem_recharge_slowdown -= 0.5
if(cling.mimicing)
cling.mimicing = ""
cling.chem_recharge_slowdown -= 0.5
to_chat(user, "<span class='notice'>We return our vocal glands to their original position.</span>")
return
return FALSE
var/mimic_voice = stripped_input(user, "Enter a name to mimic.", "Mimic Voice", null, MAX_NAME_LEN)
if(!mimic_voice)
return
return FALSE
changeling.mimicing = mimic_voice
changeling.chem_recharge_slowdown += 0.5
cling.mimicing = mimic_voice
cling.chem_recharge_slowdown += 0.5
to_chat(user, "<span class='notice'>We shape our glands to take the voice of <b>[mimic_voice]</b>, this will stop us from regenerating chemicals while active.</span>")
to_chat(user, "<span class='notice'>Use this power again to return to our original voice and reproduce chemicals again.</span>")
SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("[name]"))
return TRUE
@@ -7,16 +7,13 @@
Armor
*/
//Parent to shields and blades because muh copypasted code.
/datum/action/changeling/weapon
name = "Organic Weapon"
desc = "Go tell a coder if you see this"
helptext = "Yell at coderbus"
chemical_cost = 1000
dna_cost = -1
genetic_damage = 1000
power_type = CHANGELING_UNOBTAINABLE_POWER
var/silent = FALSE
var/weapon_type
var/weapon_name_simple
@@ -39,7 +36,7 @@
/datum/action/changeling/weapon/sting_action(mob/user)
if(!user.drop_item())
to_chat(user, "[user.get_active_hand()] is stuck to your hand, you cannot grow a [weapon_name_simple] over it!")
return
return FALSE
var/obj/item/W = new weapon_type(user, silent)
user.put_in_hands(W)
return W
@@ -50,9 +47,8 @@
desc = "Go tell a coder if you see this"
helptext = "Yell at coderbus"
chemical_cost = 1000
dna_cost = -1
genetic_damage = 1000
power_type = CHANGELING_UNOBTAINABLE_POWER
var/helmet_type = /obj/item
var/suit_type = /obj/item
var/suit_name_simple = " "
@@ -61,9 +57,8 @@
var/blood_on_castoff = 0
/datum/action/changeling/suit/try_to_sting(mob/user, mob/target)
var/datum/changeling/changeling = user.mind.changeling
if(!ishuman(user) || !changeling)
return
if(!ishuman(user))
return FALSE
var/mob/living/carbon/human/H = user
if(istype(H.wear_suit, suit_type) || istype(H.head, helmet_type))
@@ -79,18 +74,18 @@
H.add_splatter_floor()
playsound(H.loc, 'sound/effects/splat.ogg', 50, 1) //So real sounds
changeling.geneticdamage += genetic_damage //Casting off a space suit leaves you weak for a few seconds.
changeling.chem_recharge_slowdown -= recharge_slowdown
return
cling.genetic_damage += genetic_damage //Casting off a space suit leaves you weak for a few seconds.
cling.chem_recharge_slowdown -= recharge_slowdown
return FALSE
..(H, target)
/datum/action/changeling/suit/sting_action(mob/living/carbon/human/user)
if(!user.unEquip(user.wear_suit))
to_chat(user, "\the [user.wear_suit] is stuck to your body, you cannot grow a [suit_name_simple] over it!")
return
return FALSE
if(!user.unEquip(user.head))
to_chat(user, "\the [user.head] is stuck on your head, you cannot grow a [helmet_name_simple] over it!")
return
return FALSE
user.unEquip(user.head)
user.unEquip(user.wear_suit)
@@ -98,9 +93,8 @@
user.equip_to_slot_if_possible(new suit_type(user), slot_wear_suit, TRUE, TRUE)
user.equip_to_slot_if_possible(new helmet_type(user), slot_head, TRUE, TRUE)
var/datum/changeling/changeling = user.mind.changeling
changeling.chem_recharge_slowdown += recharge_slowdown
return 1
cling.chem_recharge_slowdown += recharge_slowdown
return TRUE
//fancy headers yo
@@ -115,10 +109,11 @@
chemical_cost = 25
dna_cost = 2
genetic_damage = 10
req_human = 1
req_human = TRUE
max_genetic_damage = 20
weapon_type = /obj/item/melee/arm_blade
weapon_name_simple = "blade"
power_type = CHANGELING_PURCHASABLE_POWER
/obj/item/melee/arm_blade
name = "arm blade"
@@ -182,11 +177,12 @@
chemical_cost = 10
dna_cost = 2
genetic_damage = 5
req_human = 1
req_human = TRUE
max_genetic_damage = 10
weapon_type = /obj/item/gun/magic/tentacle
weapon_name_simple = "tentacle"
silent = TRUE
power_type = CHANGELING_PURCHASABLE_POWER
/obj/item/gun/magic/tentacle
name = "tentacle"
@@ -355,22 +351,18 @@
chemical_cost = 20
dna_cost = 1
genetic_damage = 12
req_human = 1
req_human = TRUE
max_genetic_damage = 20
weapon_type = /obj/item/shield/changeling
weapon_name_simple = "shield"
power_type = CHANGELING_PURCHASABLE_POWER
/datum/action/changeling/weapon/shield/sting_action(mob/user)
var/datum/changeling/changeling = user.mind.changeling //So we can read the absorbedcount.
if(!changeling)
return
var/obj/item/shield/changeling/S = ..(user)
if(!S)
return
S.remaining_uses = round(changeling.absorbedcount * 3)
return 1
return FALSE
S.remaining_uses = round(cling.absorbed_count * 3)
return TRUE
/obj/item/shield/changeling
name = "shield-like mass"
@@ -410,9 +402,9 @@
chemical_cost = 20
dna_cost = 2
genetic_damage = 8
req_human = 1
req_human = TRUE
max_genetic_damage = 20
power_type = CHANGELING_PURCHASABLE_POWER
suit_type = /obj/item/clothing/suit/space/changeling
helmet_type = /obj/item/clothing/head/helmet/space/changeling
suit_name_simple = "flesh shell"
@@ -458,9 +450,9 @@
chemical_cost = 25
dna_cost = 2
genetic_damage = 11
req_human = 1
req_human = TRUE
max_genetic_damage = 20
power_type = CHANGELING_PURCHASABLE_POWER
suit_type = /obj/item/clothing/suit/armor/changeling
helmet_type = /obj/item/clothing/head/helmet/changeling
suit_name_simple = "armor"
@@ -6,6 +6,7 @@
chemical_cost = 20
dna_cost = 1
req_stat = UNCONSCIOUS
power_type = CHANGELING_PURCHASABLE_POWER
//Heals the things that the other regenerative abilities don't.
/datum/action/changeling/panacea/sting_action(mob/user)
@@ -41,4 +42,4 @@
D.cure()
SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("[name]"))
return 1
return TRUE
@@ -3,11 +3,11 @@
desc = "We regenerate, healing all damage from our form."
button_icon_state = "revive"
req_stat = DEAD
always_keep = 1
bypass_fake_death = TRUE
//Revive from regenerative stasis
/datum/action/changeling/revive/sting_action(mob/living/carbon/user)
REMOVE_TRAIT(user, TRAIT_FAKEDEATH, "changeling")
REMOVE_TRAIT(user, TRAIT_FAKEDEATH, CHANGELING_TRAIT)
for(var/obj/item/grab/G in user.grabbed_by)
var/mob/living/carbon/M = G.assailant
user.visible_message("<span class='warning'>[user] suddenly hits [M] in the face and slips out of their grab!</span>")
@@ -18,7 +18,7 @@
user.updatehealth("revive sting")
user.update_blind_effects()
user.update_blurry_effects()
user.mind.changeling.regenerating = FALSE
cling.regenerating = FALSE
user.UpdateAppearance() //Ensures that the user's appearance matches their DNA.
to_chat(user, "<span class='notice'>We have regenerated.</span>")
@@ -26,8 +26,9 @@
user.regenerate_icons()
user.update_revive() //Handle waking up the changeling after the regenerative stasis has completed.
src.Remove(user)
cling.acquired_powers -= src
Remove(user)
user.med_hud_set_status()
user.med_hud_set_health()
SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("[name]"))
return 1
return TRUE
@@ -5,7 +5,8 @@
button_icon_state = "resonant_shriek"
chemical_cost = 30
dna_cost = 1
req_human = 1
req_human = TRUE
power_type = CHANGELING_PURCHASABLE_POWER
//A flashy ability, good for crowd control and sowing chaos.
/datum/action/changeling/resonant_shriek/sting_action(mob/user)
@@ -15,7 +16,7 @@
var/mob/living/carbon/human/H = M
if(H.check_ear_prot() >= HEARING_PROTECTION_TOTAL)
continue
if(!M.mind || !M.mind.changeling)
if(!M.mind || !ischangeling(M))
M.AdjustEarDamage(0, 30)
M.AdjustConfused(40 SECONDS)
M.Jitter(100 SECONDS)
@@ -31,7 +32,7 @@
L.break_light_tube()
SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("[name]"))
return 1
return TRUE
/datum/action/changeling/dissonant_shriek
name = "Dissonant Shriek"
@@ -39,6 +40,7 @@
button_icon_state = "dissonant_shriek"
chemical_cost = 30
dna_cost = 1
power_type = CHANGELING_PURCHASABLE_POWER
//A flashy ability, good for crowd control and sewing chaos.
/datum/action/changeling/dissonant_shriek/sting_action(mob/user)
@@ -46,4 +48,4 @@
L.on = 1
L.break_light_tube()
empulse(get_turf(user), 2, 4, 1)
return 1
return TRUE
@@ -6,12 +6,13 @@
chemical_cost = 45
dna_cost = 1
req_dna = 5
power_type = CHANGELING_PURCHASABLE_POWER
//Makes some spiderlings. Good for setting traps and causing general trouble.
/datum/action/changeling/spiders/sting_action(mob/user)
for(var/i=0, i<2, i++)
for(var/i in 1 to 2)
var/obj/structure/spider/spiderling/S = new(user.loc)
S.grow_as = /mob/living/simple_animal/hostile/poison/giant_spider/hunter
SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("[name]"))
return 1
return TRUE
@@ -8,8 +8,13 @@
button_icon_state = "strained_muscles"
chemical_cost = 0
dna_cost = 1
req_human = 1
req_human = TRUE
max_genetic_damage = 0
power_type = CHANGELING_PURCHASABLE_POWER
/datum/action/changeling/strained_muscles/Remove(mob/living/L)
L.remove_status_effect(STATUS_EFFECT_SPEEDLEGS)
..()
/datum/action/changeling/strained_muscles/sting_action(mob/living/carbon/user)
if(!user.has_status_effect(STATUS_EFFECT_SPEEDLEGS))
@@ -22,4 +27,4 @@
user.remove_status_effect(STATUS_EFFECT_SPEEDLEGS)
SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("[name]"))
return 1
return TRUE
@@ -5,34 +5,34 @@
button_icon_state = "mindswap"
chemical_cost = 40
dna_cost = 1
req_human = 1 //Monkeys can't grab
req_human = TRUE //Monkeys can't grab
power_type = CHANGELING_PURCHASABLE_POWER
/datum/action/changeling/swap_form/can_sting(mob/living/carbon/user)
if(!..())
return
return FALSE
var/obj/item/grab/G = user.get_active_hand()
if(!istype(G) || (G.state < GRAB_AGGRESSIVE))
to_chat(user, "<span class='warning'>We must have an aggressive grab on creature in our active hand to do this!</span>")
return
return FALSE
var/mob/living/carbon/human/target = G.affecting
if(HAS_TRAIT(target, TRAIT_BADDNA) || HAS_TRAIT(target, TRAIT_HUSK) || HAS_TRAIT(target, TRAIT_SKELETONIZED))
to_chat(user, "<span class='warning'>DNA of [target] is ruined beyond usability!</span>")
return
return FALSE
if(!istype(target) || !target.mind || issmall(target) || HAS_TRAIT(target, TRAIT_GENELESS))
to_chat(user, "<span class='warning'>[target] is not compatible with this ability.</span>")
return
if(target.mind.changeling)
return FALSE
if(ischangeling(target))
to_chat(user, "<span class='warning'>We are unable to swap forms with another changeling!</span>")
return
return FALSE
if(target.has_brain_worms() || user.has_brain_worms())
to_chat(user, "<span class='warning'>A foreign presence repels us from this body!</span>")
return
return 1
return FALSE
return TRUE
/datum/action/changeling/swap_form/sting_action(mob/living/carbon/user)
var/obj/item/grab/G = user.get_active_hand()
var/mob/living/carbon/human/target = G.affecting
var/datum/changeling/changeling = user.mind.changeling
to_chat(user, "<span class='notice'>We tighten our grip. We must hold still....</span>")
target.Jitter(1000 SECONDS)
@@ -44,18 +44,15 @@
to_chat(target, "<span class='userdanger'>[user] tightens [user.p_their()] grip as a painful sensation invades your body.</span>")
var/lingpowers = list()
for(var/power in changeling.purchasedpowers)
lingpowers += power
var/datum/dna/DNA = cling.get_dna(user.dna)
cling.absorbed_dna -= DNA
cling.protected_dna -= DNA
cling.absorbed_count--
if(!cling.get_dna(target.dna))
cling.absorb_dna(target)
cling.trim_dna()
changeling.absorbed_dna -= changeling.find_dna(user.dna)
changeling.protected_dna -= changeling.find_dna(user.dna)
changeling.absorbedcount -= 1
if(!changeling.has_dna(target.dna))
changeling.absorb_dna(target, user)
changeling.trim_dna()
var/mob/dead/observer/ghost = target.ghostize(0)
var/mob/dead/observer/ghost = target.ghostize(FALSE)
user.mind.transfer_to(target)
if(ghost && ghost.mind)
ghost.mind.transfer_to(user)
@@ -63,17 +60,9 @@
user.key = ghost.key
qdel(ghost)
user.Paralyse(4 SECONDS)
target.add_language("Changeling")
user.remove_language("Changeling")
user.regenerate_icons()
if(target.stat == DEAD && target.suiciding) //If Target committed suicide, unset flag for User
target.suiciding = 0
for(var/power in lingpowers)
var/datum/action/changeling/S = power
target.mind.changeling.purchasedpowers += S
if(istype(S) && S.needs_button)
S.Grant(target)
target.suiciding = FALSE
to_chat(target, "<span class='warning'>Our genes cry out as we swap our [user] form for [target].</span>")
return 1
return TRUE
@@ -1,70 +1,68 @@
/datum/action/changeling/sting
name = "Tiny Prick"
desc = "Stabby stabby"
power_type = CHANGELING_UNOBTAINABLE_POWER
var/sting_icon = null
/// A middle click override used to intercept changeling stings performed on a target.
var/datum/middleClickOverride/callback_invoker/click_override
/datum/action/changeling/sting/New(Target)
. = ..()
click_override = new /datum/middleClickOverride/callback_invoker(CALLBACK(src, .proc/try_to_sting))
click_override = new(CALLBACK(src, .proc/try_to_sting))
/datum/action/changeling/sting/Destroy(force, ...)
if(cling.owner.current.middleClickOverride == click_override)
cling.owner.current.middleClickOverride = null
QDEL_NULL(click_override)
if(cling.chosen_sting == src)
cling.chosen_sting = null
return ..()
/datum/action/changeling/sting/Trigger()
var/mob/user = owner
if(!user || !user.mind || !user.mind.changeling)
return
if(!(user.mind.changeling.chosen_sting))
set_sting(user)
if(!cling.chosen_sting)
set_sting()
else
unset_sting(user)
return
unset_sting()
/datum/action/changeling/sting/proc/set_sting(mob/living/user)
to_chat(user, "<span class='notice'>We prepare our sting, use alt+click or middle mouse button on target to sting them.</span>")
/datum/action/changeling/sting/proc/set_sting()
var/mob/living/user = owner
to_chat(user, "<span class='warning'>We prepare our sting, use alt+click or middle mouse button on a target to sting them.</span>")
user.middleClickOverride = click_override
user.mind.changeling.chosen_sting = src
cling.chosen_sting = src
user.hud_used.lingstingdisplay.icon_state = sting_icon
user.hud_used.lingstingdisplay.invisibility = 0
/datum/action/changeling/sting/proc/unset_sting(mob/living/user)
/datum/action/changeling/sting/proc/unset_sting()
var/mob/living/user = owner
to_chat(user, "<span class='warning'>We retract our sting, we can't sting anyone for now.</span>")
user.middleClickOverride = null
user.mind.changeling.chosen_sting = null
cling.chosen_sting = null
user.hud_used.lingstingdisplay.icon_state = null
user.hud_used.lingstingdisplay.invisibility = 101
/mob/living/carbon/proc/unset_sting()
if(mind && mind.changeling && mind.changeling.chosen_sting)
mind.changeling.chosen_sting.unset_sting(src)
/datum/action/changeling/sting/can_sting(mob/user, mob/target)
if(!..())
return
if(!user.mind.changeling.chosen_sting)
if(!..() || !iscarbon(target) || !isturf(user.loc) || !AStar(user, target.loc, /turf/proc/Distance, cling.sting_range, simulated_only = 0))
return FALSE
if(!cling.chosen_sting)
to_chat(user, "We haven't prepared our sting yet!")
if(!iscarbon(target))
return
return FALSE
if(ismachineperson(target))
to_chat(user, "<span class='warning'>This won't work on synthetics.</span>")
return
if(!isturf(user.loc))
return
if(!AStar(user, target.loc, /turf/proc/Distance, user.mind.changeling.sting_range, simulated_only = 0))
return
if(target.mind && target.mind.changeling)
sting_feedback(user,target)
take_chemical_cost(user.mind.changeling)
return
return 1
return FALSE
if(ischangeling(target))
sting_feedback(user, target)
take_chemical_cost()
return FALSE
return TRUE
/datum/action/changeling/sting/sting_feedback(mob/user, mob/target)
if(!target)
return
to_chat(user, "<span class='notice'>We stealthily sting [target.name].</span>")
if(target.mind && target.mind.changeling)
if(ischangeling(target))
to_chat(target, "<span class='warning'>You feel a tiny prick.</span>")
add_attack_logs(user, target, "Unsuccessful sting (changeling)")
return 1
return TRUE
/datum/action/changeling/sting/extract_dna
name = "Extract DNA Sting"
@@ -73,18 +71,18 @@
button_icon_state = "sting_extract"
sting_icon = "sting_extract"
chemical_cost = 25
dna_cost = 0
power_type = CHANGELING_INNATE_POWER
/datum/action/changeling/sting/extract_dna/can_sting(mob/user, mob/target)
if(..())
return user.mind.changeling.can_absorb_dna(user, target)
return cling.can_absorb_dna(target)
/datum/action/changeling/sting/extract_dna/sting_action(mob/user, mob/living/carbon/human/target)
add_attack_logs(user, target, "Extraction sting (changeling)")
if(!(user.mind.changeling.has_dna(target.dna)))
user.mind.changeling.absorb_dna(target, user)
if(!cling.get_dna(target.dna))
cling.absorb_dna(target)
SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("[name]"))
return 1
return TRUE
/datum/action/changeling/sting/mute
name = "Mute Sting"
@@ -94,12 +92,13 @@
sting_icon = "sting_mute"
chemical_cost = 20
dna_cost = 2
power_type = CHANGELING_PURCHASABLE_POWER
/datum/action/changeling/sting/mute/sting_action(mob/user, mob/living/carbon/target)
add_attack_logs(user, target, "Mute sting (changeling)")
target.AdjustSilence(60 SECONDS)
SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("[name]"))
return 1
return TRUE
/datum/action/changeling/sting/blind
name = "Blind Sting"
@@ -109,6 +108,7 @@
sting_icon = "sting_blind"
chemical_cost = 25
dna_cost = 1
power_type = CHANGELING_PURCHASABLE_POWER
/datum/action/changeling/sting/blind/sting_action(mob/living/user, mob/living/target)
add_attack_logs(user, target, "Blind sting (changeling)")
@@ -117,7 +117,7 @@
target.EyeBlind(40 SECONDS)
target.EyeBlurry(80 SECONDS)
SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("[name]"))
return 1
return TRUE
/datum/action/changeling/sting/LSD
name = "Hallucination Sting"
@@ -127,14 +127,17 @@
sting_icon = "sting_lsd"
chemical_cost = 10
dna_cost = 1
power_type = CHANGELING_PURCHASABLE_POWER
/datum/action/changeling/sting/LSD/sting_action(mob/user, mob/living/carbon/target)
add_attack_logs(user, target, "LSD sting (changeling)")
spawn(rand(300,600))
if(target)
target.Hallucinate(400 SECONDS)
addtimer(CALLBACK(src, .proc/start_hallucinations, target, 400 SECONDS), rand(30 SECONDS, 60 SECONDS))
SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("[name]"))
return 1
return TRUE
/datum/action/changeling/sting/LSD/proc/start_hallucinations(mob/living/carbon/target, amount)
if(!QDELETED(target))
target.Hallucinate(amount)
/datum/action/changeling/sting/cryo //Enable when mob cooling is fixed so that frostoil actually makes you cold, instead of mostly just hungry.
name = "Cryogenic Sting"
@@ -144,6 +147,7 @@
sting_icon = "sting_cryo"
chemical_cost = 15
dna_cost = 2
power_type = CHANGELING_PURCHASABLE_POWER
/datum/action/changeling/sting/cryo/sting_action(mob/user, mob/target)
add_attack_logs(user, target, "Cryo sting (changeling)")
@@ -151,4 +155,4 @@
target.reagents.add_reagent("frostoil", 30)
target.reagents.add_reagent("ice", 30)
SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("[name]"))
return 1
return TRUE
@@ -0,0 +1,21 @@
/datum/action/changeling/transform
name = "Transform"
desc = "We take on the appearance and voice of one we have absorbed. Costs 5 chemicals."
button_icon_state = "transform"
chemical_cost = 5
power_type = CHANGELING_INNATE_POWER
req_dna = 1
req_human = TRUE
max_genetic_damage = 3
//Change our DNA to that of somebody we've absorbed.
/datum/action/changeling/transform/sting_action(mob/living/carbon/human/user)
var/datum/dna/chosen_dna = cling.select_dna("Select the target DNA: ", "Target DNA")
if(!chosen_dna)
return FALSE
transform_dna(user, chosen_dna)
cling.update_languages()
SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("[name]"))
return TRUE
+2 -2
View File
@@ -269,7 +269,7 @@
if(!istype(user) || !user)
return
if(user.mind) //Prevents changelings and vampires from being able to learn it
if(user.mind.changeling) //Changelings
if(ischangeling(user))
to_chat(user, "<span class ='warning'>We try multiple times, but we are not able to comprehend the contents of the scroll!</span>")
return
else if(user.mind.has_antag_datum(/datum/antagonist/vampire)) //Vampires
@@ -293,7 +293,7 @@
if(!istype(user) || !user)
return
if(user.mind) //Prevents changelings and vampires from being able to learn it
if(user.mind.changeling) //Changelings
if(ischangeling(user))
to_chat(user, "<span class='warning'>We try multiple times, but we simply cannot grasp the basics of CQC!</span>")
return
else if(user.mind.has_antag_datum(/datum/antagonist/vampire)) //Vampires
+1 -3
View File
@@ -94,9 +94,7 @@
// Not-very-datumized antags follow
// Associative list of antag name => whether this mind is this antag
var/other_antags = list(
"Changeling" = (mind.changeling != null)
)
var/list/other_antags = list()
if(SSticker && SSticker.mode)
other_antags += list(
"Blob" = (mind.special_role == SPECIAL_ROLE_BLOB),
+4 -3
View File
@@ -530,9 +530,10 @@
follow = TRUE
/datum/language/ling/broadcast(mob/living/speaker, message, speaker_mask)
if(speaker.mind && speaker.mind.changeling)
..(speaker, message, speaker.mind.changeling.changelingID)
else if(speaker.mind && speaker.mind.linglink)
var/datum/antagonist/changeling/cling = speaker.mind?.has_antag_datum(/datum/antagonist/changeling)
if(cling)
..(speaker, message, cling.changelingID)
else if(speaker.mind?.linglink)
..()
else
..(speaker,message)
@@ -208,9 +208,10 @@
stat("Chemicals", B.chemicals)
if(mind)
if(mind.changeling)
stat("Chemical Storage", "[mind.changeling.chem_charges]/[mind.changeling.chem_storage]")
stat("Absorbed DNA", mind.changeling.absorbedcount)
var/datum/antagonist/changeling/cling = mind.has_antag_datum(/datum/antagonist/changeling)
if(cling)
stat("Chemical Storage", "[cling.chem_charges]/[cling.chem_storage]")
stat("Absorbed DNA", cling.absorbed_count)
var/datum/antagonist/vampire/V = mind.has_antag_datum(/datum/antagonist/vampire)
if(V)
@@ -757,17 +757,6 @@
if(!has_embedded_objects())
clear_alert("embeddedobject")
/mob/living/carbon/human/handle_changeling()
if(mind.changeling)
mind.changeling.regenerate(src)
if(hud_used)
hud_used.lingchemdisplay.invisibility = 0
hud_used.lingchemdisplay.maptext = "<div align='center' valign='middle' style='position:relative; top:0px; left:6px'><font face='Small Fonts' color='#dd66dd'>[round(mind.changeling.chem_charges)]</font></div>"
else
if(hud_used)
hud_used.lingchemdisplay.invisibility = 101
/mob/living/carbon/human/proc/handle_pulse(times_fired)
if(times_fired % 5 == 1)
return pulse //update pulse every 5 life ticks (~1 tick/sec, depending on server load)
+4 -2
View File
@@ -83,8 +83,10 @@
if(has_changer)
return has_changer
if(mind && mind.changeling && mind.changeling.mimicing)
return mind.changeling.mimicing
if(mind)
var/datum/antagonist/changeling/cling = mind.has_antag_datum(/datum/antagonist/changeling)
if(cling?.mimicing)
return cling.mimicing
if(GetSpecialVoice())
return GetSpecialVoice()
+1 -5
View File
@@ -25,8 +25,7 @@
if(LAZYLEN(processing_patches))
handle_patches()
if(mind)
handle_changeling()
handle_wetness(times_fired)
// Increase germ_level regularly
@@ -221,9 +220,6 @@
/mob/living/carbon/proc/handle_blood()
return
/mob/living/carbon/proc/handle_changeling()
return
/mob/living/carbon/handle_mutations_and_radiation()
radiation -= min(radiation, RAD_LOSS_PER_TICK)
if(radiation > RAD_MOB_SAFE)
+9 -9
View File
@@ -204,21 +204,21 @@
//basic fast checks go first. When overriding this proc, I recommend calling ..() at the end.
var/turf/T = get_turf(src)
if(!T)
return 0
return FALSE
if(!is_level_reachable(T.z))
return 0
if(user != null && src == user)
return 0
return FALSE
if(!isnull(user) && src == user)
return FALSE
if(invisibility || alpha == 0)//cloaked
return 0
if(digitalcamo)
return 0
return FALSE
if(HAS_TRAIT(src, TRAIT_AI_UNTRACKABLE))
return FALSE
// Now, are they viewable by a camera? (This is last because it's the most intensive check)
if(!near_camera(src))
return 0
return FALSE
return 1
return TRUE
//mob verbs are a lot faster than object verbs
//for more info on why this is not atom/pull, see examinate() in mob.dm
-8
View File
@@ -18,14 +18,6 @@
if(ranged_ability)
ranged_ability.add_ranged_ability(src, "<span class='notice'>You currently have <b>[ranged_ability]</b> active!</span>")
//for when action buttons are lost and need to be regained, such as when the mind enters a new mob
var/datum/changeling/changeling = usr.mind.changeling
if(changeling)
for(var/power in changeling.purchasedpowers)
var/datum/action/changeling/S = power
if(istype(S) && S.needs_button)
S.Grant(src)
//Should update regardless of if we can ventcrawl, since we can end up in pipes in other ways.
update_pipe_vision()
+1 -1
View File
@@ -4,7 +4,7 @@
SSticker.mode.remove_revolutionary(mind, 1)
SSticker.mode.remove_cultist(mind, 1)
SSticker.mode.remove_wizard(mind)
SSticker.mode.remove_changeling(mind)
mind.remove_antag_datum(/datum/antagonist/changeling)
mind.remove_antag_datum(/datum/antagonist/vampire)
SSticker.mode.remove_abductor(mind)
..()
@@ -56,7 +56,7 @@
return
Infect(target)
to_chat(src, "<span class='userdanger'>With our egg laid, our death approaches rapidly...</span>")
addtimer(CALLBACK(src, .proc/death), 100)
addtimer(CALLBACK(src, .proc/death), 10 SECONDS)
/obj/item/organ/internal/body_egg/changeling_egg
name = "changeling egg"
@@ -70,7 +70,7 @@
time++
if(time >= EGG_INCUBATION_TIME)
Pop()
remove(owner)
STOP_PROCESSING(SSobj, src)
qdel(src)
/obj/item/organ/internal/body_egg/changeling_egg/proc/Pop()
@@ -82,17 +82,25 @@
if(origin && origin.current && (origin.current.stat == DEAD))
origin.transfer_to(M)
if(!origin.changeling)
M.make_changeling()
if(origin.changeling.can_absorb_dna(M, owner))
origin.changeling.absorb_dna(owner, M)
var/datum/antagonist/changeling/cling = M.mind.has_antag_datum(/datum/antagonist/changeling)
if(cling.can_absorb_dna(owner))
cling.absorb_dna(owner)
var/datum/action/changeling/humanform/HF = new
HF.Grant(M)
for(var/power in origin.changeling.purchasedpowers)
var/datum/action/changeling/S = power
if(istype(S) && S.needs_button)
S.Grant(M)
cling.update_languages()
// When they became a headslug, power typepaths were added to this list, so we need to make new ones from the paths.
for(var/power_path in cling.acquired_powers)
cling.give_power(new power_path, M, FALSE)
cling.acquired_powers -= power_path
var/datum/action/changeling/evolution_menu/E = locate() in cling.acquired_powers
// Add purchasable powers they have back to the evolution menu's purchased list.
for(var/datum/action/changeling/power as anything in cling.acquired_powers)
if(power.power_type == CHANGELING_PURCHASABLE_POWER)
E.purchased_abilities += power.type
cling.give_power(new /datum/action/changeling/humanform)
M.key = origin.key
owner.gib()
-2
View File
@@ -157,8 +157,6 @@
var/area/lastarea = null
var/digitalcamo = 0 // Can they be tracked by the AI?
var/has_unlimited_silicon_privilege = 0 // Can they interact with station electronics
var/atom/movable/remote_control //Calls relaymove() to whatever it is
+1 -5
View File
@@ -242,16 +242,12 @@
return
if(!(M.status_flags & CANPARALYSE))
return
if(M.mind && M.mind.changeling && M.mind.changeling.regenerating) //no messing with changeling's fake death
return
M.emote("deathgasp")
ADD_TRAIT(M, TRAIT_FAKEDEATH, id)
M.updatehealth("fakedeath reagent")
/datum/reagent/proc/fakerevive(mob/living/M)
if(!HAS_TRAIT(M, TRAIT_FAKEDEATH))
return
if(M.mind && M.mind.changeling && M.mind.changeling.regenerating)
if(!HAS_TRAIT_FROM(M, TRAIT_FAKEDEATH, id))
return
if(M.resting)
M.StopResting()