mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-08-22 19:47:22 +01:00
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:
@@ -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"
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
+26
-49
@@ -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
|
||||
+5
-8
@@ -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)
|
||||
+2
-1
@@ -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
|
||||
+3
-4
@@ -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)
|
||||
+9
-4
@@ -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
|
||||
+2
-1
@@ -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)
|
||||
+8
-9
@@ -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 ..()
|
||||
+1
@@ -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,
|
||||
+9
-2
@@ -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
|
||||
+6
-18
@@ -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
|
||||
+8
-10
@@ -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
|
||||
+28
-26
@@ -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
|
||||
+10
-9
@@ -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
|
||||
+25
-33
@@ -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"
|
||||
+2
-1
@@ -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
|
||||
+6
-5
@@ -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
|
||||
+6
-4
@@ -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
|
||||
+3
-2
@@ -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
|
||||
+7
-2
@@ -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
|
||||
+20
-31
@@ -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
|
||||
+50
-46
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user