diff --git a/code/__defines/borer.dm b/code/__defines/borer.dm new file mode 100644 index 0000000000..3cf697c672 --- /dev/null +++ b/code/__defines/borer.dm @@ -0,0 +1,6 @@ +#define BORER_MAX_CHEMS 80 +#define BORER_POWER_COST_TORTURE 5 +#define BORER_POWER_COST_SECRETE 25 +#define BORER_POWER_COST_REPRODUCE 50 + +#define BORER_PSYCHIC_SAY_MINIMUM_CHEMS 20 diff --git a/code/_onclick/hud/human.dm b/code/_onclick/hud/human.dm index 546b6d3979..a21cc66c28 100644 --- a/code/_onclick/hud/human.dm +++ b/code/_onclick/hud/human.dm @@ -410,6 +410,13 @@ name = "chemical storage" icon_state = "power_display" +/atom/movable/screen/borer + invisibility = INVISIBILITY_ABSTRACT + +/atom/movable/screen/borer/chems + name = "chemical storage" + icon_state = "power_display" + /atom/movable/screen/wizard invisibility = INVISIBILITY_ABSTRACT diff --git a/code/game/antagonist/alien/borer.dm b/code/game/antagonist/alien/borer.dm index 04edda17e2..872581998e 100644 --- a/code/game/antagonist/alien/borer.dm +++ b/code/game/antagonist/alien/borer.dm @@ -5,7 +5,7 @@ var/datum/antagonist/borer/borers role_type = BE_ALIEN role_text = "Cortical Borer" role_text_plural = "Cortical Borers" - mob_path = /mob/living/simple_mob/animal/borer + mob_path = /mob/living/simple_mob/animal/borer/roundstart // Use roundstart borer, or ghostcheck makes it take forever to enter mob when assigned bantype = "Borer" welcome_text = "Use your Infest power to crawl into the ear of a host and fuse with their brain. You can only take control temporarily, and at risk of hurting your host, so be clever and careful; your host is encouraged to help you however they can. Talk to your fellow borers with :x." antag_indicator = "brainworm" diff --git a/code/game/antagonist/antagonist_create.dm b/code/game/antagonist/antagonist_create.dm index 22462d1704..e2c4f9aef0 100644 --- a/code/game/antagonist/antagonist_create.dm +++ b/code/game/antagonist/antagonist_create.dm @@ -26,6 +26,8 @@ M = new /mob/living/carbon/human(get_turf(source)) M.real_name = source.real_name M.name = M.real_name + if(!isnull(source.mind)) + source.mind.transfer_to(M) M.ckey = source.ckey add_antagonist(M.mind, 1, 0, 1) // Equip them and move them to spawn. return M diff --git a/code/game/machinery/virtual_reality/vr_console.dm b/code/game/machinery/virtual_reality/vr_console.dm index 1c095b75b7..50ee9d1fa4 100644 --- a/code/game/machinery/virtual_reality/vr_console.dm +++ b/code/game/machinery/virtual_reality/vr_console.dm @@ -192,11 +192,12 @@ update_icon() - if(!M.has_brain_worms()) - update_use_power(USE_POWER_ACTIVE) - enter_vr() - else + if(M.has_brain_worms()) to_chat(user, span_warning("\The [src] rejects [M] with a sharp beep.")) + return + + update_use_power(USE_POWER_ACTIVE) + enter_vr() return /obj/machinery/vr_sleeper/proc/go_out() diff --git a/code/game/objects/items/devices/scanners/health.dm b/code/game/objects/items/devices/scanners/health.dm index ef4793d002..43c3000867 100644 --- a/code/game/objects/items/devices/scanners/health.dm +++ b/code/game/objects/items/devices/scanners/health.dm @@ -268,7 +268,7 @@ dat += "
" // if (M.reagents && M.reagents.get_reagent_amount(REAGENT_ID_INAPROVALINE)) // user.show_message(span_notice("Bloodstream Analysis located [M.reagents:get_reagent_amount(REAGENT_ID_INAPROVALINE)] units of rejuvenation chemicals.")) - if (M.has_brain_worms()) + if (advscan >= 2 && M.has_brain_worms()) // Borers need to hide dat += span_warning("Subject suffering from aberrant brain activity. Recommend further scanning.") dat += "
" else if (M.getBrainLoss() >= 60 || !M.has_brain()) diff --git a/code/game/objects/items/devices/scanners/motion_tracker.dm b/code/game/objects/items/devices/scanners/motion_tracker.dm index b2e38c665c..27ae896214 100644 --- a/code/game/objects/items/devices/scanners/motion_tracker.dm +++ b/code/game/objects/items/devices/scanners/motion_tracker.dm @@ -19,11 +19,14 @@ /obj/item/motiontracker/Initialize(mapload) RegisterSignal(SSmotiontracker, COMSIG_MOVABLE_MOTIONTRACKER, PROC_REF(handle_motion_tracking)) . = ..() + if(ismob(loc)) + var/mob/M = loc + M.motiontracker_subscribe() /obj/item/motiontracker/Destroy(force, ...) if(ismob(loc)) var/mob/M = loc - M.motiontracker_subscribe() + M.motiontracker_unsubscribe() UnregisterSignal(SSmotiontracker, COMSIG_MOVABLE_MOTIONTRACKER) . = ..() diff --git a/code/modules/mob/death.dm b/code/modules/mob/death.dm index c7acb4e306..ce8a69e516 100644 --- a/code/modules/mob/death.dm +++ b/code/modules/mob/death.dm @@ -71,6 +71,11 @@ if(stat == DEAD) return 0 + + var/mob/living/simple_mob/animal/borer/has_worm = has_brain_worms() + if(has_worm) // This is our host's problem to deal with + has_worm.detatch() + SEND_SIGNAL(src, COMSIG_MOB_DEATH, gibbed) SEND_GLOBAL_SIGNAL(COMSIG_GLOB_MOB_DEATH, src, gibbed) diff --git a/code/modules/mob/living/carbon/carbon_powers.dm b/code/modules/mob/living/carbon/carbon_powers.dm index e49aa30f10..f4fdf84f6c 100644 --- a/code/modules/mob/living/carbon/carbon_powers.dm +++ b/code/modules/mob/living/carbon/carbon_powers.dm @@ -1,66 +1,3 @@ -//Brain slug proc for voluntary removal of control. -/mob/living/carbon/proc/release_control() - - set category = "Abilities.Brainslug" - set name = "Release Control" - set desc = "Release control of your host's body." - - var/mob/living/simple_mob/animal/borer/B = has_brain_worms() - - if(B && B.host_brain) - to_chat(src, span_danger("You withdraw your probosci, releasing control of [B.host_brain]")) - - B.detatch() - - remove_verb(src, /mob/living/carbon/proc/release_control) - remove_verb(src, /mob/living/carbon/proc/punish_host) - remove_verb(src, /mob/living/carbon/proc/spawn_larvae) - - else - to_chat(src, span_danger("ERROR NO BORER OR BRAINMOB DETECTED IN THIS MOB, THIS IS A BUG !")) - -//Brain slug proc for tormenting the host. -/mob/living/carbon/proc/punish_host() - set category = "Abilities.Brainslug" - set name = "Torment host" - set desc = "Punish your host with agony." - - var/mob/living/simple_mob/animal/borer/B = has_brain_worms() - - if(!B) - return - - if(B.host_brain.ckey) - to_chat(src, span_danger("You send a punishing spike of psychic agony lancing into your host's brain.")) - if (!can_feel_pain()) - to_chat(B.host_brain, span_warning("You feel a strange sensation as a foreign influence prods your mind.")) - to_chat(src, span_danger("It doesn't seem to be as effective as you hoped.")) - else - to_chat(B.host_brain, span_danger(span_large("Horrific, burning agony lances through you, ripping a soundless scream from your trapped mind!"))) - -/mob/living/carbon/proc/spawn_larvae() - set category = "Abilities.Brainslug" - set name = "Reproduce" - set desc = "Spawn several young." - - var/mob/living/simple_mob/animal/borer/B = has_brain_worms() - - if(!B) - return - - if(B.chemicals >= 100) - to_chat(src, span_danger("Your host twitches and quivers as you rapidly excrete a larva from your sluglike body.")) - visible_message(span_danger("\The [src] heaves violently, expelling a rush of vomit and a wriggling, sluglike creature!")) - B.chemicals -= 100 - B.has_reproduced = 1 - - vomit(1) - new /mob/living/simple_mob/animal/borer(get_turf(src)) - - else - to_chat(src, span_warning("You do not have enough chemicals stored to reproduce.")) - return - /mob/living/proc/toggle_active_cloaking() // Borrowed from Rogue Star, thanks guys! set category = "Abilities.General" set name = "Toggle Active Cloaking" diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer.dm b/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer.dm index 28a71d1338..759fb0c797 100644 --- a/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer.dm +++ b/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer.dm @@ -21,6 +21,7 @@ status_flags = CANPUSH pass_flags = PASSTABLE movement_cooldown = 1.5 + mob_size = MOB_TINY // no landmines for you universal_understand = TRUE can_be_antagged = TRUE @@ -35,16 +36,37 @@ var/antag = TRUE // If false, will avoid setting up objectives and events var/chemicals = 10 // A resource used for reproduction and powers. - var/max_chemicals = 250 // Max of said resource. var/true_name = null // String used when speaking among other worms. var/controlling = FALSE // Used in human death ceck. var/docile = FALSE // Sugar can stop borers from acting. + var/docile_counter = 0 // How long we are docile for var/has_reproduced = FALSE var/used_dominate // world.time when the dominate power was last used. - var/datum/ghost_query/Q // Used to unregister our signal can_be_drop_prey = FALSE + vent_crawl_time = 30 // faster vent crawler + + var/static/borer_chem_list = list( + // Antag utlity + "Repair Brain Tissue" = list(REAGENT_ID_ALKYSINE, 5), + "Repair Brute" = list(REAGENT_ID_BICARIDINE, 10), + "Repair Burn" = list(REAGENT_ID_KELOTANE, 10), + "Enhance Speed" = list(REAGENT_ID_HYPERZINE, 10), + // Stablization + "Stablize Mind" = list(REAGENT_ID_CITALOPRAM, 10), + "Stablize Nerves" = list(REAGENT_ID_ADRANOL, 10), + "Stablize Bleeding" = list(REAGENT_ID_INAPROVALINE, 5), + "Stablize Infection" = list(REAGENT_ID_SPACEACILLIN, 15), + "Stablize Pain" = list(REAGENT_ID_TRAMADOL, 5), + // Scene tools + "Make Drunk" = list(REAGENT_ID_ETHANOL, 5), + "Cure Drunk" = list(REAGENT_ID_ETHYLREDOXRAZINE, 10), + "Euphoric High" = list(REAGENT_ID_BLISS, 5), + "Mood Stimulant" = list(REAGENT_ID_APHRODISIAC, 5), + // Borer last resport + "Revive Dead Host" = null + ) /mob/living/simple_mob/animal/borer/roundstart roundstart = TRUE @@ -53,18 +75,19 @@ antag = FALSE /mob/living/simple_mob/animal/borer/Login() - ..() + . = ..() if(antag && mind) borers.add_antagonist(mind) /mob/living/simple_mob/animal/borer/Initialize(mapload) add_language("Cortical Link") - add_verb(src, /mob/living/proc/ventcrawl) add_verb(src, /mob/living/proc/hide) + motiontracker_subscribe() true_name = "[pick("Primary","Secondary","Tertiary","Quaternary")] [rand(1000,9999)]" - ..() + + . = ..() if(!roundstart && antag) return INITIALIZE_HINT_LATELOAD @@ -72,52 +95,145 @@ /mob/living/simple_mob/animal/borer/LateInitialize() request_player() +/mob/living/simple_mob/animal/borer/Destroy() + . = ..() + motiontracker_unsubscribe() + QDEL_NULL(ghost_check) + if(host) + detatch() + leave_host() + /mob/living/simple_mob/animal/borer/handle_special() - if(host && !stat && !host.stat) - // Handle docility. - if(host.reagents.has_reagent(REAGENT_ID_SUGAR) && !docile) - var/message = "You feel the soporific flow of sugar in your host's blood, lulling you into docility." - var/target = controlling ? host : src - to_chat(target, span_warning(message)) - docile = TRUE - - else if(docile) - var/message = "You shake off your lethargy as the sugar leaves your host's blood." - var/target = controlling ? host : src - to_chat(target, span_notice(message)) - docile = FALSE - - // Chem regen. - if(chemicals < max_chemicals) - chemicals++ - - // Control stuff. - if(controlling) - if(docile) - to_chat(host, span_warning("You are feeling far too docile to continue controlling your host...")) - host.release_control() - return - - if(prob(5)) - host.adjustBrainLoss(0.1) - - if(prob(host.brainloss/20)) - host.say("*[pick(list("blink","blink_r","choke","aflap","drool","twitch","twitch_v","gasp"))]") + handle_chemicals() + handle_docile() + handle_braindamage() /mob/living/simple_mob/animal/borer/get_status_tab_items() . = ..() - . += "Chemicals: [chemicals]" + . += "Chemicals: [FLOOR(chemicals,1)]" + +/mob/living/simple_mob/animal/borer/proc/handle_chemicals() + if(stat == DEAD || !host || host.stat == DEAD) + return + if(chemicals >= BORER_MAX_CHEMS || docile) + return + + var/chem_before = chemicals + if(controlling) + chemicals += 0.25 + else + chemicals += 0.1 + var/new_chem = FLOOR(chemicals/10,1) + if(new_chem > FLOOR(chem_before/10,1)) + to_chat(host, span_alien("Your chemicals have increased to [new_chem * 10]")) + +/mob/living/simple_mob/animal/borer/proc/handle_docile() + if(stat == DEAD) + docile_counter = 0 + return + + // Start docile + if(host && (host.reagents.has_reagent(REAGENT_ID_SUGAR) || host.ingested.has_reagent(REAGENT_ID_SUGAR))) + docile_counter = 5 SECONDS + if(!docile) + var/message = "You feel the soporific flow of sugar in your host's blood, lulling you into docility." + var/target = controlling ? host : src + to_chat(target, span_danger( message)) + docile = TRUE + + // Drop control if docile + if(controlling && docile) + to_chat(host, span_vdanger("You are feeling far too docile to continue controlling your host...")) + host.release_control() + return + + // wear it off + docile_counter-- + if(docile_counter > 0) + return + + // End docile + if(docile) + to_chat(controlling ? host : src, span_notice("You shake off your lethargy as the sugar leaves your host's blood.")) + docile = FALSE + docile_counter = 0 + +/mob/living/simple_mob/animal/borer/proc/handle_braindamage() + if(QDELETED(src) || !host || QDELETED(host) || !controlling) + return + if(prob(2)) + host.adjustBrainLoss(0.1) + if(prob(host.brainloss/20)) + host.say("*[pick(list("blink","blink_r","choke","aflap","drool","twitch","twitch_v","gasp"))]") + +/mob/living/simple_mob/animal/borer/proc/can_use_power_in_host() + if(QDELETED(src)) + return FALSE + if(!host || QDELETED(host)) + to_chat(src, span_warning("You are not inside a host body.")) + return FALSE + if(stat) + to_chat(controlling ? host : src, span_warning("You cannot that that in your current state.")) + return FALSE + return TRUE + +/mob/living/simple_mob/animal/borer/proc/can_use_power_controlling_host() + if(!can_use_power_in_host()) + return FALSE + if(!controlling) + to_chat(controlling ? host : src, span_warning("You need to be in complete control to do this.")) + return FALSE + if(host.stat) + to_chat(controlling ? host : src, span_warning("Your host is in no condition to do that.")) + return FALSE + return TRUE + +/mob/living/simple_mob/animal/borer/proc/can_use_power_docile() + if(docile) + to_chat(controlling ? host : src, span_info("You are feeling far too docile to do that.")) + return !docile + +/mob/living/simple_mob/animal/borer/proc/use_chems(amount) + if(chemicals < amount) + to_chat(controlling ? host : src, span_warning("You don't have enough chemicals, requires [amount]! Currently you have [FLOOR(chemicals,1)].")) + return FALSE + chemicals -= amount + to_chat(controlling ? host : src, span_info("You use [amount] chemicals, [FLOOR(chemicals,1)] remain.")) + return TRUE + +/mob/living/simple_mob/animal/borer/handle_regular_hud_updates() + . = ..() + if(!.) + return + if(borer_chem_display) + borer_chem_display.invisibility = INVISIBILITY_NONE + switch(chemicals) + if(0 to 9) + borer_chem_display.icon_state = "ling_chems0e" + if(10 to 19) + borer_chem_display.icon_state = "ling_chems10e" + if(20 to 29) + borer_chem_display.icon_state = "ling_chems20e" + if(30 to 39) + borer_chem_display.icon_state = "ling_chems30e" + if(40 to 49) + borer_chem_display.icon_state = "ling_chems40e" + if(50 to 59) + borer_chem_display.icon_state = "ling_chems50e" + if(60 to 69) + borer_chem_display.icon_state = "ling_chems60e" + if(70 to 79) + borer_chem_display.icon_state = "ling_chems70e" + if(80 to INFINITY) + borer_chem_display.icon_state = "ling_chems80e" /mob/living/simple_mob/animal/borer/proc/detatch() if(!host || !controlling) return - if(ishuman(host)) - var/mob/living/carbon/human/H = host - var/obj/item/organ/external/head = H.get_organ(BP_HEAD) - if(head) - head.implants -= src - + var/obj/item/organ/external/head = host.get_organ(BP_HEAD) + if(head) + head.implants -= src controlling = FALSE host.remove_language("Cortical Link") @@ -125,6 +241,7 @@ remove_verb(host, /mob/living/carbon/proc/punish_host) remove_verb(host, /mob/living/carbon/proc/spawn_larvae) + // This entire section is awful and a relic of ancient times. It needs to be replaced if(host_brain) // these are here so bans and multikey warnings are not triggered on the wrong people when ckey is changed. // computer_id and IP are not updated magically on their own in offline mobs -walter0o @@ -160,7 +277,7 @@ host.lastKnownIP = b2h_ip qdel(host_brain) - + // End horrible ip swapping code for bans /mob/living/simple_mob/animal/borer/proc/leave_host() if(!host) @@ -169,32 +286,16 @@ if(host.mind) borers.remove_antagonist(host.mind) - forceMove(get_turf(host)) - + if(!QDELETED(src)) + forceMove(get_turf(host.loc)) unset_machine() - if(ishuman(host)) - var/mob/living/carbon/human/H = host - var/obj/item/organ/external/head = H.get_organ(BP_HEAD) - if(head) - head.implants -= src - + var/obj/item/organ/external/head = host.get_organ(BP_HEAD) + if(head) + head.implants -= src host.unset_machine() host = null -/mob/living/simple_mob/animal/borer/proc/request_player() - Q = new /datum/ghost_query/borer() - RegisterSignal(Q, COMSIG_GHOST_QUERY_COMPLETE, PROC_REF(get_winner)) - Q.query() // This will sleep the proc for awhile. - -/mob/living/simple_mob/animal/borer/proc/get_winner() - SIGNAL_HANDLER - if(Q && Q.candidates.len) //Q should NEVER get deleted but...whatever, sanity. - var/mob/observer/dead/D = Q.candidates[1] - transfer_personality(D) - UnregisterSignal(Q, COMSIG_GHOST_QUERY_COMPLETE) - QDEL_NULL(Q) //get rid of the query - /mob/living/simple_mob/animal/borer/proc/transfer_personality(mob/candidate) if(!candidate) return @@ -206,17 +307,25 @@ mind.assigned_role = JOB_CORTICAL_BORER mind.special_role = JOB_CORTICAL_BORER + // TODO - This needs to be made into something more pref friendly if it's ever going to be used on virgo. to_chat(src, span_notice("You are a cortical borer! You are a brain slug that worms its way \ into the head of its victim. Use stealth, persuasion and your powers of mind control to keep you, \ your host and your eventual spawn safe and warm.")) to_chat(src, "You can speak to your victim with say, to other borers with say :x, and use your Abilities tab to access powers.") /mob/living/simple_mob/animal/borer/cannot_use_vents() - return + return host || stat + +/mob/living/simple_mob/animal/borer/extra_huds(var/datum/hud/hud,var/icon/ui_style,var/list/hud_elements) + // Chem hud + borer_chem_display = new /atom/movable/screen/borer/chems() + borer_chem_display.screen_loc = ui_ling_chemical_display + borer_chem_display.icon_state = "ling_chems" + hud_elements |= borer_chem_display /mob/living/simple_mob/animal/borer/UnarmedAttack(var/atom/A, var/proximity) if(ismob(loc)) - to_chat(src, span_warning("You cannot interact with that from inside a host!")) + to_chat(src, span_notice("You cannot interact with that from inside a host!")) return . = ..() @@ -230,7 +339,7 @@ if(stat >= DEAD) return say_dead(message) - else if(stat) + if(stat) return if(client && client.prefs.muted & MUTE_IC) @@ -247,10 +356,10 @@ return if(!host) - if(chemicals >= 30) + if(chemicals >= BORER_PSYCHIC_SAY_MINIMUM_CHEMS) to_chat(src, span_alien("..You emit a psionic pulse with an encoded message..")) var/list/nearby_mobs = list() - for(var/mob/living/LM in view(src, 1 + round(6 * (chemicals / max_chemicals)))) + for(var/mob/living/LM in view(src, 1 + round(6 * (chemicals / BORER_MAX_CHEMS)))) if(LM == src) continue if(!LM.stat) diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer_captive.dm b/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer_captive.dm index ca5feabcfa..b25d6175ad 100644 --- a/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer_captive.dm +++ b/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer_captive.dm @@ -7,12 +7,12 @@ /mob/living/captive_brain/say(var/message, var/datum/language/speaking = null, var/whispering = 0) - if (src.client) + if(client) if(client.prefs.muted & MUTE_IC) to_chat(src, span_red("You cannot speak in IC (muted).")) return - if(istype(src.loc, /mob/living/simple_mob/animal/borer)) + if(istype(loc, /mob/living/simple_mob/animal/borer)) message = sanitize(message) if (!message) @@ -21,7 +21,7 @@ if (stat == 2) return say_dead(message) - var/mob/living/simple_mob/animal/borer/B = src.loc + var/mob/living/simple_mob/animal/borer/B = loc to_chat(src, "You whisper silently, \"[message]\"") to_chat(B.host, "The captive mind of [src] whispers, \"[message]\"") @@ -41,24 +41,19 @@ /mob/living/captive_brain/process_resist() //Resisting control by an alien mind. - if(istype(src.loc, /mob/living/simple_mob/animal/borer)) - var/mob/living/simple_mob/animal/borer/B = src.loc - var/mob/living/captive_brain/H = src - - to_chat(H, span_danger("You begin doggedly resisting the parasite's control (this will take approximately sixty seconds).")) + if(istype(loc, /mob/living/simple_mob/animal/borer)) + var/mob/living/simple_mob/animal/borer/B = loc + to_chat(src, span_danger("You begin doggedly resisting the parasite's control (this will take approximately sixty seconds).")) to_chat(B.host, span_danger("You feel the captive mind of [src] begin to resist your control.")) - - spawn(rand(200,250)+B.host.brainloss) - if(!B || !B.controlling) return - - B.host.adjustBrainLoss(rand(0.1,0.5)) - to_chat(H, span_danger("With an immense exertion of will, you regain control of your body!")) - to_chat(B.host, span_danger("You feel control of the host brain ripped from your grasp, and retract your probosci before the wild neural impulses can damage you.")) - B.detatch() - remove_verb(src, /mob/living/carbon/proc/release_control) - remove_verb(src, /mob/living/carbon/proc/punish_host) - remove_verb(src, /mob/living/carbon/proc/spawn_larvae) - + addtimer(CALLBACK(src, PROC_REF(break_control_of_borer)), (rand(20,25) + (B.host.brainloss/10)) SECONDS, TIMER_DELETE_ME) return + . = ..() - ..() +/mob/living/captive_brain/proc/break_control_of_borer() + var/mob/living/simple_mob/animal/borer/B = loc + if(!B || !B.controlling) + return + to_chat(src, span_danger("With an immense exertion of will, you regain control of your body!")) + to_chat(B.host, span_danger("You feel control of the host brain ripped from your grasp, and retract your probosci before the wild neural impulses can damage you.")) + B.host.adjustBrainLoss(rand(0.1,0.5)) + B.detatch() diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer_control.dm b/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer_control.dm new file mode 100644 index 0000000000..3a7b28e97c --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer_control.dm @@ -0,0 +1,112 @@ +/** + * Assume control of a borer's host, putting the borer's client in control, and putting the other into a dominated brain. + */ +/mob/living/simple_mob/animal/borer/verb/bond_brain() + set category = "Abilities.Borer" + set name = "Assume Control" + set desc = "Fully connect to the brain of your host." + + if(!can_use_power_in_host()) + return + if(!can_use_power_docile()) + return + if(host.stat == DEAD) + to_chat(src, span_warning("Your host is in no condition to do that.")) + return + + to_chat(src, span_alien("You begin delicately adjusting your connection to the host brain...")) + addtimer(CALLBACK(src, PROC_REF(finish_bond_brain)), 100 + (host.brainloss * 5), TIMER_DELETE_ME) + +// This entire section is awful and a relic of ancient times. It needs to be replaced +/mob/living/simple_mob/animal/borer/proc/finish_bond_brain() + PRIVATE_PROC(TRUE) + RETURN_TYPE(null) + + if(!host || QDELETED(src) || controlling) + return + if(host.stat == DEAD) + to_chat(src, span_warning("Your host is in no condition to do that.")) + return + + to_chat(src, span_alien("You plunge your probosci deep into the cortex of the host brain, interfacing directly with their nervous system.")) + to_chat(host, span_danger("You feel a strange shifting sensation behind your eyes as an alien consciousness displaces yours.")) + host.add_language("Cortical Link") + + // host -> brain + var/h2b_id = host.computer_id + var/h2b_ip= host.lastKnownIP + host.computer_id = null + host.lastKnownIP = null + + qdel(host_brain) + host_brain = new(src) + host_brain.ckey = host.ckey + host_brain.name = host.name + + if(!host_brain.computer_id) + host_brain.computer_id = h2b_id + + if(!host_brain.lastKnownIP) + host_brain.lastKnownIP = h2b_ip + + // self -> host + var/s2h_id = src.computer_id + var/s2h_ip= src.lastKnownIP + src.computer_id = null + src.lastKnownIP = null + + host.ckey = src.ckey + + if(!host.computer_id) + host.computer_id = s2h_id + if(!host.lastKnownIP) + host.lastKnownIP = s2h_ip + controlling = TRUE + + add_verb(host, /mob/living/carbon/proc/release_control) + add_verb(host, /mob/living/carbon/proc/punish_host) + if(antag) + add_verb(host, /mob/living/carbon/proc/spawn_larvae) +// End horrible ip swapping code for bans + +/** + * Releases manual control of the borer from a mind-dominated host. Returning control to the original mind. + */ +/mob/living/simple_mob/animal/borer/verb/release_host() + set category = "Abilities.Borer" + set name = "Release Host" + set desc = "Slither out of your host." + + if(!host) + to_chat(src, span_warning("You are not inside a host body.")) + return + if(stat) + to_chat(src, span_warning("You cannot leave your host in your current state.")) + return + if(!can_use_power_docile()) + return + + to_chat(src, span_alien("You begin disconnecting from [host]'s synapses and prodding at their internal ear canal.")) + if(!host.stat) + to_chat(host, span_danger("An odd, uncomfortable pressure begins to build inside your skull, behind your ear...")) + + addtimer(CALLBACK(src, PROC_REF(finish_release_host)), 10 SECONDS, TIMER_DELETE_ME) + +/mob/living/simple_mob/animal/borer/proc/finish_release_host() + PRIVATE_PROC(TRUE) + + if(!host || QDELETED(src)) + return + + if(stat) + to_chat(src, span_warning("You cannot release your host in your current state.")) + return + + to_chat(src, span_alien("You wiggle out of [host]'s ear and plop to the ground.")) + if(host.mind) + if(!host.stat) + to_chat(host, span_danger("Something slimy wiggles out of your ear and plops to the ground!")) + to_chat(host, span_danger("As though waking from a dream, you shake off the insidious mind control of the brain worm. Your thoughts are your own again.")) + + detatch() + leave_host() diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer_host_powers.dm b/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer_host_powers.dm new file mode 100644 index 0000000000..13457329ed --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer_host_powers.dm @@ -0,0 +1,68 @@ +/** + * Brain slug proc for voluntary removal of control. + */ +/mob/living/carbon/proc/release_control() + + set category = "Abilities.Brainslug" + set name = "Release Control" + set desc = "Release control of your host's body." + + var/mob/living/simple_mob/animal/borer/B = has_brain_worms() + if(!B) + return + if(!B.host_brain) + return + + to_chat(src, span_alien("You withdraw your probosci, releasing control of [B.host_brain]")) + B.detatch() + +/** + * Brain slug proc for tormenting the host. + */ +/mob/living/carbon/proc/punish_host() + set category = "Abilities.Brainslug" + set name = "Torment host" + set desc = "Punish your host with agony." + + var/mob/living/simple_mob/animal/borer/B = has_brain_worms() + if(!B) + return + if(!B.host_brain) + return + if(!B.can_use_power_docile()) + return + if(!B.use_chems(BORER_POWER_COST_TORTURE)) + return + + to_chat(src, span_alien("You send a punishing spike of psychic agony lancing into your host's brain.")) + if(!B.host_brain.client || !can_feel_pain()) + to_chat(B.host_brain, span_warning("You feel a strange sensation as a foreign influence prods your mind.")) + to_chat(src, span_danger("It doesn't seem to be as effective as you hoped.")) + return + to_chat(B.host_brain, span_danger(span_large("Horrific, burning agony lances through you, ripping a soundless scream from your trapped mind!"))) + +/** + * Brain slug proc for spitting up new borers. They are ghostjoin by default, but are infertile. + */ +/mob/living/carbon/proc/spawn_larvae() + set category = "Abilities.Brainslug" + set name = "Reproduce" + set desc = "Spawn several young." + + var/mob/living/simple_mob/animal/borer/B = has_brain_worms() + if(!B || !B.can_use_power_controlling_host()) + return + if(!B.can_use_power_docile()) + return + if(!B.antag) + to_chat(src, span_warning("You cannot reproduce, you are infertile!")) + return + if(!B.use_chems(BORER_POWER_COST_REPRODUCE)) + return + + to_chat(src, span_alien("Your host twitches and quivers as you rapidly excrete a larva from your sluglike body.")) + visible_message(span_danger("\The [src] heaves violently, expelling a rush of vomit and a wriggling, sluglike creature!")) + + do_vomit(lost_nutrition = 1) // Needs to be instant + new /mob/living/simple_mob/animal/borer/non_antag(get_turf(src)) + B.has_reproduced = TRUE diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer_powers.dm b/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer_powers.dm index ad11e46ba9..d5f6108037 100644 --- a/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer_powers.dm +++ b/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer_powers.dm @@ -1,43 +1,55 @@ -/mob/living/simple_mob/animal/borer/verb/release_host() +/** + * Put a nearby target to sleep to allow for infestation. + */ +/mob/living/simple_mob/animal/borer/verb/knockout_victim() set category = "Abilities.Borer" - set name = "Release Host" - set desc = "Slither out of your host." + set name = "Knockout Victim" + set desc = "Use your psychic influence to put a target into a temporary catatonic state." - if(!host) - to_chat(src, span_warning("You are not inside a host body.")) + if(world.time - used_dominate < 150) + to_chat(src, span_warning("You cannot use that ability again so soon.")) + return + if(host) + to_chat(src, span_warning("You cannot do that from within a host body.")) return - if(stat) - to_chat(src, span_warning("You cannot leave your host in your current state.")) - - if(docile) - to_chat(src, span_blue("You are feeling far too docile to do that.")) + to_chat(src, span_warning("You cannot do that in your current state.")) return - if(!host || !src) return + var/attack_range = 5 + var/list/choices = list() + for(var/mob/living/carbon/human/attackable in view(attack_range,src)) + if(attackable.stat != DEAD && !attackable.has_brain_worms()) + choices += attackable + if(world.time - used_dominate < 150) + to_chat(src, span_warning("You cannot use that ability again so soon.")) + return - to_chat(src, span_warning("You begin disconnecting from [host]'s synapses and prodding at their internal ear canal.")) + if(!choices.len) + to_chat(src, span_notice("There are no viable targets within range...")) + return + var/mob/living/carbon/human/attack_target = choices[1] + if(choices.len > 1) + tgui_input_list(src, "Who do you wish to dominate?", "Target Choice", choices) - if(!host.stat) - to_chat(host, span_warning("An odd, uncomfortable pressure begins to build inside your skull, behind your ear...")) + if(!attack_target || QDELETED(src)) + return + if(!(attack_target in view(attack_range,src))) + to_chat(src, span_warning("\The [attack_target] escaped your influence...")) + return + if(attack_target.has_brain_worms()) + to_chat(src, span_warning("You cannot influence someone who is already infested!")) + return - spawn(100) - - if(!host || !src) return - - if(src.stat) - to_chat(src, span_warning("You cannot release your host in your current state.")) - return - - to_chat(src, span_warning("You wiggle out of [host]'s ear and plop to the ground.")) - if(host.mind) - if(!host.stat) - to_chat(host, span_danger("Something slimy wiggles out of your ear and plops to the ground!")) - to_chat(host, span_danger("As though waking from a dream, you shake off the insidious mind control of the brain worm. Your thoughts are your own again.")) - - detatch() - leave_host() + to_chat(src, span_alien("You focus your psychic lance on [attack_target] and freeze their limbs with a wave of terrible dread.")) + to_chat(attack_target, span_vdanger("You feel a creeping, horrible sense of dread come over you, freezing your limbs and setting your heart racing.")) + attack_target.Sleeping(10) // This was paralyze, but it resulted in players instantly screaming over radio. So get slept nerds. + used_dominate = world.time + add_attack_logs(src, attack_target, "psychic knockout (borer)") +/** + * Crawls inside of a target mob, and adds the borer as an implant to the mob's brain. If no brain exists the body will become a borer husk zombie. + */ /mob/living/simple_mob/animal/borer/verb/infest() set category = "Abilities.Borer" set name = "Infest" @@ -46,311 +58,190 @@ if(host) to_chat(src, span_warning("You are already within a host.")) return - if(stat) to_chat(src, span_warning("You cannot infest a target in your current state.")) return var/list/choices = list() - for(var/mob/living/carbon/C in view(1,src)) - if(src.Adjacent(C)) - choices += C - + for(var/mob/living/carbon/human/check_target in view(1,src)) + if(Adjacent(check_target) && !check_target.has_brain_worms()) + choices += check_target if(!choices.len) to_chat(src, span_warning("There are no viable hosts within range...")) return - var/mob/living/carbon/M = tgui_input_list(src, "Who do you wish to infest?", "Target Choice", choices) + var/mob/living/carbon/human/infest_target = choices[1] + if(choices.len > 1) + infest_target = tgui_input_list(src, "Who do you wish to infest?", "Target Choice", choices) + if(QDELETED(infest_target) || QDELETED(src)) + return + infest_target(infest_target) - if(!M || !src) return - - if(!(src.Adjacent(M))) return - - if(M.has_brain_worms()) +/// Infests mob with borer. +/mob/living/simple_mob/animal/borer/proc/infest_target(mob/living/carbon/human/infest_target) + if(!istype(infest_target)) + to_chat(src, span_warning("\The [infest_target] is not suitable for infestation...")) + return + if(!(Adjacent(infest_target))) + to_chat(src, span_warning("\The [infest_target] has escaped your range...")) + return + if(infest_target.has_brain_worms()) to_chat(src, span_warning("You cannot infest someone who is already infested!")) return - if(ishuman(M)) - var/mob/living/carbon/human/H = M + var/entering_timer = 30 + var/protected = FALSE + var/obj/item/organ/external/E = infest_target.organs_by_name[BP_HEAD] + if(!E || E.is_stump()) + to_chat(src, span_warning("\The [infest_target] does not have a head!")) - var/obj/item/organ/external/E = H.organs_by_name[BP_HEAD] - if(!E || E.is_stump()) - to_chat(src, span_warning("\The [H] does not have a head!")) - - if(!H.should_have_organ(O_BRAIN)) - to_chat(src, span_warning("\The [H] does not seem to have an ear canal to breach.")) - return - - if(H.check_head_coverage()) - to_chat(src, span_warning("You cannot get through that host's protective gear.")) - return - - to_chat(M, "Something slimy begins probing at the opening of your ear canal...") - to_chat(src, span_warning("You slither up [M] and begin probing at their ear canal...")) - - if(!do_after(src, 3 SECONDS, target = M)) - to_chat(src, span_warning("As [M] moves away, you are dislodged and fall to the ground.")) + if(!infest_target.should_have_organ(O_BRAIN)) + to_chat(src, span_warning("\The [infest_target] does not seem to have an ear canal to breach.")) return + if(infest_target.check_head_coverage()) + to_chat(src, span_alien("You begin to flatten and squirm into \the [infest_target]'s helmet to find a way inside them.")) + entering_timer = 55 + protected = TRUE - if(!M || !src) return - - if(src.stat) - to_chat(src, span_warning("You cannot infest a target in your current state.")) - return - - if(M in view(1, src)) - to_chat(src, span_warning("You wiggle into [M]'s ear.")) - if(!M.stat) - to_chat(M, "Something disgusting and slimy wiggles into your ear!") - - src.host = M - src.forceMove(M) - - //Update their traitor status. - if(host.mind) - borers.add_antagonist_mind(host.mind, 1, borers.faction_role_text, borers.faction_welcome) - - if(ishuman(M)) - var/mob/living/carbon/human/H = M - var/obj/item/organ/I = H.internal_organs_by_name[O_BRAIN] - if(!I) // No brain organ, so the borer moves in and replaces it permanently. - replace_brain() - else - // If they're in normally, implant removal can get them out. - var/obj/item/organ/external/head = H.get_organ(BP_HEAD) - head.implants += src - - return + if(!protected) + to_chat(infest_target, span_vdanger("Something slimy begins probing at the opening of your ear canal...")) else + to_chat(infest_target, span_vdanger("Something slimy begins trying to find a way past your helmet...")) + to_chat(src, span_alien("You slither up to \the [infest_target] and begin probing at their ear canal...")) + + if(!do_after(src, entering_timer, target = infest_target)) + to_chat(src, span_danger("As [infest_target] moves away, you are dislodged and fall to the ground.")) + return + if(!infest_target || QDELETED(src)) + return + if(stat) + to_chat(src, span_warning("You cannot infest \the [infest_target] in your current state.")) + return + if(!(Adjacent(infest_target))) to_chat(src, span_warning("They are no longer in range!")) return - -/* -/mob/living/simple_mob/animal/borer/verb/devour_brain() - set category = "Abilities.Borer" - set name = "Devour Brain" - set desc = "Take permanent control of a dead host." - - if(!host) - to_chat(src, span_warning("You are not inside a host body.")) + if(!infest_target.internal_organs_by_name[O_BRAIN]) // See section below about replace_brain() being disabled + to_chat(src, span_danger("\The [infest_target] has no brain to bond to!")) return - if(host.stat != 2) - to_chat(src, span_warning("Your host is still alive.")) + to_chat(src, span_alien("You wiggle into [infest_target]'s ear.")) + if(!infest_target.stat) + to_chat(infest_target, span_vdanger("Something disgusting and slimy wiggles into your ear!")) + host = infest_target + forceMove(host) + + //Update their traitor status. + if(host.mind) + borers.add_antagonist_mind(host.mind, 1, borers.faction_role_text, borers.faction_welcome) + + /* This is likely not desired, and has some major issues with ghost behavior. Disabling for now + // No brain organ, so the borer moves in and replaces it permanently. + if(!host.internal_organs_by_name[O_BRAIN]) + add_attack_logs(src, host, "merged with brainless body (borer)") + replace_brain() return + */ - if(stat) - to_chat(src, span_warning("You cannot do that in your current state.")) - - if(docile) - to_chat(src, span_warning(span_blue("You are feeling far too docile to do that."))) - return - - - to_chat(src, span_danger("It only takes a few moments to render the dead host brain down into a nutrient-rich slurry...")) - replace_brain() -*/ - -// BRAIN WORM ZOMBIES AAAAH. -/mob/living/simple_mob/animal/borer/proc/replace_brain() - - var/mob/living/carbon/human/H = host - - if(!istype(host)) - to_chat(src, span_warning("This host does not have a suitable brain.")) - return - - to_chat(src, span_danger("You settle into the empty brainpan and begin to expand, fusing inextricably with the dead flesh of [H].")) - - H.add_language("Cortical Link") - - if(host.stat == 2) - add_verb(H, /mob/living/carbon/human/proc/jumpstart) - - add_verb(H, /mob/living/carbon/human/proc/psychic_whisper) - add_verb(H, /mob/living/carbon/human/proc/tackle) - if(antag) - add_verb(H, /mob/living/carbon/proc/spawn_larvae) - - if(H.client) - H.ghostize(0) - - if(src.mind) - src.mind.special_role = "Borer Husk" - src.mind.transfer_to(host) - - H.ChangeToHusk() - - var/obj/item/organ/internal/borer/B = new(H) - H.internal_organs_by_name[O_BRAIN] = B - H.internal_organs |= B - - var/obj/item/organ/external/affecting = H.get_organ(BP_HEAD) - affecting.implants -= src - - var/s2h_id = src.computer_id - var/s2h_ip= src.lastKnownIP - src.computer_id = null - src.lastKnownIP = null - - if(!H.computer_id) - H.computer_id = s2h_id - - if(!H.lastKnownIP) - H.lastKnownIP = s2h_ip + // If they're in normally, implant removal can get them out. + var/obj/item/organ/external/head = host.get_organ(BP_HEAD) + head.implants += src + add_attack_logs(src, host, "infested target (borer)") +/** + * Releases chemicals from the borer into their host. Can be used as a standalone chemist in your head for an antag cooperating with their borer. + */ /mob/living/simple_mob/animal/borer/verb/secrete_chemicals() set category = "Abilities.Borer" set name = "Secrete Chemicals" - set desc = "Push some chemicals into your host's bloodstream." + set desc = "Drain some chemicals into your host's bloodstream." - if(!host) - to_chat(src, span_warning("You are not inside a host body.")) + if(!can_use_power_in_host()) + return + if(!can_use_power_docile()) + return + if(controlling) + to_chat(src, span_warning("You cannot do that while in full control of a host.")) return - if(stat) - to_chat(src, span_warning("You cannot secrete chemicals in your current state.")) - - if(docile) - to_chat(src, span_warning(span_blue("You are feeling far too docile to do that."))) - return - - if(chemicals < 50) - to_chat(src, span_warning("You don't have enough chemicals!")) - - var/chem = tgui_input_list(src, "Select a chemical to secrete.", "Chemicals", list(REAGENT_ID_ALKYSINE,REAGENT_ID_BICARIDINE,REAGENT_ID_HYPERZINE,REAGENT_ID_TRAMADOL)) - - if(!chem || chemicals < 50 || !host || controlling || !src || stat) //Sanity check. - return - - to_chat(src, span_bolddanger("You squirt a measure of [chem] from your reservoirs into [host]'s bloodstream.")) - host.reagents.add_reagent(chem, 10) - chemicals -= 50 - -/mob/living/simple_mob/animal/borer/verb/dominate_victim() - set category = "Abilities.Borer" - set name = "Paralyze Victim" - set desc = "Freeze the limbs of a potential host with supernatural fear." - - if(world.time - used_dominate < 150) - to_chat(src, span_warning("You cannot use that ability again so soon.")) - return - - if(host) - to_chat(src, span_warning("You cannot do that from within a host body.")) - return - - if(src.stat) - to_chat(src, span_warning("You cannot do that in your current state.")) - return - - var/list/choices = list() - for(var/mob/living/carbon/C in view(3,src)) - if(C.stat != 2) - choices += C - - if(world.time - used_dominate < 150) - to_chat(src, span_warning("You cannot use that ability again so soon.")) - return - - var/mob/living/carbon/M = tgui_input_list(src, "Who do you wish to dominate?", "Target Choice", choices) - - if(!M || !src) return - - if(M.has_brain_worms()) - to_chat(src, span_warning("You cannot infest someone who is already infested!")) - return - - to_chat(src, span_red("You focus your psychic lance on [M] and freeze their limbs with a wave of terrible dread.")) - to_chat(M, span_red("You feel a creeping, horrible sense of dread come over you, freezing your limbs and setting your heart racing.")) - M.Weaken(10) - - used_dominate = world.time - -/mob/living/simple_mob/animal/borer/verb/bond_brain() - set category = "Abilities.Borer" - set name = "Assume Control" - set desc = "Fully connect to the brain of your host." - - if(!host) - to_chat(src, span_warning("You are not inside a host body.")) - return - - if(src.stat) - to_chat(src, span_warning("You cannot do that in your current state.")) - return - - if(docile) - to_chat(src, span_blue("You are feeling far too docile to do that.")) - return - - to_chat(src, span_warning("You begin delicately adjusting your connection to the host brain...")) - - spawn(100+(host.brainloss*5)) - - if(!host || !src || controlling) + var/injection_choice = tgui_input_list(src, "Select a chemical to secrete.", "Chemicals", borer_chem_list) + if(injection_choice == "Revive Dead Host") + if(!can_use_power_in_host()) return - else - - to_chat(src, span_bolddanger("You plunge your probosci deep into the cortex of the host brain, interfacing directly with their nervous system.")) - to_chat(host, span_bolddanger("You feel a strange shifting sensation behind your eyes as an alien consciousness displaces yours.")) - host.add_language("Cortical Link") - - // host -> brain - var/h2b_id = host.computer_id - var/h2b_ip= host.lastKnownIP - host.computer_id = null - host.lastKnownIP = null - - qdel(host_brain) - host_brain = new(src) - - host_brain.ckey = host.ckey - - host_brain.name = host.name - - if(!host_brain.computer_id) - host_brain.computer_id = h2b_id - - if(!host_brain.lastKnownIP) - host_brain.lastKnownIP = h2b_ip - - // self -> host - var/s2h_id = src.computer_id - var/s2h_ip= src.lastKnownIP - src.computer_id = null - src.lastKnownIP = null - - host.ckey = src.ckey - - if(!host.computer_id) - host.computer_id = s2h_id - - if(!host.lastKnownIP) - host.lastKnownIP = s2h_ip - - controlling = 1 - - add_verb(host, /mob/living/carbon/proc/release_control) - add_verb(host, /mob/living/carbon/proc/punish_host) - if(antag) - add_verb(host, /mob/living/carbon/proc/spawn_larvae) - + if(controlling) + to_chat(src, span_warning("You cannot do that while in full control of a host.")) return + if(!can_use_power_docile()) + return + if(host.stat != DEAD) + to_chat(src, span_danger("Your host must be dead!")) + return + if(HUSK in host.mutations) + to_chat(src, span_danger("Your host is too destroyed to revive.")) + return + if(!host.can_defib) + to_chat(src, span_vdanger("Your host's brain is not connected to its body!")) + return + if(!use_chems(BORER_POWER_COST_SECRETE)) + return + to_chat(src, span_alien("You squirt an intense mix of chemicals from your reservoirs into [host]'s bloodstream.")) + add_attack_logs(src, host, "jumpstart host (borer)") + host.jumpstart() + return + // Is this even legal to do still? We had to wait for a selection... + if(!can_use_power_in_host()) + return + if(!can_use_power_docile()) + return + if(controlling) + to_chat(src, span_warning("You cannot do that while in full control of a host.")) + return + + // Get the chem we're injecting + var/list/injection_data = borer_chem_list[injection_choice] + if(!injection_data) //Sanity check. + return + var/injecting_chem = injection_data[1] + var/injectsize = injection_data[2] + if(!injecting_chem || !injectsize) + return + + // Finally get to injecting + if(!use_chems(BORER_POWER_COST_SECRETE)) + return + var/datum/reagent/inject_reagent = SSchemistry.chemical_reagents[injecting_chem] + if(!inject_reagent) + CRASH("Invalid chem reagent [injecting_chem], in borer chemical injection.") + + add_attack_logs(src, host, "chemical injection [inject_reagent] (borer)") + to_chat(src, span_alien("You squirt a measure of [inject_reagent] from your reservoirs into [host]'s bloodstream.")) + host.bloodstr.add_reagent(injecting_chem, injectsize) + +/// Does some basic regeneration on a host /mob/living/carbon/human/proc/jumpstart() set category = "Abilities.Borer" set name = "Revive Host" set desc = "Send a jolt of electricity through your host, reviving them." - if(stat != 2) - to_chat(src, "Your host is already alive.") - return - + // This is meant to be a bit silly, cause borers don't have much options otherwise remove_verb(src, /mob/living/carbon/human/proc/jumpstart) - visible_message(span_warning("With a hideous, rattling moan, [src] shudders back to life!")) + visible_message(span_danger("With a hideous, rattling moan, [src] shudders back to life!")) + // Dump damage or we won't be able to revive properly + setHalLoss(0) + setOxyLoss(0) + adjustBruteLoss(-20) + adjustFireLoss(-20) + adjust_nutrition(-200) + + // Boilerplate revivial rejuvenate() restore_blood() fixblood() update_canmove() + + // Get some healing in us + bloodstr.add_reagent(REAGENT_BICARIDINE, 5) + bloodstr.add_reagent(REAGENT_KELOTANE, 5) + bloodstr.add_reagent(REAGENT_ID_TRAMADOL, 5) + bloodstr.add_reagent(REAGENT_ID_ALKYSINE, 5) diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer_query.dm b/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer_query.dm new file mode 100644 index 0000000000..0502f7a967 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer_query.dm @@ -0,0 +1,15 @@ +/mob/living/simple_mob/animal/borer + var/datum/ghost_query/ghost_check // Used to unregister our signal + +/mob/living/simple_mob/animal/borer/proc/request_player() + ghost_check = new /datum/ghost_query/borer() + RegisterSignal(ghost_check, COMSIG_GHOST_QUERY_COMPLETE, PROC_REF(get_winner)) + ghost_check.query() // This will sleep the proc for awhile. + +/mob/living/simple_mob/animal/borer/proc/get_winner() + SIGNAL_HANDLER + if(ghost_check && ghost_check.candidates.len) //ghost_check should NEVER get deleted but...whatever, sanity. + var/mob/observer/dead/D = ghost_check.candidates[1] + transfer_personality(D) + UnregisterSignal(ghost_check, COMSIG_GHOST_QUERY_COMPLETE) + QDEL_NULL(ghost_check) //get rid of the query diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer_zombie.dm b/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer_zombie.dm new file mode 100644 index 0000000000..d30589417d --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer_zombie.dm @@ -0,0 +1,66 @@ +/** + * Consumes the brain of a dead host, turning it into a husk zombie. + * + * Disabled currently due to ghosting behavior, allows you to enter a dead body ghost around for meta knowledge, then return and jumpstart the host. + * Ideally a borer is about cooperating with your host in some way, and not just killing and eating their brain to go full single player antag anyway... + */ +/mob/living/simple_mob/animal/borer/verb/devour_brain() + set category = "Abilities.Borer" + set name = "Devour Brain" + set desc = "Take permanent control of a dead host." + + if(!can_use_power_in_host()) + return + if(!can_use_power_docile()) + return + if(host.stat != DEAD) + to_chat(src, span_warning("Your host is still alive.")) + return + + to_chat(src, span_alien("It only takes a few moments to render the dead host brain down into a nutrient-rich slurry...")) + add_attack_logs(src, host, "devour host brain (borer)") + replace_brain() + +/// BRAIN WORM ZOMBIES AAAAH. Husks mob and turns them into a borer zombie. +/mob/living/simple_mob/animal/borer/proc/replace_brain() + if(!istype(host)) + to_chat(src, span_warning("This host does not have a suitable brain.")) + return + to_chat(src, span_alien("You settle into the empty brainpan and begin to expand, fusing inextricably with the dead flesh of [host].")) + + host.add_language("Cortical Link") + + if(host.stat == DEAD) + add_verb(host, /mob/living/carbon/human/proc/jumpstart) + add_verb(host, /mob/living/carbon/human/proc/psychic_whisper) + add_verb(host, /mob/living/carbon/human/proc/tackle) + if(antag) + add_verb(host, /mob/living/carbon/proc/spawn_larvae) + + // Zombify + if(host.client) + host.ghostize(FALSE) + if(mind) + mind.special_role = "Borer Husk" + mind.transfer_to(host) + host.ChangeToHusk() + + // Convert into borer brain + var/obj/item/organ/internal/borer/borer_organ = new(host) + host.internal_organs_by_name[O_BRAIN] = borer_organ + host.internal_organs |= borer_organ + var/obj/item/organ/external/affecting = host.get_organ(BP_HEAD) + affecting.implants -= src + + // This section is awful and a relic of ancient times. It needs to be replaced + var/s2h_id = computer_id + var/s2h_ip= lastKnownIP + computer_id = null + lastKnownIP = null + + if(!host.computer_id) + host.computer_id = s2h_id + + if(!host.lastKnownIP) + host.lastKnownIP = s2h_ip + // End ip trickery diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm index d84be90d5d..3031cbe2ad 100644 --- a/code/modules/mob/mob_defines.dm +++ b/code/modules/mob/mob_defines.dm @@ -42,6 +42,7 @@ var/atom/movable/screen/gun/run/gun_run_icon = null var/atom/movable/screen/gun/mode/gun_setting_icon = null var/atom/movable/screen/ling/chems/ling_chem_display = null + var/atom/movable/screen/borer/chems/borer_chem_display = null var/atom/movable/screen/wizard/energy/wiz_energy_display = null var/atom/movable/screen/wizard/instability/wiz_instability_display = null var/atom/movable/screen/autowhisper_display = null diff --git a/vorestation.dme b/vorestation.dme index 75fbdf6c18..277ecd5695 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -61,6 +61,7 @@ #include "code\__defines\belly_messages.dm" #include "code\__defines\belly_modes_vr.dm" #include "code\__defines\blueprints.dm" +#include "code\__defines\borer.dm" #include "code\__defines\borg_overlays.dm" #include "code\__defines\callbacks.dm" #include "code\__defines\changeling.dm" @@ -3659,7 +3660,10 @@ #include "code\modules\mob\living\simple_mob\subtypes\animal\alien animals\teppi.dm" #include "code\modules\mob\living\simple_mob\subtypes\animal\borer\borer.dm" #include "code\modules\mob\living\simple_mob\subtypes\animal\borer\borer_captive.dm" +#include "code\modules\mob\living\simple_mob\subtypes\animal\borer\borer_control.dm" +#include "code\modules\mob\living\simple_mob\subtypes\animal\borer\borer_host_powers.dm" #include "code\modules\mob\living\simple_mob\subtypes\animal\borer\borer_powers.dm" +#include "code\modules\mob\living\simple_mob\subtypes\animal\borer\borer_query.dm" #include "code\modules\mob\living\simple_mob\subtypes\animal\farm animals\chicken.dm" #include "code\modules\mob\living\simple_mob\subtypes\animal\farm animals\cow.dm" #include "code\modules\mob\living\simple_mob\subtypes\animal\farm animals\goat.dm"