From 2d8e0c6e7ac1840a80431286a220e25a7ed99f33 Mon Sep 17 00:00:00 2001 From: forest2001 Date: Fri, 3 Mar 2023 22:38:06 +0000 Subject: [PATCH 01/25] Ported Paradise SS13 Cortical Borers tiny bit of text clean up Working chems working chems p2 Text SPANs Action swap and categories Moved procs to separate file Apparently this didn't go through in the last commmit? Ported Paradise SS13 Cortical Borers tiny bit of text clean up Working chems working chems p2 Text SPANs Action swap and categories Moved procs to separate file reproduction levels icons icons Cure hiding icon Rebase Checks PROC_REF PROC_REF 515 is lame UID Temp Reproduce icon UID death Disables Zombie Powder and stun action wheel too Update ColonialMarinesALPHA.dme Co-authored-by: harryob Lang fix 0.5 Lang fix, property, chem names. Var tidy and chem cats Speech, Chem Fix & Enzyme Grinding Chem log Spans and Ghost chat emote freedom parenthesis stun fix Death handling & hud fix Follow and Death death check Player SimpleMobs in Orbit Enzymes and FOLLOW Defines, Balance, Brain Damage Defines and Balancing Spawn message & Orbit order Orbit order X Impurity Part 1 Luminescence and Icons Icon Rebase Hibernation message types Ported Paradise SS13 Cortical Borers working chems p2 Text SPANs Moved procs to separate file Ported Paradise SS13 Cortical Borers tiny bit of text clean up Working chems working chems p2 Text SPANs Action swap and categories Moved procs to separate file icons icons hiding icon Rebase UID Temp Reproduce icon UID death Disables Zombie Powder and stun action wheel too Update ColonialMarinesALPHA.dme Co-authored-by: harryob Var tidy and chem cats Speech, Chem Fix & Enzyme Grinding Spans and Ghost chat Death handling & hud fix Follow and Death Player SimpleMobs in Orbit Enzymes and FOLLOW Defines, Balance, Brain Damage Defines and Balancing Spawn message & Orbit order Orbit order X Luminescence and Icons Icon Rebase 515 rebase ma antiruntime carbon tests --- code/__DEFINES/chemistry.dm | 1 + code/__DEFINES/language.dm | 1 + code/datums/emergency_calls/custom.dm | 2 +- code/datums/mob_hud.dm | 6 + code/modules/borer/_defines.dm | 8 + code/modules/borer/borer.dm | 407 ++++++++++ code/modules/borer/borer_chemicals.dm | 137 ++++ code/modules/borer/borer_html.dm | 69 ++ code/modules/borer/borer_procs.dm | 720 ++++++++++++++++++ code/modules/mob/holder.dm | 6 + code/modules/mob/language/languages.dm | 44 ++ .../mob/living/carbon/carbon_defines.dm | 2 + code/modules/mob/living/carbon/human/death.dm | 3 + code/modules/mob/living/carbon/human/human.dm | 18 + code/modules/mob/living/carbon/human/life.dm | 3 + code/modules/mob/living/say.dm | 4 + .../mob/living/simple_animal/simple_animal.dm | 5 +- .../chemistry_machinery/reagent_grinder.dm | 10 + .../chemistry_properties/prop_positive.dm | 13 + .../reagents/chemistry_reactions/other.dm | 8 + .../reagents/chemistry_reagents/other.dm | 24 + code/span_macros.dm | 1 + colonialmarines.dme | 5 + icons/mob/brainslug.dmi | Bin 0 -> 4277 bytes icons/mob/hud/actions_borer.dmi | Bin 0 -> 2032 bytes .../tgui/interfaces/BorerChemDispenser.js | 1 + 26 files changed, 1495 insertions(+), 3 deletions(-) create mode 100644 code/modules/borer/_defines.dm create mode 100644 code/modules/borer/borer.dm create mode 100644 code/modules/borer/borer_chemicals.dm create mode 100644 code/modules/borer/borer_html.dm create mode 100644 code/modules/borer/borer_procs.dm create mode 100644 icons/mob/brainslug.dmi create mode 100644 icons/mob/hud/actions_borer.dmi create mode 100644 tgui/packages/tgui/interfaces/BorerChemDispenser.js diff --git a/code/__DEFINES/chemistry.dm b/code/__DEFINES/chemistry.dm index 184f136b0aa5..c232a1cad77f 100644 --- a/code/__DEFINES/chemistry.dm +++ b/code/__DEFINES/chemistry.dm @@ -83,6 +83,7 @@ #define CHEM_EFFECT_HYPER_THROTTLE (1<<2) //universal understand but not speech #define CHEM_EFFECT_ORGAN_STASIS (1<<3) //peri stabiliser #define CHEM_EFFECT_NO_BLEEDING (1<<4) //replacement for quickclot +#define CHEM_EFFECT_ANTI_PARASITE (1<<4) //PROPERTY_ANTIPARASITIC //Blood plasma diff --git a/code/__DEFINES/language.dm b/code/__DEFINES/language.dm index 8cac90defb26..9aa2ad702a94 100644 --- a/code/__DEFINES/language.dm +++ b/code/__DEFINES/language.dm @@ -15,6 +15,7 @@ #define LANGUAGE_APOLLO "Apollo Link" #define LANGUAGE_TELEPATH "Telepath Implant" +#define LANGUAGE_BORER "Cortical Link" #define ALL_HUMAN_LANGUAGES list(LANGUAGE_ENGLISH, LANGUAGE_JAPANESE, LANGUAGE_CHINESE, LANGUAGE_RUSSIAN, LANGUAGE_GERMAN, LANGUAGE_SPANISH) diff --git a/code/datums/emergency_calls/custom.dm b/code/datums/emergency_calls/custom.dm index 0117c83fc19c..f65fdba536b6 100644 --- a/code/datums/emergency_calls/custom.dm +++ b/code/datums/emergency_calls/custom.dm @@ -28,7 +28,7 @@ return M.transfer_to(H, TRUE) - + to_chat(H, SPAN_ALERT("[objectives]")) players_to_offer -= H return diff --git a/code/datums/mob_hud.dm b/code/datums/mob_hud.dm index b1d9a9c2fade..fa2af17240f9 100644 --- a/code/datums/mob_hud.dm +++ b/code/datums/mob_hud.dm @@ -446,6 +446,12 @@ var/list/datum/mob_hud/huds = list( return + var/mob/living/carbon/cortical_borer/B = has_brain_worms() + if(B && B.controlling) + holder.icon_state = "hudbrainworm" + if(!holder2_set) + holder2.icon_state = "hudbrainworm" + return for(var/datum/disease/D in viruses) if(!D.hidden[SCANNER]) diff --git a/code/modules/borer/_defines.dm b/code/modules/borer/_defines.dm new file mode 100644 index 000000000000..15929f6fa492 --- /dev/null +++ b/code/modules/borer/_defines.dm @@ -0,0 +1,8 @@ +/// Chemical categories +#define BORER_CAT_HEAL "Medicines" +#define BORER_CAT_PUNISH "Motivators" +#define BORER_CAT_STIM "Stimulants" +#define BORER_CAT_SELF "Self Protection" + +///Amount of chemicals needed for a borer to reproduce, provided reproduction is toggled. +#define BORER_LARVAE_COST 400 diff --git a/code/modules/borer/borer.dm b/code/modules/borer/borer.dm new file mode 100644 index 000000000000..683956bf1fb4 --- /dev/null +++ b/code/modules/borer/borer.dm @@ -0,0 +1,407 @@ +/mob/living/captive_brain + name = "host brain" + real_name = "host brain" + +/mob/living/captive_brain/say(message) + if(client) + if(client.prefs.muted & MUTE_IC) + to_chat(src, SPAN_WARNING("You cannot speak in IC (muted).")) + return + if(client.handle_spam_prevention(message, MUTE_IC)) + return + + if(istype(loc,/mob/living/carbon/cortical_borer)) + message = trim(sanitize(copytext(message, 1, MAX_MESSAGE_LEN))) + if(!message) + return + if(stat == DEAD) + return say_dead(message) + var/mob/living/carbon/cortical_borer/B = loc + to_chat(src, SPAN_BORER("You whisper silently, [message]"), type = MESSAGE_TYPE_RADIO) + to_chat(B.host, SPAN_BORER("The captive mind of [src] whispers, \"[message]\""), type = MESSAGE_TYPE_RADIO) + log_say("BORER: ([key_name(src)] to [key_name(B.host)]) [message]", src) + for (var/mob/dead in GLOB.dead_mob_list) + var/track_borer = " (F)" + if(!istype(dead,/mob/new_player) && !istype(dead,/mob/living/brain)) //No meta-evesdropping + dead.show_message(SPAN_BORER("BORER: ([name] (trapped mind) to [B.truename][track_borer]) whispers: [message]"), SHOW_MESSAGE_VISIBLE) + +/mob/living/captive_brain/say_understands(mob/other, datum/language/speaking = null) + var/mob/living/carbon/cortical_borer/B = loc + if(!istype(B)) + log_debug(EXCEPTION("Trapped mind found without a borer!"), src) + return FALSE + return B.host.say_understands(other, speaking) + +/mob/living/captive_brain/emote(act, m_type = 1, message = null, intentional = FALSE, force_silence = FALSE) + return + +/mob/living/captive_brain/resist() + var/mob/living/carbon/cortical_borer/B = loc + if(!istype(B)) + log_debug(EXCEPTION("Trapped mind found without a borer!"), src) + return + + to_chat(src, SPAN_DANGER("You begin doggedly resisting the parasite's control (this will take approximately sixty seconds).")) + to_chat(B.host, SPAN_XENOWARNING("You feel the captive mind of [src] begin to resist your control.")) + + var/delay = (rand(350,450) + B.host.getBrainLoss()) + addtimer(CALLBACK(src, PROC_REF(return_control), B), delay) + +/mob/living/carbon/cortical_borer + name = "cortical borer" + real_name = "cortical borer" + desc = "A small, quivering sluglike creature." + speak_emote = list("chirrups") + icon = 'icons/mob/brainslug.dmi' + icon_state = "brainslug" + speed = 0 + a_intent = INTENT_HARM + status_flags = CANPUSH + attacktext = "nips" + friendly = "prods" + mob_size = MOB_SIZE_SMALL + density = 0 + pass_flags = PASS_FLAGS_CRAWLER + mob_size = MOB_SIZE_SMALL + faction = list("creature") + hud_possible = list(HEALTH_HUD,STATUS_HUD) + universal_understand = TRUE + + holder_type = /obj/item/holder/borer + + var/generation = 1 + var/static/list/borer_names = list( + "Primary", "Secondary", "Tertiary", "Quaternary", "Quinary", "Senary", + "Septenary", "Octonary", "Novenary", "Decenary", "Undenary", "Duodenary", + ) + var/talk_inside_host = FALSE // So that borers don't accidentally give themselves away on a botched message + var/used_dominate + var/attempting_to_dominate = FALSE // To prevent people from spam opening the Dominate Victim input + + var/enzymes = 10 // Enzymes used for chemical injection. + var/max_enzymes = 500 + var/contaminant = 0 //Contaminant builds up on enzyme usage, roughly proportionate to cost of use. + var/max_contaminant = 120 //Decreases through hibernation or reproduction. + var/hibernating = FALSE //Usable inside a host, but not when controlling. Allows clearing of impurities. + + var/mob/living/carbon/human/host // Human host for the brain worm. + var/truename // Name used for brainworm-speak. + var/mob/living/captive_brain/host_brain // Used for swapping control of the body back and forth. + var/controlling // Used in human death check. + var/docile = FALSE // Anti-Parasite or Anti-Enzyme chemicals can stop borers from acting. + var/bonding = FALSE + var/leaving = FALSE + var/hiding = FALSE + var/can_reproduce = FALSE // Locked to manual override to prevent things getting out of hand. + var/infect_hunter = FALSE // Locked for normal use. + + var/list/datum/reagent/synthesized_chems + + /// All of these surely have a better way of being handled. + var/datum/action/innate/borer/talk_to_host/action_talk_to_host = new + var/datum/action/innate/borer/infest_host/action_infest_host = new + var/datum/action/innate/borer/toggle_hide/action_toggle_hide = new + var/datum/action/innate/borer/talk_to_borer/action_talk_to_borer = new + var/datum/action/innate/borer/talk_to_brain/action_talk_to_brain = new + var/datum/action/innate/borer/take_control/action_take_control = new + var/datum/action/innate/borer/give_back_control/action_give_back_control = new + var/datum/action/innate/borer/leave_body/action_leave_body = new + var/datum/action/innate/borer/make_chems/action_make_chems = new + var/datum/action/innate/borer/make_larvae/action_make_larvae = new + var/datum/action/innate/borer/freeze_victim/action_freeze_victim = new + var/datum/action/innate/borer/torment/action_torment = new + var/datum/action/innate/borer/scan_chems/action_scan_chems = new + var/datum/action/innate/borer/hibernate/action_hibernate = new + +/mob/living/carbon/cortical_borer/initialize_pass_flags(datum/pass_flags_container/PF) + ..() + if (PF) + PF.flags_pass = PASS_MOB_THRU|PASS_FLAGS_CRAWLER + PF.flags_can_pass_all = PASS_ALL^PASS_OVER_THROW_ITEM + +/mob/living/carbon/cortical_borer/proc/summon() + var/datum/emergency_call/custom/em_call = new() + em_call.name = "Cortical Borer" + em_call.mob_max = 1 + em_call.players_to_offer = list(src) + em_call.owner = null + em_call.ert_message = "A new Cortical Borer has been birthed!" + em_call.objectives = "Create enjoyable Roleplay. Do not kill your host. Do not take control unless granted permission or directed to by admins. Hivemind is :0 (That's Zero, not Oscar)" + + em_call.activate(announce = FALSE) + + message_admins("A new Cortical Borer has spawned at [get_area(loc)]") + +/mob/living/carbon/cortical_borer/New(atom/newloc, gen=1, ERT = FALSE, reproduction = 0) + ..(newloc) + generation = gen + add_language(LANGUAGE_BORER) + var/mob_number = rand(1000,9999) + real_name = "Cortical Borer [mob_number]" + truename = "[borer_names[min(generation, borer_names.len)]] [mob_number]" + can_reproduce = reproduction + GrantBorerActions() + GiveBorerHUD() + if((!is_admin_level(z)) && ERT) + summon() + +/mob/living/carbon/cortical_borer/death() + var/datum/language/corticalborer/c_link = GLOB.all_languages[LANGUAGE_BORER] + c_link.broadcast(src, null, null, TRUE) + . = ..() + +/mob/living/carbon/cortical_borer/update_icons() + if(stat == DEAD) + icon_state = "Borer Dead" + + else if(lying) + if((resting || sleeping) && (!knocked_down && !knocked_out && health > 0)) + icon_state = "Borer Resting" + else + icon_state = "Borer Stunned" + else + icon_state = "Borer" + +/mob/living/carbon/cortical_borer/proc/GiveBorerHUD() + var/datum/mob_hud/H = huds[MOB_HUD_MEDICAL_OBSERVER] + H.add_hud_to(src) + +/mob/living/carbon/cortical_borer/can_ventcrawl() + return TRUE + +/mob/living/carbon/cortical_borer/initialize_pass_flags(datum/pass_flags_container/PF) + ..() + if (PF) + PF.flags_pass = PASS_MOB_THRU|PASS_FLAGS_CRAWLER + PF.flags_can_pass_all = PASS_ALL^PASS_OVER_THROW_ITEM + +/mob/living/carbon/cortical_borer/get_status_tab_items() + . = ..() + + var/CR = "Yes" + if(!can_reproduce) + CR = "Forbidden" + else if((enzymes < BORER_LARVAE_COST)) + CR = "No" + + . += "" + . += "Borer:" + . += "Name: [truename]" + . += "Can Reproduce: [CR]" + . += "Enzymes: [round(enzymes)]/[round(max_enzymes)]" + . += "Contaminant: [round(contaminant)]/[round(max_contaminant)]" + if(host) + . += "" + . += "Host Brain Damage: [host.brainloss]/100" + +/mob/living/carbon/cortical_borer/say(message)//I need to parse the message properly so it doesn't look stupid + var/datum/language/parsed_language = parse_language(message) + var/new_message = message + if(parsed_language) + new_message = copytext(message, 3) + if(istype(parsed_language, /datum/language/corticalborer)) + parsed_language.broadcast(src, new_message) + return + if(hibernating) + to_chat(src, SPAN_WARNING("You cannot speak aloud while hibernating!")) + return + if(loc == host && !talk_inside_host) + to_chat(src, SPAN_WARNING("You've disabled audible speech while inside a host! Re-enable it under the borer tab, or stick to borer communications.")) + return + . = ..() + + +/mob/living/carbon/cortical_borer/Life(delta_time) + ..() + if(host) + if(!stat && host.stat != DEAD) + if(((host.chem_effect_flags & CHEM_EFFECT_ANTI_PARASITE) && !host.reagents.has_reagent("benzyme")) || host.reagents.has_reagent("bcure")) + if(!docile) + if(controlling) + to_chat(host, SPAN_XENODANGER("You feel the soporific flow of a chemical in your host's blood, lulling you into docility.")) + else + to_chat(src, SPAN_XENODANGER("You feel the soporific flow of a chemical in your host's blood, lulling you into docility.")) + docile = TRUE + else + if(docile) + if(controlling) + to_chat(host, SPAN_XENONOTICE("You shake off your lethargy as the chemical leaves your host's blood.")) + else + to_chat(src, SPAN_XENONOTICE("You shake off your lethargy as the chemical leaves your host's blood.")) + docile = FALSE + if(!hibernating && (enzymes < max_enzymes)) + enzymes++ + if(contaminant > 0) + if(hibernating) + contaminant = max(contaminant -= 1, 0) + else + contaminant = max(contaminant -= 0.1, 0) + if(controlling) + if(docile) + to_chat(host, SPAN_XENOWARNING("You are feeling far too docile to continue controlling your host...")) + host.release_control() + return + else + if(contaminant > 0) + if(!luminosity) + SetLuminosity(2) + contaminant = max(contaminant - 0.3, 0) + else + SetLuminosity(0) + + update_canmove() + update_icons() +/datum/action/innate/borer + icon_file = 'icons/mob/hud/actions_borer.dmi' + +/datum/action/innate/borer/talk_to_host + name = "Converse with Host" + action_icon_state = "borer_whisper" + +/datum/action/innate/borer/talk_to_host/action_activate() + var/mob/living/carbon/cortical_borer/B = owner + B.Communicate() + +/datum/action/innate/borer/infest_host + name = "Infest" + action_icon_state = "borer_infest" + +/datum/action/innate/borer/infest_host/action_activate() + var/mob/living/carbon/cortical_borer/B = owner + B.infest() + +/datum/action/innate/borer/toggle_hide + name = "Toggle Hide" + action_icon_state = "borer_hiding_0" + +/datum/action/innate/borer/toggle_hide/action_activate() + var/mob/living/carbon/cortical_borer/B = owner + B.hide_borer() + + button.overlays.Cut() + button.overlays += image('icons/mob/hud/actions_borer.dmi', button, "borer_hiding_[B.hiding]") + +/datum/action/innate/borer/talk_to_borer + name = "Converse with Borer" + action_icon_state = "borer_whisper" + +/datum/action/innate/borer/talk_to_borer/action_activate() + var/mob/living/carbon/cortical_borer/B = owner.has_brain_worms() + B.host = owner + B.host.borer_comm() + +/datum/action/innate/borer/talk_to_brain + name = "Converse with Trapped Mind" + action_icon_state = "borer_whisper" + +/datum/action/innate/borer/talk_to_brain/action_activate() + var/mob/living/carbon/cortical_borer/B = owner.has_brain_worms() + B.host = owner + B.host.trapped_mind_comm() + +/datum/action/innate/borer/take_control + name = "Assume Control" + action_icon_state = "borer_control" + +/datum/action/innate/borer/take_control/action_activate() + var/mob/living/carbon/cortical_borer/B = owner + if(B.hibernating) + to_chat(B, SPAN_WARNING("You cannot do that while hibernating!")) + return + B.bond_brain() + +/datum/action/innate/borer/give_back_control + name = "Release Control" + action_icon_state = "borer_leave" + +/datum/action/innate/borer/give_back_control/action_activate() + var/mob/living/carbon/cortical_borer/B = owner.has_brain_worms() + B.host = owner + B.host.release_control() + +/datum/action/innate/borer/leave_body + name = "Leave Host" + action_icon_state = "borer_leave" + +/datum/action/innate/borer/leave_body/action_activate() + var/mob/living/carbon/cortical_borer/B = owner + if(B.hibernating) + to_chat(B, SPAN_WARNING("You cannot do that while hibernating!")) + return + B.release_host() + +/datum/action/innate/borer/make_chems + name = "Secrete Chemicals" + action_icon_state = "borer_chems" + +/datum/action/innate/borer/make_chems/action_activate() + var/mob/living/carbon/cortical_borer/B = owner + if(B.hibernating) + to_chat(B, SPAN_WARNING("You cannot do that while hibernating!")) + return + B.secrete_chemicals() + +/datum/action/innate/borer/scan_chems + name = "Scan Chemicals" + action_icon_state = "borer_scan" + +/datum/action/innate/borer/scan_chems/action_activate() + var/mob/living/carbon/cortical_borer/B = owner + if(B.hibernating) + to_chat(B, SPAN_WARNING("You cannot do that while hibernating!")) + return + borerscan(B, B.host) + +/datum/action/innate/borer/make_larvae + name = "Reproduce" + action_icon_state = "borer_reproduce" + +/datum/action/innate/borer/make_larvae/action_activate() + var/mob/living/carbon/cortical_borer/B = owner.has_brain_worms() + B.host = owner + B.host.spawn_larvae() + +/datum/action/innate/borer/freeze_victim + name = "Dominate Victim" + action_icon_state = "borer_stun" + +/datum/action/innate/borer/freeze_victim/action_activate() + var/mob/living/carbon/cortical_borer/B = owner + B.dominate_victim() + +/datum/action/innate/borer/torment + name = "Torment Host" + action_icon_state = "borer_torment" + +/datum/action/innate/borer/torment/action_activate() + var/mob/living/carbon/cortical_borer/B = owner.has_brain_worms() + B.host = owner + B.host.punish_host() + +/datum/action/innate/borer/hibernate + name = "Toggle Hibernation" + action_icon_state = "borer_sleeping_0" + +/datum/action/innate/borer/hibernate/action_activate() + var/mob/living/carbon/cortical_borer/B = owner + B.hibernate() + button.overlays.Cut() + button.overlays += image('icons/mob/hud/actions_borer.dmi', button, "borer_sleeping_[B.hibernating]") + +/mob/living/carbon/cortical_borer/MouseDrop(atom/over_object) + if(!CAN_PICKUP(usr, src)) + return ..() + var/mob/living/carbon/H = over_object + if(!istype(H) || !Adjacent(H) || H != usr) return ..() + + if(H.a_intent == INTENT_HELP) + get_scooped(H) + return + else + return ..() + +/mob/living/carbon/cortical_borer/get_scooped(mob/living/carbon/grabber) + if(stat != DEAD) + to_chat(grabber, SPAN_WARNING("You probably shouldn't pick that thing up while it still lives.")) + return + ..() diff --git a/code/modules/borer/borer_chemicals.dm b/code/modules/borer/borer_chemicals.dm new file mode 100644 index 000000000000..ddb4b775ff5f --- /dev/null +++ b/code/modules/borer/borer_chemicals.dm @@ -0,0 +1,137 @@ +/datum/borer_chem + var/chem_name = "Unset" + /// Chemical identifier, used in the proc to create it. + var/chem_id = "unset" + var/desc = "This is a chemical" + /// Synthetic chemicals. + var/impure = TRUE + var/cost = 50 + var/quantity = 10 + + var/category = BORER_CAT_HEAL + + +//Medical Chems +/datum/borer_chem/human/tricordrazine + chem_name = "Tricordrazine" + chem_id = "tricordrazine" + desc = "Can be used to treat a wide range of injuries." + +/datum/borer_chem/human/anti_toxin + chem_name = "Dylovene" + chem_id = "anti_toxin" + desc = "General use chemical that neutralizes most toxins in the bloodstream. Can be used as a mild anti-hallucinogen and to reduce tiredness." + +/datum/borer_chem/human/dexalin + chem_name = "Dexalin" + chem_id = "dexalin" + desc = "Feeds oxygen directly into red bloodcells. Used as an antidote to lexorin poisoning." + +/datum/borer_chem/human/peridaxon + chem_name = "Peridaxon" + chem_id = "peridaxon" + desc = "Prevents symptoms caused by damaged internal organs while in the bloodstream, but does not fix the organ damage. Overdosing will cause internal tissue damage." + +/datum/borer_chem/human/imidazoline + chem_name = "Imidazoline" + chem_id = "imidazoline" + desc = "Used for treating non-genetic eye trauma." + cost = 90 + quantity = 5 + +/datum/borer_chem/human/alkysine + chem_name = "Alkysine" + chem_id = "alkysine" + desc = "Small amounts can repair extensive brain trauma. Overdosing on alkysine is extremely toxic." + cost = 80 + quantity = 5 + +/datum/borer_chem/human/quickclot + chem_name = "Quickclot" + chem_id = "quickclot" + desc = "Vastly improves the blood's natural ability to coagulate and stop bleeding. Overdosing will result in severe tissue damage." + cost = 90 + quantity = 5 + +/datum/borer_chem/human/iron + chem_id = "Iron" + chem_id = "iron" + desc = "Promotes production of blood. Overdosing on iron is extremely toxic." + cost = 20 + impure = FALSE + +/datum/borer_chem/human/oxycodone + chem_id = "Oxycodone" + chem_id = "oxycodone" + desc = "An extremely strong painkiller." + cost = 120 + quantity = 5 + +/datum/borer_chem/human/epinephrine + chem_name = "Epinephrine" + chem_id = "adrenaline" + desc = "Useful for restarting the heart. Overdosing may stress the heart and cause tissue damage." + + +//"Motivation" Chems +/datum/borer_chem/human/stimulant_brain + chem_name = "Neurological Stimulant" + chem_id = "brain_stimulant" + desc = "A powerful stimulant that enhances brain function. Lethal in high doses. Lasts one minute per unit." + cost = 300 + quantity = 1 + category = BORER_CAT_STIM + +/datum/borer_chem/human/stimulant_muscle + chem_name = "Musculature Stimulant" + chem_id = "speed_stimulant" + desc = "A powerful stimulant that enhances musculature. Lethal in high doses. Lasts one minute per unit." + cost = 300 + quantity = 1 + category = BORER_CAT_STIM + +/datum/borer_chem/human/neurotoxin + chem_name = "Neurotoxin" + chem_id = PLASMA_NEUROTOXIN + desc = "A potent and hallucinagenic neurotoxin." + cost = 125 + quantity = 1 + category = BORER_CAT_PUNISH + +/datum/borer_chem/human/antineurotoxin + chem_name = "Anti-Neurotoxin" + chem_id = "antineurotoxin" + desc = "A bioagent that counteracts neurotoxins." + cost = 100 + category = BORER_CAT_PUNISH + + + +//Yautja chemicals +/datum/borer_chem/yautja/thwei + chem_name = "Thwei" + chem_id = "thwei" + desc = "A synthetic cocktail of chemicals used to accelerate healing in the Yautja species. It has no effect on humans." + cost = 150 + quantity = 20 + + + +//Anti-Sugar +/datum/borer_chem/human/enzyme + chem_name = "Cortical Enzyme" + chem_id = "benzyme" + desc = "An enzyme focused on consuming chemicals in the bloodstream. Helps fight addictions. This will work as a preventative measure against anti-parasite drugs so long as it is in the bloodstream. Can cause brain damage." + cost = 150 + quantity = 8 + category = BORER_CAT_SELF + impure = FALSE + +/datum/borer_chem/yautja/enzyme + chem_name = "Cortical Enzyme" + chem_id = "benzyme" + desc = "An enzyme focused on consuming chemicals in the bloodstream. Helps fight addictions. This will work as a preventative measure against anti-parasite drugs so long as it is in the bloodstream. Can cause brain damage." + cost = 150 + quantity = 6 + category = BORER_CAT_SELF + impure = FALSE diff --git a/code/modules/borer/borer_html.dm b/code/modules/borer/borer_html.dm new file mode 100644 index 000000000000..f11884e8c435 --- /dev/null +++ b/code/modules/borer/borer_html.dm @@ -0,0 +1,69 @@ +/mob/living/carbon/cortical_borer/proc/get_html_template(content) + var/html = {" + + + Biochemical Synthesizer + + + + + + +
+ [content] +
"} + return html diff --git a/code/modules/borer/borer_procs.dm b/code/modules/borer/borer_procs.dm new file mode 100644 index 000000000000..9db7f1542c14 --- /dev/null +++ b/code/modules/borer/borer_procs.dm @@ -0,0 +1,720 @@ +//############# Physical Interaction Procs ############# +/mob/living/carbon/cortical_borer/UnarmedAttack(atom/A) + A.attack_borer(src) + +/atom/proc/attack_borer(mob/living/carbon/cortical_borer/user) + return + + +//Brainslug scans the reagents in a target's bloodstream. +/mob/living/carbon/human/attack_borer(mob/M) + borerscan(M, src) + +/proc/borerscan(mob/living/user, mob/living/M) + if(ishuman(M)) + var/mob/living/carbon/human/H = M + if(H.reagents) + if(H.reagents.reagent_list.len) + to_chat(user, SPAN_XENONOTICE("Subject contains the following reagents:")) + for(var/datum/reagent/R in H.reagents.reagent_list) + to_chat(user, "[R.overdose != 0 && R.volume >= R.overdose && !(R.flags & REAGENT_CANNOT_OVERDOSE) ? SPAN_WARNING("OD: ") : ""] [round(R.volume, 1)]u [R.name]") + else + to_chat(user, SPAN_XENONOTICE("Subject contains no reagents.")) + +//Brainslug scuttles under a door, same code as used by xeno larva. +/obj/structure/machinery/door/airlock/attack_borer(mob/living/carbon/cortical_borer/M) + M.scuttle(src) + +/mob/living/carbon/cortical_borer/proc/scuttle(obj/structure/S) + var/move_dir = get_dir(src, loc) + for(var/atom/movable/AM in get_turf(S)) + if(AM != S && AM.density && AM.BlockedPassDirs(src, move_dir)) + to_chat(src, SPAN_WARNING("\The [AM] prevents you from squeezing under \the [S]!")) + return + // Is it an airlock? + if(istype(S, /obj/structure/machinery/door/airlock)) + var/obj/structure/machinery/door/airlock/A = S + if(A.locked || A.welded) //Can't pass through airlocks that have been bolted down or welded + to_chat(src, SPAN_WARNING("\The [A] is locked down tight. You can't squeeze underneath!")) + return + visible_message(SPAN_WARNING("\The [src] scuttles underneath \the [S]!"), \ + SPAN_WARNING("You squeeze and scuttle underneath \the [S]."), null, 5) + forceMove(S.loc) + +//############# Action Give/Take Procs ############# +/mob/living/carbon/cortical_borer/proc/GrantBorerActions() + action_infest_host.give_to(src) + action_toggle_hide.give_to(src) + action_freeze_victim.give_to(src) + +/mob/living/carbon/cortical_borer/proc/RemoveBorerActions() + action_infest_host.remove_from(src) + action_toggle_hide.remove_from(src) + action_freeze_victim.remove_from(src) + +/mob/living/carbon/cortical_borer/proc/GrantInfestActions() + action_talk_to_host.give_to(src) + action_leave_body.give_to(src) + action_take_control.give_to(src) + action_make_chems.give_to(src) + action_scan_chems.give_to(src) + action_hibernate.give_to(src) + +/mob/living/carbon/cortical_borer/proc/RemoveInfestActions() + action_talk_to_host.remove_from(src) + action_take_control.remove_from(src) + action_leave_body.remove_from(src) + action_make_chems.remove_from(src) + action_scan_chems.remove_from(src) + action_hibernate.remove_from(src) + +/mob/living/carbon/cortical_borer/proc/GrantControlActions() + action_talk_to_brain.give_to(host) + action_give_back_control.give_to(host) + action_make_larvae.give_to(host) + action_torment.give_to(host) + +/mob/living/carbon/cortical_borer/proc/RemoveControlActions() + action_talk_to_brain.remove_from(host) + action_make_larvae.remove_from(host) + action_give_back_control.remove_from(host) + action_torment.remove_from(host) + +/mob/living/carbon/cortical_borer/proc/hibernate() + hibernating = !hibernating + if(hibernating) + to_chat(src, SPAN_XENONOTICE("You are now hibernating! Your body will dissolve impurities built up from the creation of chemicals, however your enzyme reserves will not replenish. You cannot act beyond communicating whilst in hibernation.")) + sleeping = 2 + else + sleeping = 0 + to_chat(src, SPAN_XENOWARNING("You are no longer hibernating. You have access to your full capabilities once more.")) + +//############# Control Related Procs ############# +//Check for brain worms in head. +/mob/proc/has_brain_worms() + return FALSE + +/mob/living/carbon/has_brain_worms() + if(borer) + return borer + else + return FALSE + + +//Brainslug infests a target +/mob/living/carbon/cortical_borer/verb/infest() + set category = "Borer" + set name = "Infest" + set desc = "Infest a suitable humanoid host." + + if(host) + to_chat(src, "You are already within a host.") + return + if(stat) + to_chat(src, "You cannot infest a target in your current state.") + return + var/list/choices = list() + for(var/mob/living/carbon/human/H in view(1,src)) + var/obj/limb/head/head = H.get_limb("head") + if(head.status & LIMB_ROBOT) + continue + if(isspeciesyautja(H) && !infect_hunter) + continue + if(H.stat != DEAD && Adjacent(H) && !H.has_brain_worms()) + choices += H + var/mob/living/carbon/human/target = tgui_input_list(src, "Who do you wish to infest?", "Targets", choices) + if(!target || !src) + return + if(!Adjacent(target)) + return + if(target.has_brain_worms()) + to_chat(src, SPAN_WARNING("You cannot infest someone who is already infested!")) + return + if(is_mob_incapacitated()) + return + to_chat(src, SPAN_NOTICE("You slither up [target] and begin probing at their ear canal...")) + if(!do_after(src, 50, INTERRUPT_ALL_OUT_OF_RANGE, BUSY_ICON_HOSTILE, target)) + to_chat(src, SPAN_WARNING("As [target] moves away, you are dislodged and fall to the ground.")) + return + if(!target || !src) + return + if(stat) + to_chat(src, SPAN_XENOWARNING("You cannot infest a target in your current state.")) + return + if(target.stat == DEAD) + to_chat(src, SPAN_WARNING("That is not an appropriate target.")) + return + if(target in view(1, src)) + to_chat(src, SPAN_NOTICE("You wiggle into [target]'s ear.")) + /* + if(!target.stat) + to_chat(target, "Something disgusting and slimy wiggles into your ear!") + */ // Let's see how stealthborers work out + perform_infestation(target) + return + else + to_chat(src, "They are no longer in range!") + return + +/mob/living/carbon/cortical_borer/proc/perform_infestation(mob/living/carbon/target) + if(!target) + return + if(target.has_brain_worms()) + to_chat(src, SPAN_XENOWARNING("[target] is already infested!")) + return + host = target + log_interact(src, host, "Borer: [key_name(src)] Infested [key_name(host)]") + target.borer = src + forceMove(target) + host.status_flags |= PASSEMOTES + host.verbs += /mob/living/proc/borer_comm + RemoveBorerActions() + GrantInfestActions() + + +//Brainslug abandons the host +/mob/living/carbon/cortical_borer/verb/release_host() + set category = "Borer" + set name = "Release Host" + set desc = "Slither out of your host." + if(!host) + to_chat(src, SPAN_XENOWARNING("You are not inside a host body.")) + return + if(stat) + to_chat(src, SPAN_XENOWARNING("You cannot leave your host in your current state.")) + return + if(docile) + to_chat(src, SPAN_XENOWARNING("You are feeling far too docile to do that.")) + return + if(!host || !src) + return + if(leaving) + leaving = FALSE + to_chat(src, SPAN_XENOWARNING("You decide against leaving your host.")) + return + to_chat(src, SPAN_XENOHIGHDANGER("You begin disconnecting from [host]'s synapses and prodding at their internal ear canal.")) + leaving = TRUE + addtimer(CALLBACK(src, PROC_REF(let_go)), 200) + +/mob/living/carbon/cortical_borer/proc/let_go() + if(!host || !src || QDELETED(host) || QDELETED(src)) + return + if(!leaving) + return + if(controlling) + return + if(stat) + to_chat(src, SPAN_XENOWARNING("You cannot release a target in your current state.")) + return + + to_chat(src, SPAN_XENOHIGHDANGER("You wiggle out of [host]'s ear and plop to the ground.")) + + leaving = FALSE + leave_host() + +/mob/living/carbon/cortical_borer/proc/leave_host() + if(!host) + return + if(controlling) + detach() + GrantBorerActions() + RemoveInfestActions() + forceMove(get_turf(host)) + + host.reset_view(null) + + var/mob/living/carbon/H = host + H.borer = null + H.verbs -= /mob/living/proc/borer_comm + action_talk_to_borer.remove_from(host) + H.status_flags &= ~PASSEMOTES + host = null + return + + +//Brainslug takes control of the body +/mob/living/carbon/cortical_borer/verb/bond_brain() + set category = "Borer" + set name = "Assume Control" + set desc = "Fully connect to the brain of your host." + + if(!host) + to_chat(src, SPAN_XENOWARNING("You are not inside a host body.")) + return + + if(host.stat == DEAD) + to_chat(src, SPAN_XENODANGER("This host is in no condition to be controlled.")) + return + + if(stat) + to_chat(src, SPAN_XENOWARNING("You cannot do that in your current state.")) + return + + if(docile) + to_chat(src, SPAN_XENOWARNING("You are feeling far too docile to do that.")) + return + + if(bonding) + bonding = FALSE + to_chat(src, SPAN_XENOWARNING("You stop attempting to take control of your host.")) + return + + to_chat(src, "You begin delicately adjusting your connection to the host brain...") + + if(QDELETED(src) || QDELETED(host)) + return + + bonding = TRUE + + var/delay = 300+(host.getBrainLoss()*5) + addtimer(CALLBACK(src, PROC_REF(assume_control)), delay) + +/mob/living/carbon/cortical_borer/proc/assume_control() + if(!host || !src || controlling) + return + if(!bonding) + return + if(docile) + to_chat(src, SPAN_XENOWARNING("You are feeling far too docile to do that.")) + return + else + to_chat(src, SPAN_XENOHIGHDANGER("You plunge your probosci deep into the cortex of the host brain, interfacing directly with their nervous system.")) + to_chat(host, SPAN_HIGHDANGER("You feel a strange shifting sensation behind your eyes as an alien consciousness displaces yours.")) + to_chat(host, SPAN_NOTICE("You can [SPAN_BOLD("resist")] this consciousness, but be warned you may suffer some degree of brain damage in the process!")) + var/borer_key = src.key + log_interact(src, host, "Borer: [key_name(src)] Assumed control of [key_name(host)]") + // host -> brain + var/h2b_id = host.computer_id + var/h2b_ip= host.lastKnownIP + host.computer_id = null + host.lastKnownIP = null + + qdel(host_brain) + host_brain = new(src) + + host_brain.ckey = host.ckey + + host_brain.name = host.name + + if(!host_brain.computer_id) + host_brain.computer_id = h2b_id + + if(!host_brain.lastKnownIP) + host_brain.lastKnownIP = h2b_ip + + // self -> host + var/s2h_id = src.computer_id + var/s2h_ip= src.lastKnownIP + src.computer_id = null + src.lastKnownIP = null + + host.ckey = src.ckey + + if(!host.computer_id) + host.computer_id = s2h_id + + if(!host.lastKnownIP) + host.lastKnownIP = s2h_ip + + bonding = FALSE + controlling = TRUE + + host.verbs += /mob/living/carbon/proc/release_control + host.verbs += /mob/living/carbon/proc/punish_host + host.verbs += /mob/living/carbon/proc/spawn_larvae + host.verbs -= /mob/living/proc/borer_comm + host.verbs += /mob/living/proc/trapped_mind_comm + + GrantControlActions() + action_talk_to_borer.remove_from(host) + host.med_hud_set_status() + + if(src && !src.key) + src.key = "@[borer_key]" + return + +//Captive mind reclaims their body. +/mob/living/captive_brain/proc/return_control(mob/living/carbon/cortical_borer/B) + if(!B || !B.controlling) + return + B.host.adjustBrainLoss(rand(5,10)) + to_chat(src, SPAN_DANGER("With an immense exertion of will, you regain control of your body!")) + to_chat(B.host, SPAN_XENODANGER("You feel control of the host brain ripped from your grasp, and retract your probosci before the wild neural impulses can damage you.")) + B.detach() + +///Brain slug proc for voluntary removal of control. +/mob/living/carbon/proc/release_control() + + set category = "Borer" + set name = "Release Control" + set desc = "Release control of your host's body." + + var/mob/living/carbon/cortical_borer/B = has_brain_worms() + + if(B && B.host_brain) + to_chat(src, SPAN_XENONOTICE("You withdraw your probosci, releasing control of [B.host_brain]")) + + B.detach() + + else + log_debug(EXCEPTION("Missing borer or missing host brain upon borer release."), src) + +/mob/living/carbon/cortical_borer/proc/detach() + if(!host || !controlling) + return + + controlling = FALSE + reset_view(null) + + host.verbs -= /mob/living/carbon/proc/release_control + host.verbs -= /mob/living/carbon/proc/punish_host + host.verbs -= /mob/living/carbon/proc/spawn_larvae + host.verbs += /mob/living/proc/borer_comm + host.verbs -= /mob/living/proc/trapped_mind_comm + + RemoveControlActions() + action_talk_to_borer.give_to(host) + host.med_hud_set_status() + sleeping = 0 + if(host_brain) + log_interact(host, src, "Borer: [key_name(host)] Took control back") + // host -> self + var/h2s_id = host.computer_id + var/h2s_ip= host.lastKnownIP + host.computer_id = null + host.lastKnownIP = null + src.ckey = host.ckey + if(!src.computer_id) + src.computer_id = h2s_id + if(!host_brain.lastKnownIP) + src.lastKnownIP = h2s_ip + + // brain -> host + var/b2h_id = host_brain.computer_id + var/b2h_ip = host_brain.lastKnownIP + host_brain.computer_id = null + host_brain.lastKnownIP = null + host.ckey = host_brain.ckey + + if(!host.computer_id) + host.computer_id = b2h_id + if(!host.lastKnownIP) + host.lastKnownIP = b2h_ip + qdel(host_brain) + return + +//Host Has died +/mob/living/carbon/cortical_borer/proc/host_death(perma = FALSE) + if(!(host && loc == host)) + log_debug("Borer ([key_name(src)]) called host_death without being inside a host!") + return + if(controlling) + detach() + to_chat(src, SPAN_HIGHDANGER("You release your proboscis and flee as the psychic shock of your host's death washes over you!")) + if(perma) + to_chat(src, SPAN_HIGHDANGER("You flee your host in anguish!")) + leave_host() + +//############# External Ability Procs ############# +/mob/living/carbon/cortical_borer/verb/hide_borer() + set category = "Borer" + set name = "Hide" + set desc = "Become invisible to the common eye." + + if(host) + to_chat(usr, SPAN_XENOWARNING("You cannot do this while you're inside a host.")) + return + + if(stat != CONSCIOUS) + return + + if(!hiding) + layer = TURF_LAYER+0.2 + to_chat(src, SPAN_XENONOTICE("You are now hiding.")) + hiding = TRUE + else + layer = MOB_LAYER + to_chat(src, SPAN_XENONOTICE("You stop hiding.")) + hiding = FALSE + +/mob/living/carbon/cortical_borer/verb/dominate_victim() + set category = "Borer" + set name = "Dominate Victim" + set desc = "Freeze the limbs of a potential host with supernatural fear." + + if(world.time - used_dominate < 150) + to_chat(src, SPAN_XENOWARNING("You cannot use that ability again so soon.")) + return + if(host) + to_chat(src, SPAN_XENOWARNING("You cannot do that from within a host body.")) + return + if(stat) + to_chat(src, SPAN_XENOWARNING("You cannot do that in your current state.")) + return + if(attempting_to_dominate) + to_chat(src, SPAN_XENOWARNING("You're already targeting someone!")) + return + var/list/choices = list() + for(var/mob/living/carbon/C in view(3,src)) + if((C.stat != DEAD) && !(issynth(C))) + choices += C + if(world.time - used_dominate < 300) + to_chat(src, SPAN_XENOWARNING("You cannot use that ability again so soon.")) + return + attempting_to_dominate = TRUE + var/mob/living/carbon/M = tgui_input_list(src, "Who do you wish to dominate?", "Targets", choices) + if(!M) + attempting_to_dominate = FALSE + return + if(!src) //different statement to avoid a runtime since if the source is deleted then attempting_to_dominate would also be deleted + return + if(M.has_brain_worms()) + to_chat(src, SPAN_XENOWARNING("You cannot dominate someone who is already infested!")) + attempting_to_dominate = FALSE + return + if(is_mob_incapacitated()) + attempting_to_dominate = FALSE + return + if(get_dist(src, M) > 5) //to avoid people remotely doing from across the map etc, 7 is the default view range + to_chat(src, SPAN_XENOWARNING("You're too far away!")) + attempting_to_dominate = FALSE + return + to_chat(src, SPAN_XENONOTICE("You begin to focus your psychic lance on [M], this will take a few seconds.")) + if(!do_after(src, 30, INTERRUPT_OUT_OF_RANGE, NO_BUSY_ICON, M, max_dist = 5)) + to_chat(src, SPAN_XENODANGER("You are out of position to dominate [M], get closer!")) + attempting_to_dominate = FALSE + return + + to_chat(src, SPAN_XENOWARNING("You focus your psychic lance on [M] and freeze their limbs with a wave of terrible dread.")) + to_chat(M, SPAN_WARNING("You feel a creeping, horrible sense of dread come over you, freezing your limbs and setting your heart racing.")) + M.KnockDown(3) + used_dominate = world.time + attempting_to_dominate = FALSE + + +//############# Internal Abiity Procs ############# +/mob/living/carbon/proc/punish_host() + set category = "Borer" + set name = "Torment Host" + set desc = "Punish your host with agony." + + var/mob/living/carbon/cortical_borer/B = has_brain_worms() + + if(!B) + return + + if(B.host_brain) + to_chat(src, SPAN_XENONOTICE("You send a punishing spike of psychic agony lancing into your host's brain.")) + to_chat(B.host_brain, SPAN_XENOHIGHDANGER("Horrific, burning agony lances through you, ripping a soundless scream from your trapped mind!")) + + +/mob/living/carbon/proc/spawn_larvae() + set category = "Borer" + set name = "Reproduce" + set desc = "Spawn a new borer." + + var/mob/living/carbon/cortical_borer/B = has_brain_worms() + + if(!B) + return + if(B.can_reproduce) + if(B.enzymes >= BORER_LARVAE_COST) + to_chat(src, SPAN_XENOWARNING("Your host twitches and quivers as you rapdly excrete a larva from your sluglike body.")) + visible_message(SPAN_WARNING("[src] heaves violently, expelling a rush of vomit and a wriggling, sluglike creature!")) + B.enzymes = 0 + var/turf/T = get_turf(src) + T.add_vomit_floor() + B.contaminant = 0 + var/repro = max(B.can_reproduce - 1, 0) + new /mob/living/carbon/cortical_borer(T, B.generation + 1, TRUE, repro) + else + to_chat(src, SPAN_XENONOTICE("You need at least [BORER_LARVAE_COST] enzymes to reproduce!")) + return + else + to_chat(src, SPAN_XENOWARNING("You are not allowed to reproduce!")) + + + +/mob/living/carbon/cortical_borer/verb/secrete_chemicals() + set category = "Borer" + set name = "Secrete Chemicals" + set desc = "Push some chemicals into your host's bloodstream." + + if(!host) + to_chat(src, SPAN_XENOWARNING("You are not inside a host body.")) + return + + if(stat) + to_chat(src, SPAN_XENOWARNING("You cannot secrete chemicals in your current state.")) + + if(docile) + to_chat(src, SPAN_XENOWARNING("You are feeling far too docile to do that.")) + return + + var/content = "" + + content += "" + + if(ishuman(host)) + if(isspeciesyautja(host)) + for(var/datum in subtypesof(/datum/borer_chem/yautja)) + var/datum/borer_chem/C = datum + var/chem = initial(C.chem_id) + var/datum/reagent/R = chemical_reagents_list[chem] + if(R) + content += "" + else + for(var/datum in subtypesof(/datum/borer_chem/human)) + var/datum/borer_chem/C = datum + var/chem = initial(C.chem_id) + var/datum/reagent/R = chemical_reagents_list[chem] + if(R) + content += "" + + content += "
[initial(C.quantity)] units of [R.name] ([initial(C.cost)] Enzymes)

[initial(C.desc)]

[initial(C.quantity)] units of [R.name] ([initial(C.cost)] Enzymes)

[initial(C.desc)]

" + + var/html = get_html_template(content) + + usr << browse(null, "window=ViewBorer\ref[src]Chems;size=585x400") + usr << browse(html, "window=ViewBorer\ref[src]Chems;size=585x400") + + return + + + +//############# Communication Procs ############# +/mob/living/carbon/cortical_borer/verb/Communicate() + set category = "Borer" + set name = "Converse with Host" + set desc = "Send a silent message to your host." + + if(!host) + to_chat(src, "You do not have a host to communicate with!") + return + + if(host.stat == DEAD) + to_chat(src, SPAN_XENODANGER("Not even you can commune with the dead.")) + return + + if(stat) + to_chat(src, "You cannot do that in your current state.") + return + + var/input = stripped_input(src, "Please enter a message to tell your host.", "Borer", "") + if(!input) + return + + if(src && !QDELETED(src) && !QDELETED(host)) + var/say_string = (docile) ? "slurs" :"states" + if(host) + to_chat(host, SPAN_XENO("[truename] [say_string]: [input]"), type = MESSAGE_TYPE_RADIO) + log_say("BORER: ([key_name(src)] to [key_name(host)]) [input]", src) + to_chat(src, SPAN_XENO("[truename] [say_string]: [input]"), type = MESSAGE_TYPE_RADIO) + action_talk_to_borer.give_to(host) + for (var/mob/dead in GLOB.dead_mob_list) + var/track_host = " (F)" + if(!istype(dead,/mob/new_player) && !istype(dead,/mob/living/brain)) //No meta-evesdropping + dead.show_message(SPAN_BORER("BORER: ([truename] to [host.real_name][track_host]) [say_string]: [input]"), SHOW_MESSAGE_VISIBLE) + +/mob/living/carbon/cortical_borer/verb/toggle_silence_inside_host() + set name = "Toggle speech inside Host" + set category = "Borer" + set desc = "Toggle whether you will be able to say audible messages while inside your host." + + if(talk_inside_host) + talk_inside_host = FALSE + to_chat(src, SPAN_XENONOTICE("You will no longer talk audibly while inside a host.")) + else + talk_inside_host = TRUE + to_chat(src, SPAN_XENONOTICE("You will now be able to audibly speak from inside of a host.")) + +/mob/living/proc/borer_comm() + set name = "Converse with Borer" + set category = "Borer" + set desc = "Communicate mentally with your borer." + + + var/mob/living/carbon/cortical_borer/B = has_brain_worms() + if(!B) + return + + if(stat == DEAD) + to_chat(src, SPAN_XENODANGER("You're dead, Jim.")) + return + + var/input = stripped_input(src, "Please enter a message to tell the borer.", "Message", "") + if(!input) + return + + to_chat(B, SPAN_XENO("[src] says: [input]"), type = MESSAGE_TYPE_RADIO) + log_say("BORER: ([key_name(src)] to [key_name(B)]) [input]", src) + to_chat(src, SPAN_XENO("[src] says: [input]"), type = MESSAGE_TYPE_RADIO) + for (var/mob/dead in GLOB.dead_mob_list) + var/track_host = " (F)" + if(!istype(dead,/mob/new_player) && !istype(dead,/mob/living/brain)) //No meta-evesdropping + dead.show_message(SPAN_BORER("BORER: ([name][track_host] to [B.truename]) says: [input]"), SHOW_MESSAGE_VISIBLE) + +/mob/living/proc/trapped_mind_comm() + set name = "Converse with Trapped Mind" + set category = "Borer" + set desc = "Communicate mentally with the trapped mind of your host." + + + var/mob/living/carbon/cortical_borer/B = has_brain_worms() + if(!B || !B.host_brain) + return + var/mob/living/captive_brain/CB = B.host_brain + var/input = stripped_input(src, "Please enter a message to tell the trapped mind.", "Message", "") + if(!input) + return + + to_chat(CB, SPAN_XENO("[B.truename] says: [input]"), type = MESSAGE_TYPE_RADIO) + log_say("BORER: ([key_name(src)] to [key_name(CB)]) [input]", B) + to_chat(src, SPAN_XENO("[B.truename] says: [input]"), type = MESSAGE_TYPE_RADIO) + for (var/mob/dead in GLOB.dead_mob_list) + var/track_borer = " (F)" + if(!istype(dead,/mob/new_player) && !istype(dead,/mob/living/brain)) //No meta-evesdropping + dead.show_message(SPAN_BORER("BORER: ([B.truename][track_borer] to [real_name] (trapped mind)) says: [input]"), SHOW_MESSAGE_VISIBLE) + + + + +//############# TOPIC ############# +/mob/living/carbon/cortical_borer/Topic(href, href_list, hsrc) + if(href_list["borer_use_chem"]) + locate(href_list["src"]) + if(!istype(src, /mob/living/carbon/cortical_borer)) + return + if(docile) + to_chat(src, SPAN_XENOWARNING("You are feeling far too docile to do that.")) + return + + var/topic_chem = href_list["borer_use_chem"] + var/datum/borer_chem/C = null + + for(var/datum in typesof(/datum/borer_chem)) + var/datum/borer_chem/test = datum + if(initial(test.chem_id) == topic_chem) + C = new test() + break + + if(!C || !host || controlling || !src || stat) + return + var/datum/reagent/R = chemical_reagents_list[C.chem_id] + if(enzymes < C.cost) + to_chat(src, SPAN_XENOWARNING("You need [C.cost] enzymes stored to secrete [R.name]!")) + return + var/contamination = round(C.cost / 10) + if(C.impure && ((contaminant + contamination) > max_contaminant)) + to_chat(src, SPAN_XENOWARNING("You are too contaminated to secrete [R.name]!")) + return + to_chat(src, SPAN_XENONOTICE("You squirt a measure of [R.name] from your reservoirs into [host]'s bloodstream.")) + contaminant += contamination + host.reagents.add_reagent(C.chem_id, C.quantity) + enzymes -= C.cost + log_interact(src, host, "[key_name(src)] has injected [C.quantity] units of [R.name] into their host, [key_name(host)]") + + // This is used because we use a static set of datums to determine what chems are available, + // instead of a table or something. Thus, when we instance it, we can safely delete it + qdel(C) + ..() diff --git a/code/modules/mob/holder.dm b/code/modules/mob/holder.dm index b2a68c997ec9..61b5c07ba159 100644 --- a/code/modules/mob/holder.dm +++ b/code/modules/mob/holder.dm @@ -115,3 +115,9 @@ /obj/item/holder/mouse/brown/Tom name = "Tom" desc = "Jerry the cat is not amused." + +/obj/item/holder/borer + name = "cortical borer" + desc = "Gross..." + icon = 'icons/mob/animal.dmi' + icon_state = "brainslug_dead" diff --git a/code/modules/mob/language/languages.dm b/code/modules/mob/language/languages.dm index 546c2bf7714f..5760255ff38d 100644 --- a/code/modules/mob/language/languages.dm +++ b/code/modules/mob/language/languages.dm @@ -202,3 +202,47 @@ color = "tajaran" key = "7" flags = RESTRICTED|HIVEMIND + +/datum/language/corticalborer + name = LANGUAGE_BORER + desc = "Cortical borers possess a strange link between their tiny minds." + speech_verb = "sings" + ask_verb = "sings" + exclaim_verb = "sings" + color = "alien" + key = "0" + flags = RESTRICTED|HIVEMIND + +/datum/language/corticalborer/broadcast(mob/living/speaker, message, speaker_mask, death) + var/mob/living/carbon/cortical_borer/B + if(!speaker_mask) + speaker_mask = speaker + if(iscarbon(speaker)) + var/mob/living/carbon/M = speaker + B = M.has_brain_worms() + else if(istype(speaker,/mob/living/carbon/cortical_borer)) + B = speaker + + if(B) + speaker_mask = B.truename + + var/message_start = "[name], [speaker_mask]" + var/message_body = "[speech_verb], \"[message]\"" + log_say("[key_name(speaker)] : ([name]) [message]") + + for(var/mob/player in GLOB.player_list) + var/understood = FALSE + var/ghost = FALSE + if(istype(player,/mob/dead)) + understood = TRUE + ghost = TRUE + else if((src in player.languages) && check_special_condition(player)) + understood = TRUE + if(understood) + if(!speaker_mask) + speaker_mask = speaker.name + if(death) + var/area/A = get_area(speaker) + to_chat(player, "[message_start] has [SPAN_BOLD("perished")][A? " at [sanitize_area(A.name)]":""]!") + else + to_chat(player, "[ghost? "(F) ":""][message_start] [message_body]") diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm index ecd1b6c97ca9..7993a7435e2b 100644 --- a/code/modules/mob/living/carbon/carbon_defines.dm +++ b/code/modules/mob/living/carbon/carbon_defines.dm @@ -23,6 +23,8 @@ var/datum/huntdata/hunter_data //Stores all information relating to Hunters for use with their HUD and other systems. + var/mob/living/carbon/cortical_borer/borer = null + /mob/living/carbon/vv_get_dropdown() . = ..() VV_DROPDOWN_OPTION("", "-----CARBON-----") diff --git a/code/modules/mob/living/carbon/human/death.dm b/code/modules/mob/living/carbon/human/death.dm index 5496cae5370f..4854bf954e28 100644 --- a/code/modules/mob/living/carbon/human/death.dm +++ b/code/modules/mob/living/carbon/human/death.dm @@ -47,6 +47,9 @@ disable_lights() disable_special_items() disable_headsets() //Disable radios for dead people to reduce load + var/mob/living/carbon/cortical_borer/Player2 = has_brain_worms() + if(Player2) + Player2.host_death(undefibbable) if(pulledby && isxeno(pulledby)) // Xenos lose grab on dead humans pulledby.stop_pulling() //Handle species-specific deaths. diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 9f08f32dc2b6..accd036c16e6 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -136,6 +136,24 @@ if(eta_status) . += "Evacuation: [eta_status]" + var/mob/living/carbon/cortical_borer/B = borer + if(B && B.controlling) + + var/CR = "Yes" + if(!B.can_reproduce) + CR = "Forbidden" + else if((B.enzymes < BORER_LARVAE_COST)) + CR = "No" + + . += "" + . += "Borer:" + . += "Name: [B.truename]" + . += "Can Reproduce: [CR]" + . += "Enzymes: [round(B.enzymes)]/[round(B.max_enzymes)]" + . += "" + . += "Host Brain Damage: [brainloss]/100" + + /mob/living/carbon/human/ex_act(severity, direction, datum/cause_data/cause_data) if(lying) severity *= EXPLOSION_PRONE_MULTIPLIER diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm index d64e5d1bfde0..df9f0c259e4f 100644 --- a/code/modules/mob/living/carbon/human/life.dm +++ b/code/modules/mob/living/carbon/human/life.dm @@ -66,6 +66,9 @@ if(life_tick > 5 && timeofdeath && (timeofdeath < 5 || world.time - timeofdeath > revive_grace_period) && !issynth(src)) //We are dead beyond revival, or we're junk mobs spawned like the clowns on the clown shuttle undefibbable = TRUE SEND_SIGNAL(src, COMSIG_HUMAN_SET_UNDEFIBBABLE) + var/mob/living/carbon/cortical_borer/B = has_brain_worms() + if(B) + B.host_death(TRUE) med_hud_set_status() else if(stat != DEAD) diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm index e5934f4fa13b..bb9cf9309a2b 100644 --- a/code/modules/mob/living/say.dm +++ b/code/modules/mob/living/say.dm @@ -128,6 +128,10 @@ var/list/department_radio_keys = list( continue if(M.loc && (M.locs[1] in hearturfs)) listening |= M + for(var/mob/inner_mob in M.contents) + listening |= inner_mob + for(var/mob/living/captive_brain/brain in inner_mob) + listening |= brain var/speech_bubble_test = say_test(message) var/image/speech_bubble = image('icons/mob/effects/talk.dmi', src, "[bubble_type][speech_bubble_test]", FLY_LAYER) diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index 2bf44188eff4..9090ac3ebb4d 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -228,6 +228,7 @@ if(icon_gib) new /obj/effect/overlay/temp/gib_animation/animal(loc, src, icon_gib) + /mob/living/simple_animal/attack_animal(mob/living/M as mob) if(M.melee_damage_upper == 0) M.emote("[M.friendly] [src]") @@ -349,7 +350,7 @@ if (targeted_by && target_locked) overlays += target_locked -/mob/living/simple_animal/say(message) +/mob/living/simple_animal/say(message, datum/language/speaking = null) if(stat) return @@ -367,7 +368,7 @@ message = capitalize(trim_left(message)) - ..(message, null, verb, nolog = !ckey) //if the animal has a ckey then it will log the message + ..(message, speaking, verb, nolog = !ckey) //if the animal has a ckey then it will log the message /mob/living/simple_animal/update_canmove() . = ..() diff --git a/code/modules/reagents/chemistry_machinery/reagent_grinder.dm b/code/modules/reagents/chemistry_machinery/reagent_grinder.dm index 1de4a84451e1..2929d3aba0d2 100644 --- a/code/modules/reagents/chemistry_machinery/reagent_grinder.dm +++ b/code/modules/reagents/chemistry_machinery/reagent_grinder.dm @@ -54,6 +54,7 @@ /obj/item/reagent_container/food/snacks/watermelonslice = list("watermelonjuice" = 0), /obj/item/reagent_container/food/snacks/grown/grapes = list("grapejuice" = 0), /obj/item/reagent_container/food/snacks/grown/poisonberries = list("poisonberryjuice" = 0), + /obj/item/holder/borer = list("benzyme" = 0), ) @@ -411,6 +412,15 @@ if(!O.reagents.total_volume) remove_object(O) + //Borer, for enzymes + for(var/obj/item/holder/borer/b_holder in holdingitems) + if(beaker.reagents.total_volume >= beaker.reagents.maximum_volume) + break + var/space = beaker.reagents.maximum_volume - beaker.reagents.total_volume + beaker.reagents.add_reagent("benzyme",min(6, space)) + remove_object(b_holder) + break + /obj/structure/machinery/reagentgrinder/proc/cleanup() SIGNAL_HANDLER if(linked_storage) diff --git a/code/modules/reagents/chemistry_properties/prop_positive.dm b/code/modules/reagents/chemistry_properties/prop_positive.dm index d1e58578a203..6d928d9fcd7c 100644 --- a/code/modules/reagents/chemistry_properties/prop_positive.dm +++ b/code/modules/reagents/chemistry_properties/prop_positive.dm @@ -485,9 +485,22 @@ H.vomit() else A.counter = 90 + if(H.chem_effect_flags & CHEM_EFFECT_ANTI_PARASITE) + return + + H.chem_effect_flags |= CHEM_EFFECT_ANTI_PARASITE + to_chat(H, SPAN_NOTICE("Your body feels warmer.")) /datum/chem_property/positive/antiparasitic/process_overdose(mob/living/M, potency = 1) M.apply_damage(potency, TOX) + var/mob/living/carbon/cortical_borer/player_2 = M.has_brain_worms() + if(player_2) + if(player_2.controlling) + player_2.detach() + to_chat(src, SPAN_HIGHDANGER("You relinquish the unknown chemical overwhelms you!")) + + player_2.leave_host() + to_chat(src, SPAN_HIGHDANGER("The overwhelming flow of powerful chemicals forces you to flee your host!")) /datum/chem_property/positive/antiparasitic/process_critical(mob/living/M, potency = 1) M.apply_damage(POTENCY_MULTIPLIER_VHIGH*potency, TOX) diff --git a/code/modules/reagents/chemistry_reactions/other.dm b/code/modules/reagents/chemistry_reactions/other.dm index 3956a4d9dc5c..bfa7a57688e6 100644 --- a/code/modules/reagents/chemistry_reactions/other.dm +++ b/code/modules/reagents/chemistry_reactions/other.dm @@ -462,3 +462,11 @@ result = "eggplasma" required_reagents = list("blood" = 10, "eggplasma" = 1) result_amount = 2 + +/datum/chemical_reaction/borer_cure + name = "Anti-Enzyme" + id = "bcure" + result = "bcure" + required_reagents = list("benzyme" = 2, "anti_toxin" = 4) + result_amount = 3 + mob_react = FALSE diff --git a/code/modules/reagents/chemistry_reagents/other.dm b/code/modules/reagents/chemistry_reagents/other.dm index aaf9901f46af..fdb84b7aa48b 100644 --- a/code/modules/reagents/chemistry_reagents/other.dm +++ b/code/modules/reagents/chemistry_reagents/other.dm @@ -1013,3 +1013,27 @@ chemclass = CHEM_CLASS_SPECIAL properties = list(PROPERTY_TRANSFORMATIVE = 4, PROPERTY_NUTRITIOUS = 3, PROPERTY_HEMOGENIC = 1) flags = REAGENT_SCANNABLE + +/datum/reagent/borer_enzyme + name = "Cortical Enzyme" + id = "benzyme" + description = "An enzyme secreted by a parasite that consumes certain chemicals from the bloodstream. Also seems to help fight addictions." + reagent_state = LIQUID + color = "#25c08c" + overdose = LOW_REAGENTS_OVERDOSE + overdose_critical = LOW_REAGENTS_OVERDOSE_CRITICAL + chemclass = CHEM_CLASS_SPECIAL + flags = REAGENT_SCANNABLE|REAGENT_NO_GENERATION + properties = list(PROPERTY_CROSSMETABOLIZING = 2, PROPERTY_ANTIADDICTIVE = 2) + +/datum/reagent/borer_cure + name = "Anti-Enzyme" + id = "bcure" + description = "An anti-parasite drug synthesised from parastic enzymes. Effectively fights toxins in the bloodstream." + reagent_state = LIQUID + color = "#25c08c" + overdose = LOW_REAGENTS_OVERDOSE + overdose_critical = LOW_REAGENTS_OVERDOSE_CRITICAL + chemclass = CHEM_CLASS_SPECIAL + flags = REAGENT_SCANNABLE|REAGENT_NO_GENERATION + properties = list(PROPERTY_CROSSMETABOLIZING = 2, PROPERTY_ANTITOXIN = 4) diff --git a/code/span_macros.dm b/code/span_macros.dm index 110da044e74a..9ea39092291a 100644 --- a/code/span_macros.dm +++ b/code/span_macros.dm @@ -82,6 +82,7 @@ #define SPAN_AVOIDHARM(X) "[X]" #define SPAN_SCANNER(X) "[X]" +#define SPAN_BORER(X) "[X]" #define SPAN_ROSE(X) "[X]" #define SPAN_LANGCHAT(X) "[X]" diff --git a/colonialmarines.dme b/colonialmarines.dme index f8c06ba3d841..d562a3dcbf1c 100644 --- a/colonialmarines.dme +++ b/colonialmarines.dme @@ -1378,6 +1378,11 @@ #include "code\modules\asset_cache\assets\tgui.dm" #include "code\modules\asset_cache\assets\vending.dm" #include "code\modules\asset_cache\transports\asset_transport.dm" +#include "code\modules\borer\_defines.dm" +#include "code\modules\borer\borer.dm" +#include "code\modules\borer\borer_chemicals.dm" +#include "code\modules\borer\borer_html.dm" +#include "code\modules\borer\borer_procs.dm" #include "code\modules\buildmode\bm-mode.dm" #include "code\modules\buildmode\buildmode.dm" #include "code\modules\buildmode\buttons.dm" diff --git a/icons/mob/brainslug.dmi b/icons/mob/brainslug.dmi new file mode 100644 index 0000000000000000000000000000000000000000..db29b87ac953f66b36ab8fd1596c80f537c02914 GIT binary patch literal 4277 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 JRmN_2{s+a-{Wt&s literal 0 HcmV?d00001 diff --git a/icons/mob/hud/actions_borer.dmi b/icons/mob/hud/actions_borer.dmi new file mode 100644 index 0000000000000000000000000000000000000000..9447b8f546211f96dd4fa71ebe28446e13d25f14 GIT binary patch literal 2032 zcmVC0001ZP)t-sz`(#! zIbbj%I$1zwVlP;HJZ~r*FgzVWNgqil7%U+WBLMZ=HmsP}mH^CV03>f>Hybe*3m7*b zJef1hNGng5Nr+1%QLIy%HX%DjAwFjc3Zw!8wo?G6tTx&80K?ZGmYXztS4((GOI27I zY;Pq0%w~mUX28I}vW^k-00001bW%=J06^y0W&i*Hy?RtwbVOxyV{&P5bZKvH004NL zm6E{$?nJ!Mj?u8nppm$MWWTyNuEDC!7XOkM#28)IRlM2glSZPQ$e4)q9o{$VN3 zo{6s3(FDk(P4E%Jz|}CCt1*CHfXT*yBlyUqo$r9Suyw8TeUlx-b0&^3VQ6Q=J(LE3 z>4?vF;jdAu%DVV`;TtkP#8j>IGD!dc23<)+K~#90?V5{vn>q}D!)!_*q)xh&lbR6d zdU^jxoMd6*D>TVz((fBun+pQzN0x1*Zn2nUmLDNWNPgW;zepGf=V_YupV@=2x8#4+ zp8&Gua>>69fNO~X@H}5G^MXeVtgZyW@RINtKn4-W@-*(CT(MOdbFf}N;@jindL7cA z{1ZU{AVvS902GPxk_hm$kH01e0TdYopnx$-#~ldY!6V*2{^|gD0DRdCGI#(g`O`uP zN8B&~!n5+S2rM^RfWQOblmqBL<0@bw2Wh@qmC+pNBhUxH(Zl_3Hd_=;U;@zOPZ1!` zvy@WCcTlcYkR-)ajsyX@_KyS}0H^S303vW5lw}Gj3UVpHR*MfnNrIEGzzX;m4}lb2kEN6o8@hlMn*1L;no35g_tF1U91pu>NkgF#O=Y*lrjvs{P&pz^yPc z&k-P105BNr8AIRzEcppQ`L_YoGr$WFAo+Il0;>XyhYR355tsnE1^`hxfCR=6^QGDf zhLsq;awEbEwena1NZ$(o;PXO2-5K#ls`u@uR zXkW$w1oa0*#6Y~hC;edxK$3kK2cZ6d|Hw5}-wzR}5A0CK0myzG1t_UM5C#~l@9iPz z5qy13_D2AyKOltr*gaL>yEz~u0AycER(k*{`Ag~#Kn`H(aYlV__Ydko1k|yb>;s;x zT>wEjp!x&&83@j(?@#@M!=Vo5fTSEy_VF_im;f~SOF`wi`Xb6R>U$mH!y(*1AgEfB zef34wCV;yCasVKLGwOQ*q3gSE=mFZjs5`617`?E>w6UeDWm;^T7uTFkMqD9f)e~&{Q)2laR9HrpXwjf z;NP+j1ojY!(&-OCOwK5;?f!w3wL1i|cI-n;tRxg(e?WK}{0U5Aw0R;O4!T}hm?x1^C1pw=>*&h&Ye_*`6 zPx^Jp+7K)NSbt6X(zd@)&j40=F0B5*RDG|L@DKq|&j6NvY1v<>mB;Q62(v#hRo|a> z!$S~2tvt4UY1$9=2ZWIWQ}z91{~%yrnmI7bEVIlq%eU134`09j`ue|Vjx8SR+NN!~ zv#-A%plzF@I@qzUJ9f7R=$fW&wW4dgiviYa{g>acfme`_K7rUD6P6GJ#UONj#J)a( zl;J-f8yNf#1g8F-VGO!1X^%rL zK78FioS^j|AHx}z0qy_ub0DzRf%lFjD$q?I0tcYC|M7tiJo}4e&D;~%+BERm-}&b! z5~xVLS`ldU^23@o0HE!I2Ntk@J-{1#(*UgwK@i}Bg#oS|0><9EcRK{lSwnDb62g(U z=3ON}X5;bvB)oDOGDV<6(D+F>Jq@p1h!8-JL81nsarILR5xZt7X4d#)Lbm5G#iYD! zGa~N{=RnI^^?u1_WXn0*@#!cm#!A}bO`iwo2tE{mYbD3_4geSHzgXYL^+EL+*UvJ` zEVIlqUB7=jz|>|j1KbQC^2l{7fapW#Z2(3dyKe$8R(RY3V6^nQ0YLBnRQ Date: Sun, 2 Apr 2023 05:16:11 +0100 Subject: [PATCH 02/25] Modernisation HUD and more modernisation holder icon follow and PP fix + Life() ack! Minor tweaks and updates ert name ladders x f scanner detection --- code/__DEFINES/mob_hud.dm | 2 + code/__DEFINES/typecheck/xenos.dm | 1 + code/__HELPERS/unsorted.dm | 2 + code/datums/mob_hud.dm | 34 ++-- .../game/machinery/medical_pod/bodyscanner.dm | 3 + .../admin/player_panel/player_panel.dm | 2 + code/modules/borer/_defines.dm | 5 + code/modules/borer/borer.dm | 146 +++++++++++++----- code/modules/borer/borer_procs.dm | 141 ++++++++--------- code/modules/mob/dead/observer/orbit.dm | 5 + code/modules/mob/holder.dm | 4 +- .../mob/living/carbon/human/human_defines.dm | 2 +- .../mob/living/carbon/xenomorph/Xenomorph.dm | 2 +- code/modules/mob/living/living_healthscan.dm | 2 +- .../chemistry_properties/prop_positive.dm | 2 +- code/modules/surgery/brainworm.dm | 80 ++++++++++ colonialmarines.dme | 1 + icons/mob/brainslug.dmi | Bin 4277 -> 4282 bytes icons/mob/hud/hud.dmi | Bin 19035 -> 19314 bytes tgui/packages/tgui/interfaces/HealthScan.js | 5 + tgui/packages/tgui/interfaces/Orbit/index.tsx | 2 + tgui/packages/tgui/interfaces/Orbit/types.ts | 1 + 22 files changed, 312 insertions(+), 130 deletions(-) create mode 100644 code/modules/surgery/brainworm.dm diff --git a/code/__DEFINES/mob_hud.dm b/code/__DEFINES/mob_hud.dm index e6e8212e0826..9298fa930db4 100644 --- a/code/__DEFINES/mob_hud.dm +++ b/code/__DEFINES/mob_hud.dm @@ -25,6 +25,7 @@ #define STATUS_HUD_XENO_CULTIST "24" // Whether they are a xeno cultist or not #define HUNTER_CLAN "25" //Displays a colored icon to represent ingame Hunter Clans #define HUNTER_HUD "26" //Displays various statuses on mobs for Hunters to identify targets +#define STATUS_HUD_BRAINWORM "27" //Displays infested HUD for brainworms. //data HUD (medhud, sechud) defines #define MOB_HUD_SECURITY_BASIC 1 @@ -44,6 +45,7 @@ #define MOB_HUD_FACTION_PMC 15 #define MOB_HUD_HUNTER 16 #define MOB_HUD_HUNTER_CLAN 17 +#define MOB_HUD_BRAINWORM 18 //for SL/FTL/LZ targeting on locator huds #define TRACKER_SL "track_sl" diff --git a/code/__DEFINES/typecheck/xenos.dm b/code/__DEFINES/typecheck/xenos.dm index 4d1b7819bdf1..6ae8fe1955a0 100644 --- a/code/__DEFINES/typecheck/xenos.dm +++ b/code/__DEFINES/typecheck/xenos.dm @@ -1,5 +1,6 @@ //Xenomorph Hud Test APOPHIS 22MAY2015 #define isxeno(A) (istype(A, /mob/living/carbon/xenomorph)) +#define isborer(A) (istype(A, /mob/living/carbon/cortical_borer)) #define isxeno_human(A) (isxeno(A) || ishuman(A)) //ask walter if i should turn into castechecks diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index 5d25df2150c0..25890101758b 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -624,6 +624,8 @@ moblist.Add(M) for(var/mob/living/carbon/xenomorph/M in sortmob) moblist.Add(M) + for(var/mob/living/carbon/cortical_borer/M in sortmob) + moblist.Add(M) for(var/mob/dead/observer/M in sortmob) moblist.Add(M) for(var/mob/new_player/M in sortmob) diff --git a/code/datums/mob_hud.dm b/code/datums/mob_hud.dm index fa2af17240f9..a6d4e2c09a09 100644 --- a/code/datums/mob_hud.dm +++ b/code/datums/mob_hud.dm @@ -18,7 +18,8 @@ var/list/datum/mob_hud/huds = list( MOB_HUD_FACTION_CLF = new /datum/mob_hud/faction/clf(), MOB_HUD_FACTION_PMC = new /datum/mob_hud/faction/pmc(), MOB_HUD_HUNTER = new /datum/mob_hud/hunter_hud(), - MOB_HUD_HUNTER_CLAN = new /datum/mob_hud/hunter_clan() + MOB_HUD_HUNTER_CLAN = new /datum/mob_hud/hunter_clan(), + MOB_HUD_BRAINWORM = new /datum/mob_hud/brainworm(), ) /datum/mob_hud @@ -129,7 +130,7 @@ var/list/datum/mob_hud/huds = list( //medical hud used by ghosts /datum/mob_hud/medical/observer - hud_icons = list(HEALTH_HUD, STATUS_HUD_OOC, STATUS_HUD_XENO_CULTIST) + hud_icons = list(HEALTH_HUD, STATUS_HUD_OOC, STATUS_HUD_XENO_CULTIST, STATUS_HUD_BRAINWORM) //infection status that appears on humans, viewed by xenos only and observers. @@ -151,6 +152,8 @@ var/list/datum/mob_hud/huds = list( /datum/mob_hud/hunter_hud hud_icons = list(HUNTER_HUD) +/datum/mob_hud/brainworm + hud_icons = list(HEALTH_HUD_XENO, PLASMA_HUD, PHEROMONE_HUD, QUEEN_OVERWATCH_HUD, ARMOR_HUD_XENO, XENO_STATUS_HUD, XENO_BANISHED_HUD, HEALTH_HUD, STATUS_HUD_OOC, STATUS_HUD_XENO_CULTIST, STATUS_HUD_BRAINWORM) //Security /datum/mob_hud/security @@ -207,7 +210,7 @@ var/list/datum/mob_hud/huds = list( /mob/living/carbon/xenomorph/add_to_all_mob_huds() for(var/datum/mob_hud/hud in huds) - if(!istype(hud, /datum/mob_hud/xeno)) + if(!istype(hud, /datum/mob_hud/xeno) && !istype(hud, /datum/mob_hud/brainworm)) continue hud.add_to_hud(src) @@ -230,7 +233,7 @@ var/list/datum/mob_hud/huds = list( if(istype(hud, /datum/mob_hud/xeno)) hud.remove_from_hud(src) hud.remove_hud_from(src) - else if (istype(hud, /datum/mob_hud/xeno_infection)) + else if (istype(hud, /datum/mob_hud/xeno_infection) || istype(hud, /datum/mob_hud/brainworm)) hud.remove_hud_from(src) @@ -263,6 +266,7 @@ var/list/datum/mob_hud/huds = list( /mob/living/carbon/xenomorph/med_hud_set_health() var/image/holder = hud_list[HEALTH_HUD_XENO] + var/image/holder2 = hud_list[STATUS_HUD_BRAINWORM] var/health_hud_type = "xenohealth" if(stat == DEAD) @@ -278,6 +282,13 @@ var/list/datum/mob_hud/huds = list( amount = -1 //don't want the 'zero health' icon when we are crit holder.icon_state = "[health_hud_type][amount]" + holder2.icon_state = null + if(has_brain_worms()) + holder2.icon_state = "hudbrainwormhost" + holder2.pixel_x = 9 + holder2.pixel_y = -8 + + /mob/living/carbon/xenomorph/proc/overlay_shields() var/image/holder = hud_list[HEALTH_HUD_XENO] holder.overlays.Cut() @@ -340,10 +351,12 @@ var/list/datum/mob_hud/huds = list( holder2.overlays.Cut() var/image/holder3 = hud_list[STATUS_HUD_XENO_INFECTION] var/image/holder4 = hud_list[STATUS_HUD_XENO_CULTIST] + var/image/holder5 = hud_list[STATUS_HUD_BRAINWORM] holder2.color = null holder3.color = null holder4.color = null + holder5.color = null holder4.icon_state = "hudblank" @@ -447,11 +460,14 @@ var/list/datum/mob_hud/huds = list( return var/mob/living/carbon/cortical_borer/B = has_brain_worms() - if(B && B.controlling) - holder.icon_state = "hudbrainworm" - if(!holder2_set) - holder2.icon_state = "hudbrainworm" - return + holder5.icon_state = null + if(B) + holder5.icon_state = "hudbrainwormhost" + if(B.controlling) + holder.icon_state = "hudbrainworm" + if(!holder2_set) + holder2.icon_state = "hudbrainworm" + return for(var/datum/disease/D in viruses) if(!D.hidden[SCANNER]) diff --git a/code/game/machinery/medical_pod/bodyscanner.dm b/code/game/machinery/medical_pod/bodyscanner.dm index 4756121e50ae..3cd2cf075d1b 100644 --- a/code/game/machinery/medical_pod/bodyscanner.dm +++ b/code/game/machinery/medical_pod/bodyscanner.dm @@ -390,6 +390,9 @@ if(occ["sdisabilities"] & NEARSIGHTED) dat += SET_CLASS("Retinal misalignment detected.", INTERFACE_RED) dat += "
" + if(connected.occupant.has_brain_worms()) + dat += SET_CLASS("Cranial anomoly detected.", INTERFACE_RED) + dat += "
" dat += "" return dat diff --git a/code/modules/admin/player_panel/player_panel.dm b/code/modules/admin/player_panel/player_panel.dm index 12686e683521..ada3d1100687 100644 --- a/code/modules/admin/player_panel/player_panel.dm +++ b/code/modules/admin/player_panel/player_panel.dm @@ -201,6 +201,8 @@ M_job = "Monkey" else if(isxeno(M)) M_job = "Alien" + else if(isborer(M)) + M_job = "Brainslug" else M_job = "Carbon-based" else if(isSilicon(M)) //silicon diff --git a/code/modules/borer/_defines.dm b/code/modules/borer/_defines.dm index 15929f6fa492..a1c7b4959e50 100644 --- a/code/modules/borer/_defines.dm +++ b/code/modules/borer/_defines.dm @@ -6,3 +6,8 @@ ///Amount of chemicals needed for a borer to reproduce, provided reproduction is toggled. #define BORER_LARVAE_COST 400 + +#define ACTION_SET_HOSTLESS "actions_hostless" +#define ACTION_SET_HUMANOID "actions_human" +#define ACTION_SET_XENO "actions_xeno" +#define ACTION_SET_CONTROL "actions_control" diff --git a/code/modules/borer/borer.dm b/code/modules/borer/borer.dm index 683956bf1fb4..81afde2f1be9 100644 --- a/code/modules/borer/borer.dm +++ b/code/modules/borer/borer.dm @@ -53,7 +53,7 @@ desc = "A small, quivering sluglike creature." speak_emote = list("chirrups") icon = 'icons/mob/brainslug.dmi' - icon_state = "brainslug" + icon_state = "Borer" speed = 0 a_intent = INTENT_HARM status_flags = CANPUSH @@ -70,6 +70,7 @@ holder_type = /obj/item/holder/borer var/generation = 1 + var/stealthy = FALSE var/static/list/borer_names = list( "Primary", "Secondary", "Tertiary", "Quaternary", "Quinary", "Senary", "Septenary", "Octonary", "Novenary", "Decenary", "Undenary", "Duodenary", @@ -93,25 +94,55 @@ var/leaving = FALSE var/hiding = FALSE var/can_reproduce = FALSE // Locked to manual override to prevent things getting out of hand. - var/infect_hunter = FALSE // Locked for normal use. + + var/infect_humans = TRUE // Locked for normal use. + var/infect_xenos = FALSE + var/infect_yautja = FALSE var/list/datum/reagent/synthesized_chems - /// All of these surely have a better way of being handled. - var/datum/action/innate/borer/talk_to_host/action_talk_to_host = new - var/datum/action/innate/borer/infest_host/action_infest_host = new - var/datum/action/innate/borer/toggle_hide/action_toggle_hide = new - var/datum/action/innate/borer/talk_to_borer/action_talk_to_borer = new - var/datum/action/innate/borer/talk_to_brain/action_talk_to_brain = new - var/datum/action/innate/borer/take_control/action_take_control = new - var/datum/action/innate/borer/give_back_control/action_give_back_control = new - var/datum/action/innate/borer/leave_body/action_leave_body = new - var/datum/action/innate/borer/make_chems/action_make_chems = new - var/datum/action/innate/borer/make_larvae/action_make_larvae = new - var/datum/action/innate/borer/freeze_victim/action_freeze_victim = new - var/datum/action/innate/borer/torment/action_torment = new - var/datum/action/innate/borer/scan_chems/action_scan_chems = new - var/datum/action/innate/borer/hibernate/action_hibernate = new + var/current_actions = ACTION_SET_HOSTLESS + var/list/actions_hostless = list( + /datum/action/innate/borer/toggle_hide, + /datum/action/innate/borer/freeze_victim, + /datum/action/innate/borer/infest_host + ) + var/list/actions_humanoidhost = list( + /datum/action/innate/borer/take_control, + /datum/action/innate/borer/talk_to_host, + /datum/action/innate/borer/leave_body, + /datum/action/innate/borer/hibernate, + /datum/action/innate/borer/scan_chems, + /datum/action/innate/borer/make_chems + ) + var/list/actions_xenohost = list( + /datum/action/innate/borer/take_control, + /datum/action/innate/borer/talk_to_host, + /datum/action/innate/borer/leave_body, + /datum/action/innate/borer/hibernate + ) + var/list/actions_control = list( + /datum/action/innate/borer/give_back_control, + /datum/action/innate/borer/make_larvae, + /datum/action/innate/borer/talk_to_brain, + /datum/action/innate/borer/torment + ) + +//################### INIT & LIFE ###################// +/mob/living/carbon/cortical_borer/New(atom/newloc, gen=1, ERT = FALSE, reproduction = 0) + ..(newloc) + SSmob.living_misc_mobs += src + generation = gen + add_language(LANGUAGE_BORER) + var/mob_number = rand(1000,9999) + real_name = "Cortical Borer [mob_number]" + truename = "[borer_names[min(generation, borer_names.len)]] [mob_number]" + can_reproduce = reproduction + give_new_actions(ACTION_SET_HOSTLESS) + //GrantBorerActions() + GiveBorerHUD() + if((!is_admin_level(z)) && ERT) + summon() /mob/living/carbon/cortical_borer/initialize_pass_flags(datum/pass_flags_container/PF) ..() @@ -119,9 +150,59 @@ PF.flags_pass = PASS_MOB_THRU|PASS_FLAGS_CRAWLER PF.flags_can_pass_all = PASS_ALL^PASS_OVER_THROW_ITEM +/mob/living/carbon/cortical_borer/initialize_pain() + pain = new /datum/pain/zombie(src) +/mob/living/carbon/cortical_borer/initialize_stamina() + stamina = new /datum/stamina/none(src) + +/mob/living/carbon/cortical_borer/updatehealth() + if(status_flags & GODMODE) + health = maxHealth + set_stat(CONSCIOUS) + else + health = maxHealth - getFireLoss() - getBruteLoss() - getToxLoss() //Borer can only take brute, fire and tox damage. + + if(stat != DEAD && !gibbing) + if(health <= -50) //dead + death(last_damage_data) + return + else if(health <= 0) //in crit + handle_crit() + +/mob/living/carbon/cortical_borer/proc/handle_crit() + if(stat == DEAD || gibbing) + return + + sound_environment_override = SOUND_ENVIRONMENT_NONE + set_stat(UNCONSCIOUS) + blinded = TRUE + if(layer != initial(layer)) //Unhide + layer = initial(layer) + recalculate_move_delay = TRUE + if(!lying) + update_canmove() + update_icons() + +/mob/living/carbon/cortical_borer/death() + var/datum/language/corticalborer/c_link = GLOB.all_languages[LANGUAGE_BORER] + c_link.broadcast(src, null, src.truename, TRUE) + SSmob.living_misc_mobs -= src + . = ..() + +/mob/living/carbon/cortical_borer/rejuvenate() + ..() + update_icons() + update_canmove() + SSmob.living_misc_mobs |= src + +/mob/living/carbon/cortical_borer/Destroy() + SSmob.living_misc_mobs -= src + return ..() +//###################################################// + /mob/living/carbon/cortical_borer/proc/summon() var/datum/emergency_call/custom/em_call = new() - em_call.name = "Cortical Borer" + em_call.name = real_name em_call.mob_max = 1 em_call.players_to_offer = list(src) em_call.owner = null @@ -132,24 +213,6 @@ message_admins("A new Cortical Borer has spawned at [get_area(loc)]") -/mob/living/carbon/cortical_borer/New(atom/newloc, gen=1, ERT = FALSE, reproduction = 0) - ..(newloc) - generation = gen - add_language(LANGUAGE_BORER) - var/mob_number = rand(1000,9999) - real_name = "Cortical Borer [mob_number]" - truename = "[borer_names[min(generation, borer_names.len)]] [mob_number]" - can_reproduce = reproduction - GrantBorerActions() - GiveBorerHUD() - if((!is_admin_level(z)) && ERT) - summon() - -/mob/living/carbon/cortical_borer/death() - var/datum/language/corticalborer/c_link = GLOB.all_languages[LANGUAGE_BORER] - c_link.broadcast(src, null, null, TRUE) - . = ..() - /mob/living/carbon/cortical_borer/update_icons() if(stat == DEAD) icon_state = "Borer Dead" @@ -163,7 +226,7 @@ icon_state = "Borer" /mob/living/carbon/cortical_borer/proc/GiveBorerHUD() - var/datum/mob_hud/H = huds[MOB_HUD_MEDICAL_OBSERVER] + var/datum/mob_hud/H = huds[MOB_HUD_BRAINWORM] H.add_hud_to(src) /mob/living/carbon/cortical_borer/can_ventcrawl() @@ -213,14 +276,16 @@ /mob/living/carbon/cortical_borer/Life(delta_time) ..() + update_canmove() + update_icons() if(host) if(!stat && host.stat != DEAD) if(((host.chem_effect_flags & CHEM_EFFECT_ANTI_PARASITE) && !host.reagents.has_reagent("benzyme")) || host.reagents.has_reagent("bcure")) if(!docile) if(controlling) - to_chat(host, SPAN_XENODANGER("You feel the soporific flow of a chemical in your host's blood, lulling you into docility.")) + to_chat(host, SPAN_XENODANGER("You feel the flow of a soporific chemical in your host's blood, lulling you into docility.")) else - to_chat(src, SPAN_XENODANGER("You feel the soporific flow of a chemical in your host's blood, lulling you into docility.")) + to_chat(src, SPAN_XENODANGER("You feel the flow of a soporific chemical in your host's blood, lulling you into docility.")) docile = TRUE else if(docile) @@ -248,9 +313,6 @@ contaminant = max(contaminant - 0.3, 0) else SetLuminosity(0) - - update_canmove() - update_icons() /datum/action/innate/borer icon_file = 'icons/mob/hud/actions_borer.dmi' diff --git a/code/modules/borer/borer_procs.dm b/code/modules/borer/borer_procs.dm index 9db7f1542c14..37e036a6b850 100644 --- a/code/modules/borer/borer_procs.dm +++ b/code/modules/borer/borer_procs.dm @@ -1,6 +1,9 @@ //############# Physical Interaction Procs ############# /mob/living/carbon/cortical_borer/UnarmedAttack(atom/A) - A.attack_borer(src) + if(istype(A, /obj/structure/ladder)) + A.attack_hand(src) + else + A.attack_borer(src) /atom/proc/attack_borer(mob/living/carbon/cortical_borer/user) return @@ -42,43 +45,47 @@ forceMove(S.loc) //############# Action Give/Take Procs ############# -/mob/living/carbon/cortical_borer/proc/GrantBorerActions() - action_infest_host.give_to(src) - action_toggle_hide.give_to(src) - action_freeze_victim.give_to(src) - -/mob/living/carbon/cortical_borer/proc/RemoveBorerActions() - action_infest_host.remove_from(src) - action_toggle_hide.remove_from(src) - action_freeze_victim.remove_from(src) - -/mob/living/carbon/cortical_borer/proc/GrantInfestActions() - action_talk_to_host.give_to(src) - action_leave_body.give_to(src) - action_take_control.give_to(src) - action_make_chems.give_to(src) - action_scan_chems.give_to(src) - action_hibernate.give_to(src) - -/mob/living/carbon/cortical_borer/proc/RemoveInfestActions() - action_talk_to_host.remove_from(src) - action_take_control.remove_from(src) - action_leave_body.remove_from(src) - action_make_chems.remove_from(src) - action_scan_chems.remove_from(src) - action_hibernate.remove_from(src) - -/mob/living/carbon/cortical_borer/proc/GrantControlActions() - action_talk_to_brain.give_to(host) - action_give_back_control.give_to(host) - action_make_larvae.give_to(host) - action_torment.give_to(host) - -/mob/living/carbon/cortical_borer/proc/RemoveControlActions() - action_talk_to_brain.remove_from(host) - action_make_larvae.remove_from(host) - action_give_back_control.remove_from(host) - action_torment.remove_from(host) +/mob/living/carbon/cortical_borer/proc/give_new_actions(actions_list = ACTION_SET_HOSTLESS, target = src) + for(var/datum/action/innate/borer/action in actions) + action.hide_from(target) + + if(host && current_actions == ACTION_SET_CONTROL) + for(var/datum/action/innate/borer/action in host.actions) + action.hide_from(host) + + var/list/abilities_to_give + switch(actions_list) + if(ACTION_SET_HOSTLESS) + abilities_to_give = actions_hostless.Copy() + if(host) + for(var/datum/action/innate/borer/action in host.actions) + action.hide_from(host) + if(ACTION_SET_HUMANOID) + abilities_to_give = actions_humanoidhost.Copy() + if(ACTION_SET_XENO) + abilities_to_give = actions_xenohost.Copy() + if(ACTION_SET_CONTROL) + if(!host) + return FALSE + abilities_to_give = actions_control.Copy() + target = host + + for(var/path in abilities_to_give) + give_action(target, path) + current_actions = actions_list + return TRUE + +/mob/living/carbon/cortical_borer/proc/get_host_actions() + if(!host) + return FALSE + if(ishuman(host)) + give_new_actions(ACTION_SET_HUMANOID) + else if(isxeno(host)) + give_new_actions(ACTION_SET_XENO) + else + return FALSE + give_action(host, /datum/action/innate/borer/talk_to_borer) + return TRUE /mob/living/carbon/cortical_borer/proc/hibernate() hibernating = !hibernating @@ -114,15 +121,21 @@ to_chat(src, "You cannot infest a target in your current state.") return var/list/choices = list() - for(var/mob/living/carbon/human/H in view(1,src)) - var/obj/limb/head/head = H.get_limb("head") - if(head.status & LIMB_ROBOT) + for(var/mob/living/carbon/candidate in view(1,src)) + var/obj/limb/head/head = candidate.get_limb("head") + if((isborer(candidate)) || (head?.status & (LIMB_DESTROYED|LIMB_ROBOT|LIMB_SYNTHSKIN)))//No infecting synths, or borers. continue - if(isspeciesyautja(H) && !infect_hunter) + if(ishuman(candidate)) + var/mob/living/carbon/human/h_candidate = candidate + if(isspecieshuman(h_candidate) && !infect_humans)//Can it infect humans? Normally, yes. + continue + else if(isspeciesyautja(h_candidate) && !infect_yautja)//Can it infect yautja? Normally, no. + continue + if(isxeno(candidate) && !infect_xenos)//Can it infect xenos? Normally, no. continue - if(H.stat != DEAD && Adjacent(H) && !H.has_brain_worms()) - choices += H - var/mob/living/carbon/human/target = tgui_input_list(src, "Who do you wish to infest?", "Targets", choices) + if(candidate.stat != DEAD && Adjacent(candidate) && !candidate.has_brain_worms()) + choices += candidate + var/mob/living/carbon/target = tgui_input_list(src, "Who do you wish to infest?", "Targets", choices) if(!target || !src) return if(!Adjacent(target)) @@ -146,10 +159,8 @@ return if(target in view(1, src)) to_chat(src, SPAN_NOTICE("You wiggle into [target]'s ear.")) - /* - if(!target.stat) - to_chat(target, "Something disgusting and slimy wiggles into your ear!") - */ // Let's see how stealthborers work out + if(!stealthy && !target.stat) + to_chat(target, SPAN_DANGER("Something disgusting and slimy wiggles into your ear!")) perform_infestation(target) return else @@ -168,8 +179,7 @@ forceMove(target) host.status_flags |= PASSEMOTES host.verbs += /mob/living/proc/borer_comm - RemoveBorerActions() - GrantInfestActions() + get_host_actions() //Brainslug abandons the host @@ -217,16 +227,16 @@ return if(controlling) detach() - GrantBorerActions() - RemoveInfestActions() + give_new_actions(ACTION_SET_HOSTLESS) + forceMove(get_turf(host)) + apply_effect(1, STUN) + log_interact(src, host, "Borer: [key_name(src)] left their host; [key_name(host)]") host.reset_view(null) var/mob/living/carbon/H = host H.borer = null - H.verbs -= /mob/living/proc/borer_comm - action_talk_to_borer.remove_from(host) H.status_flags &= ~PASSEMOTES host = null return @@ -319,14 +329,7 @@ bonding = FALSE controlling = TRUE - host.verbs += /mob/living/carbon/proc/release_control - host.verbs += /mob/living/carbon/proc/punish_host - host.verbs += /mob/living/carbon/proc/spawn_larvae - host.verbs -= /mob/living/proc/borer_comm - host.verbs += /mob/living/proc/trapped_mind_comm - - GrantControlActions() - action_talk_to_borer.remove_from(host) + give_new_actions(ACTION_SET_CONTROL) host.med_hud_set_status() if(src && !src.key) @@ -366,14 +369,7 @@ controlling = FALSE reset_view(null) - host.verbs -= /mob/living/carbon/proc/release_control - host.verbs -= /mob/living/carbon/proc/punish_host - host.verbs -= /mob/living/carbon/proc/spawn_larvae - host.verbs += /mob/living/proc/borer_comm - host.verbs -= /mob/living/proc/trapped_mind_comm - - RemoveControlActions() - action_talk_to_borer.give_to(host) + get_host_actions() host.med_hud_set_status() sleeping = 0 if(host_brain) @@ -456,7 +452,7 @@ return var/list/choices = list() for(var/mob/living/carbon/C in view(3,src)) - if((C.stat != DEAD) && !(issynth(C))) + if((C != src) && (C.stat != DEAD) && !(issynth(C))) choices += C if(world.time - used_dominate < 300) to_chat(src, SPAN_XENOWARNING("You cannot use that ability again so soon.")) @@ -610,7 +606,6 @@ to_chat(host, SPAN_XENO("[truename] [say_string]: [input]"), type = MESSAGE_TYPE_RADIO) log_say("BORER: ([key_name(src)] to [key_name(host)]) [input]", src) to_chat(src, SPAN_XENO("[truename] [say_string]: [input]"), type = MESSAGE_TYPE_RADIO) - action_talk_to_borer.give_to(host) for (var/mob/dead in GLOB.dead_mob_list) var/track_host = " (F)" if(!istype(dead,/mob/new_player) && !istype(dead,/mob/living/brain)) //No meta-evesdropping diff --git a/code/modules/mob/dead/observer/orbit.dm b/code/modules/mob/dead/observer/orbit.dm index 94d1203493da..39a7eb968368 100644 --- a/code/modules/mob/dead/observer/orbit.dm +++ b/code/modules/mob/dead/observer/orbit.dm @@ -56,6 +56,7 @@ /datum/orbit_menu/ui_static_data(mob/user) var/list/data = list() + var/list/borers = list() var/list/humans = list() var/list/marines = list() var/list/survivors = list() @@ -111,6 +112,9 @@ var/mob/living/player = M serialized["health"] = FLOOR((player.health / player.maxHealth * 100), 1) + if(isborer(player)) + borers += list(serialized) + if(isxeno(player)) var/mob/living/carbon/xenomorph/xeno = player if(xeno.caste) @@ -159,6 +163,7 @@ else if(isAI(M)) humans += list(serialized) + data["borers"] = borers data["humans"] = humans data["marines"] = marines data["survivors"] = survivors diff --git a/code/modules/mob/holder.dm b/code/modules/mob/holder.dm index 61b5c07ba159..1ad847564617 100644 --- a/code/modules/mob/holder.dm +++ b/code/modules/mob/holder.dm @@ -119,5 +119,5 @@ /obj/item/holder/borer name = "cortical borer" desc = "Gross..." - icon = 'icons/mob/animal.dmi' - icon_state = "brainslug_dead" + icon = 'icons/mob/brainslug.dmi' + icon_state = "Borer Dead" diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm index 16d456867f91..1d56f1cdff38 100644 --- a/code/modules/mob/living/carbon/human/human_defines.dm +++ b/code/modules/mob/living/carbon/human/human_defines.dm @@ -136,7 +136,7 @@ var/last_chew = 0 //taken from human.dm - hud_possible = list(HEALTH_HUD,STATUS_HUD, STATUS_HUD_OOC, STATUS_HUD_XENO_INFECTION, STATUS_HUD_XENO_CULTIST, ID_HUD, WANTED_HUD, ORDER_HUD, XENO_HOSTILE_ACID, XENO_HOSTILE_SLOW, XENO_HOSTILE_TAG, XENO_HOSTILE_FREEZE, HUNTER_CLAN, HUNTER_HUD, FACTION_HUD) + hud_possible = list(HEALTH_HUD,STATUS_HUD, STATUS_HUD_OOC, STATUS_HUD_XENO_INFECTION, STATUS_HUD_XENO_CULTIST, ID_HUD, WANTED_HUD, ORDER_HUD, XENO_HOSTILE_ACID, XENO_HOSTILE_SLOW, XENO_HOSTILE_TAG, XENO_HOSTILE_FREEZE, HUNTER_CLAN, HUNTER_HUD, FACTION_HUD, STATUS_HUD_BRAINWORM) var/embedded_flag //To check if we've need to roll for damage on movement while an item is imbedded in us. var/allow_gun_usage = TRUE var/melee_allowed = TRUE diff --git a/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm b/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm index 3449b4db8325..72a0ead4b198 100644 --- a/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm +++ b/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm @@ -44,7 +44,7 @@ see_in_dark = 12 recovery_constant = 1.5 see_invisible = SEE_INVISIBLE_LIVING - hud_possible = list(HEALTH_HUD_XENO, PLASMA_HUD, PHEROMONE_HUD, QUEEN_OVERWATCH_HUD, ARMOR_HUD_XENO, XENO_STATUS_HUD, XENO_BANISHED_HUD, XENO_HOSTILE_ACID, XENO_HOSTILE_SLOW, XENO_HOSTILE_TAG, XENO_HOSTILE_FREEZE, HUNTER_HUD) + hud_possible = list(HEALTH_HUD_XENO, PLASMA_HUD, PHEROMONE_HUD, QUEEN_OVERWATCH_HUD, ARMOR_HUD_XENO, XENO_STATUS_HUD, XENO_BANISHED_HUD, XENO_HOSTILE_ACID, XENO_HOSTILE_SLOW, XENO_HOSTILE_TAG, XENO_HOSTILE_FREEZE, HUNTER_HUD, STATUS_HUD_BRAINWORM) unacidable = TRUE rebounds = TRUE faction = FACTION_XENOMORPH diff --git a/code/modules/mob/living/living_healthscan.dm b/code/modules/mob/living/living_healthscan.dm index 067f39e1ab42..a8323fd98118 100644 --- a/code/modules/mob/living/living_healthscan.dm +++ b/code/modules/mob/living/living_healthscan.dm @@ -153,7 +153,7 @@ GLOBAL_LIST_INIT(known_implants, subtypesof(/obj/item/implant)) //snowflake :3 data["lung_ruptured"] = human_target_mob.is_lung_ruptured() - + data["brainslug"] = human_target_mob.has_brain_worms() //shrapnel, limbs, limb damage, limb statflags, cyber limbs var/core_fracture_detected = FALSE var/unknown_implants = 0 diff --git a/code/modules/reagents/chemistry_properties/prop_positive.dm b/code/modules/reagents/chemistry_properties/prop_positive.dm index 6d928d9fcd7c..b95935162bcc 100644 --- a/code/modules/reagents/chemistry_properties/prop_positive.dm +++ b/code/modules/reagents/chemistry_properties/prop_positive.dm @@ -497,7 +497,7 @@ if(player_2) if(player_2.controlling) player_2.detach() - to_chat(src, SPAN_HIGHDANGER("You relinquish the unknown chemical overwhelms you!")) + to_chat(src, SPAN_HIGHDANGER("You relinquish control as the unknown chemical overwhelms you!")) player_2.leave_host() to_chat(src, SPAN_HIGHDANGER("The overwhelming flow of powerful chemicals forces you to flee your host!")) diff --git a/code/modules/surgery/brainworm.dm b/code/modules/surgery/brainworm.dm new file mode 100644 index 000000000000..3a379605e36b --- /dev/null +++ b/code/modules/surgery/brainworm.dm @@ -0,0 +1,80 @@ +/datum/surgery/borer_removal + name = "Experimental Cranial Parasite Removal" + priority = SURGERY_PRIORITY_MAXIMUM + possible_locs = list("head") + invasiveness = list(SURGERY_DEPTH_DEEP) + pain_reduction_required = PAIN_REDUCTION_MEDIUM + required_surgery_skill = SKILL_SURGERY_TRAINED + steps = list( + /datum/surgery_step/remove_borer, + ) + +/datum/surgery/borer_removal/can_start(mob/user, mob/living/carbon/patient, obj/limb/L, obj/item/tool) + if(!locate(/obj/structure/machinery/optable) in get_turf(patient)) + return FALSE + + return patient.has_brain_worms() + +//------------------------------------ + +/datum/surgery_step/remove_borer + name = "Remove Cranial Parasite" + desc = "extract the cranial parasite" + accept_hand = TRUE + /*Similar to PINCH, but balanced around 100 = using bare hands. Haemostat is faster and better, + other tools are slower but don't burn the surgeon.*/ + tools = list( + /obj/item/tool/surgery/hemostat = 1.5, + /obj/item/tool/wirecutters = SURGERY_TOOL_MULT_SUBOPTIMAL, + /obj/item/tool/kitchen/utensil/fork = SURGERY_TOOL_MULT_SUBSTITUTE + ) + time = 6 SECONDS + preop_sound = 'sound/surgery/hemostat1.ogg' + success_sound = 'sound/surgery/organ2.ogg' + failure_sound = 'sound/effects/acid_sizzle2.ogg' + +/datum/surgery_step/remove_borer/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, tool_type, datum/surgery/surgery) + var/mob/living/carbon/cortical_borer/parasite = target.borer + if(parasite) + to_chat(parasite, SPAN_HIGHDANGER("[user] is attempting to extract you from your host's head!")) + else return FALSE + if(tool) + user.affected_message(target, + SPAN_NOTICE("You try to extract the parasite from [target]'s head with \the [tool]."), + SPAN_NOTICE("[user] tries to extract the parasite from your head with \the [tool]."), + SPAN_NOTICE("[user] tries to extract the parasite from [target]'s head with \the [tool].")) + else + user.affected_message(target, + SPAN_NOTICE("You try to extract the parasite from [target]'s head."), + SPAN_NOTICE("[user] tries to extract the parasite from your head."), + SPAN_NOTICE("[user] tries to extract the parasite from [target]'s head.")) + + target.custom_pain("Something hurts horribly in your head!",1) + log_interact(user, target, "[key_name(user)] started to remove a borer from [key_name(target)]'s skull.") + +/datum/surgery_step/remove_borer/success(mob/living/carbon/user, mob/living/carbon/target, target_zone, obj/item/tool, tool_type, datum/surgery/surgery) + var/mob/living/carbon/cortical_borer/parasite = target.borer + if(parasite) + user.affected_message(target, + SPAN_WARNING("You pull a wriggling parasite out of [target]'s head!"), + SPAN_WARNING("[user] pulls a wriggling parasite out of [target]'s head!"), + SPAN_WARNING("[user] pulls a wriggling parasite out of [target]'s head!")) + + user.count_niche_stat(STATISTICS_NICHE_SURGERY_LARVA) + to_chat(parasite, SPAN_HIGHDANGER("You are ripped forcibly from your host's head!")) + parasite.leave_host() + + log_interact(user, target, "[key_name(user)] removed a parasite from [key_name(target)]'s head with [tool ? "\the [tool]" : "their hands"], ending [surgery].") + +/datum/surgery_step/remove_borer/failure(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool, tool_type, datum/surgery/surgery) + user.affected_message(target, + SPAN_WARNING("Your hand slips, bruising [target]'s brain!"), + SPAN_WARNING("[user]'s hand slips, bruising your brain!"), + SPAN_WARNING("[user]'s hand slips, bruising [target]'s brain!")) + + target.apply_damage(10, BRAIN) + if(target.stat == CONSCIOUS) + target.emote("scream") + target.apply_damage(15, BURN, target_zone) + log_interact(user, target, "[key_name(user)] failed to remove a parasite from [key_name(target)]'s head with [tool ? "\the [tool]" : "their hands"].") + return FALSE diff --git a/colonialmarines.dme b/colonialmarines.dme index d562a3dcbf1c..211c667398c6 100644 --- a/colonialmarines.dme +++ b/colonialmarines.dme @@ -2149,6 +2149,7 @@ #include "code\modules\surgery\amputation.dm" #include "code\modules\surgery\bones.dm" #include "code\modules\surgery\brainrepair.dm" +#include "code\modules\surgery\brainworm.dm" #include "code\modules\surgery\chestburster.dm" #include "code\modules\surgery\eye.dm" #include "code\modules\surgery\face.dm" diff --git a/icons/mob/brainslug.dmi b/icons/mob/brainslug.dmi index db29b87ac953f66b36ab8fd1596c80f537c02914..42e83a7753f6aad2688b75046a5e5244e79c38c1 100644 GIT binary patch delta 4116 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

}`Qw_#44R+UX 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 i_5or|WBR?ni}r>kwCRd~rR?Ee8?NF~Yu diff --git a/tgui/packages/tgui/interfaces/HealthScan.js b/tgui/packages/tgui/interfaces/HealthScan.js index cf3e8e59eab9..84ddb1a53d05 100644 --- a/tgui/packages/tgui/interfaces/HealthScan.js +++ b/tgui/packages/tgui/interfaces/HealthScan.js @@ -31,6 +31,7 @@ export const HealthScan = (props, context) => { implants, core_fracture, lung_ruptured, + brainslug, hugged, detail_level, permadead, @@ -195,6 +196,7 @@ export const HealthScan = (props, context) => { {pulse} {internal_bleeding || + (brainslug && bodyscanner) || implants || hugged || core_fracture || @@ -222,6 +224,9 @@ export const HealthScan = (props, context) => { {lung_ruptured && bodyscanner ? ( Ruptured lung detected! ) : null} + {brainslug && bodyscanner ? ( + Cranial anomaly detected! + ) : null} {core_fracture && healthanalyser ? ( Bone fractures detected! Advanced scanner required for location. diff --git a/tgui/packages/tgui/interfaces/Orbit/index.tsx b/tgui/packages/tgui/interfaces/Orbit/index.tsx index a900ee804a28..9f6e9004b951 100644 --- a/tgui/packages/tgui/interfaces/Orbit/index.tsx +++ b/tgui/packages/tgui/interfaces/Orbit/index.tsx @@ -99,6 +99,7 @@ const ObservableSearch = (props, context) => { const ObservableContent = (props, context) => { const { data } = useBackend(context); const { + borers = [], humans = [], marines = [], survivors = [], @@ -117,6 +118,7 @@ const ObservableContent = (props, context) => { return ( + diff --git a/tgui/packages/tgui/interfaces/Orbit/types.ts b/tgui/packages/tgui/interfaces/Orbit/types.ts index 3fe11af8e625..d3b339ae04ed 100644 --- a/tgui/packages/tgui/interfaces/Orbit/types.ts +++ b/tgui/packages/tgui/interfaces/Orbit/types.ts @@ -2,6 +2,7 @@ import { BooleanLike } from 'common/react'; export type OrbitData = { auto_observe: BooleanLike; + borers: Observable[]; humans: Observable[]; marines: Observable[]; survivors: Observable[]; From 7d23cfa16f2661586d831d83a9eff2b6b9082584 Mon Sep 17 00:00:00 2001 From: forest2001 Date: Sun, 2 Apr 2023 19:28:58 +0100 Subject: [PATCH 03/25] fixes for hivemind --- code/modules/borer/borer.dm | 3 ++- code/modules/mob/language/languages.dm | 14 ++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/code/modules/borer/borer.dm b/code/modules/borer/borer.dm index 81afde2f1be9..047a80b3214a 100644 --- a/code/modules/borer/borer.dm +++ b/code/modules/borer/borer.dm @@ -39,13 +39,14 @@ var/mob/living/carbon/cortical_borer/B = loc if(!istype(B)) log_debug(EXCEPTION("Trapped mind found without a borer!"), src) - return + return FALSE to_chat(src, SPAN_DANGER("You begin doggedly resisting the parasite's control (this will take approximately sixty seconds).")) to_chat(B.host, SPAN_XENOWARNING("You feel the captive mind of [src] begin to resist your control.")) var/delay = (rand(350,450) + B.host.getBrainLoss()) addtimer(CALLBACK(src, PROC_REF(return_control), B), delay) + return TRUE /mob/living/carbon/cortical_borer name = "cortical borer" diff --git a/code/modules/mob/language/languages.dm b/code/modules/mob/language/languages.dm index 5760255ff38d..74f058e1ff66 100644 --- a/code/modules/mob/language/languages.dm +++ b/code/modules/mob/language/languages.dm @@ -213,18 +213,16 @@ key = "0" flags = RESTRICTED|HIVEMIND -/datum/language/corticalborer/broadcast(mob/living/speaker, message, speaker_mask, death) +/datum/language/corticalborer/broadcast(mob/living/carbon/speaker, message, speaker_mask, death) var/mob/living/carbon/cortical_borer/B - if(!speaker_mask) - speaker_mask = speaker - if(iscarbon(speaker)) - var/mob/living/carbon/M = speaker - B = M.has_brain_worms() - else if(istype(speaker,/mob/living/carbon/cortical_borer)) + if(isborer(speaker)) B = speaker - + else if(speaker.has_brain_worms()) + B = speaker.has_brain_worms() if(B) speaker_mask = B.truename + if(!speaker_mask) + speaker_mask = speaker.real_name var/message_start = "[name], [speaker_mask]" var/message_body = "[speech_verb], \"[message]\"" From 7448ba8634f7dc7e64c0fb153efa566e6e1c4b6b Mon Sep 17 00:00:00 2001 From: forest2001 Date: Mon, 3 Apr 2023 06:36:12 +0100 Subject: [PATCH 04/25] Fixes, modernisation & help information --- code/modules/borer/borer.dm | 48 +++- code/modules/borer/borer_procs.dm | 261 +++++++++++------- code/modules/mob/language/languages.dm | 2 +- code/modules/mob/living/carbon/human/human.dm | 3 +- .../mob/living/carbon/xenomorph/XenoProcs.dm | 18 ++ icons/mob/hud/actions_borer.dmi | Bin 2032 -> 2089 bytes 6 files changed, 227 insertions(+), 105 deletions(-) diff --git a/code/modules/borer/borer.dm b/code/modules/borer/borer.dm index 047a80b3214a..54daabcf341b 100644 --- a/code/modules/borer/borer.dm +++ b/code/modules/borer/borer.dm @@ -86,7 +86,7 @@ var/max_contaminant = 120 //Decreases through hibernation or reproduction. var/hibernating = FALSE //Usable inside a host, but not when controlling. Allows clearing of impurities. - var/mob/living/carbon/human/host // Human host for the brain worm. + var/mob/living/carbon/host // Human host for the brain worm. var/truename // Name used for brainworm-speak. var/mob/living/captive_brain/host_brain // Used for swapping control of the body back and forth. var/controlling // Used in human death check. @@ -104,11 +104,13 @@ var/current_actions = ACTION_SET_HOSTLESS var/list/actions_hostless = list( + /datum/action/innate/borer/helpme, /datum/action/innate/borer/toggle_hide, /datum/action/innate/borer/freeze_victim, /datum/action/innate/borer/infest_host ) var/list/actions_humanoidhost = list( + /datum/action/innate/borer/helpme, /datum/action/innate/borer/take_control, /datum/action/innate/borer/talk_to_host, /datum/action/innate/borer/leave_body, @@ -117,12 +119,14 @@ /datum/action/innate/borer/make_chems ) var/list/actions_xenohost = list( + /datum/action/innate/borer/helpme, /datum/action/innate/borer/take_control, /datum/action/innate/borer/talk_to_host, /datum/action/innate/borer/leave_body, /datum/action/innate/borer/hibernate ) var/list/actions_control = list( + /datum/action/innate/borer/helpme, /datum/action/innate/borer/give_back_control, /datum/action/innate/borer/make_larvae, /datum/action/innate/borer/talk_to_brain, @@ -188,6 +192,7 @@ var/datum/language/corticalborer/c_link = GLOB.all_languages[LANGUAGE_BORER] c_link.broadcast(src, null, src.truename, TRUE) SSmob.living_misc_mobs -= src + leave_host() . = ..() /mob/living/carbon/cortical_borer/rejuvenate() @@ -198,6 +203,9 @@ /mob/living/carbon/cortical_borer/Destroy() SSmob.living_misc_mobs -= src + if(host) + for(var/datum/action/innate/borer/action in host.actions) + action.hide_from(host) return ..() //###################################################// @@ -254,9 +262,14 @@ . += "Can Reproduce: [CR]" . += "Enzymes: [round(enzymes)]/[round(max_enzymes)]" . += "Contaminant: [round(contaminant)]/[round(max_contaminant)]" + . += "Health: P:[round(getBruteLoss())] B:[round(getFireLoss())] T:[round(getToxLoss())]" if(host) . += "" - . += "Host Brain Damage: [host.brainloss]/100" + if(ishuman(host)) + . += "Host Brain Damage: [host.brainloss]/100" + else if(isxeno(host)) + // . += "Host Plasma: [plasma_stored]/[plasma_max]" + . += "Host Integrity: [health]/[maxHealth]" /mob/living/carbon/cortical_borer/say(message)//I need to parse the message properly so it doesn't look stupid var/datum/language/parsed_language = parse_language(message) @@ -279,9 +292,14 @@ ..() update_canmove() update_icons() + var/heal_amt = 1 if(host) + heal_amt = 3 if(!stat && host.stat != DEAD) - if(((host.chem_effect_flags & CHEM_EFFECT_ANTI_PARASITE) && !host.reagents.has_reagent("benzyme")) || host.reagents.has_reagent("bcure")) + var/mob/living/carbon/human/human_host + if(ishuman(host)) + human_host = host + if(((human_host.chem_effect_flags & CHEM_EFFECT_ANTI_PARASITE) && !human_host.reagents.has_reagent("benzyme")) || human_host.reagents.has_reagent("bcure")) if(!docile) if(controlling) to_chat(host, SPAN_XENODANGER("You feel the flow of a soporific chemical in your host's blood, lulling you into docility.")) @@ -291,7 +309,7 @@ else if(docile) if(controlling) - to_chat(host, SPAN_XENONOTICE("You shake off your lethargy as the chemical leaves your host's blood.")) + to_chat(human_host, SPAN_XENONOTICE("You shake off your lethargy as the chemical leaves your host's blood.")) else to_chat(src, SPAN_XENONOTICE("You shake off your lethargy as the chemical leaves your host's blood.")) docile = FALSE @@ -314,9 +332,31 @@ contaminant = max(contaminant - 0.3, 0) else SetLuminosity(0) + if(bruteloss || fireloss) + heal_overall_damage(heal_amt, heal_amt) + if(toxloss) + apply_damage(-(heal_amt/2), TOX) + +//################### ABILITIES ###################// /datum/action/innate/borer icon_file = 'icons/mob/hud/actions_borer.dmi' +/datum/action/innate/borer/helpme + name = "Help!" + action_icon_state = "borer_help" + +/datum/action/innate/borer/helpme/action_activate() + var/mob/living/carbon/cortical_borer/B + if(!isborer(owner)) + if(owner.has_brain_worms()) + B = owner.has_brain_worms() + else + to_chat(owner, SPAN_DANGER("How did you get this command? It's gone now.")) + hide_action(owner, /datum/action/innate/borer/helpme) + else + B = owner + B.show_help() + /datum/action/innate/borer/talk_to_host name = "Converse with Host" action_icon_state = "borer_whisper" diff --git a/code/modules/borer/borer_procs.dm b/code/modules/borer/borer_procs.dm index 37e036a6b850..224cdc2fac5f 100644 --- a/code/modules/borer/borer_procs.dm +++ b/code/modules/borer/borer_procs.dm @@ -1,3 +1,45 @@ +//############# HELP ################## +/mob/living/carbon/cortical_borer/verb/show_help() + set category = "Borer" + set name = "Show Help" + set desc = "Show help for borer commands." + + var/target = src + + var/list/options = list("Communicating") + if(!host) + options += list("Infecting a host") + else if(controlling) + target = host + options = list("Captive Host", "Releasing Control", "Reproducing") + else if(isxeno(host)) + options = list("Assuming Control","Contaminant & Enzymes","Hibernation","Reproducing") + else + options = list("Assuming Control","Contaminant & Enzymes","Hibernation","Secreting Chemicals","Reproducing") + + var/choice = tgui_input_list(target, "What would you like help with?", "Help", options, 20 SECONDS) + + var/help_message = "" + switch(choice) + if("Infecting a host") + help_message = "Infecting a host is the first major step for a borer to complete.\n\nThis is done by getting close to a potential host (This can be Human, Xeno or Yautja depending on settings controlled by admins) and clicking the Infest button in the top left of your screen.\n\nYour host will need to keep still for you to do this, and it's rare that they do so; for this reason you have Dominate Victim, which will allow you to temporarily stun a target.\n\nNote: This is not sufficient to keep them down for 100% of the time it takes to infect however, so be careful with it." + if("Communicating") + help_message = "All borers share a hivemind, the Cortical Link, this can be accessed using :0 (that's ZERO).\n\nThe hivemind can be used by any borer who is NOT in direct control of their host.\n\nAny borer in direct control of a host can only hear what their host can hear.\n\nA borer inside a host can communicate directly with that host using 'Converse with Host'." + if("Captive Host") + help_message = "" + if("Releasing Control") + help_message = "" + if("Reproducing") + help_message = "" + if("Assuming Control") + help_message = "" + if("Contaminant & Enzymes", "Hibernation") + help_message = "" + if("Secreting Chemicals") + help_message = "" + + alert(target, help_message, choice, "Ok") + //############# Physical Interaction Procs ############# /mob/living/carbon/cortical_borer/UnarmedAttack(atom/A) if(istype(A, /obj/structure/ladder)) @@ -33,16 +75,17 @@ for(var/atom/movable/AM in get_turf(S)) if(AM != S && AM.density && AM.BlockedPassDirs(src, move_dir)) to_chat(src, SPAN_WARNING("\The [AM] prevents you from squeezing under \the [S]!")) - return + return FALSE // Is it an airlock? if(istype(S, /obj/structure/machinery/door/airlock)) var/obj/structure/machinery/door/airlock/A = S if(A.locked || A.welded) //Can't pass through airlocks that have been bolted down or welded to_chat(src, SPAN_WARNING("\The [A] is locked down tight. You can't squeeze underneath!")) - return + return FALSE visible_message(SPAN_WARNING("\The [src] scuttles underneath \the [S]!"), \ SPAN_WARNING("You squeeze and scuttle underneath \the [S]."), null, 5) forceMove(S.loc) + return TRUE //############# Action Give/Take Procs ############# /mob/living/carbon/cortical_borer/proc/give_new_actions(actions_list = ACTION_SET_HOSTLESS, target = src) @@ -69,6 +112,8 @@ return FALSE abilities_to_give = actions_control.Copy() target = host + for(var/datum/action/innate/borer/talk_to_borer/action in host.actions) + action.hide_from(host) for(var/path in abilities_to_give) give_action(target, path) @@ -102,11 +147,27 @@ return FALSE /mob/living/carbon/has_brain_worms() - if(borer) + if(isborer(borer)) return borer else return FALSE +/mob/living/carbon/cortical_borer/proc/can_target(mob/living/carbon/target) + var/obj/limb/head/head = target.get_limb("head") + if((isborer(target)) || (head?.status & (LIMB_DESTROYED|LIMB_ROBOT|LIMB_SYNTHSKIN)))//No infecting synths, or borers. + return FALSE + if(ishuman(target)) + var/mob/living/carbon/human/human_target = target + if(isspecieshuman(human_target) && !infect_humans)//Can it infect humans? Normally, yes. + return FALSE + else if(isspeciesyautja(human_target) && !infect_yautja)//Can it infect yautja? Normally, no. + return FALSE + if(isxeno(target) && !infect_xenos)//Can it infect xenos? Normally, no. + return FALSE + if(target.stat != DEAD && Adjacent(target) && !target.has_brain_worms()) + return TRUE + else + return FALSE //Brainslug infests a target /mob/living/carbon/cortical_borer/verb/infest() @@ -116,63 +177,50 @@ if(host) to_chat(src, "You are already within a host.") - return + return FALSE if(stat) to_chat(src, "You cannot infest a target in your current state.") - return + return FALSE var/list/choices = list() for(var/mob/living/carbon/candidate in view(1,src)) - var/obj/limb/head/head = candidate.get_limb("head") - if((isborer(candidate)) || (head?.status & (LIMB_DESTROYED|LIMB_ROBOT|LIMB_SYNTHSKIN)))//No infecting synths, or borers. - continue - if(ishuman(candidate)) - var/mob/living/carbon/human/h_candidate = candidate - if(isspecieshuman(h_candidate) && !infect_humans)//Can it infect humans? Normally, yes. - continue - else if(isspeciesyautja(h_candidate) && !infect_yautja)//Can it infect yautja? Normally, no. - continue - if(isxeno(candidate) && !infect_xenos)//Can it infect xenos? Normally, no. - continue - if(candidate.stat != DEAD && Adjacent(candidate) && !candidate.has_brain_worms()) + if(can_target(candidate)) choices += candidate var/mob/living/carbon/target = tgui_input_list(src, "Who do you wish to infest?", "Targets", choices) - if(!target || !src) - return - if(!Adjacent(target)) - return + if(!target || !src || !Adjacent(target)) + return FALSE if(target.has_brain_worms()) to_chat(src, SPAN_WARNING("You cannot infest someone who is already infested!")) - return + return FALSE if(is_mob_incapacitated()) - return + return FALSE to_chat(src, SPAN_NOTICE("You slither up [target] and begin probing at their ear canal...")) if(!do_after(src, 50, INTERRUPT_ALL_OUT_OF_RANGE, BUSY_ICON_HOSTILE, target)) to_chat(src, SPAN_WARNING("As [target] moves away, you are dislodged and fall to the ground.")) - return + return FALSE if(!target || !src) - return + return FALSE if(stat) to_chat(src, SPAN_XENOWARNING("You cannot infest a target in your current state.")) - return + return FALSE if(target.stat == DEAD) - to_chat(src, SPAN_WARNING("That is not an appropriate target.")) - return + to_chat(src, SPAN_WARNING("You cannot infest the dead.")) + return FALSE if(target in view(1, src)) to_chat(src, SPAN_NOTICE("You wiggle into [target]'s ear.")) if(!stealthy && !target.stat) to_chat(target, SPAN_DANGER("Something disgusting and slimy wiggles into your ear!")) perform_infestation(target) - return + return TRUE else to_chat(src, "They are no longer in range!") - return + return FALSE /mob/living/carbon/cortical_borer/proc/perform_infestation(mob/living/carbon/target) if(!target) - return + return FALSE if(target.has_brain_worms()) to_chat(src, SPAN_XENOWARNING("[target] is already infested!")) - return + return FALSE host = target log_interact(src, host, "Borer: [key_name(src)] Infested [key_name(host)]") target.borer = src @@ -180,6 +228,7 @@ host.status_flags |= PASSEMOTES host.verbs += /mob/living/proc/borer_comm get_host_actions() + return TRUE //Brainslug abandons the host @@ -189,42 +238,42 @@ set desc = "Slither out of your host." if(!host) to_chat(src, SPAN_XENOWARNING("You are not inside a host body.")) - return + return FALSE if(stat) to_chat(src, SPAN_XENOWARNING("You cannot leave your host in your current state.")) - return + return FALSE if(docile) to_chat(src, SPAN_XENOWARNING("You are feeling far too docile to do that.")) - return + return FALSE if(!host || !src) - return + return FALSE if(leaving) leaving = FALSE to_chat(src, SPAN_XENOWARNING("You decide against leaving your host.")) - return + return TRUE to_chat(src, SPAN_XENOHIGHDANGER("You begin disconnecting from [host]'s synapses and prodding at their internal ear canal.")) leaving = TRUE addtimer(CALLBACK(src, PROC_REF(let_go)), 200) + return TRUE /mob/living/carbon/cortical_borer/proc/let_go() if(!host || !src || QDELETED(host) || QDELETED(src)) - return - if(!leaving) - return - if(controlling) - return + return FALSE + if(!leaving || controlling) + return FALSE if(stat) to_chat(src, SPAN_XENOWARNING("You cannot release a target in your current state.")) - return + return FALSE to_chat(src, SPAN_XENOHIGHDANGER("You wiggle out of [host]'s ear and plop to the ground.")) leaving = FALSE leave_host() + return TRUE /mob/living/carbon/cortical_borer/proc/leave_host() if(!host) - return + return FALSE if(controlling) detach() give_new_actions(ACTION_SET_HOSTLESS) @@ -239,7 +288,7 @@ H.borer = null H.status_flags &= ~PASSEMOTES host = null - return + return TRUE //Brainslug takes control of the body @@ -250,43 +299,44 @@ if(!host) to_chat(src, SPAN_XENOWARNING("You are not inside a host body.")) - return + return FALSE if(host.stat == DEAD) to_chat(src, SPAN_XENODANGER("This host is in no condition to be controlled.")) - return + return FALSE if(stat) to_chat(src, SPAN_XENOWARNING("You cannot do that in your current state.")) - return + return FALSE if(docile) to_chat(src, SPAN_XENOWARNING("You are feeling far too docile to do that.")) - return + return FALSE if(bonding) bonding = FALSE to_chat(src, SPAN_XENOWARNING("You stop attempting to take control of your host.")) - return + return FALSE to_chat(src, "You begin delicately adjusting your connection to the host brain...") if(QDELETED(src) || QDELETED(host)) - return + return FALSE bonding = TRUE var/delay = 300+(host.getBrainLoss()*5) addtimer(CALLBACK(src, PROC_REF(assume_control)), delay) + return TRUE /mob/living/carbon/cortical_borer/proc/assume_control() if(!host || !src || controlling) - return + return FALSE if(!bonding) - return + return FALSE if(docile) to_chat(src, SPAN_XENOWARNING("You are feeling far too docile to do that.")) - return + return FALSE else to_chat(src, SPAN_XENOHIGHDANGER("You plunge your probosci deep into the cortex of the host brain, interfacing directly with their nervous system.")) to_chat(host, SPAN_HIGHDANGER("You feel a strange shifting sensation behind your eyes as an alien consciousness displaces yours.")) @@ -334,16 +384,17 @@ if(src && !src.key) src.key = "@[borer_key]" - return + return TRUE //Captive mind reclaims their body. /mob/living/captive_brain/proc/return_control(mob/living/carbon/cortical_borer/B) if(!B || !B.controlling) - return + return FALSE B.host.adjustBrainLoss(rand(5,10)) to_chat(src, SPAN_DANGER("With an immense exertion of will, you regain control of your body!")) to_chat(B.host, SPAN_XENODANGER("You feel control of the host brain ripped from your grasp, and retract your probosci before the wild neural impulses can damage you.")) B.detach() + return TRUE ///Brain slug proc for voluntary removal of control. /mob/living/carbon/proc/release_control() @@ -364,7 +415,7 @@ /mob/living/carbon/cortical_borer/proc/detach() if(!host || !controlling) - return + return FALSE controlling = FALSE reset_view(null) @@ -396,20 +447,21 @@ host.computer_id = b2h_id if(!host.lastKnownIP) host.lastKnownIP = b2h_ip - qdel(host_brain) - return + qdel(host_brain) + return TRUE //Host Has died /mob/living/carbon/cortical_borer/proc/host_death(perma = FALSE) if(!(host && loc == host)) log_debug("Borer ([key_name(src)]) called host_death without being inside a host!") - return + return FALSE if(controlling) detach() to_chat(src, SPAN_HIGHDANGER("You release your proboscis and flee as the psychic shock of your host's death washes over you!")) if(perma) to_chat(src, SPAN_HIGHDANGER("You flee your host in anguish!")) leave_host() + return TRUE //############# External Ability Procs ############# /mob/living/carbon/cortical_borer/verb/hide_borer() @@ -419,10 +471,10 @@ if(host) to_chat(usr, SPAN_XENOWARNING("You cannot do this while you're inside a host.")) - return + return FALSE if(stat != CONSCIOUS) - return + return FALSE if(!hiding) layer = TURF_LAYER+0.2 @@ -432,6 +484,7 @@ layer = MOB_LAYER to_chat(src, SPAN_XENONOTICE("You stop hiding.")) hiding = FALSE + return TRUE /mob/living/carbon/cortical_borer/verb/dominate_victim() set category = "Borer" @@ -440,53 +493,53 @@ if(world.time - used_dominate < 150) to_chat(src, SPAN_XENOWARNING("You cannot use that ability again so soon.")) - return + return FALSE if(host) to_chat(src, SPAN_XENOWARNING("You cannot do that from within a host body.")) - return + return FALSE if(stat) to_chat(src, SPAN_XENOWARNING("You cannot do that in your current state.")) - return + return FALSE if(attempting_to_dominate) to_chat(src, SPAN_XENOWARNING("You're already targeting someone!")) - return + return FALSE var/list/choices = list() for(var/mob/living/carbon/C in view(3,src)) - if((C != src) && (C.stat != DEAD) && !(issynth(C))) + if(can_target(C)) choices += C if(world.time - used_dominate < 300) to_chat(src, SPAN_XENOWARNING("You cannot use that ability again so soon.")) - return + return FALSE attempting_to_dominate = TRUE var/mob/living/carbon/M = tgui_input_list(src, "Who do you wish to dominate?", "Targets", choices) if(!M) attempting_to_dominate = FALSE - return + return FALSE if(!src) //different statement to avoid a runtime since if the source is deleted then attempting_to_dominate would also be deleted - return + return FALSE if(M.has_brain_worms()) to_chat(src, SPAN_XENOWARNING("You cannot dominate someone who is already infested!")) attempting_to_dominate = FALSE - return + return FALSE if(is_mob_incapacitated()) attempting_to_dominate = FALSE - return + return FALSE if(get_dist(src, M) > 5) //to avoid people remotely doing from across the map etc, 7 is the default view range to_chat(src, SPAN_XENOWARNING("You're too far away!")) attempting_to_dominate = FALSE - return + return FALSE to_chat(src, SPAN_XENONOTICE("You begin to focus your psychic lance on [M], this will take a few seconds.")) if(!do_after(src, 30, INTERRUPT_OUT_OF_RANGE, NO_BUSY_ICON, M, max_dist = 5)) to_chat(src, SPAN_XENODANGER("You are out of position to dominate [M], get closer!")) attempting_to_dominate = FALSE - return + return FALSE to_chat(src, SPAN_XENOWARNING("You focus your psychic lance on [M] and freeze their limbs with a wave of terrible dread.")) to_chat(M, SPAN_WARNING("You feel a creeping, horrible sense of dread come over you, freezing your limbs and setting your heart racing.")) M.KnockDown(3) used_dominate = world.time attempting_to_dominate = FALSE - + return TRUE //############# Internal Abiity Procs ############# /mob/living/carbon/proc/punish_host() @@ -497,11 +550,12 @@ var/mob/living/carbon/cortical_borer/B = has_brain_worms() if(!B) - return + return FALSE if(B.host_brain) to_chat(src, SPAN_XENONOTICE("You send a punishing spike of psychic agony lancing into your host's brain.")) to_chat(B.host_brain, SPAN_XENOHIGHDANGER("Horrific, burning agony lances through you, ripping a soundless scream from your trapped mind!")) + return TRUE /mob/living/carbon/proc/spawn_larvae() @@ -512,7 +566,7 @@ var/mob/living/carbon/cortical_borer/B = has_brain_worms() if(!B) - return + return FALSE if(B.can_reproduce) if(B.enzymes >= BORER_LARVAE_COST) to_chat(src, SPAN_XENOWARNING("Your host twitches and quivers as you rapdly excrete a larva from your sluglike body.")) @@ -523,11 +577,13 @@ B.contaminant = 0 var/repro = max(B.can_reproduce - 1, 0) new /mob/living/carbon/cortical_borer(T, B.generation + 1, TRUE, repro) + return TRUE else to_chat(src, SPAN_XENONOTICE("You need at least [BORER_LARVAE_COST] enzymes to reproduce!")) - return + return FALSE else to_chat(src, SPAN_XENOWARNING("You are not allowed to reproduce!")) + return FALSE @@ -538,21 +594,25 @@ if(!host) to_chat(src, SPAN_XENOWARNING("You are not inside a host body.")) - return + return FALSE if(stat) to_chat(src, SPAN_XENOWARNING("You cannot secrete chemicals in your current state.")) + return FALSE if(docile) to_chat(src, SPAN_XENOWARNING("You are feeling far too docile to do that.")) - return + return FALSE var/content = "" content += "" if(ishuman(host)) - if(isspeciesyautja(host)) + var/mob/living/carbon/human/human_host + if(ishuman(host)) + human_host = host + if(isspeciesyautja(human_host)) for(var/datum in subtypesof(/datum/borer_chem/yautja)) var/datum/borer_chem/C = datum var/chem = initial(C.chem_id) @@ -574,7 +634,7 @@ usr << browse(null, "window=ViewBorer\ref[src]Chems;size=585x400") usr << browse(html, "window=ViewBorer\ref[src]Chems;size=585x400") - return + return TRUE @@ -586,19 +646,19 @@ if(!host) to_chat(src, "You do not have a host to communicate with!") - return + return FALSE if(host.stat == DEAD) to_chat(src, SPAN_XENODANGER("Not even you can commune with the dead.")) - return + return FALSE - if(stat) - to_chat(src, "You cannot do that in your current state.") - return + if(stat == DEAD) + to_chat(src, "You're dead, Jim.") + return FALSE var/input = stripped_input(src, "Please enter a message to tell your host.", "Borer", "") if(!input) - return + return FALSE if(src && !QDELETED(src) && !QDELETED(host)) var/say_string = (docile) ? "slurs" :"states" @@ -610,6 +670,7 @@ var/track_host = " (F)" if(!istype(dead,/mob/new_player) && !istype(dead,/mob/living/brain)) //No meta-evesdropping dead.show_message(SPAN_BORER("BORER: ([truename] to [host.real_name][track_host]) [say_string]: [input]"), SHOW_MESSAGE_VISIBLE) + return TRUE /mob/living/carbon/cortical_borer/verb/toggle_silence_inside_host() set name = "Toggle speech inside Host" @@ -631,15 +692,15 @@ var/mob/living/carbon/cortical_borer/B = has_brain_worms() if(!B) - return + return FALSE if(stat == DEAD) to_chat(src, SPAN_XENODANGER("You're dead, Jim.")) - return + return FALSE var/input = stripped_input(src, "Please enter a message to tell the borer.", "Message", "") if(!input) - return + return FALSE to_chat(B, SPAN_XENO("[src] says: [input]"), type = MESSAGE_TYPE_RADIO) log_say("BORER: ([key_name(src)] to [key_name(B)]) [input]", src) @@ -648,6 +709,7 @@ var/track_host = " (F)" if(!istype(dead,/mob/new_player) && !istype(dead,/mob/living/brain)) //No meta-evesdropping dead.show_message(SPAN_BORER("BORER: ([name][track_host] to [B.truename]) says: [input]"), SHOW_MESSAGE_VISIBLE) + return TRUE /mob/living/proc/trapped_mind_comm() set name = "Converse with Trapped Mind" @@ -657,11 +719,11 @@ var/mob/living/carbon/cortical_borer/B = has_brain_worms() if(!B || !B.host_brain) - return + return FALSE var/mob/living/captive_brain/CB = B.host_brain var/input = stripped_input(src, "Please enter a message to tell the trapped mind.", "Message", "") if(!input) - return + return FALSE to_chat(CB, SPAN_XENO("[B.truename] says: [input]"), type = MESSAGE_TYPE_RADIO) log_say("BORER: ([key_name(src)] to [key_name(CB)]) [input]", B) @@ -670,7 +732,7 @@ var/track_borer = " (F)" if(!istype(dead,/mob/new_player) && !istype(dead,/mob/living/brain)) //No meta-evesdropping dead.show_message(SPAN_BORER("BORER: ([B.truename][track_borer] to [real_name] (trapped mind)) says: [input]"), SHOW_MESSAGE_VISIBLE) - + return TRUE @@ -679,10 +741,10 @@ if(href_list["borer_use_chem"]) locate(href_list["src"]) if(!istype(src, /mob/living/carbon/cortical_borer)) - return + return FALSE if(docile) to_chat(src, SPAN_XENOWARNING("You are feeling far too docile to do that.")) - return + return FALSE var/topic_chem = href_list["borer_use_chem"] var/datum/borer_chem/C = null @@ -694,15 +756,15 @@ break if(!C || !host || controlling || !src || stat) - return + return FALSE var/datum/reagent/R = chemical_reagents_list[C.chem_id] if(enzymes < C.cost) to_chat(src, SPAN_XENOWARNING("You need [C.cost] enzymes stored to secrete [R.name]!")) - return + return FALSE var/contamination = round(C.cost / 10) if(C.impure && ((contaminant + contamination) > max_contaminant)) to_chat(src, SPAN_XENOWARNING("You are too contaminated to secrete [R.name]!")) - return + return FALSE to_chat(src, SPAN_XENONOTICE("You squirt a measure of [R.name] from your reservoirs into [host]'s bloodstream.")) contaminant += contamination host.reagents.add_reagent(C.chem_id, C.quantity) @@ -712,4 +774,5 @@ // This is used because we use a static set of datums to determine what chems are available, // instead of a table or something. Thus, when we instance it, we can safely delete it qdel(C) + return TRUE ..() diff --git a/code/modules/mob/language/languages.dm b/code/modules/mob/language/languages.dm index 74f058e1ff66..905b6a07670f 100644 --- a/code/modules/mob/language/languages.dm +++ b/code/modules/mob/language/languages.dm @@ -243,4 +243,4 @@ var/area/A = get_area(speaker) to_chat(player, "[message_start] has [SPAN_BOLD("perished")][A? " at [sanitize_area(A.name)]":""]!") else - to_chat(player, "[ghost? "(F) ":""][message_start] [message_body]") + to_chat(player, "[ghost? "(F) ":""][message_start][message_body]") diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index accd036c16e6..d0eac93c78e9 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -136,7 +136,7 @@ if(eta_status) . += "Evacuation: [eta_status]" - var/mob/living/carbon/cortical_borer/B = borer + var/mob/living/carbon/cortical_borer/B = has_brain_worms() if(B && B.controlling) var/CR = "Yes" @@ -150,6 +150,7 @@ . += "Name: [B.truename]" . += "Can Reproduce: [CR]" . += "Enzymes: [round(B.enzymes)]/[round(B.max_enzymes)]" + . += "Health: P:[round(getBruteLoss())] B:[round(getFireLoss())] T:[round(getToxLoss())]" . += "" . += "Host Brain Damage: [brainloss]/100" diff --git a/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm b/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm index d2c099d0c1ff..6a592494756e 100644 --- a/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm +++ b/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm @@ -144,6 +144,24 @@ else . += "Hive Orders: -" + var/mob/living/carbon/cortical_borer/B = has_brain_worms() + if(B && B.controlling) + + var/CR = "Yes" + if(!B.can_reproduce) + CR = "Forbidden" + else if((B.enzymes < BORER_LARVAE_COST)) + CR = "No" + + . += "" + . += "Borer:" + . += "Name: [B.truename]" + . += "Can Reproduce: [CR]" + . += "Enzymes: [round(B.enzymes)]/[round(B.max_enzymes)]" + . += "Health: P:[round(getBruteLoss())] B:[round(getFireLoss())] T:[round(getToxLoss())]" + . += "" + . += "Host Plasma: [plasma_stored]/[plasma_max]" + . += "Host Integrity: [health]/[maxHealth]" . += "" //A simple handler for checking your state. Used in pretty much all the procs. diff --git a/icons/mob/hud/actions_borer.dmi b/icons/mob/hud/actions_borer.dmi index 9447b8f546211f96dd4fa71ebe28446e13d25f14..99efeb7c5b44c4d1e5199e21cd035c156133dbf6 100644 GIT binary patch delta 1930 zcmV;52X*-H52+B4wSU2SR9JLGWpiV4X>fFDZ*Bkpc$}5e!3u*g3J07mPbxQXkj;B2)znXS2; zIWu4fllCSinP4lkmq)-(8JJ?mnYI|WX~|iSa)Q17u`$h#NPkyjV*n&-w1V(Y^=6!=!#>!Byxm+UeAhpXdD5;kv!i8Y9a$;_(yY5x7Fui z--CbA6bNY+E&u=ql1W5CRCt{2n~Qp)N(_KyXDD*9qg~6fg9zPT-v1F#GGTCTfJ$w5 zzmM$Js=?$Z$$tzaZC9&B7WpeA3dyhU)2|{%!g(CW-E;Qf>n-_0{TU!xuh;x;09?xu z08i8PI?ecifqW(ahL?m70VEKCB#nn16gkU_AqShyBYt~)+-ySnvwtE80L18j6o4X8 z+!6sEck$N*A%HA_0A!G(c-(>T9X#UcpqWUyxp zfqw(A=4Sxq-v&_k053p*{HxitLX7 z(0o7$_qKbgzjtdu4gipSDOu$KsNyeZJ^(dvm)qt{(&w;=Mpv7MZYR}aRQC!jA>kuE0;rRhURg&zh7qT(| z)cF@Z01;f#-z$Ks8k`?U(rW-BaEnlgem)=qLn!R;g#hpH;I-Eg6<(Y`1dam(3@Lm2 z(k;?eA;7;eAHa3sq+qnaR}qkMaDRSKNzfYhaUEDGD8Rop9{>U|4B++mQ}crg{9E>c zz)pcEocRF6w;hb!2WC6m$v z0h*>hsW)5Jwx{;{0ouB58h@>5oAzda%|`#^H*DY)B&3fZ_NRzN1VJ$feSW~cK7f?r zA3l@&^P>w;*L4d76#W+K!2kT9eL4UYk0AOG*8n>H1ELWueWc2do#2aN;D3I^zB&Nk zq+h}4UN{EO=^xfTygK?fAgCMoe>xFC?R|dCJ|58P`y*@vz5kxT)PKJ-j6vH*&8e@& zhs*850b2j@(H~(M(EdL^dje}8cyC#x0$q0@Z~!{{A0Ozzv%iWq%sqf@OargonSXvF zfr`Yd6@glBKdf#70NOryU;+E{0p8e~2558$f&d>Z3@|qZjJ>z-b_nXLQZTm&;hneo zT@^o8&^h3|8w{2HlMxRCk49w1_Tyrzt8^j`xIdh@X5vj^5Ab;%?G$R1^xN| zND&n*7~L}kBMugTy93Of4=l3CzbyX)wKBtRsOt7S Q00000NkvXXt^-0~f=&~GnE(I) delta 1872 zcmV-W2e0_45bzI>wST>OR9JLGWpiV4X>fFDZ*Bkpc$}4z!3u*g42I9`Qxv@q-FDq& zD8s{i1?g(E1#Km%`udXzI}8T#_W$7ve1W9uUOuW?70*g8faHQ#H+4N_Qmn3xb}N^& z6c$`><6bE02wO~E0RbChVWdQg)*)@vT#63$2z&lvDbAjWu7B3i1jwXK@Dann)i9c? zF@Rox$;N;q_{gN4?|`_lb*=M#lO4lzCXO&+XlKJclm>w5h|hQ7uTiSXy7+wI8!|t{ zRIT+gNdN!_T}ebiRCt{2nu~gyIt+lrY)T-cPP&wnnh@xEdH+Y8WMSefG|6ew?;Bd1 z3j*m!mTjbNv45ClmLDNWNPgW;zepGf=V_YupV@=2x8#4+p8&Gua>>69fNO~X@H}5G z^MXeVtgZyW@RINtKn4-W@-*(CT(MOdbFf}N;@jindL7cA{1ZU{AVvS902GPxk_hm$ zkH01e0TdYopnx$-#~ldY!6V*2{^|gD0DRdCGI#(g`G3J`2)+AfC4~d9F(Tjtfd4AX zwPY0#ctfD3DQE*Z0N$TagXcUy|A+FtE202f4XyhYR355tsnE1^`hxfCR=6^QGDfhLsq;awEbEwena1 zNZwmfq=uZIH9}rOk(fa<&0BB#v0R;62M8rV6 zz9;=*3P6&583&;Lfd9xfRo@R0s1NK=#{tNG9R(<=BLHMyN>+OSD)~$54?qrJ>2XGVZ}$)CKm^pWn(PCftX%*>IiUIj__wT9SSBMb;*Oy8m(j zAc8aMdj(L}(f)xXy#^oxHwlFp`U4`+gu?n>2=ERsUU{8R;-wix;1FqGNIBS-Zjvra z0sf8t0L}wv2uACB6#*%u{exP9)_<^%^S~N{68u~J0U!`@0I$BE>L1kL-?9$`_7I5D z=?_3m&M2?#{(+RWI|Q_befBotnMKzI?D0)X5a`UlQJBc*3wS`iR|{=f);S6=r? z2-EO@0JR$d8L)2w1p5QR0T`+8DFSK~fWcqugZ8Bb0PC;W9}sSTV7$Ih`hRuE+7K)N zSbt6X(zd@)&j40=F0B5*RDG|L@DKq|&j6NvY1v<>mB;Q62(v#hRo|a>!$S~2tvt4U zY1$9=2ZWIWQ}z91{~%yrnmI7bEVIlq%eU134`09j`ue|Vjx8SR+NN!~v#-A%plzF@ zI@qzUJ9f7R=$fW&wW4dgi+=&uYyFqsuz^>QkUoLf9}|`k1jQh9eZ;;#ft2AtK9l>? zs|(OHO$P)N{SNcMzdmZ8PC&&Yh(5(NfR6uwXaq~2sPtn;_@x;5*N@m&C*VQ)FBtt7 z#{fG0!}^z3NB;%{O$$HABM~&-^<(z&gh4-?VH+6y4+N(EonZ{RE`Mo{LoPmi-9DV4 z^&cO@8I}R<|MPPou-1Y1jwLG4O&(`=O+@VNW5AR zX!P>Knl=ES?Slsvuzx+k8++3LtqwsD;Ddz$t{no#-n(}@1kG7PaBUL8k+LG>Be&oav_v&=GG zzkfTx)MhaQ+zcS{$aO1#=tJji07f3WZvrq@c-#VDwDh_GKuPcaRQ Date: Mon, 3 Apr 2023 08:17:40 +0100 Subject: [PATCH 05/25] Brain follow, help details & follow tweak. Brains! --- code/__DEFINES/typecheck/xenos.dm | 1 + code/__HELPERS/unsorted.dm | 2 + .../admin/player_panel/player_panel.dm | 2 + code/modules/borer/borer.dm | 45 +++++++++++--- code/modules/borer/borer_procs.dm | 55 ++++++++++++------ code/modules/mob/dead/observer/orbit.dm | 8 +-- code/modules/mob/living/carbon/human/human.dm | 3 +- .../mob/living/carbon/xenomorph/XenoProcs.dm | 3 +- code/modules/mob/living/living_defines.dm | 3 + icons/mob/hud/actions_borer.dmi | Bin 2089 -> 2187 bytes tgui/packages/tgui/interfaces/Orbit/index.tsx | 4 +- tgui/packages/tgui/interfaces/Orbit/types.ts | 2 +- 12 files changed, 91 insertions(+), 37 deletions(-) diff --git a/code/__DEFINES/typecheck/xenos.dm b/code/__DEFINES/typecheck/xenos.dm index 6ae8fe1955a0..a71dbd287e7a 100644 --- a/code/__DEFINES/typecheck/xenos.dm +++ b/code/__DEFINES/typecheck/xenos.dm @@ -1,6 +1,7 @@ //Xenomorph Hud Test APOPHIS 22MAY2015 #define isxeno(A) (istype(A, /mob/living/carbon/xenomorph)) #define isborer(A) (istype(A, /mob/living/carbon/cortical_borer)) +#define iscaptivemind(A) (istype(A, /mob/living/captive_brain)) #define isxeno_human(A) (isxeno(A) || ishuman(A)) //ask walter if i should turn into castechecks diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index 25890101758b..e190a64be913 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -626,6 +626,8 @@ moblist.Add(M) for(var/mob/living/carbon/cortical_borer/M in sortmob) moblist.Add(M) + for(var/mob/living/captive_brain/M in sortmob) + moblist.Add(M) for(var/mob/dead/observer/M in sortmob) moblist.Add(M) for(var/mob/new_player/M in sortmob) diff --git a/code/modules/admin/player_panel/player_panel.dm b/code/modules/admin/player_panel/player_panel.dm index ada3d1100687..b1eb9bdf21bd 100644 --- a/code/modules/admin/player_panel/player_panel.dm +++ b/code/modules/admin/player_panel/player_panel.dm @@ -217,6 +217,8 @@ M_job = "Corgi" else M_job = "Animal" + else if(iscaptivemind(M)) + M_job = "Captive Mind" else M_job = "Living" else if(istype(M,/mob/new_player)) diff --git a/code/modules/borer/borer.dm b/code/modules/borer/borer.dm index 54daabcf341b..25cf64bcfe96 100644 --- a/code/modules/borer/borer.dm +++ b/code/modules/borer/borer.dm @@ -1,6 +1,15 @@ /mob/living/captive_brain - name = "host brain" - real_name = "host brain" + name = "captive mind" + real_name = "captive mind" + icon = 'icons/obj/items/organs.dmi' + icon_state = "xenobrain" + + /// Whether or not the brain is mid-resisting control. + var/resisting_control = FALSE + +/mob/living/captive_brain/New(loc, ...) + . = ..() + give_action(src, /datum/action/innate/borer/brain_resist) /mob/living/captive_brain/say(message) if(client) @@ -40,14 +49,31 @@ if(!istype(B)) log_debug(EXCEPTION("Trapped mind found without a borer!"), src) return FALSE + if(resisting_control) + to_chat(src, SPAN_DANGER("You stop resisting control.")) + to_chat(B.host, SPAN_XENODANGER("The captive mind of [src] is no longer attempting to resist you.")) + resisting_control = FALSE + return FALSE - to_chat(src, SPAN_DANGER("You begin doggedly resisting the parasite's control (this will take approximately sixty seconds).")) - to_chat(B.host, SPAN_XENOWARNING("You feel the captive mind of [src] begin to resist your control.")) - + to_chat(src, SPAN_HIGHDANGER("You begin doggedly resisting the parasite's control (this will take approximately sixty seconds).")) + to_chat(B.host, SPAN_XENOHIGHDANGER("You feel the captive mind of [src] begin to resist your control.")) + resisting_control = TRUE var/delay = (rand(350,450) + B.host.getBrainLoss()) addtimer(CALLBACK(src, PROC_REF(return_control), B), delay) return TRUE +/datum/action/innate/borer/brain_resist + name = "Resist!" + action_icon_state = "brain_resist" + +/datum/action/innate/borer/brain_resist/action_activate() + if(isliving(owner)) + var/mob/living/live_owner = owner + live_owner.resist() + else + to_chat(owner, SPAN_WARNING("Error: CB1, tell forest2001! ")) + return FALSE +//################################################// /mob/living/carbon/cortical_borer name = "cortical borer" real_name = "cortical borer" @@ -262,7 +288,8 @@ . += "Can Reproduce: [CR]" . += "Enzymes: [round(enzymes)]/[round(max_enzymes)]" . += "Contaminant: [round(contaminant)]/[round(max_contaminant)]" - . += "Health: P:[round(getBruteLoss())] B:[round(getFireLoss())] T:[round(getToxLoss())]" + . += "Health: [health]/[maxHealth]" + . += "Injuries: Brute:[round(getBruteLoss())] Burn:[round(getFireLoss())] Toxin:[round(getToxLoss())]" if(host) . += "" if(ishuman(host)) @@ -302,9 +329,9 @@ if(((human_host.chem_effect_flags & CHEM_EFFECT_ANTI_PARASITE) && !human_host.reagents.has_reagent("benzyme")) || human_host.reagents.has_reagent("bcure")) if(!docile) if(controlling) - to_chat(host, SPAN_XENODANGER("You feel the flow of a soporific chemical in your host's blood, lulling you into docility.")) + to_chat(host, SPAN_XENOHIGHDANGER("You feel the flow of a soporific chemical in your host's blood, lulling you into docility.")) else - to_chat(src, SPAN_XENODANGER("You feel the flow of a soporific chemical in your host's blood, lulling you into docility.")) + to_chat(src, SPAN_XENOHIGHDANGER("You feel the flow of a soporific chemical in your host's blood, lulling you into docility.")) docile = TRUE else if(docile) @@ -322,7 +349,7 @@ contaminant = max(contaminant -= 0.1, 0) if(controlling) if(docile) - to_chat(host, SPAN_XENOWARNING("You are feeling far too docile to continue controlling your host...")) + to_chat(host, SPAN_WARNING("You are feeling far too docile to continue controlling your host...")) host.release_control() return else diff --git a/code/modules/borer/borer_procs.dm b/code/modules/borer/borer_procs.dm index 224cdc2fac5f..86b9cebc6308 100644 --- a/code/modules/borer/borer_procs.dm +++ b/code/modules/borer/borer_procs.dm @@ -6,37 +6,47 @@ var/target = src - var/list/options = list("Communicating") + var/list/options = list("Communicating","Contaminant & Enzymes") if(!host) options += list("Infecting a host") else if(controlling) target = host - options = list("Captive Host", "Releasing Control", "Reproducing") + options += list("Captive Host", "Releasing Control", "Reproducing") else if(isxeno(host)) - options = list("Assuming Control","Contaminant & Enzymes","Hibernation","Reproducing") + options += list("Assuming Control","Hibernation","Reproducing") else - options = list("Assuming Control","Contaminant & Enzymes","Hibernation","Secreting Chemicals","Reproducing") + options += list("Assuming Control","Hibernation","Secreting Chemicals","Reproducing") var/choice = tgui_input_list(target, "What would you like help with?", "Help", options, 20 SECONDS) var/help_message = "" switch(choice) if("Infecting a host") - help_message = "Infecting a host is the first major step for a borer to complete.\n\nThis is done by getting close to a potential host (This can be Human, Xeno or Yautja depending on settings controlled by admins) and clicking the Infest button in the top left of your screen.\n\nYour host will need to keep still for you to do this, and it's rare that they do so; for this reason you have Dominate Victim, which will allow you to temporarily stun a target.\n\nNote: This is not sufficient to keep them down for 100% of the time it takes to infect however, so be careful with it." + var/possible_targets = "" + if(infect_humans) possible_targets += "\nHumans" + if(infect_xenos) possible_targets += "\nXenos" + if(infect_yautja) possible_targets += "\nYautja" + if(!possible_targets) possible_targets += "No one." + help_message = "Infecting a host is the first major step for a borer to complete.\n\nThis is done by getting close to a potential host (This can be Human, Xeno or Yautja depending on settings controlled by admins) and clicking the Infest button in the top left of your screen.\n\nYour host will need to keep still for you to do this, and it's rare that they do so; for this reason you have Dominate Victim, which will allow you to temporarily stun a target.\n\nNote: Dominate is not sufficient to keep them down for 100% of the time it takes to infect however, so be careful with it.\n\nWhilst inside a host, and NOT in direct control, you can be detected by body scanners but otherwise are hidden from everyone but your host and other borers.\nYou can currently infect: [possible_targets]" if("Communicating") help_message = "All borers share a hivemind, the Cortical Link, this can be accessed using :0 (that's ZERO).\n\nThe hivemind can be used by any borer who is NOT in direct control of their host.\n\nAny borer in direct control of a host can only hear what their host can hear.\n\nA borer inside a host can communicate directly with that host using 'Converse with Host'." if("Captive Host") - help_message = "" + help_message = "Your host is now unable to act or speak to anyone but yourself. While in this state you have complete control of your host body, but you are disconnected from the hivemind.\n\nYour host can resist your control and regain their body however this can cause brain damage in humanoids.\n\nNote: Whilst in direct control of your host medical HUDs will detect you.\n\n\nIMPORTANT: While in direct control of a mob you MUST NOT perform antag actions unless you have permission from staff." if("Releasing Control") - help_message = "" + help_message = "Releasing control will do as it suggests, give your host their body back.\n\nYour host can resist your control and regain their body however this can cause brain damage in humanoids." if("Reproducing") - help_message = "" + var/capability = "able" + if(!can_reproduce) capability = "forbidden" + else if((enzymes < BORER_LARVAE_COST)) capability = "unable" + help_message = "Reproduction will take [BORER_LARVAE_COST] enzymes and direct control of your host.\n\nWhen in direct control you can use the Reproduce ability to spit out a new borer.\nMake sure to do this in a safe place, the new borer will not have a player to start with.\n\nYou are currently [capability] to reproduce." if("Assuming Control") - help_message = "" - if("Contaminant & Enzymes", "Hibernation") - help_message = "" + help_message = "Assuming control will put you in direct control of your host, acting as if you are their player.\n\nYour host will be disassociated with their body, and trapped in their own mind unable to speak to anyone but you.\nWhile in this state you are unable to make use of the hivemind.\n\nYour host can resist your control and regain their body however this can cause brain damage in humanoids.\nYou must assume control to reproduce.\n\nNote: Whilst in direct control of your host medical HUDs will detect you.\n\n\nIMPORTANT: While in direct control of a mob you MUST NOT perform antag actions unless you have permission from staff." + if("Contaminant & Enzymes") + help_message = "Enzymes are the cost of using most of your active abilities, such as secreting chemicals. They are gained passively over time whilst inside a host.\n\nUsing enzymes will in most cases produce Contaminant which upon reaching its capacity will prevent you using abilities.\nYou can clear Contaminant by hibernating when inside a host, alternately Contaminant will naturally be turned into a weak light source whilst outside a host." + if("Hibernation") + help_message = "Hibernation is how you purify contaminants from your body, allowing you to use your enzymes more freely.\n\nYou can only hibernate whilst inside a host, and it renders you unable to act other than to speak to your host.\n\nYou can freely enter or leave hibernation by clicking the Hibernate button." if("Secreting Chemicals") - help_message = "" + help_message = "Whilst inside a humanoid host you can secrete chemicals to facilitate your relationship.\nThese can vary from helpful medications to harmful control measures.\n\nSecreting chemicals costs enzymes and if a chemical is impure will cause you to gain contaminant.\nIf you are at, or will go over, your contaminant capacity you will be unable to secrete chemicals.\nPure chemicals are chemicals native to borers such as Cortical Enzyme." alert(target, help_message, choice, "Ok") @@ -185,6 +195,8 @@ for(var/mob/living/carbon/candidate in view(1,src)) if(can_target(candidate)) choices += candidate + if(!choices) + to_chat(src, SPAN_XENOWARNING("No possible targets found.")) var/mob/living/carbon/target = tgui_input_list(src, "Who do you wish to infest?", "Targets", choices) if(!target || !src || !Adjacent(target)) return FALSE @@ -381,6 +393,7 @@ give_new_actions(ACTION_SET_CONTROL) host.med_hud_set_status() + host.special_mob = TRUE if(src && !src.key) src.key = "@[borer_key]" @@ -388,11 +401,12 @@ //Captive mind reclaims their body. /mob/living/captive_brain/proc/return_control(mob/living/carbon/cortical_borer/B) - if(!B || !B.controlling) + if(!B || !B.controlling || !resisting_control) return FALSE B.host.adjustBrainLoss(rand(5,10)) - to_chat(src, SPAN_DANGER("With an immense exertion of will, you regain control of your body!")) - to_chat(B.host, SPAN_XENODANGER("You feel control of the host brain ripped from your grasp, and retract your probosci before the wild neural impulses can damage you.")) + to_chat(src, SPAN_HIGHDANGER("With an immense exertion of will, you regain control of your body!")) + to_chat(B.host, SPAN_XENOHIGHDANGER("You feel control of the host brain ripped from your grasp, and retract your probosci before the wild neural impulses can damage you.")) + resisting_control = FALSE B.detach() return TRUE @@ -425,6 +439,7 @@ sleeping = 0 if(host_brain) log_interact(host, src, "Borer: [key_name(host)] Took control back") + host.special_mob = FALSE // host -> self var/h2s_id = host.computer_id var/h2s_ip= host.lastKnownIP @@ -457,9 +472,9 @@ return FALSE if(controlling) detach() - to_chat(src, SPAN_HIGHDANGER("You release your proboscis and flee as the psychic shock of your host's death washes over you!")) + to_chat(src, SPAN_XENOHIGHDANGER("You release your proboscis and flee as the psychic shock of your host's death washes over you!")) if(perma) - to_chat(src, SPAN_HIGHDANGER("You flee your host in anguish!")) + to_chat(src, SPAN_XENOHIGHDANGER("You flee your host in anguish!")) leave_host() return TRUE @@ -470,7 +485,7 @@ set desc = "Become invisible to the common eye." if(host) - to_chat(usr, SPAN_XENOWARNING("You cannot do this while you're inside a host.")) + to_chat(usr, SPAN_WARNING("You cannot do this while you're inside a host.")) return FALSE if(stat != CONSCIOUS) @@ -511,6 +526,8 @@ to_chat(src, SPAN_XENOWARNING("You cannot use that ability again so soon.")) return FALSE attempting_to_dominate = TRUE + if(!choices) + to_chat(src, SPAN_XENOWARNING("No possible targets found.")) var/mob/living/carbon/M = tgui_input_list(src, "Who do you wish to dominate?", "Targets", choices) if(!M) attempting_to_dominate = FALSE @@ -554,7 +571,7 @@ if(B.host_brain) to_chat(src, SPAN_XENONOTICE("You send a punishing spike of psychic agony lancing into your host's brain.")) - to_chat(B.host_brain, SPAN_XENOHIGHDANGER("Horrific, burning agony lances through you, ripping a soundless scream from your trapped mind!")) + to_chat(B.host_brain, SPAN_HIGHDANGER("Horrific, burning agony lances through you, ripping a soundless scream from your trapped mind!")) return TRUE diff --git a/code/modules/mob/dead/observer/orbit.dm b/code/modules/mob/dead/observer/orbit.dm index 39a7eb968368..bb35d8a87dc8 100644 --- a/code/modules/mob/dead/observer/orbit.dm +++ b/code/modules/mob/dead/observer/orbit.dm @@ -56,7 +56,7 @@ /datum/orbit_menu/ui_static_data(mob/user) var/list/data = list() - var/list/borers = list() + var/list/special_mobs = list() var/list/humans = list() var/list/marines = list() var/list/survivors = list() @@ -112,8 +112,8 @@ var/mob/living/player = M serialized["health"] = FLOOR((player.health / player.maxHealth * 100), 1) - if(isborer(player)) - borers += list(serialized) + if(isborer(player) || iscaptivemind(player) || player.special_mob) + special_mobs += list(serialized) if(isxeno(player)) var/mob/living/carbon/xenomorph/xeno = player @@ -163,7 +163,7 @@ else if(isAI(M)) humans += list(serialized) - data["borers"] = borers + data["special_mobs"] = special_mobs data["humans"] = humans data["marines"] = marines data["survivors"] = survivors diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index d0eac93c78e9..aff43fc0fe6f 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -150,7 +150,8 @@ . += "Name: [B.truename]" . += "Can Reproduce: [CR]" . += "Enzymes: [round(B.enzymes)]/[round(B.max_enzymes)]" - . += "Health: P:[round(getBruteLoss())] B:[round(getFireLoss())] T:[round(getToxLoss())]" + . += "Health: [B.health]/[B.maxHealth]" + . += "Injuries: Brute:[round(B.getBruteLoss())] Burn:[round(B.getFireLoss())] Toxin:[round(B.getToxLoss())]" . += "" . += "Host Brain Damage: [brainloss]/100" diff --git a/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm b/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm index 6a592494756e..2f3bd91b8f93 100644 --- a/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm +++ b/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm @@ -158,7 +158,8 @@ . += "Name: [B.truename]" . += "Can Reproduce: [CR]" . += "Enzymes: [round(B.enzymes)]/[round(B.max_enzymes)]" - . += "Health: P:[round(getBruteLoss())] B:[round(getFireLoss())] T:[round(getToxLoss())]" + . += "Health: [B.health]/[B.maxHealth]" + . += "Injuries: Brute:[round(B.getBruteLoss())] Burn:[round(B.getFireLoss())] Toxin:[round(B.getToxLoss())]" . += "" . += "Host Plasma: [plasma_stored]/[plasma_max]" . += "Host Integrity: [health]/[maxHealth]" diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index 5495dcbb0da4..4d017cdd54c7 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -106,3 +106,6 @@ /// This is what the value is changed to when the mob dies. Actual BMV definition in atom/movable. var/dead_black_market_value = 0 + + /// Used to highlight on follow menu. + var/special_mob = FALSE diff --git a/icons/mob/hud/actions_borer.dmi b/icons/mob/hud/actions_borer.dmi index 99efeb7c5b44c4d1e5199e21cd035c156133dbf6..d1098b87d803641b4ca6ee29871f2c6dec5717ae 100644 GIT binary patch delta 2028 zcmVfFDZ*Bkpc$}4zu?mAg42Ea#DGuFBZM$|U zN^z*KAXklEK<^|u_4O+iIuuIdmOp%fFCmG(<@ci0*`rW15TkecsxOD5iWY2wUBmgL zA`95rumuh#z*@#nPe4^|Vc=Lr4HB>2R7EYB0k-;!`6pHRCt{2n~Qp)N(_Ky2Z|tev};*Ah|ulj{U7lp z69(rF>b7UkcYnxktr|>zGMRy-&T6&DB7cP>KKb!|`c=Y6(lpDm;W>M7^@jXG{RJRj zuh;390i;So0BKRI*F~8I3~cTM!0?i!A%GkrkQZ6lLA7C0dN4)^&rUL0LbLeN+~Jg z`T-E0RkuZ8y;A}N4nRsdfd2DT2F&LmD>j=dm;*xuh5%T4IRD*lkD>^40Gj+60u)7_ zQOdXus?7$Hq@2nTFJP+tBY^{uQg{^r5!eo@DuWaSxfNiq#08)v!AV%+JV@<%no#0p zIYeON1%G&YDoZ_4GJ6PN1ol6Ff_Hd;5ZFVIg8u?Dw8V!3=YcZ>oZ~F#F3t*>3zy$#RbzUgRYanojKu%N8dU61~KcNPXd4B$d@_Z3lD?0+Y3fE)#2sN5uk036Ui!)yeI!V`gA6ad!W-5!P?+?V?uOUwFrv;go@ z7=M`O2#`qt7!3A|A+P|}=>1lka% zYk4UcS7LaXY7t(^mB#=;0vEtQumBEpJ;NM)k^~w6EVAJ6axGfRtFi|0;{jy^kgs$? z(%Yixi?Yx)Fc61>rq9UdDDs64u*f2dEPt}dU(+9eo2{|#_=^635KYr~^cMi^5AdLY zV0}Lu0PTw~0H^){4;Tp7_oP2f0SK}$!T{7CaBsP$>iaPQ&550wFaX(af&dlu2mAoB z`raIZ0m0QbWIqBx{Q=JH+wQ6Q-p&CL03iE9u*Lz9$zM@_0CE6Jk1OhXvwzTdB7Y#? zYsfy}iN*%tlmoIqfX{*8iu(T2KRBHlZw?5`0cjtf1Az`elfU9rp34`!x}v^UAwHe_ z{R4t(1lgA_M56=9^RGq#BDkWymjF!@>>miyD*z&}laTYVKfparD6H=}2k-FUl-CI* zUY0`yPJsr75TkuzC+Vu<;9u(x;D0=@h9Fws%LoV&>>o4&w3>aK2gVRo;NR#E009pJ zIQ9Kh|DXZ?hJ7F~hk#dBe*j`~MR{%Z4}@s!ArOsaA7Wx8A$R%%+=;*x0OZ!#Kd=@W zAsqX{hyeHW2O7=fEP1EV9TVUsC@+%>VrT^?%!* zJAAM2+OF-d{`~y_UDuxFn}0p)`*Z*O0Daqbol^8&e>1>#tN!xqH}DGL(*!qK*zs96oR2oWcsl) z{7?+s&qwUb1Mp4y3dZolGJsBhv*F>@(!T*g+rj_ynFw0v^D+B)z<;P8k1!34{zn2` z|IRQ5eV=saF&7`^+lK>`{^Mgj!Z4uxe}0Yx#yarcvqT2E9YSCM4E8@h(1Bxrm28=P z0Nd&YUWYUP{6qp7iB}^6ty+Fq+j#(#eel3M_U{LHV{ZzeQz7sId@w)2y+gp*d;e~R zpuK7c?oC2?=dFF0$$yX8c)mUf@0^BA5~vWgZW2yU!#fuu1W;p;$U$gr{nSFl?pcbN zG5(m4-StZ`DQ~+Nk#~l3pktlmv>zX5^y%5N>hf=L+Je{coBCyN7Yg5R{_k?#*o1I!+R@%-Hg zMD`!d0GO48ab%G_j delta 1930 zcmV;52X*+15vdT6wSU2SR9JLGWpiV4X>fFDZ*Bkpc$}5e!3u*g3J07mPbxQXkj;B2)znXS2; zIWu4fllCSinP4lkmq)-(8JJ?mnYI|WX~|iSa)Q17u`$h#NPkyjV*n&-w1V(Y^=6!=!#>!Byxm+UeAhpXdD5;kv!i8Y9a$;_(yY5x7Fui z--CbA6bNY+E&u=ql1W5CRCt{2n~Qp)N(_KyXDD*9qg~6fg9zPT-v1F#GGTCTfJ$w5 zzmM$Js=?$Z$$tzaZC9&B7WpeA3dyhU)2|{%!g(CW-E;Qf>n-_0{TU!xuh;x;09?xu z08i8PI?ecifqW(ahL?m70VEKCB#nn16gkU_AqShyBYt~)+-ySnvwtE80L18j6o4X8 z+!6sEck$N*A%HA_0A!G(c-(>T9X#UcpqWUyxp zfqw(A=4Sxq-v&_k053p*{HxitLX7 z(0o7$_qKbgzjtdu4gipSDOu$KsNyeZJ^(dvm)qt{(&w;=Mpv7MZYR}aRQC!jA>kuE0;rRhURg&zh7qT(| z)cF@Z01;f#-z$Ks8k`?U(rW-BaEnlgem)=qLn!R;g#hpH;I-Eg6<(Y`1dam(3@Lm2 z(k;?eA;7;eAHa3sq+qnaR}qkMaDRSKNzfYhaUEDGD8Rop9{>U|4B++mQ}crg{9E>c zz)pcEocRF6w;hb!2WC6m$v z0h*>hsW)5Jwx{;{0ouB58h@>5oAzda%|`#^H*DY)B&3fZ_NRzN1VJ$feSW~cK7f?r zA3l@&^P>w;*L4d76#W+K!2kT9eL4UYk0AOG*8n>H1ELWueWc2do#2aN;D3I^zB&Nk zq+h}4UN{EO=^xfTygK?fAgCMoe>xFC?R|dCJ|58P`y*@vz5kxT)PKJ-j6vH*&8e@& zhs*850b2j@(H~(M(EdL^dje}8cyC#x0$q0@Z~!{{A0Ozzv%iWq%sqf@OargonSXvF zfr`Yd6@glBKdf#70NOryU;+E{0p8e~2558$f&d>Z3@|qZjJ>z-b_nXLQZTm&;hneo zT@^o8&^h3|8w{2HlMxRCk49w1_Tyrzt8^j`xIdh@X5vj^5Ab;%?G$R1^xN| zND&n*7~L}kBMugTy93Of4=l3CzbyX)wKBtRsOt7S Q00000NkvXXt^-0~f@6k(HUIzs diff --git a/tgui/packages/tgui/interfaces/Orbit/index.tsx b/tgui/packages/tgui/interfaces/Orbit/index.tsx index 9f6e9004b951..2856598e97b8 100644 --- a/tgui/packages/tgui/interfaces/Orbit/index.tsx +++ b/tgui/packages/tgui/interfaces/Orbit/index.tsx @@ -99,7 +99,7 @@ const ObservableSearch = (props, context) => { const ObservableContent = (props, context) => { const { data } = useBackend(context); const { - borers = [], + special_mobs = [], humans = [], marines = [], survivors = [], @@ -118,7 +118,7 @@ const ObservableContent = (props, context) => { return ( - + diff --git a/tgui/packages/tgui/interfaces/Orbit/types.ts b/tgui/packages/tgui/interfaces/Orbit/types.ts index d3b339ae04ed..d41e3389945e 100644 --- a/tgui/packages/tgui/interfaces/Orbit/types.ts +++ b/tgui/packages/tgui/interfaces/Orbit/types.ts @@ -2,7 +2,7 @@ import { BooleanLike } from 'common/react'; export type OrbitData = { auto_observe: BooleanLike; - borers: Observable[]; + special_mobs: Observable[]; humans: Observable[]; marines: Observable[]; survivors: Observable[]; From 251271ef11adc347b30a06e5496867a4e7309c71 Mon Sep 17 00:00:00 2001 From: forest2001 Date: Mon, 3 Apr 2023 23:08:35 +0100 Subject: [PATCH 06/25] reproduction consistencies and hearing fix --- code/modules/borer/borer.dm | 16 ++++++++-------- code/modules/borer/borer_procs.dm | 6 +++--- code/modules/mob/language/languages.dm | 5 ++++- code/modules/mob/living/say.dm | 4 ++++ 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/code/modules/borer/borer.dm b/code/modules/borer/borer.dm index 25cf64bcfe96..52d3c9929ad3 100644 --- a/code/modules/borer/borer.dm +++ b/code/modules/borer/borer.dm @@ -133,34 +133,34 @@ /datum/action/innate/borer/helpme, /datum/action/innate/borer/toggle_hide, /datum/action/innate/borer/freeze_victim, - /datum/action/innate/borer/infest_host + /datum/action/innate/borer/infest_host, ) var/list/actions_humanoidhost = list( /datum/action/innate/borer/helpme, - /datum/action/innate/borer/take_control, /datum/action/innate/borer/talk_to_host, - /datum/action/innate/borer/leave_body, /datum/action/innate/borer/hibernate, + /datum/action/innate/borer/take_control, + /datum/action/innate/borer/leave_body, /datum/action/innate/borer/scan_chems, - /datum/action/innate/borer/make_chems + /datum/action/innate/borer/make_chems, ) var/list/actions_xenohost = list( /datum/action/innate/borer/helpme, - /datum/action/innate/borer/take_control, /datum/action/innate/borer/talk_to_host, + /datum/action/innate/borer/hibernate, + /datum/action/innate/borer/take_control, /datum/action/innate/borer/leave_body, - /datum/action/innate/borer/hibernate ) var/list/actions_control = list( /datum/action/innate/borer/helpme, /datum/action/innate/borer/give_back_control, /datum/action/innate/borer/make_larvae, /datum/action/innate/borer/talk_to_brain, - /datum/action/innate/borer/torment + /datum/action/innate/borer/torment, ) //################### INIT & LIFE ###################// -/mob/living/carbon/cortical_borer/New(atom/newloc, gen=1, ERT = FALSE, reproduction = 0) +/mob/living/carbon/cortical_borer/New(atom/newloc, gen=1, ERT = FALSE, reproduction = 0, infect_humans = TRUE, infect_xenos = FALSE, infect_yautja = FALSE) ..(newloc) SSmob.living_misc_mobs += src generation = gen diff --git a/code/modules/borer/borer_procs.dm b/code/modules/borer/borer_procs.dm index 86b9cebc6308..96df31acc7d5 100644 --- a/code/modules/borer/borer_procs.dm +++ b/code/modules/borer/borer_procs.dm @@ -593,7 +593,7 @@ T.add_vomit_floor() B.contaminant = 0 var/repro = max(B.can_reproduce - 1, 0) - new /mob/living/carbon/cortical_borer(T, B.generation + 1, TRUE, repro) + new /mob/living/carbon/cortical_borer(T, B.generation + 1, TRUE, repro, B.infect_humans, B.infect_xenos, B.infect_yautja) return TRUE else to_chat(src, SPAN_XENONOTICE("You need at least [BORER_LARVAE_COST] enzymes to reproduce!")) @@ -719,9 +719,9 @@ if(!input) return FALSE - to_chat(B, SPAN_XENO("[src] says: [input]"), type = MESSAGE_TYPE_RADIO) + to_chat(B, SPAN_XENO("[src.real_name] says: [input]"), type = MESSAGE_TYPE_RADIO) log_say("BORER: ([key_name(src)] to [key_name(B)]) [input]", src) - to_chat(src, SPAN_XENO("[src] says: [input]"), type = MESSAGE_TYPE_RADIO) + to_chat(src, SPAN_XENO("[src.real_name] says: [input]"), type = MESSAGE_TYPE_RADIO) for (var/mob/dead in GLOB.dead_mob_list) var/track_host = " (F)" if(!istype(dead,/mob/new_player) && !istype(dead,/mob/living/brain)) //No meta-evesdropping diff --git a/code/modules/mob/language/languages.dm b/code/modules/mob/language/languages.dm index 905b6a07670f..306a324cbea7 100644 --- a/code/modules/mob/language/languages.dm +++ b/code/modules/mob/language/languages.dm @@ -215,6 +215,7 @@ /datum/language/corticalborer/broadcast(mob/living/carbon/speaker, message, speaker_mask, death) var/mob/living/carbon/cortical_borer/B + if(!message) return FALSE if(isborer(speaker)) B = speaker else if(speaker.has_brain_worms()) @@ -224,6 +225,7 @@ if(!speaker_mask) speaker_mask = speaker.real_name + message = trim(message) var/message_start = "[name], [speaker_mask]" var/message_body = "[speech_verb], \"[message]\"" log_say("[key_name(speaker)] : ([name]) [message]") @@ -243,4 +245,5 @@ var/area/A = get_area(speaker) to_chat(player, "[message_start] has [SPAN_BOLD("perished")][A? " at [sanitize_area(A.name)]":""]!") else - to_chat(player, "[ghost? "(F) ":""][message_start][message_body]") + to_chat(player, "[ghost? "(F) ":""][message_start] [message_body]") + return TRUE diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm index bb9cf9309a2b..d1430de4a43d 100644 --- a/code/modules/mob/living/say.dm +++ b/code/modules/mob/living/say.dm @@ -116,6 +116,10 @@ var/list/department_radio_keys = list( for(var/obj/O in M.contents) if(O.flags_atom & USES_HEARING) listening_obj |= O + for(var/mob/inner_mob in M.contents) + listening |= inner_mob + for(var/mob/living/captive_brain/brain in inner_mob) + listening |= brain else if(istype(I, /obj/)) var/obj/O = I hearturfs += O.locs[1] From 5fc3926987760d088a15923b0b32b59c7652757f Mon Sep 17 00:00:00 2001 From: forest2001 Date: Thu, 6 Apr 2023 07:27:51 +0100 Subject: [PATCH 07/25] bitflags! --- code/__DEFINES/borer_defines.dm | 52 ++++++ code/datums/mob_hud.dm | 2 +- code/modules/borer/_defines.dm | 13 -- code/modules/borer/borer.dm | 157 ++++++++---------- code/modules/borer/borer_procs.dm | 82 ++++++--- code/modules/mob/living/carbon/human/human.dm | 2 +- .../mob/living/carbon/xenomorph/XenoProcs.dm | 2 +- .../chemistry_properties/prop_positive.dm | 2 +- code/modules/surgery/brainworm.dm | 1 + colonialmarines.dme | 2 +- icons/mob/hud/actions_borer.dmi | Bin 2187 -> 2693 bytes 11 files changed, 188 insertions(+), 127 deletions(-) create mode 100644 code/__DEFINES/borer_defines.dm delete mode 100644 code/modules/borer/_defines.dm diff --git a/code/__DEFINES/borer_defines.dm b/code/__DEFINES/borer_defines.dm new file mode 100644 index 000000000000..181a7d830f47 --- /dev/null +++ b/code/__DEFINES/borer_defines.dm @@ -0,0 +1,52 @@ +/// Chemical categories +#define BORER_CAT_HEAL "Medicines" +#define BORER_CAT_PUNISH "Motivators" +#define BORER_CAT_STIM "Stimulants" +#define BORER_CAT_SELF "Self Protection" + +///Amount of chemicals needed for a borer to reproduce, provided reproduction is toggled. +#define BORER_LARVAE_COST 400 + +#define ACTION_SET_HOSTLESS "actions_hostless" +#define ACTION_SET_HUMANOID "actions_human" +#define ACTION_SET_XENO "actions_xeno" +#define ACTION_SET_CONTROL "actions_control" + +/// Borer target bitflags +#define BORER_TARGET_HUMANS (1<<0) +#define BORER_TARGET_XENOS (1<<1) +#define BORER_TARGET_YAUTJA (1<<2) + +DEFINE_BITFIELD(borer_flags_targets, list( + "TARGET_HUMANS" = BORER_TARGET_HUMANS, + "TARGET_XENOS" = BORER_TARGET_XENOS, + "TARGET_YAUTJA" = BORER_TARGET_YAUTJA, +)) + +/// Borer active/toggle-able ability flags. +/// Middle of crawling into a new host +#define BORER_PROCESS_INFESTING (1<<0) +/// Middle of taking control of a host body +#define BORER_PROCESS_BONDING (1<<1) +/// Middle of leaving a host +#define BORER_PROCESS_LEAVING (1<<2) +/// Hiding or not. (Changes layer) +#define BORER_ABILITY_HIDE (1<<3) +/// Sleeps to purify contaminant +#define BORER_ABILITY_HIBERNATING (1<<4) + +DEFINE_BITFIELD(borer_flags_actives, list( + "PROCESS_INFESTING" = BORER_PROCESS_INFESTING, + "PROCESS_BONDING" = BORER_PROCESS_BONDING, + "PROCESS_LEAVING" = BORER_PROCESS_LEAVING, + "ACTIVE_HIDING" = BORER_ABILITY_HIDE, + "ACTIVE_HIBERNATING" = BORER_ABILITY_HIBERNATING, +)) + +#define BORER_STATUS_CONTROLLING (1<<0) +#define BORER_STATUS_DOCILE (1<<1) + +DEFINE_BITFIELD(borer_flags_status, list( + "STATUS_CONTROLLING" = BORER_STATUS_CONTROLLING, + "STATUS_DOCILE" = BORER_STATUS_DOCILE, +)) diff --git a/code/datums/mob_hud.dm b/code/datums/mob_hud.dm index a6d4e2c09a09..1d0519b54c81 100644 --- a/code/datums/mob_hud.dm +++ b/code/datums/mob_hud.dm @@ -463,7 +463,7 @@ var/list/datum/mob_hud/huds = list( holder5.icon_state = null if(B) holder5.icon_state = "hudbrainwormhost" - if(B.controlling) + if(B.borer_flags_status & BORER_STATUS_CONTROLLING) holder.icon_state = "hudbrainworm" if(!holder2_set) holder2.icon_state = "hudbrainworm" diff --git a/code/modules/borer/_defines.dm b/code/modules/borer/_defines.dm deleted file mode 100644 index a1c7b4959e50..000000000000 --- a/code/modules/borer/_defines.dm +++ /dev/null @@ -1,13 +0,0 @@ -/// Chemical categories -#define BORER_CAT_HEAL "Medicines" -#define BORER_CAT_PUNISH "Motivators" -#define BORER_CAT_STIM "Stimulants" -#define BORER_CAT_SELF "Self Protection" - -///Amount of chemicals needed for a borer to reproduce, provided reproduction is toggled. -#define BORER_LARVAE_COST 400 - -#define ACTION_SET_HOSTLESS "actions_hostless" -#define ACTION_SET_HUMANOID "actions_human" -#define ACTION_SET_XENO "actions_xeno" -#define ACTION_SET_CONTROL "actions_control" diff --git a/code/modules/borer/borer.dm b/code/modules/borer/borer.dm index 52d3c9929ad3..832a96aa35f3 100644 --- a/code/modules/borer/borer.dm +++ b/code/modules/borer/borer.dm @@ -115,16 +115,18 @@ var/mob/living/carbon/host // Human host for the brain worm. var/truename // Name used for brainworm-speak. var/mob/living/captive_brain/host_brain // Used for swapping control of the body back and forth. - var/controlling // Used in human death check. var/docile = FALSE // Anti-Parasite or Anti-Enzyme chemicals can stop borers from acting. var/bonding = FALSE var/leaving = FALSE var/hiding = FALSE var/can_reproduce = FALSE // Locked to manual override to prevent things getting out of hand. - var/infect_humans = TRUE // Locked for normal use. - var/infect_xenos = FALSE - var/infect_yautja = FALSE + /// Flags that show what active abilities are toggled. Better than a dozen different boolean vars. + var/borer_flags_actives + /// Flags determining what the borer can infect + var/borer_flags_targets = BORER_TARGET_HUMANS + /// Borer status, controlling or docile. + var/borer_flags_status //Controlling or Docile. Unsure if I want to put hibernating in here or in actives as active abilities will stop enzyme production. var/list/datum/reagent/synthesized_chems @@ -160,7 +162,7 @@ ) //################### INIT & LIFE ###################// -/mob/living/carbon/cortical_borer/New(atom/newloc, gen=1, ERT = FALSE, reproduction = 0, infect_humans = TRUE, infect_xenos = FALSE, infect_yautja = FALSE) +/mob/living/carbon/cortical_borer/New(atom/newloc, gen=1, ERT = FALSE, reproduction = 0, new_targets = BORER_TARGET_HUMANS) ..(newloc) SSmob.living_misc_mobs += src generation = gen @@ -169,6 +171,7 @@ real_name = "Cortical Borer [mob_number]" truename = "[borer_names[min(generation, borer_names.len)]] [mob_number]" can_reproduce = reproduction + borer_flags_targets = new_targets give_new_actions(ACTION_SET_HOSTLESS) //GrantBorerActions() GiveBorerHUD() @@ -186,6 +189,55 @@ /mob/living/carbon/cortical_borer/initialize_stamina() stamina = new /datum/stamina/none(src) +/mob/living/carbon/cortical_borer/Life(delta_time) + ..() + update_canmove() + update_icons() + var/heal_amt = 1 + if(host) + heal_amt = 3 + if(!stat && host.stat != DEAD) + var/mob/living/carbon/human/human_host + if(ishuman(host)) + human_host = host + if(((human_host.chem_effect_flags & CHEM_EFFECT_ANTI_PARASITE) && !human_host.reagents.has_reagent("benzyme")) || human_host.reagents.has_reagent("bcure")) + if(!docile) + if(borer_flags_status & BORER_STATUS_CONTROLLING) + to_chat(host, SPAN_XENOHIGHDANGER("You feel the flow of a soporific chemical in your host's blood, lulling you into docility.")) + else + to_chat(src, SPAN_XENOHIGHDANGER("You feel the flow of a soporific chemical in your host's blood, lulling you into docility.")) + docile = TRUE + else + if(docile) + if(borer_flags_status & BORER_STATUS_CONTROLLING) + to_chat(human_host, SPAN_XENONOTICE("You shake off your lethargy as the chemical leaves your host's blood.")) + else + to_chat(src, SPAN_XENONOTICE("You shake off your lethargy as the chemical leaves your host's blood.")) + docile = FALSE + if(!hibernating && (enzymes < max_enzymes)) + enzymes++ + if(contaminant > 0) + if(hibernating) + contaminant = max(contaminant -= 1, 0) + else + contaminant = max(contaminant -= 0.1, 0) + if(borer_flags_status & BORER_STATUS_CONTROLLING) + if(docile) + to_chat(host, SPAN_WARNING("You are feeling far too docile to continue controlling your host...")) + host.release_control() + return + else + if(contaminant > 0) + if(!luminosity) + SetLuminosity(2) + contaminant = max(contaminant - 0.3, 0) + else + SetLuminosity(0) + if(bruteloss || fireloss) + heal_overall_damage(heal_amt, heal_amt) + if(toxloss && !contaminant)//no clearing toxic impurities while contaminated. + apply_damage(-(heal_amt/2), TOX) + /mob/living/carbon/cortical_borer/updatehealth() if(status_flags & GODMODE) health = maxHealth @@ -235,18 +287,6 @@ return ..() //###################################################// -/mob/living/carbon/cortical_borer/proc/summon() - var/datum/emergency_call/custom/em_call = new() - em_call.name = real_name - em_call.mob_max = 1 - em_call.players_to_offer = list(src) - em_call.owner = null - em_call.ert_message = "A new Cortical Borer has been birthed!" - em_call.objectives = "Create enjoyable Roleplay. Do not kill your host. Do not take control unless granted permission or directed to by admins. Hivemind is :0 (That's Zero, not Oscar)" - - em_call.activate(announce = FALSE) - - message_admins("A new Cortical Borer has spawned at [get_area(loc)]") /mob/living/carbon/cortical_borer/update_icons() if(stat == DEAD) @@ -292,11 +332,15 @@ . += "Injuries: Brute:[round(getBruteLoss())] Burn:[round(getFireLoss())] Toxin:[round(getToxLoss())]" if(host) . += "" + var/health_perc = host.maxHealth / 100 + . += "Host Integrity: [host.health / health_perc]%" if(ishuman(host)) - . += "Host Brain Damage: [host.brainloss]/100" + . += "Host Brain Damage: [host.brainloss]%" else if(isxeno(host)) - // . += "Host Plasma: [plasma_stored]/[plasma_max]" - . += "Host Integrity: [health]/[maxHealth]" + var/mob/living/carbon/xenomorph/xeno_host = host + if(xeno_host.plasma_max) + var/plasma_perc = xeno_host.plasma_max / 100 + . += "Host Plasma: [xeno_host.plasma_stored / plasma_perc]%" /mob/living/carbon/cortical_borer/say(message)//I need to parse the message properly so it doesn't look stupid var/datum/language/parsed_language = parse_language(message) @@ -314,56 +358,6 @@ return . = ..() - -/mob/living/carbon/cortical_borer/Life(delta_time) - ..() - update_canmove() - update_icons() - var/heal_amt = 1 - if(host) - heal_amt = 3 - if(!stat && host.stat != DEAD) - var/mob/living/carbon/human/human_host - if(ishuman(host)) - human_host = host - if(((human_host.chem_effect_flags & CHEM_EFFECT_ANTI_PARASITE) && !human_host.reagents.has_reagent("benzyme")) || human_host.reagents.has_reagent("bcure")) - if(!docile) - if(controlling) - to_chat(host, SPAN_XENOHIGHDANGER("You feel the flow of a soporific chemical in your host's blood, lulling you into docility.")) - else - to_chat(src, SPAN_XENOHIGHDANGER("You feel the flow of a soporific chemical in your host's blood, lulling you into docility.")) - docile = TRUE - else - if(docile) - if(controlling) - to_chat(human_host, SPAN_XENONOTICE("You shake off your lethargy as the chemical leaves your host's blood.")) - else - to_chat(src, SPAN_XENONOTICE("You shake off your lethargy as the chemical leaves your host's blood.")) - docile = FALSE - if(!hibernating && (enzymes < max_enzymes)) - enzymes++ - if(contaminant > 0) - if(hibernating) - contaminant = max(contaminant -= 1, 0) - else - contaminant = max(contaminant -= 0.1, 0) - if(controlling) - if(docile) - to_chat(host, SPAN_WARNING("You are feeling far too docile to continue controlling your host...")) - host.release_control() - return - else - if(contaminant > 0) - if(!luminosity) - SetLuminosity(2) - contaminant = max(contaminant - 0.3, 0) - else - SetLuminosity(0) - if(bruteloss || fireloss) - heal_overall_damage(heal_amt, heal_amt) - if(toxloss) - apply_damage(-(heal_amt/2), TOX) - //################### ABILITIES ###################// /datum/action/innate/borer icon_file = 'icons/mob/hud/actions_borer.dmi' @@ -405,6 +399,7 @@ action_icon_state = "borer_hiding_0" /datum/action/innate/borer/toggle_hide/action_activate() + if(!isborer(owner)) return FALSE var/mob/living/carbon/cortical_borer/B = owner B.hide_borer() @@ -434,6 +429,7 @@ action_icon_state = "borer_control" /datum/action/innate/borer/take_control/action_activate() + if(!isborer(owner)) return FALSE var/mob/living/carbon/cortical_borer/B = owner if(B.hibernating) to_chat(B, SPAN_WARNING("You cannot do that while hibernating!")) @@ -454,6 +450,7 @@ action_icon_state = "borer_leave" /datum/action/innate/borer/leave_body/action_activate() + if(!isborer(owner)) return FALSE var/mob/living/carbon/cortical_borer/B = owner if(B.hibernating) to_chat(B, SPAN_WARNING("You cannot do that while hibernating!")) @@ -465,6 +462,7 @@ action_icon_state = "borer_chems" /datum/action/innate/borer/make_chems/action_activate() + if(!isborer(owner)) return FALSE var/mob/living/carbon/cortical_borer/B = owner if(B.hibernating) to_chat(B, SPAN_WARNING("You cannot do that while hibernating!")) @@ -476,6 +474,7 @@ action_icon_state = "borer_scan" /datum/action/innate/borer/scan_chems/action_activate() + if(!isborer(owner)) return FALSE var/mob/living/carbon/cortical_borer/B = owner if(B.hibernating) to_chat(B, SPAN_WARNING("You cannot do that while hibernating!")) @@ -517,21 +516,3 @@ B.hibernate() button.overlays.Cut() button.overlays += image('icons/mob/hud/actions_borer.dmi', button, "borer_sleeping_[B.hibernating]") - -/mob/living/carbon/cortical_borer/MouseDrop(atom/over_object) - if(!CAN_PICKUP(usr, src)) - return ..() - var/mob/living/carbon/H = over_object - if(!istype(H) || !Adjacent(H) || H != usr) return ..() - - if(H.a_intent == INTENT_HELP) - get_scooped(H) - return - else - return ..() - -/mob/living/carbon/cortical_borer/get_scooped(mob/living/carbon/grabber) - if(stat != DEAD) - to_chat(grabber, SPAN_WARNING("You probably shouldn't pick that thing up while it still lives.")) - return - ..() diff --git a/code/modules/borer/borer_procs.dm b/code/modules/borer/borer_procs.dm index 96df31acc7d5..fc48062c5b59 100644 --- a/code/modules/borer/borer_procs.dm +++ b/code/modules/borer/borer_procs.dm @@ -9,7 +9,7 @@ var/list/options = list("Communicating","Contaminant & Enzymes") if(!host) options += list("Infecting a host") - else if(controlling) + else if(borer_flags_status & BORER_STATUS_CONTROLLING) target = host options += list("Captive Host", "Releasing Control", "Reproducing") else if(isxeno(host)) @@ -23,9 +23,9 @@ switch(choice) if("Infecting a host") var/possible_targets = "" - if(infect_humans) possible_targets += "\nHumans" - if(infect_xenos) possible_targets += "\nXenos" - if(infect_yautja) possible_targets += "\nYautja" + if(borer_flags_targets & BORER_TARGET_HUMANS) possible_targets += "\nHumans" + if(borer_flags_targets & BORER_TARGET_XENOS) possible_targets += "\nXenos" + if(borer_flags_targets & BORER_TARGET_YAUTJA) possible_targets += "\nYautja" if(!possible_targets) possible_targets += "No one." help_message = "Infecting a host is the first major step for a borer to complete.\n\nThis is done by getting close to a potential host (This can be Human, Xeno or Yautja depending on settings controlled by admins) and clicking the Infest button in the top left of your screen.\n\nYour host will need to keep still for you to do this, and it's rare that they do so; for this reason you have Dominate Victim, which will allow you to temporarily stun a target.\n\nNote: Dominate is not sufficient to keep them down for 100% of the time it takes to infect however, so be careful with it.\n\nWhilst inside a host, and NOT in direct control, you can be detected by body scanners but otherwise are hidden from everyone but your host and other borers.\nYou can currently infect: [possible_targets]" if("Communicating") @@ -51,6 +51,19 @@ alert(target, help_message, choice, "Ok") //############# Physical Interaction Procs ############# +/mob/living/carbon/cortical_borer/proc/summon() + var/datum/emergency_call/custom/em_call = new() + em_call.name = real_name + em_call.mob_max = 1 + em_call.players_to_offer = list(src) + em_call.owner = null + em_call.ert_message = "A new Cortical Borer has been birthed!" + em_call.objectives = "Create enjoyable Roleplay. Do not kill your host. Do not take control unless granted permission or directed to by admins. Hivemind is :0 (That's Zero, not Oscar)" + + em_call.activate(announce = FALSE) + + message_admins("A new Cortical Borer has spawned at [get_area(loc)]") + /mob/living/carbon/cortical_borer/UnarmedAttack(atom/A) if(istype(A, /obj/structure/ladder)) A.attack_hand(src) @@ -60,21 +73,48 @@ /atom/proc/attack_borer(mob/living/carbon/cortical_borer/user) return +/mob/living/carbon/cortical_borer/MouseDrop(atom/over_object) + if(!CAN_PICKUP(usr, src)) + return ..() + var/mob/living/carbon/H = over_object + if(!istype(H) || !Adjacent(H) || H != usr) return ..() + + if(H.a_intent == INTENT_HELP) + get_scooped(H) + return + else + return ..() + +/mob/living/carbon/cortical_borer/get_scooped(mob/living/carbon/grabber) + if(stat != DEAD) + to_chat(grabber, SPAN_WARNING("You probably shouldn't pick that thing up while it still lives.")) + return + ..() //Brainslug scans the reagents in a target's bloodstream. /mob/living/carbon/human/attack_borer(mob/M) borerscan(M, src) +/mob/living/carbon/xenomorph/attack_borer(mob/M) + borerscan(M, src) /proc/borerscan(mob/living/user, mob/living/M) if(ishuman(M)) - var/mob/living/carbon/human/H = M - if(H.reagents) - if(H.reagents.reagent_list.len) + var/mob/living/carbon/human/human_target = M + if(human_target.reagents) + if(human_target.reagents.reagent_list.len) to_chat(user, SPAN_XENONOTICE("Subject contains the following reagents:")) - for(var/datum/reagent/R in H.reagents.reagent_list) + for(var/datum/reagent/R in human_target.reagents.reagent_list) to_chat(user, "[R.overdose != 0 && R.volume >= R.overdose && !(R.flags & REAGENT_CANNOT_OVERDOSE) ? SPAN_WARNING("OD: ") : ""] [round(R.volume, 1)]u [R.name]") else to_chat(user, SPAN_XENONOTICE("Subject contains no reagents.")) + if(isxeno(M)) + var/mob/living/carbon/xenomorph/xeno_target = M + to_chat(user, SPAN_XENONOTICE("Subject status as follows:")) + var/health_perc = xeno_target.maxHealth / 100 + to_chat(user, SPAN_XENONOTICE("Subject is at [xeno_target.health / health_perc]% bio integrity.")) + if(xeno_target.plasma_max) + var/plasma_perc = xeno_target.plasma_max / 100 + to_chat(user, SPAN_XENONOTICE("Subject has [xeno_target.plasma_stored / plasma_perc]% bio plasma.")) //Brainslug scuttles under a door, same code as used by xeno larva. /obj/structure/machinery/door/airlock/attack_borer(mob/living/carbon/cortical_borer/M) @@ -168,11 +208,11 @@ return FALSE if(ishuman(target)) var/mob/living/carbon/human/human_target = target - if(isspecieshuman(human_target) && !infect_humans)//Can it infect humans? Normally, yes. + if(isspecieshuman(human_target) && !(borer_flags_targets & BORER_TARGET_HUMANS))//Can it infect humans? Normally, yes. return FALSE - else if(isspeciesyautja(human_target) && !infect_yautja)//Can it infect yautja? Normally, no. + else if(isspeciesyautja(human_target) && !(borer_flags_targets & BORER_TARGET_YAUTJA))//Can it infect yautja? Normally, no. return FALSE - if(isxeno(target) && !infect_xenos)//Can it infect xenos? Normally, no. + if(isxeno(target) && !(borer_flags_targets & BORER_TARGET_XENOS))//Can it infect xenos? Normally, no. return FALSE if(target.stat != DEAD && Adjacent(target) && !target.has_brain_worms()) return TRUE @@ -271,7 +311,7 @@ /mob/living/carbon/cortical_borer/proc/let_go() if(!host || !src || QDELETED(host) || QDELETED(src)) return FALSE - if(!leaving || controlling) + if(!leaving || borer_flags_status & BORER_STATUS_CONTROLLING) return FALSE if(stat) to_chat(src, SPAN_XENOWARNING("You cannot release a target in your current state.")) @@ -286,7 +326,7 @@ /mob/living/carbon/cortical_borer/proc/leave_host() if(!host) return FALSE - if(controlling) + if(borer_flags_status & BORER_STATUS_CONTROLLING) detach() give_new_actions(ACTION_SET_HOSTLESS) @@ -342,7 +382,7 @@ return TRUE /mob/living/carbon/cortical_borer/proc/assume_control() - if(!host || !src || controlling) + if(!host || !src || borer_flags_status & BORER_STATUS_CONTROLLING) return FALSE if(!bonding) return FALSE @@ -389,7 +429,7 @@ host.lastKnownIP = s2h_ip bonding = FALSE - controlling = TRUE + borer_flags_status |= BORER_STATUS_CONTROLLING give_new_actions(ACTION_SET_CONTROL) host.med_hud_set_status() @@ -401,7 +441,7 @@ //Captive mind reclaims their body. /mob/living/captive_brain/proc/return_control(mob/living/carbon/cortical_borer/B) - if(!B || !B.controlling || !resisting_control) + if(!B || !(B.borer_flags_status & BORER_STATUS_CONTROLLING) || !resisting_control) return FALSE B.host.adjustBrainLoss(rand(5,10)) to_chat(src, SPAN_HIGHDANGER("With an immense exertion of will, you regain control of your body!")) @@ -428,10 +468,10 @@ log_debug(EXCEPTION("Missing borer or missing host brain upon borer release."), src) /mob/living/carbon/cortical_borer/proc/detach() - if(!host || !controlling) + if(!host || !(borer_flags_status & BORER_STATUS_CONTROLLING)) return FALSE - controlling = FALSE + borer_flags_status &= ~BORER_STATUS_CONTROLLING reset_view(null) get_host_actions() @@ -470,7 +510,7 @@ if(!(host && loc == host)) log_debug("Borer ([key_name(src)]) called host_death without being inside a host!") return FALSE - if(controlling) + if(borer_flags_status & BORER_STATUS_CONTROLLING) detach() to_chat(src, SPAN_XENOHIGHDANGER("You release your proboscis and flee as the psychic shock of your host's death washes over you!")) if(perma) @@ -593,7 +633,7 @@ T.add_vomit_floor() B.contaminant = 0 var/repro = max(B.can_reproduce - 1, 0) - new /mob/living/carbon/cortical_borer(T, B.generation + 1, TRUE, repro, B.infect_humans, B.infect_xenos, B.infect_yautja) + new /mob/living/carbon/cortical_borer(T, B.generation + 1, TRUE, repro, B.borer_flags_targets) return TRUE else to_chat(src, SPAN_XENONOTICE("You need at least [BORER_LARVAE_COST] enzymes to reproduce!")) @@ -772,7 +812,7 @@ C = new test() break - if(!C || !host || controlling || !src || stat) + if(!C || !host || (borer_flags_status & BORER_STATUS_CONTROLLING) || !src || stat) return FALSE var/datum/reagent/R = chemical_reagents_list[C.chem_id] if(enzymes < C.cost) diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index aff43fc0fe6f..cf2ce6dd29a2 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -137,7 +137,7 @@ . += "Evacuation: [eta_status]" var/mob/living/carbon/cortical_borer/B = has_brain_worms() - if(B && B.controlling) + if(B && (B.borer_flags_status & BORER_STATUS_CONTROLLING)) var/CR = "Yes" if(!B.can_reproduce) diff --git a/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm b/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm index 2f3bd91b8f93..8341c8e4af2c 100644 --- a/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm +++ b/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm @@ -145,7 +145,7 @@ . += "Hive Orders: -" var/mob/living/carbon/cortical_borer/B = has_brain_worms() - if(B && B.controlling) + if(B && (B.borer_flags_status & BORER_STATUS_CONTROLLING)) var/CR = "Yes" if(!B.can_reproduce) diff --git a/code/modules/reagents/chemistry_properties/prop_positive.dm b/code/modules/reagents/chemistry_properties/prop_positive.dm index b95935162bcc..ee1f7b8940ef 100644 --- a/code/modules/reagents/chemistry_properties/prop_positive.dm +++ b/code/modules/reagents/chemistry_properties/prop_positive.dm @@ -495,7 +495,7 @@ M.apply_damage(potency, TOX) var/mob/living/carbon/cortical_borer/player_2 = M.has_brain_worms() if(player_2) - if(player_2.controlling) + if(player_2.borer_flags_status & BORER_STATUS_CONTROLLING) player_2.detach() to_chat(src, SPAN_HIGHDANGER("You relinquish control as the unknown chemical overwhelms you!")) diff --git a/code/modules/surgery/brainworm.dm b/code/modules/surgery/brainworm.dm index 3a379605e36b..7ad8223a0fc8 100644 --- a/code/modules/surgery/brainworm.dm +++ b/code/modules/surgery/brainworm.dm @@ -63,6 +63,7 @@ user.count_niche_stat(STATISTICS_NICHE_SURGERY_LARVA) to_chat(parasite, SPAN_HIGHDANGER("You are ripped forcibly from your host's head!")) parasite.leave_host() + parasite.apply_damage(30, BRUTE) log_interact(user, target, "[key_name(user)] removed a parasite from [key_name(target)]'s head with [tool ? "\the [tool]" : "their hands"], ending [surgery].") diff --git a/colonialmarines.dme b/colonialmarines.dme index 211c667398c6..441e53be7292 100644 --- a/colonialmarines.dme +++ b/colonialmarines.dme @@ -33,6 +33,7 @@ #include "code\__DEFINES\atmospherics.dm" #include "code\__DEFINES\autolathe.dm" #include "code\__DEFINES\blood.dm" +#include "code\__DEFINES\borer_defines.dm" #include "code\__DEFINES\bsql.config.dm" #include "code\__DEFINES\bullet_traits.dm" #include "code\__DEFINES\callback.dm" @@ -1378,7 +1379,6 @@ #include "code\modules\asset_cache\assets\tgui.dm" #include "code\modules\asset_cache\assets\vending.dm" #include "code\modules\asset_cache\transports\asset_transport.dm" -#include "code\modules\borer\_defines.dm" #include "code\modules\borer\borer.dm" #include "code\modules\borer\borer_chemicals.dm" #include "code\modules\borer\borer_html.dm" diff --git a/icons/mob/hud/actions_borer.dmi b/icons/mob/hud/actions_borer.dmi index d1098b87d803641b4ca6ee29871f2c6dec5717ae..cdf8d9617e138862e6531e4d9feeb99ab4041ada 100644 GIT binary patch literal 2693 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 literal 2187 zcmV;62z2*}P)C0001ZP)t-sz`(#! zIbbj%I$1zwVlP;HJZ~r*FgzVWNgqil7%U+WBLMZ=HmsP}mH^CV03>f>Hybe*3m7*b zJef1hNGng5Nr+1%QLIy%HX%DjAwFjc3Zw!8wo?G6tTx&80K?ZGmYXztS4((GOI27I zY;Pq0%w~mUX28I}vW^k-00001bW%=J06^y0W&i*H$$C^+bVOxyV{&P5bZKvH004NL zm6EXvgFp<1XYVNv-Aiq|b}346sIMSbjb1?SBsulJ4e1R_^iN59cqSV== zP%{vtclxR?hog!XY=T|G`J^HX*x9fJ4ko}_#!pW`Rc&G5SVavIuiaEdEtvtf`itc- zIU%}O<|8o&i1jjjcj!WJ8JU3J`tr$U@yB;QQ6bW!dB z#yvit0jZINe*BM;LAS~?{=Q?P^D=vX_Y1@+-nZ9j$_M}e2JA^hK~#90?VF2wqDl;a zWe18NcC>3*JBZNj<^3P=BohYb4(hgN&v(dftr|>zGMRy-&T6&DB7cP>KKb!|`c=Y6 z(lpDm;W>M7^@jXG{RJRjuh;390i;So0BKRI*F~8I3~cTM!0?i!A%GkrkQZ6lLA7C< zD&%0heZ*gnkK3(JfALQQ9)Jw}M*%1j)h!W7vmyS9zz0y~5P%ZKD2qGbu7gKBef&`Y zZ~)TvAjseV$mGvTDJkOm0T7;5w?$yRQvw7IKuS4){_|7@%;z90Hk&G#149Ic09blB z|J`nnq6l;Vn*1386h)p<%D4`y%?6UBoXQa|V5FKYxOEcz_VtLy&_10yDJ4hXUt; zGX$LDEb*=rPlv++5C-?pQc4S8r$xX80RMGfD9LLeaE3rmQ_y;H0K7k;29J4u{)O^< zD1!id2OvqaW1Y(kC}0}o_0c?FoCP>bGR`YI0^lE21lka%Yk4UcS7LaXY7t(^mB#=;0vEtQumBEpJ;NM)k^~w6EVAJ6axGfR ztFi|0;{jy^kgs$?(%Yixi?Yx)Fc61>rq9UdDDs64u*f2dEV9U7(;tAFt+DR-ivEBQ zP1AVv7Xa)J@SuTUeLouj?Tat~r~UvB7zo$*q(4mo2(mB20Ms9FZ@H%G`!NE|iJh7- z0NHPX02TEI`~b1~-W-Af!PPfpKLSAg0nY8)?y36T&H)hsAp1hF#sQGYUr~PmasW$@ zE9!f*f6#a$Am3}qKH!PQ2H=zfvOj>&f#8bz{?b1Zr2+9FzAD;t(4nUK?;#8i? z7reTnzE>eWo&5a+f@%cWmoG%41IY8QMgStXqP~{^O%v=N2+}J6BCwN?^RYj`JxwUA z?>PtW@Zgl!2_;^ZLj+EN28Ix$ePJi*s^Z{Z>kr^Ou!bO7-^&OH5$qo{0<@ZaoCn4b zRN&v}4*&rV132~lRR5p>|Au`aFo%FwR(}9uaz%M<_78+;>>&`1WglW7=fEP1EV9TVUsC@+ z%>VrT^?%!*JAAM2+OF-d{`~y_UDuxFn?39MbN~GSecN`OQuJMaGr)GM{_^WL@CxG7 zClLE{!V-d@82COPu&)jvrT>r5D9N9@Z3@J;#(#_+;2fKGq2;o;TNzX3tp!TIPniGynWV0vU-{BLb~jepuUi0F-_3z&!Tv2Y6#|3ZPRV@B(}=Kft|1 zz}S2LZib+}Y6$L4LU`w`eV56P*?7J_3GbYSOcJONv~ChkPs2MGA_P!lkjO!3ZT-|j z#O_&&nKAyDklpo5F)44m7?F2|bD(3L{C&&C$dqd?$Cr0uF_zMuzx95A-ocv!a4qHB z{R;p$>u1*YVZB#<#pf4UWRXP{`TFk%OagrM#pZtk5Z-?zX5^y%5N>hf=L+Je{coBCyN7Yg5R{_k?#*o1I!+R z@%-HgMD`!d0GO48ae&D82j&3GnTDqLW Date: Thu, 6 Apr 2023 20:39:14 +0100 Subject: [PATCH 08/25] Gen1 buff chems and gen1 fix --- code/modules/borer/borer.dm | 9 +++++++-- code/modules/borer/borer_chemicals.dm | 16 +++++++++++++++- code/modules/borer/borer_procs.dm | 19 +++++++++++-------- 3 files changed, 33 insertions(+), 11 deletions(-) diff --git a/code/modules/borer/borer.dm b/code/modules/borer/borer.dm index 832a96aa35f3..80240f9eff11 100644 --- a/code/modules/borer/borer.dm +++ b/code/modules/borer/borer.dm @@ -112,7 +112,7 @@ var/max_contaminant = 120 //Decreases through hibernation or reproduction. var/hibernating = FALSE //Usable inside a host, but not when controlling. Allows clearing of impurities. - var/mob/living/carbon/host // Human host for the brain worm. + var/mob/living/carbon/host // Carbon host for the brain worm. var/truename // Name used for brainworm-speak. var/mob/living/captive_brain/host_brain // Used for swapping control of the body back and forth. var/docile = FALSE // Anti-Parasite or Anti-Enzyme chemicals can stop borers from acting. @@ -173,8 +173,11 @@ can_reproduce = reproduction borer_flags_targets = new_targets give_new_actions(ACTION_SET_HOSTLESS) - //GrantBorerActions() GiveBorerHUD() + if(generation == 1) + maxHealth = maxHealth + (maxHealth / 2) + max_enzymes = max_enzymes + (max_enzymes / 2) + max_contaminant = max_contaminant + (max_contaminant / 2) if((!is_admin_level(z)) && ERT) summon() @@ -216,6 +219,8 @@ docile = FALSE if(!hibernating && (enzymes < max_enzymes)) enzymes++ + if(generation == 1) + enzymes++ if(contaminant > 0) if(hibernating) contaminant = max(contaminant -= 1, 0) diff --git a/code/modules/borer/borer_chemicals.dm b/code/modules/borer/borer_chemicals.dm index ddb4b775ff5f..09e03fff7567 100644 --- a/code/modules/borer/borer_chemicals.dm +++ b/code/modules/borer/borer_chemicals.dm @@ -103,9 +103,23 @@ chem_id = "antineurotoxin" desc = "A bioagent that counteracts neurotoxins." cost = 100 - category = BORER_CAT_PUNISH + category = BORER_CAT_STIM +/datum/borer_chem/human/chloralhydrate + chem_name = "Chloral Hydrate" + chem_id = "chloralhydrate" + desc = "A powerful sedative which causes near instant sleepiness, but can be deadly in large quantities." + cost = 125 + quantity = 5 + category = BORER_CAT_PUNISH +/datum/borer_chem/human/potassium_chlorophoride + chem_name = "Potassium Chlorophoride" + chem_id = "potassium_chlorophoride" + desc = "A powerful chemical based on Potassium Chloride that causes instant cardiac arrest." + cost = 250 + quantity = 3 + category = BORER_CAT_PUNISH //Yautja chemicals /datum/borer_chem/yautja/thwei diff --git a/code/modules/borer/borer_procs.dm b/code/modules/borer/borer_procs.dm index fc48062c5b59..f37b62af89fb 100644 --- a/code/modules/borer/borer_procs.dm +++ b/code/modules/borer/borer_procs.dm @@ -38,7 +38,7 @@ var/capability = "able" if(!can_reproduce) capability = "forbidden" else if((enzymes < BORER_LARVAE_COST)) capability = "unable" - help_message = "Reproduction will take [BORER_LARVAE_COST] enzymes and direct control of your host.\n\nWhen in direct control you can use the Reproduce ability to spit out a new borer.\nMake sure to do this in a safe place, the new borer will not have a player to start with.\n\nYou are currently [capability] to reproduce." + help_message = "Reproduction will take a minimum of [BORER_LARVAE_COST] enzymes and direct control of your host.\n\nWhen in direct control you can use the Reproduce ability to spit out a new borer.\nMake sure to do this in a safe place, the new borer will not have a player to start with.\n\nYou are currently [capability] to reproduce." if("Assuming Control") help_message = "Assuming control will put you in direct control of your host, acting as if you are their player.\n\nYour host will be disassociated with their body, and trapped in their own mind unable to speak to anyone but you.\nWhile in this state you are unable to make use of the hivemind.\n\nYour host can resist your control and regain their body however this can cause brain damage in humanoids.\nYou must assume control to reproduce.\n\nNote: Whilst in direct control of your host medical HUDs will detect you.\n\n\nIMPORTANT: While in direct control of a mob you MUST NOT perform antag actions unless you have permission from staff." if("Contaminant & Enzymes") @@ -628,7 +628,10 @@ if(B.enzymes >= BORER_LARVAE_COST) to_chat(src, SPAN_XENOWARNING("Your host twitches and quivers as you rapdly excrete a larva from your sluglike body.")) visible_message(SPAN_WARNING("[src] heaves violently, expelling a rush of vomit and a wriggling, sluglike creature!")) - B.enzymes = 0 + if(B.generation == 1) + B.enzymes -= BORER_LARVAE_COST + else + B.enzymes = 0 var/turf/T = get_turf(src) T.add_vomit_floor() B.contaminant = 0 @@ -675,14 +678,14 @@ var/chem = initial(C.chem_id) var/datum/reagent/R = chemical_reagents_list[chem] if(R) - content += "" + content += "" else for(var/datum in subtypesof(/datum/borer_chem/human)) var/datum/borer_chem/C = datum var/chem = initial(C.chem_id) var/datum/reagent/R = chemical_reagents_list[chem] if(R) - content += "" + content += "" content += "
[initial(C.quantity)] units of [R.name] ([initial(C.cost)] Enzymes)

[initial(C.desc)]

[initial(C.quantity)] units of [C.chem_name] ([initial(C.cost)] Enzymes)

[initial(C.desc)]

[initial(C.quantity)] units of [R.name] ([initial(C.cost)] Enzymes)

[initial(C.desc)]

[initial(C.quantity)] units of [C.chem_name] ([initial(C.cost)] Enzymes)

[initial(C.desc)]

" @@ -797,7 +800,7 @@ /mob/living/carbon/cortical_borer/Topic(href, href_list, hsrc) if(href_list["borer_use_chem"]) locate(href_list["src"]) - if(!istype(src, /mob/living/carbon/cortical_borer)) + if(!isborer(src)) return FALSE if(docile) to_chat(src, SPAN_XENOWARNING("You are feeling far too docile to do that.")) @@ -816,13 +819,13 @@ return FALSE var/datum/reagent/R = chemical_reagents_list[C.chem_id] if(enzymes < C.cost) - to_chat(src, SPAN_XENOWARNING("You need [C.cost] enzymes stored to secrete [R.name]!")) + to_chat(src, SPAN_XENOWARNING("You need [C.cost] enzymes stored to secrete [C.chem_name]!")) return FALSE var/contamination = round(C.cost / 10) if(C.impure && ((contaminant + contamination) > max_contaminant)) - to_chat(src, SPAN_XENOWARNING("You are too contaminated to secrete [R.name]!")) + to_chat(src, SPAN_XENOWARNING("You are too contaminated to secrete [C.chem_name]!")) return FALSE - to_chat(src, SPAN_XENONOTICE("You squirt a measure of [R.name] from your reservoirs into [host]'s bloodstream.")) + to_chat(src, SPAN_XENONOTICE("You squirt a measure of [C.chem_name] from your reservoirs into [host]'s bloodstream.")) contaminant += contamination host.reagents.add_reagent(C.chem_id, C.quantity) enzymes -= C.cost From edf838597baf8ee9cafed1f050cb186ba6681c03 Mon Sep 17 00:00:00 2001 From: forest2001 Date: Thu, 6 Apr 2023 21:01:01 +0100 Subject: [PATCH 09/25] icon changes --- code/modules/borer/borer.dm | 10 +++++----- icons/mob/hud/actions_borer.dmi | Bin 2693 -> 3118 bytes 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/code/modules/borer/borer.dm b/code/modules/borer/borer.dm index 80240f9eff11..d180567d2a8d 100644 --- a/code/modules/borer/borer.dm +++ b/code/modules/borer/borer.dm @@ -385,7 +385,7 @@ /datum/action/innate/borer/talk_to_host name = "Converse with Host" - action_icon_state = "borer_whisper" + action_icon_state = "borer_talk" /datum/action/innate/borer/talk_to_host/action_activate() var/mob/living/carbon/cortical_borer/B = owner @@ -413,7 +413,7 @@ /datum/action/innate/borer/talk_to_borer name = "Converse with Borer" - action_icon_state = "borer_whisper" + action_icon_state = "borer_talk" /datum/action/innate/borer/talk_to_borer/action_activate() var/mob/living/carbon/cortical_borer/B = owner.has_brain_worms() @@ -422,7 +422,7 @@ /datum/action/innate/borer/talk_to_brain name = "Converse with Trapped Mind" - action_icon_state = "borer_whisper" + action_icon_state = "borer_talk" /datum/action/innate/borer/talk_to_brain/action_activate() var/mob/living/carbon/cortical_borer/B = owner.has_brain_worms() @@ -464,7 +464,7 @@ /datum/action/innate/borer/make_chems name = "Secrete Chemicals" - action_icon_state = "borer_chems" + action_icon_state = "borer_human_chems" /datum/action/innate/borer/make_chems/action_activate() if(!isborer(owner)) return FALSE @@ -476,7 +476,7 @@ /datum/action/innate/borer/scan_chems name = "Scan Chemicals" - action_icon_state = "borer_scan" + action_icon_state = "borer_human_scan" /datum/action/innate/borer/scan_chems/action_activate() if(!isborer(owner)) return FALSE diff --git a/icons/mob/hud/actions_borer.dmi b/icons/mob/hud/actions_borer.dmi index cdf8d9617e138862e6531e4d9feeb99ab4041ada..aab12b0b5b6aea52cf24f0f855d80e9fe5dba48f 100644 GIT binary patch delta 3063 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 FV1kMww#NVf delta 2635 zcmV-R3bggE7=;xjiBL{Q4GJ0x0000DNk~Le0001>0001>2m=5B0K5^G9{>OVc#$O{ zkxCu|ByVDoXdZuJFIcuy0GTt)rmQyE^#H@yAC{XmRahBpZzKsB+>QVM00DGTPE!Ct z=GbNc00ARfFDZ*Bkpc$}q_!ES>v42I9}6j6Jh+HKbzXhCfx3Q}Rx zc0vr?#u6mRN$Kkk80ukCMQCrf^4a{f zy2x!s6#{>1J;{Cr;|t8y)5kDx!$Y3V{!Emk%m_4EH0^o73&STmUMOiuRhCV!AaB8v zHH!L6Eop*=RCy_Ev+D|=nKK@@0NVj+vEM(|`A7V2_9Y7nZSwJJ8DL;lifk{LQ;5Tgo7~e=>hE>pzAECq)sh^mKxXmyYkXLXxM1J`jzJ|~; z2b&DKRc09CF>_FpU-CUaH-tk#-Dz9{xS6_0togTf<({9BkXLd$4G@;`5P|eLVFOplgv0$6yL=dF^x%@Nek74Rn$Dd_+o)7&aHVc$S+N;L-0QZN(AyWX2rT}6* zoea2f_(_(gheH}?Qvs=nrxw6BFk62GqYber3)vnjuut>JfFfR%R^$r=oJ2GZagC_@ zbIczA7>RfjAcFk_lZci%NDYAT9tsd=8Obex7X4AgQ%WKYAVU2l^_MpTAQxRCO$GVl z&0}sb2eW*nKFD)MA|8Hy8YzGcUqh$?OwvuO9`*d0n1h&qkB@^d2N62qfzz9ti1_Hk05gODK(oL!9NO3C3dq2aPn# zPUwl#6a2&P=_xZ-P}8U>RpO~qLE<>oQQl@Y2pA13m>rKI{|P_v(eZz19^wwG1k}Xq z0PULv(V#aQ&Hs~}j>q`pBNnr0j}MQ>Q|1q-ji)-mt|@ac+L-*OBb?vPpD=wOef)VQ zS$sU5#sI?kts74}@&15=9bw1M_kbU81f1lb=MU(LFkb$Ed=J=*r)~Tgz#d|6k&wXw zkg%a}ZuxmC(r;9u{h@ zNq+Z5;A#_hZzHZo`zIQ4kiIvE{^ta&&;N`iNiy6F%mL`wjQ9hhKF1V5qxsIkxd55a z=h%$^&HAD@;9hs#J7)zA`l2sjBJ{Z%0*Xp1>XRr{MPGCS zYV5CqKaDMCRb8LjZ=e=1l>x)j~d%&Lj#r*ZX5xW_a zzUVaGnfv-2^Vau9ZTa>3qSJKx9Bb!bFrZ6c46`L%c3OXJ`4{d>mp&f$uzTC$7s1`z zh|BKYh+TH;`;R^R;ebSA6aKt!+IGx;JH@0K09^iC1F}RK(*n5smm34N#}@=c0GA&v zzmQ9QBS4na<0lCQ04{%PHR5DI!(}C7huV-q1L*K~EeizHUYN7PSsD;%03CjATtCbM zE2x(?pgw=+^Cy}l=mWU?f-k*X@)_o!P8?$L_(-4ywEM*pxlGXy{p2+O_>^VDN>W6-dqzmxr z0Mz;U8kwda`n8$P6yuZZpa9zaH6iMUS(e|RQHOs6LVs!W!#r#bx&Y4(K%E~K3AC9D z0_I^AGy$F+fOfyA8k~qPmppWl=l~C_1>naY0J!}1R;Fun0>T0Lsa72R%%3*un1^33 zlFLLu=awIKCvd=aFL24$&8W#<&vAGF0w9}j!j z!ybPYZd!frhr>Uk{^!&O*F|wH@dRC!MOjqJ$@k z(o>by;sDn$w%0XT3rJBzT>|Y}{&?PU)?T7MC& zVO6b5(Qee=(DK%2-0!;e(iLy@Lm&LA$eI_rs7p9VP{+6LZny3GYrgjG+Ba=H;k`l= zFa7+QUGYN&3)!AYQEC9?+wTD)QQm%U)!zOKh(ve$uZ%BCBvj+2Kj&{Gq4;qQ{+54$ zr7WQq@R`^F-^pzN3HhqN`XwwDHN^0f-^CYSytrDf${f7+A|aemmatHUnB2R~a6mc6 zYd*-uAi;ebHR;ViTrlOrPIa7);2JJT$-ggavp^bylO4D6x~T1z_z`Xj0gc Date: Thu, 11 May 2023 22:48:17 +0100 Subject: [PATCH 10/25] rebase --- code/__DEFINES/chemistry.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/__DEFINES/chemistry.dm b/code/__DEFINES/chemistry.dm index c232a1cad77f..b24000c29ed4 100644 --- a/code/__DEFINES/chemistry.dm +++ b/code/__DEFINES/chemistry.dm @@ -83,7 +83,7 @@ #define CHEM_EFFECT_HYPER_THROTTLE (1<<2) //universal understand but not speech #define CHEM_EFFECT_ORGAN_STASIS (1<<3) //peri stabiliser #define CHEM_EFFECT_NO_BLEEDING (1<<4) //replacement for quickclot -#define CHEM_EFFECT_ANTI_PARASITE (1<<4) //PROPERTY_ANTIPARASITIC +#define CHEM_EFFECT_ANTI_PARASITE (1<<5) //PROPERTY_ANTIPARASITIC //Blood plasma From d34b5a1daf52498f4faee01907fff78787fb2414 Mon Sep 17 00:00:00 2001 From: forest2001 Date: Fri, 3 Mar 2023 22:38:06 +0000 Subject: [PATCH 11/25] Ported Paradise SS13 Cortical Borers tiny bit of text clean up Working chems working chems p2 Text SPANs Action swap and categories Moved procs to separate file Apparently this didn't go through in the last commmit? Ported Paradise SS13 Cortical Borers tiny bit of text clean up Working chems working chems p2 Text SPANs Action swap and categories Moved procs to separate file reproduction levels icons icons Cure hiding icon Rebase Checks PROC_REF PROC_REF 515 is lame UID Temp Reproduce icon UID death Disables Zombie Powder and stun action wheel too Update ColonialMarinesALPHA.dme Co-authored-by: harryob Lang fix 0.5 Lang fix, property, chem names. Var tidy and chem cats Speech, Chem Fix & Enzyme Grinding Chem log Spans and Ghost chat emote freedom parenthesis stun fix Death handling & hud fix Follow and Death death check Player SimpleMobs in Orbit Enzymes and FOLLOW Defines, Balance, Brain Damage Defines and Balancing Spawn message & Orbit order Orbit order X Impurity Part 1 Luminescence and Icons Icon Rebase Hibernation message types Ported Paradise SS13 Cortical Borers working chems p2 Text SPANs Moved procs to separate file Ported Paradise SS13 Cortical Borers tiny bit of text clean up Working chems working chems p2 Text SPANs Action swap and categories Moved procs to separate file icons icons hiding icon Rebase UID Temp Reproduce icon UID death Disables Zombie Powder and stun action wheel too Update ColonialMarinesALPHA.dme Co-authored-by: harryob Var tidy and chem cats Speech, Chem Fix & Enzyme Grinding Spans and Ghost chat Death handling & hud fix Follow and Death Player SimpleMobs in Orbit Enzymes and FOLLOW Defines, Balance, Brain Damage Defines and Balancing Spawn message & Orbit order Orbit order X Luminescence and Icons Icon Rebase 515 rebase ma antiruntime carbon tests --- code/__DEFINES/chemistry.dm | 1 + code/__DEFINES/language.dm | 1 + code/datums/emergency_calls/custom.dm | 2 +- code/datums/mob_hud.dm | 6 + code/modules/borer/_defines.dm | 8 + code/modules/borer/borer.dm | 407 ++++++++++ code/modules/borer/borer_chemicals.dm | 137 ++++ code/modules/borer/borer_html.dm | 69 ++ code/modules/borer/borer_procs.dm | 720 ++++++++++++++++++ code/modules/mob/holder.dm | 6 + code/modules/mob/language/languages.dm | 44 ++ .../mob/living/carbon/carbon_defines.dm | 2 + code/modules/mob/living/carbon/human/death.dm | 3 + code/modules/mob/living/carbon/human/human.dm | 18 + code/modules/mob/living/carbon/human/life.dm | 3 + code/modules/mob/living/say.dm | 4 + .../mob/living/simple_animal/simple_animal.dm | 5 +- .../chemistry_machinery/reagent_grinder.dm | 10 + .../chemistry_properties/prop_positive.dm | 13 + .../reagents/chemistry_reactions/other.dm | 8 + .../reagents/chemistry_reagents/other.dm | 24 + code/span_macros.dm | 1 + colonialmarines.dme | 5 + icons/mob/brainslug.dmi | Bin 0 -> 4277 bytes icons/mob/hud/actions_borer.dmi | Bin 0 -> 2032 bytes .../tgui/interfaces/BorerChemDispenser.js | 1 + 26 files changed, 1495 insertions(+), 3 deletions(-) create mode 100644 code/modules/borer/_defines.dm create mode 100644 code/modules/borer/borer.dm create mode 100644 code/modules/borer/borer_chemicals.dm create mode 100644 code/modules/borer/borer_html.dm create mode 100644 code/modules/borer/borer_procs.dm create mode 100644 icons/mob/brainslug.dmi create mode 100644 icons/mob/hud/actions_borer.dmi create mode 100644 tgui/packages/tgui/interfaces/BorerChemDispenser.js diff --git a/code/__DEFINES/chemistry.dm b/code/__DEFINES/chemistry.dm index 9a29754381f3..d3720c7f3d0f 100644 --- a/code/__DEFINES/chemistry.dm +++ b/code/__DEFINES/chemistry.dm @@ -83,6 +83,7 @@ #define CHEM_EFFECT_HYPER_THROTTLE (1<<2) //universal understand but not speech #define CHEM_EFFECT_ORGAN_STASIS (1<<3) //peri stabiliser #define CHEM_EFFECT_NO_BLEEDING (1<<4) //replacement for quickclot +#define CHEM_EFFECT_ANTI_PARASITE (1<<4) //PROPERTY_ANTIPARASITIC //Blood plasma diff --git a/code/__DEFINES/language.dm b/code/__DEFINES/language.dm index 8cac90defb26..9aa2ad702a94 100644 --- a/code/__DEFINES/language.dm +++ b/code/__DEFINES/language.dm @@ -15,6 +15,7 @@ #define LANGUAGE_APOLLO "Apollo Link" #define LANGUAGE_TELEPATH "Telepath Implant" +#define LANGUAGE_BORER "Cortical Link" #define ALL_HUMAN_LANGUAGES list(LANGUAGE_ENGLISH, LANGUAGE_JAPANESE, LANGUAGE_CHINESE, LANGUAGE_RUSSIAN, LANGUAGE_GERMAN, LANGUAGE_SPANISH) diff --git a/code/datums/emergency_calls/custom.dm b/code/datums/emergency_calls/custom.dm index 0117c83fc19c..f65fdba536b6 100644 --- a/code/datums/emergency_calls/custom.dm +++ b/code/datums/emergency_calls/custom.dm @@ -28,7 +28,7 @@ return M.transfer_to(H, TRUE) - + to_chat(H, SPAN_ALERT("[objectives]")) players_to_offer -= H return diff --git a/code/datums/mob_hud.dm b/code/datums/mob_hud.dm index b1d9a9c2fade..fa2af17240f9 100644 --- a/code/datums/mob_hud.dm +++ b/code/datums/mob_hud.dm @@ -446,6 +446,12 @@ var/list/datum/mob_hud/huds = list( return + var/mob/living/carbon/cortical_borer/B = has_brain_worms() + if(B && B.controlling) + holder.icon_state = "hudbrainworm" + if(!holder2_set) + holder2.icon_state = "hudbrainworm" + return for(var/datum/disease/D in viruses) if(!D.hidden[SCANNER]) diff --git a/code/modules/borer/_defines.dm b/code/modules/borer/_defines.dm new file mode 100644 index 000000000000..15929f6fa492 --- /dev/null +++ b/code/modules/borer/_defines.dm @@ -0,0 +1,8 @@ +/// Chemical categories +#define BORER_CAT_HEAL "Medicines" +#define BORER_CAT_PUNISH "Motivators" +#define BORER_CAT_STIM "Stimulants" +#define BORER_CAT_SELF "Self Protection" + +///Amount of chemicals needed for a borer to reproduce, provided reproduction is toggled. +#define BORER_LARVAE_COST 400 diff --git a/code/modules/borer/borer.dm b/code/modules/borer/borer.dm new file mode 100644 index 000000000000..683956bf1fb4 --- /dev/null +++ b/code/modules/borer/borer.dm @@ -0,0 +1,407 @@ +/mob/living/captive_brain + name = "host brain" + real_name = "host brain" + +/mob/living/captive_brain/say(message) + if(client) + if(client.prefs.muted & MUTE_IC) + to_chat(src, SPAN_WARNING("You cannot speak in IC (muted).")) + return + if(client.handle_spam_prevention(message, MUTE_IC)) + return + + if(istype(loc,/mob/living/carbon/cortical_borer)) + message = trim(sanitize(copytext(message, 1, MAX_MESSAGE_LEN))) + if(!message) + return + if(stat == DEAD) + return say_dead(message) + var/mob/living/carbon/cortical_borer/B = loc + to_chat(src, SPAN_BORER("You whisper silently, [message]"), type = MESSAGE_TYPE_RADIO) + to_chat(B.host, SPAN_BORER("The captive mind of [src] whispers, \"[message]\""), type = MESSAGE_TYPE_RADIO) + log_say("BORER: ([key_name(src)] to [key_name(B.host)]) [message]", src) + for (var/mob/dead in GLOB.dead_mob_list) + var/track_borer = " (F)" + if(!istype(dead,/mob/new_player) && !istype(dead,/mob/living/brain)) //No meta-evesdropping + dead.show_message(SPAN_BORER("BORER: ([name] (trapped mind) to [B.truename][track_borer]) whispers: [message]"), SHOW_MESSAGE_VISIBLE) + +/mob/living/captive_brain/say_understands(mob/other, datum/language/speaking = null) + var/mob/living/carbon/cortical_borer/B = loc + if(!istype(B)) + log_debug(EXCEPTION("Trapped mind found without a borer!"), src) + return FALSE + return B.host.say_understands(other, speaking) + +/mob/living/captive_brain/emote(act, m_type = 1, message = null, intentional = FALSE, force_silence = FALSE) + return + +/mob/living/captive_brain/resist() + var/mob/living/carbon/cortical_borer/B = loc + if(!istype(B)) + log_debug(EXCEPTION("Trapped mind found without a borer!"), src) + return + + to_chat(src, SPAN_DANGER("You begin doggedly resisting the parasite's control (this will take approximately sixty seconds).")) + to_chat(B.host, SPAN_XENOWARNING("You feel the captive mind of [src] begin to resist your control.")) + + var/delay = (rand(350,450) + B.host.getBrainLoss()) + addtimer(CALLBACK(src, PROC_REF(return_control), B), delay) + +/mob/living/carbon/cortical_borer + name = "cortical borer" + real_name = "cortical borer" + desc = "A small, quivering sluglike creature." + speak_emote = list("chirrups") + icon = 'icons/mob/brainslug.dmi' + icon_state = "brainslug" + speed = 0 + a_intent = INTENT_HARM + status_flags = CANPUSH + attacktext = "nips" + friendly = "prods" + mob_size = MOB_SIZE_SMALL + density = 0 + pass_flags = PASS_FLAGS_CRAWLER + mob_size = MOB_SIZE_SMALL + faction = list("creature") + hud_possible = list(HEALTH_HUD,STATUS_HUD) + universal_understand = TRUE + + holder_type = /obj/item/holder/borer + + var/generation = 1 + var/static/list/borer_names = list( + "Primary", "Secondary", "Tertiary", "Quaternary", "Quinary", "Senary", + "Septenary", "Octonary", "Novenary", "Decenary", "Undenary", "Duodenary", + ) + var/talk_inside_host = FALSE // So that borers don't accidentally give themselves away on a botched message + var/used_dominate + var/attempting_to_dominate = FALSE // To prevent people from spam opening the Dominate Victim input + + var/enzymes = 10 // Enzymes used for chemical injection. + var/max_enzymes = 500 + var/contaminant = 0 //Contaminant builds up on enzyme usage, roughly proportionate to cost of use. + var/max_contaminant = 120 //Decreases through hibernation or reproduction. + var/hibernating = FALSE //Usable inside a host, but not when controlling. Allows clearing of impurities. + + var/mob/living/carbon/human/host // Human host for the brain worm. + var/truename // Name used for brainworm-speak. + var/mob/living/captive_brain/host_brain // Used for swapping control of the body back and forth. + var/controlling // Used in human death check. + var/docile = FALSE // Anti-Parasite or Anti-Enzyme chemicals can stop borers from acting. + var/bonding = FALSE + var/leaving = FALSE + var/hiding = FALSE + var/can_reproduce = FALSE // Locked to manual override to prevent things getting out of hand. + var/infect_hunter = FALSE // Locked for normal use. + + var/list/datum/reagent/synthesized_chems + + /// All of these surely have a better way of being handled. + var/datum/action/innate/borer/talk_to_host/action_talk_to_host = new + var/datum/action/innate/borer/infest_host/action_infest_host = new + var/datum/action/innate/borer/toggle_hide/action_toggle_hide = new + var/datum/action/innate/borer/talk_to_borer/action_talk_to_borer = new + var/datum/action/innate/borer/talk_to_brain/action_talk_to_brain = new + var/datum/action/innate/borer/take_control/action_take_control = new + var/datum/action/innate/borer/give_back_control/action_give_back_control = new + var/datum/action/innate/borer/leave_body/action_leave_body = new + var/datum/action/innate/borer/make_chems/action_make_chems = new + var/datum/action/innate/borer/make_larvae/action_make_larvae = new + var/datum/action/innate/borer/freeze_victim/action_freeze_victim = new + var/datum/action/innate/borer/torment/action_torment = new + var/datum/action/innate/borer/scan_chems/action_scan_chems = new + var/datum/action/innate/borer/hibernate/action_hibernate = new + +/mob/living/carbon/cortical_borer/initialize_pass_flags(datum/pass_flags_container/PF) + ..() + if (PF) + PF.flags_pass = PASS_MOB_THRU|PASS_FLAGS_CRAWLER + PF.flags_can_pass_all = PASS_ALL^PASS_OVER_THROW_ITEM + +/mob/living/carbon/cortical_borer/proc/summon() + var/datum/emergency_call/custom/em_call = new() + em_call.name = "Cortical Borer" + em_call.mob_max = 1 + em_call.players_to_offer = list(src) + em_call.owner = null + em_call.ert_message = "A new Cortical Borer has been birthed!" + em_call.objectives = "Create enjoyable Roleplay. Do not kill your host. Do not take control unless granted permission or directed to by admins. Hivemind is :0 (That's Zero, not Oscar)" + + em_call.activate(announce = FALSE) + + message_admins("A new Cortical Borer has spawned at [get_area(loc)]") + +/mob/living/carbon/cortical_borer/New(atom/newloc, gen=1, ERT = FALSE, reproduction = 0) + ..(newloc) + generation = gen + add_language(LANGUAGE_BORER) + var/mob_number = rand(1000,9999) + real_name = "Cortical Borer [mob_number]" + truename = "[borer_names[min(generation, borer_names.len)]] [mob_number]" + can_reproduce = reproduction + GrantBorerActions() + GiveBorerHUD() + if((!is_admin_level(z)) && ERT) + summon() + +/mob/living/carbon/cortical_borer/death() + var/datum/language/corticalborer/c_link = GLOB.all_languages[LANGUAGE_BORER] + c_link.broadcast(src, null, null, TRUE) + . = ..() + +/mob/living/carbon/cortical_borer/update_icons() + if(stat == DEAD) + icon_state = "Borer Dead" + + else if(lying) + if((resting || sleeping) && (!knocked_down && !knocked_out && health > 0)) + icon_state = "Borer Resting" + else + icon_state = "Borer Stunned" + else + icon_state = "Borer" + +/mob/living/carbon/cortical_borer/proc/GiveBorerHUD() + var/datum/mob_hud/H = huds[MOB_HUD_MEDICAL_OBSERVER] + H.add_hud_to(src) + +/mob/living/carbon/cortical_borer/can_ventcrawl() + return TRUE + +/mob/living/carbon/cortical_borer/initialize_pass_flags(datum/pass_flags_container/PF) + ..() + if (PF) + PF.flags_pass = PASS_MOB_THRU|PASS_FLAGS_CRAWLER + PF.flags_can_pass_all = PASS_ALL^PASS_OVER_THROW_ITEM + +/mob/living/carbon/cortical_borer/get_status_tab_items() + . = ..() + + var/CR = "Yes" + if(!can_reproduce) + CR = "Forbidden" + else if((enzymes < BORER_LARVAE_COST)) + CR = "No" + + . += "" + . += "Borer:" + . += "Name: [truename]" + . += "Can Reproduce: [CR]" + . += "Enzymes: [round(enzymes)]/[round(max_enzymes)]" + . += "Contaminant: [round(contaminant)]/[round(max_contaminant)]" + if(host) + . += "" + . += "Host Brain Damage: [host.brainloss]/100" + +/mob/living/carbon/cortical_borer/say(message)//I need to parse the message properly so it doesn't look stupid + var/datum/language/parsed_language = parse_language(message) + var/new_message = message + if(parsed_language) + new_message = copytext(message, 3) + if(istype(parsed_language, /datum/language/corticalborer)) + parsed_language.broadcast(src, new_message) + return + if(hibernating) + to_chat(src, SPAN_WARNING("You cannot speak aloud while hibernating!")) + return + if(loc == host && !talk_inside_host) + to_chat(src, SPAN_WARNING("You've disabled audible speech while inside a host! Re-enable it under the borer tab, or stick to borer communications.")) + return + . = ..() + + +/mob/living/carbon/cortical_borer/Life(delta_time) + ..() + if(host) + if(!stat && host.stat != DEAD) + if(((host.chem_effect_flags & CHEM_EFFECT_ANTI_PARASITE) && !host.reagents.has_reagent("benzyme")) || host.reagents.has_reagent("bcure")) + if(!docile) + if(controlling) + to_chat(host, SPAN_XENODANGER("You feel the soporific flow of a chemical in your host's blood, lulling you into docility.")) + else + to_chat(src, SPAN_XENODANGER("You feel the soporific flow of a chemical in your host's blood, lulling you into docility.")) + docile = TRUE + else + if(docile) + if(controlling) + to_chat(host, SPAN_XENONOTICE("You shake off your lethargy as the chemical leaves your host's blood.")) + else + to_chat(src, SPAN_XENONOTICE("You shake off your lethargy as the chemical leaves your host's blood.")) + docile = FALSE + if(!hibernating && (enzymes < max_enzymes)) + enzymes++ + if(contaminant > 0) + if(hibernating) + contaminant = max(contaminant -= 1, 0) + else + contaminant = max(contaminant -= 0.1, 0) + if(controlling) + if(docile) + to_chat(host, SPAN_XENOWARNING("You are feeling far too docile to continue controlling your host...")) + host.release_control() + return + else + if(contaminant > 0) + if(!luminosity) + SetLuminosity(2) + contaminant = max(contaminant - 0.3, 0) + else + SetLuminosity(0) + + update_canmove() + update_icons() +/datum/action/innate/borer + icon_file = 'icons/mob/hud/actions_borer.dmi' + +/datum/action/innate/borer/talk_to_host + name = "Converse with Host" + action_icon_state = "borer_whisper" + +/datum/action/innate/borer/talk_to_host/action_activate() + var/mob/living/carbon/cortical_borer/B = owner + B.Communicate() + +/datum/action/innate/borer/infest_host + name = "Infest" + action_icon_state = "borer_infest" + +/datum/action/innate/borer/infest_host/action_activate() + var/mob/living/carbon/cortical_borer/B = owner + B.infest() + +/datum/action/innate/borer/toggle_hide + name = "Toggle Hide" + action_icon_state = "borer_hiding_0" + +/datum/action/innate/borer/toggle_hide/action_activate() + var/mob/living/carbon/cortical_borer/B = owner + B.hide_borer() + + button.overlays.Cut() + button.overlays += image('icons/mob/hud/actions_borer.dmi', button, "borer_hiding_[B.hiding]") + +/datum/action/innate/borer/talk_to_borer + name = "Converse with Borer" + action_icon_state = "borer_whisper" + +/datum/action/innate/borer/talk_to_borer/action_activate() + var/mob/living/carbon/cortical_borer/B = owner.has_brain_worms() + B.host = owner + B.host.borer_comm() + +/datum/action/innate/borer/talk_to_brain + name = "Converse with Trapped Mind" + action_icon_state = "borer_whisper" + +/datum/action/innate/borer/talk_to_brain/action_activate() + var/mob/living/carbon/cortical_borer/B = owner.has_brain_worms() + B.host = owner + B.host.trapped_mind_comm() + +/datum/action/innate/borer/take_control + name = "Assume Control" + action_icon_state = "borer_control" + +/datum/action/innate/borer/take_control/action_activate() + var/mob/living/carbon/cortical_borer/B = owner + if(B.hibernating) + to_chat(B, SPAN_WARNING("You cannot do that while hibernating!")) + return + B.bond_brain() + +/datum/action/innate/borer/give_back_control + name = "Release Control" + action_icon_state = "borer_leave" + +/datum/action/innate/borer/give_back_control/action_activate() + var/mob/living/carbon/cortical_borer/B = owner.has_brain_worms() + B.host = owner + B.host.release_control() + +/datum/action/innate/borer/leave_body + name = "Leave Host" + action_icon_state = "borer_leave" + +/datum/action/innate/borer/leave_body/action_activate() + var/mob/living/carbon/cortical_borer/B = owner + if(B.hibernating) + to_chat(B, SPAN_WARNING("You cannot do that while hibernating!")) + return + B.release_host() + +/datum/action/innate/borer/make_chems + name = "Secrete Chemicals" + action_icon_state = "borer_chems" + +/datum/action/innate/borer/make_chems/action_activate() + var/mob/living/carbon/cortical_borer/B = owner + if(B.hibernating) + to_chat(B, SPAN_WARNING("You cannot do that while hibernating!")) + return + B.secrete_chemicals() + +/datum/action/innate/borer/scan_chems + name = "Scan Chemicals" + action_icon_state = "borer_scan" + +/datum/action/innate/borer/scan_chems/action_activate() + var/mob/living/carbon/cortical_borer/B = owner + if(B.hibernating) + to_chat(B, SPAN_WARNING("You cannot do that while hibernating!")) + return + borerscan(B, B.host) + +/datum/action/innate/borer/make_larvae + name = "Reproduce" + action_icon_state = "borer_reproduce" + +/datum/action/innate/borer/make_larvae/action_activate() + var/mob/living/carbon/cortical_borer/B = owner.has_brain_worms() + B.host = owner + B.host.spawn_larvae() + +/datum/action/innate/borer/freeze_victim + name = "Dominate Victim" + action_icon_state = "borer_stun" + +/datum/action/innate/borer/freeze_victim/action_activate() + var/mob/living/carbon/cortical_borer/B = owner + B.dominate_victim() + +/datum/action/innate/borer/torment + name = "Torment Host" + action_icon_state = "borer_torment" + +/datum/action/innate/borer/torment/action_activate() + var/mob/living/carbon/cortical_borer/B = owner.has_brain_worms() + B.host = owner + B.host.punish_host() + +/datum/action/innate/borer/hibernate + name = "Toggle Hibernation" + action_icon_state = "borer_sleeping_0" + +/datum/action/innate/borer/hibernate/action_activate() + var/mob/living/carbon/cortical_borer/B = owner + B.hibernate() + button.overlays.Cut() + button.overlays += image('icons/mob/hud/actions_borer.dmi', button, "borer_sleeping_[B.hibernating]") + +/mob/living/carbon/cortical_borer/MouseDrop(atom/over_object) + if(!CAN_PICKUP(usr, src)) + return ..() + var/mob/living/carbon/H = over_object + if(!istype(H) || !Adjacent(H) || H != usr) return ..() + + if(H.a_intent == INTENT_HELP) + get_scooped(H) + return + else + return ..() + +/mob/living/carbon/cortical_borer/get_scooped(mob/living/carbon/grabber) + if(stat != DEAD) + to_chat(grabber, SPAN_WARNING("You probably shouldn't pick that thing up while it still lives.")) + return + ..() diff --git a/code/modules/borer/borer_chemicals.dm b/code/modules/borer/borer_chemicals.dm new file mode 100644 index 000000000000..ddb4b775ff5f --- /dev/null +++ b/code/modules/borer/borer_chemicals.dm @@ -0,0 +1,137 @@ +/datum/borer_chem + var/chem_name = "Unset" + /// Chemical identifier, used in the proc to create it. + var/chem_id = "unset" + var/desc = "This is a chemical" + /// Synthetic chemicals. + var/impure = TRUE + var/cost = 50 + var/quantity = 10 + + var/category = BORER_CAT_HEAL + + +//Medical Chems +/datum/borer_chem/human/tricordrazine + chem_name = "Tricordrazine" + chem_id = "tricordrazine" + desc = "Can be used to treat a wide range of injuries." + +/datum/borer_chem/human/anti_toxin + chem_name = "Dylovene" + chem_id = "anti_toxin" + desc = "General use chemical that neutralizes most toxins in the bloodstream. Can be used as a mild anti-hallucinogen and to reduce tiredness." + +/datum/borer_chem/human/dexalin + chem_name = "Dexalin" + chem_id = "dexalin" + desc = "Feeds oxygen directly into red bloodcells. Used as an antidote to lexorin poisoning." + +/datum/borer_chem/human/peridaxon + chem_name = "Peridaxon" + chem_id = "peridaxon" + desc = "Prevents symptoms caused by damaged internal organs while in the bloodstream, but does not fix the organ damage. Overdosing will cause internal tissue damage." + +/datum/borer_chem/human/imidazoline + chem_name = "Imidazoline" + chem_id = "imidazoline" + desc = "Used for treating non-genetic eye trauma." + cost = 90 + quantity = 5 + +/datum/borer_chem/human/alkysine + chem_name = "Alkysine" + chem_id = "alkysine" + desc = "Small amounts can repair extensive brain trauma. Overdosing on alkysine is extremely toxic." + cost = 80 + quantity = 5 + +/datum/borer_chem/human/quickclot + chem_name = "Quickclot" + chem_id = "quickclot" + desc = "Vastly improves the blood's natural ability to coagulate and stop bleeding. Overdosing will result in severe tissue damage." + cost = 90 + quantity = 5 + +/datum/borer_chem/human/iron + chem_id = "Iron" + chem_id = "iron" + desc = "Promotes production of blood. Overdosing on iron is extremely toxic." + cost = 20 + impure = FALSE + +/datum/borer_chem/human/oxycodone + chem_id = "Oxycodone" + chem_id = "oxycodone" + desc = "An extremely strong painkiller." + cost = 120 + quantity = 5 + +/datum/borer_chem/human/epinephrine + chem_name = "Epinephrine" + chem_id = "adrenaline" + desc = "Useful for restarting the heart. Overdosing may stress the heart and cause tissue damage." + + +//"Motivation" Chems +/datum/borer_chem/human/stimulant_brain + chem_name = "Neurological Stimulant" + chem_id = "brain_stimulant" + desc = "A powerful stimulant that enhances brain function. Lethal in high doses. Lasts one minute per unit." + cost = 300 + quantity = 1 + category = BORER_CAT_STIM + +/datum/borer_chem/human/stimulant_muscle + chem_name = "Musculature Stimulant" + chem_id = "speed_stimulant" + desc = "A powerful stimulant that enhances musculature. Lethal in high doses. Lasts one minute per unit." + cost = 300 + quantity = 1 + category = BORER_CAT_STIM + +/datum/borer_chem/human/neurotoxin + chem_name = "Neurotoxin" + chem_id = PLASMA_NEUROTOXIN + desc = "A potent and hallucinagenic neurotoxin." + cost = 125 + quantity = 1 + category = BORER_CAT_PUNISH + +/datum/borer_chem/human/antineurotoxin + chem_name = "Anti-Neurotoxin" + chem_id = "antineurotoxin" + desc = "A bioagent that counteracts neurotoxins." + cost = 100 + category = BORER_CAT_PUNISH + + + +//Yautja chemicals +/datum/borer_chem/yautja/thwei + chem_name = "Thwei" + chem_id = "thwei" + desc = "A synthetic cocktail of chemicals used to accelerate healing in the Yautja species. It has no effect on humans." + cost = 150 + quantity = 20 + + + +//Anti-Sugar +/datum/borer_chem/human/enzyme + chem_name = "Cortical Enzyme" + chem_id = "benzyme" + desc = "An enzyme focused on consuming chemicals in the bloodstream. Helps fight addictions. This will work as a preventative measure against anti-parasite drugs so long as it is in the bloodstream. Can cause brain damage." + cost = 150 + quantity = 8 + category = BORER_CAT_SELF + impure = FALSE + +/datum/borer_chem/yautja/enzyme + chem_name = "Cortical Enzyme" + chem_id = "benzyme" + desc = "An enzyme focused on consuming chemicals in the bloodstream. Helps fight addictions. This will work as a preventative measure against anti-parasite drugs so long as it is in the bloodstream. Can cause brain damage." + cost = 150 + quantity = 6 + category = BORER_CAT_SELF + impure = FALSE diff --git a/code/modules/borer/borer_html.dm b/code/modules/borer/borer_html.dm new file mode 100644 index 000000000000..f11884e8c435 --- /dev/null +++ b/code/modules/borer/borer_html.dm @@ -0,0 +1,69 @@ +/mob/living/carbon/cortical_borer/proc/get_html_template(content) + var/html = {" + + + Biochemical Synthesizer + + + + + + +

+ [content] +
"} + return html diff --git a/code/modules/borer/borer_procs.dm b/code/modules/borer/borer_procs.dm new file mode 100644 index 000000000000..9db7f1542c14 --- /dev/null +++ b/code/modules/borer/borer_procs.dm @@ -0,0 +1,720 @@ +//############# Physical Interaction Procs ############# +/mob/living/carbon/cortical_borer/UnarmedAttack(atom/A) + A.attack_borer(src) + +/atom/proc/attack_borer(mob/living/carbon/cortical_borer/user) + return + + +//Brainslug scans the reagents in a target's bloodstream. +/mob/living/carbon/human/attack_borer(mob/M) + borerscan(M, src) + +/proc/borerscan(mob/living/user, mob/living/M) + if(ishuman(M)) + var/mob/living/carbon/human/H = M + if(H.reagents) + if(H.reagents.reagent_list.len) + to_chat(user, SPAN_XENONOTICE("Subject contains the following reagents:")) + for(var/datum/reagent/R in H.reagents.reagent_list) + to_chat(user, "[R.overdose != 0 && R.volume >= R.overdose && !(R.flags & REAGENT_CANNOT_OVERDOSE) ? SPAN_WARNING("OD: ") : ""] [round(R.volume, 1)]u [R.name]") + else + to_chat(user, SPAN_XENONOTICE("Subject contains no reagents.")) + +//Brainslug scuttles under a door, same code as used by xeno larva. +/obj/structure/machinery/door/airlock/attack_borer(mob/living/carbon/cortical_borer/M) + M.scuttle(src) + +/mob/living/carbon/cortical_borer/proc/scuttle(obj/structure/S) + var/move_dir = get_dir(src, loc) + for(var/atom/movable/AM in get_turf(S)) + if(AM != S && AM.density && AM.BlockedPassDirs(src, move_dir)) + to_chat(src, SPAN_WARNING("\The [AM] prevents you from squeezing under \the [S]!")) + return + // Is it an airlock? + if(istype(S, /obj/structure/machinery/door/airlock)) + var/obj/structure/machinery/door/airlock/A = S + if(A.locked || A.welded) //Can't pass through airlocks that have been bolted down or welded + to_chat(src, SPAN_WARNING("\The [A] is locked down tight. You can't squeeze underneath!")) + return + visible_message(SPAN_WARNING("\The [src] scuttles underneath \the [S]!"), \ + SPAN_WARNING("You squeeze and scuttle underneath \the [S]."), null, 5) + forceMove(S.loc) + +//############# Action Give/Take Procs ############# +/mob/living/carbon/cortical_borer/proc/GrantBorerActions() + action_infest_host.give_to(src) + action_toggle_hide.give_to(src) + action_freeze_victim.give_to(src) + +/mob/living/carbon/cortical_borer/proc/RemoveBorerActions() + action_infest_host.remove_from(src) + action_toggle_hide.remove_from(src) + action_freeze_victim.remove_from(src) + +/mob/living/carbon/cortical_borer/proc/GrantInfestActions() + action_talk_to_host.give_to(src) + action_leave_body.give_to(src) + action_take_control.give_to(src) + action_make_chems.give_to(src) + action_scan_chems.give_to(src) + action_hibernate.give_to(src) + +/mob/living/carbon/cortical_borer/proc/RemoveInfestActions() + action_talk_to_host.remove_from(src) + action_take_control.remove_from(src) + action_leave_body.remove_from(src) + action_make_chems.remove_from(src) + action_scan_chems.remove_from(src) + action_hibernate.remove_from(src) + +/mob/living/carbon/cortical_borer/proc/GrantControlActions() + action_talk_to_brain.give_to(host) + action_give_back_control.give_to(host) + action_make_larvae.give_to(host) + action_torment.give_to(host) + +/mob/living/carbon/cortical_borer/proc/RemoveControlActions() + action_talk_to_brain.remove_from(host) + action_make_larvae.remove_from(host) + action_give_back_control.remove_from(host) + action_torment.remove_from(host) + +/mob/living/carbon/cortical_borer/proc/hibernate() + hibernating = !hibernating + if(hibernating) + to_chat(src, SPAN_XENONOTICE("You are now hibernating! Your body will dissolve impurities built up from the creation of chemicals, however your enzyme reserves will not replenish. You cannot act beyond communicating whilst in hibernation.")) + sleeping = 2 + else + sleeping = 0 + to_chat(src, SPAN_XENOWARNING("You are no longer hibernating. You have access to your full capabilities once more.")) + +//############# Control Related Procs ############# +//Check for brain worms in head. +/mob/proc/has_brain_worms() + return FALSE + +/mob/living/carbon/has_brain_worms() + if(borer) + return borer + else + return FALSE + + +//Brainslug infests a target +/mob/living/carbon/cortical_borer/verb/infest() + set category = "Borer" + set name = "Infest" + set desc = "Infest a suitable humanoid host." + + if(host) + to_chat(src, "You are already within a host.") + return + if(stat) + to_chat(src, "You cannot infest a target in your current state.") + return + var/list/choices = list() + for(var/mob/living/carbon/human/H in view(1,src)) + var/obj/limb/head/head = H.get_limb("head") + if(head.status & LIMB_ROBOT) + continue + if(isspeciesyautja(H) && !infect_hunter) + continue + if(H.stat != DEAD && Adjacent(H) && !H.has_brain_worms()) + choices += H + var/mob/living/carbon/human/target = tgui_input_list(src, "Who do you wish to infest?", "Targets", choices) + if(!target || !src) + return + if(!Adjacent(target)) + return + if(target.has_brain_worms()) + to_chat(src, SPAN_WARNING("You cannot infest someone who is already infested!")) + return + if(is_mob_incapacitated()) + return + to_chat(src, SPAN_NOTICE("You slither up [target] and begin probing at their ear canal...")) + if(!do_after(src, 50, INTERRUPT_ALL_OUT_OF_RANGE, BUSY_ICON_HOSTILE, target)) + to_chat(src, SPAN_WARNING("As [target] moves away, you are dislodged and fall to the ground.")) + return + if(!target || !src) + return + if(stat) + to_chat(src, SPAN_XENOWARNING("You cannot infest a target in your current state.")) + return + if(target.stat == DEAD) + to_chat(src, SPAN_WARNING("That is not an appropriate target.")) + return + if(target in view(1, src)) + to_chat(src, SPAN_NOTICE("You wiggle into [target]'s ear.")) + /* + if(!target.stat) + to_chat(target, "Something disgusting and slimy wiggles into your ear!") + */ // Let's see how stealthborers work out + perform_infestation(target) + return + else + to_chat(src, "They are no longer in range!") + return + +/mob/living/carbon/cortical_borer/proc/perform_infestation(mob/living/carbon/target) + if(!target) + return + if(target.has_brain_worms()) + to_chat(src, SPAN_XENOWARNING("[target] is already infested!")) + return + host = target + log_interact(src, host, "Borer: [key_name(src)] Infested [key_name(host)]") + target.borer = src + forceMove(target) + host.status_flags |= PASSEMOTES + host.verbs += /mob/living/proc/borer_comm + RemoveBorerActions() + GrantInfestActions() + + +//Brainslug abandons the host +/mob/living/carbon/cortical_borer/verb/release_host() + set category = "Borer" + set name = "Release Host" + set desc = "Slither out of your host." + if(!host) + to_chat(src, SPAN_XENOWARNING("You are not inside a host body.")) + return + if(stat) + to_chat(src, SPAN_XENOWARNING("You cannot leave your host in your current state.")) + return + if(docile) + to_chat(src, SPAN_XENOWARNING("You are feeling far too docile to do that.")) + return + if(!host || !src) + return + if(leaving) + leaving = FALSE + to_chat(src, SPAN_XENOWARNING("You decide against leaving your host.")) + return + to_chat(src, SPAN_XENOHIGHDANGER("You begin disconnecting from [host]'s synapses and prodding at their internal ear canal.")) + leaving = TRUE + addtimer(CALLBACK(src, PROC_REF(let_go)), 200) + +/mob/living/carbon/cortical_borer/proc/let_go() + if(!host || !src || QDELETED(host) || QDELETED(src)) + return + if(!leaving) + return + if(controlling) + return + if(stat) + to_chat(src, SPAN_XENOWARNING("You cannot release a target in your current state.")) + return + + to_chat(src, SPAN_XENOHIGHDANGER("You wiggle out of [host]'s ear and plop to the ground.")) + + leaving = FALSE + leave_host() + +/mob/living/carbon/cortical_borer/proc/leave_host() + if(!host) + return + if(controlling) + detach() + GrantBorerActions() + RemoveInfestActions() + forceMove(get_turf(host)) + + host.reset_view(null) + + var/mob/living/carbon/H = host + H.borer = null + H.verbs -= /mob/living/proc/borer_comm + action_talk_to_borer.remove_from(host) + H.status_flags &= ~PASSEMOTES + host = null + return + + +//Brainslug takes control of the body +/mob/living/carbon/cortical_borer/verb/bond_brain() + set category = "Borer" + set name = "Assume Control" + set desc = "Fully connect to the brain of your host." + + if(!host) + to_chat(src, SPAN_XENOWARNING("You are not inside a host body.")) + return + + if(host.stat == DEAD) + to_chat(src, SPAN_XENODANGER("This host is in no condition to be controlled.")) + return + + if(stat) + to_chat(src, SPAN_XENOWARNING("You cannot do that in your current state.")) + return + + if(docile) + to_chat(src, SPAN_XENOWARNING("You are feeling far too docile to do that.")) + return + + if(bonding) + bonding = FALSE + to_chat(src, SPAN_XENOWARNING("You stop attempting to take control of your host.")) + return + + to_chat(src, "You begin delicately adjusting your connection to the host brain...") + + if(QDELETED(src) || QDELETED(host)) + return + + bonding = TRUE + + var/delay = 300+(host.getBrainLoss()*5) + addtimer(CALLBACK(src, PROC_REF(assume_control)), delay) + +/mob/living/carbon/cortical_borer/proc/assume_control() + if(!host || !src || controlling) + return + if(!bonding) + return + if(docile) + to_chat(src, SPAN_XENOWARNING("You are feeling far too docile to do that.")) + return + else + to_chat(src, SPAN_XENOHIGHDANGER("You plunge your probosci deep into the cortex of the host brain, interfacing directly with their nervous system.")) + to_chat(host, SPAN_HIGHDANGER("You feel a strange shifting sensation behind your eyes as an alien consciousness displaces yours.")) + to_chat(host, SPAN_NOTICE("You can [SPAN_BOLD("resist")] this consciousness, but be warned you may suffer some degree of brain damage in the process!")) + var/borer_key = src.key + log_interact(src, host, "Borer: [key_name(src)] Assumed control of [key_name(host)]") + // host -> brain + var/h2b_id = host.computer_id + var/h2b_ip= host.lastKnownIP + host.computer_id = null + host.lastKnownIP = null + + qdel(host_brain) + host_brain = new(src) + + host_brain.ckey = host.ckey + + host_brain.name = host.name + + if(!host_brain.computer_id) + host_brain.computer_id = h2b_id + + if(!host_brain.lastKnownIP) + host_brain.lastKnownIP = h2b_ip + + // self -> host + var/s2h_id = src.computer_id + var/s2h_ip= src.lastKnownIP + src.computer_id = null + src.lastKnownIP = null + + host.ckey = src.ckey + + if(!host.computer_id) + host.computer_id = s2h_id + + if(!host.lastKnownIP) + host.lastKnownIP = s2h_ip + + bonding = FALSE + controlling = TRUE + + host.verbs += /mob/living/carbon/proc/release_control + host.verbs += /mob/living/carbon/proc/punish_host + host.verbs += /mob/living/carbon/proc/spawn_larvae + host.verbs -= /mob/living/proc/borer_comm + host.verbs += /mob/living/proc/trapped_mind_comm + + GrantControlActions() + action_talk_to_borer.remove_from(host) + host.med_hud_set_status() + + if(src && !src.key) + src.key = "@[borer_key]" + return + +//Captive mind reclaims their body. +/mob/living/captive_brain/proc/return_control(mob/living/carbon/cortical_borer/B) + if(!B || !B.controlling) + return + B.host.adjustBrainLoss(rand(5,10)) + to_chat(src, SPAN_DANGER("With an immense exertion of will, you regain control of your body!")) + to_chat(B.host, SPAN_XENODANGER("You feel control of the host brain ripped from your grasp, and retract your probosci before the wild neural impulses can damage you.")) + B.detach() + +///Brain slug proc for voluntary removal of control. +/mob/living/carbon/proc/release_control() + + set category = "Borer" + set name = "Release Control" + set desc = "Release control of your host's body." + + var/mob/living/carbon/cortical_borer/B = has_brain_worms() + + if(B && B.host_brain) + to_chat(src, SPAN_XENONOTICE("You withdraw your probosci, releasing control of [B.host_brain]")) + + B.detach() + + else + log_debug(EXCEPTION("Missing borer or missing host brain upon borer release."), src) + +/mob/living/carbon/cortical_borer/proc/detach() + if(!host || !controlling) + return + + controlling = FALSE + reset_view(null) + + host.verbs -= /mob/living/carbon/proc/release_control + host.verbs -= /mob/living/carbon/proc/punish_host + host.verbs -= /mob/living/carbon/proc/spawn_larvae + host.verbs += /mob/living/proc/borer_comm + host.verbs -= /mob/living/proc/trapped_mind_comm + + RemoveControlActions() + action_talk_to_borer.give_to(host) + host.med_hud_set_status() + sleeping = 0 + if(host_brain) + log_interact(host, src, "Borer: [key_name(host)] Took control back") + // host -> self + var/h2s_id = host.computer_id + var/h2s_ip= host.lastKnownIP + host.computer_id = null + host.lastKnownIP = null + src.ckey = host.ckey + if(!src.computer_id) + src.computer_id = h2s_id + if(!host_brain.lastKnownIP) + src.lastKnownIP = h2s_ip + + // brain -> host + var/b2h_id = host_brain.computer_id + var/b2h_ip = host_brain.lastKnownIP + host_brain.computer_id = null + host_brain.lastKnownIP = null + host.ckey = host_brain.ckey + + if(!host.computer_id) + host.computer_id = b2h_id + if(!host.lastKnownIP) + host.lastKnownIP = b2h_ip + qdel(host_brain) + return + +//Host Has died +/mob/living/carbon/cortical_borer/proc/host_death(perma = FALSE) + if(!(host && loc == host)) + log_debug("Borer ([key_name(src)]) called host_death without being inside a host!") + return + if(controlling) + detach() + to_chat(src, SPAN_HIGHDANGER("You release your proboscis and flee as the psychic shock of your host's death washes over you!")) + if(perma) + to_chat(src, SPAN_HIGHDANGER("You flee your host in anguish!")) + leave_host() + +//############# External Ability Procs ############# +/mob/living/carbon/cortical_borer/verb/hide_borer() + set category = "Borer" + set name = "Hide" + set desc = "Become invisible to the common eye." + + if(host) + to_chat(usr, SPAN_XENOWARNING("You cannot do this while you're inside a host.")) + return + + if(stat != CONSCIOUS) + return + + if(!hiding) + layer = TURF_LAYER+0.2 + to_chat(src, SPAN_XENONOTICE("You are now hiding.")) + hiding = TRUE + else + layer = MOB_LAYER + to_chat(src, SPAN_XENONOTICE("You stop hiding.")) + hiding = FALSE + +/mob/living/carbon/cortical_borer/verb/dominate_victim() + set category = "Borer" + set name = "Dominate Victim" + set desc = "Freeze the limbs of a potential host with supernatural fear." + + if(world.time - used_dominate < 150) + to_chat(src, SPAN_XENOWARNING("You cannot use that ability again so soon.")) + return + if(host) + to_chat(src, SPAN_XENOWARNING("You cannot do that from within a host body.")) + return + if(stat) + to_chat(src, SPAN_XENOWARNING("You cannot do that in your current state.")) + return + if(attempting_to_dominate) + to_chat(src, SPAN_XENOWARNING("You're already targeting someone!")) + return + var/list/choices = list() + for(var/mob/living/carbon/C in view(3,src)) + if((C.stat != DEAD) && !(issynth(C))) + choices += C + if(world.time - used_dominate < 300) + to_chat(src, SPAN_XENOWARNING("You cannot use that ability again so soon.")) + return + attempting_to_dominate = TRUE + var/mob/living/carbon/M = tgui_input_list(src, "Who do you wish to dominate?", "Targets", choices) + if(!M) + attempting_to_dominate = FALSE + return + if(!src) //different statement to avoid a runtime since if the source is deleted then attempting_to_dominate would also be deleted + return + if(M.has_brain_worms()) + to_chat(src, SPAN_XENOWARNING("You cannot dominate someone who is already infested!")) + attempting_to_dominate = FALSE + return + if(is_mob_incapacitated()) + attempting_to_dominate = FALSE + return + if(get_dist(src, M) > 5) //to avoid people remotely doing from across the map etc, 7 is the default view range + to_chat(src, SPAN_XENOWARNING("You're too far away!")) + attempting_to_dominate = FALSE + return + to_chat(src, SPAN_XENONOTICE("You begin to focus your psychic lance on [M], this will take a few seconds.")) + if(!do_after(src, 30, INTERRUPT_OUT_OF_RANGE, NO_BUSY_ICON, M, max_dist = 5)) + to_chat(src, SPAN_XENODANGER("You are out of position to dominate [M], get closer!")) + attempting_to_dominate = FALSE + return + + to_chat(src, SPAN_XENOWARNING("You focus your psychic lance on [M] and freeze their limbs with a wave of terrible dread.")) + to_chat(M, SPAN_WARNING("You feel a creeping, horrible sense of dread come over you, freezing your limbs and setting your heart racing.")) + M.KnockDown(3) + used_dominate = world.time + attempting_to_dominate = FALSE + + +//############# Internal Abiity Procs ############# +/mob/living/carbon/proc/punish_host() + set category = "Borer" + set name = "Torment Host" + set desc = "Punish your host with agony." + + var/mob/living/carbon/cortical_borer/B = has_brain_worms() + + if(!B) + return + + if(B.host_brain) + to_chat(src, SPAN_XENONOTICE("You send a punishing spike of psychic agony lancing into your host's brain.")) + to_chat(B.host_brain, SPAN_XENOHIGHDANGER("Horrific, burning agony lances through you, ripping a soundless scream from your trapped mind!")) + + +/mob/living/carbon/proc/spawn_larvae() + set category = "Borer" + set name = "Reproduce" + set desc = "Spawn a new borer." + + var/mob/living/carbon/cortical_borer/B = has_brain_worms() + + if(!B) + return + if(B.can_reproduce) + if(B.enzymes >= BORER_LARVAE_COST) + to_chat(src, SPAN_XENOWARNING("Your host twitches and quivers as you rapdly excrete a larva from your sluglike body.")) + visible_message(SPAN_WARNING("[src] heaves violently, expelling a rush of vomit and a wriggling, sluglike creature!")) + B.enzymes = 0 + var/turf/T = get_turf(src) + T.add_vomit_floor() + B.contaminant = 0 + var/repro = max(B.can_reproduce - 1, 0) + new /mob/living/carbon/cortical_borer(T, B.generation + 1, TRUE, repro) + else + to_chat(src, SPAN_XENONOTICE("You need at least [BORER_LARVAE_COST] enzymes to reproduce!")) + return + else + to_chat(src, SPAN_XENOWARNING("You are not allowed to reproduce!")) + + + +/mob/living/carbon/cortical_borer/verb/secrete_chemicals() + set category = "Borer" + set name = "Secrete Chemicals" + set desc = "Push some chemicals into your host's bloodstream." + + if(!host) + to_chat(src, SPAN_XENOWARNING("You are not inside a host body.")) + return + + if(stat) + to_chat(src, SPAN_XENOWARNING("You cannot secrete chemicals in your current state.")) + + if(docile) + to_chat(src, SPAN_XENOWARNING("You are feeling far too docile to do that.")) + return + + var/content = "" + + content += "" + + if(ishuman(host)) + if(isspeciesyautja(host)) + for(var/datum in subtypesof(/datum/borer_chem/yautja)) + var/datum/borer_chem/C = datum + var/chem = initial(C.chem_id) + var/datum/reagent/R = chemical_reagents_list[chem] + if(R) + content += "" + else + for(var/datum in subtypesof(/datum/borer_chem/human)) + var/datum/borer_chem/C = datum + var/chem = initial(C.chem_id) + var/datum/reagent/R = chemical_reagents_list[chem] + if(R) + content += "" + + content += "
[initial(C.quantity)] units of [R.name] ([initial(C.cost)] Enzymes)

[initial(C.desc)]

[initial(C.quantity)] units of [R.name] ([initial(C.cost)] Enzymes)

[initial(C.desc)]

" + + var/html = get_html_template(content) + + usr << browse(null, "window=ViewBorer\ref[src]Chems;size=585x400") + usr << browse(html, "window=ViewBorer\ref[src]Chems;size=585x400") + + return + + + +//############# Communication Procs ############# +/mob/living/carbon/cortical_borer/verb/Communicate() + set category = "Borer" + set name = "Converse with Host" + set desc = "Send a silent message to your host." + + if(!host) + to_chat(src, "You do not have a host to communicate with!") + return + + if(host.stat == DEAD) + to_chat(src, SPAN_XENODANGER("Not even you can commune with the dead.")) + return + + if(stat) + to_chat(src, "You cannot do that in your current state.") + return + + var/input = stripped_input(src, "Please enter a message to tell your host.", "Borer", "") + if(!input) + return + + if(src && !QDELETED(src) && !QDELETED(host)) + var/say_string = (docile) ? "slurs" :"states" + if(host) + to_chat(host, SPAN_XENO("[truename] [say_string]: [input]"), type = MESSAGE_TYPE_RADIO) + log_say("BORER: ([key_name(src)] to [key_name(host)]) [input]", src) + to_chat(src, SPAN_XENO("[truename] [say_string]: [input]"), type = MESSAGE_TYPE_RADIO) + action_talk_to_borer.give_to(host) + for (var/mob/dead in GLOB.dead_mob_list) + var/track_host = " (F)" + if(!istype(dead,/mob/new_player) && !istype(dead,/mob/living/brain)) //No meta-evesdropping + dead.show_message(SPAN_BORER("BORER: ([truename] to [host.real_name][track_host]) [say_string]: [input]"), SHOW_MESSAGE_VISIBLE) + +/mob/living/carbon/cortical_borer/verb/toggle_silence_inside_host() + set name = "Toggle speech inside Host" + set category = "Borer" + set desc = "Toggle whether you will be able to say audible messages while inside your host." + + if(talk_inside_host) + talk_inside_host = FALSE + to_chat(src, SPAN_XENONOTICE("You will no longer talk audibly while inside a host.")) + else + talk_inside_host = TRUE + to_chat(src, SPAN_XENONOTICE("You will now be able to audibly speak from inside of a host.")) + +/mob/living/proc/borer_comm() + set name = "Converse with Borer" + set category = "Borer" + set desc = "Communicate mentally with your borer." + + + var/mob/living/carbon/cortical_borer/B = has_brain_worms() + if(!B) + return + + if(stat == DEAD) + to_chat(src, SPAN_XENODANGER("You're dead, Jim.")) + return + + var/input = stripped_input(src, "Please enter a message to tell the borer.", "Message", "") + if(!input) + return + + to_chat(B, SPAN_XENO("[src] says: [input]"), type = MESSAGE_TYPE_RADIO) + log_say("BORER: ([key_name(src)] to [key_name(B)]) [input]", src) + to_chat(src, SPAN_XENO("[src] says: [input]"), type = MESSAGE_TYPE_RADIO) + for (var/mob/dead in GLOB.dead_mob_list) + var/track_host = " (F)" + if(!istype(dead,/mob/new_player) && !istype(dead,/mob/living/brain)) //No meta-evesdropping + dead.show_message(SPAN_BORER("BORER: ([name][track_host] to [B.truename]) says: [input]"), SHOW_MESSAGE_VISIBLE) + +/mob/living/proc/trapped_mind_comm() + set name = "Converse with Trapped Mind" + set category = "Borer" + set desc = "Communicate mentally with the trapped mind of your host." + + + var/mob/living/carbon/cortical_borer/B = has_brain_worms() + if(!B || !B.host_brain) + return + var/mob/living/captive_brain/CB = B.host_brain + var/input = stripped_input(src, "Please enter a message to tell the trapped mind.", "Message", "") + if(!input) + return + + to_chat(CB, SPAN_XENO("[B.truename] says: [input]"), type = MESSAGE_TYPE_RADIO) + log_say("BORER: ([key_name(src)] to [key_name(CB)]) [input]", B) + to_chat(src, SPAN_XENO("[B.truename] says: [input]"), type = MESSAGE_TYPE_RADIO) + for (var/mob/dead in GLOB.dead_mob_list) + var/track_borer = " (F)" + if(!istype(dead,/mob/new_player) && !istype(dead,/mob/living/brain)) //No meta-evesdropping + dead.show_message(SPAN_BORER("BORER: ([B.truename][track_borer] to [real_name] (trapped mind)) says: [input]"), SHOW_MESSAGE_VISIBLE) + + + + +//############# TOPIC ############# +/mob/living/carbon/cortical_borer/Topic(href, href_list, hsrc) + if(href_list["borer_use_chem"]) + locate(href_list["src"]) + if(!istype(src, /mob/living/carbon/cortical_borer)) + return + if(docile) + to_chat(src, SPAN_XENOWARNING("You are feeling far too docile to do that.")) + return + + var/topic_chem = href_list["borer_use_chem"] + var/datum/borer_chem/C = null + + for(var/datum in typesof(/datum/borer_chem)) + var/datum/borer_chem/test = datum + if(initial(test.chem_id) == topic_chem) + C = new test() + break + + if(!C || !host || controlling || !src || stat) + return + var/datum/reagent/R = chemical_reagents_list[C.chem_id] + if(enzymes < C.cost) + to_chat(src, SPAN_XENOWARNING("You need [C.cost] enzymes stored to secrete [R.name]!")) + return + var/contamination = round(C.cost / 10) + if(C.impure && ((contaminant + contamination) > max_contaminant)) + to_chat(src, SPAN_XENOWARNING("You are too contaminated to secrete [R.name]!")) + return + to_chat(src, SPAN_XENONOTICE("You squirt a measure of [R.name] from your reservoirs into [host]'s bloodstream.")) + contaminant += contamination + host.reagents.add_reagent(C.chem_id, C.quantity) + enzymes -= C.cost + log_interact(src, host, "[key_name(src)] has injected [C.quantity] units of [R.name] into their host, [key_name(host)]") + + // This is used because we use a static set of datums to determine what chems are available, + // instead of a table or something. Thus, when we instance it, we can safely delete it + qdel(C) + ..() diff --git a/code/modules/mob/holder.dm b/code/modules/mob/holder.dm index b2a68c997ec9..61b5c07ba159 100644 --- a/code/modules/mob/holder.dm +++ b/code/modules/mob/holder.dm @@ -115,3 +115,9 @@ /obj/item/holder/mouse/brown/Tom name = "Tom" desc = "Jerry the cat is not amused." + +/obj/item/holder/borer + name = "cortical borer" + desc = "Gross..." + icon = 'icons/mob/animal.dmi' + icon_state = "brainslug_dead" diff --git a/code/modules/mob/language/languages.dm b/code/modules/mob/language/languages.dm index 546c2bf7714f..5760255ff38d 100644 --- a/code/modules/mob/language/languages.dm +++ b/code/modules/mob/language/languages.dm @@ -202,3 +202,47 @@ color = "tajaran" key = "7" flags = RESTRICTED|HIVEMIND + +/datum/language/corticalborer + name = LANGUAGE_BORER + desc = "Cortical borers possess a strange link between their tiny minds." + speech_verb = "sings" + ask_verb = "sings" + exclaim_verb = "sings" + color = "alien" + key = "0" + flags = RESTRICTED|HIVEMIND + +/datum/language/corticalborer/broadcast(mob/living/speaker, message, speaker_mask, death) + var/mob/living/carbon/cortical_borer/B + if(!speaker_mask) + speaker_mask = speaker + if(iscarbon(speaker)) + var/mob/living/carbon/M = speaker + B = M.has_brain_worms() + else if(istype(speaker,/mob/living/carbon/cortical_borer)) + B = speaker + + if(B) + speaker_mask = B.truename + + var/message_start = "[name], [speaker_mask]" + var/message_body = "[speech_verb], \"[message]\"" + log_say("[key_name(speaker)] : ([name]) [message]") + + for(var/mob/player in GLOB.player_list) + var/understood = FALSE + var/ghost = FALSE + if(istype(player,/mob/dead)) + understood = TRUE + ghost = TRUE + else if((src in player.languages) && check_special_condition(player)) + understood = TRUE + if(understood) + if(!speaker_mask) + speaker_mask = speaker.name + if(death) + var/area/A = get_area(speaker) + to_chat(player, "[message_start] has [SPAN_BOLD("perished")][A? " at [sanitize_area(A.name)]":""]!") + else + to_chat(player, "[ghost? "(F) ":""][message_start] [message_body]") diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm index ecd1b6c97ca9..7993a7435e2b 100644 --- a/code/modules/mob/living/carbon/carbon_defines.dm +++ b/code/modules/mob/living/carbon/carbon_defines.dm @@ -23,6 +23,8 @@ var/datum/huntdata/hunter_data //Stores all information relating to Hunters for use with their HUD and other systems. + var/mob/living/carbon/cortical_borer/borer = null + /mob/living/carbon/vv_get_dropdown() . = ..() VV_DROPDOWN_OPTION("", "-----CARBON-----") diff --git a/code/modules/mob/living/carbon/human/death.dm b/code/modules/mob/living/carbon/human/death.dm index b3106c6daaab..bc155e47dbef 100644 --- a/code/modules/mob/living/carbon/human/death.dm +++ b/code/modules/mob/living/carbon/human/death.dm @@ -49,6 +49,9 @@ disable_lights() disable_special_items() disable_headsets() //Disable radios for dead people to reduce load + var/mob/living/carbon/cortical_borer/Player2 = has_brain_worms() + if(Player2) + Player2.host_death(undefibbable) if(pulledby && isxeno(pulledby)) // Xenos lose grab on dead humans pulledby.stop_pulling() //Handle species-specific deaths. diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 9f08f32dc2b6..accd036c16e6 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -136,6 +136,24 @@ if(eta_status) . += "Evacuation: [eta_status]" + var/mob/living/carbon/cortical_borer/B = borer + if(B && B.controlling) + + var/CR = "Yes" + if(!B.can_reproduce) + CR = "Forbidden" + else if((B.enzymes < BORER_LARVAE_COST)) + CR = "No" + + . += "" + . += "Borer:" + . += "Name: [B.truename]" + . += "Can Reproduce: [CR]" + . += "Enzymes: [round(B.enzymes)]/[round(B.max_enzymes)]" + . += "" + . += "Host Brain Damage: [brainloss]/100" + + /mob/living/carbon/human/ex_act(severity, direction, datum/cause_data/cause_data) if(lying) severity *= EXPLOSION_PRONE_MULTIPLIER diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm index d64e5d1bfde0..df9f0c259e4f 100644 --- a/code/modules/mob/living/carbon/human/life.dm +++ b/code/modules/mob/living/carbon/human/life.dm @@ -66,6 +66,9 @@ if(life_tick > 5 && timeofdeath && (timeofdeath < 5 || world.time - timeofdeath > revive_grace_period) && !issynth(src)) //We are dead beyond revival, or we're junk mobs spawned like the clowns on the clown shuttle undefibbable = TRUE SEND_SIGNAL(src, COMSIG_HUMAN_SET_UNDEFIBBABLE) + var/mob/living/carbon/cortical_borer/B = has_brain_worms() + if(B) + B.host_death(TRUE) med_hud_set_status() else if(stat != DEAD) diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm index e5934f4fa13b..bb9cf9309a2b 100644 --- a/code/modules/mob/living/say.dm +++ b/code/modules/mob/living/say.dm @@ -128,6 +128,10 @@ var/list/department_radio_keys = list( continue if(M.loc && (M.locs[1] in hearturfs)) listening |= M + for(var/mob/inner_mob in M.contents) + listening |= inner_mob + for(var/mob/living/captive_brain/brain in inner_mob) + listening |= brain var/speech_bubble_test = say_test(message) var/image/speech_bubble = image('icons/mob/effects/talk.dmi', src, "[bubble_type][speech_bubble_test]", FLY_LAYER) diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index 2bf44188eff4..9090ac3ebb4d 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -228,6 +228,7 @@ if(icon_gib) new /obj/effect/overlay/temp/gib_animation/animal(loc, src, icon_gib) + /mob/living/simple_animal/attack_animal(mob/living/M as mob) if(M.melee_damage_upper == 0) M.emote("[M.friendly] [src]") @@ -349,7 +350,7 @@ if (targeted_by && target_locked) overlays += target_locked -/mob/living/simple_animal/say(message) +/mob/living/simple_animal/say(message, datum/language/speaking = null) if(stat) return @@ -367,7 +368,7 @@ message = capitalize(trim_left(message)) - ..(message, null, verb, nolog = !ckey) //if the animal has a ckey then it will log the message + ..(message, speaking, verb, nolog = !ckey) //if the animal has a ckey then it will log the message /mob/living/simple_animal/update_canmove() . = ..() diff --git a/code/modules/reagents/chemistry_machinery/reagent_grinder.dm b/code/modules/reagents/chemistry_machinery/reagent_grinder.dm index 1de4a84451e1..2929d3aba0d2 100644 --- a/code/modules/reagents/chemistry_machinery/reagent_grinder.dm +++ b/code/modules/reagents/chemistry_machinery/reagent_grinder.dm @@ -54,6 +54,7 @@ /obj/item/reagent_container/food/snacks/watermelonslice = list("watermelonjuice" = 0), /obj/item/reagent_container/food/snacks/grown/grapes = list("grapejuice" = 0), /obj/item/reagent_container/food/snacks/grown/poisonberries = list("poisonberryjuice" = 0), + /obj/item/holder/borer = list("benzyme" = 0), ) @@ -411,6 +412,15 @@ if(!O.reagents.total_volume) remove_object(O) + //Borer, for enzymes + for(var/obj/item/holder/borer/b_holder in holdingitems) + if(beaker.reagents.total_volume >= beaker.reagents.maximum_volume) + break + var/space = beaker.reagents.maximum_volume - beaker.reagents.total_volume + beaker.reagents.add_reagent("benzyme",min(6, space)) + remove_object(b_holder) + break + /obj/structure/machinery/reagentgrinder/proc/cleanup() SIGNAL_HANDLER if(linked_storage) diff --git a/code/modules/reagents/chemistry_properties/prop_positive.dm b/code/modules/reagents/chemistry_properties/prop_positive.dm index 65099aa3e462..0d085b445bf0 100644 --- a/code/modules/reagents/chemistry_properties/prop_positive.dm +++ b/code/modules/reagents/chemistry_properties/prop_positive.dm @@ -499,9 +499,22 @@ H.vomit() else A.counter = 90 + if(H.chem_effect_flags & CHEM_EFFECT_ANTI_PARASITE) + return + + H.chem_effect_flags |= CHEM_EFFECT_ANTI_PARASITE + to_chat(H, SPAN_NOTICE("Your body feels warmer.")) /datum/chem_property/positive/antiparasitic/process_overdose(mob/living/M, potency = 1) M.apply_damage(potency, TOX) + var/mob/living/carbon/cortical_borer/player_2 = M.has_brain_worms() + if(player_2) + if(player_2.controlling) + player_2.detach() + to_chat(src, SPAN_HIGHDANGER("You relinquish the unknown chemical overwhelms you!")) + + player_2.leave_host() + to_chat(src, SPAN_HIGHDANGER("The overwhelming flow of powerful chemicals forces you to flee your host!")) /datum/chem_property/positive/antiparasitic/process_critical(mob/living/M, potency = 1) M.apply_damage(POTENCY_MULTIPLIER_VHIGH*potency, TOX) diff --git a/code/modules/reagents/chemistry_reactions/other.dm b/code/modules/reagents/chemistry_reactions/other.dm index 3956a4d9dc5c..bfa7a57688e6 100644 --- a/code/modules/reagents/chemistry_reactions/other.dm +++ b/code/modules/reagents/chemistry_reactions/other.dm @@ -462,3 +462,11 @@ result = "eggplasma" required_reagents = list("blood" = 10, "eggplasma" = 1) result_amount = 2 + +/datum/chemical_reaction/borer_cure + name = "Anti-Enzyme" + id = "bcure" + result = "bcure" + required_reagents = list("benzyme" = 2, "anti_toxin" = 4) + result_amount = 3 + mob_react = FALSE diff --git a/code/modules/reagents/chemistry_reagents/other.dm b/code/modules/reagents/chemistry_reagents/other.dm index aaf9901f46af..fdb84b7aa48b 100644 --- a/code/modules/reagents/chemistry_reagents/other.dm +++ b/code/modules/reagents/chemistry_reagents/other.dm @@ -1013,3 +1013,27 @@ chemclass = CHEM_CLASS_SPECIAL properties = list(PROPERTY_TRANSFORMATIVE = 4, PROPERTY_NUTRITIOUS = 3, PROPERTY_HEMOGENIC = 1) flags = REAGENT_SCANNABLE + +/datum/reagent/borer_enzyme + name = "Cortical Enzyme" + id = "benzyme" + description = "An enzyme secreted by a parasite that consumes certain chemicals from the bloodstream. Also seems to help fight addictions." + reagent_state = LIQUID + color = "#25c08c" + overdose = LOW_REAGENTS_OVERDOSE + overdose_critical = LOW_REAGENTS_OVERDOSE_CRITICAL + chemclass = CHEM_CLASS_SPECIAL + flags = REAGENT_SCANNABLE|REAGENT_NO_GENERATION + properties = list(PROPERTY_CROSSMETABOLIZING = 2, PROPERTY_ANTIADDICTIVE = 2) + +/datum/reagent/borer_cure + name = "Anti-Enzyme" + id = "bcure" + description = "An anti-parasite drug synthesised from parastic enzymes. Effectively fights toxins in the bloodstream." + reagent_state = LIQUID + color = "#25c08c" + overdose = LOW_REAGENTS_OVERDOSE + overdose_critical = LOW_REAGENTS_OVERDOSE_CRITICAL + chemclass = CHEM_CLASS_SPECIAL + flags = REAGENT_SCANNABLE|REAGENT_NO_GENERATION + properties = list(PROPERTY_CROSSMETABOLIZING = 2, PROPERTY_ANTITOXIN = 4) diff --git a/code/span_macros.dm b/code/span_macros.dm index 110da044e74a..9ea39092291a 100644 --- a/code/span_macros.dm +++ b/code/span_macros.dm @@ -82,6 +82,7 @@ #define SPAN_AVOIDHARM(X) "[X]" #define SPAN_SCANNER(X) "[X]" +#define SPAN_BORER(X) "[X]" #define SPAN_ROSE(X) "[X]" #define SPAN_LANGCHAT(X) "[X]" diff --git a/colonialmarines.dme b/colonialmarines.dme index f8c06ba3d841..d562a3dcbf1c 100644 --- a/colonialmarines.dme +++ b/colonialmarines.dme @@ -1378,6 +1378,11 @@ #include "code\modules\asset_cache\assets\tgui.dm" #include "code\modules\asset_cache\assets\vending.dm" #include "code\modules\asset_cache\transports\asset_transport.dm" +#include "code\modules\borer\_defines.dm" +#include "code\modules\borer\borer.dm" +#include "code\modules\borer\borer_chemicals.dm" +#include "code\modules\borer\borer_html.dm" +#include "code\modules\borer\borer_procs.dm" #include "code\modules\buildmode\bm-mode.dm" #include "code\modules\buildmode\buildmode.dm" #include "code\modules\buildmode\buttons.dm" diff --git a/icons/mob/brainslug.dmi b/icons/mob/brainslug.dmi new file mode 100644 index 0000000000000000000000000000000000000000..db29b87ac953f66b36ab8fd1596c80f537c02914 GIT binary patch literal 4277 zcmb7Ic{G&K-@gXgvXgxo*=4JQFoR?_7?Ku7qU>ZVvdmNxp|M1=g)Av+3@XM@)@)H^ zi-c;%8YA1x`}F?#p7)R6@0{;M2x3v&}Rv|^#* z#(D^vKSxGdKucbvqjRW$^A`xW(3%#BrVS@nx={NN{^Yw8)*RIg9pRN0fCrXZ%k5-xR zc|IXHddPpJuyWeKaME8gW@X>&p!QKo812OsDQ#k3gT2bpztIVb$Lz+TUIOG7KPN z50IGdOo>#A%!S@8qo}ggsoFkDI7S%16peZNia7E3v4 z!P^Xt3-vyk9XPRnSs{`x&mr_JefO4PhF|DYt{T^XIWG=w{8P+6LgF-J& zuaKMcPTdO*StuWF8jd|r@7X22UMCpkGJx9+m#>O62j%bW|DL`idqJwDJ1xQlEKO_^ zXrYs<7@5KNfR-CEv)5!=L%P&XfTroOWYhUh95+3vbEe7P!T-`;3C(B6#%c};`y8Ac z7ggmu{;NyFj;>;JU`H2QT8NEU?W(bmV*(juPfNE+r&Z56~^^JUacHB z&9=oqKbLkuoN4&9lZ901lwVaiusxqxOpYyy&=@1>(N*ngDjap?s$lu9X-pcSS zPDoF9_P5&e;r1h9xP6kERXl&&P^I>Of)B?-jy?Vxt*;l~`0k`EyvC=$j@VB3uDCH- z+DV{V`P&*Z+2#PDxBMzQj~vKZ=bRB^t&I&;1LIG2cgvQ{PXAe84X!0(Xj*+jH&>Gp zO1%033e*XB<5pnl7v?~`oImqYPRR1@OJa@w70hn-FI9Tb^}N8Z;2ULCG<-3taow9o zTEy&BxO>)gBW{4dHQ5!oA?lwGxRXwxXaHi&a*O{r#k%lt?zAPbx6H-)S+UdgdC1(tWrKlNPTs?cI~D+&TMrzF5G?T^vstA z&x_#63UX?UG?gEs5Pm@p2X3blVwcDl&n?qVZ%l;J=UStpr=zN(1Ih1iHF<24x3=Ez zD5TsBht5N-6VF`eUB`Wd&eB^A&8+A{0dU)$GcAjJesxCXM()pRe+$(Lu8to>Mjs^1 z=jpySz@W1eT1Ly$dg}z*M%w4qR)P&7hPvSN5~Na&vK!7mfbr-Q0j3m1fcww3eH+?1 z5VSDc`dmsEu}xjwxEM)$zI^?m=hj_3NgH!zpI0=)l*Pw}*?j~Ku-0ecC*92yXYU$2 z9VX9>=-x10rJc_W z4n2i4=TUS<7Dw#DEW&gz+2__}HPB|6&PJ;aK%^?xjGEU|m>_QurWM-lTH_oxpAP}U zws67HFsGrJo?JcL_@hrJ@K#?%GV}-H4@PV4e3jE&Ig-cMrzVN%n_^;)=_WG(CstQ(nFuikt0AFTaof_4 zVJjEBh{};&XFd*g?7mubRozQJ0V9vz*_>*P2SaP|QyS~y0T-{wL7{`3@`dAUCdatY zk9kY3kF&UPi$xo+yqa8{rYkefroKOZ(t)+IUQvlVt>4tNtS_Cqz9a}0q_NK`SL z#X_;xqWv_+?YQ8F%6m|}8e$?1A|_bfbVGDZ*y4si{Zb&^cp1XCJ7Mv1Wfc<;5K_4< zkrJQ+qIR<@Ri$K_nqK1=utKOX^V1ip8X0JIz#8X$Qar-8Jr+53HM=iHjk0#s@b}bA z$g^zwpQ9FZ^{gW|hBfl!e&xb~AOt%>NRFWY^%eRSxhM47K`K}L<;xSNs`jYdhs<4Q z%Eer!^QPdHusLSdk7MrTh?xio;&jDD%-__cE@0-=zhKdp_UQKf{ZU7rLJdxTeQbXh zBJzi^XF61;L%=JU!L&sZX#CnT7IowQ)JzGlr(CI%!;glLy}ovMeO3X0Jr0J-QaGwP z&@A||T8az**)l`E@|kMBnw5Q0 z!?|5II4W#ck^e9XF6`mJlNRdz@WN7G$yvPBLpyZQeS-5-&UB7;m#AJW3rbJ%z_L5T zQa@;dp%>U4LPT(=-tSoy6xEqS_8XP zU|d!<*uOb7G$3!7>MY(J4Q8Ju@(q=~GG>KhDFXMAh#mlRx#^<^W+~}h9xVlm!-YY; zEY>@C#bJ`8x6zOg(8^)$Eji12uMA#%SVSUrCa*2jr_Z4;%c1z**Cv=}EIn#&F4o`| z+^Q#3#&9t3;uDxR@4Fdk?Se1}37e5xqn6CG@9vh9((3uUh~!|-SZyA+#yh`QCv^>u za3RI{+Y)YR>i%n@(sn;%sB12k&ADtz*1s+_g%D|l@`i2Au> z!;vm}To396@GO~STsO9L{`54j@JK=28am8(_()m!Z+~DN3oR)p7#Y#;|mo2(9z8Bx_xu9Htru! zdq=wj!(~7NszXb*dqMhauvw%o@N^>&oD=X+PE_fU>1Q*^RQFy0G@azSuW7RAcrNgS zi-a^gpS0sM5fb+#=U9Hd43^-Wh+S5OUXjUw|8w7_rEA85 zFS}t!V!#O@gC{E1GN~{0aE|jmi(KR}g#qGlo{R z1;eI1HxMt(6oSIY{WjLCwKtzQhN;CtbW>W^oKSf3bd@9?Beg2}bAI=`j*FyM#i=%} zp)A444ygi zIhx@UtCs15^k?vTSc2V?6kuom@kz$hYlB5}h~G1SM239bz_e~ru8Y;U<|vC z(sD3JM%tzKFZJ#mE4yQF((-iy2KTvN&xfv7)MJ$`rR|I6d$VlFuWI}z*!>ffRb-?s z@=JXfH$9IKOeL`|KEbdqh20s-CNjK`K@ZOz>YI7~CCS*8`%Wt;HS5$gIG(~{s`Y(c zN1)_1@N487Y*2?|TyXd?M&dNvYLi&ZTYfNZ%6p;4Hgihzsi}rIsin5ojPgxPtYhrs zH9}}iX6SO6Iu@UkbDe=;<}xINbqxGLW$H#S*cTcXn!kJ#hWIC1=oVfm2R?cCCvnGe zH=r8~OUZEI$M>gvM>EUQN=r)?kCKA!g|%Kz^llcQO>zxw9-eCK;t3n)g>2+;UEwKl zBb)`tq7jT)Ue*0%GE@u2C0VE(ii!f2>{JAE4ff7A zO8O!z&l&!^#9_ZZ{3r6}u*`rb{bAAXj{liuV1XwHslti+#Ey z=HzyC&zMoMW0AAMnJJ+&(&CHx$k<)taZ%ffgAx<8TMTkviX?V8a!Vut1O0~p7H3f= JRmN_2{s+a-{Wt&s literal 0 HcmV?d00001 diff --git a/icons/mob/hud/actions_borer.dmi b/icons/mob/hud/actions_borer.dmi new file mode 100644 index 0000000000000000000000000000000000000000..9447b8f546211f96dd4fa71ebe28446e13d25f14 GIT binary patch literal 2032 zcmVC0001ZP)t-sz`(#! zIbbj%I$1zwVlP;HJZ~r*FgzVWNgqil7%U+WBLMZ=HmsP}mH^CV03>f>Hybe*3m7*b zJef1hNGng5Nr+1%QLIy%HX%DjAwFjc3Zw!8wo?G6tTx&80K?ZGmYXztS4((GOI27I zY;Pq0%w~mUX28I}vW^k-00001bW%=J06^y0W&i*Hy?RtwbVOxyV{&P5bZKvH004NL zm6E{$?nJ!Mj?u8nppm$MWWTyNuEDC!7XOkM#28)IRlM2glSZPQ$e4)q9o{$VN3 zo{6s3(FDk(P4E%Jz|}CCt1*CHfXT*yBlyUqo$r9Suyw8TeUlx-b0&^3VQ6Q=J(LE3 z>4?vF;jdAu%DVV`;TtkP#8j>IGD!dc23<)+K~#90?V5{vn>q}D!)!_*q)xh&lbR6d zdU^jxoMd6*D>TVz((fBun+pQzN0x1*Zn2nUmLDNWNPgW;zepGf=V_YupV@=2x8#4+ zp8&Gua>>69fNO~X@H}5G^MXeVtgZyW@RINtKn4-W@-*(CT(MOdbFf}N;@jindL7cA z{1ZU{AVvS902GPxk_hm$kH01e0TdYopnx$-#~ldY!6V*2{^|gD0DRdCGI#(g`O`uP zN8B&~!n5+S2rM^RfWQOblmqBL<0@bw2Wh@qmC+pNBhUxH(Zl_3Hd_=;U;@zOPZ1!` zvy@WCcTlcYkR-)ajsyX@_KyS}0H^S303vW5lw}Gj3UVpHR*MfnNrIEGzzX;m4}lb2kEN6o8@hlMn*1L;no35g_tF1U91pu>NkgF#O=Y*lrjvs{P&pz^yPc z&k-P105BNr8AIRzEcppQ`L_YoGr$WFAo+Il0;>XyhYR355tsnE1^`hxfCR=6^QGDf zhLsq;awEbEwena1NZ$(o;PXO2-5K#ls`u@uR zXkW$w1oa0*#6Y~hC;edxK$3kK2cZ6d|Hw5}-wzR}5A0CK0myzG1t_UM5C#~l@9iPz z5qy13_D2AyKOltr*gaL>yEz~u0AycER(k*{`Ag~#Kn`H(aYlV__Ydko1k|yb>;s;x zT>wEjp!x&&83@j(?@#@M!=Vo5fTSEy_VF_im;f~SOF`wi`Xb6R>U$mH!y(*1AgEfB zef34wCV;yCasVKLGwOQ*q3gSE=mFZjs5`617`?E>w6UeDWm;^T7uTFkMqD9f)e~&{Q)2laR9HrpXwjf z;NP+j1ojY!(&-OCOwK5;?f!w3wL1i|cI-n;tRxg(e?WK}{0U5Aw0R;O4!T}hm?x1^C1pw=>*&h&Ye_*`6 zPx^Jp+7K)NSbt6X(zd@)&j40=F0B5*RDG|L@DKq|&j6NvY1v<>mB;Q62(v#hRo|a> z!$S~2tvt4UY1$9=2ZWIWQ}z91{~%yrnmI7bEVIlq%eU134`09j`ue|Vjx8SR+NN!~ zv#-A%plzF@I@qzUJ9f7R=$fW&wW4dgiviYa{g>acfme`_K7rUD6P6GJ#UONj#J)a( zl;J-f8yNf#1g8F-VGO!1X^%rL zK78FioS^j|AHx}z0qy_ub0DzRf%lFjD$q?I0tcYC|M7tiJo}4e&D;~%+BERm-}&b! z5~xVLS`ldU^23@o0HE!I2Ntk@J-{1#(*UgwK@i}Bg#oS|0><9EcRK{lSwnDb62g(U z=3ON}X5;bvB)oDOGDV<6(D+F>Jq@p1h!8-JL81nsarILR5xZt7X4d#)Lbm5G#iYD! zGa~N{=RnI^^?u1_WXn0*@#!cm#!A}bO`iwo2tE{mYbD3_4geSHzgXYL^+EL+*UvJ` zEVIlqUB7=jz|>|j1KbQC^2l{7fapW#Z2(3dyKe$8R(RY3V6^nQ0YLBnRQ Date: Sun, 2 Apr 2023 05:16:11 +0100 Subject: [PATCH 12/25] Modernisation HUD and more modernisation holder icon follow and PP fix + Life() ack! Minor tweaks and updates ert name ladders x f scanner detection --- code/__DEFINES/mob_hud.dm | 2 + code/__DEFINES/typecheck/xenos.dm | 1 + code/__HELPERS/unsorted.dm | 2 + code/datums/mob_hud.dm | 34 ++-- .../game/machinery/medical_pod/bodyscanner.dm | 3 + .../admin/player_panel/player_panel.dm | 2 + code/modules/borer/_defines.dm | 5 + code/modules/borer/borer.dm | 146 +++++++++++++----- code/modules/borer/borer_procs.dm | 141 ++++++++--------- code/modules/mob/dead/observer/orbit.dm | 5 + code/modules/mob/holder.dm | 4 +- .../mob/living/carbon/human/human_defines.dm | 2 +- .../mob/living/carbon/xenomorph/Xenomorph.dm | 2 +- code/modules/mob/living/living_healthscan.dm | 2 +- .../chemistry_properties/prop_positive.dm | 2 +- code/modules/surgery/brainworm.dm | 80 ++++++++++ colonialmarines.dme | 1 + icons/mob/brainslug.dmi | Bin 4277 -> 4282 bytes icons/mob/hud/hud.dmi | Bin 19035 -> 19314 bytes tgui/packages/tgui/interfaces/HealthScan.js | 5 + tgui/packages/tgui/interfaces/Orbit/index.tsx | 2 + tgui/packages/tgui/interfaces/Orbit/types.ts | 1 + 22 files changed, 312 insertions(+), 130 deletions(-) create mode 100644 code/modules/surgery/brainworm.dm diff --git a/code/__DEFINES/mob_hud.dm b/code/__DEFINES/mob_hud.dm index e6e8212e0826..9298fa930db4 100644 --- a/code/__DEFINES/mob_hud.dm +++ b/code/__DEFINES/mob_hud.dm @@ -25,6 +25,7 @@ #define STATUS_HUD_XENO_CULTIST "24" // Whether they are a xeno cultist or not #define HUNTER_CLAN "25" //Displays a colored icon to represent ingame Hunter Clans #define HUNTER_HUD "26" //Displays various statuses on mobs for Hunters to identify targets +#define STATUS_HUD_BRAINWORM "27" //Displays infested HUD for brainworms. //data HUD (medhud, sechud) defines #define MOB_HUD_SECURITY_BASIC 1 @@ -44,6 +45,7 @@ #define MOB_HUD_FACTION_PMC 15 #define MOB_HUD_HUNTER 16 #define MOB_HUD_HUNTER_CLAN 17 +#define MOB_HUD_BRAINWORM 18 //for SL/FTL/LZ targeting on locator huds #define TRACKER_SL "track_sl" diff --git a/code/__DEFINES/typecheck/xenos.dm b/code/__DEFINES/typecheck/xenos.dm index 4d1b7819bdf1..6ae8fe1955a0 100644 --- a/code/__DEFINES/typecheck/xenos.dm +++ b/code/__DEFINES/typecheck/xenos.dm @@ -1,5 +1,6 @@ //Xenomorph Hud Test APOPHIS 22MAY2015 #define isxeno(A) (istype(A, /mob/living/carbon/xenomorph)) +#define isborer(A) (istype(A, /mob/living/carbon/cortical_borer)) #define isxeno_human(A) (isxeno(A) || ishuman(A)) //ask walter if i should turn into castechecks diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index 5d25df2150c0..25890101758b 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -624,6 +624,8 @@ moblist.Add(M) for(var/mob/living/carbon/xenomorph/M in sortmob) moblist.Add(M) + for(var/mob/living/carbon/cortical_borer/M in sortmob) + moblist.Add(M) for(var/mob/dead/observer/M in sortmob) moblist.Add(M) for(var/mob/new_player/M in sortmob) diff --git a/code/datums/mob_hud.dm b/code/datums/mob_hud.dm index fa2af17240f9..a6d4e2c09a09 100644 --- a/code/datums/mob_hud.dm +++ b/code/datums/mob_hud.dm @@ -18,7 +18,8 @@ var/list/datum/mob_hud/huds = list( MOB_HUD_FACTION_CLF = new /datum/mob_hud/faction/clf(), MOB_HUD_FACTION_PMC = new /datum/mob_hud/faction/pmc(), MOB_HUD_HUNTER = new /datum/mob_hud/hunter_hud(), - MOB_HUD_HUNTER_CLAN = new /datum/mob_hud/hunter_clan() + MOB_HUD_HUNTER_CLAN = new /datum/mob_hud/hunter_clan(), + MOB_HUD_BRAINWORM = new /datum/mob_hud/brainworm(), ) /datum/mob_hud @@ -129,7 +130,7 @@ var/list/datum/mob_hud/huds = list( //medical hud used by ghosts /datum/mob_hud/medical/observer - hud_icons = list(HEALTH_HUD, STATUS_HUD_OOC, STATUS_HUD_XENO_CULTIST) + hud_icons = list(HEALTH_HUD, STATUS_HUD_OOC, STATUS_HUD_XENO_CULTIST, STATUS_HUD_BRAINWORM) //infection status that appears on humans, viewed by xenos only and observers. @@ -151,6 +152,8 @@ var/list/datum/mob_hud/huds = list( /datum/mob_hud/hunter_hud hud_icons = list(HUNTER_HUD) +/datum/mob_hud/brainworm + hud_icons = list(HEALTH_HUD_XENO, PLASMA_HUD, PHEROMONE_HUD, QUEEN_OVERWATCH_HUD, ARMOR_HUD_XENO, XENO_STATUS_HUD, XENO_BANISHED_HUD, HEALTH_HUD, STATUS_HUD_OOC, STATUS_HUD_XENO_CULTIST, STATUS_HUD_BRAINWORM) //Security /datum/mob_hud/security @@ -207,7 +210,7 @@ var/list/datum/mob_hud/huds = list( /mob/living/carbon/xenomorph/add_to_all_mob_huds() for(var/datum/mob_hud/hud in huds) - if(!istype(hud, /datum/mob_hud/xeno)) + if(!istype(hud, /datum/mob_hud/xeno) && !istype(hud, /datum/mob_hud/brainworm)) continue hud.add_to_hud(src) @@ -230,7 +233,7 @@ var/list/datum/mob_hud/huds = list( if(istype(hud, /datum/mob_hud/xeno)) hud.remove_from_hud(src) hud.remove_hud_from(src) - else if (istype(hud, /datum/mob_hud/xeno_infection)) + else if (istype(hud, /datum/mob_hud/xeno_infection) || istype(hud, /datum/mob_hud/brainworm)) hud.remove_hud_from(src) @@ -263,6 +266,7 @@ var/list/datum/mob_hud/huds = list( /mob/living/carbon/xenomorph/med_hud_set_health() var/image/holder = hud_list[HEALTH_HUD_XENO] + var/image/holder2 = hud_list[STATUS_HUD_BRAINWORM] var/health_hud_type = "xenohealth" if(stat == DEAD) @@ -278,6 +282,13 @@ var/list/datum/mob_hud/huds = list( amount = -1 //don't want the 'zero health' icon when we are crit holder.icon_state = "[health_hud_type][amount]" + holder2.icon_state = null + if(has_brain_worms()) + holder2.icon_state = "hudbrainwormhost" + holder2.pixel_x = 9 + holder2.pixel_y = -8 + + /mob/living/carbon/xenomorph/proc/overlay_shields() var/image/holder = hud_list[HEALTH_HUD_XENO] holder.overlays.Cut() @@ -340,10 +351,12 @@ var/list/datum/mob_hud/huds = list( holder2.overlays.Cut() var/image/holder3 = hud_list[STATUS_HUD_XENO_INFECTION] var/image/holder4 = hud_list[STATUS_HUD_XENO_CULTIST] + var/image/holder5 = hud_list[STATUS_HUD_BRAINWORM] holder2.color = null holder3.color = null holder4.color = null + holder5.color = null holder4.icon_state = "hudblank" @@ -447,11 +460,14 @@ var/list/datum/mob_hud/huds = list( return var/mob/living/carbon/cortical_borer/B = has_brain_worms() - if(B && B.controlling) - holder.icon_state = "hudbrainworm" - if(!holder2_set) - holder2.icon_state = "hudbrainworm" - return + holder5.icon_state = null + if(B) + holder5.icon_state = "hudbrainwormhost" + if(B.controlling) + holder.icon_state = "hudbrainworm" + if(!holder2_set) + holder2.icon_state = "hudbrainworm" + return for(var/datum/disease/D in viruses) if(!D.hidden[SCANNER]) diff --git a/code/game/machinery/medical_pod/bodyscanner.dm b/code/game/machinery/medical_pod/bodyscanner.dm index 4756121e50ae..3cd2cf075d1b 100644 --- a/code/game/machinery/medical_pod/bodyscanner.dm +++ b/code/game/machinery/medical_pod/bodyscanner.dm @@ -390,6 +390,9 @@ if(occ["sdisabilities"] & NEARSIGHTED) dat += SET_CLASS("Retinal misalignment detected.", INTERFACE_RED) dat += "
" + if(connected.occupant.has_brain_worms()) + dat += SET_CLASS("Cranial anomoly detected.", INTERFACE_RED) + dat += "
" dat += "" return dat diff --git a/code/modules/admin/player_panel/player_panel.dm b/code/modules/admin/player_panel/player_panel.dm index 12686e683521..ada3d1100687 100644 --- a/code/modules/admin/player_panel/player_panel.dm +++ b/code/modules/admin/player_panel/player_panel.dm @@ -201,6 +201,8 @@ M_job = "Monkey" else if(isxeno(M)) M_job = "Alien" + else if(isborer(M)) + M_job = "Brainslug" else M_job = "Carbon-based" else if(isSilicon(M)) //silicon diff --git a/code/modules/borer/_defines.dm b/code/modules/borer/_defines.dm index 15929f6fa492..a1c7b4959e50 100644 --- a/code/modules/borer/_defines.dm +++ b/code/modules/borer/_defines.dm @@ -6,3 +6,8 @@ ///Amount of chemicals needed for a borer to reproduce, provided reproduction is toggled. #define BORER_LARVAE_COST 400 + +#define ACTION_SET_HOSTLESS "actions_hostless" +#define ACTION_SET_HUMANOID "actions_human" +#define ACTION_SET_XENO "actions_xeno" +#define ACTION_SET_CONTROL "actions_control" diff --git a/code/modules/borer/borer.dm b/code/modules/borer/borer.dm index 683956bf1fb4..81afde2f1be9 100644 --- a/code/modules/borer/borer.dm +++ b/code/modules/borer/borer.dm @@ -53,7 +53,7 @@ desc = "A small, quivering sluglike creature." speak_emote = list("chirrups") icon = 'icons/mob/brainslug.dmi' - icon_state = "brainslug" + icon_state = "Borer" speed = 0 a_intent = INTENT_HARM status_flags = CANPUSH @@ -70,6 +70,7 @@ holder_type = /obj/item/holder/borer var/generation = 1 + var/stealthy = FALSE var/static/list/borer_names = list( "Primary", "Secondary", "Tertiary", "Quaternary", "Quinary", "Senary", "Septenary", "Octonary", "Novenary", "Decenary", "Undenary", "Duodenary", @@ -93,25 +94,55 @@ var/leaving = FALSE var/hiding = FALSE var/can_reproduce = FALSE // Locked to manual override to prevent things getting out of hand. - var/infect_hunter = FALSE // Locked for normal use. + + var/infect_humans = TRUE // Locked for normal use. + var/infect_xenos = FALSE + var/infect_yautja = FALSE var/list/datum/reagent/synthesized_chems - /// All of these surely have a better way of being handled. - var/datum/action/innate/borer/talk_to_host/action_talk_to_host = new - var/datum/action/innate/borer/infest_host/action_infest_host = new - var/datum/action/innate/borer/toggle_hide/action_toggle_hide = new - var/datum/action/innate/borer/talk_to_borer/action_talk_to_borer = new - var/datum/action/innate/borer/talk_to_brain/action_talk_to_brain = new - var/datum/action/innate/borer/take_control/action_take_control = new - var/datum/action/innate/borer/give_back_control/action_give_back_control = new - var/datum/action/innate/borer/leave_body/action_leave_body = new - var/datum/action/innate/borer/make_chems/action_make_chems = new - var/datum/action/innate/borer/make_larvae/action_make_larvae = new - var/datum/action/innate/borer/freeze_victim/action_freeze_victim = new - var/datum/action/innate/borer/torment/action_torment = new - var/datum/action/innate/borer/scan_chems/action_scan_chems = new - var/datum/action/innate/borer/hibernate/action_hibernate = new + var/current_actions = ACTION_SET_HOSTLESS + var/list/actions_hostless = list( + /datum/action/innate/borer/toggle_hide, + /datum/action/innate/borer/freeze_victim, + /datum/action/innate/borer/infest_host + ) + var/list/actions_humanoidhost = list( + /datum/action/innate/borer/take_control, + /datum/action/innate/borer/talk_to_host, + /datum/action/innate/borer/leave_body, + /datum/action/innate/borer/hibernate, + /datum/action/innate/borer/scan_chems, + /datum/action/innate/borer/make_chems + ) + var/list/actions_xenohost = list( + /datum/action/innate/borer/take_control, + /datum/action/innate/borer/talk_to_host, + /datum/action/innate/borer/leave_body, + /datum/action/innate/borer/hibernate + ) + var/list/actions_control = list( + /datum/action/innate/borer/give_back_control, + /datum/action/innate/borer/make_larvae, + /datum/action/innate/borer/talk_to_brain, + /datum/action/innate/borer/torment + ) + +//################### INIT & LIFE ###################// +/mob/living/carbon/cortical_borer/New(atom/newloc, gen=1, ERT = FALSE, reproduction = 0) + ..(newloc) + SSmob.living_misc_mobs += src + generation = gen + add_language(LANGUAGE_BORER) + var/mob_number = rand(1000,9999) + real_name = "Cortical Borer [mob_number]" + truename = "[borer_names[min(generation, borer_names.len)]] [mob_number]" + can_reproduce = reproduction + give_new_actions(ACTION_SET_HOSTLESS) + //GrantBorerActions() + GiveBorerHUD() + if((!is_admin_level(z)) && ERT) + summon() /mob/living/carbon/cortical_borer/initialize_pass_flags(datum/pass_flags_container/PF) ..() @@ -119,9 +150,59 @@ PF.flags_pass = PASS_MOB_THRU|PASS_FLAGS_CRAWLER PF.flags_can_pass_all = PASS_ALL^PASS_OVER_THROW_ITEM +/mob/living/carbon/cortical_borer/initialize_pain() + pain = new /datum/pain/zombie(src) +/mob/living/carbon/cortical_borer/initialize_stamina() + stamina = new /datum/stamina/none(src) + +/mob/living/carbon/cortical_borer/updatehealth() + if(status_flags & GODMODE) + health = maxHealth + set_stat(CONSCIOUS) + else + health = maxHealth - getFireLoss() - getBruteLoss() - getToxLoss() //Borer can only take brute, fire and tox damage. + + if(stat != DEAD && !gibbing) + if(health <= -50) //dead + death(last_damage_data) + return + else if(health <= 0) //in crit + handle_crit() + +/mob/living/carbon/cortical_borer/proc/handle_crit() + if(stat == DEAD || gibbing) + return + + sound_environment_override = SOUND_ENVIRONMENT_NONE + set_stat(UNCONSCIOUS) + blinded = TRUE + if(layer != initial(layer)) //Unhide + layer = initial(layer) + recalculate_move_delay = TRUE + if(!lying) + update_canmove() + update_icons() + +/mob/living/carbon/cortical_borer/death() + var/datum/language/corticalborer/c_link = GLOB.all_languages[LANGUAGE_BORER] + c_link.broadcast(src, null, src.truename, TRUE) + SSmob.living_misc_mobs -= src + . = ..() + +/mob/living/carbon/cortical_borer/rejuvenate() + ..() + update_icons() + update_canmove() + SSmob.living_misc_mobs |= src + +/mob/living/carbon/cortical_borer/Destroy() + SSmob.living_misc_mobs -= src + return ..() +//###################################################// + /mob/living/carbon/cortical_borer/proc/summon() var/datum/emergency_call/custom/em_call = new() - em_call.name = "Cortical Borer" + em_call.name = real_name em_call.mob_max = 1 em_call.players_to_offer = list(src) em_call.owner = null @@ -132,24 +213,6 @@ message_admins("A new Cortical Borer has spawned at [get_area(loc)]") -/mob/living/carbon/cortical_borer/New(atom/newloc, gen=1, ERT = FALSE, reproduction = 0) - ..(newloc) - generation = gen - add_language(LANGUAGE_BORER) - var/mob_number = rand(1000,9999) - real_name = "Cortical Borer [mob_number]" - truename = "[borer_names[min(generation, borer_names.len)]] [mob_number]" - can_reproduce = reproduction - GrantBorerActions() - GiveBorerHUD() - if((!is_admin_level(z)) && ERT) - summon() - -/mob/living/carbon/cortical_borer/death() - var/datum/language/corticalborer/c_link = GLOB.all_languages[LANGUAGE_BORER] - c_link.broadcast(src, null, null, TRUE) - . = ..() - /mob/living/carbon/cortical_borer/update_icons() if(stat == DEAD) icon_state = "Borer Dead" @@ -163,7 +226,7 @@ icon_state = "Borer" /mob/living/carbon/cortical_borer/proc/GiveBorerHUD() - var/datum/mob_hud/H = huds[MOB_HUD_MEDICAL_OBSERVER] + var/datum/mob_hud/H = huds[MOB_HUD_BRAINWORM] H.add_hud_to(src) /mob/living/carbon/cortical_borer/can_ventcrawl() @@ -213,14 +276,16 @@ /mob/living/carbon/cortical_borer/Life(delta_time) ..() + update_canmove() + update_icons() if(host) if(!stat && host.stat != DEAD) if(((host.chem_effect_flags & CHEM_EFFECT_ANTI_PARASITE) && !host.reagents.has_reagent("benzyme")) || host.reagents.has_reagent("bcure")) if(!docile) if(controlling) - to_chat(host, SPAN_XENODANGER("You feel the soporific flow of a chemical in your host's blood, lulling you into docility.")) + to_chat(host, SPAN_XENODANGER("You feel the flow of a soporific chemical in your host's blood, lulling you into docility.")) else - to_chat(src, SPAN_XENODANGER("You feel the soporific flow of a chemical in your host's blood, lulling you into docility.")) + to_chat(src, SPAN_XENODANGER("You feel the flow of a soporific chemical in your host's blood, lulling you into docility.")) docile = TRUE else if(docile) @@ -248,9 +313,6 @@ contaminant = max(contaminant - 0.3, 0) else SetLuminosity(0) - - update_canmove() - update_icons() /datum/action/innate/borer icon_file = 'icons/mob/hud/actions_borer.dmi' diff --git a/code/modules/borer/borer_procs.dm b/code/modules/borer/borer_procs.dm index 9db7f1542c14..37e036a6b850 100644 --- a/code/modules/borer/borer_procs.dm +++ b/code/modules/borer/borer_procs.dm @@ -1,6 +1,9 @@ //############# Physical Interaction Procs ############# /mob/living/carbon/cortical_borer/UnarmedAttack(atom/A) - A.attack_borer(src) + if(istype(A, /obj/structure/ladder)) + A.attack_hand(src) + else + A.attack_borer(src) /atom/proc/attack_borer(mob/living/carbon/cortical_borer/user) return @@ -42,43 +45,47 @@ forceMove(S.loc) //############# Action Give/Take Procs ############# -/mob/living/carbon/cortical_borer/proc/GrantBorerActions() - action_infest_host.give_to(src) - action_toggle_hide.give_to(src) - action_freeze_victim.give_to(src) - -/mob/living/carbon/cortical_borer/proc/RemoveBorerActions() - action_infest_host.remove_from(src) - action_toggle_hide.remove_from(src) - action_freeze_victim.remove_from(src) - -/mob/living/carbon/cortical_borer/proc/GrantInfestActions() - action_talk_to_host.give_to(src) - action_leave_body.give_to(src) - action_take_control.give_to(src) - action_make_chems.give_to(src) - action_scan_chems.give_to(src) - action_hibernate.give_to(src) - -/mob/living/carbon/cortical_borer/proc/RemoveInfestActions() - action_talk_to_host.remove_from(src) - action_take_control.remove_from(src) - action_leave_body.remove_from(src) - action_make_chems.remove_from(src) - action_scan_chems.remove_from(src) - action_hibernate.remove_from(src) - -/mob/living/carbon/cortical_borer/proc/GrantControlActions() - action_talk_to_brain.give_to(host) - action_give_back_control.give_to(host) - action_make_larvae.give_to(host) - action_torment.give_to(host) - -/mob/living/carbon/cortical_borer/proc/RemoveControlActions() - action_talk_to_brain.remove_from(host) - action_make_larvae.remove_from(host) - action_give_back_control.remove_from(host) - action_torment.remove_from(host) +/mob/living/carbon/cortical_borer/proc/give_new_actions(actions_list = ACTION_SET_HOSTLESS, target = src) + for(var/datum/action/innate/borer/action in actions) + action.hide_from(target) + + if(host && current_actions == ACTION_SET_CONTROL) + for(var/datum/action/innate/borer/action in host.actions) + action.hide_from(host) + + var/list/abilities_to_give + switch(actions_list) + if(ACTION_SET_HOSTLESS) + abilities_to_give = actions_hostless.Copy() + if(host) + for(var/datum/action/innate/borer/action in host.actions) + action.hide_from(host) + if(ACTION_SET_HUMANOID) + abilities_to_give = actions_humanoidhost.Copy() + if(ACTION_SET_XENO) + abilities_to_give = actions_xenohost.Copy() + if(ACTION_SET_CONTROL) + if(!host) + return FALSE + abilities_to_give = actions_control.Copy() + target = host + + for(var/path in abilities_to_give) + give_action(target, path) + current_actions = actions_list + return TRUE + +/mob/living/carbon/cortical_borer/proc/get_host_actions() + if(!host) + return FALSE + if(ishuman(host)) + give_new_actions(ACTION_SET_HUMANOID) + else if(isxeno(host)) + give_new_actions(ACTION_SET_XENO) + else + return FALSE + give_action(host, /datum/action/innate/borer/talk_to_borer) + return TRUE /mob/living/carbon/cortical_borer/proc/hibernate() hibernating = !hibernating @@ -114,15 +121,21 @@ to_chat(src, "You cannot infest a target in your current state.") return var/list/choices = list() - for(var/mob/living/carbon/human/H in view(1,src)) - var/obj/limb/head/head = H.get_limb("head") - if(head.status & LIMB_ROBOT) + for(var/mob/living/carbon/candidate in view(1,src)) + var/obj/limb/head/head = candidate.get_limb("head") + if((isborer(candidate)) || (head?.status & (LIMB_DESTROYED|LIMB_ROBOT|LIMB_SYNTHSKIN)))//No infecting synths, or borers. continue - if(isspeciesyautja(H) && !infect_hunter) + if(ishuman(candidate)) + var/mob/living/carbon/human/h_candidate = candidate + if(isspecieshuman(h_candidate) && !infect_humans)//Can it infect humans? Normally, yes. + continue + else if(isspeciesyautja(h_candidate) && !infect_yautja)//Can it infect yautja? Normally, no. + continue + if(isxeno(candidate) && !infect_xenos)//Can it infect xenos? Normally, no. continue - if(H.stat != DEAD && Adjacent(H) && !H.has_brain_worms()) - choices += H - var/mob/living/carbon/human/target = tgui_input_list(src, "Who do you wish to infest?", "Targets", choices) + if(candidate.stat != DEAD && Adjacent(candidate) && !candidate.has_brain_worms()) + choices += candidate + var/mob/living/carbon/target = tgui_input_list(src, "Who do you wish to infest?", "Targets", choices) if(!target || !src) return if(!Adjacent(target)) @@ -146,10 +159,8 @@ return if(target in view(1, src)) to_chat(src, SPAN_NOTICE("You wiggle into [target]'s ear.")) - /* - if(!target.stat) - to_chat(target, "Something disgusting and slimy wiggles into your ear!") - */ // Let's see how stealthborers work out + if(!stealthy && !target.stat) + to_chat(target, SPAN_DANGER("Something disgusting and slimy wiggles into your ear!")) perform_infestation(target) return else @@ -168,8 +179,7 @@ forceMove(target) host.status_flags |= PASSEMOTES host.verbs += /mob/living/proc/borer_comm - RemoveBorerActions() - GrantInfestActions() + get_host_actions() //Brainslug abandons the host @@ -217,16 +227,16 @@ return if(controlling) detach() - GrantBorerActions() - RemoveInfestActions() + give_new_actions(ACTION_SET_HOSTLESS) + forceMove(get_turf(host)) + apply_effect(1, STUN) + log_interact(src, host, "Borer: [key_name(src)] left their host; [key_name(host)]") host.reset_view(null) var/mob/living/carbon/H = host H.borer = null - H.verbs -= /mob/living/proc/borer_comm - action_talk_to_borer.remove_from(host) H.status_flags &= ~PASSEMOTES host = null return @@ -319,14 +329,7 @@ bonding = FALSE controlling = TRUE - host.verbs += /mob/living/carbon/proc/release_control - host.verbs += /mob/living/carbon/proc/punish_host - host.verbs += /mob/living/carbon/proc/spawn_larvae - host.verbs -= /mob/living/proc/borer_comm - host.verbs += /mob/living/proc/trapped_mind_comm - - GrantControlActions() - action_talk_to_borer.remove_from(host) + give_new_actions(ACTION_SET_CONTROL) host.med_hud_set_status() if(src && !src.key) @@ -366,14 +369,7 @@ controlling = FALSE reset_view(null) - host.verbs -= /mob/living/carbon/proc/release_control - host.verbs -= /mob/living/carbon/proc/punish_host - host.verbs -= /mob/living/carbon/proc/spawn_larvae - host.verbs += /mob/living/proc/borer_comm - host.verbs -= /mob/living/proc/trapped_mind_comm - - RemoveControlActions() - action_talk_to_borer.give_to(host) + get_host_actions() host.med_hud_set_status() sleeping = 0 if(host_brain) @@ -456,7 +452,7 @@ return var/list/choices = list() for(var/mob/living/carbon/C in view(3,src)) - if((C.stat != DEAD) && !(issynth(C))) + if((C != src) && (C.stat != DEAD) && !(issynth(C))) choices += C if(world.time - used_dominate < 300) to_chat(src, SPAN_XENOWARNING("You cannot use that ability again so soon.")) @@ -610,7 +606,6 @@ to_chat(host, SPAN_XENO("[truename] [say_string]: [input]"), type = MESSAGE_TYPE_RADIO) log_say("BORER: ([key_name(src)] to [key_name(host)]) [input]", src) to_chat(src, SPAN_XENO("[truename] [say_string]: [input]"), type = MESSAGE_TYPE_RADIO) - action_talk_to_borer.give_to(host) for (var/mob/dead in GLOB.dead_mob_list) var/track_host = " (F)" if(!istype(dead,/mob/new_player) && !istype(dead,/mob/living/brain)) //No meta-evesdropping diff --git a/code/modules/mob/dead/observer/orbit.dm b/code/modules/mob/dead/observer/orbit.dm index 94d1203493da..39a7eb968368 100644 --- a/code/modules/mob/dead/observer/orbit.dm +++ b/code/modules/mob/dead/observer/orbit.dm @@ -56,6 +56,7 @@ /datum/orbit_menu/ui_static_data(mob/user) var/list/data = list() + var/list/borers = list() var/list/humans = list() var/list/marines = list() var/list/survivors = list() @@ -111,6 +112,9 @@ var/mob/living/player = M serialized["health"] = FLOOR((player.health / player.maxHealth * 100), 1) + if(isborer(player)) + borers += list(serialized) + if(isxeno(player)) var/mob/living/carbon/xenomorph/xeno = player if(xeno.caste) @@ -159,6 +163,7 @@ else if(isAI(M)) humans += list(serialized) + data["borers"] = borers data["humans"] = humans data["marines"] = marines data["survivors"] = survivors diff --git a/code/modules/mob/holder.dm b/code/modules/mob/holder.dm index 61b5c07ba159..1ad847564617 100644 --- a/code/modules/mob/holder.dm +++ b/code/modules/mob/holder.dm @@ -119,5 +119,5 @@ /obj/item/holder/borer name = "cortical borer" desc = "Gross..." - icon = 'icons/mob/animal.dmi' - icon_state = "brainslug_dead" + icon = 'icons/mob/brainslug.dmi' + icon_state = "Borer Dead" diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm index 16d456867f91..1d56f1cdff38 100644 --- a/code/modules/mob/living/carbon/human/human_defines.dm +++ b/code/modules/mob/living/carbon/human/human_defines.dm @@ -136,7 +136,7 @@ var/last_chew = 0 //taken from human.dm - hud_possible = list(HEALTH_HUD,STATUS_HUD, STATUS_HUD_OOC, STATUS_HUD_XENO_INFECTION, STATUS_HUD_XENO_CULTIST, ID_HUD, WANTED_HUD, ORDER_HUD, XENO_HOSTILE_ACID, XENO_HOSTILE_SLOW, XENO_HOSTILE_TAG, XENO_HOSTILE_FREEZE, HUNTER_CLAN, HUNTER_HUD, FACTION_HUD) + hud_possible = list(HEALTH_HUD,STATUS_HUD, STATUS_HUD_OOC, STATUS_HUD_XENO_INFECTION, STATUS_HUD_XENO_CULTIST, ID_HUD, WANTED_HUD, ORDER_HUD, XENO_HOSTILE_ACID, XENO_HOSTILE_SLOW, XENO_HOSTILE_TAG, XENO_HOSTILE_FREEZE, HUNTER_CLAN, HUNTER_HUD, FACTION_HUD, STATUS_HUD_BRAINWORM) var/embedded_flag //To check if we've need to roll for damage on movement while an item is imbedded in us. var/allow_gun_usage = TRUE var/melee_allowed = TRUE diff --git a/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm b/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm index 3449b4db8325..72a0ead4b198 100644 --- a/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm +++ b/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm @@ -44,7 +44,7 @@ see_in_dark = 12 recovery_constant = 1.5 see_invisible = SEE_INVISIBLE_LIVING - hud_possible = list(HEALTH_HUD_XENO, PLASMA_HUD, PHEROMONE_HUD, QUEEN_OVERWATCH_HUD, ARMOR_HUD_XENO, XENO_STATUS_HUD, XENO_BANISHED_HUD, XENO_HOSTILE_ACID, XENO_HOSTILE_SLOW, XENO_HOSTILE_TAG, XENO_HOSTILE_FREEZE, HUNTER_HUD) + hud_possible = list(HEALTH_HUD_XENO, PLASMA_HUD, PHEROMONE_HUD, QUEEN_OVERWATCH_HUD, ARMOR_HUD_XENO, XENO_STATUS_HUD, XENO_BANISHED_HUD, XENO_HOSTILE_ACID, XENO_HOSTILE_SLOW, XENO_HOSTILE_TAG, XENO_HOSTILE_FREEZE, HUNTER_HUD, STATUS_HUD_BRAINWORM) unacidable = TRUE rebounds = TRUE faction = FACTION_XENOMORPH diff --git a/code/modules/mob/living/living_healthscan.dm b/code/modules/mob/living/living_healthscan.dm index 067f39e1ab42..a8323fd98118 100644 --- a/code/modules/mob/living/living_healthscan.dm +++ b/code/modules/mob/living/living_healthscan.dm @@ -153,7 +153,7 @@ GLOBAL_LIST_INIT(known_implants, subtypesof(/obj/item/implant)) //snowflake :3 data["lung_ruptured"] = human_target_mob.is_lung_ruptured() - + data["brainslug"] = human_target_mob.has_brain_worms() //shrapnel, limbs, limb damage, limb statflags, cyber limbs var/core_fracture_detected = FALSE var/unknown_implants = 0 diff --git a/code/modules/reagents/chemistry_properties/prop_positive.dm b/code/modules/reagents/chemistry_properties/prop_positive.dm index 0d085b445bf0..17a98b1f9d85 100644 --- a/code/modules/reagents/chemistry_properties/prop_positive.dm +++ b/code/modules/reagents/chemistry_properties/prop_positive.dm @@ -511,7 +511,7 @@ if(player_2) if(player_2.controlling) player_2.detach() - to_chat(src, SPAN_HIGHDANGER("You relinquish the unknown chemical overwhelms you!")) + to_chat(src, SPAN_HIGHDANGER("You relinquish control as the unknown chemical overwhelms you!")) player_2.leave_host() to_chat(src, SPAN_HIGHDANGER("The overwhelming flow of powerful chemicals forces you to flee your host!")) diff --git a/code/modules/surgery/brainworm.dm b/code/modules/surgery/brainworm.dm new file mode 100644 index 000000000000..3a379605e36b --- /dev/null +++ b/code/modules/surgery/brainworm.dm @@ -0,0 +1,80 @@ +/datum/surgery/borer_removal + name = "Experimental Cranial Parasite Removal" + priority = SURGERY_PRIORITY_MAXIMUM + possible_locs = list("head") + invasiveness = list(SURGERY_DEPTH_DEEP) + pain_reduction_required = PAIN_REDUCTION_MEDIUM + required_surgery_skill = SKILL_SURGERY_TRAINED + steps = list( + /datum/surgery_step/remove_borer, + ) + +/datum/surgery/borer_removal/can_start(mob/user, mob/living/carbon/patient, obj/limb/L, obj/item/tool) + if(!locate(/obj/structure/machinery/optable) in get_turf(patient)) + return FALSE + + return patient.has_brain_worms() + +//------------------------------------ + +/datum/surgery_step/remove_borer + name = "Remove Cranial Parasite" + desc = "extract the cranial parasite" + accept_hand = TRUE + /*Similar to PINCH, but balanced around 100 = using bare hands. Haemostat is faster and better, + other tools are slower but don't burn the surgeon.*/ + tools = list( + /obj/item/tool/surgery/hemostat = 1.5, + /obj/item/tool/wirecutters = SURGERY_TOOL_MULT_SUBOPTIMAL, + /obj/item/tool/kitchen/utensil/fork = SURGERY_TOOL_MULT_SUBSTITUTE + ) + time = 6 SECONDS + preop_sound = 'sound/surgery/hemostat1.ogg' + success_sound = 'sound/surgery/organ2.ogg' + failure_sound = 'sound/effects/acid_sizzle2.ogg' + +/datum/surgery_step/remove_borer/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, tool_type, datum/surgery/surgery) + var/mob/living/carbon/cortical_borer/parasite = target.borer + if(parasite) + to_chat(parasite, SPAN_HIGHDANGER("[user] is attempting to extract you from your host's head!")) + else return FALSE + if(tool) + user.affected_message(target, + SPAN_NOTICE("You try to extract the parasite from [target]'s head with \the [tool]."), + SPAN_NOTICE("[user] tries to extract the parasite from your head with \the [tool]."), + SPAN_NOTICE("[user] tries to extract the parasite from [target]'s head with \the [tool].")) + else + user.affected_message(target, + SPAN_NOTICE("You try to extract the parasite from [target]'s head."), + SPAN_NOTICE("[user] tries to extract the parasite from your head."), + SPAN_NOTICE("[user] tries to extract the parasite from [target]'s head.")) + + target.custom_pain("Something hurts horribly in your head!",1) + log_interact(user, target, "[key_name(user)] started to remove a borer from [key_name(target)]'s skull.") + +/datum/surgery_step/remove_borer/success(mob/living/carbon/user, mob/living/carbon/target, target_zone, obj/item/tool, tool_type, datum/surgery/surgery) + var/mob/living/carbon/cortical_borer/parasite = target.borer + if(parasite) + user.affected_message(target, + SPAN_WARNING("You pull a wriggling parasite out of [target]'s head!"), + SPAN_WARNING("[user] pulls a wriggling parasite out of [target]'s head!"), + SPAN_WARNING("[user] pulls a wriggling parasite out of [target]'s head!")) + + user.count_niche_stat(STATISTICS_NICHE_SURGERY_LARVA) + to_chat(parasite, SPAN_HIGHDANGER("You are ripped forcibly from your host's head!")) + parasite.leave_host() + + log_interact(user, target, "[key_name(user)] removed a parasite from [key_name(target)]'s head with [tool ? "\the [tool]" : "their hands"], ending [surgery].") + +/datum/surgery_step/remove_borer/failure(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool, tool_type, datum/surgery/surgery) + user.affected_message(target, + SPAN_WARNING("Your hand slips, bruising [target]'s brain!"), + SPAN_WARNING("[user]'s hand slips, bruising your brain!"), + SPAN_WARNING("[user]'s hand slips, bruising [target]'s brain!")) + + target.apply_damage(10, BRAIN) + if(target.stat == CONSCIOUS) + target.emote("scream") + target.apply_damage(15, BURN, target_zone) + log_interact(user, target, "[key_name(user)] failed to remove a parasite from [key_name(target)]'s head with [tool ? "\the [tool]" : "their hands"].") + return FALSE diff --git a/colonialmarines.dme b/colonialmarines.dme index d562a3dcbf1c..211c667398c6 100644 --- a/colonialmarines.dme +++ b/colonialmarines.dme @@ -2149,6 +2149,7 @@ #include "code\modules\surgery\amputation.dm" #include "code\modules\surgery\bones.dm" #include "code\modules\surgery\brainrepair.dm" +#include "code\modules\surgery\brainworm.dm" #include "code\modules\surgery\chestburster.dm" #include "code\modules\surgery\eye.dm" #include "code\modules\surgery\face.dm" diff --git a/icons/mob/brainslug.dmi b/icons/mob/brainslug.dmi index db29b87ac953f66b36ab8fd1596c80f537c02914..42e83a7753f6aad2688b75046a5e5244e79c38c1 100644 GIT binary patch delta 4116 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

}`Qw_#44R+UX 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 i_5or|WBR?ni}r>kwCRd~rR?Ee8?NF~Yu diff --git a/tgui/packages/tgui/interfaces/HealthScan.js b/tgui/packages/tgui/interfaces/HealthScan.js index cf3e8e59eab9..84ddb1a53d05 100644 --- a/tgui/packages/tgui/interfaces/HealthScan.js +++ b/tgui/packages/tgui/interfaces/HealthScan.js @@ -31,6 +31,7 @@ export const HealthScan = (props, context) => { implants, core_fracture, lung_ruptured, + brainslug, hugged, detail_level, permadead, @@ -195,6 +196,7 @@ export const HealthScan = (props, context) => { {pulse} {internal_bleeding || + (brainslug && bodyscanner) || implants || hugged || core_fracture || @@ -222,6 +224,9 @@ export const HealthScan = (props, context) => { {lung_ruptured && bodyscanner ? ( Ruptured lung detected! ) : null} + {brainslug && bodyscanner ? ( + Cranial anomaly detected! + ) : null} {core_fracture && healthanalyser ? ( Bone fractures detected! Advanced scanner required for location. diff --git a/tgui/packages/tgui/interfaces/Orbit/index.tsx b/tgui/packages/tgui/interfaces/Orbit/index.tsx index a900ee804a28..9f6e9004b951 100644 --- a/tgui/packages/tgui/interfaces/Orbit/index.tsx +++ b/tgui/packages/tgui/interfaces/Orbit/index.tsx @@ -99,6 +99,7 @@ const ObservableSearch = (props, context) => { const ObservableContent = (props, context) => { const { data } = useBackend(context); const { + borers = [], humans = [], marines = [], survivors = [], @@ -117,6 +118,7 @@ const ObservableContent = (props, context) => { return ( + diff --git a/tgui/packages/tgui/interfaces/Orbit/types.ts b/tgui/packages/tgui/interfaces/Orbit/types.ts index 3fe11af8e625..d3b339ae04ed 100644 --- a/tgui/packages/tgui/interfaces/Orbit/types.ts +++ b/tgui/packages/tgui/interfaces/Orbit/types.ts @@ -2,6 +2,7 @@ import { BooleanLike } from 'common/react'; export type OrbitData = { auto_observe: BooleanLike; + borers: Observable[]; humans: Observable[]; marines: Observable[]; survivors: Observable[]; From fb3fb7f975f4c5e2e6f5a02daa6d7d95534deb7a Mon Sep 17 00:00:00 2001 From: forest2001 Date: Sun, 2 Apr 2023 19:28:58 +0100 Subject: [PATCH 13/25] fixes for hivemind --- code/modules/borer/borer.dm | 3 ++- code/modules/mob/language/languages.dm | 14 ++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/code/modules/borer/borer.dm b/code/modules/borer/borer.dm index 81afde2f1be9..047a80b3214a 100644 --- a/code/modules/borer/borer.dm +++ b/code/modules/borer/borer.dm @@ -39,13 +39,14 @@ var/mob/living/carbon/cortical_borer/B = loc if(!istype(B)) log_debug(EXCEPTION("Trapped mind found without a borer!"), src) - return + return FALSE to_chat(src, SPAN_DANGER("You begin doggedly resisting the parasite's control (this will take approximately sixty seconds).")) to_chat(B.host, SPAN_XENOWARNING("You feel the captive mind of [src] begin to resist your control.")) var/delay = (rand(350,450) + B.host.getBrainLoss()) addtimer(CALLBACK(src, PROC_REF(return_control), B), delay) + return TRUE /mob/living/carbon/cortical_borer name = "cortical borer" diff --git a/code/modules/mob/language/languages.dm b/code/modules/mob/language/languages.dm index 5760255ff38d..74f058e1ff66 100644 --- a/code/modules/mob/language/languages.dm +++ b/code/modules/mob/language/languages.dm @@ -213,18 +213,16 @@ key = "0" flags = RESTRICTED|HIVEMIND -/datum/language/corticalborer/broadcast(mob/living/speaker, message, speaker_mask, death) +/datum/language/corticalborer/broadcast(mob/living/carbon/speaker, message, speaker_mask, death) var/mob/living/carbon/cortical_borer/B - if(!speaker_mask) - speaker_mask = speaker - if(iscarbon(speaker)) - var/mob/living/carbon/M = speaker - B = M.has_brain_worms() - else if(istype(speaker,/mob/living/carbon/cortical_borer)) + if(isborer(speaker)) B = speaker - + else if(speaker.has_brain_worms()) + B = speaker.has_brain_worms() if(B) speaker_mask = B.truename + if(!speaker_mask) + speaker_mask = speaker.real_name var/message_start = "[name], [speaker_mask]" var/message_body = "[speech_verb], \"[message]\"" From df59bfcad50e9d76be2b88fa6130152d4dd935a2 Mon Sep 17 00:00:00 2001 From: forest2001 Date: Mon, 3 Apr 2023 06:36:12 +0100 Subject: [PATCH 14/25] Fixes, modernisation & help information --- code/modules/borer/borer.dm | 48 +++- code/modules/borer/borer_procs.dm | 261 +++++++++++------- code/modules/mob/language/languages.dm | 2 +- code/modules/mob/living/carbon/human/human.dm | 3 +- .../mob/living/carbon/xenomorph/XenoProcs.dm | 18 ++ icons/mob/hud/actions_borer.dmi | Bin 2032 -> 2089 bytes 6 files changed, 227 insertions(+), 105 deletions(-) diff --git a/code/modules/borer/borer.dm b/code/modules/borer/borer.dm index 047a80b3214a..54daabcf341b 100644 --- a/code/modules/borer/borer.dm +++ b/code/modules/borer/borer.dm @@ -86,7 +86,7 @@ var/max_contaminant = 120 //Decreases through hibernation or reproduction. var/hibernating = FALSE //Usable inside a host, but not when controlling. Allows clearing of impurities. - var/mob/living/carbon/human/host // Human host for the brain worm. + var/mob/living/carbon/host // Human host for the brain worm. var/truename // Name used for brainworm-speak. var/mob/living/captive_brain/host_brain // Used for swapping control of the body back and forth. var/controlling // Used in human death check. @@ -104,11 +104,13 @@ var/current_actions = ACTION_SET_HOSTLESS var/list/actions_hostless = list( + /datum/action/innate/borer/helpme, /datum/action/innate/borer/toggle_hide, /datum/action/innate/borer/freeze_victim, /datum/action/innate/borer/infest_host ) var/list/actions_humanoidhost = list( + /datum/action/innate/borer/helpme, /datum/action/innate/borer/take_control, /datum/action/innate/borer/talk_to_host, /datum/action/innate/borer/leave_body, @@ -117,12 +119,14 @@ /datum/action/innate/borer/make_chems ) var/list/actions_xenohost = list( + /datum/action/innate/borer/helpme, /datum/action/innate/borer/take_control, /datum/action/innate/borer/talk_to_host, /datum/action/innate/borer/leave_body, /datum/action/innate/borer/hibernate ) var/list/actions_control = list( + /datum/action/innate/borer/helpme, /datum/action/innate/borer/give_back_control, /datum/action/innate/borer/make_larvae, /datum/action/innate/borer/talk_to_brain, @@ -188,6 +192,7 @@ var/datum/language/corticalborer/c_link = GLOB.all_languages[LANGUAGE_BORER] c_link.broadcast(src, null, src.truename, TRUE) SSmob.living_misc_mobs -= src + leave_host() . = ..() /mob/living/carbon/cortical_borer/rejuvenate() @@ -198,6 +203,9 @@ /mob/living/carbon/cortical_borer/Destroy() SSmob.living_misc_mobs -= src + if(host) + for(var/datum/action/innate/borer/action in host.actions) + action.hide_from(host) return ..() //###################################################// @@ -254,9 +262,14 @@ . += "Can Reproduce: [CR]" . += "Enzymes: [round(enzymes)]/[round(max_enzymes)]" . += "Contaminant: [round(contaminant)]/[round(max_contaminant)]" + . += "Health: P:[round(getBruteLoss())] B:[round(getFireLoss())] T:[round(getToxLoss())]" if(host) . += "" - . += "Host Brain Damage: [host.brainloss]/100" + if(ishuman(host)) + . += "Host Brain Damage: [host.brainloss]/100" + else if(isxeno(host)) + // . += "Host Plasma: [plasma_stored]/[plasma_max]" + . += "Host Integrity: [health]/[maxHealth]" /mob/living/carbon/cortical_borer/say(message)//I need to parse the message properly so it doesn't look stupid var/datum/language/parsed_language = parse_language(message) @@ -279,9 +292,14 @@ ..() update_canmove() update_icons() + var/heal_amt = 1 if(host) + heal_amt = 3 if(!stat && host.stat != DEAD) - if(((host.chem_effect_flags & CHEM_EFFECT_ANTI_PARASITE) && !host.reagents.has_reagent("benzyme")) || host.reagents.has_reagent("bcure")) + var/mob/living/carbon/human/human_host + if(ishuman(host)) + human_host = host + if(((human_host.chem_effect_flags & CHEM_EFFECT_ANTI_PARASITE) && !human_host.reagents.has_reagent("benzyme")) || human_host.reagents.has_reagent("bcure")) if(!docile) if(controlling) to_chat(host, SPAN_XENODANGER("You feel the flow of a soporific chemical in your host's blood, lulling you into docility.")) @@ -291,7 +309,7 @@ else if(docile) if(controlling) - to_chat(host, SPAN_XENONOTICE("You shake off your lethargy as the chemical leaves your host's blood.")) + to_chat(human_host, SPAN_XENONOTICE("You shake off your lethargy as the chemical leaves your host's blood.")) else to_chat(src, SPAN_XENONOTICE("You shake off your lethargy as the chemical leaves your host's blood.")) docile = FALSE @@ -314,9 +332,31 @@ contaminant = max(contaminant - 0.3, 0) else SetLuminosity(0) + if(bruteloss || fireloss) + heal_overall_damage(heal_amt, heal_amt) + if(toxloss) + apply_damage(-(heal_amt/2), TOX) + +//################### ABILITIES ###################// /datum/action/innate/borer icon_file = 'icons/mob/hud/actions_borer.dmi' +/datum/action/innate/borer/helpme + name = "Help!" + action_icon_state = "borer_help" + +/datum/action/innate/borer/helpme/action_activate() + var/mob/living/carbon/cortical_borer/B + if(!isborer(owner)) + if(owner.has_brain_worms()) + B = owner.has_brain_worms() + else + to_chat(owner, SPAN_DANGER("How did you get this command? It's gone now.")) + hide_action(owner, /datum/action/innate/borer/helpme) + else + B = owner + B.show_help() + /datum/action/innate/borer/talk_to_host name = "Converse with Host" action_icon_state = "borer_whisper" diff --git a/code/modules/borer/borer_procs.dm b/code/modules/borer/borer_procs.dm index 37e036a6b850..224cdc2fac5f 100644 --- a/code/modules/borer/borer_procs.dm +++ b/code/modules/borer/borer_procs.dm @@ -1,3 +1,45 @@ +//############# HELP ################## +/mob/living/carbon/cortical_borer/verb/show_help() + set category = "Borer" + set name = "Show Help" + set desc = "Show help for borer commands." + + var/target = src + + var/list/options = list("Communicating") + if(!host) + options += list("Infecting a host") + else if(controlling) + target = host + options = list("Captive Host", "Releasing Control", "Reproducing") + else if(isxeno(host)) + options = list("Assuming Control","Contaminant & Enzymes","Hibernation","Reproducing") + else + options = list("Assuming Control","Contaminant & Enzymes","Hibernation","Secreting Chemicals","Reproducing") + + var/choice = tgui_input_list(target, "What would you like help with?", "Help", options, 20 SECONDS) + + var/help_message = "" + switch(choice) + if("Infecting a host") + help_message = "Infecting a host is the first major step for a borer to complete.\n\nThis is done by getting close to a potential host (This can be Human, Xeno or Yautja depending on settings controlled by admins) and clicking the Infest button in the top left of your screen.\n\nYour host will need to keep still for you to do this, and it's rare that they do so; for this reason you have Dominate Victim, which will allow you to temporarily stun a target.\n\nNote: This is not sufficient to keep them down for 100% of the time it takes to infect however, so be careful with it." + if("Communicating") + help_message = "All borers share a hivemind, the Cortical Link, this can be accessed using :0 (that's ZERO).\n\nThe hivemind can be used by any borer who is NOT in direct control of their host.\n\nAny borer in direct control of a host can only hear what their host can hear.\n\nA borer inside a host can communicate directly with that host using 'Converse with Host'." + if("Captive Host") + help_message = "" + if("Releasing Control") + help_message = "" + if("Reproducing") + help_message = "" + if("Assuming Control") + help_message = "" + if("Contaminant & Enzymes", "Hibernation") + help_message = "" + if("Secreting Chemicals") + help_message = "" + + alert(target, help_message, choice, "Ok") + //############# Physical Interaction Procs ############# /mob/living/carbon/cortical_borer/UnarmedAttack(atom/A) if(istype(A, /obj/structure/ladder)) @@ -33,16 +75,17 @@ for(var/atom/movable/AM in get_turf(S)) if(AM != S && AM.density && AM.BlockedPassDirs(src, move_dir)) to_chat(src, SPAN_WARNING("\The [AM] prevents you from squeezing under \the [S]!")) - return + return FALSE // Is it an airlock? if(istype(S, /obj/structure/machinery/door/airlock)) var/obj/structure/machinery/door/airlock/A = S if(A.locked || A.welded) //Can't pass through airlocks that have been bolted down or welded to_chat(src, SPAN_WARNING("\The [A] is locked down tight. You can't squeeze underneath!")) - return + return FALSE visible_message(SPAN_WARNING("\The [src] scuttles underneath \the [S]!"), \ SPAN_WARNING("You squeeze and scuttle underneath \the [S]."), null, 5) forceMove(S.loc) + return TRUE //############# Action Give/Take Procs ############# /mob/living/carbon/cortical_borer/proc/give_new_actions(actions_list = ACTION_SET_HOSTLESS, target = src) @@ -69,6 +112,8 @@ return FALSE abilities_to_give = actions_control.Copy() target = host + for(var/datum/action/innate/borer/talk_to_borer/action in host.actions) + action.hide_from(host) for(var/path in abilities_to_give) give_action(target, path) @@ -102,11 +147,27 @@ return FALSE /mob/living/carbon/has_brain_worms() - if(borer) + if(isborer(borer)) return borer else return FALSE +/mob/living/carbon/cortical_borer/proc/can_target(mob/living/carbon/target) + var/obj/limb/head/head = target.get_limb("head") + if((isborer(target)) || (head?.status & (LIMB_DESTROYED|LIMB_ROBOT|LIMB_SYNTHSKIN)))//No infecting synths, or borers. + return FALSE + if(ishuman(target)) + var/mob/living/carbon/human/human_target = target + if(isspecieshuman(human_target) && !infect_humans)//Can it infect humans? Normally, yes. + return FALSE + else if(isspeciesyautja(human_target) && !infect_yautja)//Can it infect yautja? Normally, no. + return FALSE + if(isxeno(target) && !infect_xenos)//Can it infect xenos? Normally, no. + return FALSE + if(target.stat != DEAD && Adjacent(target) && !target.has_brain_worms()) + return TRUE + else + return FALSE //Brainslug infests a target /mob/living/carbon/cortical_borer/verb/infest() @@ -116,63 +177,50 @@ if(host) to_chat(src, "You are already within a host.") - return + return FALSE if(stat) to_chat(src, "You cannot infest a target in your current state.") - return + return FALSE var/list/choices = list() for(var/mob/living/carbon/candidate in view(1,src)) - var/obj/limb/head/head = candidate.get_limb("head") - if((isborer(candidate)) || (head?.status & (LIMB_DESTROYED|LIMB_ROBOT|LIMB_SYNTHSKIN)))//No infecting synths, or borers. - continue - if(ishuman(candidate)) - var/mob/living/carbon/human/h_candidate = candidate - if(isspecieshuman(h_candidate) && !infect_humans)//Can it infect humans? Normally, yes. - continue - else if(isspeciesyautja(h_candidate) && !infect_yautja)//Can it infect yautja? Normally, no. - continue - if(isxeno(candidate) && !infect_xenos)//Can it infect xenos? Normally, no. - continue - if(candidate.stat != DEAD && Adjacent(candidate) && !candidate.has_brain_worms()) + if(can_target(candidate)) choices += candidate var/mob/living/carbon/target = tgui_input_list(src, "Who do you wish to infest?", "Targets", choices) - if(!target || !src) - return - if(!Adjacent(target)) - return + if(!target || !src || !Adjacent(target)) + return FALSE if(target.has_brain_worms()) to_chat(src, SPAN_WARNING("You cannot infest someone who is already infested!")) - return + return FALSE if(is_mob_incapacitated()) - return + return FALSE to_chat(src, SPAN_NOTICE("You slither up [target] and begin probing at their ear canal...")) if(!do_after(src, 50, INTERRUPT_ALL_OUT_OF_RANGE, BUSY_ICON_HOSTILE, target)) to_chat(src, SPAN_WARNING("As [target] moves away, you are dislodged and fall to the ground.")) - return + return FALSE if(!target || !src) - return + return FALSE if(stat) to_chat(src, SPAN_XENOWARNING("You cannot infest a target in your current state.")) - return + return FALSE if(target.stat == DEAD) - to_chat(src, SPAN_WARNING("That is not an appropriate target.")) - return + to_chat(src, SPAN_WARNING("You cannot infest the dead.")) + return FALSE if(target in view(1, src)) to_chat(src, SPAN_NOTICE("You wiggle into [target]'s ear.")) if(!stealthy && !target.stat) to_chat(target, SPAN_DANGER("Something disgusting and slimy wiggles into your ear!")) perform_infestation(target) - return + return TRUE else to_chat(src, "They are no longer in range!") - return + return FALSE /mob/living/carbon/cortical_borer/proc/perform_infestation(mob/living/carbon/target) if(!target) - return + return FALSE if(target.has_brain_worms()) to_chat(src, SPAN_XENOWARNING("[target] is already infested!")) - return + return FALSE host = target log_interact(src, host, "Borer: [key_name(src)] Infested [key_name(host)]") target.borer = src @@ -180,6 +228,7 @@ host.status_flags |= PASSEMOTES host.verbs += /mob/living/proc/borer_comm get_host_actions() + return TRUE //Brainslug abandons the host @@ -189,42 +238,42 @@ set desc = "Slither out of your host." if(!host) to_chat(src, SPAN_XENOWARNING("You are not inside a host body.")) - return + return FALSE if(stat) to_chat(src, SPAN_XENOWARNING("You cannot leave your host in your current state.")) - return + return FALSE if(docile) to_chat(src, SPAN_XENOWARNING("You are feeling far too docile to do that.")) - return + return FALSE if(!host || !src) - return + return FALSE if(leaving) leaving = FALSE to_chat(src, SPAN_XENOWARNING("You decide against leaving your host.")) - return + return TRUE to_chat(src, SPAN_XENOHIGHDANGER("You begin disconnecting from [host]'s synapses and prodding at their internal ear canal.")) leaving = TRUE addtimer(CALLBACK(src, PROC_REF(let_go)), 200) + return TRUE /mob/living/carbon/cortical_borer/proc/let_go() if(!host || !src || QDELETED(host) || QDELETED(src)) - return - if(!leaving) - return - if(controlling) - return + return FALSE + if(!leaving || controlling) + return FALSE if(stat) to_chat(src, SPAN_XENOWARNING("You cannot release a target in your current state.")) - return + return FALSE to_chat(src, SPAN_XENOHIGHDANGER("You wiggle out of [host]'s ear and plop to the ground.")) leaving = FALSE leave_host() + return TRUE /mob/living/carbon/cortical_borer/proc/leave_host() if(!host) - return + return FALSE if(controlling) detach() give_new_actions(ACTION_SET_HOSTLESS) @@ -239,7 +288,7 @@ H.borer = null H.status_flags &= ~PASSEMOTES host = null - return + return TRUE //Brainslug takes control of the body @@ -250,43 +299,44 @@ if(!host) to_chat(src, SPAN_XENOWARNING("You are not inside a host body.")) - return + return FALSE if(host.stat == DEAD) to_chat(src, SPAN_XENODANGER("This host is in no condition to be controlled.")) - return + return FALSE if(stat) to_chat(src, SPAN_XENOWARNING("You cannot do that in your current state.")) - return + return FALSE if(docile) to_chat(src, SPAN_XENOWARNING("You are feeling far too docile to do that.")) - return + return FALSE if(bonding) bonding = FALSE to_chat(src, SPAN_XENOWARNING("You stop attempting to take control of your host.")) - return + return FALSE to_chat(src, "You begin delicately adjusting your connection to the host brain...") if(QDELETED(src) || QDELETED(host)) - return + return FALSE bonding = TRUE var/delay = 300+(host.getBrainLoss()*5) addtimer(CALLBACK(src, PROC_REF(assume_control)), delay) + return TRUE /mob/living/carbon/cortical_borer/proc/assume_control() if(!host || !src || controlling) - return + return FALSE if(!bonding) - return + return FALSE if(docile) to_chat(src, SPAN_XENOWARNING("You are feeling far too docile to do that.")) - return + return FALSE else to_chat(src, SPAN_XENOHIGHDANGER("You plunge your probosci deep into the cortex of the host brain, interfacing directly with their nervous system.")) to_chat(host, SPAN_HIGHDANGER("You feel a strange shifting sensation behind your eyes as an alien consciousness displaces yours.")) @@ -334,16 +384,17 @@ if(src && !src.key) src.key = "@[borer_key]" - return + return TRUE //Captive mind reclaims their body. /mob/living/captive_brain/proc/return_control(mob/living/carbon/cortical_borer/B) if(!B || !B.controlling) - return + return FALSE B.host.adjustBrainLoss(rand(5,10)) to_chat(src, SPAN_DANGER("With an immense exertion of will, you regain control of your body!")) to_chat(B.host, SPAN_XENODANGER("You feel control of the host brain ripped from your grasp, and retract your probosci before the wild neural impulses can damage you.")) B.detach() + return TRUE ///Brain slug proc for voluntary removal of control. /mob/living/carbon/proc/release_control() @@ -364,7 +415,7 @@ /mob/living/carbon/cortical_borer/proc/detach() if(!host || !controlling) - return + return FALSE controlling = FALSE reset_view(null) @@ -396,20 +447,21 @@ host.computer_id = b2h_id if(!host.lastKnownIP) host.lastKnownIP = b2h_ip - qdel(host_brain) - return + qdel(host_brain) + return TRUE //Host Has died /mob/living/carbon/cortical_borer/proc/host_death(perma = FALSE) if(!(host && loc == host)) log_debug("Borer ([key_name(src)]) called host_death without being inside a host!") - return + return FALSE if(controlling) detach() to_chat(src, SPAN_HIGHDANGER("You release your proboscis and flee as the psychic shock of your host's death washes over you!")) if(perma) to_chat(src, SPAN_HIGHDANGER("You flee your host in anguish!")) leave_host() + return TRUE //############# External Ability Procs ############# /mob/living/carbon/cortical_borer/verb/hide_borer() @@ -419,10 +471,10 @@ if(host) to_chat(usr, SPAN_XENOWARNING("You cannot do this while you're inside a host.")) - return + return FALSE if(stat != CONSCIOUS) - return + return FALSE if(!hiding) layer = TURF_LAYER+0.2 @@ -432,6 +484,7 @@ layer = MOB_LAYER to_chat(src, SPAN_XENONOTICE("You stop hiding.")) hiding = FALSE + return TRUE /mob/living/carbon/cortical_borer/verb/dominate_victim() set category = "Borer" @@ -440,53 +493,53 @@ if(world.time - used_dominate < 150) to_chat(src, SPAN_XENOWARNING("You cannot use that ability again so soon.")) - return + return FALSE if(host) to_chat(src, SPAN_XENOWARNING("You cannot do that from within a host body.")) - return + return FALSE if(stat) to_chat(src, SPAN_XENOWARNING("You cannot do that in your current state.")) - return + return FALSE if(attempting_to_dominate) to_chat(src, SPAN_XENOWARNING("You're already targeting someone!")) - return + return FALSE var/list/choices = list() for(var/mob/living/carbon/C in view(3,src)) - if((C != src) && (C.stat != DEAD) && !(issynth(C))) + if(can_target(C)) choices += C if(world.time - used_dominate < 300) to_chat(src, SPAN_XENOWARNING("You cannot use that ability again so soon.")) - return + return FALSE attempting_to_dominate = TRUE var/mob/living/carbon/M = tgui_input_list(src, "Who do you wish to dominate?", "Targets", choices) if(!M) attempting_to_dominate = FALSE - return + return FALSE if(!src) //different statement to avoid a runtime since if the source is deleted then attempting_to_dominate would also be deleted - return + return FALSE if(M.has_brain_worms()) to_chat(src, SPAN_XENOWARNING("You cannot dominate someone who is already infested!")) attempting_to_dominate = FALSE - return + return FALSE if(is_mob_incapacitated()) attempting_to_dominate = FALSE - return + return FALSE if(get_dist(src, M) > 5) //to avoid people remotely doing from across the map etc, 7 is the default view range to_chat(src, SPAN_XENOWARNING("You're too far away!")) attempting_to_dominate = FALSE - return + return FALSE to_chat(src, SPAN_XENONOTICE("You begin to focus your psychic lance on [M], this will take a few seconds.")) if(!do_after(src, 30, INTERRUPT_OUT_OF_RANGE, NO_BUSY_ICON, M, max_dist = 5)) to_chat(src, SPAN_XENODANGER("You are out of position to dominate [M], get closer!")) attempting_to_dominate = FALSE - return + return FALSE to_chat(src, SPAN_XENOWARNING("You focus your psychic lance on [M] and freeze their limbs with a wave of terrible dread.")) to_chat(M, SPAN_WARNING("You feel a creeping, horrible sense of dread come over you, freezing your limbs and setting your heart racing.")) M.KnockDown(3) used_dominate = world.time attempting_to_dominate = FALSE - + return TRUE //############# Internal Abiity Procs ############# /mob/living/carbon/proc/punish_host() @@ -497,11 +550,12 @@ var/mob/living/carbon/cortical_borer/B = has_brain_worms() if(!B) - return + return FALSE if(B.host_brain) to_chat(src, SPAN_XENONOTICE("You send a punishing spike of psychic agony lancing into your host's brain.")) to_chat(B.host_brain, SPAN_XENOHIGHDANGER("Horrific, burning agony lances through you, ripping a soundless scream from your trapped mind!")) + return TRUE /mob/living/carbon/proc/spawn_larvae() @@ -512,7 +566,7 @@ var/mob/living/carbon/cortical_borer/B = has_brain_worms() if(!B) - return + return FALSE if(B.can_reproduce) if(B.enzymes >= BORER_LARVAE_COST) to_chat(src, SPAN_XENOWARNING("Your host twitches and quivers as you rapdly excrete a larva from your sluglike body.")) @@ -523,11 +577,13 @@ B.contaminant = 0 var/repro = max(B.can_reproduce - 1, 0) new /mob/living/carbon/cortical_borer(T, B.generation + 1, TRUE, repro) + return TRUE else to_chat(src, SPAN_XENONOTICE("You need at least [BORER_LARVAE_COST] enzymes to reproduce!")) - return + return FALSE else to_chat(src, SPAN_XENOWARNING("You are not allowed to reproduce!")) + return FALSE @@ -538,21 +594,25 @@ if(!host) to_chat(src, SPAN_XENOWARNING("You are not inside a host body.")) - return + return FALSE if(stat) to_chat(src, SPAN_XENOWARNING("You cannot secrete chemicals in your current state.")) + return FALSE if(docile) to_chat(src, SPAN_XENOWARNING("You are feeling far too docile to do that.")) - return + return FALSE var/content = "" content += "" if(ishuman(host)) - if(isspeciesyautja(host)) + var/mob/living/carbon/human/human_host + if(ishuman(host)) + human_host = host + if(isspeciesyautja(human_host)) for(var/datum in subtypesof(/datum/borer_chem/yautja)) var/datum/borer_chem/C = datum var/chem = initial(C.chem_id) @@ -574,7 +634,7 @@ usr << browse(null, "window=ViewBorer\ref[src]Chems;size=585x400") usr << browse(html, "window=ViewBorer\ref[src]Chems;size=585x400") - return + return TRUE @@ -586,19 +646,19 @@ if(!host) to_chat(src, "You do not have a host to communicate with!") - return + return FALSE if(host.stat == DEAD) to_chat(src, SPAN_XENODANGER("Not even you can commune with the dead.")) - return + return FALSE - if(stat) - to_chat(src, "You cannot do that in your current state.") - return + if(stat == DEAD) + to_chat(src, "You're dead, Jim.") + return FALSE var/input = stripped_input(src, "Please enter a message to tell your host.", "Borer", "") if(!input) - return + return FALSE if(src && !QDELETED(src) && !QDELETED(host)) var/say_string = (docile) ? "slurs" :"states" @@ -610,6 +670,7 @@ var/track_host = " (F)" if(!istype(dead,/mob/new_player) && !istype(dead,/mob/living/brain)) //No meta-evesdropping dead.show_message(SPAN_BORER("BORER: ([truename] to [host.real_name][track_host]) [say_string]: [input]"), SHOW_MESSAGE_VISIBLE) + return TRUE /mob/living/carbon/cortical_borer/verb/toggle_silence_inside_host() set name = "Toggle speech inside Host" @@ -631,15 +692,15 @@ var/mob/living/carbon/cortical_borer/B = has_brain_worms() if(!B) - return + return FALSE if(stat == DEAD) to_chat(src, SPAN_XENODANGER("You're dead, Jim.")) - return + return FALSE var/input = stripped_input(src, "Please enter a message to tell the borer.", "Message", "") if(!input) - return + return FALSE to_chat(B, SPAN_XENO("[src] says: [input]"), type = MESSAGE_TYPE_RADIO) log_say("BORER: ([key_name(src)] to [key_name(B)]) [input]", src) @@ -648,6 +709,7 @@ var/track_host = " (F)" if(!istype(dead,/mob/new_player) && !istype(dead,/mob/living/brain)) //No meta-evesdropping dead.show_message(SPAN_BORER("BORER: ([name][track_host] to [B.truename]) says: [input]"), SHOW_MESSAGE_VISIBLE) + return TRUE /mob/living/proc/trapped_mind_comm() set name = "Converse with Trapped Mind" @@ -657,11 +719,11 @@ var/mob/living/carbon/cortical_borer/B = has_brain_worms() if(!B || !B.host_brain) - return + return FALSE var/mob/living/captive_brain/CB = B.host_brain var/input = stripped_input(src, "Please enter a message to tell the trapped mind.", "Message", "") if(!input) - return + return FALSE to_chat(CB, SPAN_XENO("[B.truename] says: [input]"), type = MESSAGE_TYPE_RADIO) log_say("BORER: ([key_name(src)] to [key_name(CB)]) [input]", B) @@ -670,7 +732,7 @@ var/track_borer = " (F)" if(!istype(dead,/mob/new_player) && !istype(dead,/mob/living/brain)) //No meta-evesdropping dead.show_message(SPAN_BORER("BORER: ([B.truename][track_borer] to [real_name] (trapped mind)) says: [input]"), SHOW_MESSAGE_VISIBLE) - + return TRUE @@ -679,10 +741,10 @@ if(href_list["borer_use_chem"]) locate(href_list["src"]) if(!istype(src, /mob/living/carbon/cortical_borer)) - return + return FALSE if(docile) to_chat(src, SPAN_XENOWARNING("You are feeling far too docile to do that.")) - return + return FALSE var/topic_chem = href_list["borer_use_chem"] var/datum/borer_chem/C = null @@ -694,15 +756,15 @@ break if(!C || !host || controlling || !src || stat) - return + return FALSE var/datum/reagent/R = chemical_reagents_list[C.chem_id] if(enzymes < C.cost) to_chat(src, SPAN_XENOWARNING("You need [C.cost] enzymes stored to secrete [R.name]!")) - return + return FALSE var/contamination = round(C.cost / 10) if(C.impure && ((contaminant + contamination) > max_contaminant)) to_chat(src, SPAN_XENOWARNING("You are too contaminated to secrete [R.name]!")) - return + return FALSE to_chat(src, SPAN_XENONOTICE("You squirt a measure of [R.name] from your reservoirs into [host]'s bloodstream.")) contaminant += contamination host.reagents.add_reagent(C.chem_id, C.quantity) @@ -712,4 +774,5 @@ // This is used because we use a static set of datums to determine what chems are available, // instead of a table or something. Thus, when we instance it, we can safely delete it qdel(C) + return TRUE ..() diff --git a/code/modules/mob/language/languages.dm b/code/modules/mob/language/languages.dm index 74f058e1ff66..905b6a07670f 100644 --- a/code/modules/mob/language/languages.dm +++ b/code/modules/mob/language/languages.dm @@ -243,4 +243,4 @@ var/area/A = get_area(speaker) to_chat(player, "[message_start] has [SPAN_BOLD("perished")][A? " at [sanitize_area(A.name)]":""]!") else - to_chat(player, "[ghost? "(F) ":""][message_start] [message_body]") + to_chat(player, "[ghost? "(F) ":""][message_start][message_body]") diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index accd036c16e6..d0eac93c78e9 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -136,7 +136,7 @@ if(eta_status) . += "Evacuation: [eta_status]" - var/mob/living/carbon/cortical_borer/B = borer + var/mob/living/carbon/cortical_borer/B = has_brain_worms() if(B && B.controlling) var/CR = "Yes" @@ -150,6 +150,7 @@ . += "Name: [B.truename]" . += "Can Reproduce: [CR]" . += "Enzymes: [round(B.enzymes)]/[round(B.max_enzymes)]" + . += "Health: P:[round(getBruteLoss())] B:[round(getFireLoss())] T:[round(getToxLoss())]" . += "" . += "Host Brain Damage: [brainloss]/100" diff --git a/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm b/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm index d2c099d0c1ff..6a592494756e 100644 --- a/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm +++ b/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm @@ -144,6 +144,24 @@ else . += "Hive Orders: -" + var/mob/living/carbon/cortical_borer/B = has_brain_worms() + if(B && B.controlling) + + var/CR = "Yes" + if(!B.can_reproduce) + CR = "Forbidden" + else if((B.enzymes < BORER_LARVAE_COST)) + CR = "No" + + . += "" + . += "Borer:" + . += "Name: [B.truename]" + . += "Can Reproduce: [CR]" + . += "Enzymes: [round(B.enzymes)]/[round(B.max_enzymes)]" + . += "Health: P:[round(getBruteLoss())] B:[round(getFireLoss())] T:[round(getToxLoss())]" + . += "" + . += "Host Plasma: [plasma_stored]/[plasma_max]" + . += "Host Integrity: [health]/[maxHealth]" . += "" //A simple handler for checking your state. Used in pretty much all the procs. diff --git a/icons/mob/hud/actions_borer.dmi b/icons/mob/hud/actions_borer.dmi index 9447b8f546211f96dd4fa71ebe28446e13d25f14..99efeb7c5b44c4d1e5199e21cd035c156133dbf6 100644 GIT binary patch delta 1930 zcmV;52X*-H52+B4wSU2SR9JLGWpiV4X>fFDZ*Bkpc$}5e!3u*g3J07mPbxQXkj;B2)znXS2; zIWu4fllCSinP4lkmq)-(8JJ?mnYI|WX~|iSa)Q17u`$h#NPkyjV*n&-w1V(Y^=6!=!#>!Byxm+UeAhpXdD5;kv!i8Y9a$;_(yY5x7Fui z--CbA6bNY+E&u=ql1W5CRCt{2n~Qp)N(_KyXDD*9qg~6fg9zPT-v1F#GGTCTfJ$w5 zzmM$Js=?$Z$$tzaZC9&B7WpeA3dyhU)2|{%!g(CW-E;Qf>n-_0{TU!xuh;x;09?xu z08i8PI?ecifqW(ahL?m70VEKCB#nn16gkU_AqShyBYt~)+-ySnvwtE80L18j6o4X8 z+!6sEck$N*A%HA_0A!G(c-(>T9X#UcpqWUyxp zfqw(A=4Sxq-v&_k053p*{HxitLX7 z(0o7$_qKbgzjtdu4gipSDOu$KsNyeZJ^(dvm)qt{(&w;=Mpv7MZYR}aRQC!jA>kuE0;rRhURg&zh7qT(| z)cF@Z01;f#-z$Ks8k`?U(rW-BaEnlgem)=qLn!R;g#hpH;I-Eg6<(Y`1dam(3@Lm2 z(k;?eA;7;eAHa3sq+qnaR}qkMaDRSKNzfYhaUEDGD8Rop9{>U|4B++mQ}crg{9E>c zz)pcEocRF6w;hb!2WC6m$v z0h*>hsW)5Jwx{;{0ouB58h@>5oAzda%|`#^H*DY)B&3fZ_NRzN1VJ$feSW~cK7f?r zA3l@&^P>w;*L4d76#W+K!2kT9eL4UYk0AOG*8n>H1ELWueWc2do#2aN;D3I^zB&Nk zq+h}4UN{EO=^xfTygK?fAgCMoe>xFC?R|dCJ|58P`y*@vz5kxT)PKJ-j6vH*&8e@& zhs*850b2j@(H~(M(EdL^dje}8cyC#x0$q0@Z~!{{A0Ozzv%iWq%sqf@OargonSXvF zfr`Yd6@glBKdf#70NOryU;+E{0p8e~2558$f&d>Z3@|qZjJ>z-b_nXLQZTm&;hneo zT@^o8&^h3|8w{2HlMxRCk49w1_Tyrzt8^j`xIdh@X5vj^5Ab;%?G$R1^xN| zND&n*7~L}kBMugTy93Of4=l3CzbyX)wKBtRsOt7S Q00000NkvXXt^-0~f=&~GnE(I) delta 1872 zcmV-W2e0_45bzI>wST>OR9JLGWpiV4X>fFDZ*Bkpc$}4z!3u*g42I9`Qxv@q-FDq& zD8s{i1?g(E1#Km%`udXzI}8T#_W$7ve1W9uUOuW?70*g8faHQ#H+4N_Qmn3xb}N^& z6c$`><6bE02wO~E0RbChVWdQg)*)@vT#63$2z&lvDbAjWu7B3i1jwXK@Dann)i9c? zF@Rox$;N;q_{gN4?|`_lb*=M#lO4lzCXO&+XlKJclm>w5h|hQ7uTiSXy7+wI8!|t{ zRIT+gNdN!_T}ebiRCt{2nu~gyIt+lrY)T-cPP&wnnh@xEdH+Y8WMSefG|6ew?;Bd1 z3j*m!mTjbNv45ClmLDNWNPgW;zepGf=V_YupV@=2x8#4+p8&Gua>>69fNO~X@H}5G z^MXeVtgZyW@RINtKn4-W@-*(CT(MOdbFf}N;@jindL7cA{1ZU{AVvS902GPxk_hm$ zkH01e0TdYopnx$-#~ldY!6V*2{^|gD0DRdCGI#(g`G3J`2)+AfC4~d9F(Tjtfd4AX zwPY0#ctfD3DQE*Z0N$TagXcUy|A+FtE202f4XyhYR355tsnE1^`hxfCR=6^QGDfhLsq;awEbEwena1 zNZwmfq=uZIH9}rOk(fa<&0BB#v0R;62M8rV6 zz9;=*3P6&583&;Lfd9xfRo@R0s1NK=#{tNG9R(<=BLHMyN>+OSD)~$54?qrJ>2XGVZ}$)CKm^pWn(PCftX%*>IiUIj__wT9SSBMb;*Oy8m(j zAc8aMdj(L}(f)xXy#^oxHwlFp`U4`+gu?n>2=ERsUU{8R;-wix;1FqGNIBS-Zjvra z0sf8t0L}wv2uACB6#*%u{exP9)_<^%^S~N{68u~J0U!`@0I$BE>L1kL-?9$`_7I5D z=?_3m&M2?#{(+RWI|Q_befBotnMKzI?D0)X5a`UlQJBc*3wS`iR|{=f);S6=r? z2-EO@0JR$d8L)2w1p5QR0T`+8DFSK~fWcqugZ8Bb0PC;W9}sSTV7$Ih`hRuE+7K)N zSbt6X(zd@)&j40=F0B5*RDG|L@DKq|&j6NvY1v<>mB;Q62(v#hRo|a>!$S~2tvt4U zY1$9=2ZWIWQ}z91{~%yrnmI7bEVIlq%eU134`09j`ue|Vjx8SR+NN!~v#-A%plzF@ zI@qzUJ9f7R=$fW&wW4dgi+=&uYyFqsuz^>QkUoLf9}|`k1jQh9eZ;;#ft2AtK9l>? zs|(OHO$P)N{SNcMzdmZ8PC&&Yh(5(NfR6uwXaq~2sPtn;_@x;5*N@m&C*VQ)FBtt7 z#{fG0!}^z3NB;%{O$$HABM~&-^<(z&gh4-?VH+6y4+N(EonZ{RE`Mo{LoPmi-9DV4 z^&cO@8I}R<|MPPou-1Y1jwLG4O&(`=O+@VNW5AR zX!P>Knl=ES?Slsvuzx+k8++3LtqwsD;Ddz$t{no#-n(}@1kG7PaBUL8k+LG>Be&oav_v&=GG zzkfTx)MhaQ+zcS{$aO1#=tJji07f3WZvrq@c-#VDwDh_GKuPcaRQ Date: Mon, 3 Apr 2023 08:17:40 +0100 Subject: [PATCH 15/25] Brain follow, help details & follow tweak. Brains! --- code/__DEFINES/typecheck/xenos.dm | 1 + code/__HELPERS/unsorted.dm | 2 + .../admin/player_panel/player_panel.dm | 2 + code/modules/borer/borer.dm | 45 +++++++++++--- code/modules/borer/borer_procs.dm | 55 ++++++++++++------ code/modules/mob/dead/observer/orbit.dm | 8 +-- code/modules/mob/living/carbon/human/human.dm | 3 +- .../mob/living/carbon/xenomorph/XenoProcs.dm | 3 +- code/modules/mob/living/living_defines.dm | 3 + icons/mob/hud/actions_borer.dmi | Bin 2089 -> 2187 bytes tgui/packages/tgui/interfaces/Orbit/index.tsx | 4 +- tgui/packages/tgui/interfaces/Orbit/types.ts | 2 +- 12 files changed, 91 insertions(+), 37 deletions(-) diff --git a/code/__DEFINES/typecheck/xenos.dm b/code/__DEFINES/typecheck/xenos.dm index 6ae8fe1955a0..a71dbd287e7a 100644 --- a/code/__DEFINES/typecheck/xenos.dm +++ b/code/__DEFINES/typecheck/xenos.dm @@ -1,6 +1,7 @@ //Xenomorph Hud Test APOPHIS 22MAY2015 #define isxeno(A) (istype(A, /mob/living/carbon/xenomorph)) #define isborer(A) (istype(A, /mob/living/carbon/cortical_borer)) +#define iscaptivemind(A) (istype(A, /mob/living/captive_brain)) #define isxeno_human(A) (isxeno(A) || ishuman(A)) //ask walter if i should turn into castechecks diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index 25890101758b..e190a64be913 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -626,6 +626,8 @@ moblist.Add(M) for(var/mob/living/carbon/cortical_borer/M in sortmob) moblist.Add(M) + for(var/mob/living/captive_brain/M in sortmob) + moblist.Add(M) for(var/mob/dead/observer/M in sortmob) moblist.Add(M) for(var/mob/new_player/M in sortmob) diff --git a/code/modules/admin/player_panel/player_panel.dm b/code/modules/admin/player_panel/player_panel.dm index ada3d1100687..b1eb9bdf21bd 100644 --- a/code/modules/admin/player_panel/player_panel.dm +++ b/code/modules/admin/player_panel/player_panel.dm @@ -217,6 +217,8 @@ M_job = "Corgi" else M_job = "Animal" + else if(iscaptivemind(M)) + M_job = "Captive Mind" else M_job = "Living" else if(istype(M,/mob/new_player)) diff --git a/code/modules/borer/borer.dm b/code/modules/borer/borer.dm index 54daabcf341b..25cf64bcfe96 100644 --- a/code/modules/borer/borer.dm +++ b/code/modules/borer/borer.dm @@ -1,6 +1,15 @@ /mob/living/captive_brain - name = "host brain" - real_name = "host brain" + name = "captive mind" + real_name = "captive mind" + icon = 'icons/obj/items/organs.dmi' + icon_state = "xenobrain" + + /// Whether or not the brain is mid-resisting control. + var/resisting_control = FALSE + +/mob/living/captive_brain/New(loc, ...) + . = ..() + give_action(src, /datum/action/innate/borer/brain_resist) /mob/living/captive_brain/say(message) if(client) @@ -40,14 +49,31 @@ if(!istype(B)) log_debug(EXCEPTION("Trapped mind found without a borer!"), src) return FALSE + if(resisting_control) + to_chat(src, SPAN_DANGER("You stop resisting control.")) + to_chat(B.host, SPAN_XENODANGER("The captive mind of [src] is no longer attempting to resist you.")) + resisting_control = FALSE + return FALSE - to_chat(src, SPAN_DANGER("You begin doggedly resisting the parasite's control (this will take approximately sixty seconds).")) - to_chat(B.host, SPAN_XENOWARNING("You feel the captive mind of [src] begin to resist your control.")) - + to_chat(src, SPAN_HIGHDANGER("You begin doggedly resisting the parasite's control (this will take approximately sixty seconds).")) + to_chat(B.host, SPAN_XENOHIGHDANGER("You feel the captive mind of [src] begin to resist your control.")) + resisting_control = TRUE var/delay = (rand(350,450) + B.host.getBrainLoss()) addtimer(CALLBACK(src, PROC_REF(return_control), B), delay) return TRUE +/datum/action/innate/borer/brain_resist + name = "Resist!" + action_icon_state = "brain_resist" + +/datum/action/innate/borer/brain_resist/action_activate() + if(isliving(owner)) + var/mob/living/live_owner = owner + live_owner.resist() + else + to_chat(owner, SPAN_WARNING("Error: CB1, tell forest2001! ")) + return FALSE +//################################################// /mob/living/carbon/cortical_borer name = "cortical borer" real_name = "cortical borer" @@ -262,7 +288,8 @@ . += "Can Reproduce: [CR]" . += "Enzymes: [round(enzymes)]/[round(max_enzymes)]" . += "Contaminant: [round(contaminant)]/[round(max_contaminant)]" - . += "Health: P:[round(getBruteLoss())] B:[round(getFireLoss())] T:[round(getToxLoss())]" + . += "Health: [health]/[maxHealth]" + . += "Injuries: Brute:[round(getBruteLoss())] Burn:[round(getFireLoss())] Toxin:[round(getToxLoss())]" if(host) . += "" if(ishuman(host)) @@ -302,9 +329,9 @@ if(((human_host.chem_effect_flags & CHEM_EFFECT_ANTI_PARASITE) && !human_host.reagents.has_reagent("benzyme")) || human_host.reagents.has_reagent("bcure")) if(!docile) if(controlling) - to_chat(host, SPAN_XENODANGER("You feel the flow of a soporific chemical in your host's blood, lulling you into docility.")) + to_chat(host, SPAN_XENOHIGHDANGER("You feel the flow of a soporific chemical in your host's blood, lulling you into docility.")) else - to_chat(src, SPAN_XENODANGER("You feel the flow of a soporific chemical in your host's blood, lulling you into docility.")) + to_chat(src, SPAN_XENOHIGHDANGER("You feel the flow of a soporific chemical in your host's blood, lulling you into docility.")) docile = TRUE else if(docile) @@ -322,7 +349,7 @@ contaminant = max(contaminant -= 0.1, 0) if(controlling) if(docile) - to_chat(host, SPAN_XENOWARNING("You are feeling far too docile to continue controlling your host...")) + to_chat(host, SPAN_WARNING("You are feeling far too docile to continue controlling your host...")) host.release_control() return else diff --git a/code/modules/borer/borer_procs.dm b/code/modules/borer/borer_procs.dm index 224cdc2fac5f..86b9cebc6308 100644 --- a/code/modules/borer/borer_procs.dm +++ b/code/modules/borer/borer_procs.dm @@ -6,37 +6,47 @@ var/target = src - var/list/options = list("Communicating") + var/list/options = list("Communicating","Contaminant & Enzymes") if(!host) options += list("Infecting a host") else if(controlling) target = host - options = list("Captive Host", "Releasing Control", "Reproducing") + options += list("Captive Host", "Releasing Control", "Reproducing") else if(isxeno(host)) - options = list("Assuming Control","Contaminant & Enzymes","Hibernation","Reproducing") + options += list("Assuming Control","Hibernation","Reproducing") else - options = list("Assuming Control","Contaminant & Enzymes","Hibernation","Secreting Chemicals","Reproducing") + options += list("Assuming Control","Hibernation","Secreting Chemicals","Reproducing") var/choice = tgui_input_list(target, "What would you like help with?", "Help", options, 20 SECONDS) var/help_message = "" switch(choice) if("Infecting a host") - help_message = "Infecting a host is the first major step for a borer to complete.\n\nThis is done by getting close to a potential host (This can be Human, Xeno or Yautja depending on settings controlled by admins) and clicking the Infest button in the top left of your screen.\n\nYour host will need to keep still for you to do this, and it's rare that they do so; for this reason you have Dominate Victim, which will allow you to temporarily stun a target.\n\nNote: This is not sufficient to keep them down for 100% of the time it takes to infect however, so be careful with it." + var/possible_targets = "" + if(infect_humans) possible_targets += "\nHumans" + if(infect_xenos) possible_targets += "\nXenos" + if(infect_yautja) possible_targets += "\nYautja" + if(!possible_targets) possible_targets += "No one." + help_message = "Infecting a host is the first major step for a borer to complete.\n\nThis is done by getting close to a potential host (This can be Human, Xeno or Yautja depending on settings controlled by admins) and clicking the Infest button in the top left of your screen.\n\nYour host will need to keep still for you to do this, and it's rare that they do so; for this reason you have Dominate Victim, which will allow you to temporarily stun a target.\n\nNote: Dominate is not sufficient to keep them down for 100% of the time it takes to infect however, so be careful with it.\n\nWhilst inside a host, and NOT in direct control, you can be detected by body scanners but otherwise are hidden from everyone but your host and other borers.\nYou can currently infect: [possible_targets]" if("Communicating") help_message = "All borers share a hivemind, the Cortical Link, this can be accessed using :0 (that's ZERO).\n\nThe hivemind can be used by any borer who is NOT in direct control of their host.\n\nAny borer in direct control of a host can only hear what their host can hear.\n\nA borer inside a host can communicate directly with that host using 'Converse with Host'." if("Captive Host") - help_message = "" + help_message = "Your host is now unable to act or speak to anyone but yourself. While in this state you have complete control of your host body, but you are disconnected from the hivemind.\n\nYour host can resist your control and regain their body however this can cause brain damage in humanoids.\n\nNote: Whilst in direct control of your host medical HUDs will detect you.\n\n\nIMPORTANT: While in direct control of a mob you MUST NOT perform antag actions unless you have permission from staff." if("Releasing Control") - help_message = "" + help_message = "Releasing control will do as it suggests, give your host their body back.\n\nYour host can resist your control and regain their body however this can cause brain damage in humanoids." if("Reproducing") - help_message = "" + var/capability = "able" + if(!can_reproduce) capability = "forbidden" + else if((enzymes < BORER_LARVAE_COST)) capability = "unable" + help_message = "Reproduction will take [BORER_LARVAE_COST] enzymes and direct control of your host.\n\nWhen in direct control you can use the Reproduce ability to spit out a new borer.\nMake sure to do this in a safe place, the new borer will not have a player to start with.\n\nYou are currently [capability] to reproduce." if("Assuming Control") - help_message = "" - if("Contaminant & Enzymes", "Hibernation") - help_message = "" + help_message = "Assuming control will put you in direct control of your host, acting as if you are their player.\n\nYour host will be disassociated with their body, and trapped in their own mind unable to speak to anyone but you.\nWhile in this state you are unable to make use of the hivemind.\n\nYour host can resist your control and regain their body however this can cause brain damage in humanoids.\nYou must assume control to reproduce.\n\nNote: Whilst in direct control of your host medical HUDs will detect you.\n\n\nIMPORTANT: While in direct control of a mob you MUST NOT perform antag actions unless you have permission from staff." + if("Contaminant & Enzymes") + help_message = "Enzymes are the cost of using most of your active abilities, such as secreting chemicals. They are gained passively over time whilst inside a host.\n\nUsing enzymes will in most cases produce Contaminant which upon reaching its capacity will prevent you using abilities.\nYou can clear Contaminant by hibernating when inside a host, alternately Contaminant will naturally be turned into a weak light source whilst outside a host." + if("Hibernation") + help_message = "Hibernation is how you purify contaminants from your body, allowing you to use your enzymes more freely.\n\nYou can only hibernate whilst inside a host, and it renders you unable to act other than to speak to your host.\n\nYou can freely enter or leave hibernation by clicking the Hibernate button." if("Secreting Chemicals") - help_message = "" + help_message = "Whilst inside a humanoid host you can secrete chemicals to facilitate your relationship.\nThese can vary from helpful medications to harmful control measures.\n\nSecreting chemicals costs enzymes and if a chemical is impure will cause you to gain contaminant.\nIf you are at, or will go over, your contaminant capacity you will be unable to secrete chemicals.\nPure chemicals are chemicals native to borers such as Cortical Enzyme." alert(target, help_message, choice, "Ok") @@ -185,6 +195,8 @@ for(var/mob/living/carbon/candidate in view(1,src)) if(can_target(candidate)) choices += candidate + if(!choices) + to_chat(src, SPAN_XENOWARNING("No possible targets found.")) var/mob/living/carbon/target = tgui_input_list(src, "Who do you wish to infest?", "Targets", choices) if(!target || !src || !Adjacent(target)) return FALSE @@ -381,6 +393,7 @@ give_new_actions(ACTION_SET_CONTROL) host.med_hud_set_status() + host.special_mob = TRUE if(src && !src.key) src.key = "@[borer_key]" @@ -388,11 +401,12 @@ //Captive mind reclaims their body. /mob/living/captive_brain/proc/return_control(mob/living/carbon/cortical_borer/B) - if(!B || !B.controlling) + if(!B || !B.controlling || !resisting_control) return FALSE B.host.adjustBrainLoss(rand(5,10)) - to_chat(src, SPAN_DANGER("With an immense exertion of will, you regain control of your body!")) - to_chat(B.host, SPAN_XENODANGER("You feel control of the host brain ripped from your grasp, and retract your probosci before the wild neural impulses can damage you.")) + to_chat(src, SPAN_HIGHDANGER("With an immense exertion of will, you regain control of your body!")) + to_chat(B.host, SPAN_XENOHIGHDANGER("You feel control of the host brain ripped from your grasp, and retract your probosci before the wild neural impulses can damage you.")) + resisting_control = FALSE B.detach() return TRUE @@ -425,6 +439,7 @@ sleeping = 0 if(host_brain) log_interact(host, src, "Borer: [key_name(host)] Took control back") + host.special_mob = FALSE // host -> self var/h2s_id = host.computer_id var/h2s_ip= host.lastKnownIP @@ -457,9 +472,9 @@ return FALSE if(controlling) detach() - to_chat(src, SPAN_HIGHDANGER("You release your proboscis and flee as the psychic shock of your host's death washes over you!")) + to_chat(src, SPAN_XENOHIGHDANGER("You release your proboscis and flee as the psychic shock of your host's death washes over you!")) if(perma) - to_chat(src, SPAN_HIGHDANGER("You flee your host in anguish!")) + to_chat(src, SPAN_XENOHIGHDANGER("You flee your host in anguish!")) leave_host() return TRUE @@ -470,7 +485,7 @@ set desc = "Become invisible to the common eye." if(host) - to_chat(usr, SPAN_XENOWARNING("You cannot do this while you're inside a host.")) + to_chat(usr, SPAN_WARNING("You cannot do this while you're inside a host.")) return FALSE if(stat != CONSCIOUS) @@ -511,6 +526,8 @@ to_chat(src, SPAN_XENOWARNING("You cannot use that ability again so soon.")) return FALSE attempting_to_dominate = TRUE + if(!choices) + to_chat(src, SPAN_XENOWARNING("No possible targets found.")) var/mob/living/carbon/M = tgui_input_list(src, "Who do you wish to dominate?", "Targets", choices) if(!M) attempting_to_dominate = FALSE @@ -554,7 +571,7 @@ if(B.host_brain) to_chat(src, SPAN_XENONOTICE("You send a punishing spike of psychic agony lancing into your host's brain.")) - to_chat(B.host_brain, SPAN_XENOHIGHDANGER("Horrific, burning agony lances through you, ripping a soundless scream from your trapped mind!")) + to_chat(B.host_brain, SPAN_HIGHDANGER("Horrific, burning agony lances through you, ripping a soundless scream from your trapped mind!")) return TRUE diff --git a/code/modules/mob/dead/observer/orbit.dm b/code/modules/mob/dead/observer/orbit.dm index 39a7eb968368..bb35d8a87dc8 100644 --- a/code/modules/mob/dead/observer/orbit.dm +++ b/code/modules/mob/dead/observer/orbit.dm @@ -56,7 +56,7 @@ /datum/orbit_menu/ui_static_data(mob/user) var/list/data = list() - var/list/borers = list() + var/list/special_mobs = list() var/list/humans = list() var/list/marines = list() var/list/survivors = list() @@ -112,8 +112,8 @@ var/mob/living/player = M serialized["health"] = FLOOR((player.health / player.maxHealth * 100), 1) - if(isborer(player)) - borers += list(serialized) + if(isborer(player) || iscaptivemind(player) || player.special_mob) + special_mobs += list(serialized) if(isxeno(player)) var/mob/living/carbon/xenomorph/xeno = player @@ -163,7 +163,7 @@ else if(isAI(M)) humans += list(serialized) - data["borers"] = borers + data["special_mobs"] = special_mobs data["humans"] = humans data["marines"] = marines data["survivors"] = survivors diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index d0eac93c78e9..aff43fc0fe6f 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -150,7 +150,8 @@ . += "Name: [B.truename]" . += "Can Reproduce: [CR]" . += "Enzymes: [round(B.enzymes)]/[round(B.max_enzymes)]" - . += "Health: P:[round(getBruteLoss())] B:[round(getFireLoss())] T:[round(getToxLoss())]" + . += "Health: [B.health]/[B.maxHealth]" + . += "Injuries: Brute:[round(B.getBruteLoss())] Burn:[round(B.getFireLoss())] Toxin:[round(B.getToxLoss())]" . += "" . += "Host Brain Damage: [brainloss]/100" diff --git a/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm b/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm index 6a592494756e..2f3bd91b8f93 100644 --- a/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm +++ b/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm @@ -158,7 +158,8 @@ . += "Name: [B.truename]" . += "Can Reproduce: [CR]" . += "Enzymes: [round(B.enzymes)]/[round(B.max_enzymes)]" - . += "Health: P:[round(getBruteLoss())] B:[round(getFireLoss())] T:[round(getToxLoss())]" + . += "Health: [B.health]/[B.maxHealth]" + . += "Injuries: Brute:[round(B.getBruteLoss())] Burn:[round(B.getFireLoss())] Toxin:[round(B.getToxLoss())]" . += "" . += "Host Plasma: [plasma_stored]/[plasma_max]" . += "Host Integrity: [health]/[maxHealth]" diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index 4293595e1a51..7bdcf7a19573 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -110,3 +110,6 @@ /// This is what the value is changed to when the mob dies. Actual BMV definition in atom/movable. var/dead_black_market_value = 0 + + /// Used to highlight on follow menu. + var/special_mob = FALSE diff --git a/icons/mob/hud/actions_borer.dmi b/icons/mob/hud/actions_borer.dmi index 99efeb7c5b44c4d1e5199e21cd035c156133dbf6..d1098b87d803641b4ca6ee29871f2c6dec5717ae 100644 GIT binary patch delta 2028 zcmVfFDZ*Bkpc$}4zu?mAg42Ea#DGuFBZM$|U zN^z*KAXklEK<^|u_4O+iIuuIdmOp%fFCmG(<@ci0*`rW15TkecsxOD5iWY2wUBmgL zA`95rumuh#z*@#nPe4^|Vc=Lr4HB>2R7EYB0k-;!`6pHRCt{2n~Qp)N(_Ky2Z|tev};*Ah|ulj{U7lp z69(rF>b7UkcYnxktr|>zGMRy-&T6&DB7cP>KKb!|`c=Y6(lpDm;W>M7^@jXG{RJRj zuh;390i;So0BKRI*F~8I3~cTM!0?i!A%GkrkQZ6lLA7C0dN4)^&rUL0LbLeN+~Jg z`T-E0RkuZ8y;A}N4nRsdfd2DT2F&LmD>j=dm;*xuh5%T4IRD*lkD>^40Gj+60u)7_ zQOdXus?7$Hq@2nTFJP+tBY^{uQg{^r5!eo@DuWaSxfNiq#08)v!AV%+JV@<%no#0p zIYeON1%G&YDoZ_4GJ6PN1ol6Ff_Hd;5ZFVIg8u?Dw8V!3=YcZ>oZ~F#F3t*>3zy$#RbzUgRYanojKu%N8dU61~KcNPXd4B$d@_Z3lD?0+Y3fE)#2sN5uk036Ui!)yeI!V`gA6ad!W-5!P?+?V?uOUwFrv;go@ z7=M`O2#`qt7!3A|A+P|}=>1lka% zYk4UcS7LaXY7t(^mB#=;0vEtQumBEpJ;NM)k^~w6EVAJ6axGfRtFi|0;{jy^kgs$? z(%Yixi?Yx)Fc61>rq9UdDDs64u*f2dEPt}dU(+9eo2{|#_=^635KYr~^cMi^5AdLY zV0}Lu0PTw~0H^){4;Tp7_oP2f0SK}$!T{7CaBsP$>iaPQ&550wFaX(af&dlu2mAoB z`raIZ0m0QbWIqBx{Q=JH+wQ6Q-p&CL03iE9u*Lz9$zM@_0CE6Jk1OhXvwzTdB7Y#? zYsfy}iN*%tlmoIqfX{*8iu(T2KRBHlZw?5`0cjtf1Az`elfU9rp34`!x}v^UAwHe_ z{R4t(1lgA_M56=9^RGq#BDkWymjF!@>>miyD*z&}laTYVKfparD6H=}2k-FUl-CI* zUY0`yPJsr75TkuzC+Vu<;9u(x;D0=@h9Fws%LoV&>>o4&w3>aK2gVRo;NR#E009pJ zIQ9Kh|DXZ?hJ7F~hk#dBe*j`~MR{%Z4}@s!ArOsaA7Wx8A$R%%+=;*x0OZ!#Kd=@W zAsqX{hyeHW2O7=fEP1EV9TVUsC@+%>VrT^?%!* zJAAM2+OF-d{`~y_UDuxFn}0p)`*Z*O0Daqbol^8&e>1>#tN!xqH}DGL(*!qK*zs96oR2oWcsl) z{7?+s&qwUb1Mp4y3dZolGJsBhv*F>@(!T*g+rj_ynFw0v^D+B)z<;P8k1!34{zn2` z|IRQ5eV=saF&7`^+lK>`{^Mgj!Z4uxe}0Yx#yarcvqT2E9YSCM4E8@h(1Bxrm28=P z0Nd&YUWYUP{6qp7iB}^6ty+Fq+j#(#eel3M_U{LHV{ZzeQz7sId@w)2y+gp*d;e~R zpuK7c?oC2?=dFF0$$yX8c)mUf@0^BA5~vWgZW2yU!#fuu1W;p;$U$gr{nSFl?pcbN zG5(m4-StZ`DQ~+Nk#~l3pktlmv>zX5^y%5N>hf=L+Je{coBCyN7Yg5R{_k?#*o1I!+R@%-Hg zMD`!d0GO48ab%G_j delta 1930 zcmV;52X*+15vdT6wSU2SR9JLGWpiV4X>fFDZ*Bkpc$}5e!3u*g3J07mPbxQXkj;B2)znXS2; zIWu4fllCSinP4lkmq)-(8JJ?mnYI|WX~|iSa)Q17u`$h#NPkyjV*n&-w1V(Y^=6!=!#>!Byxm+UeAhpXdD5;kv!i8Y9a$;_(yY5x7Fui z--CbA6bNY+E&u=ql1W5CRCt{2n~Qp)N(_KyXDD*9qg~6fg9zPT-v1F#GGTCTfJ$w5 zzmM$Js=?$Z$$tzaZC9&B7WpeA3dyhU)2|{%!g(CW-E;Qf>n-_0{TU!xuh;x;09?xu z08i8PI?ecifqW(ahL?m70VEKCB#nn16gkU_AqShyBYt~)+-ySnvwtE80L18j6o4X8 z+!6sEck$N*A%HA_0A!G(c-(>T9X#UcpqWUyxp zfqw(A=4Sxq-v&_k053p*{HxitLX7 z(0o7$_qKbgzjtdu4gipSDOu$KsNyeZJ^(dvm)qt{(&w;=Mpv7MZYR}aRQC!jA>kuE0;rRhURg&zh7qT(| z)cF@Z01;f#-z$Ks8k`?U(rW-BaEnlgem)=qLn!R;g#hpH;I-Eg6<(Y`1dam(3@Lm2 z(k;?eA;7;eAHa3sq+qnaR}qkMaDRSKNzfYhaUEDGD8Rop9{>U|4B++mQ}crg{9E>c zz)pcEocRF6w;hb!2WC6m$v z0h*>hsW)5Jwx{;{0ouB58h@>5oAzda%|`#^H*DY)B&3fZ_NRzN1VJ$feSW~cK7f?r zA3l@&^P>w;*L4d76#W+K!2kT9eL4UYk0AOG*8n>H1ELWueWc2do#2aN;D3I^zB&Nk zq+h}4UN{EO=^xfTygK?fAgCMoe>xFC?R|dCJ|58P`y*@vz5kxT)PKJ-j6vH*&8e@& zhs*850b2j@(H~(M(EdL^dje}8cyC#x0$q0@Z~!{{A0Ozzv%iWq%sqf@OargonSXvF zfr`Yd6@glBKdf#70NOryU;+E{0p8e~2558$f&d>Z3@|qZjJ>z-b_nXLQZTm&;hneo zT@^o8&^h3|8w{2HlMxRCk49w1_Tyrzt8^j`xIdh@X5vj^5Ab;%?G$R1^xN| zND&n*7~L}kBMugTy93Of4=l3CzbyX)wKBtRsOt7S Q00000NkvXXt^-0~f@6k(HUIzs diff --git a/tgui/packages/tgui/interfaces/Orbit/index.tsx b/tgui/packages/tgui/interfaces/Orbit/index.tsx index 9f6e9004b951..2856598e97b8 100644 --- a/tgui/packages/tgui/interfaces/Orbit/index.tsx +++ b/tgui/packages/tgui/interfaces/Orbit/index.tsx @@ -99,7 +99,7 @@ const ObservableSearch = (props, context) => { const ObservableContent = (props, context) => { const { data } = useBackend(context); const { - borers = [], + special_mobs = [], humans = [], marines = [], survivors = [], @@ -118,7 +118,7 @@ const ObservableContent = (props, context) => { return ( - + diff --git a/tgui/packages/tgui/interfaces/Orbit/types.ts b/tgui/packages/tgui/interfaces/Orbit/types.ts index d3b339ae04ed..d41e3389945e 100644 --- a/tgui/packages/tgui/interfaces/Orbit/types.ts +++ b/tgui/packages/tgui/interfaces/Orbit/types.ts @@ -2,7 +2,7 @@ import { BooleanLike } from 'common/react'; export type OrbitData = { auto_observe: BooleanLike; - borers: Observable[]; + special_mobs: Observable[]; humans: Observable[]; marines: Observable[]; survivors: Observable[]; From bcd5c9a4bab6d64088df8e88afdb752c8bc1daa8 Mon Sep 17 00:00:00 2001 From: forest2001 Date: Mon, 3 Apr 2023 23:08:35 +0100 Subject: [PATCH 16/25] reproduction consistencies and hearing fix --- code/modules/borer/borer.dm | 16 ++++++++-------- code/modules/borer/borer_procs.dm | 6 +++--- code/modules/mob/language/languages.dm | 5 ++++- code/modules/mob/living/say.dm | 4 ++++ 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/code/modules/borer/borer.dm b/code/modules/borer/borer.dm index 25cf64bcfe96..52d3c9929ad3 100644 --- a/code/modules/borer/borer.dm +++ b/code/modules/borer/borer.dm @@ -133,34 +133,34 @@ /datum/action/innate/borer/helpme, /datum/action/innate/borer/toggle_hide, /datum/action/innate/borer/freeze_victim, - /datum/action/innate/borer/infest_host + /datum/action/innate/borer/infest_host, ) var/list/actions_humanoidhost = list( /datum/action/innate/borer/helpme, - /datum/action/innate/borer/take_control, /datum/action/innate/borer/talk_to_host, - /datum/action/innate/borer/leave_body, /datum/action/innate/borer/hibernate, + /datum/action/innate/borer/take_control, + /datum/action/innate/borer/leave_body, /datum/action/innate/borer/scan_chems, - /datum/action/innate/borer/make_chems + /datum/action/innate/borer/make_chems, ) var/list/actions_xenohost = list( /datum/action/innate/borer/helpme, - /datum/action/innate/borer/take_control, /datum/action/innate/borer/talk_to_host, + /datum/action/innate/borer/hibernate, + /datum/action/innate/borer/take_control, /datum/action/innate/borer/leave_body, - /datum/action/innate/borer/hibernate ) var/list/actions_control = list( /datum/action/innate/borer/helpme, /datum/action/innate/borer/give_back_control, /datum/action/innate/borer/make_larvae, /datum/action/innate/borer/talk_to_brain, - /datum/action/innate/borer/torment + /datum/action/innate/borer/torment, ) //################### INIT & LIFE ###################// -/mob/living/carbon/cortical_borer/New(atom/newloc, gen=1, ERT = FALSE, reproduction = 0) +/mob/living/carbon/cortical_borer/New(atom/newloc, gen=1, ERT = FALSE, reproduction = 0, infect_humans = TRUE, infect_xenos = FALSE, infect_yautja = FALSE) ..(newloc) SSmob.living_misc_mobs += src generation = gen diff --git a/code/modules/borer/borer_procs.dm b/code/modules/borer/borer_procs.dm index 86b9cebc6308..96df31acc7d5 100644 --- a/code/modules/borer/borer_procs.dm +++ b/code/modules/borer/borer_procs.dm @@ -593,7 +593,7 @@ T.add_vomit_floor() B.contaminant = 0 var/repro = max(B.can_reproduce - 1, 0) - new /mob/living/carbon/cortical_borer(T, B.generation + 1, TRUE, repro) + new /mob/living/carbon/cortical_borer(T, B.generation + 1, TRUE, repro, B.infect_humans, B.infect_xenos, B.infect_yautja) return TRUE else to_chat(src, SPAN_XENONOTICE("You need at least [BORER_LARVAE_COST] enzymes to reproduce!")) @@ -719,9 +719,9 @@ if(!input) return FALSE - to_chat(B, SPAN_XENO("[src] says: [input]"), type = MESSAGE_TYPE_RADIO) + to_chat(B, SPAN_XENO("[src.real_name] says: [input]"), type = MESSAGE_TYPE_RADIO) log_say("BORER: ([key_name(src)] to [key_name(B)]) [input]", src) - to_chat(src, SPAN_XENO("[src] says: [input]"), type = MESSAGE_TYPE_RADIO) + to_chat(src, SPAN_XENO("[src.real_name] says: [input]"), type = MESSAGE_TYPE_RADIO) for (var/mob/dead in GLOB.dead_mob_list) var/track_host = " (F)" if(!istype(dead,/mob/new_player) && !istype(dead,/mob/living/brain)) //No meta-evesdropping diff --git a/code/modules/mob/language/languages.dm b/code/modules/mob/language/languages.dm index 905b6a07670f..306a324cbea7 100644 --- a/code/modules/mob/language/languages.dm +++ b/code/modules/mob/language/languages.dm @@ -215,6 +215,7 @@ /datum/language/corticalborer/broadcast(mob/living/carbon/speaker, message, speaker_mask, death) var/mob/living/carbon/cortical_borer/B + if(!message) return FALSE if(isborer(speaker)) B = speaker else if(speaker.has_brain_worms()) @@ -224,6 +225,7 @@ if(!speaker_mask) speaker_mask = speaker.real_name + message = trim(message) var/message_start = "[name], [speaker_mask]" var/message_body = "[speech_verb], \"[message]\"" log_say("[key_name(speaker)] : ([name]) [message]") @@ -243,4 +245,5 @@ var/area/A = get_area(speaker) to_chat(player, "[message_start] has [SPAN_BOLD("perished")][A? " at [sanitize_area(A.name)]":""]!") else - to_chat(player, "[ghost? "(F) ":""][message_start][message_body]") + to_chat(player, "[ghost? "(F) ":""][message_start] [message_body]") + return TRUE diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm index bb9cf9309a2b..d1430de4a43d 100644 --- a/code/modules/mob/living/say.dm +++ b/code/modules/mob/living/say.dm @@ -116,6 +116,10 @@ var/list/department_radio_keys = list( for(var/obj/O in M.contents) if(O.flags_atom & USES_HEARING) listening_obj |= O + for(var/mob/inner_mob in M.contents) + listening |= inner_mob + for(var/mob/living/captive_brain/brain in inner_mob) + listening |= brain else if(istype(I, /obj/)) var/obj/O = I hearturfs += O.locs[1] From 8938afddc908319d7e06da8345c66b79fa7d244c Mon Sep 17 00:00:00 2001 From: forest2001 Date: Thu, 6 Apr 2023 07:27:51 +0100 Subject: [PATCH 17/25] bitflags! --- code/__DEFINES/borer_defines.dm | 52 ++++++ code/datums/mob_hud.dm | 2 +- code/modules/borer/_defines.dm | 13 -- code/modules/borer/borer.dm | 157 ++++++++---------- code/modules/borer/borer_procs.dm | 82 ++++++--- code/modules/mob/living/carbon/human/human.dm | 2 +- .../mob/living/carbon/xenomorph/XenoProcs.dm | 2 +- .../chemistry_properties/prop_positive.dm | 2 +- code/modules/surgery/brainworm.dm | 1 + colonialmarines.dme | 2 +- icons/mob/hud/actions_borer.dmi | Bin 2187 -> 2693 bytes 11 files changed, 188 insertions(+), 127 deletions(-) create mode 100644 code/__DEFINES/borer_defines.dm delete mode 100644 code/modules/borer/_defines.dm diff --git a/code/__DEFINES/borer_defines.dm b/code/__DEFINES/borer_defines.dm new file mode 100644 index 000000000000..181a7d830f47 --- /dev/null +++ b/code/__DEFINES/borer_defines.dm @@ -0,0 +1,52 @@ +/// Chemical categories +#define BORER_CAT_HEAL "Medicines" +#define BORER_CAT_PUNISH "Motivators" +#define BORER_CAT_STIM "Stimulants" +#define BORER_CAT_SELF "Self Protection" + +///Amount of chemicals needed for a borer to reproduce, provided reproduction is toggled. +#define BORER_LARVAE_COST 400 + +#define ACTION_SET_HOSTLESS "actions_hostless" +#define ACTION_SET_HUMANOID "actions_human" +#define ACTION_SET_XENO "actions_xeno" +#define ACTION_SET_CONTROL "actions_control" + +/// Borer target bitflags +#define BORER_TARGET_HUMANS (1<<0) +#define BORER_TARGET_XENOS (1<<1) +#define BORER_TARGET_YAUTJA (1<<2) + +DEFINE_BITFIELD(borer_flags_targets, list( + "TARGET_HUMANS" = BORER_TARGET_HUMANS, + "TARGET_XENOS" = BORER_TARGET_XENOS, + "TARGET_YAUTJA" = BORER_TARGET_YAUTJA, +)) + +/// Borer active/toggle-able ability flags. +/// Middle of crawling into a new host +#define BORER_PROCESS_INFESTING (1<<0) +/// Middle of taking control of a host body +#define BORER_PROCESS_BONDING (1<<1) +/// Middle of leaving a host +#define BORER_PROCESS_LEAVING (1<<2) +/// Hiding or not. (Changes layer) +#define BORER_ABILITY_HIDE (1<<3) +/// Sleeps to purify contaminant +#define BORER_ABILITY_HIBERNATING (1<<4) + +DEFINE_BITFIELD(borer_flags_actives, list( + "PROCESS_INFESTING" = BORER_PROCESS_INFESTING, + "PROCESS_BONDING" = BORER_PROCESS_BONDING, + "PROCESS_LEAVING" = BORER_PROCESS_LEAVING, + "ACTIVE_HIDING" = BORER_ABILITY_HIDE, + "ACTIVE_HIBERNATING" = BORER_ABILITY_HIBERNATING, +)) + +#define BORER_STATUS_CONTROLLING (1<<0) +#define BORER_STATUS_DOCILE (1<<1) + +DEFINE_BITFIELD(borer_flags_status, list( + "STATUS_CONTROLLING" = BORER_STATUS_CONTROLLING, + "STATUS_DOCILE" = BORER_STATUS_DOCILE, +)) diff --git a/code/datums/mob_hud.dm b/code/datums/mob_hud.dm index a6d4e2c09a09..1d0519b54c81 100644 --- a/code/datums/mob_hud.dm +++ b/code/datums/mob_hud.dm @@ -463,7 +463,7 @@ var/list/datum/mob_hud/huds = list( holder5.icon_state = null if(B) holder5.icon_state = "hudbrainwormhost" - if(B.controlling) + if(B.borer_flags_status & BORER_STATUS_CONTROLLING) holder.icon_state = "hudbrainworm" if(!holder2_set) holder2.icon_state = "hudbrainworm" diff --git a/code/modules/borer/_defines.dm b/code/modules/borer/_defines.dm deleted file mode 100644 index a1c7b4959e50..000000000000 --- a/code/modules/borer/_defines.dm +++ /dev/null @@ -1,13 +0,0 @@ -/// Chemical categories -#define BORER_CAT_HEAL "Medicines" -#define BORER_CAT_PUNISH "Motivators" -#define BORER_CAT_STIM "Stimulants" -#define BORER_CAT_SELF "Self Protection" - -///Amount of chemicals needed for a borer to reproduce, provided reproduction is toggled. -#define BORER_LARVAE_COST 400 - -#define ACTION_SET_HOSTLESS "actions_hostless" -#define ACTION_SET_HUMANOID "actions_human" -#define ACTION_SET_XENO "actions_xeno" -#define ACTION_SET_CONTROL "actions_control" diff --git a/code/modules/borer/borer.dm b/code/modules/borer/borer.dm index 52d3c9929ad3..832a96aa35f3 100644 --- a/code/modules/borer/borer.dm +++ b/code/modules/borer/borer.dm @@ -115,16 +115,18 @@ var/mob/living/carbon/host // Human host for the brain worm. var/truename // Name used for brainworm-speak. var/mob/living/captive_brain/host_brain // Used for swapping control of the body back and forth. - var/controlling // Used in human death check. var/docile = FALSE // Anti-Parasite or Anti-Enzyme chemicals can stop borers from acting. var/bonding = FALSE var/leaving = FALSE var/hiding = FALSE var/can_reproduce = FALSE // Locked to manual override to prevent things getting out of hand. - var/infect_humans = TRUE // Locked for normal use. - var/infect_xenos = FALSE - var/infect_yautja = FALSE + /// Flags that show what active abilities are toggled. Better than a dozen different boolean vars. + var/borer_flags_actives + /// Flags determining what the borer can infect + var/borer_flags_targets = BORER_TARGET_HUMANS + /// Borer status, controlling or docile. + var/borer_flags_status //Controlling or Docile. Unsure if I want to put hibernating in here or in actives as active abilities will stop enzyme production. var/list/datum/reagent/synthesized_chems @@ -160,7 +162,7 @@ ) //################### INIT & LIFE ###################// -/mob/living/carbon/cortical_borer/New(atom/newloc, gen=1, ERT = FALSE, reproduction = 0, infect_humans = TRUE, infect_xenos = FALSE, infect_yautja = FALSE) +/mob/living/carbon/cortical_borer/New(atom/newloc, gen=1, ERT = FALSE, reproduction = 0, new_targets = BORER_TARGET_HUMANS) ..(newloc) SSmob.living_misc_mobs += src generation = gen @@ -169,6 +171,7 @@ real_name = "Cortical Borer [mob_number]" truename = "[borer_names[min(generation, borer_names.len)]] [mob_number]" can_reproduce = reproduction + borer_flags_targets = new_targets give_new_actions(ACTION_SET_HOSTLESS) //GrantBorerActions() GiveBorerHUD() @@ -186,6 +189,55 @@ /mob/living/carbon/cortical_borer/initialize_stamina() stamina = new /datum/stamina/none(src) +/mob/living/carbon/cortical_borer/Life(delta_time) + ..() + update_canmove() + update_icons() + var/heal_amt = 1 + if(host) + heal_amt = 3 + if(!stat && host.stat != DEAD) + var/mob/living/carbon/human/human_host + if(ishuman(host)) + human_host = host + if(((human_host.chem_effect_flags & CHEM_EFFECT_ANTI_PARASITE) && !human_host.reagents.has_reagent("benzyme")) || human_host.reagents.has_reagent("bcure")) + if(!docile) + if(borer_flags_status & BORER_STATUS_CONTROLLING) + to_chat(host, SPAN_XENOHIGHDANGER("You feel the flow of a soporific chemical in your host's blood, lulling you into docility.")) + else + to_chat(src, SPAN_XENOHIGHDANGER("You feel the flow of a soporific chemical in your host's blood, lulling you into docility.")) + docile = TRUE + else + if(docile) + if(borer_flags_status & BORER_STATUS_CONTROLLING) + to_chat(human_host, SPAN_XENONOTICE("You shake off your lethargy as the chemical leaves your host's blood.")) + else + to_chat(src, SPAN_XENONOTICE("You shake off your lethargy as the chemical leaves your host's blood.")) + docile = FALSE + if(!hibernating && (enzymes < max_enzymes)) + enzymes++ + if(contaminant > 0) + if(hibernating) + contaminant = max(contaminant -= 1, 0) + else + contaminant = max(contaminant -= 0.1, 0) + if(borer_flags_status & BORER_STATUS_CONTROLLING) + if(docile) + to_chat(host, SPAN_WARNING("You are feeling far too docile to continue controlling your host...")) + host.release_control() + return + else + if(contaminant > 0) + if(!luminosity) + SetLuminosity(2) + contaminant = max(contaminant - 0.3, 0) + else + SetLuminosity(0) + if(bruteloss || fireloss) + heal_overall_damage(heal_amt, heal_amt) + if(toxloss && !contaminant)//no clearing toxic impurities while contaminated. + apply_damage(-(heal_amt/2), TOX) + /mob/living/carbon/cortical_borer/updatehealth() if(status_flags & GODMODE) health = maxHealth @@ -235,18 +287,6 @@ return ..() //###################################################// -/mob/living/carbon/cortical_borer/proc/summon() - var/datum/emergency_call/custom/em_call = new() - em_call.name = real_name - em_call.mob_max = 1 - em_call.players_to_offer = list(src) - em_call.owner = null - em_call.ert_message = "A new Cortical Borer has been birthed!" - em_call.objectives = "Create enjoyable Roleplay. Do not kill your host. Do not take control unless granted permission or directed to by admins. Hivemind is :0 (That's Zero, not Oscar)" - - em_call.activate(announce = FALSE) - - message_admins("A new Cortical Borer has spawned at [get_area(loc)]") /mob/living/carbon/cortical_borer/update_icons() if(stat == DEAD) @@ -292,11 +332,15 @@ . += "Injuries: Brute:[round(getBruteLoss())] Burn:[round(getFireLoss())] Toxin:[round(getToxLoss())]" if(host) . += "" + var/health_perc = host.maxHealth / 100 + . += "Host Integrity: [host.health / health_perc]%" if(ishuman(host)) - . += "Host Brain Damage: [host.brainloss]/100" + . += "Host Brain Damage: [host.brainloss]%" else if(isxeno(host)) - // . += "Host Plasma: [plasma_stored]/[plasma_max]" - . += "Host Integrity: [health]/[maxHealth]" + var/mob/living/carbon/xenomorph/xeno_host = host + if(xeno_host.plasma_max) + var/plasma_perc = xeno_host.plasma_max / 100 + . += "Host Plasma: [xeno_host.plasma_stored / plasma_perc]%" /mob/living/carbon/cortical_borer/say(message)//I need to parse the message properly so it doesn't look stupid var/datum/language/parsed_language = parse_language(message) @@ -314,56 +358,6 @@ return . = ..() - -/mob/living/carbon/cortical_borer/Life(delta_time) - ..() - update_canmove() - update_icons() - var/heal_amt = 1 - if(host) - heal_amt = 3 - if(!stat && host.stat != DEAD) - var/mob/living/carbon/human/human_host - if(ishuman(host)) - human_host = host - if(((human_host.chem_effect_flags & CHEM_EFFECT_ANTI_PARASITE) && !human_host.reagents.has_reagent("benzyme")) || human_host.reagents.has_reagent("bcure")) - if(!docile) - if(controlling) - to_chat(host, SPAN_XENOHIGHDANGER("You feel the flow of a soporific chemical in your host's blood, lulling you into docility.")) - else - to_chat(src, SPAN_XENOHIGHDANGER("You feel the flow of a soporific chemical in your host's blood, lulling you into docility.")) - docile = TRUE - else - if(docile) - if(controlling) - to_chat(human_host, SPAN_XENONOTICE("You shake off your lethargy as the chemical leaves your host's blood.")) - else - to_chat(src, SPAN_XENONOTICE("You shake off your lethargy as the chemical leaves your host's blood.")) - docile = FALSE - if(!hibernating && (enzymes < max_enzymes)) - enzymes++ - if(contaminant > 0) - if(hibernating) - contaminant = max(contaminant -= 1, 0) - else - contaminant = max(contaminant -= 0.1, 0) - if(controlling) - if(docile) - to_chat(host, SPAN_WARNING("You are feeling far too docile to continue controlling your host...")) - host.release_control() - return - else - if(contaminant > 0) - if(!luminosity) - SetLuminosity(2) - contaminant = max(contaminant - 0.3, 0) - else - SetLuminosity(0) - if(bruteloss || fireloss) - heal_overall_damage(heal_amt, heal_amt) - if(toxloss) - apply_damage(-(heal_amt/2), TOX) - //################### ABILITIES ###################// /datum/action/innate/borer icon_file = 'icons/mob/hud/actions_borer.dmi' @@ -405,6 +399,7 @@ action_icon_state = "borer_hiding_0" /datum/action/innate/borer/toggle_hide/action_activate() + if(!isborer(owner)) return FALSE var/mob/living/carbon/cortical_borer/B = owner B.hide_borer() @@ -434,6 +429,7 @@ action_icon_state = "borer_control" /datum/action/innate/borer/take_control/action_activate() + if(!isborer(owner)) return FALSE var/mob/living/carbon/cortical_borer/B = owner if(B.hibernating) to_chat(B, SPAN_WARNING("You cannot do that while hibernating!")) @@ -454,6 +450,7 @@ action_icon_state = "borer_leave" /datum/action/innate/borer/leave_body/action_activate() + if(!isborer(owner)) return FALSE var/mob/living/carbon/cortical_borer/B = owner if(B.hibernating) to_chat(B, SPAN_WARNING("You cannot do that while hibernating!")) @@ -465,6 +462,7 @@ action_icon_state = "borer_chems" /datum/action/innate/borer/make_chems/action_activate() + if(!isborer(owner)) return FALSE var/mob/living/carbon/cortical_borer/B = owner if(B.hibernating) to_chat(B, SPAN_WARNING("You cannot do that while hibernating!")) @@ -476,6 +474,7 @@ action_icon_state = "borer_scan" /datum/action/innate/borer/scan_chems/action_activate() + if(!isborer(owner)) return FALSE var/mob/living/carbon/cortical_borer/B = owner if(B.hibernating) to_chat(B, SPAN_WARNING("You cannot do that while hibernating!")) @@ -517,21 +516,3 @@ B.hibernate() button.overlays.Cut() button.overlays += image('icons/mob/hud/actions_borer.dmi', button, "borer_sleeping_[B.hibernating]") - -/mob/living/carbon/cortical_borer/MouseDrop(atom/over_object) - if(!CAN_PICKUP(usr, src)) - return ..() - var/mob/living/carbon/H = over_object - if(!istype(H) || !Adjacent(H) || H != usr) return ..() - - if(H.a_intent == INTENT_HELP) - get_scooped(H) - return - else - return ..() - -/mob/living/carbon/cortical_borer/get_scooped(mob/living/carbon/grabber) - if(stat != DEAD) - to_chat(grabber, SPAN_WARNING("You probably shouldn't pick that thing up while it still lives.")) - return - ..() diff --git a/code/modules/borer/borer_procs.dm b/code/modules/borer/borer_procs.dm index 96df31acc7d5..fc48062c5b59 100644 --- a/code/modules/borer/borer_procs.dm +++ b/code/modules/borer/borer_procs.dm @@ -9,7 +9,7 @@ var/list/options = list("Communicating","Contaminant & Enzymes") if(!host) options += list("Infecting a host") - else if(controlling) + else if(borer_flags_status & BORER_STATUS_CONTROLLING) target = host options += list("Captive Host", "Releasing Control", "Reproducing") else if(isxeno(host)) @@ -23,9 +23,9 @@ switch(choice) if("Infecting a host") var/possible_targets = "" - if(infect_humans) possible_targets += "\nHumans" - if(infect_xenos) possible_targets += "\nXenos" - if(infect_yautja) possible_targets += "\nYautja" + if(borer_flags_targets & BORER_TARGET_HUMANS) possible_targets += "\nHumans" + if(borer_flags_targets & BORER_TARGET_XENOS) possible_targets += "\nXenos" + if(borer_flags_targets & BORER_TARGET_YAUTJA) possible_targets += "\nYautja" if(!possible_targets) possible_targets += "No one." help_message = "Infecting a host is the first major step for a borer to complete.\n\nThis is done by getting close to a potential host (This can be Human, Xeno or Yautja depending on settings controlled by admins) and clicking the Infest button in the top left of your screen.\n\nYour host will need to keep still for you to do this, and it's rare that they do so; for this reason you have Dominate Victim, which will allow you to temporarily stun a target.\n\nNote: Dominate is not sufficient to keep them down for 100% of the time it takes to infect however, so be careful with it.\n\nWhilst inside a host, and NOT in direct control, you can be detected by body scanners but otherwise are hidden from everyone but your host and other borers.\nYou can currently infect: [possible_targets]" if("Communicating") @@ -51,6 +51,19 @@ alert(target, help_message, choice, "Ok") //############# Physical Interaction Procs ############# +/mob/living/carbon/cortical_borer/proc/summon() + var/datum/emergency_call/custom/em_call = new() + em_call.name = real_name + em_call.mob_max = 1 + em_call.players_to_offer = list(src) + em_call.owner = null + em_call.ert_message = "A new Cortical Borer has been birthed!" + em_call.objectives = "Create enjoyable Roleplay. Do not kill your host. Do not take control unless granted permission or directed to by admins. Hivemind is :0 (That's Zero, not Oscar)" + + em_call.activate(announce = FALSE) + + message_admins("A new Cortical Borer has spawned at [get_area(loc)]") + /mob/living/carbon/cortical_borer/UnarmedAttack(atom/A) if(istype(A, /obj/structure/ladder)) A.attack_hand(src) @@ -60,21 +73,48 @@ /atom/proc/attack_borer(mob/living/carbon/cortical_borer/user) return +/mob/living/carbon/cortical_borer/MouseDrop(atom/over_object) + if(!CAN_PICKUP(usr, src)) + return ..() + var/mob/living/carbon/H = over_object + if(!istype(H) || !Adjacent(H) || H != usr) return ..() + + if(H.a_intent == INTENT_HELP) + get_scooped(H) + return + else + return ..() + +/mob/living/carbon/cortical_borer/get_scooped(mob/living/carbon/grabber) + if(stat != DEAD) + to_chat(grabber, SPAN_WARNING("You probably shouldn't pick that thing up while it still lives.")) + return + ..() //Brainslug scans the reagents in a target's bloodstream. /mob/living/carbon/human/attack_borer(mob/M) borerscan(M, src) +/mob/living/carbon/xenomorph/attack_borer(mob/M) + borerscan(M, src) /proc/borerscan(mob/living/user, mob/living/M) if(ishuman(M)) - var/mob/living/carbon/human/H = M - if(H.reagents) - if(H.reagents.reagent_list.len) + var/mob/living/carbon/human/human_target = M + if(human_target.reagents) + if(human_target.reagents.reagent_list.len) to_chat(user, SPAN_XENONOTICE("Subject contains the following reagents:")) - for(var/datum/reagent/R in H.reagents.reagent_list) + for(var/datum/reagent/R in human_target.reagents.reagent_list) to_chat(user, "[R.overdose != 0 && R.volume >= R.overdose && !(R.flags & REAGENT_CANNOT_OVERDOSE) ? SPAN_WARNING("OD: ") : ""] [round(R.volume, 1)]u [R.name]") else to_chat(user, SPAN_XENONOTICE("Subject contains no reagents.")) + if(isxeno(M)) + var/mob/living/carbon/xenomorph/xeno_target = M + to_chat(user, SPAN_XENONOTICE("Subject status as follows:")) + var/health_perc = xeno_target.maxHealth / 100 + to_chat(user, SPAN_XENONOTICE("Subject is at [xeno_target.health / health_perc]% bio integrity.")) + if(xeno_target.plasma_max) + var/plasma_perc = xeno_target.plasma_max / 100 + to_chat(user, SPAN_XENONOTICE("Subject has [xeno_target.plasma_stored / plasma_perc]% bio plasma.")) //Brainslug scuttles under a door, same code as used by xeno larva. /obj/structure/machinery/door/airlock/attack_borer(mob/living/carbon/cortical_borer/M) @@ -168,11 +208,11 @@ return FALSE if(ishuman(target)) var/mob/living/carbon/human/human_target = target - if(isspecieshuman(human_target) && !infect_humans)//Can it infect humans? Normally, yes. + if(isspecieshuman(human_target) && !(borer_flags_targets & BORER_TARGET_HUMANS))//Can it infect humans? Normally, yes. return FALSE - else if(isspeciesyautja(human_target) && !infect_yautja)//Can it infect yautja? Normally, no. + else if(isspeciesyautja(human_target) && !(borer_flags_targets & BORER_TARGET_YAUTJA))//Can it infect yautja? Normally, no. return FALSE - if(isxeno(target) && !infect_xenos)//Can it infect xenos? Normally, no. + if(isxeno(target) && !(borer_flags_targets & BORER_TARGET_XENOS))//Can it infect xenos? Normally, no. return FALSE if(target.stat != DEAD && Adjacent(target) && !target.has_brain_worms()) return TRUE @@ -271,7 +311,7 @@ /mob/living/carbon/cortical_borer/proc/let_go() if(!host || !src || QDELETED(host) || QDELETED(src)) return FALSE - if(!leaving || controlling) + if(!leaving || borer_flags_status & BORER_STATUS_CONTROLLING) return FALSE if(stat) to_chat(src, SPAN_XENOWARNING("You cannot release a target in your current state.")) @@ -286,7 +326,7 @@ /mob/living/carbon/cortical_borer/proc/leave_host() if(!host) return FALSE - if(controlling) + if(borer_flags_status & BORER_STATUS_CONTROLLING) detach() give_new_actions(ACTION_SET_HOSTLESS) @@ -342,7 +382,7 @@ return TRUE /mob/living/carbon/cortical_borer/proc/assume_control() - if(!host || !src || controlling) + if(!host || !src || borer_flags_status & BORER_STATUS_CONTROLLING) return FALSE if(!bonding) return FALSE @@ -389,7 +429,7 @@ host.lastKnownIP = s2h_ip bonding = FALSE - controlling = TRUE + borer_flags_status |= BORER_STATUS_CONTROLLING give_new_actions(ACTION_SET_CONTROL) host.med_hud_set_status() @@ -401,7 +441,7 @@ //Captive mind reclaims their body. /mob/living/captive_brain/proc/return_control(mob/living/carbon/cortical_borer/B) - if(!B || !B.controlling || !resisting_control) + if(!B || !(B.borer_flags_status & BORER_STATUS_CONTROLLING) || !resisting_control) return FALSE B.host.adjustBrainLoss(rand(5,10)) to_chat(src, SPAN_HIGHDANGER("With an immense exertion of will, you regain control of your body!")) @@ -428,10 +468,10 @@ log_debug(EXCEPTION("Missing borer or missing host brain upon borer release."), src) /mob/living/carbon/cortical_borer/proc/detach() - if(!host || !controlling) + if(!host || !(borer_flags_status & BORER_STATUS_CONTROLLING)) return FALSE - controlling = FALSE + borer_flags_status &= ~BORER_STATUS_CONTROLLING reset_view(null) get_host_actions() @@ -470,7 +510,7 @@ if(!(host && loc == host)) log_debug("Borer ([key_name(src)]) called host_death without being inside a host!") return FALSE - if(controlling) + if(borer_flags_status & BORER_STATUS_CONTROLLING) detach() to_chat(src, SPAN_XENOHIGHDANGER("You release your proboscis and flee as the psychic shock of your host's death washes over you!")) if(perma) @@ -593,7 +633,7 @@ T.add_vomit_floor() B.contaminant = 0 var/repro = max(B.can_reproduce - 1, 0) - new /mob/living/carbon/cortical_borer(T, B.generation + 1, TRUE, repro, B.infect_humans, B.infect_xenos, B.infect_yautja) + new /mob/living/carbon/cortical_borer(T, B.generation + 1, TRUE, repro, B.borer_flags_targets) return TRUE else to_chat(src, SPAN_XENONOTICE("You need at least [BORER_LARVAE_COST] enzymes to reproduce!")) @@ -772,7 +812,7 @@ C = new test() break - if(!C || !host || controlling || !src || stat) + if(!C || !host || (borer_flags_status & BORER_STATUS_CONTROLLING) || !src || stat) return FALSE var/datum/reagent/R = chemical_reagents_list[C.chem_id] if(enzymes < C.cost) diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index aff43fc0fe6f..cf2ce6dd29a2 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -137,7 +137,7 @@ . += "Evacuation: [eta_status]" var/mob/living/carbon/cortical_borer/B = has_brain_worms() - if(B && B.controlling) + if(B && (B.borer_flags_status & BORER_STATUS_CONTROLLING)) var/CR = "Yes" if(!B.can_reproduce) diff --git a/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm b/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm index 2f3bd91b8f93..8341c8e4af2c 100644 --- a/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm +++ b/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm @@ -145,7 +145,7 @@ . += "Hive Orders: -" var/mob/living/carbon/cortical_borer/B = has_brain_worms() - if(B && B.controlling) + if(B && (B.borer_flags_status & BORER_STATUS_CONTROLLING)) var/CR = "Yes" if(!B.can_reproduce) diff --git a/code/modules/reagents/chemistry_properties/prop_positive.dm b/code/modules/reagents/chemistry_properties/prop_positive.dm index 17a98b1f9d85..3e2907e3cfd0 100644 --- a/code/modules/reagents/chemistry_properties/prop_positive.dm +++ b/code/modules/reagents/chemistry_properties/prop_positive.dm @@ -509,7 +509,7 @@ M.apply_damage(potency, TOX) var/mob/living/carbon/cortical_borer/player_2 = M.has_brain_worms() if(player_2) - if(player_2.controlling) + if(player_2.borer_flags_status & BORER_STATUS_CONTROLLING) player_2.detach() to_chat(src, SPAN_HIGHDANGER("You relinquish control as the unknown chemical overwhelms you!")) diff --git a/code/modules/surgery/brainworm.dm b/code/modules/surgery/brainworm.dm index 3a379605e36b..7ad8223a0fc8 100644 --- a/code/modules/surgery/brainworm.dm +++ b/code/modules/surgery/brainworm.dm @@ -63,6 +63,7 @@ user.count_niche_stat(STATISTICS_NICHE_SURGERY_LARVA) to_chat(parasite, SPAN_HIGHDANGER("You are ripped forcibly from your host's head!")) parasite.leave_host() + parasite.apply_damage(30, BRUTE) log_interact(user, target, "[key_name(user)] removed a parasite from [key_name(target)]'s head with [tool ? "\the [tool]" : "their hands"], ending [surgery].") diff --git a/colonialmarines.dme b/colonialmarines.dme index 211c667398c6..441e53be7292 100644 --- a/colonialmarines.dme +++ b/colonialmarines.dme @@ -33,6 +33,7 @@ #include "code\__DEFINES\atmospherics.dm" #include "code\__DEFINES\autolathe.dm" #include "code\__DEFINES\blood.dm" +#include "code\__DEFINES\borer_defines.dm" #include "code\__DEFINES\bsql.config.dm" #include "code\__DEFINES\bullet_traits.dm" #include "code\__DEFINES\callback.dm" @@ -1378,7 +1379,6 @@ #include "code\modules\asset_cache\assets\tgui.dm" #include "code\modules\asset_cache\assets\vending.dm" #include "code\modules\asset_cache\transports\asset_transport.dm" -#include "code\modules\borer\_defines.dm" #include "code\modules\borer\borer.dm" #include "code\modules\borer\borer_chemicals.dm" #include "code\modules\borer\borer_html.dm" diff --git a/icons/mob/hud/actions_borer.dmi b/icons/mob/hud/actions_borer.dmi index d1098b87d803641b4ca6ee29871f2c6dec5717ae..cdf8d9617e138862e6531e4d9feeb99ab4041ada 100644 GIT binary patch literal 2693 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 literal 2187 zcmV;62z2*}P)C0001ZP)t-sz`(#! zIbbj%I$1zwVlP;HJZ~r*FgzVWNgqil7%U+WBLMZ=HmsP}mH^CV03>f>Hybe*3m7*b zJef1hNGng5Nr+1%QLIy%HX%DjAwFjc3Zw!8wo?G6tTx&80K?ZGmYXztS4((GOI27I zY;Pq0%w~mUX28I}vW^k-00001bW%=J06^y0W&i*H$$C^+bVOxyV{&P5bZKvH004NL zm6EXvgFp<1XYVNv-Aiq|b}346sIMSbjb1?SBsulJ4e1R_^iN59cqSV== zP%{vtclxR?hog!XY=T|G`J^HX*x9fJ4ko}_#!pW`Rc&G5SVavIuiaEdEtvtf`itc- zIU%}O<|8o&i1jjjcj!WJ8JU3J`tr$U@yB;QQ6bW!dB z#yvit0jZINe*BM;LAS~?{=Q?P^D=vX_Y1@+-nZ9j$_M}e2JA^hK~#90?VF2wqDl;a zWe18NcC>3*JBZNj<^3P=BohYb4(hgN&v(dftr|>zGMRy-&T6&DB7cP>KKb!|`c=Y6 z(lpDm;W>M7^@jXG{RJRjuh;390i;So0BKRI*F~8I3~cTM!0?i!A%GkrkQZ6lLA7C< zD&%0heZ*gnkK3(JfALQQ9)Jw}M*%1j)h!W7vmyS9zz0y~5P%ZKD2qGbu7gKBef&`Y zZ~)TvAjseV$mGvTDJkOm0T7;5w?$yRQvw7IKuS4){_|7@%;z90Hk&G#149Ic09blB z|J`nnq6l;Vn*1386h)p<%D4`y%?6UBoXQa|V5FKYxOEcz_VtLy&_10yDJ4hXUt; zGX$LDEb*=rPlv++5C-?pQc4S8r$xX80RMGfD9LLeaE3rmQ_y;H0K7k;29J4u{)O^< zD1!id2OvqaW1Y(kC}0}o_0c?FoCP>bGR`YI0^lE21lka%Yk4UcS7LaXY7t(^mB#=;0vEtQumBEpJ;NM)k^~w6EVAJ6axGfR ztFi|0;{jy^kgs$?(%Yixi?Yx)Fc61>rq9UdDDs64u*f2dEV9U7(;tAFt+DR-ivEBQ zP1AVv7Xa)J@SuTUeLouj?Tat~r~UvB7zo$*q(4mo2(mB20Ms9FZ@H%G`!NE|iJh7- z0NHPX02TEI`~b1~-W-Af!PPfpKLSAg0nY8)?y36T&H)hsAp1hF#sQGYUr~PmasW$@ zE9!f*f6#a$Am3}qKH!PQ2H=zfvOj>&f#8bz{?b1Zr2+9FzAD;t(4nUK?;#8i? z7reTnzE>eWo&5a+f@%cWmoG%41IY8QMgStXqP~{^O%v=N2+}J6BCwN?^RYj`JxwUA z?>PtW@Zgl!2_;^ZLj+EN28Ix$ePJi*s^Z{Z>kr^Ou!bO7-^&OH5$qo{0<@ZaoCn4b zRN&v}4*&rV132~lRR5p>|Au`aFo%FwR(}9uaz%M<_78+;>>&`1WglW7=fEP1EV9TVUsC@+ z%>VrT^?%!*JAAM2+OF-d{`~y_UDuxFn?39MbN~GSecN`OQuJMaGr)GM{_^WL@CxG7 zClLE{!V-d@82COPu&)jvrT>r5D9N9@Z3@J;#(#_+;2fKGq2;o;TNzX3tp!TIPniGynWV0vU-{BLb~jepuUi0F-_3z&!Tv2Y6#|3ZPRV@B(}=Kft|1 zz}S2LZib+}Y6$L4LU`w`eV56P*?7J_3GbYSOcJONv~ChkPs2MGA_P!lkjO!3ZT-|j z#O_&&nKAyDklpo5F)44m7?F2|bD(3L{C&&C$dqd?$Cr0uF_zMuzx95A-ocv!a4qHB z{R;p$>u1*YVZB#<#pf4UWRXP{`TFk%OagrM#pZtk5Z-?zX5^y%5N>hf=L+Je{coBCyN7Yg5R{_k?#*o1I!+R z@%-HgMD`!d0GO48ae&D82j&3GnTDqLW Date: Thu, 6 Apr 2023 20:39:14 +0100 Subject: [PATCH 18/25] Gen1 buff chems and gen1 fix --- code/modules/borer/borer.dm | 9 +++++++-- code/modules/borer/borer_chemicals.dm | 16 +++++++++++++++- code/modules/borer/borer_procs.dm | 19 +++++++++++-------- 3 files changed, 33 insertions(+), 11 deletions(-) diff --git a/code/modules/borer/borer.dm b/code/modules/borer/borer.dm index 832a96aa35f3..80240f9eff11 100644 --- a/code/modules/borer/borer.dm +++ b/code/modules/borer/borer.dm @@ -112,7 +112,7 @@ var/max_contaminant = 120 //Decreases through hibernation or reproduction. var/hibernating = FALSE //Usable inside a host, but not when controlling. Allows clearing of impurities. - var/mob/living/carbon/host // Human host for the brain worm. + var/mob/living/carbon/host // Carbon host for the brain worm. var/truename // Name used for brainworm-speak. var/mob/living/captive_brain/host_brain // Used for swapping control of the body back and forth. var/docile = FALSE // Anti-Parasite or Anti-Enzyme chemicals can stop borers from acting. @@ -173,8 +173,11 @@ can_reproduce = reproduction borer_flags_targets = new_targets give_new_actions(ACTION_SET_HOSTLESS) - //GrantBorerActions() GiveBorerHUD() + if(generation == 1) + maxHealth = maxHealth + (maxHealth / 2) + max_enzymes = max_enzymes + (max_enzymes / 2) + max_contaminant = max_contaminant + (max_contaminant / 2) if((!is_admin_level(z)) && ERT) summon() @@ -216,6 +219,8 @@ docile = FALSE if(!hibernating && (enzymes < max_enzymes)) enzymes++ + if(generation == 1) + enzymes++ if(contaminant > 0) if(hibernating) contaminant = max(contaminant -= 1, 0) diff --git a/code/modules/borer/borer_chemicals.dm b/code/modules/borer/borer_chemicals.dm index ddb4b775ff5f..09e03fff7567 100644 --- a/code/modules/borer/borer_chemicals.dm +++ b/code/modules/borer/borer_chemicals.dm @@ -103,9 +103,23 @@ chem_id = "antineurotoxin" desc = "A bioagent that counteracts neurotoxins." cost = 100 - category = BORER_CAT_PUNISH + category = BORER_CAT_STIM +/datum/borer_chem/human/chloralhydrate + chem_name = "Chloral Hydrate" + chem_id = "chloralhydrate" + desc = "A powerful sedative which causes near instant sleepiness, but can be deadly in large quantities." + cost = 125 + quantity = 5 + category = BORER_CAT_PUNISH +/datum/borer_chem/human/potassium_chlorophoride + chem_name = "Potassium Chlorophoride" + chem_id = "potassium_chlorophoride" + desc = "A powerful chemical based on Potassium Chloride that causes instant cardiac arrest." + cost = 250 + quantity = 3 + category = BORER_CAT_PUNISH //Yautja chemicals /datum/borer_chem/yautja/thwei diff --git a/code/modules/borer/borer_procs.dm b/code/modules/borer/borer_procs.dm index fc48062c5b59..f37b62af89fb 100644 --- a/code/modules/borer/borer_procs.dm +++ b/code/modules/borer/borer_procs.dm @@ -38,7 +38,7 @@ var/capability = "able" if(!can_reproduce) capability = "forbidden" else if((enzymes < BORER_LARVAE_COST)) capability = "unable" - help_message = "Reproduction will take [BORER_LARVAE_COST] enzymes and direct control of your host.\n\nWhen in direct control you can use the Reproduce ability to spit out a new borer.\nMake sure to do this in a safe place, the new borer will not have a player to start with.\n\nYou are currently [capability] to reproduce." + help_message = "Reproduction will take a minimum of [BORER_LARVAE_COST] enzymes and direct control of your host.\n\nWhen in direct control you can use the Reproduce ability to spit out a new borer.\nMake sure to do this in a safe place, the new borer will not have a player to start with.\n\nYou are currently [capability] to reproduce." if("Assuming Control") help_message = "Assuming control will put you in direct control of your host, acting as if you are their player.\n\nYour host will be disassociated with their body, and trapped in their own mind unable to speak to anyone but you.\nWhile in this state you are unable to make use of the hivemind.\n\nYour host can resist your control and regain their body however this can cause brain damage in humanoids.\nYou must assume control to reproduce.\n\nNote: Whilst in direct control of your host medical HUDs will detect you.\n\n\nIMPORTANT: While in direct control of a mob you MUST NOT perform antag actions unless you have permission from staff." if("Contaminant & Enzymes") @@ -628,7 +628,10 @@ if(B.enzymes >= BORER_LARVAE_COST) to_chat(src, SPAN_XENOWARNING("Your host twitches and quivers as you rapdly excrete a larva from your sluglike body.")) visible_message(SPAN_WARNING("[src] heaves violently, expelling a rush of vomit and a wriggling, sluglike creature!")) - B.enzymes = 0 + if(B.generation == 1) + B.enzymes -= BORER_LARVAE_COST + else + B.enzymes = 0 var/turf/T = get_turf(src) T.add_vomit_floor() B.contaminant = 0 @@ -675,14 +678,14 @@ var/chem = initial(C.chem_id) var/datum/reagent/R = chemical_reagents_list[chem] if(R) - content += "" + content += "" else for(var/datum in subtypesof(/datum/borer_chem/human)) var/datum/borer_chem/C = datum var/chem = initial(C.chem_id) var/datum/reagent/R = chemical_reagents_list[chem] if(R) - content += "" + content += "" content += "
[initial(C.quantity)] units of [R.name] ([initial(C.cost)] Enzymes)

[initial(C.desc)]

[initial(C.quantity)] units of [C.chem_name] ([initial(C.cost)] Enzymes)

[initial(C.desc)]

[initial(C.quantity)] units of [R.name] ([initial(C.cost)] Enzymes)

[initial(C.desc)]

[initial(C.quantity)] units of [C.chem_name] ([initial(C.cost)] Enzymes)

[initial(C.desc)]

" @@ -797,7 +800,7 @@ /mob/living/carbon/cortical_borer/Topic(href, href_list, hsrc) if(href_list["borer_use_chem"]) locate(href_list["src"]) - if(!istype(src, /mob/living/carbon/cortical_borer)) + if(!isborer(src)) return FALSE if(docile) to_chat(src, SPAN_XENOWARNING("You are feeling far too docile to do that.")) @@ -816,13 +819,13 @@ return FALSE var/datum/reagent/R = chemical_reagents_list[C.chem_id] if(enzymes < C.cost) - to_chat(src, SPAN_XENOWARNING("You need [C.cost] enzymes stored to secrete [R.name]!")) + to_chat(src, SPAN_XENOWARNING("You need [C.cost] enzymes stored to secrete [C.chem_name]!")) return FALSE var/contamination = round(C.cost / 10) if(C.impure && ((contaminant + contamination) > max_contaminant)) - to_chat(src, SPAN_XENOWARNING("You are too contaminated to secrete [R.name]!")) + to_chat(src, SPAN_XENOWARNING("You are too contaminated to secrete [C.chem_name]!")) return FALSE - to_chat(src, SPAN_XENONOTICE("You squirt a measure of [R.name] from your reservoirs into [host]'s bloodstream.")) + to_chat(src, SPAN_XENONOTICE("You squirt a measure of [C.chem_name] from your reservoirs into [host]'s bloodstream.")) contaminant += contamination host.reagents.add_reagent(C.chem_id, C.quantity) enzymes -= C.cost From 13070fa8fc822a36d5f5d96345de096a4746e22e Mon Sep 17 00:00:00 2001 From: forest2001 Date: Thu, 6 Apr 2023 21:01:01 +0100 Subject: [PATCH 19/25] icon changes --- code/modules/borer/borer.dm | 10 +++++----- icons/mob/hud/actions_borer.dmi | Bin 2693 -> 3118 bytes 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/code/modules/borer/borer.dm b/code/modules/borer/borer.dm index 80240f9eff11..d180567d2a8d 100644 --- a/code/modules/borer/borer.dm +++ b/code/modules/borer/borer.dm @@ -385,7 +385,7 @@ /datum/action/innate/borer/talk_to_host name = "Converse with Host" - action_icon_state = "borer_whisper" + action_icon_state = "borer_talk" /datum/action/innate/borer/talk_to_host/action_activate() var/mob/living/carbon/cortical_borer/B = owner @@ -413,7 +413,7 @@ /datum/action/innate/borer/talk_to_borer name = "Converse with Borer" - action_icon_state = "borer_whisper" + action_icon_state = "borer_talk" /datum/action/innate/borer/talk_to_borer/action_activate() var/mob/living/carbon/cortical_borer/B = owner.has_brain_worms() @@ -422,7 +422,7 @@ /datum/action/innate/borer/talk_to_brain name = "Converse with Trapped Mind" - action_icon_state = "borer_whisper" + action_icon_state = "borer_talk" /datum/action/innate/borer/talk_to_brain/action_activate() var/mob/living/carbon/cortical_borer/B = owner.has_brain_worms() @@ -464,7 +464,7 @@ /datum/action/innate/borer/make_chems name = "Secrete Chemicals" - action_icon_state = "borer_chems" + action_icon_state = "borer_human_chems" /datum/action/innate/borer/make_chems/action_activate() if(!isborer(owner)) return FALSE @@ -476,7 +476,7 @@ /datum/action/innate/borer/scan_chems name = "Scan Chemicals" - action_icon_state = "borer_scan" + action_icon_state = "borer_human_scan" /datum/action/innate/borer/scan_chems/action_activate() if(!isborer(owner)) return FALSE diff --git a/icons/mob/hud/actions_borer.dmi b/icons/mob/hud/actions_borer.dmi index cdf8d9617e138862e6531e4d9feeb99ab4041ada..aab12b0b5b6aea52cf24f0f855d80e9fe5dba48f 100644 GIT binary patch delta 3063 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 FV1kMww#NVf delta 2635 zcmV-R3bggE7=;xjiBL{Q4GJ0x0000DNk~Le0001>0001>2m=5B0K5^G9{>OVc#$O{ zkxCu|ByVDoXdZuJFIcuy0GTt)rmQyE^#H@yAC{XmRahBpZzKsB+>QVM00DGTPE!Ct z=GbNc00ARfFDZ*Bkpc$}q_!ES>v42I9}6j6Jh+HKbzXhCfx3Q}Rx zc0vr?#u6mRN$Kkk80ukCMQCrf^4a{f zy2x!s6#{>1J;{Cr;|t8y)5kDx!$Y3V{!Emk%m_4EH0^o73&STmUMOiuRhCV!AaB8v zHH!L6Eop*=RCy_Ev+D|=nKK@@0NVj+vEM(|`A7V2_9Y7nZSwJJ8DL;lifk{LQ;5Tgo7~e=>hE>pzAECq)sh^mKxXmyYkXLXxM1J`jzJ|~; z2b&DKRc09CF>_FpU-CUaH-tk#-Dz9{xS6_0togTf<({9BkXLd$4G@;`5P|eLVFOplgv0$6yL=dF^x%@Nek74Rn$Dd_+o)7&aHVc$S+N;L-0QZN(AyWX2rT}6* zoea2f_(_(gheH}?Qvs=nrxw6BFk62GqYber3)vnjuut>JfFfR%R^$r=oJ2GZagC_@ zbIczA7>RfjAcFk_lZci%NDYAT9tsd=8Obex7X4AgQ%WKYAVU2l^_MpTAQxRCO$GVl z&0}sb2eW*nKFD)MA|8Hy8YzGcUqh$?OwvuO9`*d0n1h&qkB@^d2N62qfzz9ti1_Hk05gODK(oL!9NO3C3dq2aPn# zPUwl#6a2&P=_xZ-P}8U>RpO~qLE<>oQQl@Y2pA13m>rKI{|P_v(eZz19^wwG1k}Xq z0PULv(V#aQ&Hs~}j>q`pBNnr0j}MQ>Q|1q-ji)-mt|@ac+L-*OBb?vPpD=wOef)VQ zS$sU5#sI?kts74}@&15=9bw1M_kbU81f1lb=MU(LFkb$Ed=J=*r)~Tgz#d|6k&wXw zkg%a}ZuxmC(r;9u{h@ zNq+Z5;A#_hZzHZo`zIQ4kiIvE{^ta&&;N`iNiy6F%mL`wjQ9hhKF1V5qxsIkxd55a z=h%$^&HAD@;9hs#J7)zA`l2sjBJ{Z%0*Xp1>XRr{MPGCS zYV5CqKaDMCRb8LjZ=e=1l>x)j~d%&Lj#r*ZX5xW_a zzUVaGnfv-2^Vau9ZTa>3qSJKx9Bb!bFrZ6c46`L%c3OXJ`4{d>mp&f$uzTC$7s1`z zh|BKYh+TH;`;R^R;ebSA6aKt!+IGx;JH@0K09^iC1F}RK(*n5smm34N#}@=c0GA&v zzmQ9QBS4na<0lCQ04{%PHR5DI!(}C7huV-q1L*K~EeizHUYN7PSsD;%03CjATtCbM zE2x(?pgw=+^Cy}l=mWU?f-k*X@)_o!P8?$L_(-4ywEM*pxlGXy{p2+O_>^VDN>W6-dqzmxr z0Mz;U8kwda`n8$P6yuZZpa9zaH6iMUS(e|RQHOs6LVs!W!#r#bx&Y4(K%E~K3AC9D z0_I^AGy$F+fOfyA8k~qPmppWl=l~C_1>naY0J!}1R;Fun0>T0Lsa72R%%3*un1^33 zlFLLu=awIKCvd=aFL24$&8W#<&vAGF0w9}j!j z!ybPYZd!frhr>Uk{^!&O*F|wH@dRC!MOjqJ$@k z(o>by;sDn$w%0XT3rJBzT>|Y}{&?PU)?T7MC& zVO6b5(Qee=(DK%2-0!;e(iLy@Lm&LA$eI_rs7p9VP{+6LZny3GYrgjG+Ba=H;k`l= zFa7+QUGYN&3)!AYQEC9?+wTD)QQm%U)!zOKh(ve$uZ%BCBvj+2Kj&{Gq4;qQ{+54$ zr7WQq@R`^F-^pzN3HhqN`XwwDHN^0f-^CYSytrDf${f7+A|aemmatHUnB2R~a6mc6 zYd*-uAi;ebHR;ViTrlOrPIa7);2JJT$-ggavp^bylO4D6x~T1z_z`Xj0gc Date: Thu, 11 May 2023 22:48:17 +0100 Subject: [PATCH 20/25] rebase --- code/__DEFINES/chemistry.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/__DEFINES/chemistry.dm b/code/__DEFINES/chemistry.dm index d3720c7f3d0f..05a2b32cbc52 100644 --- a/code/__DEFINES/chemistry.dm +++ b/code/__DEFINES/chemistry.dm @@ -83,7 +83,7 @@ #define CHEM_EFFECT_HYPER_THROTTLE (1<<2) //universal understand but not speech #define CHEM_EFFECT_ORGAN_STASIS (1<<3) //peri stabiliser #define CHEM_EFFECT_NO_BLEEDING (1<<4) //replacement for quickclot -#define CHEM_EFFECT_ANTI_PARASITE (1<<4) //PROPERTY_ANTIPARASITIC +#define CHEM_EFFECT_ANTI_PARASITE (1<<5) //PROPERTY_ANTIPARASITIC //Blood plasma From f4a57dacd66288dcb53f4eb544014618898b023c Mon Sep 17 00:00:00 2001 From: forest2001 Date: Sun, 14 May 2023 16:05:05 +0100 Subject: [PATCH 21/25] I will kill prettier. This is stupid. --- tgui/packages/tgui/interfaces/Orbit/index.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tgui/packages/tgui/interfaces/Orbit/index.tsx b/tgui/packages/tgui/interfaces/Orbit/index.tsx index 2856598e97b8..653d0a3b9db0 100644 --- a/tgui/packages/tgui/interfaces/Orbit/index.tsx +++ b/tgui/packages/tgui/interfaces/Orbit/index.tsx @@ -118,7 +118,11 @@ const ObservableContent = (props, context) => { return ( - + From 91b36043cc0520ab568804eae506e50a974ec548 Mon Sep 17 00:00:00 2001 From: forest2001 Date: Tue, 25 Jul 2023 03:19:10 +0100 Subject: [PATCH 22/25] hud fix --- icons/mob/hud/hud.dmi | Bin 19038 -> 19317 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/icons/mob/hud/hud.dmi b/icons/mob/hud/hud.dmi index f8ea98d01e538e7f19a983980e265422f4c35146..261252c2a571e204add9e261951f44c85eeaf34f 100644 GIT binary patch literal 19317 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 literal 19038 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 From a710b460bf434de44464188f976faf986060a47d Mon Sep 17 00:00:00 2001 From: forest2001 Date: Tue, 25 Jul 2023 03:22:14 +0100 Subject: [PATCH 23/25] fixes --- .../mob/living/simple_animal/simple_animal.dm | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index 62f6f2eba822..e515191cd3ff 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -342,14 +342,6 @@ return (0) return (1) -//Call when target overlay should be added/removed -/mob/living/simple_animal/update_targeted() - if(!targeted_by && target_locked) - QDEL_NULL(target_locked) - overlays = null - if (targeted_by && target_locked) - overlays += target_locked - /mob/living/simple_animal/say(message) if(stat) return @@ -361,14 +353,12 @@ if(stat) return - var/verb = "says" - if(speak_emote.len) verb = pick(speak_emote) message = capitalize(trim_left(message)) - ..(message, speaking, verb, nolog = !ckey) //if the animal has a ckey then it will log the message + ..(message) //if the animal has a ckey then it will log the message /mob/living/simple_animal/update_canmove() . = ..() From ce3bfb2cb8f48a5723bd9b076d6e626afa0e5a09 Mon Sep 17 00:00:00 2001 From: forest2001 Date: Tue, 25 Jul 2023 03:23:39 +0100 Subject: [PATCH 24/25] fixes the fixes --- code/modules/mob/living/simple_animal/simple_animal.dm | 3 --- 1 file changed, 3 deletions(-) diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index e515191cd3ff..3e864ecc12ae 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -353,9 +353,6 @@ if(stat) return - if(speak_emote.len) - verb = pick(speak_emote) - message = capitalize(trim_left(message)) ..(message) //if the animal has a ckey then it will log the message From 37a31d5db1983876b04bbcf65a328ede3c7a9227 Mon Sep 17 00:00:00 2001 From: forest2001 Date: Tue, 22 Aug 2023 01:46:10 +0100 Subject: [PATCH 25/25] hudconflict --- icons/mob/hud/hud.dmi | Bin 19237 -> 19488 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/icons/mob/hud/hud.dmi b/icons/mob/hud/hud.dmi index c9e4c0c6c23d5eb406380528bdc9643513b31553..125b21e2d53b15b1cd51b07d688a0d5a7cacfeed 100644 GIT binary patch literal 19488 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 R4IBVs@O1TaS?83{1OS6FYJva& literal 19237 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 E0M`GYj{pDw