diff --git a/code/__DEFINES/borer_defines.dm b/code/__DEFINES/borer_defines.dm new file mode 100644 index 000000000000..21db1f9cb127 --- /dev/null +++ b/code/__DEFINES/borer_defines.dm @@ -0,0 +1,63 @@ +#define JOB_BRAINWORM "Brainworm" + +/// Chemical categories +#define BORER_CAT_HEAL "Medicines" +#define BORER_CAT_PUNISH "Motivators" +#define BORER_CAT_STIM "Stimulants" +#define BORER_CAT_SELF "Self Protection" +#define BORER_CAT_REPLICATED "Replicated" + +///Amount of chemicals needed for a borer to reproduce, provided reproduction is toggled. +#define BORER_LARVAE_COST 400 +/// Amount of chemicals needed for a borer to replicate a chemical. +#define BORER_REPLICATE_COST 350 + +#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) +/// Sleeps to purify contaminant +#define BORER_ABILITY_HIBERNATING (1<<2) +/// Hiding or not. (Changes layer) +#define BORER_ABILITY_HIDE (1<<3) + +DEFINE_BITFIELD(borer_flags_actives, list( + "PROCESS_INFESTING" = BORER_PROCESS_INFESTING, + "PROCESS_BONDING" = BORER_PROCESS_BONDING, + "ACTIVE_HIBERNATING" = BORER_ABILITY_HIBERNATING, + "ACTIVE_HIDING" = BORER_ABILITY_HIDE, +)) + +///Borer is in control of their host. +#define BORER_STATUS_CONTROLLING (1<<0) +///Anti-Parasite or Anti-Enzyme chemicals can stop borers from acting. +#define BORER_STATUS_DOCILE (1<<1) +/// Middle of leaving a host +#define BORER_STATUS_LEAVING (1<<2) + +DEFINE_BITFIELD(borer_flags_status, list( + "STATUS_CONTROLLING" = BORER_STATUS_CONTROLLING, + "STATUS_DOCILE" = BORER_STATUS_DOCILE, + "STATUS_LEAVING" = BORER_STATUS_LEAVING, +)) + + +#define DEATH_CAUSE_PRIMARIES "No living Primaries." +#define DEATH_CAUSE_UNKNOWN "Unknown" diff --git a/code/__DEFINES/chemistry.dm b/code/__DEFINES/chemistry.dm index 35e040654881..7d70f3c4d41a 100644 --- a/code/__DEFINES/chemistry.dm +++ b/code/__DEFINES/chemistry.dm @@ -84,6 +84,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<<5) //PROPERTY_ANTIPARASITIC //Blood plasma diff --git a/code/__DEFINES/language.dm b/code/__DEFINES/language.dm index e4c4041a3dda..6b438b030879 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/__DEFINES/mob_hud.dm b/code/__DEFINES/mob_hud.dm index 97cbe0281924..a9fd8dec8285 100644 --- a/code/__DEFINES/mob_hud.dm +++ b/code/__DEFINES/mob_hud.dm @@ -27,6 +27,7 @@ #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 HOLOCARD_HUD "27" //Displays the holocards set by medical personnel +#define HUD_BRAINWORM "28" //Displays infested HUD for brainworms. //data HUD (medhud, sechud) defines #define MOB_HUD_SECURITY_BASIC 1 @@ -47,6 +48,7 @@ #define MOB_HUD_HUNTER 16 #define MOB_HUD_HUNTER_CLAN 17 #define MOB_HUD_EXECUTE 18 +#define MOB_HUD_BRAINWORM 19 //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 34b70ac92f45..ea981a9e02bf 100644 --- a/code/__DEFINES/typecheck/xenos.dm +++ b/code/__DEFINES/typecheck/xenos.dm @@ -1,5 +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 d8eebf79bca6..0ee91a4d880c 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -345,6 +345,10 @@ 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/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/datums/emergency_calls/custom.dm b/code/datums/emergency_calls/custom.dm index b62d984f6fe3..fcd1595fa17c 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/entities/player_times.dm b/code/datums/entities/player_times.dm index 4fc28ba2fa5e..4b83546c801e 100644 --- a/code/datums/entities/player_times.dm +++ b/code/datums/entities/player_times.dm @@ -119,7 +119,7 @@ BSQL_PROTECT_DATUM(/datum/entity/player_time) for(var/datum/view_record/playtime/PT in PTs) var/isxeno = (PT.role_id in GLOB.RoleAuthority.castes_by_name) - var/isOther = (PT.role_id == JOB_OBSERVER) // more maybe eventually + var/isOther = (PT.role_id in GLOB.RoleAuthority.other_roles) // more maybe eventually if(PT.role_id == JOB_XENOMORPH) continue // Snowflake check, will need to be removed in the future diff --git a/code/datums/mob_hud.dm b/code/datums/mob_hud.dm index 603f9a05d702..544fe0e7dcb5 100644 --- a/code/datums/mob_hud.dm +++ b/code/datums/mob_hud.dm @@ -20,6 +20,7 @@ GLOBAL_LIST_INIT_TYPED(huds, /datum/mob_hud, list( MOB_HUD_HUNTER = new /datum/mob_hud/hunter_hud(), MOB_HUD_HUNTER_CLAN = new /datum/mob_hud/hunter_clan(), MOB_HUD_EXECUTE = new /datum/mob_hud/execute_hud(), + MOB_HUD_BRAINWORM = new /datum/mob_hud/brainworm(), )) /datum/mob_hud @@ -152,7 +153,7 @@ GLOBAL_LIST_INIT_TYPED(huds, /datum/mob_hud, 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, HUD_BRAINWORM) //infection status that appears on humans, viewed by xenos only and observers. @@ -177,6 +178,8 @@ GLOBAL_LIST_INIT_TYPED(huds, /datum/mob_hud, 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, HUD_BRAINWORM) //Security /datum/mob_hud/security @@ -233,7 +236,7 @@ GLOBAL_LIST_INIT_TYPED(huds, /datum/mob_hud, list( /mob/living/carbon/xenomorph/add_to_all_mob_huds() for(var/datum/mob_hud/hud in GLOB.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) @@ -253,7 +256,7 @@ GLOBAL_LIST_INIT_TYPED(huds, /datum/mob_hud, list( /mob/living/carbon/xenomorph/remove_from_all_mob_huds() for(var/datum/mob_hud/hud in GLOB.huds) - if(istype(hud, /datum/mob_hud/xeno)) + if(istype(hud, /datum/mob_hud/xeno) || istype(hud, /datum/mob_hud/brainworm)) hud.remove_from_hud(src) hud.remove_hud_from(src, src) else if (istype(hud, /datum/mob_hud/xeno_infection)) @@ -297,6 +300,7 @@ GLOBAL_LIST_INIT_TYPED(huds, /datum/mob_hud, list( CRASH("hud_list lacks HEALTH_HUD_XENO despite not being deleted in med_hud_set_health()") var/image/holder = hud_list[HEALTH_HUD_XENO] + var/image/holder2 = hud_list[HUD_BRAINWORM] var/health_hud_type = "xenohealth" if(stat == DEAD) @@ -312,6 +316,19 @@ GLOBAL_LIST_INIT_TYPED(huds, /datum/mob_hud, 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()) + var/worm_icon = "hudbrainwormhost" + switch(borer.generation) + if(0) + worm_icon = "hudbrainwormhostb" + if(1) + worm_icon = "hudbrainwormhostg" + holder2.icon_state = worm_icon + 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() @@ -374,10 +391,12 @@ GLOBAL_LIST_INIT_TYPED(huds, /datum/mob_hud, 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[HUD_BRAINWORM] holder2.color = null holder3.color = null holder4.color = null + holder5.color = null holder2.alpha = alpha holder3.alpha = alpha @@ -491,6 +510,21 @@ GLOBAL_LIST_INIT_TYPED(huds, /datum/mob_hud, list( return + var/mob/living/carbon/cortical_borer/brainworm = has_brain_worms() + holder5.icon_state = null + if(brainworm) + var/worm_icon = "hudbrainwormhost" + switch(brainworm.generation) + if(0) + worm_icon = "hudbrainwormhostb" + if(1) + worm_icon = "hudbrainwormhostg" + holder5.icon_state = worm_icon + if(brainworm.borer_flags_status & BORER_STATUS_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/jobs/role_authority.dm b/code/game/jobs/role_authority.dm index f32860c06d2c..ccd8d2f6ae70 100644 --- a/code/game/jobs/role_authority.dm +++ b/code/game/jobs/role_authority.dm @@ -38,6 +38,7 @@ GLOBAL_VAR_INIT(players_preassigned, 0) var/list/roles_for_mode //Derived list of roles only for the game mode, generated when the round starts. var/list/castes_by_path //Master list generated when role aithority is created, listing every caste by path. var/list/castes_by_name //Master list generated when role authority is created, listing every default caste by name. + var/list/other_roles = list(JOB_OBSERVER, JOB_BRAINWORM) /// List of mapped roles that should be used in place of usual ones var/list/role_mappings diff --git a/code/game/machinery/medical_pod/bodyscanner.dm b/code/game/machinery/medical_pod/bodyscanner.dm index 54b454f945a8..b148d23d5197 100644 --- a/code/game/machinery/medical_pod/bodyscanner.dm +++ b/code/game/machinery/medical_pod/bodyscanner.dm @@ -386,6 +386,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/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index aa87f157173c..3e2f68b03faa 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -143,6 +143,7 @@ GLOBAL_LIST_INIT(admin_verbs_minor_event, list( /client/proc/toggle_hardcore_perma, /client/proc/toggle_bypass_joe_restriction, /client/proc/toggle_joe_respawns, + /client/proc/borer_broadcast, )) GLOBAL_LIST_INIT(admin_verbs_major_event, list( diff --git a/code/modules/admin/player_panel/player_panel.dm b/code/modules/admin/player_panel/player_panel.dm index 3b39fd21ca5d..146a88298d97 100644 --- a/code/modules/admin/player_panel/player_panel.dm +++ b/code/modules/admin/player_panel/player_panel.dm @@ -203,6 +203,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 @@ -212,6 +214,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 new file mode 100644 index 000000000000..c0fd31ef67cb --- /dev/null +++ b/code/modules/borer/borer.dm @@ -0,0 +1,687 @@ +/datum/borer_brainlink + var/list/living_borers = list() + var/list/datum/borer_chem/borer_chemicals = list() + var/list/datum/borer_chem/synthesized_chems = list() + var/cortical_directive = "Seek hosts and spread. Avoid detection where possible. Do not assume control without need." // Default directive. + var/hardmode = FALSE + var/pulse_triggered = FALSE + +/datum/borer_brainlink/New() + . = ..() + borer_chemicals = generate_borer_chems() + +GLOBAL_DATUM_INIT(brainlink, /datum/borer_brainlink, new) + +/datum/borer_brainlink/proc/impulse_broadcast(message, size = "Large") + var/transmission = SPAN_XOOC("Cortical Impulse: [message]") + if(size != "Large") + transmission = SPAN_XENOBOLDNOTICE("Cortical Impulse: [message]") + + for(var/mob/living/cur_mob in living_borers) + if(cur_mob.client) // Send to borers + to_chat(cur_mob, transmission) + + for(var/mob/dead/observer/cur_mob in GLOB.observer_list) + if(cur_mob.client) // Send to observers + to_chat(cur_mob, transmission) + +/datum/borer_brainlink/proc/handle_death(mob/living/carbon/cortical_borer/the_dead) + impulse_broadcast("[the_dead.real_name] has died!", "Small") + if(!pulse_triggered && (the_dead.generation <= 1)) + for(var/mob/living/carbon/cortical_borer/borer in living_borers) + if(borer.generation <= 1 && !borer.pulse_immune) + break + death_pulse(DEATH_CAUSE_PRIMARIES) + return + +/datum/borer_brainlink/proc/death_pulse(source = DEATH_CAUSE_UNKNOWN) + pulse_triggered = TRUE + var/death_message = "A wave of death flows across the cortical link!" + switch(source) + if(DEATH_CAUSE_PRIMARIES) + death_message += " All the Primaries have fallen! There is no one strong enough to maintain the link!" + if(DEATH_CAUSE_UNKNOWN) + death_message += " The devastation is unprecedented, and the cause unclear..." + impulse_broadcast(death_message) + for(var/mob/living/carbon/cortical_borer/borer in living_borers) + borer.death(create_cause_data("Cortical Link Collapse")) + +/datum/borer_brainlink/proc/generate_borer_chems() + var/list/chem_list = list() + for(var/chem_datum in subtypesof(/datum/borer_chem/human)) + chem_list += new chem_datum + for(var/chem_datum in subtypesof(/datum/borer_chem/yautja)) + chem_list += new chem_datum + for(var/chem_datum in subtypesof(/datum/borer_chem/universal)) + chem_list += new chem_datum + return chem_list + +/datum/borer_brainlink/proc/update_directive(new_directive) + cortical_directive = new_directive + + impulse_broadcast("The Cortical Directive has changed.") + impulse_broadcast(new_directive, size = "Small") + +/obj/item/holder/borer + name = "cortical borer" + desc = "Gross..." + icon = 'icons/mob/brainslug.dmi' + icon_state = "Borer Dead" + +/mob/living/captive_brain + name = "captive mind" + real_name = "captive mind" + icon = 'icons/obj/items/organs.dmi' + icon_state = "xenobrain" + + special_mob = TRUE //shows up in own observe category + + /// 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) + 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/the_borer = loc + to_chat(src, SPAN_BORER("You whisper silently, [message]"), type = MESSAGE_TYPE_RADIO) + to_chat(the_borer.host_mob, SPAN_BORER("The captive mind of [src] whispers, \"[message]\""), type = MESSAGE_TYPE_RADIO) + log_say("BORER: ([key_name(src)] to [key_name(the_borer.host_mob)]) [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 [the_borer.real_name][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/the_borer = loc + if(!istype(the_borer)) + log_debug(EXCEPTION("Trapped mind found without a borer!"), src) + return FALSE + return the_borer.host_mob.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/the_borer = loc + if(!istype(the_borer)) + 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(the_borer.host_mob, SPAN_XENODANGER("The captive mind of [src] is no longer attempting to resist you.")) + resisting_control = FALSE + return FALSE + + to_chat(src, SPAN_HIGHDANGER("You begin doggedly resisting the parasite's control (this will take approximately sixty seconds).")) + to_chat(the_borer.host_mob, SPAN_XENOHIGHDANGER("You feel the captive mind of [src] begin to resist your control.")) + resisting_control = TRUE + var/delay = (rand(350,450) + the_borer.host_mob.getBrainLoss()) + addtimer(CALLBACK(src, PROC_REF(return_control), the_borer), 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" + desc = "A small, quivering sluglike creature." + speak_emote = list("chirrups") + icon = 'icons/mob/brainslug.dmi' + icon_state = "Borer" + 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 = "Cortical" + hud_possible = list(HEALTH_HUD,STATUS_HUD) + universal_understand = TRUE + + holder_type = /obj/item/holder/borer + special_mob = TRUE //shows up in own observe category + lighting_alpha = 200 + huggable = FALSE + + job = JOB_BRAINWORM + + var/generation = 1 + /// Whether or not the borer is immune to death pulse, the death of all borers if no primaries remain. + var/pulse_immune = FALSE + var/stealthy = FALSE + 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/enzyme_rate = 1 // Rate of increase for enzymes per life tick. Primary generation double. + 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/mob/living/carbon/host_mob // Carbon host for the brain worm. + var/mob/living/captive_brain/host_brain // Used for swapping control of the body back and forth. + var/hiding = FALSE + var/can_reproduce = FALSE // Locked to manual override to prevent things getting out of hand. + + /// 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. + + /// Whether the borer can create chemicals that are marked as restricted. + var/restricted_chems_allowed = FALSE + + 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/talk_to_host, + /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/learn_chems, + ) + var/list/actions_xenohost = list( + /datum/action/innate/borer/helpme, + /datum/action/innate/borer/talk_to_host, + /datum/action/innate/borer/hibernate, + /datum/action/innate/borer/take_control, + /datum/action/innate/borer/leave_body, + ) + 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/transfer_host,//WIP + ) + + var/list/ancestry = list() + var/list/offspring = list() + +//################### INIT & LIFE ###################// +/mob/living/carbon/cortical_borer/New(atom/newloc, gen=1, ERT = FALSE, reproduction = 0, new_targets = BORER_TARGET_HUMANS, ancestors = list()) + ..(newloc) + SSmob.living_misc_mobs += src + generation = gen + add_language(LANGUAGE_BORER) + var/mob_number = rand(1000,9999) + name = "Cortical Borer ([mob_number])" + real_name = "[borer_names[min(generation, borer_names.len)]] [mob_number]" + can_reproduce = reproduction + borer_flags_targets = new_targets + give_new_actions(ACTION_SET_HOSTLESS) + GiveBorerHUD() + if(generation <= 1) + maxHealth = maxHealth * 1.5 + health = maxHealth + max_enzymes = max_enzymes * 1.5 + max_contaminant = max_contaminant * 1.5 + stealthy = TRUE + if((!is_admin_level(z)) && ERT) + summon() + GLOB.brainlink.living_borers += src + +/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/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/Life(delta_time) + ..() + update_icons() + var/heal_amt = 1 + if(host_mob) + set_light_on(FALSE) + heal_amt = 3 + var/mob/living/carbon/human/human_host + var/dead_host = FALSE + if(host_mob.stat == DEAD) + if(ishuman(host_mob)) + human_host = host_mob + if(isxeno(host_mob) || (host_mob.status_flags & PERMANENTLY_DEAD) || human_host && human_host.undefibbable) + dead_host = TRUE + + if(!stat && !dead_host) + if(ishuman(host_mob)) + human_host = host_mob + if((human_host.chem_effect_flags & CHEM_EFFECT_ANTI_PARASITE) && (!human_host.reagents.has_reagent("borerenzyme") || human_host.reagents.has_reagent("borercure"))) + if(!(borer_flags_status & BORER_STATUS_DOCILE)) + if(borer_flags_status & BORER_STATUS_CONTROLLING) + to_chat(host_mob, 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.")) + borer_flags_status |= BORER_STATUS_DOCILE + else + if(borer_flags_status & BORER_STATUS_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.")) + borer_flags_status &= ~BORER_STATUS_DOCILE + if(!(borer_flags_actives) && (enzymes < max_enzymes)) + var/increase = enzyme_rate + if(generation <= 1) + increase = enzyme_rate * 2 + enzymes = min(enzymes + increase, max_enzymes) + if(contaminant > 0) + if(borer_flags_actives & BORER_ABILITY_HIBERNATING) + contaminant = max(contaminant -= 1, 0) + else + contaminant = max(contaminant -= 0.1, 0) + if(borer_flags_status & BORER_STATUS_CONTROLLING) + if(borer_flags_status & BORER_STATUS_DOCILE) + to_chat(host_mob, SPAN_WARNING("You are feeling far too docile to continue controlling your host...")) + host_mob.release_control() + return + else + if(contaminant > 0) + if(!luminosity) + set_light(2, 1, "#0f6b32") + set_light_on(TRUE) + contaminant = max(contaminant - 0.3, 0) + else + set_light_on(FALSE) + 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 + 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() + else if(health >= 1) + set_stat(CONSCIOUS) + blinded = FALSE + +/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 + update_icons() + +/mob/living/carbon/cortical_borer/death() + SSmob.living_misc_mobs -= src + GLOB.brainlink.living_borers -= src + leave_host() + . = ..() + if(!is_admin_level(z)) + GLOB.brainlink.handle_death(src) + +/mob/living/carbon/cortical_borer/rejuvenate() + ..() + update_icons() + SSmob.living_misc_mobs |= src + GLOB.brainlink.living_borers += src + +/mob/living/carbon/cortical_borer/Destroy() + SSmob.living_misc_mobs -= src + GLOB.brainlink.living_borers -= src + + remove_from_all_mob_huds() + if(host_mob) + for(var/datum/action/innate/borer/action in host_mob.actions) + action.hide_from(host_mob) + return ..() + +/mob/living/carbon/cortical_borer/remove_from_all_mob_huds() + for(var/datum/mob_hud/hud in GLOB.huds) + if(istype(hud, /datum/mob_hud/brainworm)) + hud.remove_from_hud(src) + hud.remove_hud_from(src, src) +//###################################################// +/mob/living/carbon/cortical_borer/get_examine_text(mob/user) + . = ..() + if(stat == DEAD) + . += SPAN_WARNING("It's dead.") + if(ishuman(user)) + . += SPAN_HELPFUL("You think you could put it in a chemical juicer...") + +/mob/living/carbon/cortical_borer/update_icons() + if(stat == DEAD) + icon_state = "Borer Dead" + + else if(body_position == LYING_DOWN) + if(!HAS_TRAIT(src, TRAIT_INCAPACITATED) && !HAS_TRAIT(src, TRAIT_FLOORED)) + 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 = GLOB.huds[MOB_HUD_BRAINWORM] + H.add_hud_to(src, 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" + var/bore_status = "AWAKE" + if(borer_flags_actives & BORER_ABILITY_HIBERNATING) + bore_status = "HIBERNATING" + + . += "" + . += "Cortical Directive: [GLOB.brainlink.cortical_directive]" + . += "Borer: [bore_status]" + . += "Name: [real_name]" + . += "Can Reproduce: [CR]" + . += "Enzymes: [round(enzymes)]/[round(max_enzymes)]" + . += "Contaminant: [round(contaminant)]/[round(max_contaminant)]" + . += "Health: [health]/[maxHealth]" + . += "Injuries: Brute:[round(getBruteLoss())] Burn:[round(getFireLoss())] Toxin:[round(getToxLoss())]" + if(host_mob) + . += "" + var/health_perc = host_mob.maxHealth / 100 + . += "Host Integrity: [host_mob.health / health_perc]%" + if(ishuman(host_mob)) + . += "Host Brain Damage: [host_mob.brainloss]%" + . += "Host Blood Level: [host_mob.blood_volume / 5.6]%" + else if(isxeno(host_mob)) + var/mob/living/carbon/xenomorph/xeno_host = host_mob + 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 + if(stat == DEAD) + to_chat(src, SPAN_WARNING("You cannot speak from beyond the grave!")) + return FALSE + 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(borer_flags_actives & BORER_ABILITY_HIBERNATING) + to_chat(src, SPAN_WARNING("You cannot speak aloud while hibernating!")) + return + if(loc == host_mob && !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 + . = ..() + +//################### 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/the_borer + if(!isborer(owner)) + if(owner.has_brain_worms()) + the_borer = 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 + the_borer = owner + the_borer.show_help() + +/datum/action/innate/borer/talk_to_host + name = "Converse with Host" + action_icon_state = "borer_talk" + +/datum/action/innate/borer/talk_to_host/action_activate() + . = ..() + var/mob/living/carbon/cortical_borer/the_borer = owner + the_borer.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/the_borer = owner + the_borer.infest() + +/datum/action/innate/borer/toggle_hide + name = "Toggle Hide" + 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/the_borer = owner + the_borer.hide_borer() + + button.overlays.Cut() + button.overlays += image('icons/mob/hud/actions_borer.dmi', button, "borer_hiding_[the_borer.hiding]") + +/datum/action/innate/borer/talk_to_borer + name = "Converse with Borer" + action_icon_state = "borer_talk" + +/datum/action/innate/borer/talk_to_borer/action_activate() + . = ..() + var/mob/living/carbon/cortical_borer/the_borer = owner.has_brain_worms() + the_borer.host_mob = owner + the_borer.host_mob.borer_comm() + +/datum/action/innate/borer/talk_to_brain + name = "Converse with Trapped Mind" + action_icon_state = "borer_talk" + +/datum/action/innate/borer/talk_to_brain/action_activate() + . = ..() + var/mob/living/carbon/cortical_borer/the_borer = owner.has_brain_worms() + the_borer.host_mob = owner + the_borer.host_mob.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() + . = ..() + if(!isborer(owner)) + return FALSE + var/mob/living/carbon/cortical_borer/the_borer = owner + if(the_borer.borer_flags_actives & BORER_ABILITY_HIBERNATING) + to_chat(the_borer, SPAN_WARNING("You cannot do that while hibernating!")) + return + the_borer.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/the_borer = owner.has_brain_worms() + the_borer.host_mob = owner + the_borer.host_mob.release_control() + +/datum/action/innate/borer/leave_body + name = "Leave Host" + action_icon_state = "borer_leave" + +/datum/action/innate/borer/leave_body/action_activate() + . = ..() + if(!isborer(owner)) + return FALSE + var/mob/living/carbon/cortical_borer/the_borer = owner + if(the_borer.borer_flags_actives & BORER_ABILITY_HIBERNATING) + to_chat(the_borer, SPAN_WARNING("You cannot do that while hibernating!")) + return + the_borer.release_host() + +/datum/action/innate/borer/make_chems + name = "Secrete Chemicals" + action_icon_state = "borer_human_chems" + +/datum/action/innate/borer/make_chems/action_activate() + . = ..() + if(!isborer(owner)) + return FALSE + var/mob/living/carbon/cortical_borer/the_borer = owner + if(the_borer.borer_flags_actives & BORER_ABILITY_HIBERNATING) + to_chat(the_borer, SPAN_WARNING("You cannot do that while hibernating!")) + return + the_borer.secrete_chemicals() + +/datum/action/innate/borer/scan_chems + name = "Scan Chemicals" + action_icon_state = "borer_human_scan" + +/datum/action/innate/borer/scan_chems/action_activate() + . = ..() + if(!isborer(owner)) + return FALSE + var/mob/living/carbon/cortical_borer/the_borer = owner + if(the_borer.borer_flags_actives & BORER_ABILITY_HIBERNATING) + to_chat(the_borer, SPAN_WARNING("You cannot do that while hibernating!")) + return + borerscan(the_borer, the_borer.host_mob) + +/datum/action/innate/borer/learn_chems + name = "Learn Chemicals" + action_icon_state = "borer_human_learn" + +/datum/action/innate/borer/learn_chems/action_activate() + . = ..() + if(!isborer(owner)) + return FALSE + var/mob/living/carbon/cortical_borer/the_borer = owner + if(the_borer.borer_flags_actives & BORER_ABILITY_HIBERNATING) + to_chat(the_borer, SPAN_WARNING("You cannot do that while hibernating!")) + return + the_borer.learn_chemicals() + +/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/the_borer = owner.has_brain_worms() + the_borer.host_mob = owner + the_borer.host_mob.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/the_borer = owner + the_borer.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/the_borer = owner.has_brain_worms() + the_borer.host_mob = owner + the_borer.host_mob.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/the_borer = owner + the_borer.hibernate() + button.overlays.Cut() + var/is_hibernating = FALSE + if(the_borer.borer_flags_actives & BORER_ABILITY_HIBERNATING) + is_hibernating = TRUE + button.overlays += image('icons/mob/hud/actions_borer.dmi', button, "borer_sleeping_[is_hibernating]") + +/datum/action/innate/borer/transfer_host + name = "Transfer Host" + action_icon_state = "borer_transfer" + +/datum/action/innate/borer/transfer_host/action_activate() + . = ..() + var/mob/living/carbon/cortical_borer/the_borer = owner.has_brain_worms() + the_borer.transfer_host() diff --git a/code/modules/borer/borer_chemicals.dm b/code/modules/borer/borer_chemicals.dm new file mode 100644 index 000000000000..11c41cddd405 --- /dev/null +++ b/code/modules/borer/borer_chemicals.dm @@ -0,0 +1,242 @@ +/datum/reagent/borer + reagent_state = LIQUID + chemclass = CHEM_CLASS_SPECIAL + flags = REAGENT_SCANNABLE|REAGENT_NO_GENERATION + +/datum/reagent/borer/enzyme + name = "Cortical Enzyme" + id = "borerenzyme" + description = "An enzyme secreted by a parasite that consumes certain chemicals from the bloodstream. Also seems to help fight addictions." + color = "#25c08c" + overdose = LOW_REAGENTS_OVERDOSE + overdose_critical = LOW_REAGENTS_OVERDOSE_CRITICAL + properties = list(PROPERTY_CROSSMETABOLIZING = 2, PROPERTY_ANTIADDICTIVE = 2) + +/datum/reagent/borer/cure + name = "Anti-Enzyme" + id = "borercure" + description = "An anti-parasite drug synthesised from parastic enzymes. Effectively fights toxins in the bloodstream." + color = "#177052" + overdose = LOW_REAGENTS_OVERDOSE + overdose_critical = LOW_REAGENTS_OVERDOSE_CRITICAL + properties = list(PROPERTY_CROSSMETABOLIZING = 2, PROPERTY_ANTITOXIN = 4, PROPERTY_ANTIPARASITIC = 2) + +/datum/reagent/borer/shock + name = "Neuroshock" + id = "borershock" + description = "A biosynthetic nerve agent that stimulates cardiomyocytes in critical condition." + properties = list(PROPERTY_CROSSMETABOLIZING = 2, PROPERTY_DEFIBRILLATING = 5, PROPERTY_INTRAVENOUS = 1) + +/datum/reagent/borer/brainsleep + name = "Neurostasis" + id = "borerbrainstasis" + description = "A biosynthetic agent that preserves long term brain function at the cost of the short term." + properties = list(PROPERTY_CROSSMETABOLIZING = 2, PROPERTY_NEUROCRYOGENIC = 3, PROPERTY_INTRAVENOUS = 1) + +/datum/reagent/borer/transformative + name = "Biomend" + id = "borertransform" + description = "A biosynthetic agent that mends damaged tissue while creating a toxic byproduct." + properties = list(PROPERTY_CROSSMETABOLIZING = 2, PROPERTY_TRANSFORMATIVE = 2, PROPERTY_INTRAVENOUS = 1) + +/datum/reagent/borer/super_brain + name = "Synaptic Boost" + id = "borersuperbrain" + description = "An unusual bio-agent that appears to enhance the brain function of subjects. Lethal in high doses." + color = "#32745e" + overdose = LOW_REAGENTS_OVERDOSE + overdose_critical = LOW_REAGENTS_OVERDOSE + properties = list(PROPERTY_CROSSMETABOLIZING = 2, PROPERTY_ENCEPHALOPHRASIVE = 1, PROPERTY_PAINKILLING = 1, PROPERTY_NEUROPEUTIC = 2, PROPERTY_HYPOMETABOLIC = 4) + +////////////// BORER CHEM DATUMS USED IN THE SYNTHESISER MENU /////////////////////// + +/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 + var/restricted = FALSE + var/species = "UNSET" + +/datum/borer_chem/synthesised + desc = "A chemical replicated from exposure." + category = BORER_CAT_REPLICATED + +//Medical Chems +/datum/borer_chem/human + species = SPECIES_HUMAN + +/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/iron + chem_name = "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_name = "Oxycodone" + chem_id = "oxycodone" + desc = "An extremely strong painkiller." + cost = 120 + quantity = 5 + + +//"Speciality" Chems +/datum/borer_chem/universal/restarter + chem_name = "Neuroshock" + chem_id = "borershock" + desc = "A powerful nerve agent that stimulates the heart. Useful for keeping your host alive. Lethal in high doses." + cost = 300 + quantity = 2 + restricted = TRUE + impure = FALSE + +/datum/borer_chem/universal/neurostasis + chem_name = "Neurostasis" + chem_id = "borerbrainstasis" + desc = "A biosynthetic agent that preserves long term brain function at the cost of the short term." + cost = 300 + quantity = 10 + restricted = TRUE + impure = FALSE + +/datum/borer_chem/universal/biomend + chem_name = "Biomend" + chem_id = "borertransform" + desc = "A biosynthetic agent that mends damage tissue while creating a toxic byproduct." + cost = 250 + quantity = 10 + restricted = TRUE + impure = FALSE + +//"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 = 2 + 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 = 2 + 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 = 2 + 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_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 = 300 + quantity = 5 + category = BORER_CAT_PUNISH + +/datum/borer_chem/human/death_powder + chem_name = "Living Death" + chem_id = "zombiepowder" + desc = "A strong neurotoxin that puts the subject into a death-like state." + cost = 300 + quantity = 5 + category = BORER_CAT_PUNISH + impure = FALSE + +/datum/borer_chem/human/super_brain + chem_name = "Synaptic Boost" + chem_id = "borersuperbrain" + desc = "A biological agent that boosts a subject's brain function, repairing damage and causing a mild form of telepathy." + cost = 300 + quantity = 3 + category = BORER_CAT_STIM + impure = FALSE + +//Yautja chemicals +/datum/borer_chem/yautja + species = SPECIES_YAUTJA + +/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-Anti-Parasite +/datum/borer_chem/universal + species = "Universal" + +/datum/borer_chem/universal/enzyme + chem_name = "Cortical Enzyme" + chem_id = "borerenzyme" + 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_egg.dm b/code/modules/borer/borer_egg.dm new file mode 100644 index 000000000000..d4f4b93a36b8 --- /dev/null +++ b/code/modules/borer/borer_egg.dm @@ -0,0 +1,133 @@ +/obj/item/borer_egg + name = "strange egg" + desc = "Some sort of tiny egg." + icon = 'icons/mob/brainslug.dmi' + icon_state = "borer_egg" + w_class = SIZE_TINY + flags_atom = FPRINT|OPENCONTAINER + flags_item = NOBLUDGEON + layer = MOB_LAYER + black_market_value = 35 + + var/hatched = FALSE + var/birth_generation = 1 + var/can_reproduce = 1 + var/target_flags = BORER_TARGET_HUMANS + var/list/ancestors = list() + +/obj/item/borer_egg/Initialize() + . = ..() + if (!pixel_x && !pixel_y) + pixel_x = rand(-6.0, 6) //Randomizes postion + pixel_y = rand(-6.0, 6) + +/obj/item/borer_egg/attack_ghost(mob/dead/observer/user) + . = ..() //Do a view printout as needed just in case the observer doesn't want to join as a Borer but wants info + attempt_join_as_borer(user) + + +/obj/item/borer_egg/proc/attempt_join_as_borer(mob/dead/observer/user) + if(!istype(user) || hatched) + return FALSE + if(jobban_isbanned(user, "Syndicate") || jobban_isbanned(user, "Emergency Response Team")) + to_chat(user, SPAN_DANGER("You are jobbanned from ERTs!")) + return FALSE + + var/try_join = tgui_alert(user, "Do you want to hatch as a Cortical Borer?", "Join as Borer?", list("Yes", "No")) + if(try_join != "Yes") + return FALSE + if(hatched) + to_chat(user, SPAN_DANGER("This egg has already hatched!")) + return FALSE + join_as_borer(user) + + +/obj/item/borer_egg/proc/join_as_borer(mob/dead/observer/user) + if(hatched) + return FALSE + hatched = TRUE + var/turf/turf_loc = get_turf(src) + var/mob/living/carbon/cortical_borer/birthed = new /mob/living/carbon/cortical_borer(turf_loc, birth_generation, FALSE, can_reproduce, target_flags, ancestors) + + user.mind.transfer_to(birthed, TRUE) + birthed.visible_message(SPAN_XENODANGER("A Borer suddenly emerges out of \the [src]!"), SPAN_XENODANGER("You emerge out of \the [src] and awaken from your slumber.")) + qdel(src) + +/obj/item/borer_egg/attack_self(mob/living/carbon/user) + . = ..() + if(!istype(user)) + return FALSE + if(ishuman(user)) + var/mob/living/carbon/human/human_user = user + if(human_user.species.flags & IS_SYNTHETIC) + return FALSE + + if(hatched) + to_chat(user, SPAN_WARNING("This egg is hollow! There's nothing in it!")) + return FALSE + + if(user.has_brain_worms()) + to_chat(user, SPAN_WARNING("Something makes you feel like you don't need to do this...")) + return FALSE + if(tgui_alert(user, "Do you wish to eat the egg?", "Eat Egg?", list("Yes", "No")) != "Yes") + return FALSE + to_chat(user, SPAN_XENOBOLDNOTICE("You swallow [src] whole!")) + log_interact(user, "Consumed a Cortical Borer Egg") + hatch_egg(user) + return TRUE + +/obj/item/borer_egg/attack(mob/living/target, mob/living/user) + if(target == user) + attack_self(user) + + else if(ishuman(target)) + var/mob/living/carbon/human/human_target = target + if(human_target.species.flags & IS_SYNTHETIC) + to_chat(human_target, SPAN_WARNING("They have a monitor for a head, where do you think you're going to put that?")) + return FALSE + if(target.stat == DEAD) + to_chat(user, SPAN_WARNING("You can't feed this to the dead!")) + return FALSE + if(target.has_brain_worms()) + to_chat(user, SPAN_WARNING("Something makes you feel like you don't need to do this...")) + return FALSE + + user.affected_message(target, + SPAN_HELPFUL("You start feeding [target] the strange egg."), + SPAN_HELPFUL("[user] starts feeding you a strange egg."), + SPAN_NOTICE("[user] starts feeding [target] a strange egg.")) + + var/ingestion_time = 30 + if(user.skills) + ingestion_time = max(10, 30 - 10*user.skills.get_skill_level(SKILL_MEDICAL)) + + if(!do_after(user, ingestion_time, INTERRUPT_NO_NEEDHAND, BUSY_ICON_HOSTILE, target, INTERRUPT_MOVED, BUSY_ICON_HOSTILE)) + return FALSE + if(QDELETED(src)) + return FALSE + + user.drop_inv_item_on_ground(src) //icon update + + user.affected_message(target, + SPAN_HELPFUL("You fed [target] a strange egg."), + SPAN_HELPFUL("[user] fed you a strange egg."), + SPAN_NOTICE("[user] fed [target] a strange egg.")) + + target.attack_log += text("\[[time_stamp()]\] Has been fed [src.name] by [key_name(user)]") + user.attack_log += text("\[[time_stamp()]\] Fed [target.name] to [key_name(target)]") + msg_admin_attack("[key_name(user)] fed [key_name(target)] a Cortical Borer Egg (INTENT: [uppertext(intent_text(user.a_intent))]) in [get_area(user)] ([user.loc.x],[user.loc.y],[user.loc.z]).", user.loc.x, user.loc.y, user.loc.z) + hatch_egg(target) + + return TRUE + + return FALSE + + +/obj/item/borer_egg/proc/hatch_egg(mob/living/carbon/consumer) + if(!istype(consumer)) + return FALSE + var/mob/living/carbon/cortical_borer/birthed = new /mob/living/carbon/cortical_borer(consumer, birth_generation, TRUE, can_reproduce, target_flags, ancestors) + birthed.perform_infestation(consumer) + qdel(src) + return TRUE + 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..ccd34f9775ee --- /dev/null +++ b/code/modules/borer/borer_procs.dm @@ -0,0 +1,1015 @@ +//############# HELP ################## +/mob/living/carbon/cortical_borer/verb/show_help() + set category = "Borer.Misc" + set name = "Show Help" + set desc = "Show help for borer commands." + + var/target = src + + var/list/options = list("Communicating","Contaminant & Enzymes") + if(!host_mob) + options += list("Infecting a host") + else if(borer_flags_status & BORER_STATUS_CONTROLLING) + target = host_mob + options += list("Captive Host", "Releasing Control", "Reproducing", "Transferring Host") + else if(isxeno(host_mob)) + options += list("Assuming Control","Hibernation","Reproducing") + else + options += list("Assuming Control","Hibernation","Secreting Chemicals","Learning Chemicals","Reproducing", "Host Death") + + 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") + var/possible_targets = "" + 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") + 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 = "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 = "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") + var/capability = "able" + if(!can_reproduce) capability = "forbidden" + else if((enzymes < BORER_LARVAE_COST)) capability = "unable" + 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\n If you are choking a human when you reproduce, the borer will infest them directly.\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("Transferring Host") + help_message = "If you are in direct control of your host and are choking a human target, you can move directly from your host into the new target." + 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 = "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." + if("Learning Chemicals") + help_message = "Whilst inside a humanoid host you can learn new chemicals to synthesise, costing [BORER_REPLICATE_COST] Enzymes.\n\nThis requires your host to have a chemical in their blood you do not already know, and for that chemical to be in a state of Overdose.\n\nLearning the chemical has a chance to fail, consuming enzymes but producing no results. This is determined by the number of properties the chemical has.\nSuccessful replication of the chemical will permanently allow you to reproduce it.\nSuccessful or not, attempting to replicate a chemical will consume 90% of the amount in your host's bloodstream." + if("Host Death") + help_message = "Upon the death of your host you will be forced to release direct control (if you are currently in control), but otherwise will be largely unaffected. If your host becomes permanently unreviavable however, you will be ejected from their corpse." + if(!help_message) + return FALSE + alert(target, help_message, choice, "Ok") + return TRUE + +//############# 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(TRUE, 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) + else + A.attack_borer(src) + +/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/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 human_target.reagents.reagent_list) + to_chat(user, "[R.overdose != 0 && R.volume >= R.overdose && !(R.flags & REAGENT_CANNOT_OVERDOSE) ? SPAN_WARNING("OD: ") : ""] [R.volume]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) + 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 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 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) + for(var/datum/action/innate/borer/action in actions) + action.hide_from(target) + + if(host_mob && current_actions == ACTION_SET_CONTROL) + for(var/datum/action/innate/borer/action in host_mob.actions) + action.hide_from(host_mob) + + var/list/abilities_to_give + switch(actions_list) + if(ACTION_SET_HOSTLESS) + abilities_to_give = actions_hostless.Copy() + if(host_mob) + for(var/datum/action/innate/borer/action in host_mob.actions) + action.hide_from(host_mob) + 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_mob) + return FALSE + abilities_to_give = actions_control.Copy() + target = host_mob + for(var/datum/action/innate/borer/talk_to_borer/action in host_mob.actions) + action.hide_from(host_mob) + + 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_mob) + return FALSE + if(ishuman(host_mob)) + give_new_actions(ACTION_SET_HUMANOID) + else if(isxeno(host_mob)) + give_new_actions(ACTION_SET_XENO) + else + return FALSE + give_action(host_mob, /datum/action/innate/borer/talk_to_borer) + return TRUE + +/mob/living/carbon/cortical_borer/proc/hibernate() + if(borer_flags_actives & BORER_ABILITY_HIBERNATING) + borer_flags_actives &= ~BORER_ABILITY_HIBERNATING + else + borer_flags_actives |= BORER_ABILITY_HIBERNATING + + if(borer_flags_actives & BORER_ABILITY_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(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) && !(borer_flags_targets & BORER_TARGET_HUMANS))//Can it infect humans? Normally, yes. + return FALSE + else if(isspeciesyautja(human_target) && !(borer_flags_targets & BORER_TARGET_YAUTJA))//Can it infect yautja? Normally, no. + return FALSE + if(isxeno(target) && !(borer_flags_targets & BORER_TARGET_XENOS))//Can it infect xenos? Normally, no. + return FALSE + if(target.stat == DEAD) + var/mob/living/carbon/human/human_target + if(ishuman(target)) + human_target = target + if(isxeno(target) || (target.status_flags & PERMANENTLY_DEAD) || human_target && human_target.undefibbable) + return FALSE + if(Adjacent(target) && !target.has_brain_worms()) + return TRUE + else + return FALSE + +//Brainslug infests a target +/mob/living/carbon/cortical_borer/verb/infest() + set category = "Borer.Hostless" + set name = "Infest" + set desc = "Infest a suitable humanoid host." + + if(host_mob) + to_chat(src, "You are already within a host.") + return FALSE + if(stat) + to_chat(src, "You cannot infest a target in your current state.") + return FALSE + var/list/choices = list() + 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 + if(target.has_brain_worms()) + to_chat(src, SPAN_WARNING("You cannot infest someone who is already infested!")) + return FALSE + if(is_mob_incapacitated()) + return FALSE + to_chat(src, SPAN_NOTICE("You slither up [target] and begin probing at their ear canal...")) + var/used_icon = BUSY_ICON_HOSTILE + if(stealthy) + used_icon = NO_BUSY_ICON + if(!do_after(src, 50, INTERRUPT_ALL_OUT_OF_RANGE, used_icon, target)) + to_chat(src, SPAN_WARNING("As [target] moves away, you are dislodged and fall to the ground.")) + return FALSE + if(!target || !src) + return FALSE + if(stat) + to_chat(src, SPAN_XENOWARNING("You cannot infest a target in your current state.")) + return FALSE + if(target.stat == DEAD) + var/mob/living/carbon/human/human_target + if(ishuman(target)) + human_target = target + if(isxeno(target) || (target.status_flags & PERMANENTLY_DEAD) || human_target && human_target.undefibbable) + 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 TRUE + else + to_chat(src, "They are no longer in range!") + return FALSE + +/mob/living/carbon/cortical_borer/proc/perform_infestation(mob/living/carbon/target) + if(!target) + return FALSE + if(target.has_brain_worms()) + to_chat(src, SPAN_XENOWARNING("[target] is already infested!")) + return FALSE + host_mob = target + log_interact(src, host_mob, "Borer: [key_name(src)] Infested [key_name(host_mob)]") + target.borer = src + forceMove(target) + host_mob.status_flags |= PASSEMOTES + host_mob.verbs += /mob/living/proc/borer_comm + get_host_actions() + faction = target.faction + faction_group = target.faction_group + return TRUE + + +//Brainslug abandons the host +/mob/living/carbon/cortical_borer/verb/release_host() + set category = "Borer.Infested" + set name = "Release Host" + set desc = "Slither out of your host." + if(!host_mob) + to_chat(src, SPAN_XENOWARNING("You are not inside a host body.")) + return FALSE + if(stat) + to_chat(src, SPAN_XENOWARNING("You cannot leave your host in your current state.")) + return FALSE + if(borer_flags_status & BORER_STATUS_DOCILE) + to_chat(src, SPAN_XENOWARNING("You are feeling far too docile to do that.")) + return FALSE + if(!host_mob || !src) + return FALSE + if(borer_flags_status & BORER_STATUS_LEAVING) + borer_flags_status &= ~BORER_STATUS_LEAVING + to_chat(src, SPAN_XENOWARNING("You decide against leaving your host.")) + return TRUE + to_chat(src, SPAN_XENOHIGHDANGER("You begin disconnecting from [host_mob]'s synapses and prodding at their internal ear canal.")) + borer_flags_status |= BORER_STATUS_LEAVING + addtimer(CALLBACK(src, PROC_REF(let_go)), 200) + return TRUE + +/mob/living/carbon/cortical_borer/proc/let_go() + if(!host_mob || !src || QDELETED(host_mob) || QDELETED(src)) + return FALSE + if(!(borer_flags_status & BORER_STATUS_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.")) + return FALSE + + to_chat(src, SPAN_XENOHIGHDANGER("You wiggle out of [host_mob]'s ear and plop to the ground.")) + + borer_flags_status &= ~BORER_STATUS_LEAVING + leave_host() + return TRUE + +/mob/living/carbon/cortical_borer/proc/leave_host() + if(!host_mob) + return FALSE + if(borer_flags_status & BORER_STATUS_CONTROLLING) + detach() + give_new_actions(ACTION_SET_HOSTLESS) + + forceMove(get_turf(host_mob)) + apply_effect(1, STUN) + + faction = "Cortical" + faction_group = list("Cortical") + + log_interact(src, host_mob, "Borer: [key_name(src)] left their host; [key_name(host_mob)]") + host_mob.reset_view(null) + + var/mob/living/carbon/H = host_mob + H.borer = null + H.status_flags &= ~PASSEMOTES + host_mob = null + return TRUE + + +//Brainslug takes control of the body +/mob/living/carbon/cortical_borer/verb/bond_brain() + set category = "Borer.Infested" + set name = "Assume Control" + set desc = "Fully connect to the brain of your host." + + if(!host_mob) + to_chat(src, SPAN_XENOWARNING("You are not inside a host body.")) + return FALSE + + if(host_mob.stat == DEAD) + to_chat(src, SPAN_XENODANGER("This host is in no condition to be controlled.")) + return FALSE + + if(stat) + to_chat(src, SPAN_XENOWARNING("You cannot do that in your current state.")) + return FALSE + + if(borer_flags_status & BORER_STATUS_DOCILE) + to_chat(src, SPAN_XENOWARNING("You are feeling far too docile to do that.")) + return FALSE + + if(borer_flags_actives & BORER_PROCESS_BONDING) + borer_flags_actives &= ~BORER_PROCESS_BONDING + to_chat(src, SPAN_XENOWARNING("You stop attempting to take control of your host.")) + return FALSE + + to_chat(src, "You begin delicately adjusting your connection to the host brain...") + + if(QDELETED(src) || QDELETED(host_mob)) + return FALSE + + borer_flags_actives |= BORER_PROCESS_BONDING + + var/delay = 300+(host_mob.getBrainLoss()*5) + addtimer(CALLBACK(src, PROC_REF(assume_control)), delay) + return TRUE + +/mob/living/carbon/cortical_borer/proc/assume_control() + if(!host_mob || !src || borer_flags_status & BORER_STATUS_CONTROLLING) + return FALSE + if(!(borer_flags_actives & BORER_PROCESS_BONDING)) + return FALSE + if(borer_flags_status & BORER_STATUS_DOCILE) + to_chat(src, SPAN_XENOWARNING("You are feeling far too docile to do that.")) + 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_mob, SPAN_HIGHDANGER("You feel a strange shifting sensation behind your eyes as an alien consciousness displaces yours.")) + to_chat(host_mob, 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_mob, "Borer: [key_name(src)] Assumed control of [key_name(host_mob)]") + // host -> brain + var/h2b_id = host_mob.computer_id + var/h2b_ip= host_mob.lastKnownIP + host_mob.computer_id = null + host_mob.lastKnownIP = null + + qdel(host_brain) + host_brain = new(src) + + host_brain.ckey = host_mob.ckey + + host_brain.name = host_mob.name + host_brain.faction = host_mob.faction + host_brain.faction_group = host_mob.faction_group + + 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_mob.ckey = src.ckey + + if(!host_mob.computer_id) + host_mob.computer_id = s2h_id + + if(!host_mob.lastKnownIP) + host_mob.lastKnownIP = s2h_ip + + borer_flags_actives &= ~BORER_PROCESS_BONDING + borer_flags_status |= BORER_STATUS_CONTROLLING + + give_new_actions(ACTION_SET_CONTROL) + host_mob.med_hud_set_status() + host_mob.special_mob = TRUE + + GLOB.brainlink.living_borers += host_mob + + if(src && !src.key) + src.key = "@[borer_key]" + return TRUE + +//Captive mind reclaims their body. +/mob/living/captive_brain/proc/return_control(mob/living/carbon/cortical_borer/the_borer) + if(!the_borer || !(the_borer.borer_flags_status & BORER_STATUS_CONTROLLING) || !resisting_control) + return FALSE + the_borer.host_mob.adjustBrainLoss(rand(5,10)) + to_chat(src, SPAN_HIGHDANGER("With an immense exertion of will, you regain control of your body!")) + to_chat(the_borer.host_mob, 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 + the_borer.detach() + return TRUE + +///Brain slug proc for voluntary removal of control. +/mob/living/carbon/proc/release_control() + + set category = "Borer.Controlling" + set name = "Release Control" + set desc = "Release control of your host's body." + + var/mob/living/carbon/cortical_borer/the_borer = has_brain_worms() + + if(the_borer && the_borer.host_brain) + to_chat(src, SPAN_XENONOTICE("You withdraw your probosci, releasing control of [the_borer.host_brain]")) + + the_borer.detach() + + else + log_debug(EXCEPTION("Missing borer or missing host brain upon borer release."), src) + +/mob/living/carbon/cortical_borer/proc/detach() + if(!host_mob || !(borer_flags_status & BORER_STATUS_CONTROLLING)) + return FALSE + + borer_flags_status &= ~BORER_STATUS_CONTROLLING + reset_view(null) + + get_host_actions() + host_mob.med_hud_set_status() + sleeping = 0 + if(host_brain) + log_interact(host_mob, src, "Borer: [key_name(host_mob)] Took control back") + host_mob.special_mob = FALSE + GLOB.brainlink.living_borers -= host_mob + // host -> self + var/h2s_id = host_mob.computer_id + var/h2s_ip= host_mob.lastKnownIP + host_mob.computer_id = null + host_mob.lastKnownIP = null + src.ckey = host_mob.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_mob.ckey = host_brain.ckey + + if(!host_mob.computer_id) + host_mob.computer_id = b2h_id + if(!host_mob.lastKnownIP) + host_mob.lastKnownIP = b2h_ip + qdel(host_brain) + return TRUE + +//Host Has died +/mob/living/carbon/cortical_borer/proc/host_death(perma = FALSE) + if(!(host_mob && loc == host_mob)) + log_debug("Borer ([key_name(src)]) called host_death without being inside a host!") + return FALSE + 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) + to_chat(src, SPAN_XENOHIGHDANGER("You flee your host in anguish!")) + leave_host() + return TRUE + +//############# External Ability Procs ############# +/mob/living/carbon/cortical_borer/verb/hide_borer() + set category = "Borer.Hostless" + set name = "Hide" + set desc = "Become invisible to the common eye." + + if(host_mob) + to_chat(usr, SPAN_WARNING("You cannot do this while you're inside a host.")) + return FALSE + + if(stat != CONSCIOUS) + return FALSE + + 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 + return TRUE + +/mob/living/carbon/cortical_borer/verb/dominate_victim() + set category = "Borer.Hostless" + 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 FALSE + if(host_mob) + to_chat(src, SPAN_XENOWARNING("You cannot do that from within a host body.")) + return FALSE + if(stat) + to_chat(src, SPAN_XENOWARNING("You cannot do that in your current state.")) + return FALSE + if(attempting_to_dominate) + to_chat(src, SPAN_XENOWARNING("You're already targeting someone!")) + return FALSE + var/list/choices = list() + for(var/mob/living/carbon/C in view(3,src)) + 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 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 + 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 FALSE + if(M.has_brain_worms()) + to_chat(src, SPAN_XENOWARNING("You cannot dominate someone who is already infested!")) + attempting_to_dominate = FALSE + return FALSE + if(is_mob_incapacitated()) + attempting_to_dominate = FALSE + 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 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 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() + set category = "Borer.Controlling" + set name = "Torment Host" + set desc = "Punish your host with agony." + + var/mob/living/carbon/cortical_borer/the_borer = has_brain_worms() + + if(!the_borer) + return FALSE + + if(the_borer.host_brain) + to_chat(src, SPAN_XENONOTICE("You send a punishing spike of psychic agony lancing into your host's brain.")) + to_chat(the_borer.host_brain, SPAN_HIGHDANGER("Horrific, burning agony lances through you, ripping a soundless scream from your trapped mind!")) + return TRUE + + +/mob/living/carbon/proc/spawn_larvae() + set category = "Borer.Controlling" + set name = "Reproduce" + set desc = "Spawn a new borer." + + var/mob/living/carbon/cortical_borer/brainworm = has_brain_worms() + + if(!brainworm) + return FALSE + if(brainworm.can_reproduce) + if(brainworm.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!")) + if(brainworm.generation <= 1) + brainworm.enzymes -= BORER_LARVAE_COST + else + brainworm.enzymes = 0 + var/turf/T = get_turf(src) + T.add_vomit_floor() + brainworm.contaminant = 0 + var/repro = max(brainworm.can_reproduce - 1, 0) + var/list/ancestors = brainworm.ancestry + ancestors += real_name + + var/mob/living/carbon/cortical_borer/birthed = new /mob/living/carbon/cortical_borer(T, brainworm.generation + 1, TRUE, repro, brainworm.borer_flags_targets, ancestors) + brainworm.offspring += birthed.real_name + + var/obj/item/grab/grab_effect = get_held_item() + if(istype(grab_effect) && ishuman(grab_effect.grabbed_thing)) + var/mob/living/carbon/human/human_grab = grab_effect.grabbed_thing + if(grab_level >= GRAB_AGGRESSIVE) + birthed.perform_infestation(human_grab) + else + birthed.hide_borer() + return TRUE + else + to_chat(src, SPAN_XENONOTICE("You need at least [BORER_LARVAE_COST] enzymes to reproduce!")) + return FALSE + else + to_chat(src, SPAN_XENOWARNING("You are not allowed to reproduce!")) + return FALSE + +/mob/living/carbon/cortical_borer/proc/get_possible_chems() + var/list/possibilities = list() + for(var/datum/borer_chem/chem in GLOB.brainlink.borer_chemicals) + possibilities += chem + for(var/datum/borer_chem/chem in GLOB.brainlink.synthesized_chems) + possibilities += chem + return possibilities + +/mob/living/carbon/cortical_borer/verb/secrete_chemicals() + set category = "Borer.Infested" + set name = "Secrete Chemicals" + set desc = "Push some chemicals into your host's bloodstream." + + if(!host_mob) + to_chat(src, SPAN_XENOWARNING("You are not inside a host body.")) + return FALSE + + if(stat) + to_chat(src, SPAN_XENOWARNING("You cannot secrete chemicals in your current state.")) + return FALSE + + if(borer_flags_status & BORER_STATUS_DOCILE) + to_chat(src, SPAN_XENOWARNING("You are feeling far too docile to do that.")) + return FALSE + + var/content = "" + + content += "" + + if(ishuman(host_mob)) + var/mob/living/carbon/human/human_host + human_host = host_mob + for(var/datum/borer_chem/chem_datum in get_possible_chems()) + if(chem_datum.species != "Universal") + if(isspeciesyautja(human_host)) + if(chem_datum.species != SPECIES_YAUTJA) + continue + else if(chem_datum.species != SPECIES_HUMAN) + continue + if(chem_datum.restricted && !restricted_chems_allowed) + continue + var/datum/borer_chem/current_chem = chem_datum + var/chem = current_chem.chem_id + var/datum/reagent/R = GLOB.chemical_reagents_list[chem] + if(R) + content += "" + + content += "
[current_chem.quantity] units of [current_chem.chem_name] ([current_chem.cost] Enzymes)

[current_chem.desc]

" + + var/html = get_html_template(content) + + usr << browse(html, "window=ViewBorer\ref[src]Chems;size=585x400") + + return TRUE + + +/mob/living/carbon/cortical_borer/verb/learn_chemicals() + set category = "Borer.Infested" + set name = "Replicate Chemical" + set desc = "Study a chemical compound for replication." + + if(!host_mob) + to_chat(src, SPAN_XENOWARNING("You are not inside a host body.")) + return FALSE + + if(stat) + to_chat(src, SPAN_XENOWARNING("You cannot replicate chemicals in your current state.")) + return FALSE + + if(borer_flags_status & BORER_STATUS_DOCILE) + to_chat(src, SPAN_XENOWARNING("You are feeling far too docile to do that.")) + return FALSE + + if(enzymes < BORER_REPLICATE_COST) + to_chat(src, SPAN_XENONOTICE("You need at least [BORER_REPLICATE_COST] enzymes to attempt chemical replication!")) + return FALSE + + var/list/options = list() + var/list/options_datums = list() + var/list/existing_chems = list() + for(var/datum/borer_chem/existing_chem in GLOB.brainlink.borer_chemicals) + if(!(existing_chem.chem_id in existing_chems)) + existing_chems += existing_chem.chem_id + for(var/datum/borer_chem/existing_chem in GLOB.brainlink.synthesized_chems) + if(!(existing_chem.chem_id in existing_chems)) + existing_chems += existing_chem.chem_id + + for(var/datum/reagent/potential_chem in host_mob.reagents?.reagent_list) + if(potential_chem.id in existing_chems) + continue + if(potential_chem.volume < potential_chem.overdose) + continue + options += potential_chem.id + options_datums[potential_chem.id] = potential_chem + + if(!options.len) + to_chat(src, SPAN_XENONOTICE("There are no chemicals within your host you are able to replicate!")) + return FALSE + + var/choice = tgui_input_list(usr, "Which option do you wish to attempt replication for?", "Choose Option", options, 20 SECONDS) + if(!choice) + return FALSE + if(tgui_alert(usr, "Do you wish to attempt replication of '[choice]'?", "Confirm", list("Yes", "No"), 10 SECONDS) != "Yes") + return FALSE + var/datum/reagent/chosen = options_datums[choice] + if(!chosen) + return FALSE + + enzymes -= BORER_REPLICATE_COST + var/failure_mult = 0 + for(var/property in chosen.properties) + failure_mult++ + var/failure_chance = failure_mult * 10 + + chosen.volume = (chosen.volume / 10) + + if(prob(failure_chance)) + to_chat(src, SPAN_XENOWARNING("Replication failed! This chemical has a failure chance of [failure_chance]%!")) + return FALSE + + var/datum/borer_chem/synthesised/new_chem = new + new_chem.chem_id = chosen.id + new_chem.chem_name = chosen.name + new_chem.desc = chosen.description + var/n_species = host_mob.get_species() + if(!n_species) + n_species = "UNSET" + new_chem.species = n_species + + var/new_cost = (5 * failure_chance) + if(!new_cost) + new_cost = 20 + new_chem.cost = new_cost + + var/new_quantity = chosen.overdose / 3 + if(!new_quantity) + new_quantity = 10 + new_chem.quantity = new_quantity + + GLOB.brainlink.synthesized_chems += new_chem + to_chat(src, SPAN_XENONOTICE("Replication successful!")) + GLOB.brainlink.impulse_broadcast("[real_name] has replicated [new_chem.chem_name]!", "Small") + + return TRUE + + +//############# Communication Procs ############# +/mob/living/carbon/cortical_borer/verb/Communicate() + set category = "Borer.Infested" + set name = "Converse with Host" + set desc = "Send a silent message to your host." + + if(!host_mob) + to_chat(src, "You do not have a host to communicate with!") + return FALSE + + if(host_mob.stat == DEAD) + to_chat(src, SPAN_XENODANGER("Not even you can commune with the dead.")) + return FALSE + + 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 FALSE + + if(src && !QDELETED(src) && !QDELETED(host_mob)) + var/say_string = (borer_flags_status & BORER_STATUS_DOCILE) ? "slurs" :"states" + if(host_mob) + to_chat(host_mob, SPAN_XENO("[real_name] [say_string]: [input]"), type = MESSAGE_TYPE_RADIO) + show_blurb(host_mob, 15, input, TRUE, "center", "center", COLOR_BROWN, null, null, 1) + log_say("BORER: ([key_name(src)] to [key_name(host_mob)]) [input]", src) + to_chat(src, SPAN_XENO("[real_name] [say_string]: [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: ([real_name] to [host_mob.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" + set category = "Borer.Misc" + 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.Host" + set desc = "Communicate mentally with your borer." + + + var/mob/living/carbon/cortical_borer/borer = has_brain_worms() + if(!borer) + return FALSE + + if(stat == DEAD) + to_chat(src, SPAN_XENODANGER("You're dead, Jim.")) + return FALSE + + var/input = stripped_input(src, "Please enter a message to tell the borer.", "Message", "") + if(!input) + return FALSE + + to_chat(borer, SPAN_XENO("[src.real_name] says: [input]"), type = MESSAGE_TYPE_RADIO) + show_blurb(borer, 15, input, TRUE, "center", "center", COLOR_BROWN, null, null, 1) + log_say("BORER: ([key_name(src)] to [key_name(borer)]) [input]", src) + 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 + dead.show_message(SPAN_BORER("BORER: ([name][track_host] to [borer.real_name]) says: [input]"), SHOW_MESSAGE_VISIBLE) + return TRUE + +/mob/living/proc/trapped_mind_comm() + set name = "Converse with Trapped Mind" + set category = "Borer.Controlling" + set desc = "Communicate mentally with the trapped mind of your host." + + + var/mob/living/carbon/cortical_borer/borer = has_brain_worms() + if(!borer || !borer.host_brain) + return FALSE + var/mob/living/captive_brain/CB = borer.host_brain + var/input = stripped_input(src, "Please enter a message to tell the trapped mind.", "Message", "") + if(!input) + return FALSE + + to_chat(CB, SPAN_XENO("[borer.real_name] says: [input]"), type = MESSAGE_TYPE_RADIO) + log_say("BORER: ([key_name(src)] to [key_name(CB)]) [input]", borer) + to_chat(src, SPAN_XENO("[borer.real_name] 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: ([borer.real_name][track_borer] to [real_name] (trapped mind)) says: [input]"), SHOW_MESSAGE_VISIBLE) + return TRUE + + + +//############# TOPIC ############# +/mob/living/carbon/cortical_borer/Topic(href, href_list, hsrc) + if(href_list["borer_use_chem"]) + locate(href_list["src"]) + if(!isborer(src)) + return FALSE + if(borer_flags_status & BORER_STATUS_DOCILE) + to_chat(src, SPAN_XENOWARNING("You are feeling far too docile to do that.")) + return FALSE + + var/topic_chem = href_list["borer_use_chem"] + var/datum/borer_chem/current_chem = null + + for(var/datum/borer_chem/chem_datum in GLOB.brainlink.borer_chemicals) + current_chem = chem_datum + if(current_chem.chem_id == topic_chem) + break + if(current_chem.chem_id != topic_chem) + for(var/datum/borer_chem/chem_datum in GLOB.brainlink.synthesized_chems) + current_chem = chem_datum + if(current_chem.chem_id == topic_chem) + break + + if(!current_chem || !host_mob || (borer_flags_status & BORER_STATUS_CONTROLLING) || !src || stat) + to_chat(src, SPAN_WARNING("ERROR: CHEM FAILURE - CONTACT FOREST2001")) + return FALSE + var/datum/reagent/R = GLOB.chemical_reagents_list[current_chem.chem_id] + if(enzymes < current_chem.cost) + to_chat(src, SPAN_XENOWARNING("You need [current_chem.cost] enzymes stored to secrete [current_chem.chem_name]!")) + return FALSE + var/contamination = round(current_chem.cost / 10) + if(current_chem.impure && ((contaminant + contamination) > max_contaminant)) + to_chat(src, SPAN_XENOWARNING("You are too contaminated to secrete [current_chem.chem_name]!")) + return FALSE + to_chat(src, SPAN_XENONOTICE("You squirt a measure of [current_chem.chem_name] from your reservoirs into [host_mob]'s bloodstream.")) + contaminant += contamination + host_mob.reagents.add_reagent(current_chem.chem_id, current_chem.quantity) + enzymes -= current_chem.cost + log_interact(src, host_mob, "[key_name(src)] has injected [current_chem.quantity] units of [R.name] into their host, [key_name(host_mob)]") + + // 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(current_chem) + return TRUE + ..() + + + +/client/proc/borer_broadcast(msg as text) + set category = "Admin.Factions" + set name = "Borer Impulse" + + if(!src.admin_holder || !(admin_holder.rights & R_MOD)) + to_chat(src, "Only staff members may talk on this channel.") + return + + msg = copytext(sanitize(msg), 1, MAX_MESSAGE_LEN) + + if(!msg) + return + + message_admins("Borer Impulse: [key_name(src)] : [msg]") + + msg = process_chat_markup(msg, list("*")) + + GLOB.brainlink.impulse_broadcast(msg) + + +/mob/living/carbon/human/proc/make_borer_host(worm_generation = 1, worm_repro = 1) + if(has_brain_worms()) + return FALSE + var/mob/living/carbon/cortical_borer/birthed = new /mob/living/carbon/cortical_borer(src, worm_generation, TRUE, worm_repro) + birthed.perform_infestation(src) + log_admin("[key_name(src)] was infected with a Cortical Borer by proccall.") + return TRUE diff --git a/code/modules/borer/borer_wip.dm b/code/modules/borer/borer_wip.dm new file mode 100644 index 000000000000..4bc9ce949cbb --- /dev/null +++ b/code/modules/borer/borer_wip.dm @@ -0,0 +1,100 @@ +/mob/living/carbon/cortical_borer/proc/transfer_host() + set category = "Borer.Controlling" + set name = "Transfer Host" + set desc = "Transfer from one host to another by direct contact." + + if(!host_mob) + to_chat(src, "You must be in a host to transfer from one to another!.") + return FALSE + if(stat) + to_chat(host_mob, "You cannot infest a target in your current state.") + return FALSE + var/obj/item/grab/grab_effect = host_mob.get_held_item() + if(!istype(grab_effect) || !ishuman(grab_effect.grabbed_thing)) + to_chat(host_mob, SPAN_WARNING("You need to be holding a humanoid target to do this!")) + return FALSE + if(host_mob.grab_level < GRAB_AGGRESSIVE) + to_chat(host_mob, SPAN_WARNING("You need a better grip to do that!")) + return FALSE + + var/mob/living/carbon/target = grab_effect.grabbed_thing + + if(!target || !host_mob.Adjacent(target)) + return FALSE + if(target.stat == DEAD) + var/mob/living/carbon/human/human_target + if(ishuman(target)) + human_target = target + if(isxeno(target) || (target.status_flags & PERMANENTLY_DEAD) || human_target && human_target.undefibbable) + to_chat(host_mob, SPAN_WARNING("You cannot infest the dead.")) + return FALSE + if(target.has_brain_worms()) + to_chat(host_mob, SPAN_WARNING("You cannot infest someone who is already infested!")) + return FALSE + if(host_mob.is_mob_incapacitated()) + to_chat(host_mob, "You cannot infest a target in your current state.") + return FALSE + + host_mob.visible_message(SPAN_WARNING("[src] leans forward, holding their head beside that of [target]."), SPAN_NOTICE("You position your host's head beside [target]'s, reading to crawl from one ear to another.")) + if(!do_after(host_mob, 50, INTERRUPT_ALL_OUT_OF_RANGE, BUSY_ICON_HOSTILE, target)) + host_mob.visible_message(SPAN_NOTICE("[src] draws back from [target]."), SPAN_WARNING("You withdraw back into your current host as [target] escapes your clutches.")) + return FALSE + if(!target || !host_mob) + return FALSE + if(stat) + to_chat(host_mob, SPAN_XENOWARNING("You cannot change host in your current state.")) + return FALSE + if(!host_mob.Adjacent(target)) + to_chat(host_mob, "They are no longer in range!") + return FALSE + + to_chat(host_mob, 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!")) + detach() + leave_host() + perform_infestation(target) + return TRUE + + +/mob/living/carbon/cortical_borer/proc/make_alpha() + real_name = "Alpha" + generation = 0 + can_reproduce = 2 + max_contaminant = 500 + max_enzymes = 2000 + enzyme_rate = 3 + maxHealth = 500 + health = 500 + restricted_chems_allowed = TRUE + + actions_hostless += /datum/action/innate/borer/update_directive + actions_humanoidhost += /datum/action/innate/borer/update_directive + actions_xenohost += /datum/action/innate/borer/update_directive + actions_control += /datum/action/innate/borer/update_directive + give_action(src, /datum/action/innate/borer/update_directive) + + +/datum/action/innate/borer/update_directive + name = "Change Directive" + action_icon_state = "borer_directive" + +/datum/action/innate/borer/update_directive/action_activate() + . = ..() + var/mob/living/carbon/cortical_borer/the_borer = owner + the_borer.update_directive() + +/mob/living/carbon/cortical_borer/proc/update_directive() + set category = "Borer.Misc" + set name = "Update Directive" + set desc = "Update the Cortical Directive." + + if(generation > 0) + to_chat(src, SPAN_BOLDWARNING("You cannot update the directive, you are not the Alpha!")) + return FALSE + + var/new_directive = tgui_input_text(src, "What should the new directive be?", "Cortical Directive", GLOB.brainlink.cortical_directive) + if(!new_directive || new_directive == GLOB.brainlink.cortical_directive) + return FALSE + + GLOB.brainlink.update_directive(new_directive) diff --git a/code/modules/mob/dead/observer/orbit.dm b/code/modules/mob/dead/observer/orbit.dm index 4bc4abf7036c..18b88ace5477 100644 --- a/code/modules/mob/dead/observer/orbit.dm +++ b/code/modules/mob/dead/observer/orbit.dm @@ -48,6 +48,7 @@ /datum/orbit_menu/ui_static_data(mob/user) var/list/data = list() + var/list/special_mobs = list() var/list/humans = list() var/list/marines = list() var/list/survivors = list() @@ -112,6 +113,9 @@ var/mob/living/player = M serialized["health"] = floor(player.health / player.maxHealth * 100) + if(player.special_mob) + special_mobs += list(serialized) + if(isxeno(player)) var/mob/living/carbon/xenomorph/xeno = player if(xeno.caste) @@ -180,6 +184,7 @@ if(isanimal(player)) animals += list(serialized) + data["special_mobs"] = special_mobs data["humans"] = humans data["marines"] = marines data["survivors"] = survivors diff --git a/code/modules/mob/language/languages.dm b/code/modules/mob/language/languages.dm index cfa023c7d9cd..92b56b166136 100644 --- a/code/modules/mob/language/languages.dm +++ b/code/modules/mob/language/languages.dm @@ -205,3 +205,48 @@ 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/carbon/speaker, message, speaker_mask, death) + var/mob/living/carbon/cortical_borer/the_borer + if(!message) return FALSE + if(isborer(speaker)) + the_borer = speaker + else if(speaker.has_brain_worms()) + the_borer = speaker.has_brain_worms() + if(the_borer) + speaker_mask = the_borer.real_name + 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]") + + 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]") + return TRUE diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm index 03a8abef22af..d7840fa26f39 100644 --- a/code/modules/mob/living/carbon/carbon_defines.dm +++ b/code/modules/mob/living/carbon/carbon_defines.dm @@ -28,6 +28,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 1bd9562c2cf5..dd581ecea11c 100644 --- a/code/modules/mob/living/carbon/human/death.dm +++ b/code/modules/mob/living/carbon/human/death.dm @@ -59,7 +59,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() diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 2c5ddf44b3ee..611865a75daa 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -132,6 +132,28 @@ if(SShijack.sd_unlocked) . += "Self Destruct Status: [SShijack.get_sd_eta()]" + var/mob/living/carbon/cortical_borer/the_borer = has_brain_worms() + if(the_borer && (the_borer.borer_flags_status & BORER_STATUS_CONTROLLING)) + + var/CR = "Yes" + if(!the_borer.can_reproduce) + CR = "Forbidden" + else if((the_borer.enzymes < BORER_LARVAE_COST)) + CR = "No" + + . += "" + . += "Cortical Directive: [GLOB.brainlink.cortical_directive]" + . += "Borer: CONTROLLING" + . += "Name: [the_borer.real_name]" + . += "Can Reproduce: [CR]" + . += "Enzymes: [round(the_borer.enzymes)]/[round(the_borer.max_enzymes)]" + . += "Health: [the_borer.health]/[the_borer.maxHealth]" + . += "Injuries: Brute:[round(the_borer.getBruteLoss())] Burn:[round(the_borer.getFireLoss())] Toxin:[round(the_borer.getToxLoss())]" + . += "" + . += "Host Brain Damage: [brainloss]/100" + . += "Host Blood Level: [blood_volume / 5.6]%" + + /mob/living/carbon/human/ex_act(severity, direction, datum/cause_data/cause_data) if(body_position == LYING_DOWN) severity *= EXPLOSION_PRONE_MULTIPLIER diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm index e44106d90abb..2d1f012fd465 100644 --- a/code/modules/mob/living/carbon/human/human_defines.dm +++ b/code/modules/mob/living/carbon/human/human_defines.dm @@ -137,7 +137,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, XENO_EXECUTE, HUNTER_CLAN, HUNTER_HUD, FACTION_HUD, HOLOCARD_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, XENO_EXECUTE, HUNTER_CLAN, HUNTER_HUD, FACTION_HUD, HOLOCARD_HUD, 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/human/life.dm b/code/modules/mob/living/carbon/human/life.dm index 1a43138421e4..5608373977de 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/the_borer = has_brain_worms() + if(the_borer) + the_borer.host_death(TRUE) med_hud_set_status() else if(stat != DEAD) diff --git a/code/modules/mob/living/carbon/human/life/handle_chemicals_in_body.dm b/code/modules/mob/living/carbon/human/life/handle_chemicals_in_body.dm index 9bf275a5448a..4933137c890c 100644 --- a/code/modules/mob/living/carbon/human/life/handle_chemicals_in_body.dm +++ b/code/modules/mob/living/carbon/human/life/handle_chemicals_in_body.dm @@ -38,7 +38,7 @@ /mob/living/carbon/human/proc/handle_necro_chemicals_in_body(delta_time) SHOULD_NOT_SLEEP(TRUE) if(!reagents || undefibbable) - return // Double checking due to Life() funny background=1 + return FALSE // Double checking due to Life() funny background=1 var/has_cryo_medicine = reagents.get_reagent_amount("cryoxadone") >= 1 || reagents.get_reagent_amount("clonexadone") >= 1 if(has_cryo_medicine) @@ -47,7 +47,7 @@ has_cryo_medicine = FALSE for(var/datum/reagent/cur_reagent in reagents.reagent_list) - if(!has_cryo_medicine && !istype(cur_reagent, /datum/reagent/generated)) + if(!has_cryo_medicine && !istype(cur_reagent, /datum/reagent/generated) && !istype(cur_reagent, /datum/reagent/borer)) continue var/list/mods = list( REAGENT_EFFECT = TRUE, @@ -64,10 +64,11 @@ mods[mod] |= results[mod] if(mods[REAGENT_CANCEL]) - return + return FALSE if(mods[REAGENT_FORCE]) cur_reagent.handle_processing(src, mods, delta_time) cur_reagent.holder.remove_reagent(cur_reagent.id, cur_reagent.custom_metabolism * delta_time) cur_reagent.handle_dead_processing(src, mods, delta_time) + return TRUE diff --git a/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm b/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm index b6db7210b351..006a678e460b 100644 --- a/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm +++ b/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm @@ -145,6 +145,26 @@ else . += "Hive Orders: -" + var/mob/living/carbon/cortical_borer/the_borer = has_brain_worms() + if(the_borer && (the_borer.borer_flags_status & BORER_STATUS_CONTROLLING)) + + var/CR = "Yes" + if(!the_borer.can_reproduce) + CR = "Forbidden" + else if((the_borer.enzymes < BORER_LARVAE_COST)) + CR = "No" + + . += "" + . += "Cortical Directive: [GLOB.brainlink.cortical_directive]" + . += "Borer: CONTROLLING" + . += "Name: [the_borer.real_name]" + . += "Can Reproduce: [CR]" + . += "Enzymes: [round(the_borer.enzymes)]/[round(the_borer.max_enzymes)]" + . += "Health: [the_borer.health]/[the_borer.maxHealth]" + . += "Injuries: Brute:[round(the_borer.getBruteLoss())] Burn:[round(the_borer.getFireLoss())] Toxin:[round(the_borer.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/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm b/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm index eed2dce5f7a8..2d7aa1ca50ac 100644 --- a/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm +++ b/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm @@ -45,7 +45,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, HUD_BRAINWORM) unacidable = TRUE rebounds = TRUE faction = FACTION_XENOMORPH diff --git a/code/modules/mob/living/carbon/xenomorph/attack_alien.dm b/code/modules/mob/living/carbon/xenomorph/attack_alien.dm index de0cefeea76d..6f2fc3e6b9a3 100644 --- a/code/modules/mob/living/carbon/xenomorph/attack_alien.dm +++ b/code/modules/mob/living/carbon/xenomorph/attack_alien.dm @@ -50,7 +50,7 @@ if(attacking_xeno.behavior_delegate && attacking_xeno.behavior_delegate.handle_slash(src)) return XENO_NO_DELAY_ACTION - if(stat == DEAD) + if(stat == DEAD || (status_flags & FAKEDEATH)) to_chat(attacking_xeno, SPAN_WARNING("[src] is dead, why would we want to touch it?")) return XENO_NO_DELAY_ACTION @@ -294,13 +294,13 @@ M.animation_attack_on(src) /mob/living/proc/is_xeno_grabbable() - if(stat == DEAD) + if(stat == DEAD || (status_flags & FAKEDEATH)) return FALSE return TRUE /mob/living/carbon/human/is_xeno_grabbable() - if(stat != DEAD || chestburst) + if(((stat != DEAD) && !(status_flags & FAKEDEATH)) || chestburst) return TRUE if(status_flags & XENO_HOST) diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index 88bd8e09c386..58dbea746e3c 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -85,7 +85,9 @@ /obj/item/device/radio, /obj/structure/machinery/camera, /obj/limb, - /obj/item/alien_embryo + /obj/item/alien_embryo, + /mob/living/carbon/cortical_borer, + /mob/living/captive_brain ) //blood.dm ///How much blood the mob has @@ -140,3 +142,6 @@ /// flipped icon_states for weed_food (needs to be the same length as weed_food_states) var/list/weed_food_states_flipped = list("human_1_f","human_2_f","human_3_f","human_4_f","human_5_f") + + /// Used to highlight on follow menu. + var/special_mob = FALSE diff --git a/code/modules/mob/living/living_healthscan.dm b/code/modules/mob/living/living_healthscan.dm index 6739e7046761..0700dabea397 100644 --- a/code/modules/mob/living/living_healthscan.dm +++ b/code/modules/mob/living/living_healthscan.dm @@ -159,7 +159,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/mob/living/say.dm b/code/modules/mob/living/say.dm index 7f3af1449266..76f749563b12 100644 --- a/code/modules/mob/living/say.dm +++ b/code/modules/mob/living/say.dm @@ -121,6 +121,10 @@ GLOBAL_LIST_INIT(department_radio_keys, list( var/obj/item/clothing/worn_item = O if((O.flags_atom & USES_HEARING) || ((istype(worn_item) && worn_item.accessories))) 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/structure/surface)) var/obj/structure/surface/table = I hearturfs += table.locs[1] @@ -139,6 +143,10 @@ GLOBAL_LIST_INIT(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 f3b8da1a2d76..9aa96259a1a1 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -226,6 +226,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]") diff --git a/code/modules/reagents/chemistry_machinery/reagent_grinder.dm b/code/modules/reagents/chemistry_machinery/reagent_grinder.dm index 99b19a75f527..0d16988a6fd7 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("borerenzyme" = 0), ) @@ -414,6 +415,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("borerenzyme",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 051befa14fba..9cef455c2bcd 100644 --- a/code/modules/reagents/chemistry_properties/prop_positive.dm +++ b/code/modules/reagents/chemistry_properties/prop_positive.dm @@ -503,8 +503,19 @@ else embryo.counter = embryo.per_stage_hugged_time + current_human.chem_effect_flags |= CHEM_EFFECT_ANTI_PARASITE + to_chat(current_human, 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.borer_flags_status & BORER_STATUS_CONTROLLING) + player_2.detach() + 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!")) /datum/chem_property/positive/antiparasitic/process_critical(mob/living/M, potency = 1) M.apply_damage(POTENCY_MULTIPLIER_VHIGH*potency, TOX) @@ -597,9 +608,9 @@ dead.apply_damage(-potency * POTENCY_MULTIPLIER_LOW, TOX) dead.apply_damage(-potency * POTENCY_MULTIPLIER_LOW, CLONE) if(dead.health < HEALTH_THRESHOLD_DEAD) - return + return TRUE if(!COOLDOWN_FINISHED(src, ghost_notif)) - return + return TRUE var/mob/dead/observer/ghost = dead.get_ghost() if(ghost?.client) COOLDOWN_START(src, ghost_notif, 30 SECONDS) diff --git a/code/modules/reagents/chemistry_reactions/other.dm b/code/modules/reagents/chemistry_reactions/other.dm index 08402e82ed7f..71d1eab42ae1 100644 --- a/code/modules/reagents/chemistry_reactions/other.dm +++ b/code/modules/reagents/chemistry_reactions/other.dm @@ -472,3 +472,11 @@ result = "eggplasma" required_reagents = list("blood" = 10, "eggplasma" = 1) result_amount = 2 + +/datum/chemical_reaction/borer_cure + name = "Anti-Enzyme" + id = "borercure" + result = "borercure" + required_reagents = list("borerenzyme" = 2, "anti_toxin" = 4) + result_amount = 3 + mob_react = FALSE diff --git a/code/modules/surgery/brainworm.dm b/code/modules/surgery/brainworm.dm new file mode 100644 index 000000000000..7ad8223a0fc8 --- /dev/null +++ b/code/modules/surgery/brainworm.dm @@ -0,0 +1,81 @@ +/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() + 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].") + +/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/code/span_macros.dm b/code/span_macros.dm index 218f333d7df9..3f6a301a6c4b 100644 --- a/code/span_macros.dm +++ b/code/span_macros.dm @@ -85,6 +85,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 ceb6e42f4a4f..3e475abd1ece 100644 --- a/colonialmarines.dme +++ b/colonialmarines.dme @@ -38,6 +38,7 @@ #include "code\__DEFINES\autofire.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" @@ -1515,6 +1516,12 @@ #include "code\modules\autowiki\autowiki.dm" #include "code\modules\autowiki\pages\_page.dm" #include "code\modules\autowiki\pages\guns.dm" +#include "code\modules\borer\borer.dm" +#include "code\modules\borer\borer_chemicals.dm" +#include "code\modules\borer\borer_egg.dm" +#include "code\modules\borer\borer_html.dm" +#include "code\modules\borer\borer_procs.dm" +#include "code\modules\borer\borer_wip.dm" #include "code\modules\buildmode\bm-mode.dm" #include "code\modules\buildmode\buildmode.dm" #include "code\modules\buildmode\buttons.dm" @@ -2327,6 +2334,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\generic.dm" diff --git a/icons/mob/brainslug.dmi b/icons/mob/brainslug.dmi new file mode 100644 index 000000000000..f69d6d3f2930 Binary files /dev/null and b/icons/mob/brainslug.dmi differ diff --git a/icons/mob/hud/actions_borer.dmi b/icons/mob/hud/actions_borer.dmi new file mode 100644 index 000000000000..30e165783275 Binary files /dev/null and b/icons/mob/hud/actions_borer.dmi differ diff --git a/icons/mob/hud/hud.dmi b/icons/mob/hud/hud.dmi index 8d89fb781264..35e7c303a72f 100644 Binary files a/icons/mob/hud/hud.dmi and b/icons/mob/hud/hud.dmi differ diff --git a/tgui/packages/tgui/interfaces/BorerChemDispenser.js b/tgui/packages/tgui/interfaces/BorerChemDispenser.js new file mode 100644 index 000000000000..e3a2d28b6f4a --- /dev/null +++ b/tgui/packages/tgui/interfaces/BorerChemDispenser.js @@ -0,0 +1 @@ +// I have no idea what I am doing for TGUI. It's been too long since using JS. diff --git a/tgui/packages/tgui/interfaces/HealthScan.jsx b/tgui/packages/tgui/interfaces/HealthScan.jsx index 1df8a86c06e2..b1faa80b4dc9 100644 --- a/tgui/packages/tgui/interfaces/HealthScan.jsx +++ b/tgui/packages/tgui/interfaces/HealthScan.jsx @@ -42,6 +42,7 @@ export const HealthScan = (props) => { implants, core_fracture, lung_ruptured, + brainslug, hugged, detail_level, permadead, @@ -237,6 +238,7 @@ export const HealthScan = (props) => { {pulse} {implants || + (brainslug && bodyscanner) || hugged || core_fracture || (lung_ruptured && bodyscanner) ? ( @@ -257,6 +259,9 @@ export const HealthScan = (props) => { {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 36ceb6359cde..8800ba3e7f37 100644 --- a/tgui/packages/tgui/interfaces/Orbit/index.tsx +++ b/tgui/packages/tgui/interfaces/Orbit/index.tsx @@ -282,6 +282,7 @@ const GroupedObservable = (props: { const ObservableContent = () => { const { data } = useBackend(); const { + special_mobs = [], humans = [], marines = [], survivors = [], @@ -309,6 +310,11 @@ const ObservableContent = () => { return ( +