diff --git a/baystation12.dme b/baystation12.dme index cbc43ffe2b6b0..6eddf79a8fd9d 100644 --- a/baystation12.dme +++ b/baystation12.dme @@ -34,6 +34,7 @@ #include "code\__defines\atmos.dm" #include "code\__defines\atmospherics.dm" #include "code\__defines\byond_tracy.dm" +#include "code\__defines\changeling.dm" #include "code\__defines\chemistry.dm" #include "code\__defines\client.dm" #include "code\__defines\colors.dm" @@ -348,6 +349,8 @@ #include "code\datums\item_modifiers\space_suits.dm" #include "code\datums\item_modifiers\~defines.dm" #include "code\datums\licences\license.dm" +#include "code\datums\managed_browsers\_managed_browser.dm" +#include "code\datums\managed_browsers\changelingevolution.dm" #include "code\datums\mind\memory.dm" #include "code\datums\mind\mind.dm" #include "code\datums\move_intent\move_intent.dm" @@ -610,8 +613,48 @@ #include "code\game\gamemodes\calamity\calamity.dm" #include "code\game\gamemodes\changeling\absorbed_dna.dm" #include "code\game\gamemodes\changeling\changeling.dm" +#include "code\game\gamemodes\changeling\changeling_evolutiontree.dm" #include "code\game\gamemodes\changeling\changeling_powers.dm" -#include "code\game\gamemodes\changeling\modularchangling.dm" +#include "code\game\gamemodes\changeling\generic_equip_procs.dm" +#include "code\game\gamemodes\changeling\powers\absorb.dm" +#include "code\game\gamemodes\changeling\powers\armblade.dm" +#include "code\game\gamemodes\changeling\powers\armor.dm" +#include "code\game\gamemodes\changeling\powers\augmented_eyesight.dm" +#include "code\game\gamemodes\changeling\powers\bioelectrogenesis.dm" +#include "code\game\gamemodes\changeling\powers\blind_sting.dm" +#include "code\game\gamemodes\changeling\powers\boost_range.dm" +#include "code\game\gamemodes\changeling\powers\cryo_sting.dm" +#include "code\game\gamemodes\changeling\powers\darkvision.dm" +#include "code\game\gamemodes\changeling\powers\deaf_sting.dm" +#include "code\game\gamemodes\changeling\powers\death_sting.dm" +#include "code\game\gamemodes\changeling\powers\delayed_toxin_sting.dm" +#include "code\game\gamemodes\changeling\powers\electric_lockpick.dm" +#include "code\game\gamemodes\changeling\powers\endoarmor.dm" +#include "code\game\gamemodes\changeling\powers\engorged_glands.dm" +#include "code\game\gamemodes\changeling\powers\enrage.dm" +#include "code\game\gamemodes\changeling\powers\escape_restraints.dm" +#include "code\game\gamemodes\changeling\powers\extract_dna_sting.dm" +#include "code\game\gamemodes\changeling\powers\fabricate_clothing.dm" +#include "code\game\gamemodes\changeling\powers\fake_death.dm" +#include "code\game\gamemodes\changeling\powers\false_identity.dm" +#include "code\game\gamemodes\changeling\powers\fleshmend.dm" +#include "code\game\gamemodes\changeling\powers\hivemind.dm" +#include "code\game\gamemodes\changeling\powers\lsd_sting.dm" +#include "code\game\gamemodes\changeling\powers\mimic_voice.dm" +#include "code\game\gamemodes\changeling\powers\panacea.dm" +#include "code\game\gamemodes\changeling\powers\rapid_regen.dm" +#include "code\game\gamemodes\changeling\powers\rapid_synthesis.dm" +#include "code\game\gamemodes\changeling\powers\reattach_limb.dm" +#include "code\game\gamemodes\changeling\powers\recursive_enhancement.dm" +#include "code\game\gamemodes\changeling\powers\reinforced_tendons.dm" +#include "code\game\gamemodes\changeling\powers\revive.dm" +#include "code\game\gamemodes\changeling\powers\self_respiration.dm" +#include "code\game\gamemodes\changeling\powers\shriek.dm" +#include "code\game\gamemodes\changeling\powers\silence_sting.dm" +#include "code\game\gamemodes\changeling\powers\synaptizine_overdose.dm" +#include "code\game\gamemodes\changeling\powers\tentacle.dm" +#include "code\game\gamemodes\changeling\powers\transform.dm" +#include "code\game\gamemodes\changeling\powers\visible_camouflage.dm" #include "code\game\gamemodes\cult\cult.dm" #include "code\game\gamemodes\cult\cult_items.dm" #include "code\game\gamemodes\cult\cult_structures.dm" @@ -1532,7 +1575,10 @@ #include "code\modules\augment\passive\armor.dm" #include "code\modules\augment\passive\boost.dm" #include "code\modules\augment\passive\fluff.dm" +#include "code\modules\augment\passive\ling_lenses.dm" +#include "code\modules\augment\passive\lingcore.dm" #include "code\modules\augment\passive\nanoaura.dm" +#include "code\modules\augment\passive\ragecore.dm" #include "code\modules\augment\passive\boost\muscle.dm" #include "code\modules\augment\passive\boost\reflex.dm" #include "code\modules\augment\passive\boost\shooting.dm" diff --git a/code/__defines/changeling.dm b/code/__defines/changeling.dm new file mode 100644 index 0000000000000..7323244d7b72e --- /dev/null +++ b/code/__defines/changeling.dm @@ -0,0 +1,2 @@ +#define ABSORB_NONLETHAL 1 +#define ABSORB_LETHAL 2 diff --git a/code/__defines/gamemode.dm b/code/__defines/gamemode.dm index 34f171523147f..ef75f71f548d0 100644 --- a/code/__defines/gamemode.dm +++ b/code/__defines/gamemode.dm @@ -97,6 +97,9 @@ #define Sp_CHARGES "charges" #define Sp_HOLDVAR "holdervar" +//changeling cost +#define CHANGELING_STASIS_COST 20 + //Voting-related #define VOTE_PROCESS_ABORT 1 #define VOTE_PROCESS_COMPLETE 2 @@ -105,3 +108,14 @@ #define VOTE_STATUS_PREVOTE 1 #define VOTE_STATUS_ACTIVE 2 #define VOTE_STATUS_COMPLETE 3 + +/* +Changeling Defines +*/ +#define CHANGELING_POWER_INHERENT "Inherent" +#define CHANGELING_POWER_ARMOR "Armor" +#define CHANGELING_POWER_STINGS "Stings" +#define CHANGELING_POWER_SHRIEKS "Shrieks" +#define CHANGELING_POWER_HEALTH "Health" +#define CHANGELING_POWER_ENHANCEMENTS "Enhancements" +#define CHANGELING_POWER_WEAPONS "Weapons" diff --git a/code/_helpers/mobs.dm b/code/_helpers/mobs.dm index 187145f271bac..120c1ccf5faa9 100644 --- a/code/_helpers/mobs.dm +++ b/code/_helpers/mobs.dm @@ -103,6 +103,52 @@ /proc/get_exposed_defense_zone(atom/movable/target) return pick(BP_HEAD, BP_L_HAND, BP_R_HAND, BP_L_FOOT, BP_R_FOOT, BP_L_ARM, BP_R_ARM, BP_L_LEG, BP_R_LEG, BP_CHEST, BP_GROIN) +/proc/do_mob(mob/user , mob/target, time = 30, target_zone = 0, uninterruptible = FALSE, progress = TRUE, ignore_movement = FALSE, incapacitation_affected = TRUE) + if(!user || !target) + return 0 + var/user_loc = user.loc + var/target_loc = target.loc + + var/holding = user.get_active_hand() + var/datum/progressbar/private/progbar + if (progress) + progbar = new(user, time, target) + + var/endtime = world.time+time + var/starttime = world.time + . = TRUE + while (world.time < endtime) + stoplag(1) + if (progress) + progbar.update(world.time - starttime) + if(!user || !target) + . = FALSE + break + if(uninterruptible) + continue + + if(!user || (user.incapacitated() && incapacitation_affected)) + . = FALSE + break + + if(user.loc != user_loc && !ignore_movement) + . = FALSE + break + + if(target.loc != target_loc && !ignore_movement) + . = FALSE + break + + if(user.get_active_hand() != holding) + . = FALSE + break + + if(target_zone && user.zone_sel.selecting != target_zone) + . = FALSE + break + + if (progbar) + qdel(progbar) /// Integer. Unique sequential ID from the `do_after` proc used to validate `DO_USER_UNIQUE_ACT` flag checks. /mob/var/do_unique_user_handle = 0 diff --git a/code/_onclick/hud/ability_screen_objects.dm b/code/_onclick/hud/ability_screen_objects.dm index 7b07703e89c74..43bf4f9dbbae6 100644 --- a/code/_onclick/hud/ability_screen_objects.dm +++ b/code/_onclick/hud/ability_screen_objects.dm @@ -252,9 +252,9 @@ //Changeling Abilities /obj/screen/ability/verb_based/changeling - icon_state = "ling_spell_base" - background_base_state = "ling" - + icon_state = "const_spell_base" + background_base_state = "const" +//use this to force add powers /obj/screen/movable/ability_master/proc/add_ling_ability(object_given, verb_given, name_given, ability_icon_given, arguments) if(!object_given) message_admins("ERROR: add_ling_ability() was not given an object in its arguments.") diff --git a/code/datums/extensions/chameleon.dm b/code/datums/extensions/chameleon.dm index ffacbddc2423e..36b5b2b2aa833 100644 --- a/code/datums/extensions/chameleon.dm +++ b/code/datums/extensions/chameleon.dm @@ -23,6 +23,7 @@ if (!chameleon_choices) var/chameleon_type = chameleon_base_type || holder.parent_type chameleon_choices = LAZYACCESS(chameleon_choices_by_type, chameleon_type) + if (!chameleon_choices) chameleon_choices = GenerateChameleonChoices(chameleon_type) LAZYSET(chameleon_choices_by_type, chameleon_type, chameleon_choices) @@ -69,8 +70,10 @@ return null /datum/extension/chameleon/proc/GenerateChameleonChoices(basetype) + var/choices = list() var/types = islist(basetype) ? basetype : typesof(basetype) + for (var/path in types) AddChameleonChoice(choices, path) return sortAssoc(choices) diff --git a/code/datums/managed_browsers/_managed_browser.dm b/code/datums/managed_browsers/_managed_browser.dm new file mode 100644 index 0000000000000..02fd99615d424 --- /dev/null +++ b/code/datums/managed_browsers/_managed_browser.dm @@ -0,0 +1,52 @@ +GLOBAL_VAR(managed_browser_id_ticker) + +// This holds information on managing a /datum/browser object. +// Managing can include things like persisting the state of specific information inside of this object, receiving Topic() calls, or deleting itself when the window is closed. +// This is useful for browser windows to be able to stand 'on their own' instead of being tied to something in the game world, like an object or mob. +/datum/managed_browser + var/client/my_client = null + var/browser_id = null + var/base_browser_id = null + + var/title = null + var/size_x = 200 + var/size_y = 400 + + var/display_when_created = TRUE + +/datum/managed_browser/New(client/new_client) + if(!new_client) + crash_with("Managed browser object was not given a client.") + return + if(!base_browser_id) + crash_with("Managed browser object does not have a base browser id defined in its type.") + return + + my_client = new_client + browser_id = "[base_browser_id]-[GLOB.managed_browser_id_ticker++]" + + if(display_when_created) + display() + +/datum/managed_browser/Destroy() + my_client = null + return ..() + +// Override if you want to have the browser title change conditionally. +// Otherwise it's easier to just change the title variable directly. +/datum/managed_browser/proc/get_title() + return title + +// Override to display the html information. +// It is suggested to build it with a list, and use list.Join() at the end. +// This helps prevent excessive concatination, which helps preserves BYOND's string tree from becoming a laggy mess. +/datum/managed_browser/proc/get_html() + return + +/datum/managed_browser/proc/display() + interact(get_html(), get_title(), my_client) + +/datum/managed_browser/proc/interact(html, title, client/C) + var/datum/browser/popup = new(C.mob, browser_id, title, size_x, size_y, src) + popup.set_content(html) + popup.open() diff --git a/code/datums/managed_browsers/changelingevolution.dm b/code/datums/managed_browsers/changelingevolution.dm new file mode 100644 index 0000000000000..340702906c25c --- /dev/null +++ b/code/datums/managed_browsers/changelingevolution.dm @@ -0,0 +1,165 @@ +/client + var/datum/managed_browser/changelingevolution/changelingevolution = null + +/datum/managed_browser/changelingevolution + base_browser_id = "evolution_tree" + title = "Evolution Tree" + size_x = 480 + size_y = 600 + var/textbody = null + +/datum/managed_browser/changelingevolution/New(client/new_client) + if(!new_client.mob || !new_client.mob.mind || !new_client.mob.mind.changeling) + message_admins("[new_client] tried to access changeling evolutions while not changeling.") + qdel(src) + + ..() + +/datum/managed_browser/changelingevolution/Destroy() + if(my_client) + my_client.changelingevolution = null + return ..() + +/datum/managed_browser/changelingevolution/get_html() + var/list/dat = list("") + var/geneticpoints_current = my_client.mob.mind.changeling.geneticpoints + var/geneticpoints_max = my_client.mob.mind.changeling.max_geneticpoints + + dat += "
Genetic Points Available: [geneticpoints_current] / [geneticpoints_max]
" + dat += "Obtain more by feeding on your own kind.

" + dat += "What am I?

" + dat += "Inherent" + dat += "Armor" + dat += "Weapons" + dat += "Stings" + dat += "Shrieks" + dat += "Health" + dat += "Enhancements
" + if(textbody) + dat += "" + dat += "[textbody]" + dat += "
" + dat += "" + + return dat.Join() + +/datum/managed_browser/changelingevolution/Topic(href, href_list[]) + if(!my_client) + return FALSE + + if(href_list["close"]) + return + + if(href_list["inherent"]) + generate_abilitylist(CHANGELING_POWER_INHERENT) + + if(href_list["armor"]) + generate_abilitylist(CHANGELING_POWER_ARMOR) + + if(href_list["weapons"]) + generate_abilitylist(CHANGELING_POWER_WEAPONS) + + if(href_list["stings"]) + generate_abilitylist(CHANGELING_POWER_STINGS) + + if(href_list["shrieks"]) + generate_abilitylist(CHANGELING_POWER_SHRIEKS) + + if(href_list["health"]) + generate_abilitylist(CHANGELING_POWER_HEALTH) + + if(href_list["enhancements"]) + generate_abilitylist(CHANGELING_POWER_ENHANCEMENTS) + + if(href_list["evolve"]) + var/datum/mind/M = my_client.mob.mind + var/datum/changeling/C = my_client.mob.mind.changeling + var/datum/power/changeling/Thepower = href_list["evolve"] + + for (var/datum/power/changeling/P in GLOB.powerinstances) + if(P.name == Thepower) + Thepower = P + break + + if(!istype(M)) + return + + if(!Thepower) + CRASH("Purchase failed. Inform a dev of this error.") + + if(Thepower in C.purchased_powers) + to_chat(M.current, "You already have this ability! Inform a dev of this error.") /// Should not be possible + return + + if(C.geneticpoints < Thepower.genomecost) + to_chat(M.current, "We cannot evolve this... yet. We must acquire more DNA.") + return + + C.purchased_powers += Thepower /// Set it to purchased + C.geneticpoints -= Thepower.genomecost + generate_abilitylist(Thepower.power_category) /// Refresh the UI + + my_client.mob.mind.changeling.purchasePower(M, Thepower) + + if(href_list["tutorial"]) + textbody = "
What am I?

" + textbody += "" + textbody += "You are a changeling, a creature empowered with genetic-based abilities that change your body in bizarre ways." + textbody += " It's probably best the crew doesn't know about your power -- at least not right away.

" + textbody += "What a changeling is, however, is up to you. Are you a strange alien impersonating crew? Are you a" + textbody += " normal crewmember infected with a parasite? An experiment gone wrong? It's up to you to make the story.

" + textbody += "Of course, you need to know how it works to begin with.

" + textbody += "Your abilities cost chemicals that your body will slowly regenerate with varying speeds based on enhancements obtained." + textbody += " There are a set of inherent abilities you will always have while the rest may be purchased through genomes.

" + textbody += "You may obtain more genomes if you find another changeling and absorb them, but this is not required. If you've found " + textbody += "your abilities aren't to your liking, you have up to two re-adapts available, and these may be refilled by absorbing anyone -- including monkeys.

" + textbody += "Good luck and remember, killing isn't always the end goal." + display() + +/datum/managed_browser/changelingevolution/proc/generate_abilitylist(cat) + var/list/ability_list = list() + var/info = "" + var/catname = "" + for(var/datum/power/changeling/P in GLOB.powerinstances) + if(P.power_category == cat) + ability_list[LIST_PRE_INC(ability_list)] = P + switch(cat) + if(CHANGELING_POWER_INHERENT) + catname = "Inherent" + info = "These powers are inherent to your kind and will always be accessible, provided you have the chemicals to use them." + if(CHANGELING_POWER_ARMOR) + catname = "Armor" + info = "These abilities will provide you with space protection -- and potentially armor." + if(CHANGELING_POWER_WEAPONS) + catname = "Weapons" + info = "These abilities will provide you the means to fight back." + if(CHANGELING_POWER_STINGS) + catname = "Stings" + info = "These abilities provide the means to sting organic beings for various effects -- though you must be close enough, and they must have exposed flesh." + if(CHANGELING_POWER_SHRIEKS) + catname = "Shrieks" + info = "These abilities enhance your vocal chords, empowering your screams." + if(CHANGELING_POWER_HEALTH) + catname = "Health" + info = "These abilities will enhance your health or aid you in mending your wounds." + if(CHANGELING_POWER_ENHANCEMENTS) + catname = "Enhancements" + info = "These abilities enhance you in various ways." + create_textbody(ability_list, catname, info) + +/datum/managed_browser/changelingevolution/proc/create_textbody(ability_list, cat, catinfo) + textbody = "
[cat] Skills
" + textbody += "[catinfo]


" + for(var/A in ability_list) + var/datum/power/changeling/powerdata = A + textbody += "
[initial(powerdata.name)]
" + textbody += "[initial(powerdata.desc)]

" + textbody += "[powerdata.helptext]
" + if(powerdata.enhancedtext != "") + textbody += "WHEN EHANCED: [powerdata.enhancedtext]
" + if(powerdata in my_client.mob.mind.changeling.purchased_powers) + textbody += "
This ability is already evolved!
" + else if(cat != "Inherent") + textbody += "
Evolve
" + textbody += "" + display() diff --git a/code/datums/mind/mind.dm b/code/datums/mind/mind.dm index 07cce3b41eede..3d071b22387d5 100644 --- a/code/datums/mind/mind.dm +++ b/code/datums/mind/mind.dm @@ -82,7 +82,7 @@ if(current) //remove ourself from our old body's mind variable if(changeling) current.remove_changeling_powers() - current.verbs -= /datum/changeling/proc/EvolutionMenu + current.verbs -= /datum/changeling/proc/EvolutionTree current.mind = null SSnano.user_transferred(current, new_character) // transfer active NanoUI instances to new user @@ -97,9 +97,6 @@ if(learned_spells && length(learned_spells)) restore_spells(new_character) - if(changeling) - new_character.make_changeling() - if(active) new_character.key = key //now transfer the key to link the client to our new body diff --git a/code/datums/observation/see_in_dark_set.dm b/code/datums/observation/see_in_dark_set.dm index ea1235f92b0fc..cc7a9671b79d5 100644 --- a/code/datums/observation/see_in_dark_set.dm +++ b/code/datums/observation/see_in_dark_set.dm @@ -18,7 +18,7 @@ GLOBAL_DATUM_INIT(see_in_dark_set_event, /singleton/observ/see_in_dark_set, new) * See In Dark Set Handling * ***************************/ -/mob/proc/set_see_in_dark(new_see_in_dark) +/mob/proc/set_see_in_dark(new_see_in_dark, seedarkness) var/old_see_in_dark = sight if(old_see_in_dark != new_see_in_dark) see_in_dark = new_see_in_dark diff --git a/code/game/antagonist/station/changeling.dm b/code/game/antagonist/station/changeling.dm index 68a4e5ac44750..77f60752c282f 100644 --- a/code/game/antagonist/station/changeling.dm +++ b/code/game/antagonist/station/changeling.dm @@ -27,8 +27,7 @@ GLOBAL_DATUM_INIT(changelings, /datum/antagonist/changeling, new) . = ..() if(. && player && player.current) player.current.remove_changeling_powers() - player.current.verbs -= /datum/changeling/proc/EvolutionMenu - player.current.remove_language(LANGUAGE_CHANGELING_GLOBAL) + player.current.verbs -= /datum/changeling/proc/EvolutionTree QDEL_NULL(player.changeling) /datum/antagonist/changeling/create_objectives(datum/mind/changeling) diff --git a/code/game/dna/dna2_helpers.dm b/code/game/dna/dna2_helpers.dm index 8d4c783f0729c..ef1d39e9e8f27 100644 --- a/code/game/dna/dna2_helpers.dm +++ b/code/game/dna/dna2_helpers.dm @@ -158,7 +158,9 @@ H.update_eyes() H.skin_tone = 35 - dna.GetUIValueRange(DNA_UI_SKIN_TONE, 220) // Value can be negative. - + //Necessary for mirror to set plural properly + if(H.gender == PLURAL) + H.gender = PLURAL if(H.gender != NEUTER) if (dna.GetUIState(DNA_UI_GENDER)) H.gender = FEMALE diff --git a/code/game/gamemodes/changeling/absorbed_dna.dm b/code/game/gamemodes/changeling/absorbed_dna.dm index 850939253181a..907c64ace2bcb 100644 --- a/code/game/gamemodes/changeling/absorbed_dna.dm +++ b/code/game/gamemodes/changeling/absorbed_dna.dm @@ -3,12 +3,21 @@ var/datum/dna/dna var/speciesName var/list/languages + var/gender var/pronouns - -/datum/absorbed_dna/New(newName, newDNA, newSpecies, newLanguages, newPronouns) + var/list/flavour_texts + var/list/genMods + var/list/descriptors + var/list/icon_render_details +/datum/absorbed_dna/New(newName, newDNA, newSpecies, newLanguages, newGender , newPronouns, list/newFlavour, list/New_icon_render_keys, list/newBuild) ..() name = newName dna = newDNA speciesName = newSpecies languages = newLanguages + gender = newGender pronouns = newPronouns + flavour_texts = newFlavour ? newFlavour.Copy() : null + icon_render_details = New_icon_render_keys ? New_icon_render_keys : list() + //defaults to average height and build if none set + descriptors = newBuild ? newBuild.Copy() : null \ No newline at end of file diff --git a/code/game/gamemodes/changeling/changeling.dm b/code/game/gamemodes/changeling/changeling.dm index e6966e09426ac..831bcdbe6c8ef 100644 --- a/code/game/gamemodes/changeling/changeling.dm +++ b/code/game/gamemodes/changeling/changeling.dm @@ -11,7 +11,7 @@ No one knows where it came from. No one knows who it is or what it wants. One thing is for \ certain though... there is never just one of them. Good luck." config_tag = "changeling" - required_players = 5 + required_players = 12 required_enemies = 1 end_on_antag_death = FALSE antag_scaling_coeff = 15 diff --git a/code/game/gamemodes/changeling/changeling_evolutiontree.dm b/code/game/gamemodes/changeling/changeling_evolutiontree.dm new file mode 100644 index 0000000000000..fadbdcde30c6a --- /dev/null +++ b/code/game/gamemodes/changeling/changeling_evolutiontree.dm @@ -0,0 +1,368 @@ +// READ: Don't use the apostrophe in name or desc. Causes script errors. + +//Ling power's evolution menu entry datum should be contained alongside the mob proc for the actual power, in their own file. + +var/global/list/powers = typesof(/datum/power/changeling) - /datum/power/changeling //needed for the badmin verb for now +GLOBAL_LIST_EMPTY(powerinstances) +/mob/proc/generic_sting() + return +/datum/power //Could be used by other antags too + var/name = "Power" + var/desc = "Placeholder" + var/helptext = "" + var/enhancedtext = "" + /// Is it an active power, or passive? + var/isVerb = 1 + /// Path to a verb that contains the effects. + var/verbpath + /// Is this ability significant enough to dedicate screen space for a HUD button? + var/make_hud_button = 1 + /// icon_state for icons for the ability HUD. Must be in screen_spells.dmi. + var/ability_icon_state = null + var/is_sting = FALSE +/datum/power/changeling + var/allowduringlesserform = 0 + /// Cost for the changling to evolve this power. + var/genomecost = 500000 + /// _defines/gamemode.dm + var/power_category = null + /// only override for sting powers, used for boost range + var/sting_effect = /mob/proc/generic_sting + var/sting_duration = null + var/reagents_transferred = null + +// Modularchangling, totally stolen from the new player panel. YAYY +/datum/changeling/proc/EvolutionTree()//The new one + set name = "-Evolution Tree-" + set category = "Changeling" + set desc = "Adapt yourself carefully." + + if(!usr || !usr.mind || !usr.mind.changeling) return + src = usr.mind.changeling + + if(!length(GLOB.powerinstances)) + for(var/P in powers) + GLOB.powerinstances += new P() + + var/dat = "Changeling Evolution Tree" + + //javascript, the part that does most of the work~ + dat += {" + + + + + + + "} + + //body tag start + onload and onkeypress (onkeyup) javascript event calls + dat += "" + + //title + search bar + dat += {" + + + + + + + + +
+ Changeling Evolution Menu
+ Hover over a power to see more information
+ Current evolution points left to evolve with: [geneticpoints]
+ Absorb other changelings to acquire more evolution points +

+

+ Search: +
+ + "} + + //player table header + dat += {" + + "} + + var/i = 1 + for(var/datum/power/changeling/P in GLOB.powerinstances) + var/ownsthis = 0 + + if(P in purchased_powers) + ownsthis = 1 + + + var/color = "#e6e6e6" + if(i%2 == 0) + color = "#f2f2f2" + + + dat += {" + + + + + + "} + + i++ + + + //player table ending + dat += {" +
+ + + Evolve [P] - Cost: [ownsthis ? "Purchased" : P.genomecost] + +
+
+
+ + + + "} + + show_browser(usr, dat, "window=powers;size=900x480") + + +/datum/changeling/Topic(href, href_list) + ..() + if(!ismob(usr)) + return + + if(href_list["P"]) + var/datum/mind/M = usr.mind + if(!istype(M)) + return + purchasePower(M, href_list["P"]) + call(/datum/changeling/proc/EvolutionTree)() + + + +/datum/changeling/proc/purchasePower(datum/mind/M, Pname, remake_verbs = 1) + if(!M || !M.changeling) + return + + var/datum/power/changeling/Thepower = Pname + + + for (var/datum/power/changeling/P in GLOB.powerinstances) + if(P.name == Pname) + Thepower = P + break + + + if(!Thepower) + to_chat(M.current, "This is awkward. Changeling power purchase failed, please report this bug to a coder!") + return + + if(Thepower in purchased_powers) + return + + + if(geneticpoints < Thepower.genomecost) + to_chat(M.current, "We cannot evolve this... yet. We must acquire more DNA.") + return + + geneticpoints -= Thepower.genomecost + + purchased_powers += Thepower + + if(Thepower.genomecost > 0) + purchased_powers_history.Add("[Pname] ([Thepower.genomecost] points)") + + if(Thepower.make_hud_button && Thepower.isVerb) + if(!M.current.ability_master) + M.current.ability_master = new /obj/screen/movable/ability_master(null, M.current) + M.current.ability_master.add_ling_ability( + object_given = M.current, + verb_given = Thepower.verbpath, + name_given = Thepower.name, + ability_icon_given = Thepower.ability_icon_state, + arguments = list() + ) + + if(!Thepower.isVerb && Thepower.verbpath) + call(M.current, Thepower.verbpath)() + else if(remake_verbs) + M.current.make_changeling() diff --git a/code/game/gamemodes/changeling/changeling_powers.dm b/code/game/gamemodes/changeling/changeling_powers.dm index 312daa2f227d5..f728d006f7adb 100644 --- a/code/game/gamemodes/changeling/changeling_powers.dm +++ b/code/game/gamemodes/changeling/changeling_powers.dm @@ -1,9 +1,11 @@ + var/global/list/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") /datum/changeling //stores changeling powers, changeling recharge thingie, changeling absorbed DNA and changeling ID (for changeling hivemind) var/list/datum/absorbed_dna/absorbed_dna = list() - var/list/absorbed_languages = list() + var/list/absorbed_languages = list() // Necessary because of set_species stuff var/absorbedcount = 0 + var/lingabsorbedcount = 1 //Starts at one, because that's us var/chem_charges = 20 var/chem_recharge_rate = 0.5 var/chem_storage = 50 @@ -11,17 +13,25 @@ var/global/list/possible_changeling_IDs = list("Alpha","Beta","Gamma","Delta","E var/changelingID = "Changeling" var/geneticdamage = 0 var/isabsorbing = 0 - var/geneticpoints = 25 - var/purchasedpowers = list() + var/geneticpoints = 15 + var/max_geneticpoints = 15 + var/list/purchased_powers = list() + var/list/purchased_organs = list() + var/already_regenerating = FALSE var/mimicing = "" - -/datum/changeling/Destroy() - purchasedpowers = null - absorbed_languages.Cut() - absorbed_dna.Cut() - . = ..() - -/datum/changeling/New() + var/cloaked = 0 + var/absorbing_lethally = ABSORB_NONLETHAL + var/selected_ranged_sting = null; + var/tendons_reinforced = FALSE; + var/list/toxin_victims = list() + var/armor_deployed = 0 //This is only used for changeling_generic_equip_all_slots() at the moment. + var/recursive_enhancement = 0 //Used to power up other abilities from the ling power with the same name. + var/list/purchased_powers_history = list() //Used for round-end report, includes respec uses too. + var/last_shriek = null // world.time when the ling last used a shriek. + var/next_escape = 0 // world.time when the ling can next use Escape Restraints + var/thermal_sight = FALSE // Is our Vision Augmented? With thermals? + var/last_human_form = null +/datum/changeling/New(gender=FEMALE) ..() if(length(possible_changeling_IDs)) changelingID = pick(possible_changeling_IDs) @@ -53,50 +63,83 @@ var/global/list/possible_changeling_IDs = list("Alpha","Beta","Gamma","Delta","E if(!changeling.GetDNA(newDNA.name)) // Don't duplicate - I wonder if it's possible for it to still be a different DNA? DNA code could use a rewrite changeling.absorbed_dna += newDNA - +//ling creation, add unique organ here //Restores our verbs. It will only restore verbs allowed during lesser (monkey) form if we are not human /mob/proc/make_changeling() if(!mind) return if(!mind.changeling) mind.changeling = new /datum/changeling(gender) - verbs += /datum/changeling/proc/EvolutionMenu - add_language(LANGUAGE_CHANGELING_GLOBAL) + verbs.Add(/datum/changeling/proc/EvolutionTree) + verbs.Add(/mob/proc/toggle_absorb_type) + add_language("Changeling") var/lesser_form = !ishuman(src) - - if(!length(powerinstances)) + var/has_core = FALSE + if(!length(GLOB.powerinstances)) for(var/P in powers) - powerinstances += new P() + GLOB.powerinstances += new P() // Code to auto-purchase free powers. - for(var/datum/power/changeling/P in powerinstances) + for(var/datum/power/changeling/P in GLOB.powerinstances) if(!P.genomecost) // Is it free? - if(!(P in mind.changeling.purchasedpowers)) // Do we not have it already? - mind.changeling.purchasePower(mind, P.name, 0)// Purchase it. Don't remake our verbs, we're doing it after this. + if(!(P in mind.changeling.purchased_powers)) // Do we not have it already? + mind.changeling.purchased_powers += P /// Add it. + mind.changeling.purchasePower(mind, P, 0)// Purchase it. Don't remake our verbs, we're doing it after this. - for(var/datum/power/changeling/P in mind.changeling.purchasedpowers) + for(var/datum/power/changeling/P in mind.changeling.purchased_powers) if(P.isVerb) if(lesser_form && !P.allowduringlesserform) continue if(!(P in src.verbs)) - src.verbs += P.verbpath + verbs.Add(P.verbpath) + if(P.make_hud_button) + if(!src.ability_master) + src.ability_master = new /obj/screen/movable/ability_master(null, src) + src.ability_master.add_ling_ability( + object_given = src, + verb_given = P.verbpath, + name_given = P.name, + ability_icon_given = P.ability_icon_state, + arguments = list() + ) for(var/language in languages) mind.changeling.absorbed_languages |= language + + var/mob/living/carbon/human/H = src if(istype(H)) - var/datum/absorbed_dna/newDNA = new(H.real_name, H.dna, H.species.name, H.languages) + var/saved_dna = H.dna.Clone() /// Prevent transform from breaking. + var/datum/absorbed_dna/newDNA = new(H.real_name, saved_dna, H.species.name, H.languages, H.gender, H.pronouns, H.flavor_texts, H.icon_render_keys, H.descriptors) absorbDNA(newDNA) - + for(var/obj/item/organ/internal/augment/lingcore/C in H.internal_organs) + has_core++ + if(has_core == 0 && istype(src,/mob/living/carbon/human)) + var/obj/item/organ/external/chest = H.get_organ(BP_CHEST) + var/obj/item/organ/internal/augment/core = new /obj/item/organ/internal/augment/lingcore + core.forceMove(src) + core.replaced(src, chest) + core = null + var/obj/item/organ/external/parent = H.get_organ(BP_CHEST) + var/obj/item/organ/internal/brain/mbrain = H.internal_organs_by_name[BP_BRAIN] + mbrain.parent_organ = BP_CHEST + mbrain.forceMove(parent) + var/obj/item/organ/external/head = H.get_organ(BP_HEAD) + for(var/obj/item/organ/internal/I in head.internal_organs) + if(istype(I,/obj/item/organ/internal/brain)) + head.internal_organs.Remove(I) return 1 //removes our changeling verbs /mob/proc/remove_changeling_powers() if(!mind || !mind.changeling) return - for(var/datum/power/changeling/P in mind.changeling.purchasedpowers) + for(var/datum/power/changeling/P in mind.changeling.purchased_powers) if(P.isVerb) - verbs -= P.verbpath + verbs.Remove(P.verbpath) + var/obj/screen/ability/verb_based/changeling/C = ability_master.get_ability_by_PROC_REF(P.verbpath) + if(C) + ability_master.remove_ability(C) //Helper proc. Does all the checks and stuff for us to avoid copypasta @@ -111,694 +154,116 @@ var/global/list/possible_changeling_IDs = list("Alpha","Beta","Gamma","Delta","E return if(src.stat > max_stat) - to_chat(src, SPAN_WARNING("We are incapacitated.")) + to_chat(src, "We are incapacitated.") return if(length(changeling.absorbed_dna) < required_dna) - to_chat(src, SPAN_WARNING("We require at least [required_dna] samples of compatible DNA.")) + to_chat(src, "We require at least [required_dna] samples of compatible DNA.") return if(changeling.chem_charges < required_chems) - to_chat(src, SPAN_WARNING("We require at least [required_chems] units of chemicals to do that!")) + to_chat(src, "We require at least [required_chems] units of chemicals to do that!") return if(changeling.geneticdamage > max_genetic_damage) - to_chat(src, SPAN_WARNING("Our genomes are still reassembling. We need time to recover first.")) + to_chat(src, "Our genomes are still reassembling. We need time to recover first.") return return changeling - //Used to dump the languages from the changeling datum into the actual mob. /mob/proc/changeling_update_languages(updated_languages) - languages = list() for(var/language in updated_languages) languages += language //This isn't strictly necessary but just to be safe... - add_language(LANGUAGE_CHANGELING_GLOBAL) - - return - -//Absorbs the victim's DNA making them uncloneable. Requires a strong grip on the victim. -//Doesn't cost anything as it's the most basic ability. -/mob/proc/changeling_absorb_dna() - set category = "Changeling" - set name = "Absorb DNA" - - var/datum/changeling/changeling = changeling_power(0,0,100) - if(!changeling) return - - var/obj/item/grab/G = src.get_active_hand() - if(!istype(G)) - to_chat(src, SPAN_WARNING("We must be grabbing a creature in our active hand to absorb them.")) - return - - var/mob/living/carbon/human/T = G.affecting - if(!istype(T)) - to_chat(src, SPAN_WARNING("[T] is not compatible with our biology.")) - return - - if((T.species.species_flags & SPECIES_FLAG_NO_SCAN) || T.isSynthetic()) - to_chat(src, SPAN_WARNING("We cannot extract DNA from this creature!")) - return - - if(MUTATION_HUSK in T.mutations) - to_chat(src, SPAN_WARNING("This creature's DNA is ruined beyond useability!")) - return - - if(!G.can_absorb()) - to_chat(src, SPAN_WARNING("We must have a tighter grip to absorb this creature.")) - return - - if(changeling.isabsorbing) - to_chat(src, SPAN_WARNING("We are already absorbing!")) - return - - var/obj/item/organ/external/affecting = T.get_organ(src.zone_sel.selecting) - if(!affecting) - to_chat(src, SPAN_WARNING("They are missing that body part!")) - - changeling.isabsorbing = 1 - for(var/stage = 1, stage<=3, stage++) - switch(stage) - if(1) - to_chat(src, SPAN_NOTICE("This creature is compatible. We must hold still...")) - if(2) - to_chat(src, SPAN_NOTICE("We extend a proboscis.")) - src.visible_message(SPAN_WARNING("[src] extends a proboscis!")) - if(3) - to_chat(src, SPAN_NOTICE("We stab [T] with the proboscis.")) - src.visible_message(SPAN_DANGER("[src] stabs [T] with the proboscis!")) - to_chat(T, SPAN_DANGER("You feel a sharp stabbing pain!")) - affecting.take_external_damage(39, 0, DAMAGE_FLAG_SHARP, "large organic needle") - if(!do_after(src, 15 SECONDS, T, DO_PUBLIC_UNIQUE)) - to_chat(src, SPAN_WARNING("Our absorption of [T] has been interrupted!")) - changeling.isabsorbing = 0 - return - - to_chat(src, SPAN_NOTICE("We have absorbed [T]!")) - src.visible_message(SPAN_DANGER("[src] sucks the fluids from [T]!")) - to_chat(T, SPAN_DANGER("You have been absorbed by the changeling!")) - changeling.chem_charges += 10 - changeling.geneticpoints += 2 - - //Steal all of their languages! - for(var/language in T.languages) - if(!(language in changeling.absorbed_languages)) - changeling.absorbed_languages += language - - changeling_update_languages(changeling.absorbed_languages) - - var/datum/absorbed_dna/newDNA = new(T.real_name, T.dna, T.species.name, T.languages, T.pronouns) - absorbDNA(newDNA) - - if(mind && T.mind) - T.mind.CopyMemories(mind) - - if(T.mind && T.mind.changeling) - if(T.mind.changeling.absorbed_dna) - for(var/datum/absorbed_dna/dna_data in T.mind.changeling.absorbed_dna) //steal all their loot - if(changeling.GetDNA(dna_data.name)) - continue - absorbDNA(dna_data) - changeling.absorbedcount++ - T.mind.changeling.absorbed_dna.Cut(2) - - if(T.mind.changeling.purchasedpowers) - for(var/datum/power/changeling/Tp in T.mind.changeling.purchasedpowers) - if(Tp in changeling.purchasedpowers) - continue - else - changeling.purchasedpowers += Tp - - if(!Tp.isVerb) - call(Tp.verbpath)() - else - src.make_changeling() - - changeling.chem_charges += T.mind.changeling.chem_charges - changeling.geneticpoints += T.mind.changeling.geneticpoints - T.mind.changeling.chem_charges = 0 - T.mind.changeling.geneticpoints = 0 - T.mind.changeling.absorbedcount = 0 - - changeling.absorbedcount++ - changeling.isabsorbing = 0 - - T.death(0) - T.ChangeToHusk() - return 1 - - -//Change our DNA to that of somebody we've absorbed. -/mob/proc/changeling_transform() - set category = "Changeling" - set name = "Transform (5)" - - var/datum/changeling/changeling = changeling_power(5,1,0) - if(!changeling) return - - var/list/names = list() - for(var/datum/absorbed_dna/DNA in changeling.absorbed_dna) - names += "[DNA.name]" - - var/S = input("Select the target DNA: ", "Target DNA", null) as null|anything in names - if(!S) return - - var/datum/absorbed_dna/chosen_dna = changeling.GetDNA(S) - if(!chosen_dna) - return - - changeling.chem_charges -= 5 - changeling.geneticdamage = 30 - - var/S_name = chosen_dna.speciesName - var/datum/species/S_dat = all_species[S_name] - var/changeTime = 2 SECONDS - if(mob_size != S_dat.mob_size) - src.visible_message(SPAN_WARNING("[src]'s body begins to twist, their mass changing rapidly!")) - changeTime = 8 SECONDS - else - src.visible_message(SPAN_WARNING("[src]'s body begins to twist, changing rapidly!")) - - if(!do_after(src, changeTime, do_flags = DO_DEFAULT | DO_USER_UNIQUE_ACT)) - to_chat(src, SPAN_NOTICE("You fail to change shape.")) - return - handle_changeling_transform(chosen_dna) - - src.verbs -= /mob/proc/changeling_transform - spawn(10) - src.verbs += /mob/proc/changeling_transform - - changeling_update_languages(changeling.absorbed_languages) - return 1 - -/mob/proc/handle_changeling_transform(datum/absorbed_dna/chosen_dna) - src.visible_message(SPAN_WARNING("[src] transforms!")) - - src.dna = chosen_dna.dna - src.real_name = chosen_dna.name - src.flavor_text = "" - src.pronouns = chosen_dna.pronouns - - if(ishuman(src)) - var/mob/living/carbon/human/H = src - var/newSpecies = chosen_dna.speciesName - H.set_species(newSpecies,1) - H.b_type = chosen_dna.dna.b_type - H.sync_organ_dna() - - domutcheck(src, null) - src.UpdateAppearance() - - -//Transform into a monkey. -/mob/proc/changeling_lesser_form() - set category = "Changeling" - set name = "Lesser Form (1)" - - var/datum/changeling/changeling = changeling_power(1,0,0) - if(!changeling) return - - if(src.has_brain_worms()) - to_chat(src, SPAN_WARNING("We cannot perform this ability at the present time!")) - return - - var/mob/living/carbon/human/H = src - - if(!istype(H) || !H.species.primitive_form) - to_chat(src, SPAN_WARNING("We cannot perform this ability in this form!")) - return - - changeling.chem_charges-- - H.visible_message(SPAN_WARNING("[H] transforms!")) - changeling.geneticdamage = 30 - to_chat(H, SPAN_WARNING("Our genes cry out!")) - H = H.monkeyize() - return 1 - -//Transform into a human -/mob/proc/changeling_lesser_transform() - set category = "Changeling" - set name = "Transform (1)" - - var/datum/changeling/changeling = changeling_power(1,1,0) - if(!changeling) return - - if(HAS_TRANSFORMATION_MOVEMENT_HANDLER(src)) - return - - var/list/names = list() - for(var/datum/dna/DNA in changeling.absorbed_dna) - names += "[DNA.real_name]" - - var/S = input("Select the target DNA: ", "Target DNA", null) as null|anything in names - if(!S) return - - var/datum/dna/chosen_dna = changeling.GetDNA(S) - if(!chosen_dna) - return - - var/mob/living/carbon/human/C = src - - changeling.chem_charges-- - C.remove_changeling_powers() - C.visible_message(SPAN_WARNING("[C] transforms!")) - C.dna = chosen_dna.Clone() - - var/list/implants = list() - for (var/obj/item/implant/I in C) //Still preserving implants - implants += I - - ADD_TRANSFORMATION_MOVEMENT_HANDLER(C) - C.icon = null - C.ClearOverlays() - C.set_invisibility(INVISIBILITY_ABSTRACT) - var/atom/movable/fake_overlay/animation = new /atom/movable/fake_overlay(src) - animation.icon_state = "blank" - animation.icon = 'icons/mob/mob.dmi' - flick("monkey2h", animation) - sleep(48) - qdel(animation) - - for(var/obj/item/W in src) - C.drop_from_inventory(W) - - var/mob/living/carbon/human/O = new /mob/living/carbon/human( src ) - if (C.dna.GetUIState(DNA_UI_GENDER)) - O.gender = FEMALE - else - O.gender = MALE - O.dna = C.dna.Clone() - C.dna = null - O.real_name = chosen_dna.real_name - - for(var/obj/T in C) - qdel(T) - - O.dropInto(C.loc) - - O.UpdateAppearance() - domutcheck(O, null) - O.setToxLoss(C.getToxLoss()) - O.adjustBruteLoss(C.getBruteLoss()) - O.setOxyLoss(C.getOxyLoss()) - O.adjustFireLoss(C.getFireLoss()) - O.set_stat(C.stat) - for (var/obj/item/implant/I in implants) - I.forceMove(O) - I.implanted = O - - C.mind.transfer_to(O) - O.make_changeling() - O.changeling_update_languages(changeling.absorbed_languages) - qdel(C) - return 1 - - -//Fake our own death and fully heal. You will appear to be dead but regenerate fully after a short delay. -/mob/proc/changeling_fakedeath() - set category = "Changeling" - set name = "Regenerative Stasis (20)" - - var/datum/changeling/changeling = changeling_power(20,1,100,DEAD) - if(!changeling) return - - var/mob/living/carbon/C = src - if(!C.stat && alert("Are we sure we wish to fake our death?",,"Yes","No") == "No")//Confirmation for living changelings if they want to fake their death - return - to_chat(C, SPAN_NOTICE("We will attempt to regenerate our form.")) - C.status_flags |= FAKEDEATH //play dead - C.UpdateLyingBuckledAndVerbStatus() - C.remove_changeling_powers() - - C.emote("gasp") - - spawn(rand(800,2000)) - if(changeling_power(20,1,100,DEAD)) - // charge the changeling chemical cost for stasis - changeling.chem_charges -= 20 - - to_chat(C, SPAN_NOTICE(FONT_GIANT("We are ready to rise. Use the Revive verb when you are ready."))) - C.verbs += /mob/proc/changeling_revive - return 1 - -/mob/proc/changeling_revive() - set category = "Changeling" - set name = "Revive" - - var/mob/living/carbon/C = src - // restore us to health - C.revive() - // remove our fake death flag - C.status_flags &= ~(FAKEDEATH) - // let us move again - C.UpdateLyingBuckledAndVerbStatus() - // re-add out changeling powers - C.make_changeling() - // sending display messages - to_chat(C, SPAN_NOTICE("We have regenerated.")) - C.verbs -= /mob/proc/changeling_revive - - -//Boosts the range of your next sting attack by 1 -/mob/proc/changeling_boost_range() - set category = "Changeling" - set name = "Ranged Sting (10)" - set desc="Your next sting ability can be used against targets 2 squares away." - - var/datum/changeling/changeling = changeling_power(10,0,100) - if(!changeling) return 0 - changeling.chem_charges -= 10 - to_chat(src, SPAN_NOTICE("Your throat adjusts to launch the sting.")) - changeling.sting_range = 2 - src.verbs -= /mob/proc/changeling_boost_range - spawn(5) src.verbs += /mob/proc/changeling_boost_range - return 1 - - -//Recover from stuns. -/mob/proc/changeling_unstun() - set category = "Changeling" - set name = "Epinephrine Sacs (45)" - set desc = "Removes all stuns" - - var/datum/changeling/changeling = changeling_power(45,0,100,UNCONSCIOUS) - if(!changeling) return 0 - changeling.chem_charges -= 45 - - var/mob/living/carbon/human/C = src - C.set_stat(CONSCIOUS) - C.SetParalysis(0) - C.SetStunned(0) - C.SetWeakened(0) - C.lying = 0 - C.UpdateLyingBuckledAndVerbStatus() - - src.verbs -= /mob/proc/changeling_unstun - spawn(5) src.verbs += /mob/proc/changeling_unstun - return 1 + add_language("Changeling") - -//Speeds up chemical regeneration -/mob/proc/changeling_fastchemical() - src.mind.changeling.chem_recharge_rate *= 2 - return 1 - -//Increases macimum chemical storage -/mob/proc/changeling_engorgedglands() - src.mind.changeling.chem_storage += 25 - return 1 - - -//Prevents AIs tracking you but makes you easily detectable to the human-eye. -/mob/proc/changeling_digitalcamo() - set category = "Changeling" - set name = "Toggle Digital Camoflague" - set desc = "The AI can no longer track us, but we will look different if examined. Has a constant cost while active." - - var/datum/changeling/changeling = changeling_power() - if(!changeling) return 0 - - var/mob/living/carbon/human/C = src - if(C.digitalcamo) to_chat(C, SPAN_NOTICE("We return to normal.")) - else to_chat(C, SPAN_NOTICE("We distort our form to prevent AI-tracking.")) - C.digitalcamo = !C.digitalcamo - - spawn(0) - while(C && C.digitalcamo && C.mind && C.mind.changeling) - C.mind.changeling.chem_charges = max(C.mind.changeling.chem_charges - 1, 0) - sleep(40) - - src.verbs -= /mob/proc/changeling_digitalcamo - spawn(5) src.verbs += /mob/proc/changeling_digitalcamo - return 1 - - -//Starts healing you every second for 10 seconds. Can be used whilst unconscious. -/mob/proc/changeling_rapidregen() - set category = "Changeling" - set name = "Rapid Regeneration (30)" - set desc = "Begins rapidly regenerating. Does not effect stuns or chemicals." - - var/datum/changeling/changeling = changeling_power(30,0,100,UNCONSCIOUS) - if(!changeling) return 0 - src.mind.changeling.chem_charges -= 30 - - var/mob/living/carbon/human/C = src - spawn(0) - for(var/i = 0, i<10,i++) - if(C) - C.adjustBruteLoss(-10) - C.adjustToxLoss(-10) - C.adjustOxyLoss(-10) - C.adjustFireLoss(-10) - sleep(10) - - src.verbs -= /mob/proc/changeling_rapidregen - spawn(5) src.verbs += /mob/proc/changeling_rapidregen - return 1 - -// HIVE MIND UPLOAD/DOWNLOAD DNA - -var/global/list/datum/absorbed_dna/hivemind_bank = list() - -/mob/proc/changeling_hiveupload() - set category = "Changeling" - set name = "Hive Channel (10)" - set desc = "Allows you to channel DNA in the airwaves to allow other changelings to absorb it." - - var/datum/changeling/changeling = changeling_power(10,1) - if(!changeling) return - - var/list/names = list() - for(var/datum/absorbed_dna/DNA in changeling.absorbed_dna) - var/valid = 1 - for(var/datum/absorbed_dna/DNB in hivemind_bank) - if(DNA.name == DNB.name) - valid = 0 - break - if(valid) - names += DNA.name - - if(length(names) <= 0) - to_chat(src, SPAN_NOTICE("The airwaves already have all of our DNA.")) - return - - var/S = input("Select a DNA to channel: ", "Channel DNA", null) as null|anything in names - if(!S) return - - var/datum/absorbed_dna/chosen_dna = changeling.GetDNA(S) - if(!chosen_dna) - return - - var/datum/species/spec = all_species[chosen_dna.speciesName] - - if(spec && spec.species_flags & SPECIES_FLAG_NEED_DIRECT_ABSORB) - to_chat(src, SPAN_NOTICE("That species must be absorbed directly.")) - return - - changeling.chem_charges -= 10 - hivemind_bank += chosen_dna - to_chat(src, SPAN_NOTICE("We channel the DNA of [S] to the air.")) - return 1 - -/mob/proc/changeling_hivedownload() - set category = "Changeling" - set name = "Hive Absorb (20)" - set desc = "Allows you to absorb DNA that is being channeled in the airwaves." - - var/datum/changeling/changeling = changeling_power(20,1) - if(!changeling) return - - var/list/names = list() - for(var/datum/absorbed_dna/DNA in hivemind_bank) - if(!(changeling.GetDNA(DNA.name))) - names[DNA.name] = DNA - - if(length(names) <= 0) - to_chat(src, SPAN_NOTICE("There's no new DNA to absorb from the air.")) - 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.chem_charges -= 20 - absorbDNA(chosen_dna) - to_chat(src, SPAN_NOTICE("We absorb the DNA of [S] from the air.")) - return 1 - -// Fake Voice - -/mob/proc/changeling_mimicvoice() - set category = "Changeling" - set name = "Mimic Voice" - set desc = "Shape our vocal glands to form a voice of someone we choose. We cannot regenerate chemicals when mimicing." - - - var/datum/changeling/changeling = changeling_power() - if(!changeling) return - - if(changeling.mimicing) - changeling.mimicing = "" - to_chat(src, SPAN_NOTICE("We return our vocal glands to their original location.")) - return - - var/mimic_voice = sanitize(input(usr, "Enter a name to mimic.", "Mimic Voice", null), MAX_NAME_LEN) - if(!mimic_voice) - return - - changeling.mimicing = mimic_voice - - to_chat(src, SPAN_NOTICE("We shape our glands to take the voice of [mimic_voice], this will stop us from regenerating chemicals while active.")) - to_chat(src, SPAN_NOTICE("Use this power again to return to our original voice and reproduce chemicals again.")) - - spawn(0) - while(src && src.mind && src.mind.changeling && src.mind.changeling.mimicing) - src.mind.changeling.chem_charges = max(src.mind.changeling.chem_charges - 1, 0) - sleep(40) - if(src && src.mind && src.mind.changeling) - src.mind.changeling.mimicing = "" ////////// //STINGS// //They get a pretty header because there's just so fucking many of them ;_; ////////// +/turf/proc/AdjacentTurfsRangedSting() + //Yes this is snowflakey, but I couldn't get it to work any other way.. -Luke + var/list/allowed = list( + /obj/structure/table, + /obj/structure/closet, + /obj/structure/cable, + /obj/structure/disposalpipe, + /obj/machinery, + /mob + ) + + var/L[] = new() + for(var/turf/simulated/t in oview(src,1)) + var/add = 1 + if(t.density) + add = 0 + if(add && LinkBlocked(src,t)) + add = 0 + if(add && TurfBlockedNonWindow(t)) + add = 0 + for(var/obj/O in t) + if(O.density) + add = 0 + break + if(istype(O, /obj/machinery/door)) + //not sure why this doesn't fire on LinkBlocked() + add = 0 + break + for(var/type in allowed) + if (istype(O, type)) + add = 1 + break + if(!add) + break + if(add) + L.Add(t) + return L + + /mob/proc/sting_can_reach(mob/M as mob, sting_range = 1) if(M.loc == src.loc) return 1 //target and source are in the same thing if(!isturf(src.loc) || !isturf(M.loc)) - to_chat(src, SPAN_WARNING("We cannot reach \the [M] with a sting!")) + to_chat(src, "We cannot reach \the [M] with a sting!") return 0 //One is inside, the other is outside something. // Maximum queued turfs set to 25; I don't *think* anything raises sting_range above 2, but if it does the 25 may need raising - if(!AStar(src.loc, M.loc, TYPE_PROC_REF(/turf, AdjacentTurfs), TYPE_PROC_REF(/turf, Distance), max_nodes=25, max_node_depth=sting_range)) //If we can't find a path, fail - to_chat(src, SPAN_WARNING("We cannot find a path to sting \the [M] by!")) + if(!AStar(src.loc, M.loc, /turf/proc/AdjacentTurfsRangedSting, /turf/proc/Distance, max_nodes=25, max_node_depth=sting_range)) //If we can't find a path, fail + to_chat(src, "We cannot find a path to sting \the [M] by!") return 0 return 1 //Handles the general sting code to reduce on copypasta (seeming as somebody decided to make SO MANY dumb abilities) -/mob/proc/changeling_sting(required_chems=0, verb_path, loud, sting_name = "unnamed sting") +/mob/proc/changeling_sting(required_chems=0, verb_path) var/datum/changeling/changeling = changeling_power(required_chems) + var/mob/living/carbon/human/ling = src if(!changeling) return var/list/victims = list() - for(var/mob/living/carbon/human/C in oview(changeling.sting_range)) + for(var/mob/living/carbon/C in oview(changeling.sting_range)) victims += C - var/mob/living/carbon/human/T = input(src, "Who will we sting?") as null|anything in victims + var/mob/living/carbon/T = input(src, "Who will we sting?") as null|anything in victims - if(!T) return + if(!T) + return + if(T.isSynthetic()) + to_chat(src, "We are unable to pierce the outer shell of [T].") + return if(!(T in view(changeling.sting_range))) return if(!sting_can_reach(T, changeling.sting_range)) return if(!changeling_power(required_chems)) return - var/obj/item/organ/external/target_limb = T.get_organ(src.zone_sel.selecting) - if (!target_limb) - to_chat(src, SPAN_WARNING("[T] is missing that limb.")) + + if(!(T.can_inject(ling,ling.zone_sel.selecting, FALSE) == CAN_INJECT)) + to_chat(src,SPAN_WARNING("We are unable to pierce the thick material covering [T].")) return changeling.chem_charges -= required_chems changeling.sting_range = 1 src.verbs -= verb_path spawn(10) src.verbs += verb_path - if(!loud) - to_chat(src, SPAN_NOTICE("We stealthily sting [T].")) - else - visible_message(SPAN_DANGER("[src] fires an organic shard into [T]!")) - admin_attack_log(src, T, "Stung their victim using [sting_name]", "Was stung using [sting_name]", "stung using [sting_name]") - - for(var/obj/item/clothing/clothes in list(T.head, T.wear_mask, T.wear_suit, T.w_uniform, T.gloves, T.shoes)) - if(istype(clothes) && (clothes.body_parts_covered & target_limb.body_part) && (clothes.item_flags & ITEM_FLAG_THICKMATERIAL)) - to_chat(src, SPAN_WARNING("[T]'s armor has protected them.")) - return //thick clothes will protect from the sting - if(T.isSynthetic() || BP_IS_ROBOTIC(target_limb)) return - if(!T.mind || !T.mind.changeling) return T //T will be affected by the sting - to_chat(T, SPAN_WARNING("You feel a tiny prick.")) + to_chat(src, "We stealthily sting [T].") + if(!T.mind || !T.mind.changeling) return T //T will be affected by the sting + to_chat(T, "You feel a tiny prick.") return - - -/mob/proc/changeling_lsdsting() - set category = "Changeling" - set name = "Hallucination Sting (15)" - set desc = "Causes terror in the target." - - var/mob/living/carbon/human/T = changeling_sting(15, TYPE_PROC_REF(/mob, changeling_lsdsting), sting_name = "Hallucination Sting") - if(!T) return 0 - spawn(rand(300,600)) - if(T) T.hallucination(400, 80) - return 1 - -/mob/proc/changeling_silence_sting() - set category = "Changeling" - set name = "Silence sting (10)" - set desc="Sting target" - - var/mob/living/carbon/human/T = changeling_sting(10, TYPE_PROC_REF(/mob, changeling_silence_sting), sting_name = "Silence Sting") - if(!T) return 0 - T.silent += 30 - return 1 - -/mob/proc/changeling_blind_sting() - set category = "Changeling" - set name = "Blind sting (20)" - set desc="Sting target" - - var/mob/living/carbon/human/T = changeling_sting(20, TYPE_PROC_REF(/mob, changeling_blind_sting), sting_name = "Blind Sting") - if(!T) return 0 - to_chat(T, SPAN_DANGER("Your eyes burn horrificly!")) - T.disabilities |= NEARSIGHTED - spawn(300) T.disabilities &= ~NEARSIGHTED - T.eye_blind = 10 - T.eye_blurry = 20 - return 1 - -/mob/proc/changeling_deaf_sting() - set category = "Changeling" - set name = "Deaf sting (5)" - set desc="Sting target:" - - var/mob/living/carbon/human/T = changeling_sting(5, TYPE_PROC_REF(/mob, changeling_deaf_sting), sting_name = "Deaf Sting") - if(!T) return 0 - to_chat(T, SPAN_DANGER("Your ears pop and begin ringing loudly!")) - T.ear_deaf += 15 - return 1 - -/mob/proc/changeling_DEATHsting() - set category = "Changeling" - set name = "Death Sting (40)" - set desc = "Causes spasms onto death." - var/loud = 1 - - var/mob/living/carbon/human/T = changeling_sting(40, TYPE_PROC_REF(/mob, changeling_DEATHsting), loud, sting_name = "Death Sting") - if(!T) return 0 - to_chat(T, SPAN_DANGER("You feel a small prick and your chest becomes tight.")) - T.make_jittery(400) - if(T.reagents) T.reagents.add_reagent(/datum/reagent/lexorin, 10) - return 1 - -/mob/proc/changeling_extract_dna_sting() - set category = "Changeling" - set name = "Extract DNA Sting (40)" - set desc="Stealthily sting a target to extract their DNA." - - var/datum/changeling/changeling = null - if(src.mind && src.mind.changeling) - changeling = src.mind.changeling - if(!changeling) - return 0 - - var/mob/living/carbon/human/T = changeling_sting(40, TYPE_PROC_REF(/mob, changeling_extract_dna_sting), sting_name = "Extract DNA Sting") - if(!T) return 0 - if((MUTATION_HUSK in T.mutations) || (T.species.species_flags & SPECIES_FLAG_NO_SCAN)) - to_chat(src, SPAN_WARNING("We cannot extract DNA from this creature!")) - return 0 - - if(T.species.species_flags & SPECIES_FLAG_NEED_DIRECT_ABSORB) - to_chat(src, SPAN_NOTICE("That species must be absorbed directly.")) - return - - var/datum/absorbed_dna/newDNA = new(T.real_name, T.dna, T.species.name, T.languages) - absorbDNA(newDNA) - return 1 diff --git a/code/game/gamemodes/changeling/generic_equip_procs.dm b/code/game/gamemodes/changeling/generic_equip_procs.dm new file mode 100644 index 0000000000000..803c4ea0d4a11 --- /dev/null +++ b/code/game/gamemodes/changeling/generic_equip_procs.dm @@ -0,0 +1,295 @@ +///This is a generic proc that should be called by other ling armor procs to equip them. +/mob/proc/changeling_generic_armor(armor_type, helmet_type, boot_type, chem_cost) + + if(!ishuman(src)) + return FALSE + var/list/species_restricted = list("exclude", SPECIES_NABBER, SPECIES_ADHERENT,SPECIES_VOX) + var/mob/living/carbon/human/M = src + + if(istype(M.wear_suit, armor_type) || istype(M.head, helmet_type) || istype(M.shoes, boot_type)) + chem_cost = 0 + + var/datum/changeling/changeling = changeling_power(chem_cost, 1, 100, CONSCIOUS) + + if(!changeling) + return + if(M.species in species_restricted) + to_chat(M,SPAN_WARNING("Our current form is not suited to such a transformation.")) + return + + //First, check if we're already wearing the armor, and if so, take it off. + if(istype(M.wear_suit, armor_type) || istype(M.head, helmet_type) || istype(M.shoes, boot_type)) + M.visible_message("[M] casts off their [M.wear_suit.name]!", + "We cast off our [M.wear_suit.name]", + "You hear the organic matter ripping and tearing!") + if(istype(M.wear_suit, armor_type)) + qdel(M.wear_suit) + if(istype(M.head, helmet_type)) + qdel(M.head) + if(istype(M.shoes, boot_type)) + qdel(M.shoes) + M.update_inv_wear_suit() + M.update_inv_head() + M.update_hair() + M.update_inv_shoes() + return TRUE + + if(M.head || M.wear_suit) //Make sure our slots aren't full + to_chat(src, "We require nothing to be on our head, and we cannot wear any external suits, or shoes.") + return FALSE + + var/obj/item/clothing/suit/A = new armor_type(src) + src.equip_to_slot_or_del(A, slot_wear_suit) + + var/obj/item/clothing/suit/H = new helmet_type(src) + src.equip_to_slot_or_del(H, slot_head) + + var/obj/item/clothing/shoes/B = new boot_type(src) + src.equip_to_slot_or_del(B, slot_shoes) + + src.mind.changeling.chem_charges -= chem_cost + if(istype(M.wear_suit,/obj/item/clothing/suit/space/changeling/armored)) + playsound(src, 'sound/effects/ling_horror.ogg', 30, 1) + src.visible_message(SPAN_DANGER("\the [src] lets out out a monstrous roar of fury!")) + else + playsound(src, 'sound/effects/blobattack.ogg', 30, 1) + M.update_inv_wear_suit() + M.update_inv_head() + M.update_hair() + M.update_inv_shoes() + M.mind.changeling.armor_deployed = TRUE + return TRUE + +/mob/proc/changeling_generic_equip_all_slots(list/stuff_to_equip, cost) + var/datum/changeling/changeling = changeling_power(cost,1,100,CONSCIOUS) + if(!changeling) + return + + if(!ishuman(src)) + return FALSE + + var/mob/living/carbon/human/M = src + + var/success = FALSE + + //First, check if we're already wearing the armor, and if so, take it off. + + if(M.mind.changeling.armor_deployed) + if(M.head && stuff_to_equip["head"]) + if(istype(M.head, stuff_to_equip["head"])) + qdel(M.head) + success = TRUE + + if(M.wear_id && stuff_to_equip["wear_id"]) + if(istype(M.wear_id, stuff_to_equip["wear_id"])) + qdel(M.wear_id) + success = TRUE + + if(M.wear_suit && stuff_to_equip["wear_suit"]) + if(istype(M.wear_suit, stuff_to_equip["wear_suit"])) + qdel(M.wear_suit) + success = TRUE + + if(M.gloves && stuff_to_equip["gloves"]) + if(istype(M.gloves, stuff_to_equip["gloves"])) + qdel(M.gloves) + success = TRUE + if(M.shoes && stuff_to_equip["shoes"]) + if(istype(M.shoes, stuff_to_equip["shoes"])) + qdel(M.shoes) + success = TRUE + + if(M.belt && stuff_to_equip["belt"]) + if(istype(M.belt, stuff_to_equip["belt"])) + qdel(M.belt) + success = TRUE + + if(M.glasses && stuff_to_equip["glasses"]) + if(istype(M.glasses, stuff_to_equip["glasses"])) + qdel(M.glasses) + success = TRUE + + if(M.wear_mask && stuff_to_equip["wear_mask"]) + if(istype(M.wear_mask, stuff_to_equip["wear_mask"])) + qdel(M.wear_mask) + success = TRUE + + if(M.back && stuff_to_equip["back"]) + if(istype(M.back, stuff_to_equip["back"])) + for(var/atom/movable/AM in M.back.contents) //Dump whatever's in the bag before deleting. + AM.forceMove(src.loc) + qdel(M.back) + success = TRUE + + if(M.w_uniform && stuff_to_equip["w_uniform"]) + if(istype(M.w_uniform, stuff_to_equip["w_uniform"])) + qdel(M.w_uniform) + success = TRUE + + if(success) + playsound(src, 'sound/effects/splat.ogg', 30, 1) + visible_message("[src] pulls on their clothes, peeling it off along with parts of their skin attached!", + "We remove and deform our equipment.") + M.mind.changeling.armor_deployed = 0 + src.update_icon() + return success + + else + + to_chat(M, "We begin growing our new equipment...") + + var/list/grown_items_list = list() + + var/t = stuff_to_equip["head"] + if(!M.head && t) + var/I = new t + M.equip_to_slot_or_del(I, slot_head) + grown_items_list.Add("a helmet") + playsound(src, 'sound/effects/blobattack.ogg', 30, 1) + success = TRUE + sleep(1 SECOND) + + t = stuff_to_equip["w_uniform"] + if(!M.w_uniform && t) + var/I = new t + M.equip_to_slot_or_del(I, slot_w_uniform) + grown_items_list.Add("a uniform") + playsound(src, 'sound/effects/blobattack.ogg', 30, 1) + success = TRUE + sleep(1 SECOND) + + t = stuff_to_equip["gloves"] + if(!M.gloves && t) + var/I = new t + M.equip_to_slot_or_del(I, slot_gloves) + grown_items_list.Add("some gloves") + playsound(src, 'sound/effects/splat.ogg', 30, 1) + success = TRUE + sleep(1 SECOND) + + t = stuff_to_equip["shoes"] + if(!M.shoes && t) + var/I = new t + M.equip_to_slot_or_del(I, slot_shoes) + grown_items_list.Add("shoes") + playsound(src, 'sound/effects/splat.ogg', 30, 1) + success = TRUE + sleep(1 SECOND) + + t = stuff_to_equip["belt"] + if(!M.belt && t) + var/I = new t + M.equip_to_slot_or_del(I, slot_belt) + grown_items_list.Add("a belt") + playsound(src, 'sound/effects/splat.ogg', 30, 1) + success = TRUE + sleep(1 SECOND) + + t = stuff_to_equip["glasses"] + if(!M.glasses && t) + var/I = new t + M.equip_to_slot_or_del(I, slot_glasses) + grown_items_list.Add("some glasses") + playsound(src, 'sound/effects/splat.ogg', 30, 1) + success = TRUE + sleep(1 SECOND) + + t = stuff_to_equip["wear_mask"] + if(!M.wear_mask && t) + var/I = new t + M.equip_to_slot_or_del(I, slot_wear_mask) + grown_items_list.Add("a mask") + playsound(src, 'sound/effects/splat.ogg', 30, 1) + success = TRUE + sleep(1 SECOND) + + t = stuff_to_equip["back"] + if(!M.back && t) + var/I = new t + M.equip_to_slot_or_del(I, slot_back) + grown_items_list.Add("a backpack") + playsound(src, 'sound/effects/blobattack.ogg', 30, 1) + success = TRUE + sleep(1 SECOND) + + t = stuff_to_equip["wear_suit"] + if(!M.wear_suit && t) + var/I = new t + M.equip_to_slot_or_del(I, slot_wear_suit) + grown_items_list.Add("an exosuit") + playsound(src, 'sound/effects/blobattack.ogg', 30, 1) + success = TRUE + sleep(1 SECOND) + + t = stuff_to_equip["wear_id"] + if(!M.wear_id && t) + var/I = new t + M.equip_to_slot_or_del(I, slot_wear_id) + grown_items_list.Add("an ID card") + playsound(src, 'sound/effects/splat.ogg', 30, 1) + success = TRUE + sleep(1 SECOND) + + var/feedback = english_list(grown_items_list, nothing_text = "nothing", and_text = " and ", comma_text = ", ", final_comma_text = "" ) + + to_chat(M, "We have grown [feedback].") + + if(success) + M.mind.changeling.armor_deployed = 1 + M.mind.changeling.chem_charges -= 10 + src.update_icon() + return success + +///This is a generic proc that should be called by other ling weapon procs to equip them. +/mob/proc/changeling_generic_weapon(weapon_type, make_sound = TRUE, cost) + + var/datum/changeling/changeling = changeling_power(cost,1,100,CONSCIOUS) + if(!changeling) + return + + if(!ishuman(src)) + return FALSE + if (!src || src.incapacitated() || src.lying) + to_chat(src,SPAN_WARNING("We cannot do this while we are stunned.")) + return FALSE + var/mob/living/carbon/human/M = src + if(M.l_hand && M.r_hand) //Make sure our hands aren't full. + to_chat(src, SPAN_WARNING("We do not have any free hands we can shape into a weapon!")) + return FALSE + + + var/obj/item/weapon/W = new weapon_type(src) + if(!M.put_in_hands(W)) + to_chat(src, SPAN_WARNING("We cannot shape a missing limb into a weapon!")) + qdel(W) + return FALSE + + src.mind.changeling.chem_charges -= cost + if(make_sound) + playsound(src, 'sound/effects/blobattack.ogg', 30, 1) + return TRUE + +///Special proc for the ranged sting to pass information about the type of sting +/mob/proc/changeling_equip_ranged(weapon_type, make_sound = FALSE, cost = 10, sting_type) + + var/datum/changeling/changeling = changeling_power(cost,1,100,CONSCIOUS) + if(!changeling) + return + + if(!ishuman(src)) + return FALSE + + var/mob/living/carbon/human/M = src + + if(M.l_hand && M.r_hand ) //Make sure our hands aren't full. + to_chat(src, SPAN_WARNING("We do not have a free hand to shape into a weapon. Drop something first.")) + return FALSE + + var/obj/item/gun/projectile/changeling/W = new weapon_type(src) + src.put_in_hands(W) + W.selected_sting = sting_type + src.mind.changeling.selected_ranged_sting = sting_type + src.mind.changeling.chem_charges -= cost + if(make_sound) + playsound(src, 'sound/effects/blobattack.ogg', 30, 1) + return TRUE diff --git a/code/game/gamemodes/changeling/modularchangling.dm b/code/game/gamemodes/changeling/modularchangling.dm index d4bd05f7e9350..6d1fb2485e378 100644 --- a/code/game/gamemodes/changeling/modularchangling.dm +++ b/code/game/gamemodes/changeling/modularchangling.dm @@ -461,7 +461,7 @@ var/global/list/datum/power/changeling/powerinstances = list() if(isnull(Thepower)) - to_chat(M.current, "This is awkward. Changeling power purchase failed, please report this bug to a coder!") + CRASH("This is awkward. Changeling power purchase failed, please report this bug to a coder!") return if(Thepower in purchasedpowers) diff --git a/code/game/gamemodes/changeling/powers/absorb.dm b/code/game/gamemodes/changeling/powers/absorb.dm new file mode 100644 index 0000000000000..abebe16b7ffb4 --- /dev/null +++ b/code/game/gamemodes/changeling/powers/absorb.dm @@ -0,0 +1,135 @@ +/datum/power/changeling/absorb_dna + name = "Absorb DNA" + desc = "Permits us to syphon the DNA from a human. They become one with us, and we become stronger if they were of our kind." + ability_icon_state = "ling_absorb_dna" + genomecost = 0 + power_category = CHANGELING_POWER_INHERENT + verbpath = /mob/living/proc/changeling_absorb_dna + +/** + * Absorbs the victim's DNA. Requires a strong grip on the victim. + * Doesn't cost anything as it's the most basic ability. + */ +/mob/proc/toggle_absorb_type() + set category = "Changeling" + set name = "Toggle Absorb Type" + set desc = "Allows us to determine whether we wish to kill targets we absorb" + if(src.mind.changeling) + if(src.mind.changeling.absorbing_lethally == ABSORB_NONLETHAL) + to_chat(src,SPAN_INFO("We will now kill creatures whose DNA we sample.")) + src.mind.changeling.absorbing_lethally = ABSORB_LETHAL + return + if(src.mind.changeling.absorbing_lethally == ABSORB_LETHAL) + to_chat(src,SPAN_INFO("We will no longer kill creatures whose DNA we sample.")) + src.mind.changeling.absorbing_lethally = ABSORB_NONLETHAL + return +/mob/living/proc/changeling_absorb_dna(mob/living/carbon/M) + set category = "Changeling" + set name = "Absorb DNA" + var/list/names = list() + + var/datum/changeling/changeling = changeling_power(0,0,100) + if(!changeling) return + + var/obj/item/grab/G = src.get_active_hand() + if(!istype(G)) + to_chat(src, "We must be grabbing a creature in our active hand to absorb them.") + return + + var/mob/living/carbon/human/T = G.affecting + for(var/datum/absorbed_dna/DNA in changeling.absorbed_dna) + names += "[DNA.name]" + if(T.real_name in names) + to_chat(src,SPAN_NOTICE("We have already sampled the DNA of this creature. There is nothing further to learn here.")) + return + if(T == src) + to_chat(src,SPAN_WARNING("We would have to be very silly to try absorbing ourselves.")) + return + if(!istype(T) || T.isSynthetic() || istype(T.species,/datum/species/vox)) + to_chat(src, "\The [T] is not compatible with our biology.") + return + + if(MUTATION_HUSK in T.mutations) //Lings can always absorb other lings, unless someone beat them to it first. + if(!T.mind.changeling || T.mind.changeling && T.mind.changeling.geneticpoints < 0) + to_chat(src, "This creature's DNA is ruined beyond useability!") + return + + if(!G.force_danger()) + to_chat(src, "We must have a tighter grip to absorb this creature.") + return + + if(changeling.isabsorbing) + to_chat(src, "We are already absorbing!") + return + + changeling.isabsorbing = 1 + for(var/stage = 1, stage<=3, stage++) + switch(stage) + if(1) + to_chat(src, "This creature is compatible. We must hold still...") + if(2) + to_chat(src, "We extend a proboscis.") + src.visible_message("[src] extends a proboscis!") + if(3) + to_chat(src, "We stab [T] with the proboscis.") + src.visible_message("[src] stabs [T] with the proboscis!") + to_chat(T, "You feel a sharp stabbing pain!") + admin_attack_log(src,T,"Absorbed (changeling)") + var/obj/item/organ/external/affecting = T.get_organ(src.zone_sel.selecting) + T.apply_damage(50, DAMAGE_BRUTE, affecting , damage_flags = DAMAGE_FLAG_SHARP, used_weapon="organic needle", armor_pen=50) + + + if(!do_mob(src, T, 150, progress= TRUE) || !G.force_danger()) + to_chat(src, "Our absorption of the DNA of \the [T] has been interrupted!") + changeling.isabsorbing = 0 + return + + to_chat(src, "We have absorbed the DNA of \the [T]!") + + + changeling.chem_charges += 10 + + + //add pronouns to this line + var/datum/absorbed_dna/newDNA = new(T.real_name, T.dna, T.species.name, T.languages, T.gender, T.pronouns, T.flavor_texts, T.icon_render_keys, T.descriptors) + absorbDNA(newDNA) + + if(T.mind && T.mind.changeling) + if(T.mind.changeling.absorbed_dna) + for(var/datum/absorbed_dna/dna_data in T.mind.changeling.absorbed_dna) //steal all their loot + if(dna_data in changeling.absorbed_dna) + continue + absorbDNA(dna_data) + changeling.absorbedcount++ + LIST_RESIZE(T.mind.changeling.absorbed_dna, 1) + + // This is where lings get boosts from eating eachother + if(T.mind.changeling.lingabsorbedcount) + for(var/a = 1 to T.mind.changeling.lingabsorbedcount) + changeling.lingabsorbedcount++ + changeling.geneticpoints += 6 + changeling.max_geneticpoints += 6 + + to_chat(src, SPAN_NOTICE("We have feasted upon a fellow changeling, and grown stronger.")) + + T.mind.changeling.chem_charges = 0 + T.mind.changeling.geneticpoints = -1 + T.mind.changeling.max_geneticpoints = -1 //To prevent revival. + T.mind.changeling.absorbedcount = 0 + T.mind.changeling.lingabsorbedcount = 0 + T.death(0) + T.Drain() + return TRUE + changeling.absorbedcount++ + changeling.isabsorbing = 0 + to_chat(T, SPAN_DANGER("You feel everything go black, as \the [src] releases you.")) + if(changeling.absorbing_lethally == ABSORB_LETHAL) + T.death(0) + T.Drain() + changeling.geneticpoints += 1 + changeling.max_geneticpoints += 1 + if(changeling.absorbing_lethally == ABSORB_NONLETHAL) + changeling.geneticpoints += 2 + changeling.max_geneticpoints += 2 + T.Sleeping(200) + return TRUE diff --git a/code/game/gamemodes/changeling/powers/armblade.dm b/code/game/gamemodes/changeling/powers/armblade.dm new file mode 100644 index 0000000000000..4f4b1c8a1a382 --- /dev/null +++ b/code/game/gamemodes/changeling/powers/armblade.dm @@ -0,0 +1,329 @@ + +/datum/power/changeling/arm_hammer + name = "Arm Hammer" + desc = "We reform one of our arms into a weighty hammer capable of smashing quickly through obstacles or victims alike. We can slam enemies aside with it on disarm intent, sending them flying." + helptext = "We may retract our armhammer by using the ability again with the hammer in our active hand." + ability_icon_state = "ling_hammer" + enhancedtext = "The hammer will have further armor peneratration." + genomecost = 2 + power_category = CHANGELING_POWER_WEAPONS + verbpath = /mob/proc/changeling_arm_hammer +/datum/power/changeling/arm_blade + name = "Arm Blade" + desc = "We reform one of our arms into a deadly blade capable of slicing through armor. It can pry open airlocks, breaking them in the process." + helptext = "We may retract our armblade by using the ability again with the blade in our active hand." + enhancedtext = "The blade will have greater armor peneratration and parry more effectively." + ability_icon_state = "ling_blade" + genomecost = 2 + power_category = CHANGELING_POWER_WEAPONS + verbpath = /mob/proc/changeling_arm_blade + +//HAMMERTIME +/mob/proc/changeling_arm_hammer() + set category = "Changeling" + set name = "Arm Hammer (15)" + var/holding = src.get_active_hand() + if (istype(holding, /obj/item/melee/changeling/arm_hammer)) + to_chat(src,SPAN_WARNING("We shrink our arm back to its normal size.")) + playsound(src, 'sound/effects/blobattack.ogg', 30, 1) + qdel(holding) + return 0 + if(src.mind.changeling.recursive_enhancement) + src.mind.changeling.recursive_enhancement = FALSE + if(changeling_generic_weapon(/obj/item/melee/changeling/arm_hammer/greater, cost = 15)) + //to_chat(src, "We prepare an extra sharp blade.") + return 1 + else + if(changeling_generic_weapon(/obj/item/melee/changeling/arm_hammer, cost = 15)) + return 1 + return 0 + +//Grows a scary, and powerful arm blade. +/mob/proc/changeling_arm_blade() + set category = "Changeling" + set name = "Arm Blade (20)" + var/holding = src.get_active_hand() + if (istype(holding, /obj/item/melee/changeling/arm_blade)) + to_chat(src,SPAN_WARNING("We retract our blade back into our body.")) + playsound(src, 'sound/effects/blobattack.ogg', 30, 1) + qdel(holding) + return 0 + if(src.mind.changeling.recursive_enhancement) + src.mind.changeling.recursive_enhancement = FALSE + if(changeling_generic_weapon(/obj/item/melee/changeling/arm_blade/greater, cost = 20)) + //to_chat(src, "We prepare an extra sharp blade.") + return 1 + + else + if(changeling_generic_weapon(/obj/item/melee/changeling/arm_blade, cost = 20)) + return 1 + return 0 + +//Claws +/datum/power/changeling/claw + name = "Claw" + desc = "We reform one of our arms into a deadly claw. We can instantly aggressively grab enemies with it on grab intent." + helptext = "We may retract our claw by using the ability again with the claw in our active hand." + enhancedtext = "The claw will have armor peneratration." + ability_icon_state = "ling_claw" + genomecost = 1 + power_category = CHANGELING_POWER_WEAPONS + verbpath = /mob/proc/changeling_claw + +//Grows a scary, and powerful claw. +/mob/proc/changeling_claw() + set category = "Changeling" + set name = "Claw (15)" + var/holding = src.get_active_hand() + if (istype(holding, /obj/item/melee/changeling/claw)) + to_chat(src,SPAN_WARNING("We reform our claw back into an ordinary appendage.")) + playsound(src, 'sound/effects/blobattack.ogg', 30, 1) + qdel(holding) + return 0 + if(src.mind.changeling.recursive_enhancement) + src.mind.changeling.recursive_enhancement = FALSE + if(changeling_generic_weapon(/obj/item/melee/changeling/claw/greater, 1, cost = 15)) + to_chat(src, SPAN_NOTICE("We prepare an extra sharp claw.")) + return 1 + + else + if(changeling_generic_weapon(/obj/item/melee/changeling/claw, 1, cost = 15)) + return 1 + return 0 + +/obj/item/melee/changeling + name = "arm weapon" + desc = "A grotesque weapon made out of bone and flesh that cleaves through people as a hot knife through butter." + icon = 'icons/obj/weapons/melee_physical.dmi' + icon_state = "ling_blade" + item_icons = list( + icon_l_hand = 'icons/mob/onmob/items/lefthand.dmi', + icon_r_hand = 'icons/mob/onmob/items/righthand.dmi', + ) + item_state = "arm_blade" + w_class = ITEM_SIZE_HUGE + force = 5 + hitsound = 'sound/weapons/bladeslice.ogg' + anchored = TRUE + throwforce = 0 //Just to be on the safe side + throw_range = 0 + throw_speed = 0 + var/mob/living/creator //This is just like ninja swords, needed to make sure dumb shit that removes the sword doesn't make it stay around. + var/weapType = "weapon" + var/weapLocation = "arm" + canremove = FALSE + base_parry_chance = 40 // The base chance for the weapon to parry. + +/obj/item/melee/changeling/Initialize() + . = ..() + START_PROCESSING(SSobj, src) + if(ismob(loc)) + src.creator = loc + + +/obj/item/melee/changeling/Destroy() + STOP_PROCESSING(SSobj, src) + creator = null + return ..() +/obj/item/melee/changeling/Process() + var/mob/living/carbon/human/H = creator + if ( H.handcuffed || (H.stat != CONSCIOUS)) + qdel(src) + if(!istype(loc,/mob)) + src.visible_message(SPAN_DANGER("\The [src] rapidly decays and melts into a puddle of slime!")) + new /obj/decal/cleanable/ling_vomit(src.loc) + qdel(src) + ..() +/obj/item/melee/changeling/arm_hammer + name = "arm hammer" + desc = "A hammer made out of flesh and bone, heavy enough to smash through armor and people alike." + icon_state = "ling_hammer" + item_state = "ling_hammer" + force = 25 + hitsound = 'sound/weapons/genhit3.ogg' + armor_penetration = 30 + sharp = FALSE + edge = FALSE + attack_verb = list("attacked", "struck", "smashed", "clubbed", "beaten", "hit", "battered", "smacked") + base_parry_chance = 50 + var/last_slam = null + var/cooldown = 7 SECONDS +/obj/item/melee/changeling/arm_hammer/greater + name = "arm greathammer" + desc = "A massive hammer made out of flesh and bone, heavy enough to smash through armor and people alike." + armor_penetration = 50 + +/obj/item/melee/changeling/arm_hammer/use_before(atom/target,mob/user) + if (user.a_intent == I_DISARM) + if ((last_slam + cooldown > world.time)) + to_chat(user, SPAN_WARNING("We are still recovering from our last slam attack.")) + return + if(istype(target,/mob/living/carbon)) + var/mob/living/carbon/M = target + user.do_attack_animation(M) + var/target_zone = check_zone(user.zone_sel.selecting) + M.apply_damage(force, DAMAGE_BRUTE, target_zone) + user.visible_message(SPAN_DANGER("\the [user] slams their arm hammer into \the [M] in a furious blow, sending them flying!")) + M.throw_at(get_edge_target_turf(user, user.dir), 5, 5) + playsound(src, 'sound/weapons/punch1.ogg', 30, 1) + last_slam = world.time + + if(istype(target,/turf/simulated/wall) || istype(target,/obj/structure) && (user.a_intent != I_HELP)) + target.damage_health(rand(25, 75), DAMAGE_BRUTE) + +/obj/item/melee/changeling/arm_blade + name = "arm blade" + desc = "A grotesque blade made out of bone and flesh that cleaves through people as a hot knife through butter." + icon_state = "ling_blade" + item_state = "arm_blade" + force = 35 + armor_penetration = 15 + sharp = TRUE + edge = TRUE + attack_verb = list("attacked", "slashed", "stabbed", "sliced", "ripped", "diced", "cut") + base_parry_chance = 60 + +/obj/item/melee/changeling/arm_blade/use_before(atom/target,mob/user) + if (istype(target, /obj/machinery/door/airlock)) + var/obj/machinery/door/airlock/A = target + if(A.locked) + to_chat(user,SPAN_WARNING("We cannot force open an airlock held in place by bolts.")) + return + A.visible_message(SPAN_DANGER("\The [user] forces \the [src] between \the [A], causing the metal to creak!")) + playsound(A, 'sound/machines/airlock_creaking.ogg', 100, 1) + if (do_after(user, 5 SECONDS, A, DO_DEFAULT | DO_USER_UNIQUE_ACT | DO_PUBLIC_PROGRESS) && !A.locked) + A.welded = FALSE + A.update_icon() + playsound(A, 'sound/effects/meteorimpact.ogg', 100, 1) + A.visible_message(SPAN_DANGER("\The [user] tears \the [A] open with \a [src]!")) + addtimer(new Callback(A, /obj/machinery/door/airlock/.proc/open, TRUE), 0) + A.open() + A.set_broken(TRUE) + + if (istype(target, /obj/machinery/door/firedoor)) + var/obj/machinery/door/firedoor/A = target + playsound(A, 'sound/machines/airlock_creaking.ogg', 100, 1) + if (do_after(user, 2 SECONDS, A, DO_DEFAULT | DO_USER_UNIQUE_ACT | DO_PUBLIC_PROGRESS)) + A.visible_message(SPAN_DANGER("\The [user] pries \the [A] wide open effortlessly!")) + A.open(TRUE) + if (istype(target, /obj/machinery/door/blast)) + var/obj/machinery/door/blast/A = target + if(!istype(user.get_inactive_hand(),/obj/item/melee/changeling/arm_blade)) + to_chat(user,SPAN_WARNING("We require an armblade in both arms to be able to exert enough force to pry a blast door open.")) + return + A.visible_message(SPAN_DANGER("\The [user] forces both armblades between \the [A], prying with incredible strength!")) + playsound(A, 'sound/machines/airlock_creaking.ogg', 100, 1) + if (do_after(user, 20 SECONDS, A, DO_DEFAULT | DO_USER_UNIQUE_ACT | DO_PUBLIC_PROGRESS) ) + A.update_icon() + playsound(A, 'sound/effects/meteorimpact.ogg', 100, 1) + A.visible_message(SPAN_DANGER("\The [user] tears \the [A] wide open!")) + A.force_open() + +/obj/item/melee/changeling/arm_blade/greater + name = "arm greatblade" + desc = "A grotesque blade made out of bone and flesh that cleaves through people and armor as a hot knife through butter." + armor_penetration = 30 + base_parry_chance = 70 + +/obj/item/melee/changeling/claw + name = "hand claw" + item_state = "ling_claw" + desc = "A grotesque claw made out of bone and flesh that cleaves through people as a hot knife through butter." + icon_state = "ling_claw" + force = 20 + sharp = FALSE + edge = TRUE + attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") + base_parry_chance = 50 + var/last_grab = null + var/cooldown = 5 SECONDS +/obj/item/melee/changeling/claw/use_before(mob/living/M,mob/user) + + if(user.a_intent == I_GRAB) + if ((last_grab + cooldown > world.time)) + to_chat(user, SPAN_WARNING("We are still recovering from our last grab attack.")) + return + if(istype(M,/mob/living/carbon) && (user.mind.changeling.chem_charges > 10)) + var/mob/living/carbon/human/H = user + if(!user.get_inactive_hand()) + user.swap_hand() + H.species.attempt_grab(H,M) + var/obj/item/grab/holding = H.get_active_hand() + if(istype(holding,/obj/item/grab)) + holding.upgrade(bypass_cooldown = TRUE) + last_grab = world.time + +/obj/item/melee/changeling/claw/greater + name = "hand greatclaw" + force = 25 + armor_penetration = 20 + base_parry_chance = 60 + +/datum/power/changeling/arm_shield + name = "Arm Shield" + desc = "We reform one of our arms into a resilient shield to protect ourselves from harm. The shield can deflect projectiles, but not reliably." + helptext = "We may retract our shield by using the ability again with the shield in hand." + enhancedtext = "The shield will be more resistant to damage." + ability_icon_state = "ling_shield" + genomecost = 2 + power_category = CHANGELING_POWER_WEAPONS + verbpath = /mob/proc/changeling_arm_shield + +/mob/proc/changeling_arm_shield() + set category = "Changeling" + set name = "Arm Shield (20)" + var/holding = src.get_active_hand() + if (istype(holding, /obj/item/shield/riot/changeling)) + to_chat(src,SPAN_WARNING("We retract our claw back into our body.")) + playsound(src, 'sound/effects/blobattack.ogg', 30, 1) + qdel(holding) + return 0 + if(src.mind.changeling.recursive_enhancement) + src.mind.changeling.recursive_enhancement = FALSE + if(changeling_generic_weapon(/obj/item/shield/riot/changeling/greater)) + //to_chat(src, "We prepare an extra sharp blade.") + return 1 + + else + if(changeling_generic_weapon(/obj/item/shield/riot/changeling)) + return 1 + return 0 + +/obj/item/shield/riot/changeling + name = "chitin shield" + item_state = "ling_shield" + icon_state = "ling_shield" + desc = "A monstrously thick and bulky mess of fleshy chitin, covered in shards of bone." + throwforce = 0 + throw_speed = 0 + throw_range = 0 + anchored = TRUE + var/mob/living/creator //This is just like ninja swords, needed to make sure dumb shit that removes the sword doesn't make it stay around. + var/weapType = "weapon" + var/weapLocation = "arm" + canremove = FALSE +/obj/item/shield/riot/changeling/greater + name = "chitin greatshield" + max_block = 50 + can_block_lasers = TRUE + +/obj/item/shield/riot/changeling/Initialize() + . = ..() + START_PROCESSING(SSobj, src) + if(ismob(loc)) + src.creator = loc + +/obj/item/shield/riot/changeling/Destroy() + STOP_PROCESSING(SSobj, src) + creator = null + return ..() + +/obj/item/shield/riot/changeling/Process() + var/mob/living/carbon/human/H = creator + if ( H.handcuffed || (H.stat != CONSCIOUS)) + qdel(src) + if(!istype(loc,/mob)) + src.visible_message(SPAN_DANGER("\The [src] rapidly decays and melts into a puddle of slime!")) + new /obj/decal/cleanable/ling_vomit(src.loc) + qdel(src) + ..() diff --git a/code/game/gamemodes/changeling/powers/armor.dm b/code/game/gamemodes/changeling/powers/armor.dm new file mode 100644 index 0000000000000..eeb9429c550f1 --- /dev/null +++ b/code/game/gamemodes/changeling/powers/armor.dm @@ -0,0 +1,196 @@ +/datum/power/changeling/space_suit + name = "Organic Space Suit" + desc = "We grow an organic suit to protect ourselves from space exposure." + helptext = "To remove the suit, use the ability again." + ability_icon_state = "ling_space_suit" + genomecost = 1 + verbpath = /mob/proc/changeling_spacesuit + +/mob/proc/changeling_spacesuit() + set category = "Changeling" + set name = "Organic Space Suit (20)" + if(changeling_generic_armor(/obj/item/clothing/suit/space/changeling,/obj/item/clothing/head/helmet/space/changeling,/obj/item/clothing/shoes/magboots/changeling, 20)) + return TRUE + return FALSE + +/datum/power/changeling/armor + name = "Chitinous Spacearmor" + desc = "We turn our skin into tough chitin to protect us from damage and space exposure." + helptext = "To remove the armor, use the ability again." + ability_icon_state = "ling_armor" + genomecost = 3 + verbpath = /mob/proc/changeling_spacearmor + +/mob/proc/changeling_spacearmor() + set category = "Changeling" + set name = "Chitinous Spacearmor (20)" + + if(changeling_generic_armor(/obj/item/clothing/suit/space/changeling/armored,/obj/item/clothing/head/helmet/space/changeling/armored,/obj/item/clothing/shoes/magboots/changeling/armored, 20)) + + return TRUE + return FALSE + +//Space suit + +/obj/item/clothing/suit/space/changeling + name = "organic suit" + icon_state = "lingspacesuit" + desc = "A thin, skintight mass of pressure and temperature-resistant organic tissue, evolved to facilitate space travel." + allowed = list(/obj/item/device/flashlight, /obj/item/tank/oxygen_emergency, /obj/item/tank/oxygen_emergency_extended) + armor = list(melee = 0, bullet = 0, laser = 0,energy = 0, bomb = 0, bio = 0, rad = 0) //No armor at all. + canremove = FALSE + can_breach = FALSE + flags_inv = BLOCKHAIR | HIDETAIL + var/remove_on_respec = TRUE + +/obj/item/clothing/suit/space/changeling/Initialize() + . = ..() + slowdown_per_slot[slot_wear_suit] = 0 + START_PROCESSING(SSobj, src) + if(ismob(loc)) + to_chat(src,SPAN_WARNING("[loc.name]\'s flesh splits and twists, forming into a thin fleshy membrane around their body!")) + +/obj/item/clothing/suit/space/changeling/dropped(mob/user) + playsound(src, 'sound/effects/blobattack.ogg', 30, 1) + +/obj/item/clothing/suit/space/changeling/Destroy() + + STOP_PROCESSING(SSobj, src) + + return ..() + +/obj/item/clothing/head/helmet/space/changeling + name = "flesh mass" + icon_state = "lingspacehelmet" + desc = "A covering of pressure and temperature-resistant organic tissue with a translucent, glass-like front made of organic tissue." + flags_inv = HIDEEARS|BLOCKHEADHAIR //Again, no THICKMATERIAL. + body_parts_covered = HEAD|FACE|EYES + var/remove_on_respec = TRUE + canremove = 0 + flags_inv = BLOCKHAIR | HIDETAIL | HIDEFACE + + +/obj/item/clothing/head/helmet/space/changeling/Destroy() + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/clothing/shoes/magboots/changeling + desc = "A suction cupped mass of flesh, shaped like a foot." + name = "fleshy grippers" + icon_state = "lingspacesuit" + action_button_name = "Toggle Grippers" + canremove = 0 + online_slowdown = 3 + var/remove_on_respec = TRUE + +/obj/item/clothing/shoes/magboots/changeling/set_slowdown() + slowdown_per_slot[slot_shoes] = shoes? max(0, shoes.slowdown_per_slot[slot_shoes]): 0 //So you can't put on magboots to make you walk faster. + if (magpulse) + slowdown_per_slot[slot_shoes] += online_slowdown + +/obj/item/clothing/shoes/magboots/changeling/attack_self(mob/user) + if(magpulse) + item_flags &= ~ITEM_FLAG_NOSLIP + magpulse = 0 + set_slowdown() + force = 3 + to_chat(user, "We release our grip on the floor.") + else + item_flags |= ITEM_FLAG_NOSLIP + magpulse = 1 + set_slowdown() + force = 5 + to_chat(user, "We cling to the terrain below us.") + + +/obj/item/clothing/shoes/magboots/changeling/Destroy() + STOP_PROCESSING(SSobj, src) + return ..() + +//Armor + +/obj/item/clothing/suit/space/changeling/armored + name = "chitinous mass" + desc = "A protective shell of muscle, bone and chitin, designed for violence and violence alone." + icon_state = "lingarmor" + body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS + //It costs 3 points, so it should be very protective. + //armor = list(melee = 75, bullet = 65, laser = 60, energy = 60, bomb = 60, bio = 0, rad = 0) + armor = list( + melee = ARMOR_MELEE_VERY_HIGH, + bullet = ARMOR_BALLISTIC_RESISTANT, + laser = ARMOR_LASER_HANDGUNS, + energy = ARMOR_ENERGY_RESISTANT, + bomb = ARMOR_BOMB_PADDED, + ) + siemens_coefficient = 0.3 + can_breach = FALSE + flags_inv = BLOCKHAIR | HIDETAIL + //max_heat_protection_temperature = FIRESUIT_MAX_HEAT_PROTECTION_TEMPERATURE + slowdown_general = 0 + +/obj/item/clothing/suit/space/changeling/armored/Initialize() + . = ..() + START_PROCESSING(SSobj, src) + slowdown_per_slot[slot_wear_suit] = 0 + if(ismob(loc)) + to_chat(src,SPAN_WARNING("Our muscles twist and our bones crack with a crunching noise as we form claws, teeth and armor!")) + + /* + loc.visible_message("[loc.name]\'s flesh turns black, quickly transforming into a hard, chitinous mass!", + "We harden our flesh, creating a suit of armor!", + "You hear organic matter ripping and tearing!") + */ +/obj/item/clothing/suit/space/changeling/armored/Destroy() + + + STOP_PROCESSING(SSobj, src) + + return ..() + + +/obj/item/clothing/head/helmet/space/changeling/armored + name = "chitinous mass" + desc = "An enormous eyeless maw of teeth that hungers." + icon_state = "lingarmorhelmet" + armor = list( + melee = ARMOR_MELEE_VERY_HIGH, + bullet = ARMOR_BALLISTIC_RESISTANT, + laser = ARMOR_LASER_HANDGUNS, + energy = ARMOR_ENERGY_RESISTANT, + bomb = ARMOR_BOMB_PADDED, + ) + siemens_coefficient = 0.3 + max_heat_protection_temperature = FIRE_HELMET_MAX_HEAT_PROTECTION_TEMPERATURE + flags_inv = BLOCKHAIR | HIDETAIL | HIDEFACE + +/obj/item/clothing/shoes/magboots/changeling/armored + desc = "A tough, hard mass of chitin, with long talons for digging into terrain." + name = "chitinous talons" + icon_state = "lingchameleon" + action_button_name = "Toggle Talons" + +/obj/item/clothing/gloves/combat/changeling //Combined insulated/fireproof gloves + name = "chitinous gauntlets" + desc = "Very resilient gauntlets made out of black chitin. It looks very durable, and can probably resist electrical shock in addition to the elements." + icon_state = "lingarmorgloves" + armor = list(melee = 75, bullet = 60, laser = 60,energy = 60, bomb = 60, bio = 0, rad = 0) //No idea if glove armor gets checked + siemens_coefficient = 0 + +/obj/item/clothing/shoes/boots/combat/changeling //Noslips + name = "chitinous boots" + desc = "Footwear made out of a hard, black chitinous material. The bottoms of these appear to have spikes that can protrude or extract itself into and out \ + of the floor at will, granting the wearer stability." + icon_state = "lingchameleon" + armor = list( + melee = ARMOR_MELEE_VERY_HIGH, + bullet = ARMOR_BALLISTIC_RESISTANT, + laser = ARMOR_LASER_HANDGUNS, + energy = ARMOR_ENERGY_RESISTANT, + bomb = ARMOR_BOMB_PADDED, + ) + siemens_coefficient = 0.3 + cold_protection = FEET + min_cold_protection_temperature = SHOE_MIN_COLD_PROTECTION_TEMPERATURE + heat_protection = FEET + max_heat_protection_temperature = SHOE_MAX_HEAT_PROTECTION_TEMPERATURE diff --git a/code/game/gamemodes/changeling/powers/augmented_eyesight.dm b/code/game/gamemodes/changeling/powers/augmented_eyesight.dm new file mode 100644 index 0000000000000..bc6c03d541dee --- /dev/null +++ b/code/game/gamemodes/changeling/powers/augmented_eyesight.dm @@ -0,0 +1,42 @@ +//Augmented Eyesight: Gives you thermal vision. Also, higher DNA cost because of how powerful it is. +//needs to be fixed to act per tick, polaris port didn't work in that respect + +/datum/power/changeling/augmented_eyesight + name = "Augmented Eyesight" + desc = "Allows for extra sensory information in the eyes, increasing photon reception capabilities for a short span of time." + helptext = "Grants us temporary thermal vision, allowing us to track organics beyond walls." + ability_icon_state = "ling_augmented_eyesight" + genomecost = 2 + verbpath = /mob/proc/changeling_augmented_eyesight + +/mob/proc/changeling_augmented_eyesight() + set category = "Changeling" + set name = "Augmented Eyesight (3 per tick)" + set desc = "We evolve our eyes to sense the infrared temporarily." + + var/datum/changeling/changeling = changeling_power(3,0,100,CONSCIOUS) + var/mob/living/carbon/human/H = src + var/obj/item/organ/external/parent = H.get_organ(BP_HEAD) + var/has_organ = FALSE; + for(var/obj/item/organ/internal/augment/ling_lenses/eyes in H.internal_organs) + has_organ++ + if(!changeling) + return FALSE + if(!has_organ) + var/obj/item/organ/internal/augment/eyes = new /obj/item/organ/internal/augment/ling_lenses + eyes.forceMove(src) + eyes.replaced(src, parent) + eyes = null + has_organ++ + if(has_organ) + if(H.seedarkness) + H.seedarkness = FALSE + for(var/obj/item/organ/internal/augment/ling_lenses/eyes in H.internal_organs) + if(!eyes.is_active) + to_chat(src, SPAN_NOTICE("We feel a minute twitch in our eyes, and a hidden layer to the world is revealed.")) + eyes.is_active = TRUE + else + to_chat(src,SPAN_NOTICE("Our lenses retract, causing us to lose our augmented vision.")) + eyes.is_active = FALSE + changeling.chem_charges -= 5 + return TRUE diff --git a/code/game/gamemodes/changeling/powers/bioelectrogenesis.dm b/code/game/gamemodes/changeling/powers/bioelectrogenesis.dm new file mode 100644 index 0000000000000..d145130362eec --- /dev/null +++ b/code/game/gamemodes/changeling/powers/bioelectrogenesis.dm @@ -0,0 +1,219 @@ +/datum/power/changeling/bioelectrogenesis + name = "Bioelectrogenesis" + desc = "We reconfigure a large number of cells in our body to generate an electric charge. \ + On demand, we can attempt to recharge anything in our active hand, or we can touch someone with an electrified hand, shocking them." + helptext = "We can shock someone by grabbing them and using this ability, or using the ability with an empty hand and touching them. \ + Shocking someone costs ten chemicals per use." + enhancedtext = "Shocking biologicals without grabbing only requires five chemicals, and has more disabling power." + ability_icon_state = "ling_bioelectrogenesis" + genomecost = 2 + verbpath = /mob/living/carbon/human/proc/changeling_bioelectrogenesis + +//Recharge whatever's in our hand, or shock people. +/mob/living/carbon/human/proc/changeling_bioelectrogenesis() + set category = "Changeling" + set name = "Bioelectrogenesis (15 + 10/shock)" + set desc = "Recharges anything in your hand, or shocks people." + var/obj/held_item = get_active_hand() + if (istype(held_item, /obj/item/weapon/electric_hand)) + qdel(held_item) + return 0 + var/datum/changeling/changeling = changeling_power(20,0,100,CONSCIOUS) + + + var/last_shock = null + var/cooldown = 3 SECONDS + if(!changeling) + return FALSE + + if(!held_item) + if(src.mind.changeling.recursive_enhancement) + if(changeling_generic_weapon(/obj/item/weapon/electric_hand/efficent,0)) + to_chat(src, "We will shock others more efficently.") + return TRUE + else + if(changeling_generic_weapon(/obj/item/weapon/electric_hand,0)) //Chemical cost is handled in the equip proc. + return TRUE + return FALSE + else + // Handle glove conductivity. + var/obj/item/clothing/gloves/gloves = src.gloves + var/siemens = 1 + if(gloves) + siemens = gloves.siemens_coefficient + if((last_shock + cooldown > world.time)) + to_chat(src,SPAN_DANGER("Our bioelectric hand is still recharging a voltage powerful enough to shock someone!")) + return FALSE + //If we're grabbing someone, electrocute them. + var/obj/item/grab/G = src.get_active_hand() + if(istype(G)) + if(G.affecting) + G.affecting.electrocute_act(10 * siemens, src, 1.0, BP_CHEST, 0) + var/agony = 80 * siemens //Does more than if hit with an electric hand, since grabbing is slower. + G.affecting.stun_effect_act(0, agony, BP_CHEST, src) + + admin_attack_log(src,G.affecting,"Changeling shocked") + + if(siemens) + visible_message("Arcs of electricity strike [G.affecting]!", + "Our hand channels raw electricity into [G.affecting].", + "You hear sparks!") + last_shock = world.time + else + to_chat(src, "Our gloves block us from shocking \the [G.affecting].") + src.mind.changeling.chem_charges -= 10 + return 1 + + //Otherwise, charge up whatever's in their hand. + else + //This checks both the active hand, and the contents of the active hand's held item. + var/success = 0 + var/list/L = new() //We make a new list to avoid copypasta. + + //Check our hand. + if(istype(held_item,/obj/item/cell)) + L.Add(held_item) + + //Now check our hand's item's contents, so we can recharge guns and other stuff. + for(var/obj/item/cell/cell in held_item.contents) + L.Add(cell) + + //Now for the actual recharging. + for(var/obj/item/cell/cell in L) + visible_message("Some sparks fall out from \the [src.name]\'s [held_item]!", + "Our hand channels raw electricity into \the [held_item].", + "You hear sparks!") + var/i = 10 + if(siemens) + while(i) + cell.charge += 100 * siemens //This should be a nice compromise between recharging guns and other batteries. + if(cell.charge > cell.maxcharge) + cell.charge = cell.maxcharge + break + if(siemens) + var/T = get_turf(src) + new /obj/sparks(T) + held_item.update_icon() + i-- + sleep(1 SECOND) + success = 1 + if(success == 0) //If we couldn't do anything with the ability, don't deduct the chemicals. + to_chat(src, "We are unable to affect \the [held_item].") + else + src.mind.changeling.chem_charges -= 10 + return success + +/obj/item/weapon/electric_hand + name = "electrified hand" + desc = "You could probably shock someone badly if you touched them, or recharge something." + icon = 'icons/obj/weapons/melee_energy.dmi' + icon_state = "electric_hand" + canremove = FALSE + var/shock_cost = 10 + var/agony_amount = 60 + var/electrocute_amount = 10 + var/last_shock = null + var/cooldown = 5 SECONDS +/obj/item/weapon/electric_hand/efficent + name = "supercharged hand" + desc = "Power! Unlimited Power!" + shock_cost = 5 + agony_amount = 80 + electrocute_amount = 20 + +/obj/item/weapon/electric_hand/Initialize() + . = ..() + if(ismob(loc)) + src.visible_message(SPAN_DANGER("[src]\'s hand crackles with electricity, sparks flying about!")) + var/T = get_turf(src) + new /obj/sparks(T) + +/obj/item/weapon/electric_hand/afterattack(atom/target, mob/living/carbon/human/user, proximity) + if(!target) + return + if(!proximity) + return + + // Handle glove conductivity. + var/obj/item/clothing/gloves/gloves = user.gloves + var/siemens = 1 + if(gloves) + siemens = gloves.siemens_coefficient + + //Excuse the copypasta. + if(istype(target,/mob/living/carbon)) + var/mob/living/carbon/C = target + + if(user.mind.changeling.chem_charges < shock_cost) + to_chat(user, SPAN_WARNING("We require at least 10 chemicals to electrocute [C]!")) + return FALSE + if((last_shock + cooldown > world.time)) + to_chat(user,SPAN_DANGER("Our bioelectric hand is still recharging a voltage powerful enough to shock someone!")) + return FALSE + C.electrocute_act(electrocute_amount * siemens,src,1.0,BP_CHEST) + C.stun_effect_act(0, agony_amount * siemens, BP_CHEST, src) + + admin_attack_log(user,C,"Shocked with [src]") + + if(siemens) + to_chat(user, SPAN_NOTICE("Our hand channels raw electricity into [C]")) + src.visible_message(SPAN_DANGER("Arcs of electricity strike [C]!")) + last_shock = world.time + //visible_message("Arcs of electricity strike [C]!", + //"Our hand channels raw electricity into [C]", + //"You hear sparks!") + else + to_chat(src, SPAN_WARNING("Our gloves block us from shocking \the [C].")) + //qdel(src) //Since we're no longer a one hit stun, we need to stick around. + user.mind.changeling.chem_charges -= shock_cost + return TRUE + + else if(istype(target,/mob/living/silicon)) + var/mob/living/silicon/S = target + + if(user.mind.changeling.chem_charges < 10) + to_chat(src, "We require at least 10 chemicals to electrocute [S]!") + return FALSE + + S.electrocute_act(60,src,0.75) //If only they had surge protectors. + if(siemens) + visible_message("Arcs of electricity strike [S]!", + "Our hand channels raw electricity into [S]", + "You hear sparks!") + to_chat(S, "Warning: Electrical surge detected!") + //qdel(src) + user.mind.changeling.chem_charges -= 10 + return TRUE + + else + if(istype(target,/obj)) + var/success = 0 + var/obj/T = target + //We can also recharge things we touch, such as APCs or hardsuits. + for(var/obj/item/cell/cell in T.contents) + src.visible_message(SPAN_WARNING("[src]\'s hand crackles, sparks darting from it to \the [cell]")) + /*visible_message("Some sparks fall out from \the [target]!", + "Our hand channels raw electricity into \the [target].", + "You hear sparks!")*/ + var/i = 10 + if(siemens) + while(i) + cell.charge += 100 * siemens //This should be a nice compromise between recharging guns and other batteries. + if(cell.charge > cell.maxcharge) + cell.charge = cell.maxcharge + break //No point making sparks if the cell's full. + // if(!Adjacent(T)) + // break + if(siemens) + var/Turf = get_turf(src) + new /obj/sparks(Turf) + T.update_icon() + i-- + sleep(1 SECOND) + success = 1 + break + if(success == 0) + to_chat(src, "We are unable to affect \the [target].") + else + qdel(src) + return TRUE diff --git a/code/game/gamemodes/changeling/powers/blind_sting.dm b/code/game/gamemodes/changeling/powers/blind_sting.dm new file mode 100644 index 0000000000000..8e5f178db6b59 --- /dev/null +++ b/code/game/gamemodes/changeling/powers/blind_sting.dm @@ -0,0 +1,33 @@ +/datum/power/changeling/blind_sting + name = "Blind Sting" + desc = "We silently sting a human, completely blinding them for a short time." + enhancedtext = "Duration is extended." + ability_icon_state = "ling_sting_blind" + genomecost = 2 + allowduringlesserform = 1 + verbpath = /mob/proc/changeling_blind_sting + sting_effect = /mob/proc/ling_blind + is_sting = TRUE +/mob/proc/apply_blind() + src.disabilities &= ~NEARSIGHTED +/mob/proc/ling_blind(mob/living/carbon/M, duration) + addtimer(new Callback(M,/mob/.proc/apply_blind),duration) + M.eye_blind = 10 + M.eye_blurry = 20 +/mob/proc/changeling_blind_sting() + set category = "Changeling" + set name = "Blind sting (20)" + set desc="Sting target" + + var/mob/living/carbon/T = changeling_sting(20,/mob/proc/changeling_blind_sting) + if(!T) + return FALSE + admin_attack_log(src,T,"Blind sting (changeling)") + to_chat(T, "You are surrounded by darkness. You cannot see!") + T.disabilities |= NEARSIGHTED + var/duration = 300 + if(src.mind.changeling.recursive_enhancement) + duration = duration + 150 + to_chat(src, "They will be deprived of sight for longer.") + ling_blind(T,duration) + return TRUE diff --git a/code/game/gamemodes/changeling/powers/boost_range.dm b/code/game/gamemodes/changeling/powers/boost_range.dm new file mode 100644 index 0000000000000..cae0ecaa359de --- /dev/null +++ b/code/game/gamemodes/changeling/powers/boost_range.dm @@ -0,0 +1,123 @@ +/datum/power/changeling/boost_range + name = "Boost Range" + desc = "We evolve the ability to fire biological darts from our hand to deliver our sting." + helptext = "Allows us to use the effects of our stings at a distance" + enhancedtext = "The range is extended to five tiles." + ability_icon_state = "ling_sting_boost_range" + genomecost = 1 + allowduringlesserform = 1 + verbpath = /mob/proc/changeling_boost_range + +//Boosts the range of your next sting attack by 1 + +/obj/item/ammo_casing/chemdart/changeling + name = "organic dart" + desc = "A needle thin dart formed of bone." + icon_state = "ling_dart" + icon = 'icons/obj/projectiles.dmi' + caliber = CALIBER_DART + projectile_type = /obj/item/projectile/bullet/chemdart/changeling + leaves_residue = FALSE +/obj/item/projectile/bullet/chemdart/changeling + name = "organic dart" + fire_sound = 'sound/weapons/Genhit.ogg' + icon_state = "ling_dart" + life_span = 3 + icon = 'icons/obj/projectiles.dmi' +/obj/item/projectile/bullet/chemdart/changeling/on_hit(atom/target, blocked = 0, def_zone) + if(blocked < 100 && isliving(target)) + var/mob/living/L = target + if (src.firer == target) + return + var/datum/power/changeling/sting = src.firer.mind.changeling.selected_ranged_sting + //use firer ling mind to set ling selected ranged sting + if(L.can_inject(null, def_zone) == CAN_INJECT) + call(sting.sting_effect)(target,sting.sting_duration) + + +/obj/item/gun/projectile/changeling + name = "thin mass" + desc = "A thin, narrow and hollow mass of flesh protruding from the palm. It twitches and pulses, eager to strike." + can_reload = FALSE + screen_shake = FALSE + space_recoil = FALSE + has_safety = FALSE + silenced = TRUE + auto_eject = FALSE + handle_casings = CLEAR_CASINGS + max_shells = 1 + starts_loaded = TRUE + has_safety = FALSE + silenced = TRUE + icon = 'icons/obj/guns/dartgun.dmi' + item_state = null + icon_state = "ling_dart" + fire_sound = 'sound/weapons/Genhit.ogg' + anchored = TRUE + throwforce = 0 //Just to be on the safe side + throw_range = 0 + throw_speed = 0 + screen_shake = FALSE + space_recoil = FALSE + var/mob/living/creator //This is just like ninja swords, needed to make sure dumb shit that removes the sword doesn't make it stay around. + var/weapType = "weapon" + var/weapLocation = "arm" + canremove = FALSE + jam_chance = 0 + ammo_type = /obj/item/ammo_casing/chemdart/changeling + var/list/available_stings = list() + var/selected_sting = null + +/obj/item/gun/projectile/changeling/Initialize() + . = ..() + START_PROCESSING(SSobj, src) + if(ismob(loc)) + src.creator = loc + +/obj/item/gun/projectile/changeling/Destroy() + STOP_PROCESSING(SSobj, src) + creator = null + return ..() + +/obj/item/gun/projectile/changeling/Process() + var/mob/living/carbon/human/H = creator + + if ( H.handcuffed || (H.stat != CONSCIOUS) || (length(loaded) == 0)) + //playsound(src, 'sound/effects/blobattack.ogg', 30, 1) + to_chat(creator,SPAN_WARNING("We retract our ranged stinger back into our body.")) + qdel(src) + +/mob/proc/changeling_boost_range() + set category = "Changeling" + set name = "Ranged Stinger (20)" + set desc="We transform one of our hands so that it can fire organic darts at targets." + + var/list/available_stings = list() + var/datum/changeling/changeling = changeling_power(10,0,100) + if(!changeling) + return TRUE + //changeling.chem_charges -= 10 + var/holding = src.get_active_hand() + if (istype(holding, /obj/item/gun/projectile/changeling)) + to_chat(src,SPAN_WARNING("We retract our ranged stinger back into our body.")) + //playsound(src, 'sound/effects/blobattack.ogg', 30, 1) + qdel(holding) + return 0 + for (var/datum/power/changeling/power in src.mind.changeling.purchased_powers) + if (power.is_sting) + available_stings.Add(power) + if(length(available_stings) == 0) + to_chat(src,SPAN_WARNING("We do not have any stings that we can fire at range!")) + return FALSE + var/datum/power/changeling/sting= input(src,"Select a sting.", "Select") as null|anything in available_stings + if(sting) + var/sting_name = sting.name + to_chat(src,SPAN_WARNING("We prepare to fire a [sting_name]")) + if(src.mind.changeling.recursive_enhancement) + if(changeling_equip_ranged(/obj/item/gun/projectile/changeling, sting_type = sting)) + return 1 + + else + if(changeling_equip_ranged(/obj/item/gun/projectile/changeling, sting_type = sting)) + return 1 + return 0 diff --git a/code/game/gamemodes/changeling/powers/cryo_sting.dm b/code/game/gamemodes/changeling/powers/cryo_sting.dm new file mode 100644 index 0000000000000..c51ffdd7fb5a3 --- /dev/null +++ b/code/game/gamemodes/changeling/powers/cryo_sting.dm @@ -0,0 +1,38 @@ +/datum/power/changeling/cryo_sting + name = "Cryogenic Sting" + desc = "We silently sting a biological with a cocktail of chemicals that freeze them." + helptext = "Does not provide a warning to the victim, though they will likely realize they are suddenly freezing. Has \ + a three minute cooldown between uses." + enhancedtext = "Increases the amount of chemicals injected." + ability_icon_state = "ling_sting_cryo" + genomecost = 1 + verbpath = /mob/proc/changeling_cryo_sting + sting_effect = /mob/proc/ling_cryo + is_sting = TRUE + +/mob/proc/cryo_cooldown() + to_chat(src, "Our cryogenic string is ready to be used once more.") + src.verbs |= /mob/proc/changeling_cryo_sting + +/mob/proc/ling_cryo(mob/living/carbon/M, duration) + M.reagents.add_reagent(/datum/reagent/toxin/cryotoxin, 10) + +/mob/proc/changeling_cryo_sting() + set category = "Changeling" + set name = "Cryogenic Sting (20)" + set desc = "Chills and freezes a biological creature." + + var/mob/living/carbon/T = changeling_sting(20,/mob/proc/changeling_cryo_sting) + if(!T) + return FALSE + admin_attack_log(src,T,"Cryo sting (changeling)") + var/inject_amount = 10 + if(src.mind.changeling.recursive_enhancement) + inject_amount = inject_amount * 1.5 + to_chat(src, "We inject extra chemicals.") + if(T.reagents) + T.reagents.add_reagent(/datum/reagent/toxin/cryotoxin, inject_amount) + src.verbs -= /mob/proc/changeling_cryo_sting + addtimer(new Callback(src,/mob/.proc/cryo_cooldown), 3 MINUTES) + + return TRUE diff --git a/code/game/gamemodes/changeling/powers/darkvision.dm b/code/game/gamemodes/changeling/powers/darkvision.dm new file mode 100644 index 0000000000000..011403d6c8361 --- /dev/null +++ b/code/game/gamemodes/changeling/powers/darkvision.dm @@ -0,0 +1,30 @@ +/datum/power/changeling/darksight + name = "Dark Sight" + desc = "We change the composition of our eyes, banishing the shadows from our vision." + helptext = "We will be able to see in the dark." + ability_icon_state = "ling_augmented_eyesight" + genomecost = 0 + power_category = CHANGELING_POWER_INHERENT + verbpath = /mob/proc/changeling_darksight + +/mob/proc/changeling_darksight() + set category = "Changeling" + set name = "Toggle Darkvision" + set desc = "We are able see in the dark." + + var/datum/changeling/changeling = changeling_power(0,0,100,UNCONSCIOUS) + if(!changeling) + return 0 + + if(istype(src,/mob/living/carbon)) + var/mob/living/carbon/C = src + //disables thermals if we have it to prevent permanent double stacking + src.sight &= ~(SEE_MOBS) + if(C.seedarkness) + to_chat(C, SPAN_NOTICE("We no longer need light to see.")) + C.seedarkness = 0 + else + to_chat(C, SPAN_NOTICE("We allow the shadows to return.")) + C.seedarkness = 1 + + return 0 diff --git a/code/game/gamemodes/changeling/powers/deaf_sting.dm b/code/game/gamemodes/changeling/powers/deaf_sting.dm new file mode 100644 index 0000000000000..7b93982c4c769 --- /dev/null +++ b/code/game/gamemodes/changeling/powers/deaf_sting.dm @@ -0,0 +1,29 @@ +/datum/power/changeling/deaf_sting + name = "Deaf Sting" + desc = "We silently sting a human, completely deafening them for a short time." + enhancedtext = "Deafness duration is extended." + ability_icon_state = "ling_sting_deafen" + genomecost = 1 + allowduringlesserform = 1 + verbpath = /mob/proc/changeling_deaf_sting + sting_effect = /mob/proc/ling_deaf + sting_duration = 300 + is_sting = TRUE +/mob/proc/ling_deaf(mob/living/carbon/M, duration) + M.ear_deaf += duration +/mob/proc/changeling_deaf_sting() + set category = "Changeling" + set name = "Deaf sting (5)" + set desc="Sting target:" + + var/mob/living/carbon/T = changeling_sting(5,/mob/proc/changeling_deaf_sting) + if(!T) + return FALSE + admin_attack_log(src,T,"Deaf sting (changeling)") + var/duration = 300 + if(src.mind.changeling.recursive_enhancement) + duration = duration + 100 + to_chat(src, "They will be unable to hear for a little longer.") + to_chat(T, "Your ears pop and begin ringing loudly!") + ling_deaf(T,duration) + return TRUE diff --git a/code/game/gamemodes/changeling/powers/death_sting.dm b/code/game/gamemodes/changeling/powers/death_sting.dm new file mode 100644 index 0000000000000..aebbb4905ec8e --- /dev/null +++ b/code/game/gamemodes/changeling/powers/death_sting.dm @@ -0,0 +1,23 @@ +/datum/power/changeling/DeathSting + name = "Death Sting" + desc = "We silently sting a human, filling him with potent chemicals. His rapid death is all but assured." + ability_icon_state = "ling_sting_death" + genomecost = 7 + verbpath = /mob/proc/changeling_DEATHsting + is_sting = TRUE +/mob/proc/changeling_DEATHsting() + set category = "Changeling" + set name = "Death Sting (40)" + set desc = "Causes spasms onto death." + + var/mob/living/carbon/T = changeling_sting(40,/mob/proc/changeling_DEATHsting) + if(!T) + return FALSE + admin_attack_log(src,T,"Death sting (changeling)") + to_chat(T, "You feel a small prick and your chest becomes tight.") + T.silent = 10 + T.Paralyse(10) + T.make_jittery(100) + if(T.reagents) + T.reagents.add_reagent(/datum/reagent/lexorin, 40) + return TRUE diff --git a/code/game/gamemodes/changeling/powers/delayed_toxin_sting.dm b/code/game/gamemodes/changeling/powers/delayed_toxin_sting.dm new file mode 100644 index 0000000000000..536e64ed5299d --- /dev/null +++ b/code/game/gamemodes/changeling/powers/delayed_toxin_sting.dm @@ -0,0 +1,50 @@ +/datum/power/changeling/delayed_toxic_sting + name = "Delayed Toxic Sting" + desc = "We silently sting a biological, causing a significant amount of toxins after a few minutes, allowing us to not \ + implicate ourselves. While rarely lethal, highly effective for making organics leave their post to seek medical attention." + helptext = "The toxin takes effect in about two minutes. Multiple applications within the two minutes will not cause increased toxicity." + enhancedtext = "The toxic damage is doubled." + ability_icon_state = "ling_sting_del_toxin" + genomecost = 1 + verbpath = /mob/proc/changeling_delayed_toxic_sting + sting_effect = /mob/proc/ling_delay_toxin + is_sting = TRUE +//disgusting copypasta, but the alternative is a new prop to ling powers just for this one sting +/mob/proc/ling_delay_toxin(mob/living/carbon/M, null) + sleep(2 MINUTES) + M.adjustToxLoss(rand(20,30)) +/mob/proc/changeling_delayed_toxic_sting() + set category = "Changeling" + set name = "Delayed Toxic Sting (20)" + set desc = "Injects the target with a toxin that will take effect after a few minutes." + + var/effect_delay = 2 MINUTES + var/damage = rand(20,30) + var/mob/living/carbon/T = changeling_sting(20,/mob/proc/changeling_delayed_toxic_sting) + if(!T) + return FALSE + admin_attack_log(src,T,"Delayed toxic sting (changeling)") + if(src.mind.changeling.recursive_enhancement) + //type_to_give = /datum/modifier/delayed_toxin_sting/strong + to_chat(src, "Our toxin will be extra potent, when it strikes.") + damage = rand(40,60) + src.mind.changeling.recursive_enhancement = FALSE + if(!(T in src.mind.changeling.toxin_victims)) + src.mind.changeling.toxin_victims.Add(T) + sleep(effect_delay) + if(!(T in src.mind.changeling.toxin_victims)) + return FALSE + T.adjustToxLoss(damage) + src.mind.changeling.toxin_victims.Remove(T) + + //var/test = new /datum/delayed_toxin_sting() + //var/sting_delay = /datum/modifier/delayed_toxin_sting + + + + + //T.add_modifier(type_to_give, 10 SECONDS) + + + + return TRUE diff --git a/code/game/gamemodes/changeling/powers/electric_lockpick.dm b/code/game/gamemodes/changeling/powers/electric_lockpick.dm new file mode 100644 index 0000000000000..1641c06c08966 --- /dev/null +++ b/code/game/gamemodes/changeling/powers/electric_lockpick.dm @@ -0,0 +1,99 @@ +/datum/power/changeling/electric_lockpick + name = "Electric Lockpick" + desc = "We discreetly evolve a finger to be able to send a small electric charge. \ + We can open most electrical locks, but it will be obvious when we do so." + helptext = "Use the ability, then touch something that utilizes an electrical locking system, to open it. Each use costs 10 chemicals." + ability_icon_state = "ling_electric_lockpick" + genomecost = 3 + verbpath = /mob/proc/changeling_electric_lockpick + +//Emag-lite +/mob/proc/changeling_electric_lockpick() + set category = "Changeling" + set name = "Electric Lockpick (5 + 10/use)" + set desc = "Bruteforces open most electrical locking systems, at 10 chemicals per use." + + + + var/obj/held_item = get_active_hand() + if (istype(held_item, /obj/item/weapon/finger_lockpick)) + //to_chat(src,SPAN_WARNING("We cease charging our hand with electricity")) + //playsound(src, 'sound/effects/blobattack.ogg', 30, 1) + qdel(held_item) + return 0 + var/datum/changeling/changeling = changeling_power(5,0,100,CONSCIOUS) + if(!changeling) + return FALSE + + if(!held_item) + if(changeling_generic_weapon(/obj/item/weapon/finger_lockpick,0,5)) //Chemical cost is handled in the equip proc. + return TRUE + return FALSE + +/obj/item/weapon/finger_lockpick + name = "finger lockpick" + desc = "This finger appears to be an organic datajack." + icon = 'icons/obj/weapons/melee_energy.dmi' + icon_state = "electric_lockpick" + canremove = FALSE +/obj/item/weapon/finger_lockpick/Initialize() + . = ..() + if(ismob(loc)) + to_chat(loc, "We shape our finger to fit inside electronics, and are ready to force them open.") +/obj/item/weapon/finger_lockpick/use_before(atom/target, mob/living/user, click_parameters) + + if(!target) + return + if(!target.Adjacent(user)) + return + if(!user.mind.changeling) + return + + var/datum/changeling/ling_datum = user.mind.changeling + var/breach_secure = TRUE + if(ling_datum.chem_charges < 10) + to_chat(user, "We require at least 10 chem charges to do that.") + return + + //Airlocks require an ugly block of code, but we don't want to just call emag_act(), since we don't want to break airlocks forever. + if(istype(target,/obj/machinery/door)) + if(istype(target,/obj/machinery/door/airlock/highsecurity) || istype(target,/obj/machinery/door/airlock/vault)) + to_chat(user, SPAN_WARNING("This door's security is complex. It will take us longer to determine the charge needed to breach it.")) + breach_secure = do_after(user, (15 SECONDS + rand(0, 5 SECONDS) + rand(0, 5 SECONDS)), target, do_flags = (DO_DEFAULT | DO_BOTH_UNIQUE_ACT) & ~DO_SHOW_PROGRESS) + var/obj/machinery/door/door = target + to_chat(user, "We send an electrical pulse up our finger, and into \the [target], attempting to open it.") + + if(door.density && door.operable() && breach_secure) + door.do_animate("spark") + sleep(6) + //More typechecks, because windoors can't be locked. Fun. + if(istype(target,/obj/machinery/door/airlock)) + + var/obj/machinery/door/airlock/airlock = target + + if(airlock.locked) //Check if we're bolted. + airlock.unlock() + to_chat(user, "We've unlocked \the [airlock]. Another pulse is requried to open it.") + else //We're not bolted, so open the door already. + airlock.open() + to_chat(user, "We've opened \the [airlock].") + else + door.open() //If we're a windoor, open the windoor. + to_chat(user, "We've opened \the [door].") + else //Probably broken or no power. + to_chat(user, "The door does not respond to the pulse.") + door.add_fingerprint(user) + log_and_message_admins("finger-lockpicked \an [door].") + ling_datum.chem_charges -= 10 + return TRUE + + else if(istype(target,/obj/structure/closet/secure_closet)) //This should catch everything else we might miss, without a million typechecks. + var/obj/O = target + to_chat(user, "We send an electrical pulse up our finger, and into \the [O].") + O.add_fingerprint(user) + O.emag_act(1,user,src) + log_and_message_admins("finger-lockpicked \an [O].") + ling_datum.chem_charges -= 10 + + return TRUE + return FALSE diff --git a/code/game/gamemodes/changeling/powers/endoarmor.dm b/code/game/gamemodes/changeling/powers/endoarmor.dm new file mode 100644 index 0000000000000..8a5fbc84ab768 --- /dev/null +++ b/code/game/gamemodes/changeling/powers/endoarmor.dm @@ -0,0 +1,23 @@ +/datum/power/changeling/endoarmor + name = "Endoarmor" + desc = "We grow hard plating underneath our skin, granting us subtle armor organics will not notice." + helptext = "The armor will not provide us any protection from lasers or fire." + genomecost = 1 + isVerb = 0 + verbpath = /mob/proc/changeling_endoarmor + +/mob/proc/changeling_endoarmor() + if(ishuman(src)) + + var/mob/living/carbon/human/H = src + var/obj/item/organ/internal/augment/armor = new /obj/item/organ/internal/augment/armor/changeling + //H.add_modifier(/datum/modifier/endoarmor) + var/obj/item/organ/external/parent = H.get_organ(BP_CHEST) + armor.forceMove(src) + armor.replaced(src, parent) + src.mind.changeling.purchased_organs.Add(armor) + armor = null + playsound(src, 'sound/effects/corpsecube.ogg',35,1) + to_chat(src, SPAN_NOTICE("Our flesh knits and cracks as the endoarmor forms beneath our skin.")) + + return TRUE diff --git a/code/game/gamemodes/changeling/powers/engorged_glands.dm b/code/game/gamemodes/changeling/powers/engorged_glands.dm new file mode 100644 index 0000000000000..4300fa2850e8e --- /dev/null +++ b/code/game/gamemodes/changeling/powers/engorged_glands.dm @@ -0,0 +1,13 @@ +/datum/power/changeling/EngorgedGlands + name = "Engorged Chemical Glands" + desc = "Our chemical glands swell, permitting us to store more chemicals inside of them." + helptext = "Allows us to store an extra 30 units of chemicals." + genomecost = 1 + isVerb = 0 + verbpath = /mob/proc/changeling_engorgedglands + +//Increases macimum chemical storage +/mob/proc/changeling_engorgedglands() + src.mind.changeling.chem_storage += 30 + //src.mind.changeling.chem_recharge_rate *= 2 + return TRUE diff --git a/code/game/gamemodes/changeling/powers/enrage.dm b/code/game/gamemodes/changeling/powers/enrage.dm new file mode 100644 index 0000000000000..fb45843189435 --- /dev/null +++ b/code/game/gamemodes/changeling/powers/enrage.dm @@ -0,0 +1,43 @@ +/datum/power/changeling/enrage + name = "Enrage" + desc = "We evolve modifications to our mind and body, allowing us to call on intense periods of rage for our benefit." + helptext = "Renders us nearly immune to pain and superhumanly fast for one minute, and losing the ability to \ + be accurate at ranged while active. Afterwards, we will suffer extreme amounts of exhaustion, slowing us and potentially causing us to even pass out." + enhancedtext = "The length of our rage will be extended by an additional 30 seconds." + ability_icon_state = "ling_berserk" + genomecost = 2 + power_category = CHANGELING_POWER_ENHANCEMENTS + allowduringlesserform = 1 + verbpath = /mob/living/proc/changeling_berserk + +// Makes the ling very upset. +/mob/living/proc/changeling_berserk() + set category = "Changeling" + set name = "Enrage (30)" + set desc = "Causes you to go Berserk." + + var/datum/changeling/changeling = changeling_power(30,0,100) + var/mob/living/carbon/human/H = src + var/obj/item/organ/external/parent = H.get_organ(BP_GROIN) + var/has_organ = 0; + if(!changeling) + return 0 + + for(var/obj/item/organ/internal/augment/changeling/ragecore/rage in H.internal_organs) + has_organ++ + if(has_organ == 0) + var/obj/item/organ/internal/augment/changeling/rage = new /obj/item/organ/internal/augment/changeling/ragecore + rage.forceMove(src) + rage.replaced(src, parent) + rage = null + has_organ++ + if(has_organ == 1) + for(var/obj/item/organ/internal/augment/changeling/ragecore/rage in H.internal_organs) + if(src.mind.changeling.recursive_enhancement) + rage.ticks_remaining = 90; + src.mind.changeling.recursive_enhancement = FALSE + else + rage.ticks_remaining = 60; + to_chat(src,SPAN_WARNING("Our adrenal glands release a surge of energy, pushing our body to its limits!")) + changeling.chem_charges -= 30 + return TRUE diff --git a/code/game/gamemodes/changeling/powers/escape_restraints.dm b/code/game/gamemodes/changeling/powers/escape_restraints.dm new file mode 100644 index 0000000000000..8ba5de26fd773 --- /dev/null +++ b/code/game/gamemodes/changeling/powers/escape_restraints.dm @@ -0,0 +1,57 @@ +/datum/power/changeling/escape_restraints + name = "Escape Restraints" + desc = "We evolve more complex joints" + helptext = "We can instantly escape from most restraints and bindings, but we cannot do it often." + enhancedtext = "More frequent escapes." + ability_icon_state = "ling_escape_restraints" + genomecost = 2 + verbpath = /mob/proc/changeling_escape_restraints + +//Escape Cuffs. By design this does not escape from straight jackets +/mob/proc/changeling_escape_restraints() + set category = "Changeling" + set name = "Escape Restraints (40)" + set desc = "Removes handcuffs and legcuffs instantly." + + var/escape_cooldown = 5 MINUTES //This is used later to prevent spamming + var/mob/living/carbon/human/C = src + var/datum/changeling/changeling = changeling_power(40,0,100,CONSCIOUS) + if(!changeling) + return 0 + if(world.time < changeling.next_escape) + to_chat(src, "We are still recovering from our last escape...") + return 0 + if(!(C.handcuffed || istype(C.wear_suit,/obj/item/clothing/suit/straight_jacket))) // No need to waste chems if there's nothing to break out of + to_chat(C, "We are are not restrained in a way we can escape...") + return 0 + + changeling.chem_charges -= 40 + + to_chat(C,"We contort our extremities and slip our cuffs.") + playsound(src, 'sound/effects/blobattack.ogg', 30, 1) + if(C.handcuffed) + var/obj/item/weapon/W = C.handcuffed + C.unEquip(C.handcuffed) + //C.handcuffed = null + if(C.buckled && C.buckled.buckle_require_restraints) + C.buckled.unbuckle_mob() + C.drop_from_inventory(C.handcuffed) + if (C.client) + C.client.screen -= W + W.forceMove(C.loc) + W.dropped(C) + if(W) + W.layer = initial(W.layer) + if(istype(C.wear_suit, /obj/item/clothing/suit/straight_jacket)) + var/obj/item/clothing/suit/straight_jacket/SJ = C.wear_suit + SJ.forceMove(C.loc) + SJ.dropped(C) + C.wear_suit = null + escape_cooldown *= 1.5 // Straight jackets are tedious compared to cuffs. + + if(src.mind.changeling.recursive_enhancement) + escape_cooldown *= 0.5 + + changeling.next_escape = world.time + escape_cooldown //And now we set the timer + + return TRUE diff --git a/code/game/gamemodes/changeling/powers/extract_dna_sting.dm b/code/game/gamemodes/changeling/powers/extract_dna_sting.dm new file mode 100644 index 0000000000000..ba1394461a0f7 --- /dev/null +++ b/code/game/gamemodes/changeling/powers/extract_dna_sting.dm @@ -0,0 +1,40 @@ +/datum/power/changeling/extractdna + name = "Extract DNA" + desc = "We stealthily sting a target and extract the DNA from them." + helptext = "Will give you the DNA of your target, allowing you to transform into them. Does not count towards absorb objectives." + ability_icon_state = "ling_sting_extract" + genomecost = 0 + allowduringlesserform = 1 + verbpath = /mob/proc/changeling_extract_dna_sting + +/mob/proc/changeling_extract_dna_sting() + set category = "Changeling" + set name = "Extract DNA Sting (40)" + set desc="Stealthily sting a target to extract their DNA." + + var/datum/changeling/changeling = null + if(src.mind && src.mind.changeling) + changeling = src.mind.changeling + if(!changeling) + return FALSE + + var/mob/living/carbon/human/T = changeling_sting(40, /mob/proc/changeling_extract_dna_sting) + + if(!T) + return + + if(!istype(T) || T.isSynthetic()) + to_chat(src, "\The [T] is not compatible with our biology.") + return FALSE + + if(MUTATION_HUSK in T.mutations) + to_chat(src, "This creature's DNA is ruined beyond useability!") + return FALSE + + admin_attack_log(src,T,"DNA extraction sting (changeling)") + + var/saved_dna = T.dna.Clone() /// Prevent transforming bugginess. + var/datum/absorbed_dna/newDNA = new(T.real_name, saved_dna, T.species.name, T.languages, T.gender, T.pronouns, T.flavor_texts, T.icon_render_keys, T.descriptors ) + absorbDNA(newDNA) + + return TRUE diff --git a/code/game/gamemodes/changeling/powers/fabricate_clothing.dm b/code/game/gamemodes/changeling/powers/fabricate_clothing.dm new file mode 100644 index 0000000000000..82162217f0923 --- /dev/null +++ b/code/game/gamemodes/changeling/powers/fabricate_clothing.dm @@ -0,0 +1,303 @@ +var/global/list/changeling_fabricated_clothing = list( + "w_uniform" = /obj/item/clothing/under/chameleon/changeling, + "head" = /obj/item/clothing/head/chameleon/changeling, + "wear_suit" = /obj/item/clothing/suit/chameleon/changeling, + "shoes" = /obj/item/clothing/shoes/chameleon/changeling, + "gloves" = /obj/item/clothing/gloves/chameleon/changeling, + "wear_mask" = /obj/item/clothing/mask/chameleon/changeling, + "glasses" = /obj/item/clothing/glasses/chameleon/changeling, + "back" = /obj/item/storage/backpack/chameleon/changeling, + "wear_id" = /obj/item/card/id/syndicate/changeling + ) + +/datum/power/changeling/fabricate_clothing + name = "Fabricate Clothing" + desc = "We reform our flesh to resemble various cloths, leathers, and other materials, allowing us to quickly create a disguise. \ + We cannot be relieved of this clothing by others. We start" + helptext = "The disguise we create offers no defensive ability. Each equipment slot that is empty will be filled with fabricated equipment. \ + To remove our new fabricated clothing, use this ability again." + ability_icon_state = "ling_fabricate_clothing" + genomecost = 1 + verbpath = /mob/proc/changeling_fabricate_clothing + +//Grows biological versions of chameleon clothes. +/mob/proc/changeling_fabricate_clothing() + set category = "Changeling" + set name = "Fabricate Clothing (10)" + + if(changeling_generic_equip_all_slots(changeling_fabricated_clothing, cost = 10)) + return TRUE + return FALSE + +/obj/item/clothing/under/chameleon/changeling + name = "malformed flesh" + icon_state = "lingchameleon" + item_state = "lingchameleon" + worn_state = "lingchameleon" + desc = "The flesh all around us has grown a new layer of cells that can shift appearance and create a biological fabric that cannot be distinguished from \ + ordinary cloth, allowing us to make ourselves appear to wear almost anything." + origin_tech = list() //The base chameleon items have origin technology, which we will inherit if we don't null out this variable. + canremove = 0 //Since this is essentially flesh impersonating clothes, tearing someone's skin off as if it were clothing isn't possible. + +/obj/item/clothing/under/chameleon/changeling/emp_act(severity) //As these are purely organic, EMP does nothing to them. + ..() + return + +/obj/item/clothing/under/chameleon/changeling/verb/shred() //Remove individual pieces if needed. + set name = "Shred Jumpsuit" + set category = "Chameleon Items" + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + qdel(H.wear_id) + playsound(src, 'sound/effects/splat.ogg', 30, 1) + visible_message("[H] tears off [src]!", + "We remove [src].") + qdel(src) +/obj/item/clothing/under/chameleon/changeling/verb/secrete_accessory() + set name = "Secrete Accessory" + set category = "Chameleon Items" + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + if(H.l_hand && H.r_hand) //Make sure our hands aren't full. + to_chat(H, SPAN_WARNING("Our hands are full. Drop something first.")) + return FALSE + var/A = new /obj/item/clothing/accessory/chameleon/changeling + H.put_in_hands(A) + to_chat(H, SPAN_WARNING("We pluck off a piece of our flesh, and prepare to shape it in the form of some organic trinket.")) +/obj/item/clothing/head/chameleon/changeling + name = "malformed head" + icon_state = "lingchameleon" + item_state = "lingchameleon" + flags_inv = HIDEFACE | HIDEEYES + desc = "Our head is swelled with a large quantity of rapidly shifting skin cells. We can reform our head to resemble various hats and \ + helmets that biologicals are so fond of wearing." + origin_tech = list() + canremove = 0 + +/obj/item/clothing/head/chameleon/changeling/emp_act(severity) + ..() + return + +/obj/item/clothing/head/chameleon/changeling/verb/shred() //The copypasta is real. + set name = "Shred Helmet" + set category = "Chameleon Items" + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + playsound(src, 'sound/effects/splat.ogg', 30, 1) + visible_message("[H] tears off [src]!", + "We remove [src].") + qdel(src) + +/obj/item/clothing/suit/chameleon/changeling + name = "chitinous chest" + icon_state = "lingchameleon" + item_icons = list( + slot_l_hand_str = 'icons/mob/onmob/items/lefthand_spacesuits.dmi', + slot_r_hand_str = 'icons/mob/onmob/items/righthand_spacesuits.dmi', + ) + item_state = "lingchameleon" + desc = "The cells in our chest are rapidly shifting, ready to reform into material that can resemble most pieces of clothing." + origin_tech = list() + canremove = FALSE + +/obj/item/clothing/suit/chameleon/changeling/emp_act(severity) + ..() + return + +/obj/item/clothing/suit/chameleon/changeling/verb/shred() + set name = "Shred Suit" + set category = "Chameleon Items" + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + playsound(src, 'sound/effects/splat.ogg', 30, 1) + visible_message("[H] tears off [src]!", + "We remove [src].") + qdel(src) + +/obj/item/clothing/shoes/chameleon/changeling + name = "malformed feet" + icon_state = "lingchameleon" + item_state = "lingchameleon" + desc = "Our feet are overlayed with another layer of flesh and bone on top. We can reform our feet to resemble various boots and shoes." + origin_tech = list() + canremove = FALSE + +/obj/item/clothing/shoes/chameleon/changeling/emp_act() + ..() + return + +/obj/item/clothing/shoes/chameleon/changeling/verb/shred() + set name = "Shred Shoes" + set category = "Chameleon Items" + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + playsound(src, 'sound/effects/splat.ogg', 30, 1) + visible_message("[H] tears off [src]!", + "We remove [src].") + qdel(src) + +/obj/item/storage/backpack/chameleon/changeling + name = "backpack" + icon_state = "lingchameleon" + item_icons = list( + slot_l_hand_str = 'icons/mob/onmob/items/lefthand_backpacks.dmi', + slot_r_hand_str = 'icons/mob/onmob/items/righthand_backpacks.dmi', + ) + item_state = "lingchameleon" + desc = "A large pouch imbedded in our back, it can shift form to resemble many common backpacks that other biologicals are fond of using." + origin_tech = list() + canremove = FALSE + item_flags = ITEM_FLAG_IS_CHAMELEON_ITEM + +/obj/item/storage/backpack/chameleon/changeling/emp_act() + ..() + return + +/obj/item/storage/backpack/chameleon/changeling/verb/shred() + set name = "Shred Backpack" + set category = "Chameleon Items" + if(ishuman(loc)) + if(length(contents)) + to_chat(loc,SPAN_WARNING("We cannot shred our storage while there are items still inside.")) + return + var/mob/living/carbon/human/H = loc + playsound(src, 'sound/effects/splat.ogg', 30, 1) + visible_message("[H] tears off [src]!", + "We remove [src].") + for(var/atom/movable/AM in src.contents) //Dump whatever's in the bag before deleting. + AM.forceMove(get_turf(loc)) + qdel(src) + +/obj/item/clothing/gloves/chameleon/changeling + name = "malformed hands" + icon_state = "lingchameleon" + item_state = "lingchameleon" + desc = "Our hands have a second layer of flesh on top. We can reform our hands to resemble a large variety of fabrics and materials that biologicals \ + tend to wear on their hands. Remember that these won't protect your hands from harm." + origin_tech = list() + canremove = FALSE + +/obj/item/clothing/gloves/chameleon/changeling/emp_act() + ..() + return + +/obj/item/clothing/gloves/chameleon/changeling/verb/shred() + set name = "Shred Gloves" + set category = "Chameleon Items" + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + playsound(src, 'sound/effects/splat.ogg', 30, 1) + visible_message("[H] tears off [src]!", + "We remove [src].") + qdel(src) + +/obj/item/clothing/mask/chameleon/changeling + name = "chitin visor" + flags_inv = HIDEFACE | HIDEEYES + icon_state = "lingchameleon" + item_state = "lingchameleon" + desc = "A transparent visor of brittle chitin covers our face. We can reform it to resemble various masks that biologicals use. It can also utilize internal \ + tanks.." + origin_tech = list() + canremove = FALSE + +/obj/item/clothing/mask/chameleon/changeling/emp_act() + ..() + return + +/obj/item/clothing/mask/chameleon/changeling/verb/shred() + set name = "Shred Mask" + set category = "Chameleon Items" + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + playsound(src, 'sound/effects/splat.ogg', 30, 1) + visible_message("[H] tears off [src]!", + "We remove [src].") + qdel(src) +/obj/item/clothing/accessory/chameleon/changeling + name = "malformed lump" + icon_state = "ling_accessory" + item_state = "ling_accessory" + desc = "A small, rapidly shifting and twitching clump of flesh, with veins pulsing throughout." + origin_tech = list() + throwforce = 0 //Just to be on the safe side + throw_range = 0 + throw_speed = 0 + +/obj/item/clothing/accessory/chameleon/changeling/attack_hand() + src.visible_message(SPAN_WARNING("\The [src] melts upon being touched!")) + new /obj/decal/cleanable/ling_vomit(loc) + qdel(src) + +/obj/item/clothing/accessory/chameleon/changeling/emp_act() + ..() + return + +/obj/item/clothing/accessory/chameleon/changeling/verb/shred() + set name = "Shred Accessory" + set category = "Chameleon Items" + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + playsound(src, 'sound/effects/splat.ogg', 30, 1) + visible_message("[H] tears off [src]!", + "We remove [src].") + qdel(src) + +/obj/item/clothing/glasses/chameleon/changeling + name = "chitin goggles" + icon_state = "lingchameleon" + item_state = "lingchameleon" + desc = "A transparent piece of eyewear made out of brittle chitin. We can reform it to resemble various glasses and goggles." + origin_tech = list() + canremove = FALSE + +/obj/item/clothing/glasses/chameleon/changeling/emp_act() + ..() + return + +/obj/item/clothing/glasses/chameleon/changeling/verb/shred() + set name = "Shred Glasses" + set category = "Chameleon Items" + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + playsound(src, 'sound/effects/splat.ogg', 30, 1) + visible_message("[H] tears off [src]!", + "We remove [src].") + qdel(src) + +/obj/item/card/id/syndicate/changeling + name = "identification card" + desc = "An identification card issued to SolGov crewmembers aboard the SEV Torch." + icon_state = "changeling" + assignment = "Harvester" + origin_tech = list() + electronic_warfare = 1 //The lack of RFID stuff makes it hard for AIs to track, I guess. *handwaves* + registered_user = null + access = null + canremove = FALSE + +/obj/item/card/id/syndicate/changeling/Initialize() + . = ..() + if(ismob(loc)) + registered_user = loc + access = null + +/obj/item/card/id/syndicate/changeling/verb/shred() + set name = "Shred ID Card" + set category = "Chameleon Items" + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + playsound(src, 'sound/effects/splat.ogg', 30, 1) + visible_message("[H] tears off [src]!", + "We remove [src].") + qdel(src) + +/obj/item/card/id/syndicate/changeling/Click() //Since we can't hold it in our hands, and attack_hand() doesn't work if it in inventory... + if(!registered_user) + registered_user = usr + usr.set_id_info(src) + ui_interact(registered_user) + ..() +/obj/item/card/id/syndicate/changeling/dropped(mob/user) + if(!ismob(loc)) + return diff --git a/code/game/gamemodes/changeling/powers/fake_death.dm b/code/game/gamemodes/changeling/powers/fake_death.dm new file mode 100644 index 0000000000000..4e433200e213d --- /dev/null +++ b/code/game/gamemodes/changeling/powers/fake_death.dm @@ -0,0 +1,42 @@ +/datum/power/changeling/fakedeath + name = "Regenerative Stasis" + desc = "We become weakened to a death-like state, where we will rise again from death." + helptext = "Can be used before or after death. Duration varies greatly." + ability_icon_state = "ling_regenerative_stasis" + genomecost = 0 + allowduringlesserform = 1 + verbpath = /mob/proc/changeling_fakedeath +/mob/proc/finish_revive() + //The ling will now be able to choose when to revive + verbs.Add(/mob/proc/changeling_revive) + new /obj/changeling_revive_holder(src) + to_chat(src, "We are ready to rise. Use the Revive verb when you are ready.") +//Fake our own death and fully heal. You will appear to be dead but regenerate fully after a short delay. +/mob/proc/changeling_fakedeath() + set category = "Changeling" + set name = "Regenerative Stasis (20)" + + var/datum/changeling/changeling = changeling_power(CHANGELING_STASIS_COST,1,100,DEAD) + if(!changeling) + return + + var/mob/living/carbon/C = src + + if(changeling.max_geneticpoints < 0) //Absorbed by another ling + to_chat(src, "We have no genomes, not even our own, and cannot regenerate.") + return 0 + + if(!C.stat && alert("Are we sure we wish to regenerate? We will appear to be dead while doing so.","Revival","Yes","No") == "No") + return + to_chat(C, "We will attempt to regenerate our form.") + + C.UpdateLyingBuckledAndVerbStatus() + C.remove_changeling_powers() + C.status_flags |= FAKEDEATH + changeling.chem_charges -= CHANGELING_STASIS_COST + + if(C.stat != DEAD) + C.adjustOxyLoss(C.maxHealth * 2) + addtimer(new Callback(src,/mob/.proc/finish_revive),rand(2 MINUTES, 4 MINUTES)) + + return TRUE diff --git a/code/game/gamemodes/changeling/powers/false_identity.dm b/code/game/gamemodes/changeling/powers/false_identity.dm new file mode 100644 index 0000000000000..7c9c919626b90 --- /dev/null +++ b/code/game/gamemodes/changeling/powers/false_identity.dm @@ -0,0 +1,34 @@ +/datum/power/changeling/false_identity + name = "False Identity" + desc = "We twist our body like clay into an identity of our own making." + ability_icon_state = "ling_transform" + genomecost = 1 + power_category = CHANGELING_POWER_INHERENT + verbpath = /mob/proc/changeling_false_identity + +/mob/proc/changeling_false_identity() + set category = "Changeling" + set name = "False Identity (10)" + set desc="Twists our body into a new false identity" + var/datum/changeling/changeling = changeling_power(10,0,100,CONSCIOUS) + var/mob/living/carbon/human/C = src + C.mind.changeling.last_human_form = C.real_name + C.change_appearance(APPEARANCE_BASIC, state = GLOB.z_state) + C.visible_message(SPAN_DANGER("\The [src] lets out a vile crunching noise, as their body begins to reshape itself!")) + playsound(C.loc, 'sound/effects/corpsecube.ogg', 60) + var/response = input(C, "What would you like to call your new self?", "Name change") as null | text + response = sanitize(response, MAX_NAME_LEN) + if (!response) + return + C.real_name = response + C.SetName(response) + C.dna.real_name = response + C.sync_organ_dna() + var/obj/item/organ/external/head/H = C.organs_by_name[BP_HEAD] + if (istype(H) && H.forehead_graffiti) + H.forehead_graffiti = null + if (C.mind) + C.mind.name = C.name + if(changeling) + changeling.chem_charges -= 10 + //to_world("[C.mind.changeling.last_human_form]") diff --git a/code/game/gamemodes/changeling/powers/fleshmend.dm b/code/game/gamemodes/changeling/powers/fleshmend.dm new file mode 100644 index 0000000000000..62df05b940edc --- /dev/null +++ b/code/game/gamemodes/changeling/powers/fleshmend.dm @@ -0,0 +1,48 @@ +/datum/power/changeling/fleshmend + name = "Fleshmend" + desc = "Begins a slow regeneration of our form. Does not effect stuns or chemicals." + helptext = "Can be used while unconscious." + enhancedtext = "Healing is twice as effective." + ability_icon_state = "ling_fleshmend" + genomecost = 1 + verbpath = /mob/proc/changeling_fleshmend +/mob/proc/end_fleshmend() + to_chat(src, "Our regeneration has slowed to normal levels.") + src.verbs += /mob/proc/changeling_fleshmend + var/datum/changeling/changeling = src.mind.changeling + changeling.already_regenerating = FALSE +//Starts healing you every second for 50 seconds. Can be used whilst unconscious. +/mob/proc/changeling_fleshmend() + set category = "Changeling" + set name = "Fleshmend (10)" + set desc = "Begins a slow rengeration of our form. Does not effect stuns or chemicals." + set waitfor = FALSE + var/datum/changeling/changeling = changeling_power(10,0,100,UNCONSCIOUS) + if(!changeling) + return + var/mob/living/carbon/human/C = src + if(C.on_fire) + to_chat(src,SPAN_DANGER("We cannot regenerate while engulfed in flames!")) + return + if(changeling.already_regenerating) + to_chat(src,SPAN_DANGER("We are already regenerating our flesh.")) + return + src.mind.changeling.chem_charges -= 10 + var/heal_amount = 2 + if(src.mind.changeling.recursive_enhancement) + src.mind.changeling.recursive_enhancement = FALSE + heal_amount = heal_amount * 2 + to_chat(src, "We will heal much faster.") + + to_chat(src, "We begin to heal ourselves.") + changeling.already_regenerating = TRUE + for(var/i = 0, i<50,i++) + if(C && !C.on_fire) + C.adjustBruteLoss(-heal_amount) + C.adjustOxyLoss(-heal_amount) + C.adjustFireLoss(-heal_amount) + C.regenerate_icons() + sleep(1 SECOND) + + src.verbs -= /mob/proc/changeling_fleshmend + addtimer(new Callback(src,/mob/.proc/end_fleshmend), 50 SECONDS) diff --git a/code/game/gamemodes/changeling/powers/hivemind.dm b/code/game/gamemodes/changeling/powers/hivemind.dm new file mode 100644 index 0000000000000..1eff9067366fe --- /dev/null +++ b/code/game/gamemodes/changeling/powers/hivemind.dm @@ -0,0 +1,77 @@ +// Hivemind + +/datum/power/changeling/hive_upload + name = "Hive Channel" + desc = "We can channel a DNA into the airwaves, allowing our fellow changelings to absorb it and transform into it as if they acquired the DNA themselves." + helptext = "Allows other changelings to absorb the DNA you channel from the airwaves. Will not help them towards their absorb objectives." + genomecost = 0 + make_hud_button = 0 + verbpath = /mob/proc/changeling_hiveupload + +/datum/power/changeling/hive_download + name = "Hive Absorb" + desc = "We can absorb a single DNA from the airwaves, allowing us to use more disguises with help from our fellow changelings." + helptext = "Allows you to absorb a single DNA and use it. Does not count towards your absorb objective." + genomecost = 0 + make_hud_button = 0 + verbpath = /mob/proc/changeling_hivedownload + +// HIVE MIND UPLOAD/DOWNLOAD DNA +GLOBAL_LIST_EMPTY(hivemind_bank) + +/mob/proc/changeling_hiveupload() + set category = "Changeling" + set name = "Hive Channel (10)" + set desc = "Allows you to channel DNA in the airwaves to allow other changelings to absorb it." + + var/datum/changeling/changeling = changeling_power(10,1) + if(!changeling) return + + var/list/names = list() + for(var/datum/absorbed_dna/DNA in changeling.absorbed_dna) + if(!(DNA in GLOB.hivemind_bank)) + names += DNA.name + + if(!length(names)) + to_chat(src, "The airwaves already have all of our DNA.") + return + + var/S = input("Select a DNA to channel: ", "Channel DNA", null) as null|anything in names + if(!S) return + + var/datum/absorbed_dna/chosen_dna = changeling.GetDNA(S) + if(!chosen_dna) + return + + changeling.chem_charges -= 10 + GLOB.hivemind_bank += chosen_dna + to_chat(src, "We channel the DNA of [S] to the air.") + return TRUE + +/mob/proc/changeling_hivedownload() + set category = "Changeling" + set name = "Hive Absorb (20)" + set desc = "Allows you to absorb DNA that is being channeled in the airwaves." + + var/datum/changeling/changeling = changeling_power(20,1) + if(!changeling) return + + var/list/names = list() + for(var/datum/absorbed_dna/DNA in GLOB.hivemind_bank) + if(!(DNA in changeling.absorbed_dna)) + names[DNA.name] = DNA + + if(!length(names)) + to_chat(src, "There's no new DNA to absorb from the air.") + return + + var/S = input("Select a DNA absorb from the air: ", "Absorb DNA", null) as null|anything in names + if(!S) return + var/datum/absorbed_dna/chosen_dna = names[S] + if(!chosen_dna) + return + + changeling.chem_charges -= 20 + absorbDNA(chosen_dna) + to_chat(src, "We absorb the DNA of [S] from the air.") + return TRUE diff --git a/code/game/gamemodes/changeling/powers/lsd_sting.dm b/code/game/gamemodes/changeling/powers/lsd_sting.dm new file mode 100644 index 0000000000000..fdd0235358d44 --- /dev/null +++ b/code/game/gamemodes/changeling/powers/lsd_sting.dm @@ -0,0 +1,26 @@ +//This only exists to be abused, so it's highly recommended to ensure this file is unchecked. +/datum/power/changeling/LSDSting + name = "Hallucination Sting" + desc = "We evolve the ability to sting a target with a powerful hallunicationary chemical." + helptext = "The target does not notice they have been stung. The effect occurs after 30 to 60 seconds." + genomecost = 1 + verbpath = /mob/proc/changeling_lsdsting + sting_effect = /mob/proc/ling_lsd + is_sting = TRUE + sting_duration = 400 +/mob/proc/time_lsd(T) + if(T) + ling_lsd(T,400) +/mob/proc/ling_lsd(mob/living/carbon/M, duration) + M.hallucination(duration,80) +/mob/proc/changeling_lsdsting() + set category = "Changeling" + set name = "Hallucination Sting (15)" + set desc = "Causes terror in the target." + + var/mob/living/carbon/T = changeling_sting(15,/mob/proc/changeling_lsdsting) + if(!T) return FALSE + admin_attack_log(src,T,"Hallucination sting (changeling)") + addtimer(new Callback(T,/mob/.proc/time_lsd), 400) + + return TRUE diff --git a/code/game/gamemodes/changeling/powers/mimic_voice.dm b/code/game/gamemodes/changeling/powers/mimic_voice.dm new file mode 100644 index 0000000000000..48c70fe87b1ff --- /dev/null +++ b/code/game/gamemodes/changeling/powers/mimic_voice.dm @@ -0,0 +1,39 @@ +/datum/power/changeling/mimicvoice + name = "Mimic Voice" + desc = "We shape our vocal glands to sound like a desired voice." + helptext = "Will turn your voice into the name that you enter. We must constantly expend chemicals to maintain our form like this" + ability_icon_state = "ling_mimic_voice" + genomecost = 1 + verbpath = /mob/proc/changeling_mimicvoice + +// Fake Voice + +/mob/proc/changeling_mimicvoice() + set category = "Changeling" + set name = "Mimic Voice" + set desc = "Shape our vocal glands to form a voice of someone we choose. We cannot regenerate chemicals when mimicing." + set waitfor = FALSE + + var/datum/changeling/changeling = changeling_power() + if(!changeling) return + + if(changeling.mimicing) + changeling.mimicing = "" + to_chat(src, "We return our vocal glands to their original location.") + return + + var/mimic_voice = sanitize(input(usr, "Enter a name to mimic.", "Mimic Voice", null), MAX_NAME_LEN) + if(!mimic_voice) + return + + changeling.mimicing = mimic_voice + + to_chat(src, "We shape our glands to take the voice of [mimic_voice], this will stop us from regenerating chemicals while active.") + to_chat(src, "Use this power again to return to our original voice and reproduce chemicals again.") + + + while(src && src.mind && src.mind.changeling && src.mind.changeling.mimicing) + src.mind.changeling.chem_charges = max(src.mind.changeling.chem_charges - 1, 0) + sleep(40) + if(src && src.mind && src.mind.changeling) + src.mind.changeling.mimicing = "" diff --git a/code/game/gamemodes/changeling/powers/panacea.dm b/code/game/gamemodes/changeling/powers/panacea.dm new file mode 100644 index 0000000000000..eaf3959e45df9 --- /dev/null +++ b/code/game/gamemodes/changeling/powers/panacea.dm @@ -0,0 +1,51 @@ +/datum/power/changeling/panacea + name = "Anatomic Panacea" + desc = "Expels impurifications from our form; curing diseases, removing toxins, chemicals, radiation, and resetting our genetic code completely." + helptext = "Can be used while unconscious. This will also purge any reagents inside ourselves, both harmful and beneficial." + enhancedtext = "We heal more toxins." + ability_icon_state = "ling_anatomic_panacea" + genomecost = 1 + verbpath = /mob/proc/changeling_panacea + +//Heals the things that the other regenerative abilities don't. +/mob/proc/changeling_panacea() + set category = "Changeling" + set name = "Anatomic Panacea (20)" + set desc = "Clense ourselves of impurities." + + var/datum/changeling/changeling = changeling_power(20,0,100,UNCONSCIOUS) + if(!changeling) + return FALSE + src.mind.changeling.chem_charges -= 20 + + to_chat(src, "We cleanse impurities from our form.") + + var/mob/living/carbon/human/C = src + + C.radiation = 0 + C.sdisabilities = 0 + C.disabilities = 0 + C.reagents.clear_reagents() + C.empty_stomach() + + + var/heal_amount = 5 + if(src.mind.changeling.recursive_enhancement) + src.mind.changeling.recursive_enhancement = FALSE + heal_amount = heal_amount * 2 + to_chat(src, "We will heal much faster.") + + + for(var/obj/item/organ/external/E in C.organs) + var/obj/item/organ/external/G = E + if(G.germ_level) + var/germ_heal = heal_amount * 100 + G.germ_level = min(0, G.germ_level - germ_heal) + + for(var/obj/item/organ/internal/I in C.internal_organs) + var/obj/item/organ/internal/G = I + if(G.germ_level) + var/germ_heal = heal_amount * 100 + G.germ_level = min(0, G.germ_level - germ_heal) + + return TRUE diff --git a/code/game/gamemodes/changeling/powers/rapid_regen.dm b/code/game/gamemodes/changeling/powers/rapid_regen.dm new file mode 100644 index 0000000000000..e9d7fe3b78136 --- /dev/null +++ b/code/game/gamemodes/changeling/powers/rapid_regen.dm @@ -0,0 +1,110 @@ +//had to make this because restore_all_organs just does a full heal, which we don't want. +/obj/item/organ/external/proc/ling_organ_rejuvenate(ignore_prosthetic_prefs) + + // handle internal organs. Heavily debating making this just fix some of the damage since a full heal might be much? idk + for(var/obj/item/organ/current_organ in internal_organs) + current_organ.rejuvenate(ignore_prosthetic_prefs) + + // remove embedded objects and drop them on the floor + for(var/obj/implanted_object in implants) + if(!istype(implanted_object,/obj/item/implant,) || istype(implanted_object,/obj/item/organ/internal/augment)) // We don't want to remove REAL implants. Just shrapnel etc. + implanted_object.forceMove(get_turf(src)) + implants -= implanted_object + + if(owner && !ignore_prosthetic_prefs) + if(owner.client && owner.client.prefs && owner.client.prefs.real_name == owner.real_name) + var/status = owner.client.prefs.organ_data[organ_tag] + if(status == "amputated") + remove_rejuv() + owner.updatehealth() + if(status & ORGAN_ARTERY_CUT) + status &= ~ORGAN_ARTERY_CUT + if(status & ORGAN_TENDON_CUT) + status &= ~ORGAN_TENDON_CUT + if(status & ORGAN_BROKEN) + status &= ~ORGAN_BROKEN + stage = 0 + if(!QDELETED(src) && species) + species.post_organ_rejuvenate(src, owner) +/mob/living/carbon/human/proc/ling_heal_single_organ(ignore_prosthetic_prefs) + for(var/bodypart in BP_BY_DEPTH) + var/obj/item/organ/external/current_organ = organs_by_name[bodypart] + //checks if the limb has been twisted/hurt/affected and if it has, fixes it + if(istype(current_organ) && (current_organ.status != initial(current_organ.status) && !(BP_IS_ROBOTIC(current_organ)))) + current_organ.ling_organ_rejuvenate(ignore_prosthetic_prefs) + return + verbs -= /mob/living/carbon/human/proc/undislocate +/datum/power/changeling/rapid_regen + name = "Rapid Regeneration" + desc = "We quickly heal ourselves, removing most advanced injuries, at a high chemical cost. We cannot regenerate lost limbs however." + helptext = "We will begin to regenerate our flesh, healing ourselves of brute, burn, oxygen and brain damage, as well as mending internal organs, broken bones, \ + and organ damage. The process is fast, but we must remain still and anyone who sees us will know us for what we are." + enhancedtext = "Healing increased to heal up to maximum health." + ability_icon_state = "ling_rapid_regeneration" + genomecost = 2 + verbpath = /mob/proc/changeling_rapid_regen + +//Gives a big heal, removing various injuries that might shut down normal people, like IB or fractures. +/mob/proc/changeling_rapid_regen() + set category = "Changeling" + set name = "Rapid Regeneration (30 per tick)" + set desc = "Heal ourselves of most injuries rapidly." + set waitfor = FALSE + var/finish_regen = TRUE + var/datum/changeling/changeling = changeling_power(30,0,100,UNCONSCIOUS) + if(!changeling) + return + + + if(ishuman(src)) + var/mob/living/carbon/human/C = src + if(C.on_fire) + to_chat(src,SPAN_DANGER("We cannot regenerate while engulfed in flames!")) + return + if(changeling.already_regenerating) + to_chat(src,SPAN_DANGER("We are already regenerating our flesh.")) + return + var/healing_amount = 40 + if(src.mind.changeling.recursive_enhancement) + src.mind.changeling.recursive_enhancement = FALSE + healing_amount = 80 + to_chat(src, SPAN_NOTICE("Our regeneration will be stronger.")) + if(src.mind.changeling.chem_charges < 30) + to_chat(src,SPAN_WARNING("We require at least 10 chemicals to regenerate!")) + return + to_chat(src,SPAN_NOTICE("We begin to regenerate our form. We must remain still to complete the process.")) + changeling.already_regenerating = TRUE + while((src.mind.changeling.chem_charges >= 30)) + + finish_regen = do_mob(C,C,5 SECONDS,ignore_movement = FALSE, incapacitation_affected = FALSE) + + if(finish_regen) + C.adjustBruteLoss(-healing_amount) + C.adjustFireLoss(-healing_amount) + C.adjustOxyLoss(-healing_amount) + C.adjustToxLoss(-healing_amount) + C.adjustCloneLoss(-healing_amount) + C.adjustBrainLoss(-healing_amount) + C.regenerate_blood(healing_amount*2) + C.ling_heal_single_organ() + C.blinded = 0 + C.eye_blind = 0 + C.eye_blurry = 0 + C.ear_deaf = 0 + C.ear_damage = 0 + + // make the icons look correct + C.regenerate_icons() + + // now make it obvious that we're not human (or whatever xeno race they are impersonating) + playsound(src, 'sound/effects/blobattack.ogg', 30, 1) + var/T = get_turf(src) + new /obj/gibspawner/human(T) + + else + to_chat(src,SPAN_WARNING("Our regeneration is brought to a halt by us moving!")) + changeling.already_regenerating = FALSE + return + + src.mind.changeling.chem_charges -= 30 + changeling.already_regenerating = FALSE diff --git a/code/game/gamemodes/changeling/powers/rapid_synthesis.dm b/code/game/gamemodes/changeling/powers/rapid_synthesis.dm new file mode 100644 index 0000000000000..a707d07148cb6 --- /dev/null +++ b/code/game/gamemodes/changeling/powers/rapid_synthesis.dm @@ -0,0 +1,12 @@ +/datum/power/changeling/RapidSynthesis + name = "Rapid Chemical Synthesis" + desc = "We alter our chemical glands to optimize our production of internal chemicals." + helptext = "Allows us to store an extra 30 units of chemicals, and doubles production rate." + genomecost = 1 + isVerb = 0 + verbpath = /mob/proc/changeling_rapid_synthesis + +//Increases macimum chemical storage +/mob/proc/changeling_rapid_synthesis() + src.mind.changeling.chem_recharge_rate *= 2 + return TRUE diff --git a/code/game/gamemodes/changeling/powers/reattach_limb.dm b/code/game/gamemodes/changeling/powers/reattach_limb.dm new file mode 100644 index 0000000000000..bb9116d1591d8 --- /dev/null +++ b/code/game/gamemodes/changeling/powers/reattach_limb.dm @@ -0,0 +1,68 @@ +//Change this to just regrowing it honestly +/datum/power/changeling/reattach_limb + name = "Regrow Limb" + desc = "We reform a lost or damaged appendage." + helptext = "To select the body part to regrow, target the area on the health doll and click the ability." + ability_icon_state = "ling_sting_extract" + genomecost = 1 + power_category = CHANGELING_POWER_INHERENT + allowduringlesserform = 1 + verbpath = /mob/proc/changeling_reattach_limb + +/mob/proc/changeling_reattach_limb() + set category = "Changeling" + set name = "Regrow Limb (25)" + set desc="Regrow a limb that we have lost." + + var/datum/changeling/changeling = changeling_power(25,0,100,CONSCIOUS) + if(src.mind && src.mind.changeling) + changeling = src.mind.changeling + if(!changeling || (src.mind.changeling.chem_charges < 25)) + //to_chat(src,SPAN_WARNING("We require at least 25 chemicals to regenerate a limb!")) + return FALSE + + var/mob/living/carbon/human/C = src + //zone_sel.selecting gets string + var/obj/item/organ/external/target_limb = C.zone_sel.selecting + var/list/organ_data = C.species.has_limbs[target_limb] + var/obj/item/organ/external/limb_path = organ_data["path"] + var/obj/item/organ/external/current_limb = C.get_organ(C.zone_sel.selecting) + if((current_limb.organ_tag == BP_HEAD) || (current_limb.organ_tag == BP_CHEST)) + to_chat(C,SPAN_WARNING("That part of our body is too complex for us to simply regrow. We must enter hibernating stasis to regrow it.")) + return FALSE + if (!current_limb || current_limb.is_stump()) + var/obj/item/organ/external/regrown_limb = new limb_path(C) + var/obj/item/organ/external/parent_limb = (C.get_organ(regrown_limb.parent_organ)) + if(!parent_limb || parent_limb.is_stump()) + to_chat(C,SPAN_WARNING("We cannot regrow this limb without regrowing the limb it attaches to first!")) + qdel(regrown_limb) + return + if(current_limb) + qdel(current_limb) + regrown_limb.replaced(C) + regrown_limb.forceMove(C) + regrown_limb = null + C.update_body() + C.updatehealth() + C.UpdateDamageIcon() + C.visible_message(SPAN_DANGER("\The [C] regenerates the tissue of their limb with a sickening squelch!")) + playsound(src, 'sound/effects/blobattack.ogg', 30, 1) + changeling.chem_charges -= 25 + else + current_limb.rejuvenate(ignore_prosthetic_prefs = FALSE) + C.update_body() + C.updatehealth() + C.UpdateDamageIcon() + C.visible_message(SPAN_DANGER("\The [C] regenerates the tissue of their limb with a sickening squelch!")) + playsound(src, 'sound/effects/blobattack.ogg', 30, 1) + changeling.chem_charges -= 25 + + + + if(!C) + return + + + + + return TRUE diff --git a/code/game/gamemodes/changeling/powers/recursive_enhancement.dm b/code/game/gamemodes/changeling/powers/recursive_enhancement.dm new file mode 100644 index 0000000000000..de7f241a8fd4a --- /dev/null +++ b/code/game/gamemodes/changeling/powers/recursive_enhancement.dm @@ -0,0 +1,24 @@ +/datum/power/changeling/recursive_enhancement + name = "Recursive Enhancement" + desc = "We cause our abilities to have increased or additional effects." + helptext = "To check the effects for each ability, check the blue text underneath the ability in the evolution menu." + ability_icon_state = "ling_recursive_enhancement" + genomecost = 3 + verbpath = /mob/proc/changeling_recursive_enhancement + +//Increases macimum chemical storage +/mob/proc/changeling_recursive_enhancement() + set category = "Changeling" + set name = "Recursive Enhancement" + set desc = "Empowers our abilities." + var/datum/changeling/changeling = changeling_power(0,0,100,UNCONSCIOUS) + if(!changeling) + return FALSE + if(src.mind.changeling.recursive_enhancement) + to_chat(src, "We will no longer empower our abilities.") + src.mind.changeling.recursive_enhancement = 0 + return FALSE + to_chat(src, "We empower ourselves. Our next ability will now be extra potent.") + src.mind.changeling.recursive_enhancement = 1 + changeling.chem_charges -= 10 + return TRUE diff --git a/code/game/gamemodes/changeling/powers/reinforced_tendons.dm b/code/game/gamemodes/changeling/powers/reinforced_tendons.dm new file mode 100644 index 0000000000000..b9672c1e2de1c --- /dev/null +++ b/code/game/gamemodes/changeling/powers/reinforced_tendons.dm @@ -0,0 +1,18 @@ +/datum/power/changeling/reinforced_tendons + name = "Reinforced Tendons" + desc = "We reinforce our tendons to lunch" + helptext = "We will be also be able to fall great distances without damage. If we leap at a target with both arms shaped into weapons, we will perform a special pounce attack." + genomecost = 2 + isVerb = 0 + verbpath = /mob/proc/reinforced_tendons +/mob/proc/reinforced_tendons() + + var/mob/living/carbon/human/C = src + //C.available_maneuvers.Remove(/singleton/maneuver/leap) + C.available_maneuvers.Add(/singleton/maneuver/leap/changeling) + src.mind.changeling.tendons_reinforced = TRUE + C.buff_skill(list(SKILL_HAULING = 1), 0, buff_type = /datum/skill_buff) + //C.prepared_maneuver = /singleton/maneuver/leap/grab + + playsound(src, 'sound/effects/corpsecube.ogg',35,1) + to_chat(src, SPAN_NOTICE("Our leg muscles twist and reknit themselves, forming stronger muscles that can both leap further and fall from great heights!")) diff --git a/code/game/gamemodes/changeling/powers/revive.dm b/code/game/gamemodes/changeling/powers/revive.dm new file mode 100644 index 0000000000000..d2126021d14bb --- /dev/null +++ b/code/game/gamemodes/changeling/powers/revive.dm @@ -0,0 +1,47 @@ +//Revive from revival stasis +/mob/proc/changeling_revive() + set category = "Changeling" + set name = "Revive" + set desc = "We are ready to revive ourselves on command." + + var/datum/changeling/changeling = changeling_power(0,0,100,DEAD) + if(!changeling) + return 0 + + if(changeling.max_geneticpoints < 0) //Absorbed by another ling + to_chat(src, "You have no genomes, not even your own, and cannot revive.") + return 0 + var/mob/living/carbon/C = src + // restore us to health + C.revive() + // remove our fake death flag + C.status_flags &= ~(FAKEDEATH) + // let us move again + C.UpdateLyingBuckledAndVerbStatus() + // re-add out changeling powers + C.make_changeling() + // sending display messages + to_chat(C, SPAN_NOTICE("We have regenerated.")) + C.verbs -= /mob/proc/changeling_revive + + return TRUE + +//Revive from revival stasis, but one level removed, as the tab refuses to update. Placed in its own tab to avoid hyper-exploding the original tab through the same name being used. + +/obj/changeling_revive_holder + name = "strange object" + desc = "Please report this object's existence to the dev team! You shouldn't see it." + mouse_opacity = FALSE + alpha = 1 + +/obj/changeling_revive_holder/verb/ling_revive() + set src = usr.contents + set category = "Regenerate" + set name = "Revive" + set desc = "We are ready to revive ourselves on command." + + if(iscarbon(usr)) + var/mob/living/carbon/C = usr + C.changeling_revive() + + qdel(src) diff --git a/code/game/gamemodes/changeling/powers/self_respiration.dm b/code/game/gamemodes/changeling/powers/self_respiration.dm new file mode 100644 index 0000000000000..3a0a2312b5080 --- /dev/null +++ b/code/game/gamemodes/changeling/powers/self_respiration.dm @@ -0,0 +1,34 @@ +/datum/power/changeling/self_respiration + name = "Self Respiration" + desc = "We evolve our body to no longer require drawing oxygen from the atmosphere." + helptext = "We will no longer require internals, and we cannot inhale any gas, including harmful ones." + ability_icon_state = "ling_toggle_breath" + genomecost = 0 + power_category = CHANGELING_POWER_INHERENT + verbpath = /mob/proc/changeling_self_respiration + +//No breathing required +/mob/proc/changeling_self_respiration() + set category = "Changeling" + set name = "Activate Internal Air Sacs" + set desc = "We choose whether or not to breathe." + var/mnoBreath = 100 + + var/datum/changeling/changeling = changeling_power(0,0,100,UNCONSCIOUS) + if(!changeling) + return FALSE + + if(istype(src,/mob/living/carbon)) + var/mob/living/carbon/C = src + if (mnoBreath in C.mutations) + //C.mutations |= mNobreath + C.mutations.Remove(mnoBreath) + to_chat(src,SPAN_NOTICE("We resume breathing, to avoid suspicion.")) + return FALSE + else + C.mutations.Add(mnoBreath) + to_chat(src, "We stop breathing, as we no longer need to.") + return TRUE + //else + //C.does_not_breathe = 0 + //to_chat(src, "We resume breathing, as we now need to again.") diff --git a/code/game/gamemodes/changeling/powers/shriek.dm b/code/game/gamemodes/changeling/powers/shriek.dm new file mode 100644 index 0000000000000..dfa48a2183e81 --- /dev/null +++ b/code/game/gamemodes/changeling/powers/shriek.dm @@ -0,0 +1,152 @@ +/datum/power/changeling/resonant_shriek + name = "Resonant Shriek" + desc = "Our lungs and vocal cords shift, allowing us to briefly emit a noise that deafens and confuses the weak-minded." + helptext = "Lights are blown, organics are disoriented, and synthetics act as if they were flashed." + enhancedtext = "Range is doubled." + ability_icon_state = "ling_resonant_shriek" + genomecost = 2 + verbpath = /mob/proc/changeling_resonant_shriek + +/datum/power/changeling/dissonant_shriek + name = "Dissonant Shriek" + desc = "We shift our vocal cords to release a high-frequency sound that overloads nearby radios" + helptext = "Radios within a radius of us are shut off." + enhancedtext = "Range is doubled." + ability_icon_state = "ling_dissonant_shriek" + genomecost = 2 + verbpath = /mob/proc/changeling_dissonant_shriek + +//A flashy ability, good for crowd control and sewing chaos. +/mob/proc/changeling_resonant_shriek() + set category = "Changeling" + set name = "Resonant Shriek (15)" + set desc = "Emits a high-frequency sound that deafens organics, blows out nearby lights, and overloads synthetics' sensors." + + var/mob/living/carbon/human/H = src + + var/datum/changeling/changeling = changeling_power(15,0,100,CONSCIOUS) + if(!changeling) return 0 + + if(is_muzzled()) + to_chat(src, "Mmmf mrrfff!") + return 0 + + if(ishuman(src)) + if(H.silent) + to_chat(src, "You can't speak!") + return 0 + + if(world.time < (changeling.last_shriek + 10 SECONDS) ) + to_chat(src, "We are still recovering from our last shriek...") + return 0 + + if(!isturf(loc)) + to_chat(src, "Shrieking here would be a bad idea.") + return 0 + + H.remove_cloaking_source(src) //No more invisible shrieking + + changeling.chem_charges -= 15 + var/range = 4 + if(src.mind.changeling.recursive_enhancement) + src.mind.changeling.recursive_enhancement = FALSE + range = range * 2 + to_chat(src, "We are extra loud.") + + visible_message("[src] appears to shout.") + playsound(src, 'sound/effects/ling_shriek.ogg',100,1) + var/list/affected = list() + for(var/mob/living/M in range(range, src)) + if(iscarbon(M)) + if(!M.mind || !M.mind.changeling) + if(M.get_sound_volume_multiplier() < 0.2) + continue + to_chat(M, "You hear an extremely loud screeching sound! It \ + [pick("confuses","confounds","perturbs","befuddles","dazes","unsettles","disorients")] you.") + M.adjustEarDamage(0,30) + affected += M + else + if(M != src) + to_chat(M, "You hear a familiar screech from nearby. It has no effect on you.") + + if(issilicon(M)) + to_chat(M, "Auditory input overloaded. Reinitializing...") + M.Weaken(rand(5,10)) + affected += M + + for(var/obj/machinery/light/L in range(range, src)) + L.on = 1 + L.broken() + for(var/obj/item/device/flashlight/F in range(range, src)) + //add code for flashlights that cannot be turned off here + if (F.on) + F.on = FALSE + F.set_light(0) + F.update_icon() + changeling.last_shriek = world.time + + admin_attack_log(src,affected,"Used resonant shriek") + return TRUE + +//EMP version +/mob/proc/changeling_dissonant_shriek() + set category = "Changeling" + set name = "Dissonant Shriek (15)" + set desc = "We shift our vocal cords to release a high-frequency sound that overloads nearby radios." + + var/mob/living/carbon/human/H = src + + var/datum/changeling/changeling = changeling_power(15,0,100,CONSCIOUS) + if(!changeling) + return FALSE + + if(is_muzzled()) + to_chat(src, "Mmmf mrrfff!") + return FALSE + + if(ishuman(src)) + if(H.silent) + to_chat(src, "You can't speak!") + return FALSE + + if(world.time < (changeling.last_shriek + 10 SECONDS) ) + to_chat(src, "We are still recovering from our last shriek...") + return FALSE + + if(!isturf(loc)) + to_chat(src, "Shrieking here would be a bad idea.") + return FALSE + + H.remove_cloaking_source(src) //No more invisible shrieking + + changeling.chem_charges -= 15 + + var/range_heavy = 1 + var/range_med = 2 + var/range_light = 4 + var/range_long = 6 + if(src.mind.changeling.recursive_enhancement) + range_heavy = range_heavy * 2 + range_med = range_med * 2 + range_light = range_light * 2 + range_long = range_long * 2 + to_chat(src, "We are extra loud.") + src.mind.changeling.recursive_enhancement = FALSE + + visible_message("[src] appears to shout.") + playsound(src, 'sound/effects/ling_shriek2.ogg',100,1) + admin_attack_log(src,null,"Use dissonant shriek") + + + for(var/obj/item/device/radio/R in range(range_long,src)) + R.broadcasting = FALSE + R.listening = FALSE + for (var/ch_name in R.channels) + R.channels[ch_name] = FALSE + if(R.cell) + R.cell.emp_act(2) + + + changeling.last_shriek = world.time + + return TRUE diff --git a/code/game/gamemodes/changeling/powers/silence_sting.dm b/code/game/gamemodes/changeling/powers/silence_sting.dm new file mode 100644 index 0000000000000..c570902e5b322 --- /dev/null +++ b/code/game/gamemodes/changeling/powers/silence_sting.dm @@ -0,0 +1,29 @@ +/datum/power/changeling/silence_sting + name = "Silence Sting" + desc = "We silently sting a human, completely silencing them for a short time." + helptext = "Does not provide a warning to a victim that they have been stung, until they try to speak and cannot." + enhancedtext = "Silence duration is extended." + ability_icon_state = "ling_sting_mute" + genomecost = 2 + allowduringlesserform = 1 + verbpath = /mob/proc/changeling_silence_sting + sting_effect = /mob/proc/silence + is_sting = TRUE + sting_duration = 30 +/mob/proc/silence(mob/living/carbon/M, duration) + M.silent += duration +/mob/proc/changeling_silence_sting() + set category = "Changeling" + set name = "Silence sting (10)" + set desc="Sting target" + + var/mob/living/carbon/T = changeling_sting(10,/mob/proc/changeling_silence_sting) + if(!T) return 0 + admin_attack_log(src,T,"Silence sting (changeling)") + var/duration = 30 + if(src.mind.changeling.recursive_enhancement) + src.mind.changeling.recursive_enhancement = FALSE + duration = duration + 10 + to_chat(src, "They will be unable to cry out in fear for a little longer.") + silence(T,duration) + return TRUE diff --git a/code/game/gamemodes/changeling/powers/synaptizine_overdose.dm b/code/game/gamemodes/changeling/powers/synaptizine_overdose.dm new file mode 100644 index 0000000000000..8392e7f397db7 --- /dev/null +++ b/code/game/gamemodes/changeling/powers/synaptizine_overdose.dm @@ -0,0 +1,43 @@ +/datum/power/changeling/epinephrine_overdose + name = "Adrenaline Surge" + desc = "We evolve additional sacs of adrenaline throughout our body." + helptext = "We can instantly recover from stuns and reduce the effect of future stuns, but we will suffer toxicity in the long term. Can be used while unconscious." + enhancedtext = "Immunity from most disabling effects for 30 seconds." + ability_icon_state = "ling_epinephrine_overdose" + genomecost = 2 + verbpath = /mob/proc/changeling_synaptizine_overdose + +//Recover from stuns. +/mob/proc/changeling_synaptizine_overdose() + set category = "Changeling" + set name = "Adrenaline Surge (20)" + set desc = "Removes all stuns instantly, and reduces future stuns." + + var/datum/changeling/changeling = changeling_power(20,0,100,UNCONSCIOUS) + if(!changeling) + return FALSE + changeling.chem_charges -= 20 + + var/mob/living/carbon/human/C = src + to_chat(C, "Energy rushes through us. [C.lying ? "We arise." : ""]") + C.set_stat(CONSCIOUS) + C.SetParalysis(0) + C.SetStunned(0) + C.SetWeakened(0) + C.setHalLoss(0) + C.lying = 0 + C.blinded = 0 + C.eye_blind = 0 + C.eye_blurry = 0 + C.ear_deaf = 0 + C.ear_damage = 0 + C.clear_confused() + C.sleeping = 0 +// C.reagents.add_reagent("toxin", 10) + C.reagents.add_reagent("synaptizine", 10) + + if(src.mind.changeling.recursive_enhancement) + src.mind.changeling.recursive_enhancement = FALSE + + + return TRUE diff --git a/code/game/gamemodes/changeling/powers/tentacle.dm b/code/game/gamemodes/changeling/powers/tentacle.dm new file mode 100644 index 0000000000000..a713ef335ea80 --- /dev/null +++ b/code/game/gamemodes/changeling/powers/tentacle.dm @@ -0,0 +1,103 @@ +/proc/get_steps_with_obstacles(turf/Turf1,turf/Turf2) + var/list/steps = list() + while(Turf1 != Turf2) + var/turf/next_tile = get_step_towards(Turf1,Turf2) + steps.Add(next_tile) + Turf1 = next_tile + return steps +/proc/find_tentacle_hit(list/tentacle_obstacles) + for(var/turf/check_turf in tentacle_obstacles) + if(check_turf.density) + return check_turf + for(var/obj/item in check_turf) + if(!item.throwpass && item.density) + return item +//commented out until sprite gets added +/* +/datum/power/changeling/tentacle + name = "Tentacle" + desc = "We reform our arm into a tentacle capable of grabbing objects or victims at a distance. Victims grabbed by the tentacle will have their stamina drained, and struggle to run away." + helptext = "Items grabbed by the tentacle will be pulled directly into our hand." + enhancedtext = "The range is extended to five tiles." + ability_icon_state = "ling_sting_boost_range" + genomecost = 2 + allowduringlesserform = 1 + verbpath = /mob/proc/changeling_tentacle +*/ +/obj/item/tentacle + name = "tentacle" + desc = "A quivering tentacle of flesh that shudders with anticipation." + icon = 'icons/obj/guns/confuseray.dmi' + icon_state = "confuseray" + canremove = FALSE + throw_speed = 0 + throwforce = 0 + throw_range = 0 + var/default_range = 5 + var/last_tentacle = null + var/cooldown = 4 SECONDS +/obj/item/tentacle/enhanced + default_range = 7 +/obj/ebeam/ling + pass_flags = PASS_FLAG_TABLE + +/obj/item/tentacle/afterattack(atom/target, mob/user, proximity_flag, click_parameters) + if ((last_tentacle + cooldown > world.time)) + to_chat(user, SPAN_WARNING("We are still recovering from our last tentacle attack.")) + return + if(get_dist(user, target) >= default_range) + to_chat(user,SPAN_WARNING("Our tentacle cannot extend to that distance!")) + return + var/list/tentacle_obstacles = get_steps_with_obstacles(get_turf(user),get_turf(target)) + if(find_tentacle_hit(tentacle_obstacles)) + target = find_tentacle_hit(tentacle_obstacles) + user.Beam(BeamTarget = target, icon_state = "r_beam", time = 3, maxdistance = INFINITY, beam_type = /obj/ebeam/ling) + + user.visible_message(SPAN_DANGER("\the [user] lashes at \the [target] with their tentacle!")) + playsound(src, 'sound/effects/blobattack.ogg', 30, 1) + last_tentacle = world.time + if(isobj(target)) + var/obj/grabbed_object = target + if(grabbed_object.anchored) + to_chat(user,SPAN_WARNING("\the [grabbed_object] is too secured in place for our tentacle to get a good grip!")) + return FALSE + user.throw_mode_on() + grabbed_object.throw_at(user, 5,3) + qdel(src) + if(ismob(target)) + var/mob/victim = target + var/turf/tile = get_step_towards(user,victim) + victim.throw_at(tile, 5,1) + if(victim.get_active_hand()) + victim.unequip_item() + if(istype(victim,/mob/living/carbon/human)) + var/mob/living/carbon/human/V = victim + V.stamina = 0 + return TRUE +/mob/proc/changeling_tentacle() + set category = "Changeling" + set name = "Tentacle (20)" + set desc="We transform one of our arms into a tentacle that can pull distant targets towards us." + + var/datum/changeling/changeling = changeling_power(10,0,100) + if(!changeling) + return TRUE + + var/holding = src.get_active_hand() + if (istype(holding, /obj/item/tentacle)) + to_chat(src,SPAN_WARNING("We reform our arm from a tentacle back into its normal form.")) + playsound(src, 'sound/effects/blobattack.ogg', 30, 1) + qdel(holding) + return 0 + + changeling.chem_charges -= 10 + + if(src.mind.changeling.recursive_enhancement) + if(changeling_generic_weapon(/obj/item/tentacle/enhanced, 10)) + src.mind.changeling.recursive_enhancement = FALSE + return 1 + + else + if(changeling_generic_weapon(/obj/item/tentacle, 10)) + return 1 + return 0 diff --git a/code/game/gamemodes/changeling/powers/transform.dm b/code/game/gamemodes/changeling/powers/transform.dm new file mode 100644 index 0000000000000..08105e63f6f93 --- /dev/null +++ b/code/game/gamemodes/changeling/powers/transform.dm @@ -0,0 +1,69 @@ +/datum/power/changeling/transform + name = "Transform" + desc = "We take on the appearance and voice of one we have absorbed." + ability_icon_state = "ling_transform" + genomecost = 0 + power_category = CHANGELING_POWER_INHERENT + verbpath = /mob/proc/changeling_transform +/mob/proc/transform_cooldown() + src.verbs += /mob/proc/changeling_transform + src.regenerate_icons() + src.update_hud() +//Change our DNA to that of somebody we've absorbed. +/mob/proc/changeling_transform() + set category = "Changeling" + set name = "Transform (5)" + + var/datum/changeling/changeling = changeling_power(5,1,0) + if(!changeling) return + + if(!isturf(loc) || !(/mob/proc/changeling_transform in src.verbs)) + to_chat(src, "Transforming here would be a bad idea.") + return 0 + + var/list/names = list() + for(var/datum/absorbed_dna/DNA in changeling.absorbed_dna) + names += "[DNA.name]" + + var/S = input("Select the target DNA: ", "Target DNA", null) as null|anything in names + if(!S) return + + var/datum/absorbed_dna/chosen_dna = changeling.GetDNA(S) + if(!chosen_dna) + return + + changeling.chem_charges -= 5 + src.visible_message("[src] transforms!") + changeling.geneticdamage = 5 + + if(ishuman(src)) + var/mob/living/carbon/human/H = src + var/newSpecies = chosen_dna.speciesName + H.set_species(newSpecies,1) + H.ability_master.toggle_open(forced_state = 2) + for(var/obj/item/organ/internal/augment/ling_organ in H.mind.changeling.purchased_organs) + var/obj/item/organ/external/parent = H.get_organ(BP_CHEST) + ling_organ.forceMove(src) + ling_organ.replaced(src, parent) + ling_organ = null + src.dna.b_type = "AB+" //This is needed to avoid blood rejection bugs. The fact that the blood type might not match up w/ records could be a *FEATURE* too. + if(ishuman(src)) + var/mob/living/carbon/human/H = src + H.dna = chosen_dna.dna.Clone() + H.b_type = "AB+" //For some reason we have two blood types on the mob. + H.gender = chosen_dna.gender + H.pronouns = chosen_dna.pronouns + H.flavor_texts = chosen_dna.flavour_texts ? chosen_dna.flavour_texts.Copy() : null + if(H.descriptors) + H.descriptors = chosen_dna.descriptors.Copy() + H.sync_organ_dna() + H.icon_render_keys = chosen_dna.icon_render_details + src.real_name = chosen_dna.name + domutcheck(src, null) + + src.UpdateAppearance() + + changeling_update_languages(changeling.absorbed_languages) + + src.verbs -= /mob/proc/changeling_transform + addtimer(new Callback(src,/mob/.proc/transform_cooldown), 10 SECONDS) diff --git a/code/game/gamemodes/changeling/powers/visible_camouflage.dm b/code/game/gamemodes/changeling/powers/visible_camouflage.dm new file mode 100644 index 0000000000000..e6ed45b873599 --- /dev/null +++ b/code/game/gamemodes/changeling/powers/visible_camouflage.dm @@ -0,0 +1,75 @@ +/datum/power/changeling/visible_camouflage + name = "Camouflage" + desc = "We rapidly shape the color of our skin and secrete easily reversible dye on our clothes, to blend in with our surroundings. \ + We are undetectable, so long as we move slowly.(Toggle)" + helptext = "Running, and performing most acts will reveal us. Our chemical regeneration is halted while we are hidden." + enhancedtext = "Can run while hidden." + ability_icon_state = "ling_camoflage" + genomecost = 3 + verbpath = /mob/proc/changeling_visible_camouflage + +//Hide us from anyone who would do us harm. +/mob/proc/changeling_visible_camouflage() + set category = "Changeling" + set name = "Visible Camouflage (10)" + set desc = "Turns yourself almost invisible, as long as you move slowly." + + + if(istype(src,/mob/living/carbon/human)) + var/mob/living/carbon/human/H = src + + if(H.mind.changeling.cloaked) + H.mind.changeling.cloaked = 0 + return TRUE + + //We delay the check, so that people can uncloak without needing 10 chemicals to do so. + var/datum/changeling/changeling = changeling_power(10,0,100,CONSCIOUS) + + if(!changeling) + return FALSE + changeling.chem_charges -= 10 + var/old_regen_rate = H.mind.changeling.chem_recharge_rate + + to_chat(H, "We vanish from sight, and will remain hidden, so long as we move carefully.") + H.mind.changeling.cloaked = 1 + H.mind.changeling.chem_recharge_rate = 0 + animate(src,alpha = 255, alpha = 10, time = 10) + + var/must_walk = TRUE + if(src.mind.changeling.recursive_enhancement) + src.mind.changeling.recursive_enhancement = FALSE + must_walk = FALSE + to_chat(src, "We may move at our normal speed while hidden.") + + if(must_walk) + H.set_move_intent(default_walk_intent) + + var/remain_cloaked = TRUE + while(remain_cloaked) //This loop will keep going until the player uncloaks. + sleep(1 SECOND) // Sleep at the start so that if something invalidates a cloak, it will drop immediately after the check and not in one second. + + if(MOVING_QUICKLY(H) && must_walk) // Moving too fast uncloaks you. + remain_cloaked = 0 + if(!H.mind.changeling.cloaked) + remain_cloaked = 0 + if(H.stat) // Dead or unconscious lings can't stay cloaked. + remain_cloaked = 0 + //using or equipping an item will uncloak you + if(H.l_hand || H.r_hand) + remain_cloaked = 0 + if(H.incapacitated(INCAPACITATION_DISABLED)) // Stunned lings also can't stay cloaked. + remain_cloaked = 0 + + if(mind.changeling.chem_recharge_rate != 0) //Without this, there is an exploit that can be done, if one buys engorged chem sacks while cloaked. + old_regen_rate += mind.changeling.chem_recharge_rate //Unfortunately, it has to occupy this part of the proc. This fixes it while at the same time + mind.changeling.chem_recharge_rate = 0 //making sure nobody loses out on their bonus regeneration after they're done hiding. + + + + H.invisibility = initial(invisibility) + visible_message("[src] suddenly fades in, seemingly from nowhere!", + "We revert our camouflage, revealing ourselves.") + H.mind.changeling.cloaked = 0 + H.mind.changeling.chem_recharge_rate = old_regen_rate + + animate(src,alpha = 10, alpha = 255, time = 10) diff --git a/code/game/objects/effects/spiders.dm b/code/game/objects/effects/spiders.dm index d0663f1930023..079a88e8d5039 100644 --- a/code/game/objects/effects/spiders.dm +++ b/code/game/objects/effects/spiders.dm @@ -294,7 +294,13 @@ icon_state = "greenshatter" anchored = TRUE layer = BLOOD_LAYER - +/obj/decal/cleanable/ling_vomit + name = "green goop" + desc = "Green squishy mess." + icon = 'icons/effects/effects.dmi' + icon_state = "greenshatter" + anchored = TRUE + layer = BLOOD_LAYER /obj/spider/cocoon name = "cocoon" desc = "Something wrapped in silky spider web." diff --git a/code/game/objects/structures/railing.dm b/code/game/objects/structures/railing.dm index d9a2528cc320b..c1dff74f87270 100644 --- a/code/game/objects/structures/railing.dm +++ b/code/game/objects/structures/railing.dm @@ -360,9 +360,13 @@ /obj/structure/railing/hitby(atom/movable/AM, datum/thrownthing/TT) var/mob/living/L = AM + var/chance if (!istype(L)) return - var/chance = TT.thrower.skill_check(SKILL_HAULING, SKILL_EXPERIENCED) ? 100 : 50 + if (istype(TT.thrower, L)) + chance = TT.thrower.skill_check(SKILL_HAULING, SKILL_EXPERIENCED) ? 100 : 50 + else + chance = 50 if (prob(chance)) slam_into(L) diff --git a/code/modules/augment/passive/armor.dm b/code/modules/augment/passive/armor.dm index d8695936dd7cb..1206d8f873923 100644 --- a/code/modules/augment/passive/armor.dm +++ b/code/modules/augment/passive/armor.dm @@ -6,3 +6,13 @@ augment_flags = AUGMENT_MECHANICAL | AUGMENT_BIOLOGICAL | AUGMENT_SCANNABLE | AUGMENT_INSPECTABLE var/brute_mult = 0.8 var/burn_mult = 1 +/obj/item/organ/internal/augment/armor/changeling + name = "organic armor" + icon_state = "endoarmor" + augment_flags = AUGMENT_BIOLOGICAL + status = ORGAN_CONFIGURE + desc = "A collection of bone, chitin and tissue carefully wrapped around the chest cavity." + brute_mult = 0.75 +/obj/item/organ/internal/augment/armor/changeling/emp_act() + SHOULD_CALL_PARENT(FALSE) + return \ No newline at end of file diff --git a/code/modules/augment/passive/ling_lenses.dm b/code/modules/augment/passive/ling_lenses.dm new file mode 100644 index 0000000000000..1e3ec15e96852 --- /dev/null +++ b/code/modules/augment/passive/ling_lenses.dm @@ -0,0 +1,19 @@ +/obj/item/organ/internal/augment/ling_lenses + name = "organic eyelid lense" + desc = "a thin, almost transparent lense surrounded by nerve fibers" + icon_state = "thermal_eyes" + status = ORGAN_CONFIGURE + augment_slots = AUGMENT_HEAD + augment_flags = AUGMENT_BIOLOGICAL + var/is_active = FALSE +/obj/item/organ/internal/augment/ling_lenses/emp_act() + ..() + return +/obj/item/organ/internal/augment/ling_lenses/Process() + ..() + if(is_active && (owner.mind.changeling.chem_charges > 0)) + owner.set_sight(owner.sight | SEE_MOBS) + owner.mind.changeling.chem_charges -= 3 + if(owner.mind.changeling.chem_charges < 3) + to_chat(owner,SPAN_NOTICE("Our lenses retract, causing us to lose our augmented vision.")) + is_active = FALSE diff --git a/code/modules/augment/passive/lingcore.dm b/code/modules/augment/passive/lingcore.dm new file mode 100644 index 0000000000000..eec814725f3a5 --- /dev/null +++ b/code/modules/augment/passive/lingcore.dm @@ -0,0 +1,57 @@ +/obj/item/organ/internal/augment/lingcore + name = "bizarre mass" + desc = "a twitching, pulsating mass that almost resembles a deformed embryo" + icon_state = "lingcore" + w_class = ITEM_SIZE_TINY + augment_slots = AUGMENT_CHEST + status = ORGAN_CONFIGURE + augment_flags = AUGMENT_BIOLOGICAL + var/ticks_remaining = 0; + var/datum/mind/backup + var/ownerckey + var/default_language + var/list/languages = list() + +/obj/item/organ/internal/augment/lingcore/emp_act() + ..() + return +/obj/item/organ/internal/augment/lingcore/getToxLoss() + return 0 +/obj/item/organ/internal/augment/lingcore/Process() + ..() + if(istype(owner,/mob/living/carbon/human)) + var/mob/living/carbon/human/C = owner + var/obj/item/organ/internal/brain/B = C.internal_organs_by_name[BP_BRAIN] + if(B.germ_level != 0) + B.germ_level = 0 +/obj/item/organ/internal/augment/lingcore/onInstall() + ..() + if(istype(owner,/mob/living/carbon) && (owner.mind) && (!owner.mind.changeling)) + overwrite() + owner.make_changeling() + if(owner.mind && owner.mind.changeling) + do_backup() +/obj/item/organ/internal/augment/lingcore/onRemove() + ..() + if(istype(owner,/mob/living/carbon) ) + owner.death(0) + owner.Drain() +/obj/item/organ/internal/augment/lingcore/proc/do_backup() + if(owner && owner.stat != DEAD && !is_broken() && owner.mind) + languages = owner.languages.Copy() + backup = owner.mind + default_language = owner.default_language + if(owner.ckey) + ownerckey = owner.ckey +/obj/item/organ/internal/augment/lingcore/proc/overwrite() + if(owner.mind && owner.ckey) //Someone is already in this body! + if(owner.mind == backup) // Oh, it's the same mind in the backup. Someone must've spammed the 'Start Procedure' button in a panic. + return + owner.visible_message(SPAN_DANGER("\The [owner] spasms and seizes!")) + owner.ghostize() + backup.active = 1 + backup.transfer_to(owner) + if (default_language) + owner.default_language = default_language + owner.languages = languages.Copy() + to_chat(owner, SPAN_NOTICE("Our tendrils extend throughout our new form, wrapping around nerve filaments. We awaken.")) diff --git a/code/modules/augment/passive/ragecore.dm b/code/modules/augment/passive/ragecore.dm new file mode 100644 index 0000000000000..333bf1d4d33f4 --- /dev/null +++ b/code/modules/augment/passive/ragecore.dm @@ -0,0 +1,29 @@ +/obj/item/organ/internal/augment/changeling/ragecore + name = "strange mass" + desc = "a throbbing, writhing sack of meat that almost seems to beat like a living heart" + icon_state = "berserk_sac" + augment_slots = AUGMENT_GROIN + status = ORGAN_CONFIGURE + augment_flags = AUGMENT_BIOLOGICAL + var/ticks_remaining = 0; +/obj/item/organ/internal/augment/changeling/ragecore/emp_act() + ..() + return +/obj/item/organ/internal/augment/changeling/ragecore/Process() + ..() + if (ticks_remaining > 0) + owner.add_chemical_effect(CE_PAINKILLER, 160) // About twice as strong as tramadol at full strength + owner.add_chemical_effect(CE_SPEEDBOOST, 1) + owner.add_chemical_effect(CE_STIMULANT, 4) + ticks_remaining -= 1 + if (ticks_remaining == 15) + to_chat(owner, SPAN_WARNING("We feel our adrenal sacs beginning to exhaust themselves. If we do not retreat soon, we will be left vulnerable.")) + if (!ticks_remaining) // ...but comes at a price. Brief short term benefit for a long-term comedown + to_chat(owner, SPAN_WARNING("We feel our body grow weak and sluggish, our adrenal sacs depleted.")) + owner.drowsyness = max(owner.drowsyness, 15) + owner.set_confused(15) + owner.slurring = max(owner.slurring, 30) + owner.chem_effects[CE_PAINKILLER] = 0 + owner.stamina = 0 + if(MOVING_QUICKLY(owner)) + owner.set_moving_slowly() diff --git a/code/modules/clothing/chameleon.dm b/code/modules/clothing/chameleon.dm index 474811ab4261b..600fa4c9db4bc 100644 --- a/code/modules/clothing/chameleon.dm +++ b/code/modules/clothing/chameleon.dm @@ -7,6 +7,10 @@ origin_tech = list(TECH_ESOTERIC = 3) item_flags = ITEM_FLAG_INVALID_FOR_CHAMELEON | ITEM_FLAG_IS_CHAMELEON_ITEM +/obj/item/clothing/under/chameleon/Initialize() + . = ..() + set_extension(src,/datum/extension/chameleon/clothing/under, /obj/item/clothing/under) + /obj/item/clothing/head/chameleon name = "cap" icon_state = "greysoft" @@ -15,6 +19,11 @@ body_parts_covered = 0 item_flags = ITEM_FLAG_INVALID_FOR_CHAMELEON | ITEM_FLAG_IS_CHAMELEON_ITEM +/obj/item/clothing/head/chameleon/Initialize() + . = ..() + + set_extension(src, /datum/extension/chameleon/clothing/head,/obj/item/clothing/head) + /obj/item/clothing/suit/chameleon name = "armor" icon_state = "armor" @@ -23,6 +32,10 @@ origin_tech = list(TECH_ESOTERIC = 3) item_flags = ITEM_FLAG_INVALID_FOR_CHAMELEON | ITEM_FLAG_IS_CHAMELEON_ITEM +/obj/item/clothing/suit/chameleon/Initialize() + . = ..() + set_extension(src, /datum/extension/chameleon/clothing/suit,/obj/item/clothing/suit) + /obj/item/clothing/shoes/chameleon name = "shoes" icon_state = "black" @@ -31,6 +44,10 @@ origin_tech = list(TECH_ESOTERIC = 3) item_flags = ITEM_FLAG_INVALID_FOR_CHAMELEON | ITEM_FLAG_IS_CHAMELEON_ITEM +/obj/item/clothing/shoes/chameleon/Initialize() + . = ..() + set_extension(src, /datum/extension/chameleon/clothing/shoes, /obj/item/clothing/shoes) + /obj/item/storage/backpack/chameleon name = "backpack" icon_state = "backpack" @@ -39,6 +56,7 @@ origin_tech = list(TECH_ESOTERIC = 3) item_flags = ITEM_FLAG_INVALID_FOR_CHAMELEON | ITEM_FLAG_IS_CHAMELEON_ITEM + /obj/item/clothing/gloves/chameleon name = "gloves" icon_state = "black" @@ -47,6 +65,10 @@ origin_tech = list(TECH_ESOTERIC = 3) item_flags = ITEM_FLAG_INVALID_FOR_CHAMELEON | ITEM_FLAG_IS_CHAMELEON_ITEM +/obj/item/clothing/gloves/chameleon/Initialize() + . = ..() + set_extension(src, /datum/extension/chameleon/clothing/gloves, /obj/item/clothing/gloves) + /obj/item/clothing/mask/chameleon name = "mask" icon_state = "fullgas" @@ -55,6 +77,10 @@ origin_tech = list(TECH_ESOTERIC = 3) item_flags = ITEM_FLAG_INVALID_FOR_CHAMELEON | ITEM_FLAG_IS_CHAMELEON_ITEM +/obj/item/clothing/mask/chameleon/Initialize() + . = ..() + set_extension(src, /datum/extension/chameleon/clothing/mask, /obj/item/clothing/mask) + /obj/item/clothing/glasses/chameleon name = "goggles" icon_state = "meson" @@ -63,6 +89,10 @@ origin_tech = list(TECH_ESOTERIC = 3) item_flags = ITEM_FLAG_INVALID_FOR_CHAMELEON | ITEM_FLAG_IS_CHAMELEON_ITEM +/obj/item/clothing/glasses/chameleon/Initialize() + . = ..() + set_extension(src, /datum/extension/chameleon/clothing/glasses, /obj/item/clothing/glasses) + /obj/item/device/radio/headset/chameleon name = "radio headset" icon_state = "headset" @@ -79,6 +109,10 @@ origin_tech = list(TECH_ESOTERIC = 3) item_flags = ITEM_FLAG_INVALID_FOR_CHAMELEON | ITEM_FLAG_IS_CHAMELEON_ITEM +/obj/item/clothing/accessory/chameleon/Initialize() + . = ..() + set_extension(src,/datum/extension/chameleon/clothing/accessory, /obj/item/clothing/accessory) + /obj/item/gun/energy/chameleon name = "chameleon gun" desc = "A hologram projector in the shape of a gun. There is a dial on the side to change the gun's disguise." diff --git a/code/modules/clothing/under/accessories/_accessory.dm b/code/modules/clothing/under/accessories/_accessory.dm index 628320f5f7ef1..2c98c7c7d559d 100644 --- a/code/modules/clothing/under/accessories/_accessory.dm +++ b/code/modules/clothing/under/accessories/_accessory.dm @@ -84,6 +84,9 @@ //when user attached an accessory to S /obj/item/clothing/accessory/proc/on_attached(obj/item/clothing/S, mob/user) + if(istype(src,/obj/item/clothing/accessory/chameleon/changeling)) + if(is_type_in_list(S,changeling_fabricated_clothing)) + return if(!istype(S)) return parent = S diff --git a/code/modules/mob/living/carbon/human/appearance.dm b/code/modules/mob/living/carbon/human/appearance.dm index 29134484954ce..315e5896f1b7e 100644 --- a/code/modules/mob/living/carbon/human/appearance.dm +++ b/code/modules/mob/living/carbon/human/appearance.dm @@ -23,11 +23,15 @@ /mob/living/carbon/human/proc/change_gender(gender) if(src.gender == gender) return - src.gender = gender + reset_hair() + force_update_limbs() update_body() update_dna() + src.sync_organ_dna() + if(src.gender == MALE) + src.UpdateAppearance() return 1 /mob/living/carbon/human/proc/randomize_gender() diff --git a/code/modules/mob/living/carbon/human/death.dm b/code/modules/mob/living/carbon/human/death.dm index 9349e5bcdff45..a6d6fe3b79745 100644 --- a/code/modules/mob/living/carbon/human/death.dm +++ b/code/modules/mob/living/carbon/human/death.dm @@ -87,6 +87,11 @@ update_body(1) return +/mob/living/carbon/human/proc/Drain() + ChangeToHusk() + mutations |= MUTATION_HUSK + return + /mob/living/carbon/human/proc/ChangeToSkeleton() if(MUTATION_SKELETON in src.mutations) return diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm index 604313ccf91e6..c32aeef4f2a36 100644 --- a/code/modules/mob/living/carbon/human/update_icons.dm +++ b/code/modules/mob/living/carbon/human/update_icons.dm @@ -119,6 +119,11 @@ If you have any questions/constructive-comments/bugs-to-report/or have a massivl Please contact me on #coderbus IRC. ~Carn x */ +//Add an entry to overlays, assuming it exists +/mob/living/carbon/human/proc/apply_layer(cache_index) + if((. = overlays_standing[cache_index])) + overlays.Add(.) + //Human Overlays Indexes///////// #define HO_BODY_LAYER 1 #define HO_MUTATIONS_LAYER 2 @@ -147,7 +152,8 @@ Please contact me on #coderbus IRC. ~Carn x #define HO_L_HAND_LAYER 25 #define HO_R_HAND_LAYER 26 #define HO_FIRE_LAYER 27 //If you're on fire -#define TOTAL_LAYERS 28 +#define HO_EFFECTS_LAYER 28 +#define TOTAL_LAYERS 29 ////////////////////////////////// /mob/living/carbon/human @@ -328,6 +334,7 @@ var/global/list/damage_icon_parts = list() var/list/icon_render_keys = list() /mob/living/carbon/human/proc/update_body(update_icons=1) + //Update all limbs and visible organs one by one var/list/needs_update = list() var/limb_count_update = FALSE @@ -766,7 +773,6 @@ var/global/list/damage_icon_parts = list() if(update_icons) queue_icon_update() - /mob/living/carbon/human/update_fire(update_icons=1) overlays_standing[HO_FIRE_LAYER] = null if(on_fire) diff --git a/code/modules/mob/living/life.dm b/code/modules/mob/living/life.dm index 95787852e45e7..844bcc992e4ac 100644 --- a/code/modules/mob/living/life.dm +++ b/code/modules/mob/living/life.dm @@ -11,7 +11,6 @@ if(machine && !CanMouseDrop(machine, src)) machine = null - //Handle temperature/pressure differences between body and environment var/datum/gas_mixture/environment = loc.return_air() if(environment) @@ -194,12 +193,21 @@ /mob/living/proc/update_sight() if(stat == DEAD || eyeobj) update_dead_sight() - else - update_living_sight() + if (seedarkness) + set_sight(0) + set_see_in_dark(0) + if(stat == DEAD || eyeobj) + update_dead_sight() + else + update_living_sight() - var/list/vision = get_accumulated_vision_handlers() - set_sight(sight | vision[1]) - set_see_invisible(max(vision[2], see_invisible)) + var/list/vision = get_accumulated_vision_handlers() + set_sight(sight | vision[1]) + set_see_invisible(max(vision[2], see_invisible)) + + else + set_see_in_dark(8) + set_see_invisible(SEE_INVISIBLE_NOLIGHTING) /mob/living/proc/update_living_sight() var/set_sight_flags = sight & ~(SEE_TURFS|SEE_MOBS|SEE_OBJS) diff --git a/code/modules/mob/living/maneuvers/maneuver_leap.dm b/code/modules/mob/living/maneuvers/maneuver_leap.dm index 8f785dde54418..815e377cb4c39 100644 --- a/code/modules/mob/living/maneuvers/maneuver_leap.dm +++ b/code/modules/mob/living/maneuvers/maneuver_leap.dm @@ -81,3 +81,19 @@ animate(pixel_z = user.default_pixel_z, time = 3, easing = SINE_EASING | EASE_OUT) user.throw_at(get_turf(target), strength, 1, user, FALSE, new Callback(src, TYPE_PROC_REF(/singleton/maneuver/leap, end_leap), user, target)) addtimer(new Callback(user, TYPE_PROC_REF(/mob/living, jump_layer_shift_end)), 4.5) + +/singleton/maneuver/leap/changeling + name = "vigorous leap" + stamina_cost = 20 + reflexive_modifier = 0.5 + cooldown = 5 SECONDS + delay = 0 SECONDS + +/singleton/maneuver/leap/changeling/end_leap(mob/living/user, atom/target) + . = ..() + if(ishuman(user) && !user.lying && ismob(target) && user.Adjacent(target)) + if(istype(user.get_active_hand(),/obj/item/melee/changeling)) + target.use_weapon(user.get_active_hand(),user) + if(istype(user.get_inactive_hand(), /obj/item/melee/changeling)) + user.swap_hand() + target.use_weapon(user.get_active_hand(),user) \ No newline at end of file diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm index 11549b6583b87..4b99ee3c9e197 100644 --- a/code/modules/mob/mob_defines.dm +++ b/code/modules/mob/mob_defines.dm @@ -153,6 +153,9 @@ var/voice_name = "unidentifiable voice" + // To check if mobs can see shadows or not. By default they can. + var/seedarkness = 1 + var/faction = MOB_FACTION_NEUTRAL //Used for checking whether hostile simple animals will attack you, possibly more stuff later var/last_faction = MOB_FACTION_NEUTRAL var/blinded = null diff --git a/code/modules/mob/observer/ghost/ghost.dm b/code/modules/mob/observer/ghost/ghost.dm index 89e89882922a6..325c6d2ab01e2 100644 --- a/code/modules/mob/observer/ghost/ghost.dm +++ b/code/modules/mob/observer/ghost/ghost.dm @@ -30,7 +30,7 @@ var/global/list/image/ghost_sightless_images = list() //this is a list of images var/admin_ghosted = 0 var/anonsay = 0 var/ghostvision = 1 //is the ghost able to see things humans can't? - var/seedarkness = 1 + seedarkness = 1 var/obj/item/device/multitool/ghost_multitool var/list/hud_images // A list of hud images diff --git a/code/modules/multiz/movement.dm b/code/modules/multiz/movement.dm index d5876e2fbf631..88e22de9f3668 100644 --- a/code/modules/multiz/movement.dm +++ b/code/modules/multiz/movement.dm @@ -242,6 +242,7 @@ SPAN_NOTICE("\The [src]'s suit whirrs loudly as \the [rig] absorbs the fall!"), SPAN_NOTICE("You hear an electric *whirr* right after the slam!") ) + if (fall_damage()) for (var/mob/living/M in landing.contents) if (M == src) @@ -263,7 +264,8 @@ /mob/living/carbon/human/handle_fall_effect(turf/landing) if(species && species.handle_fall_special(src, landing)) return - + if(src.mind && src.mind.changeling && src.mind.changeling.tendons_reinforced) + return var/obj/item/rig/rig = get_rig() if (istype(rig)) for (var/obj/item/rig_module/actuators/A in rig.installed_modules) diff --git a/code/modules/organs/external/_external.dm b/code/modules/organs/external/_external.dm index e5c33df72149b..fc69b33afa8d5 100644 --- a/code/modules/organs/external/_external.dm +++ b/code/modules/organs/external/_external.dm @@ -429,7 +429,7 @@ This function completely restores a damaged organ to perfect condition. // remove embedded objects and drop them on the floor for(var/obj/implanted_object in implants) - if(!istype(implanted_object,/obj/item/implant)) // We don't want to remove REAL implants. Just shrapnel etc. + if(!istype(implanted_object,/obj/item/implant,) || istype(implanted_object,/obj/item/organ/internal/augment)) // We don't want to remove REAL implants. Just shrapnel etc. implanted_object.forceMove(get_turf(src)) implants -= implanted_object diff --git a/code/modules/organs/external/_external_damage.dm b/code/modules/organs/external/_external_damage.dm index b8a0be3f1831b..86cf857db14b1 100644 --- a/code/modules/organs/external/_external_damage.dm +++ b/code/modules/organs/external/_external_damage.dm @@ -10,6 +10,7 @@ take_external_damage(amount) /obj/item/organ/external/proc/take_external_damage(brute, burn, damage_flags, used_weapon = null) + brute = round(brute * get_brute_mod(damage_flags), 0.1) burn = round(burn * get_burn_mod(damage_flags), 0.1) diff --git a/code/modules/organs/internal/lungs.dm b/code/modules/organs/internal/lungs.dm index 54757fc8ecc89..07e27811e8c4f 100644 --- a/code/modules/organs/internal/lungs.dm +++ b/code/modules/organs/internal/lungs.dm @@ -29,7 +29,7 @@ var/breathing = 0 var/last_successful_breath var/breath_fail_ratio // How badly they failed a breath. Higher is worse. - + var/mnoBreath = 100 /obj/item/organ/internal/lungs/proc/can_drown() return (is_broken() || !has_gills) @@ -80,7 +80,7 @@ if(prob(5)) owner.emote("cough") //respitory tract infection - if(is_bruised() && !owner.is_asystole()) + if(is_bruised() && !owner.is_asystole() && !(mnoBreath in owner.mutations)) if(prob(2)) if(active_breathing) owner.visible_message( diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm index fe797006d15e2..7ce951d91ed88 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -164,7 +164,11 @@ return 0 if(!user.IsAdvancedToolUser()) return 0 - + if(istype(user,/mob/living/carbon/human)) + var/mob/living/carbon/human/H = user + if(istype(H.wear_suit,/obj/item/clothing/suit/space/changeling/armored) && !istype(src,/obj/item/gun/projectile/changeling)) + to_chat(user,SPAN_WARNING("This form is too bulky to make use of the trigger guard!")) + return FALSE var/mob/living/M = user if(!safety() && world.time > last_safety_check + 5 MINUTES && !user.skill_check(SKILL_WEAPONS, SKILL_BASIC)) if(prob(30)) diff --git a/code/modules/projectiles/guns/projectile.dm b/code/modules/projectiles/guns/projectile.dm index 2321283af05ce..89130ca6e809e 100644 --- a/code/modules/projectiles/guns/projectile.dm +++ b/code/modules/projectiles/guns/projectile.dm @@ -36,7 +36,7 @@ var/mag_insert_sound = 'sound/weapons/guns/interaction/pistol_magin.ogg' var/mag_remove_sound = 'sound/weapons/guns/interaction/pistol_magout.ogg' var/can_special_reload = TRUE //Whether or not we can tactical/speed reload - + var/can_reload = TRUE var/is_jammed = 0 //Whether this gun is jammed var/jam_chance = 0 //Chance it jams on fire //TODO generalize ammo icon states for guns @@ -134,6 +134,8 @@ //Attempts to load A into src, depending on the type of thing being loaded and the load_method //Maybe this should be broken up into separate procs for each load method? /obj/item/gun/projectile/proc/load_ammo(obj/item/A, mob/user) + if(!can_reload) + return if(istype(A, /obj/item/ammo_magazine)) . = TRUE var/obj/item/ammo_magazine/AM = A @@ -255,6 +257,8 @@ //attempts to unload src. If allow_dump is set to 0, the speedloader unloading method will be disabled /obj/item/gun/projectile/proc/unload_ammo(mob/user, allow_dump=1) + if(!can_reload) + return if(is_jammed) user.visible_message("\The [user] begins to unjam [src].", "You clear the jam and unload [src]") if(!do_after(user, 0.4 SECONDS, src, DO_DEFAULT | DO_BOTH_UNIQUE_ACT)) diff --git a/code/modules/species/species.dm b/code/modules/species/species.dm index dc9ab272f5e55..d9aa5435a9c36 100644 --- a/code/modules/species/species.dm +++ b/code/modules/species/species.dm @@ -419,7 +419,6 @@ The slots that you can use are found in items_clothing.dm and are the inventory warning("[O.type] has a default organ tag \"[O.organ_tag]\" that differs from the species' organ tag \"[organ_tag]\". Updating organ_tag to match.") O.organ_tag = organ_tag H.internal_organs_by_name[organ_tag] = O - for(var/name in H.organs_by_name) H.organs |= H.organs_by_name[name] @@ -429,7 +428,6 @@ The slots that you can use are found in items_clothing.dm and are the inventory for(var/obj/item/organ/O in (H.organs|H.internal_organs)) O.owner = H post_organ_rejuvenate(O, H) - H.sync_organ_dna() /* ------------------------> code\modules\emotes\definitions\_species.dm (У нас всё в моде в mods\emote_panel\code\species.dm) /datum/species/proc/hug(mob/living/carbon/human/H, mob/living/target) diff --git a/icons/mob/modifier_effects.dmi b/icons/mob/modifier_effects.dmi new file mode 100644 index 0000000000000..99a4f6e4ed0d4 Binary files /dev/null and b/icons/mob/modifier_effects.dmi differ diff --git a/icons/mob/onmob/items/lefthand.dmi b/icons/mob/onmob/items/lefthand.dmi index 52eb1962b7f75..363f44c1f6015 100644 Binary files a/icons/mob/onmob/items/lefthand.dmi and b/icons/mob/onmob/items/lefthand.dmi differ diff --git a/icons/mob/onmob/items/lefthand_spacesuits.dmi b/icons/mob/onmob/items/lefthand_spacesuits.dmi index a2a812e1ed62b..2411f534473f9 100644 Binary files a/icons/mob/onmob/items/lefthand_spacesuits.dmi and b/icons/mob/onmob/items/lefthand_spacesuits.dmi differ diff --git a/icons/mob/onmob/items/righthand.dmi b/icons/mob/onmob/items/righthand.dmi index b2d9e57420577..ef5c0ffc0ab61 100644 Binary files a/icons/mob/onmob/items/righthand.dmi and b/icons/mob/onmob/items/righthand.dmi differ diff --git a/icons/mob/onmob/items/righthand_spacesuits.dmi b/icons/mob/onmob/items/righthand_spacesuits.dmi index e0a3f2cd06ae1..9640ec709b3df 100644 Binary files a/icons/mob/onmob/items/righthand_spacesuits.dmi and b/icons/mob/onmob/items/righthand_spacesuits.dmi differ diff --git a/icons/mob/onmob/onmob_belt.dmi b/icons/mob/onmob/onmob_belt.dmi index 9e7941b095e4d..4ab840928e9f3 100644 Binary files a/icons/mob/onmob/onmob_belt.dmi and b/icons/mob/onmob/onmob_belt.dmi differ diff --git a/icons/mob/onmob/onmob_eyes.dmi b/icons/mob/onmob/onmob_eyes.dmi index 47c97701f868a..d49a12a5e0fa2 100644 Binary files a/icons/mob/onmob/onmob_eyes.dmi and b/icons/mob/onmob/onmob_eyes.dmi differ diff --git a/icons/mob/onmob/onmob_feet.dmi b/icons/mob/onmob/onmob_feet.dmi index 954ce5656840e..2754ebb2c31fd 100644 Binary files a/icons/mob/onmob/onmob_feet.dmi and b/icons/mob/onmob/onmob_feet.dmi differ diff --git a/icons/mob/onmob/onmob_hands.dmi b/icons/mob/onmob/onmob_hands.dmi index 43964e3ce8f4a..8adb2586a3f02 100644 Binary files a/icons/mob/onmob/onmob_hands.dmi and b/icons/mob/onmob/onmob_hands.dmi differ diff --git a/icons/mob/onmob/onmob_head.dmi b/icons/mob/onmob/onmob_head.dmi index ab89699f89b6c..d9ccd6d0c834a 100644 Binary files a/icons/mob/onmob/onmob_head.dmi and b/icons/mob/onmob/onmob_head.dmi differ diff --git a/icons/mob/onmob/onmob_mask.dmi b/icons/mob/onmob/onmob_mask.dmi index 26b7ec6ddeda4..44a3b562407ff 100644 Binary files a/icons/mob/onmob/onmob_mask.dmi and b/icons/mob/onmob/onmob_mask.dmi differ diff --git a/icons/mob/onmob/onmob_suit.dmi b/icons/mob/onmob/onmob_suit.dmi index cdd965312a55c..320ee8cf7a664 100644 Binary files a/icons/mob/onmob/onmob_suit.dmi and b/icons/mob/onmob/onmob_suit.dmi differ diff --git a/icons/mob/onmob/onmob_under.dmi b/icons/mob/onmob/onmob_under.dmi index 2cb6b7ddc48de..f39d63695a3c8 100644 Binary files a/icons/mob/onmob/onmob_under.dmi and b/icons/mob/onmob/onmob_under.dmi differ diff --git a/icons/mob/screen_spells.dmi b/icons/mob/screen_spells.dmi index 50be07d787b8c..541f09908c282 100644 Binary files a/icons/mob/screen_spells.dmi and b/icons/mob/screen_spells.dmi differ diff --git a/icons/obj/augment.dmi b/icons/obj/augment.dmi index 48e29f6e018c5..7416d992f9b9d 100644 Binary files a/icons/obj/augment.dmi and b/icons/obj/augment.dmi differ diff --git a/icons/obj/clothing/obj_accessories.dmi b/icons/obj/clothing/obj_accessories.dmi index 9948e18d39640..a1f1b10ed5130 100644 Binary files a/icons/obj/clothing/obj_accessories.dmi and b/icons/obj/clothing/obj_accessories.dmi differ diff --git a/icons/obj/clothing/obj_backpacks.dmi b/icons/obj/clothing/obj_backpacks.dmi index 121b8260e5a3a..0b397695adae9 100644 Binary files a/icons/obj/clothing/obj_backpacks.dmi and b/icons/obj/clothing/obj_backpacks.dmi differ diff --git a/icons/obj/clothing/obj_belt.dmi b/icons/obj/clothing/obj_belt.dmi index cb6487083229e..e47e6491a2af2 100644 Binary files a/icons/obj/clothing/obj_belt.dmi and b/icons/obj/clothing/obj_belt.dmi differ diff --git a/icons/obj/clothing/obj_eyes.dmi b/icons/obj/clothing/obj_eyes.dmi index c746f3a1a661b..5db018dc41f7e 100644 Binary files a/icons/obj/clothing/obj_eyes.dmi and b/icons/obj/clothing/obj_eyes.dmi differ diff --git a/icons/obj/clothing/obj_feet.dmi b/icons/obj/clothing/obj_feet.dmi index 8d29e712d9cd1..e5086830b489d 100644 Binary files a/icons/obj/clothing/obj_feet.dmi and b/icons/obj/clothing/obj_feet.dmi differ diff --git a/icons/obj/clothing/obj_hands.dmi b/icons/obj/clothing/obj_hands.dmi index 09b807a538398..0bdb00ca1c2cd 100644 Binary files a/icons/obj/clothing/obj_hands.dmi and b/icons/obj/clothing/obj_hands.dmi differ diff --git a/icons/obj/clothing/obj_head.dmi b/icons/obj/clothing/obj_head.dmi index 22964cf7c9af4..b6e612c8feaf4 100644 Binary files a/icons/obj/clothing/obj_head.dmi and b/icons/obj/clothing/obj_head.dmi differ diff --git a/icons/obj/clothing/obj_mask.dmi b/icons/obj/clothing/obj_mask.dmi index 168436c1a2538..0bcb6f8cc641f 100644 Binary files a/icons/obj/clothing/obj_mask.dmi and b/icons/obj/clothing/obj_mask.dmi differ diff --git a/icons/obj/clothing/obj_suit.dmi b/icons/obj/clothing/obj_suit.dmi index 95fdc5db3973f..8475fc1a2213e 100644 Binary files a/icons/obj/clothing/obj_suit.dmi and b/icons/obj/clothing/obj_suit.dmi differ diff --git a/icons/obj/clothing/obj_under.dmi b/icons/obj/clothing/obj_under.dmi index f9968bcbaf687..76b6f671b826d 100644 Binary files a/icons/obj/clothing/obj_under.dmi and b/icons/obj/clothing/obj_under.dmi differ diff --git a/icons/obj/guns/dartgun.dmi b/icons/obj/guns/dartgun.dmi index 628a1464c798d..65413149c669b 100644 Binary files a/icons/obj/guns/dartgun.dmi and b/icons/obj/guns/dartgun.dmi differ diff --git a/icons/obj/projectiles.dmi b/icons/obj/projectiles.dmi index 13ab8d2ac82bd..8fd3c7d33364b 100644 Binary files a/icons/obj/projectiles.dmi and b/icons/obj/projectiles.dmi differ diff --git a/icons/obj/weapons/melee_energy.dmi b/icons/obj/weapons/melee_energy.dmi index 4d7e0b4c5e91a..9cd3b3c83022e 100644 Binary files a/icons/obj/weapons/melee_energy.dmi and b/icons/obj/weapons/melee_energy.dmi differ diff --git a/icons/obj/weapons/melee_physical.dmi b/icons/obj/weapons/melee_physical.dmi index bd59c7d83eea6..ccda4b64e103d 100644 Binary files a/icons/obj/weapons/melee_physical.dmi and b/icons/obj/weapons/melee_physical.dmi differ diff --git a/sound/effects/ling_horror.ogg b/sound/effects/ling_horror.ogg new file mode 100644 index 0000000000000..3dc6450bab89d Binary files /dev/null and b/sound/effects/ling_horror.ogg differ diff --git a/sound/effects/ling_shriek.ogg b/sound/effects/ling_shriek.ogg new file mode 100644 index 0000000000000..5dd92382720fa Binary files /dev/null and b/sound/effects/ling_shriek.ogg differ diff --git a/sound/effects/ling_shriek2.ogg b/sound/effects/ling_shriek2.ogg new file mode 100644 index 0000000000000..3292fff8e7db7 Binary files /dev/null and b/sound/effects/ling_shriek2.ogg differ diff --git a/test/check-paths.sh b/test/check-paths.sh index 9f3300ab701d6..60dc47576b5e4 100755 --- a/test/check-paths.sh +++ b/test/check-paths.sh @@ -48,7 +48,7 @@ exactly 27 "text2path uses" 'text2path' exactly 5 "update_icon() override" '/update_icon\((.*)\)' -P exactly 4 "goto use" 'goto ' exactly 1 "NOOP match" 'NOOP' -exactly 332 "spawn uses" '^\s*spawn\s*\(\s*(-\s*)?\d*\s*\)' -P +exactly 323 "spawn uses" '^\s*spawn\s*\(\s*(-\s*)?\d*\s*\)' -P exactly 0 "tag uses" '\stag = ' -P '**/*.dmm' exactly 0 "anchored = 0/1" 'anchored\s*=\s*\d' -P exactly 2 "density = 0/1" 'density\s*=\s*\d' -P