diff --git a/aurorastation.dme b/aurorastation.dme
index 155e8b48b6a..378054b98be 100644
--- a/aurorastation.dme
+++ b/aurorastation.dme
@@ -120,6 +120,8 @@
#include "code\_onclick\hud\_defines.dm"
#include "code\_onclick\hud\action.dm"
#include "code\_onclick\hud\ai.dm"
+#include "code\_onclick\hud\borer_hud.dm"
+#include "code\_onclick\hud\captive_brain_hud.dm"
#include "code\_onclick\hud\fullscreen.dm"
#include "code\_onclick\hud\gun_mode.dm"
#include "code\_onclick\hud\hud.dm"
@@ -135,6 +137,7 @@
#include "code\_onclick\hud\screen_objects.dm"
#include "code\_onclick\hud\spell_screen_objects.dm"
#include "code\_onclick\hud\screen_object_types\ai_screen_objs.dm"
+#include "code\_onclick\hud\screen_object_types\borer.dm"
#include "code\_onclick\hud\screen_object_types\internals.dm"
#include "code\_onclick\hud\screen_object_types\vampire.dm"
#include "code\ATMOSPHERICS\_atmos_setup.dm"
diff --git a/code/_onclick/hud/borer_hud.dm b/code/_onclick/hud/borer_hud.dm
new file mode 100644
index 00000000000..407ffb559c5
--- /dev/null
+++ b/code/_onclick/hud/borer_hud.dm
@@ -0,0 +1,13 @@
+/mob/living/simple_animal/borer/instantiate_hud(var/datum/hud/HUD)
+ HUD.borer_hud()
+
+/datum/hud/proc/borer_hud()
+ src.adding = list()
+
+ var/obj/screen/borer/chemicals/C = new /obj/screen/borer/chemicals()
+ adding += C
+
+ var/mob/living/simple_animal/borer/B = mymob
+ B.chem_hud = C
+
+ mymob.client.screen += src.adding
\ No newline at end of file
diff --git a/code/_onclick/hud/captive_brain_hud.dm b/code/_onclick/hud/captive_brain_hud.dm
new file mode 100644
index 00000000000..8a25ab24dbc
--- /dev/null
+++ b/code/_onclick/hud/captive_brain_hud.dm
@@ -0,0 +1,15 @@
+/mob/living/captive_brain/instantiate_hud(var/datum/hud/HUD)
+ HUD.captive_brain_hud()
+
+/datum/hud/proc/captive_brain_hud()
+ src.adding = list()
+
+ var/obj/screen/resist = new /obj/screen()
+ resist.name = "resist"
+ resist.icon = 'icons/mob/screen/captive_brain.dmi'
+ resist.icon_state = "resist"
+ resist.screen_loc = "EAST-8,BOTTOM:8"
+ resist.layer = SCREEN_LAYER
+ adding += resist
+
+ mymob.client.screen += src.adding
\ No newline at end of file
diff --git a/code/_onclick/hud/screen_object_types/borer.dm b/code/_onclick/hud/screen_object_types/borer.dm
new file mode 100644
index 00000000000..73888dbc65a
--- /dev/null
+++ b/code/_onclick/hud/screen_object_types/borer.dm
@@ -0,0 +1,14 @@
+/obj/screen/borer
+ icon = 'icons/mob/screen/borer.dmi'
+ maptext_x = 5
+ maptext_y = 16
+
+/obj/screen/borer/chemicals
+ name = "chemicals"
+ icon_state = "chemicals"
+ screen_loc = ui_alien_toxin
+
+/obj/screen/borer/chemicals/Click(var/location, var/control, var/params)
+ if(istype(usr, /mob/living/simple_animal/borer))
+ var/mob/living/simple_animal/borer/B = usr
+ to_chat(usr, SPAN_NOTICE("You have [B.chemicals]u of chemicals to use for your abilities."))
\ No newline at end of file
diff --git a/code/datums/progressbar.dm b/code/datums/progressbar.dm
index 1abbedec4a6..1227f6bd3c6 100644
--- a/code/datums/progressbar.dm
+++ b/code/datums/progressbar.dm
@@ -3,6 +3,7 @@
/datum/progressbar
var/goal = 1
+ var/destroy_on_full = FALSE
var/last_progress = 0
var/image/bar
var/shown = 0
@@ -46,6 +47,8 @@
if (!shown)
user.client.images += bar
shown = TRUE
+ if(destroy_on_full && progress == goal)
+ QDEL_IN(src, 5)
/datum/progressbar/proc/shiftDown()
--listindex
@@ -76,5 +79,8 @@
client.images -= bar
client = null
+/datum/progressbar/autocomplete
+ destroy_on_full = TRUE
+
#undef PROGRESSBAR_ANIMATION_TIME
#undef PROGRESSBAR_HEIGHT
\ No newline at end of file
diff --git a/code/modules/mob/living/carbon/human/human_helpers.dm b/code/modules/mob/living/carbon/human/human_helpers.dm
index e91e6ba56f6..521791d3235 100644
--- a/code/modules/mob/living/carbon/human/human_helpers.dm
+++ b/code/modules/mob/living/carbon/human/human_helpers.dm
@@ -160,7 +160,7 @@
return O
-/mob/living/carbon/human/proc/awaken_psi_basic(var/source)
+/mob/living/carbon/human/proc/awaken_psi_basic(var/source, var/allow_latency = TRUE)
var/static/list/psi_operancy_messages = list(
"There's something in your skull!",
"Something is eating your thoughts!",
@@ -176,11 +176,9 @@
var/list/faculties = list(PSI_COERCION, PSI_REDACTION, PSI_ENERGISTICS, PSI_PSYCHOKINESIS)
for(var/i = 1 to new_latencies)
custom_pain(SPAN_DANGER("[pick(psi_operancy_messages)]"), 25)
- set_psi_rank(pick_n_take(faculties), 1)
+ set_psi_rank(pick_n_take(faculties), allow_latency ? PSI_RANK_LATENT : PSI_RANK_OPERANT) // if set to latent, it spikes anywhere from OPERANT to PARAMOUNT
sleep(30)
- psi.update()
- sleep(45)
- psi.check_latency_trigger(100, source, TRUE)
+ addtimer(CALLBACK(psi, /datum/psi_complexus/.proc/check_latency_trigger, 100, source, TRUE), 4.5 SECONDS)
/mob/living/carbon/human/get_resist_power()
return species.resist_mod
diff --git a/code/modules/mob/living/simple_animal/borer/borer.dm b/code/modules/mob/living/simple_animal/borer/borer.dm
index 74789a4a75f..8219b63f997 100644
--- a/code/modules/mob/living/simple_animal/borer/borer.dm
+++ b/code/modules/mob/living/simple_animal/borer/borer.dm
@@ -28,11 +28,15 @@
hunger_enabled = FALSE
var/used_dominate
+ var/datum/progressbar/autocomplete/ability_bar
+ var/ability_start_time = 0
+ var/obj/screen/borer/chemicals/chem_hud
var/chemicals = 10 // Chemicals used for reproduction and spitting neurotoxin.
var/mob/living/carbon/human/host // Human host for the brain worm.
var/truename // Name used for brainworm-speak.
var/mob/living/captive_brain/host_brain // Used for swapping control of the body back and forth.
var/controlling // Used in human death check.
+ var/list/static/controlling_emotes = list("blink","blink_r","choke","drool","twitch","twitch_v","gasp")
var/has_reproduced
var/request_player = TRUE
@@ -43,6 +47,8 @@
..()
if(mind)
borers.add_antagonist(mind)
+ if(client && host)
+ client.screen += host.healths
/mob/living/simple_animal/borer/Initialize()
. = ..()
@@ -56,6 +62,11 @@
if(request_player && !ckey && !client)
SSghostroles.add_spawn_atom("borer", src)
+/mob/living/simple_animal/borer/Destroy()
+ QDEL_NULL(ability_bar)
+ QDEL_NULL(host_brain)
+ return ..()
+
/mob/living/simple_animal/borer/death(gibbed, deathmessage)
SSghostroles.remove_spawn_atom("borer", src)
return ..(gibbed,deathmessage)
@@ -64,14 +75,28 @@
return FALSE
/mob/living/simple_animal/borer/Life()
- ..()
if(host)
if(!stat && host.stat != DEAD)
if(chemicals < 250)
chemicals++
- if(host && !host.stat)
- if(controlling && prob(host.getBrainLoss()/20))
- host.say("*[pick(list("blink","blink_r","choke","drool","twitch","twitch_s","gasp"))]")
+ if(!host.stat && controlling && prob(2))
+ host.emote(pick(controlling_emotes))
+ ..()
+ if(!QDELETED(ability_bar))
+ ability_bar.update(world.time - ability_start_time)
+
+/mob/living/simple_animal/borer/proc/start_ability(var/atom/target, var/time)
+ if(!QDELETED(ability_bar))
+ return FALSE
+ ability_bar = new /datum/progressbar/autocomplete(src, time, target)
+ ability_start_time = world.time
+ ability_bar.update(0)
+ return TRUE
+
+/mob/living/simple_animal/borer/handle_regular_hud_updates()
+ . = ..()
+ if(chem_hud)
+ chem_hud.maptext = SMALL_FONTS(7, chemicals)
/mob/living/simple_animal/borer/Stat()
..()
@@ -89,6 +114,9 @@
if(!host || !controlling)
return
+ if(ability_bar)
+ QDEL_NULL(ability_bar)
+
if(istype(host,/mob/living/carbon/human))
var/mob/living/carbon/human/H = host
var/obj/item/organ/external/head = H.get_organ(BP_HEAD)
@@ -158,8 +186,27 @@
host.machine = null
host.status_flags &= ~PASSEMOTES
+ if(client)
+ client.screen -= host.healths
host = null
return
/mob/living/simple_animal/borer/cannot_use_vents()
return
+
+/mob/living/simple_animal/borer/UnarmedAttack(atom/A, proximity)
+ if(iscarbon(A))
+ var/mob/living/carbon/C = A
+ if(C.lying || C.weakened)
+ do_infest(C)
+ else
+ do_paralyze(C)
+ return
+ return ..()
+
+/mob/living/simple_animal/borer/RangedAttack(atom/A, params)
+ if(iscarbon(A) && get_dist(src, A) <= 2)
+ var/mob/living/carbon/C = A
+ do_paralyze(C)
+ return
+ return ..()
\ No newline at end of file
diff --git a/code/modules/mob/living/simple_animal/borer/borer_captive.dm b/code/modules/mob/living/simple_animal/borer/borer_captive.dm
index a38b3509497..26a52b799f3 100644
--- a/code/modules/mob/living/simple_animal/borer/borer_captive.dm
+++ b/code/modules/mob/living/simple_animal/borer/borer_captive.dm
@@ -3,6 +3,9 @@
real_name = "host brain"
universal_understand = TRUE
+ var/datum/progressbar/resist_bar
+ var/resist_start_time = 0
+
/mob/living/captive_brain/say(var/message)
if(istype(src.loc,/mob/living/simple_animal/borer))
message = sanitize(message)
@@ -22,19 +25,36 @@
else if(M.stat == DEAD && M.client.prefs.toggles & CHAT_GHOSTEARS)
to_chat(M, "The captive mind of [src] whispers, \"[message]\"")
+/mob/living/captive_brain/Destroy()
+ QDEL_NULL(resist_bar)
+ return ..()
+
/mob/living/captive_brain/emote(var/message)
return
+/mob/living/captive_brain/Life()
+ if(resist_bar)
+ resist_bar.update(world.time - resist_start_time)
+ return ..()
+
/mob/living/captive_brain/process_resist()
//Resisting control by an alien mind.
+ if(resist_bar)
+ to_chat(src, SPAN_WARNING("You're already resisting the alien control!"))
+ return
+
if(istype(src.loc,/mob/living/simple_animal/borer))
var/mob/living/simple_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)."))
+ to_chat(H, SPAN_DANGER("You begin doggedly resisting the parasite's control."))
to_chat(B.host, SPAN_DANGER("You feel the captive mind of [src] begin to resist your control."))
- addtimer(CALLBACK(src, .proc/eject_borer, B, H), rand(200, 250) + B.host.getBrainLoss())
+ var/resist_time = rand(40 SECONDS, 1 MINUTE)
+ addtimer(CALLBACK(src, .proc/eject_borer, B, H), resist_time)
+ resist_bar = new /datum/progressbar/autocomplete(src, resist_time, B.host)
+ resist_start_time = world.time
+ resist_bar.update(0)
return
..()
diff --git a/code/modules/mob/living/simple_animal/borer/borer_powers.dm b/code/modules/mob/living/simple_animal/borer/borer_powers.dm
index 0b1f6745338..e9d27079c73 100644
--- a/code/modules/mob/living/simple_animal/borer/borer_powers.dm
+++ b/code/modules/mob/living/simple_animal/borer/borer_powers.dm
@@ -10,12 +10,16 @@
to_chat(src, SPAN_NOTICE("You cannot leave your host in your current state."))
return
- to_chat(src, SPAN_NOTICE("You begin disconnecting from [host]'s synapses and prodding at their internal ear canal."))
+ var/exit_time = 10 SECONDS
+ if(!start_ability(host, exit_time))
+ to_chat(src, SPAN_WARNING("You're busy doing something else, complete that task first."))
+ return
+ to_chat(src, SPAN_NOTICE("You begin disconnecting from [host]'s synapses and prodding at their internal ear canal."))
if(!host.stat)
to_chat(host, SPAN_WARNING("An odd, uncomfortable pressure begins to build inside your skull, behind your ear..."))
- addtimer(CALLBACK(src, .proc/exit_host), 100)
+ addtimer(CALLBACK(src, .proc/exit_host), exit_time)
/mob/living/simple_animal/borer/proc/exit_host()
if(!host || !src)
@@ -48,19 +52,28 @@
return
var/list/choices = list()
- for(var/mob/living/carbon/C in view(1,src))
+ for(var/mob/living/carbon/C in view(2, src))
if(src.Adjacent(C))
choices += C
- if(!choices.len)
+ if(!length(choices))
to_chat(src, SPAN_NOTICE("There are no viable hosts within range."))
return
var/mob/living/carbon/M = input(src,"Who do you wish to infest?") in null|choices
+ if(M)
+ do_infest(M)
+/mob/living/simple_animal/borer/proc/do_infest(var/mob/living/carbon/M)
+ if(host)
+ to_chat(src, SPAN_NOTICE("You are already within a host."))
+ return
+ if(stat)
+ to_chat(src, SPAN_NOTICE("You cannot infest a target in your current state."))
+ return
if(!M || !src)
return
- if(!(src.Adjacent(M)))
+ if(!Adjacent(M))
return
if(M.has_brain_worms())
to_chat(src, SPAN_WARNING("You cannot infest someone who is already infested!"))
@@ -108,6 +121,9 @@
src.host.status_flags |= PASSEMOTES
src.forceMove(M)
+ if(client)
+ client.screen += host.healths
+
//Update their traitor status.
if(host.mind)
borers.add_antagonist_mind(host.mind, 1, borers.faction_role_text, borers.faction_welcome)
@@ -240,12 +256,14 @@
if(C.stat != 2)
choices += C
+ var/mob/living/carbon/M = input(src, "Who do you wish to dominate?") in null|choices
+ if(M)
+ do_paralyze(M)
+
+/mob/living/simple_animal/borer/proc/do_paralyze(var/mob/living/carbon/M)
if(world.time - used_dominate < 150)
to_chat(src, SPAN_NOTICE("You cannot use that ability again so soon."))
return
-
- var/mob/living/carbon/M = input(src, "Who do you wish to dominate?") in null|choices
-
if(!M || !src)
return
if(M.isSynthetic())
@@ -283,10 +301,15 @@
to_chat(src, SPAN_WARNING("\The [host]'s mind is shielded against your powers."))
return
+ var/takeover_time = 10 SECONDS + (host.getBrainLoss() * 5)
+ if(!start_ability(host, takeover_time))
+ to_chat(src, SPAN_WARNING("You're busy doing something else, complete that task first."))
+ return
+
to_chat(src, SPAN_WARNING("You begin delicately adjusting your connection to the host brain..."))
to_chat(host, SPAN_WARNING("You feel a tingling sensation at the back of your head."))
- addtimer(CALLBACK(src, .proc/host_takeover), 100+(host.getBrainLoss()*5))
+ addtimer(CALLBACK(src, .proc/host_takeover), takeover_time)
/mob/living/simple_animal/borer/proc/host_takeover()
if(!host || !src || controlling)
@@ -307,8 +330,8 @@
host_brain = new(src)
host_brain.ckey = host.ckey
-
- host_brain.name = host.name
+ host_brain.name = host.real_name
+ host_brain.real_name = "[host_brain.name] (captive host)"
if(!host_brain.computer_id)
host_brain.computer_id = h2b_id
@@ -441,20 +464,30 @@
to_chat(src, SPAN_WARNING("\The [host]'s mind is shielded against your powers."))
return
+ var/jumpstart_time = 15 SECONDS
+ if(!start_ability(host, jumpstart_time))
+ to_chat(src, SPAN_WARNING("You're busy doing something else, complete that task first."))
+ return
+
chemicals -= 150
to_chat(src, SPAN_NOTICE("You probe your tendrils deep within your host's zona bovinae, seeking to unleash their potential."))
to_chat(host, SPAN_DANGER("You feel some tendrils probe at the back of your head..."))
to_chat(host, FONT_LARGE(SPAN_WARNING("You feel something terrible coming on...")))
- addtimer(CALLBACK(src, .proc/jumpstart_psi), 150)
+
+ addtimer(CALLBACK(src, .proc/jumpstart_psi), jumpstart_time)
/mob/living/simple_animal/borer/proc/jumpstart_psi()
+ if(!host)
+ return
+
to_chat(src, SPAN_NOTICE("You succeed in interfacing with the host's zona bovinae, this will be a painful process for them."))
- host.awaken_psi_basic("something in your head")
+ host.awaken_psi_basic("something in your head", FALSE)
+ host.add_language(LANGUAGE_TCB) // if we don't have TCB, give them TCB | this allows monkey borers to RP
/mob/living/simple_animal/borer/verb/advance_faculty()
set category = "Abilities"
set name = "Advance Psionic Faculty (75)"
- set desc = "Advance one of your host's psionic faculties' by one step."
+ set desc = "Advance one of your host's psionic faculties by one step."
if(!host)
to_chat(src, SPAN_NOTICE("You are not inside a host body."))
@@ -479,19 +512,31 @@
if(!selected_faculty)
return
selected_faculty = lowertext(selected_faculty)
- if(host.psi.get_rank(selected_faculty) >= PSI_RANK_GRANDMASTER)
+ var/max_rank = islesserform(host) ? PSI_RANK_OPERANT : PSI_RANK_MASTER
+ if(host.psi.get_rank(selected_faculty) >= max_rank)
to_chat(src, SPAN_NOTICE("This faculty has already been pushed to the max potential you can achieve!"))
return
+ var/faculty_time = 10 SECONDS
+ if(!start_ability(host, faculty_time))
+ to_chat(src, SPAN_WARNING("You're busy doing something else, complete that task first."))
+ return
+
chemicals -= 75
to_chat(src, SPAN_NOTICE("You probe your tendrils deep within your host's zona bovinae, seeking to upgrade their abilities."))
to_chat(host, SPAN_WARNING("You feel a burning, tingling sensation at the back of your head..."))
- addtimer(CALLBACK(src, .proc/faculty_upgrade, selected_faculty), 100)
+
+ addtimer(CALLBACK(src, .proc/faculty_upgrade, selected_faculty), faculty_time)
/mob/living/simple_animal/borer/proc/faculty_upgrade(var/selected_faculty)
- host.psi.set_rank(selected_faculty, host.psi.get_rank(selected_faculty) + 1)
- host.psi.update()
- to_chat(src, SPAN_NOTICE("You successfully manage to upgrade your host's [selected_faculty] faculty."))
+ if(!host)
+ return
+
+ var/host_psi_rank = host.psi.get_rank(selected_faculty)
+ var/next_rank = host_psi_rank > PSI_RANK_BLUNT ? host_psi_rank + 1 : PSI_RANK_OPERANT
+ host.psi.set_rank(selected_faculty, next_rank)
+ host.psi.update(TRUE)
+ to_chat(src, SPAN_NOTICE("You successfully manage to upgrade your host to [psychic_ranks_to_strings[host.psi.ranks[selected_faculty]]] [selected_faculty]."))
to_chat(host, SPAN_GOOD("A breeze of fresh air washes over your mind, you feel powerful!"))
to_chat(host, SPAN_NOTICE("You have been psionically enlightened. You are now a [psychic_ranks_to_strings[host.psi.ranks[selected_faculty]]] in the [selected_faculty] faculty."))
diff --git a/code/modules/mob/living/simple_animal/borer/say.dm b/code/modules/mob/living/simple_animal/borer/say.dm
index cea49f8b01f..c504b285386 100644
--- a/code/modules/mob/living/simple_animal/borer/say.dm
+++ b/code/modules/mob/living/simple_animal/borer/say.dm
@@ -31,15 +31,14 @@
to_chat(src, SPAN_NOTICE("There are no viable hosts to speak with."))
else
var/mob/living/carbon/human/chosen_sayer = pick(viable_sayers)
- log_say("[key_name(src)] : ([name]) [message]", ckey=key_name(src))
+ log_say("[key_name(src)] : (forcing [key_name(chosen_sayer)]) [message]", ckey=key_name(src))
chosen_sayer.say(message)
return
to_chat(src, "You drop words into [host]'s mind: \"[message]\"")
to_chat(host, "Your own thoughts speak: \"[message]\"")
+ log_say("[key_name(src)] : (borer whisper -> [key_name(host)]) [message]")
- for(var/mob/M in player_list)
- if(istype(M, /mob/abstract/new_player))
- continue
- else if(M.stat == DEAD && M.client.prefs.toggles & CHAT_GHOSTEARS)
- to_chat(M, "[src.truename] whispers to [host], \"[message]\"")
+ for(var/mob/M in mob_list)
+ if(M.client && M.stat == DEAD && !isnewplayer(M) && (M.client.prefs.toggles & CHAT_GHOSTEARS))
+ to_chat(M, "[src.truename] whispers to [host], \"[message]\"")
\ No newline at end of file
diff --git a/html/changelogs/geeves-borer_stuff.yml b/html/changelogs/geeves-borer_stuff.yml
new file mode 100644
index 00000000000..27bfd3d86f1
--- /dev/null
+++ b/html/changelogs/geeves-borer_stuff.yml
@@ -0,0 +1,18 @@
+author: Geeves
+
+delete-after: True
+
+changes:
+ - rscadd: "Cortical borers now get a UI element that shows how many chemicals they have to use. They also gain their host's health doll when they infest them."
+ - rscadd: "Hosts that get taken over by cortical borers now get a big resist button UI element, which they can use to resist the borer's control."
+ - tweak: "Resisting against borer control now takes fourty seconds to a minute, up from twenty seconds to twenty-five seconds."
+ - rscadd: "Various cortical borer action now have a progress bar. Resisting borer control now also has a progress bar."
+ - tweak: "Hosts being controlled by borers now emote randomly regardless of brain damage."
+ - rscadd: "Borers can now paralyze and infest people by clicking on them. It will paralyze if they're not lying down/paralyzed, and will infest if they are."
+ - tweak: "Borers can now paralyze people from two tiles away (one tile between them)."
+ - rscadd: "Awakening a humanoid's psionics will now also grant them the ability to speak Basic."
+ - tweak: "The max rank a borer can upgrade a humanoid to has been reduced to Masterclass, down from Grandmasterclass. Lesser forms, such as monkeys, can only be upgraded to operant levels."
+ - bugfix: "Dead players can now properly see when a ghost is whispering to someone."
+ - tweak: "Borer psionic awakening no longer has a chance to give someone paramount psionics."
+ - tweak: "Borers upgrading psionics will now skip the Latent rank, which does nothing."
+ - bugfix: "Upgrading faculties will now always update the psionic powers available."
\ No newline at end of file
diff --git a/icons/mob/screen/borer.dmi b/icons/mob/screen/borer.dmi
new file mode 100644
index 00000000000..89b09c649f0
Binary files /dev/null and b/icons/mob/screen/borer.dmi differ
diff --git a/icons/mob/screen/captive_brain.dmi b/icons/mob/screen/captive_brain.dmi
new file mode 100644
index 00000000000..e7aefc64494
Binary files /dev/null and b/icons/mob/screen/captive_brain.dmi differ