diff --git a/code/__DEFINES/say.dm b/code/__DEFINES/say.dm
index 070837bee97..4b4417607b1 100644
--- a/code/__DEFINES/say.dm
+++ b/code/__DEFINES/say.dm
@@ -15,6 +15,7 @@
#define MODE_ALIEN "alientalk"
#define MODE_HOLOPAD "holopad"
#define MODE_CHANGELING "changeling"
+#define MODE_VOCALCORDS "cords"
//Spans. Robot speech, italics, etc. Applied in compose_message().
#define SPAN_ROBOT "robot"
diff --git a/code/__HELPERS/text.dm b/code/__HELPERS/text.dm
index 1f2d86c97d8..37dfc5afb1a 100644
--- a/code/__HELPERS/text.dm
+++ b/code/__HELPERS/text.dm
@@ -206,6 +206,20 @@
if(start)
return findtextEx(text, suffix, start, null)
+//Checks if any of a given list of needles is in the haystack
+/proc/text_in_list(haystack, list/needle_list)
+ for(var/needle in needle_list)
+ if(findtext(haystack, needle))
+ return 1
+ return 0
+
+//Like above, but case sensitive
+/proc/text_in_list_case(haystack, list/needle_list)
+ for(var/needle in needle_list)
+ if(findtextEx(haystack, needle))
+ return 1
+ return 0
+
//Adds 'u' number of zeros ahead of the text 't'
/proc/add_zero(t, u)
while (length(t) < u)
diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm
index b108c7c47b9..6459bf34f12 100644
--- a/code/modules/mob/living/say.dm
+++ b/code/modules/mob/living/say.dm
@@ -17,6 +17,7 @@ var/list/department_radio_keys = list(
":o" = "AI Private", "#o" = "AI Private", ".o" = "AI Private",
":g" = "changeling", "#g" = "changeling", ".g" = "changeling",
":y" = "Centcom", "#y" = "Centcom", ".y" = "Centcom",
+ ":x" = "cords", "#x" = "cords", ".x" = "cords",
":R" = "right hand", "#R" = "right hand", ".R" = "right hand",
":L" = "left hand", "#L" = "left hand", ".L" = "left hand",
@@ -36,6 +37,7 @@ var/list/department_radio_keys = list(
":O" = "AI Private", "#O" = "AI Private", ".O" = "AI Private",
":G" = "changeling", "#G" = "changeling", ".G" = "changeling",
":Y" = "Centcom", "#Y" = "Centcom", ".Y" = "Centcom",
+ ":X" = "cords", "#X" = "cords", ".X" = "cords",
//kinda localization -- rastaf0
//same keys as above, but on russian keyboard layout. This file uses cp1251 as encoding.
@@ -243,6 +245,13 @@ var/list/crit_allowed_modes = list(MODE_WHISPER,MODE_CHANGELING,MODE_ALIEN)
if(hivecheck())
alien_talk(message)
return 1
+ if(message_mode == MODE_VOCALCORDS)
+ if(iscarbon(src))
+ var/mob/living/carbon/C = src
+ var/obj/item/organ/vocal_cords/V = C.getorganslot("vocal_cords")
+ if(V.can_speak_with())
+ C.say(V.speak_with(message), spans = V.spans)
+ return 1
return 0
/mob/living/proc/treat_message(message)
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
index 0653bf47470..ef94ca5c846 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
@@ -46,7 +46,7 @@ Difficulty: Very Hard
del_on_death = 1
medal_type = MEDAL_PREFIX
score_type = COLOSSUS_SCORE
- loot = list(/obj/machinery/anomalous_crystal/random, /obj/item/organ/colossus)
+ loot = list(/obj/machinery/anomalous_crystal/random, /obj/item/organ/vocal_cords/colossus)
butcher_results = list(/obj/item/weapon/ore/diamond = 5, /obj/item/stack/sheet/sinew = 5, /obj/item/stack/sheet/animalhide/ashdrake = 10, /obj/item/stack/sheet/bone = 30)
deathmessage = "disintegrates, leaving a glowing core in its wake."
death_sound = 'sound/magic/demon_dies.ogg'
diff --git a/code/modules/surgery/organs/lavaland.dm b/code/modules/surgery/organs/lavaland.dm
index 3be4be79079..a8f16a0853a 100644
--- a/code/modules/surgery/organs/lavaland.dm
+++ b/code/modules/surgery/organs/lavaland.dm
@@ -1,21 +1,27 @@
-/obj/item/organ/colossus
+/obj/item/organ/vocal_cords/colossus
name = "divine vocal cords"
icon_state = "voice_of_god"
zone = "mouth"
slot = "vocal_cords"
actions_types = list(/datum/action/item_action/organ_action/colossus)
-
-/datum/action/item_action/organ_action/colossus
- name = "Voice of God"
- var/next_command = null
+ var/next_command = 0
var/cooldown_stun = 900
var/cooldown_damage = 600
var/cooldown_meme = 300
var/cooldown_none = 150
var/base_multiplier = 1
+ spans = list("colossus","yell")
+
+/datum/action/item_action/organ_action/colossus
+ name = "Voice of God"
+ var/obj/item/organ/vocal_cords/colossus/cords = null
+
+/datum/action/item_action/organ_action/colossus/New()
+ ..()
+ cords = target
/datum/action/item_action/organ_action/colossus/IsAvailable()
- if(world.time < next_command)
+ if(world.time < cords.next_command)
return 0
if(!owner)
return 0
@@ -28,20 +34,36 @@
/datum/action/item_action/organ_action/colossus/Trigger()
. = ..()
+ if(!IsAvailable())
+ if(world.time < cords.next_command)
+ owner << "You must wait [(cords.next_command - world.time)/10] seconds before Speaking again."
+ return
var/command = stripped_input(owner, "Speak with the Voice of God", "Command", max_length = 140)
if(!command)
return
+ owner.say(".x[command]")
+
+/obj/item/organ/vocal_cords/colossus/can_speak_with()
+ if(world.time < next_command)
+ owner << "You must wait [(next_command - world.time)/10] seconds before Speaking again."
+ return 0
+ if(!owner)
+ return 0
+ if(!owner.can_speak())
+ owner << "You are unable to speak!"
+ return 0
+ if(owner.stat)
+ return 0
+ return 1
+
+/obj/item/organ/vocal_cords/colossus/speak_with(message)
+ var/spoken = uppertext(message)
+ playsound(get_turf(owner), 'sound/magic/clockwork/invoke_general.ogg', 300, 1, 5)
+
var/mob/living/list/listeners = list()
for(var/mob/living/L in get_hearers_in_view(8, owner))
if(!L.ear_deaf && L != owner && L.stat != DEAD)
listeners += L
- if(!IsAvailable())
- if(world.time < next_command)
- owner << "You must wait [(next_command - world.time)/10] seconds before Speaking again."
- return
- var/spoken = uppertext(command)
- owner.say(spoken, spans = list("colossus","yell"))
- playsound(get_turf(owner), 'sound/magic/clockwork/invoke_general.ogg', 300, 1, 5)
if(!listeners.len)
next_command = world.time + cooldown_none
@@ -63,92 +85,92 @@
for(var/V in listeners)
var/mob/living/L = V
var/start
- if(L.mind && L.mind.devilinfo && findtext(command, L.mind.devilinfo.truename))
- start = findtext(command, L.mind.devilinfo.truename)
+ if(L.mind && L.mind.devilinfo && findtext(message, L.mind.devilinfo.truename))
+ start = findtext(message, L.mind.devilinfo.truename)
listeners = list(L)
power_multiplier *= 5 //if you're a devil and god himself addressed you, you fucked up
//Cut out the name so it doesn't trigger commands
- command = copytext(command, 0, start)+copytext(command, start + length(L.mind.devilinfo.truename), length(command) + 1)
+ message = copytext(message, 0, start)+copytext(message, start + length(L.mind.devilinfo.truename), length(message) + 1)
break
- else if(findtext(command, L.real_name))
- start = findtext(command, L.real_name)
+ else if(findtext(message, L.real_name))
+ start = findtext(message, L.real_name)
listeners = list(L) //focus on a particular person
power_multiplier *= 2
//Cut out the name so it doesn't trigger commands
- command = copytext(command, 0, start)+copytext(command, start + length(L.real_name), length(command) + 1)
+ message = copytext(message, 0, start)+copytext(message, start + length(L.real_name), length(message) + 1)
break
//STUN
- if(findtext(command, "stop") || findtext(command, "wait") || findtext(command, "stand still") || findtext(command, "hold on") || findtext(command, "halt"))
+ if(text_in_list(message, list("stop","wait","stand still","hold on","halt")))
for(var/V in listeners)
var/mob/living/L = V
L.Stun(3 * power_multiplier)
next_command = world.time + cooldown_stun
//WEAKEN
- else if(findtext(command, "drop") || findtext(command, "fall"))
+ else if(text_in_list(message, list("drop","fall")))
for(var/V in listeners)
var/mob/living/L = V
L.Weaken(3 * power_multiplier)
next_command = world.time + cooldown_stun
//SLEEP
- else if(findtext(command, "sleep"))
+ else if(text_in_list(message, list("sleep")))
for(var/V in listeners)
var/mob/living/L = V
L.Sleeping(3 * power_multiplier)
next_command = world.time + cooldown_stun
//VOMIT
- else if(findtext(command, "vomit") || findtext(command, "throw up"))
+ else if(text_in_list(message, list("vomit","throw up")))
for(var/mob/living/carbon/C in listeners)
C.vomit(10 * power_multiplier)
next_command = world.time + cooldown_stun
//SILENCE
- else if(findtext(command, "shut up") || findtext(command, "silence") || findtext(command, "ssh") || findtext(command, "quiet"))
+ else if(text_in_list(message, list("shut up","silence","ssh","quiet")))
for(var/mob/living/carbon/C in listeners)
- if(owner.mind && (owner.mind.assigned_role == "Librarian" || owner.mind.assigned_role == "Mime")
+ if(owner.mind && (owner.mind.assigned_role == "Librarian" || owner.mind.assigned_role == "Mime"))
power_multiplier *= 3
C.silent += (10 * power_multiplier)
next_command = world.time + cooldown_stun
//HALLUCINATE
- else if(findtext(command, "see the truth") || findtext(command, "hallucinate"))
+ else if(text_in_list(message, list("see the truth","hallucinate")))
for(var/V in listeners)
var/mob/living/L = V
new /obj/effect/hallucination/delusion(get_turf(L),L,duration=150 * power_multiplier,skip_nearby=0)
next_command = world.time + cooldown_damage
//WAKE UP
- else if(findtext(command, "wake up") || findtext(command, "awaken"))
+ else if(text_in_list(message, list("wake up","awaken")))
for(var/V in listeners)
var/mob/living/L = V
L.SetSleeping(0)
next_command = world.time + cooldown_damage
//HEAL
- else if(findtext(command, "live") || findtext(command, "heal") || findtext(command, "survive") || findtext(command, "mend") || findtext(command, "heroes never die"))
+ else if(text_in_list(message, list("live","heal","survive","mend","heroes never die")))
for(var/V in listeners)
var/mob/living/L = V
L.heal_overall_damage(10 * power_multiplier, 10 * power_multiplier, 0, 0)
next_command = world.time + cooldown_damage
//BRUTE DAMAGE
- else if(findtext(command, "die") || findtext(command, "suffer"))
+ else if(text_in_list(message, list("die","suffer")))
for(var/V in listeners)
var/mob/living/L = V
L.apply_damage(15 * power_multiplier, def_zone = "chest")
next_command = world.time + cooldown_damage
//BLEED
- else if(findtext(command, "bleed"))
+ else if(text_in_list(message, list("bleed")))
for(var/mob/living/carbon/human/H in listeners)
H.bleed_rate += (5 * power_multiplier)
next_command = world.time + cooldown_damage
//FIRE
- else if(findtext(command, "burn") || findtext(command, "ignite") || findtext(command, "hell"))
+ else if(text_in_list(message, list("burn","ignite","hell")))
for(var/V in listeners)
var/mob/living/L = V
L.adjust_fire_stacks(1 * power_multiplier)
@@ -156,7 +178,7 @@
next_command = world.time + cooldown_damage
//REPULSE
- else if(findtext(command, "shoo") || findtext(command, "go away") || findtext(command, "leave me alone") || findtext(command, "begone") || findtext(command, "flee") || findtext(command, "fus ro dah"))
+ else if(text_in_list(message, list("shoo","go away","leave me alone","begone","flee","fus ro dah")))
for(var/V in listeners)
var/mob/living/L = V
var/throwtarget = get_edge_target_turf(owner, get_dir(owner, get_step_away(L, owner)))
@@ -164,7 +186,7 @@
next_command = world.time + cooldown_damage
//WHO ARE YOU?
- else if(findtext(command, "who are you") || findtext(command, "say your name") || findtext(command, "state your name") || findtext(command, "identify"))
+ else if(text_in_list(message, list("who are you","say your name","state your name","identify")))
for(var/V in listeners)
var/mob/living/L = V
if(L.mind && L.mind.devilinfo)
@@ -174,85 +196,85 @@
next_command = world.time + cooldown_meme
//SAY MY NAME
- else if(findtext(command, "say my name"))
+ else if(text_in_list(message, list("say my name")))
for(var/V in listeners)
var/mob/living/L = V
L.say("[owner.name]!") //"Unknown!"
next_command = world.time + cooldown_meme
//KNOCK KNOCK
- else if(findtext(command, "knock knock"))
+ else if(text_in_list(message, list("knock knock")))
for(var/V in listeners)
var/mob/living/L = V
L.say("Who's there?")
next_command = world.time + cooldown_meme
- //MOVE
- else if(findtext(command, "state laws") || findtext(command, "state your laws"))
+ //STATE LAWS
+ else if(text_in_list(message, list("state laws","state your laws")))
for(var/mob/living/silicon/S in listeners)
S.statelaws()
next_command = world.time + cooldown_meme
//MOVE
- else if(findtext(command, "move"))
+ else if(text_in_list(message, list("move")))
for(var/V in listeners)
var/mob/living/L = V
step(L, pick(cardinal))
next_command = world.time + cooldown_meme
//WALK
- else if(findtext(command, "walk") || findtext(command, "slow down"))
+ else if(text_in_list(message, list("walk","slow down")))
for(var/V in listeners)
var/mob/living/L = V
L.m_intent = MOVE_INTENT_WALK
next_command = world.time + cooldown_meme
//RUN
- else if(findtext(command, "run"))
+ else if(text_in_list(message, list("run")))
for(var/V in listeners)
var/mob/living/L = V
L.m_intent = MOVE_INTENT_RUN
next_command = world.time + cooldown_meme
//HELP INTENT
- else if(findtext(command, "help"))
+ else if(text_in_list(message, list("help")))
for(var/mob/living/carbon/human/H in listeners)
H.a_intent_change(INTENT_HELP)
next_command = world.time + cooldown_meme
//DISARM INTENT
- else if(findtext(command, "disarm"))
+ else if(text_in_list(message, list("disarm")))
for(var/mob/living/carbon/human/H in listeners)
H.a_intent_change(INTENT_DISARM)
next_command = world.time + cooldown_meme
//GRAB INTENT
- else if(findtext(command, "grab"))
+ else if(text_in_list(message, list("grab")))
for(var/mob/living/carbon/human/H in listeners)
H.a_intent_change(INTENT_GRAB)
next_command = world.time + cooldown_meme
//HARM INTENT
- else if(findtext(command, "harm") || findtext(command, "fight"))
+ else if(text_in_list(message, list("harm","fight")))
for(var/mob/living/carbon/human/H in listeners)
H.a_intent_change(INTENT_HARM)
next_command = world.time + cooldown_meme
//THROW/CATCH
- else if(findtext(command, "throw") || findtext(command, "catch"))
+ else if(text_in_list(message, list("throw","catch")))
for(var/mob/living/carbon/C in listeners)
C.throw_mode_on()
next_command = world.time + cooldown_meme
//FLIP
- else if(findtext(command, "flip") || findtext(command, "rotate") || findtext(command, "revolve") || findtext(command, "roll") || findtext(command, "somersault"))
+ else if(text_in_list(message, list("flip","rotate","revolve","roll","somersault")))
for(var/V in listeners)
var/mob/living/L = V
L.emote("flip")
next_command = world.time + cooldown_meme
//REST
- else if(findtext(command, "rest"))
+ else if(text_in_list(message, list("rest")))
for(var/V in listeners)
var/mob/living/L = V
if(!L.resting)
@@ -260,7 +282,7 @@
next_command = world.time + cooldown_meme
//GET UP
- else if(findtext(command, "get up"))
+ else if(text_in_list(message, list("get up")))
for(var/V in listeners)
var/mob/living/L = V
if(L.resting)
@@ -271,7 +293,7 @@
next_command = world.time + cooldown_damage
//SIT
- else if(findtext(command, "sit"))
+ else if(text_in_list(message, list("sit")))
for(var/V in listeners)
var/mob/living/L = V
for(var/obj/structure/chair/chair in get_turf(L))
@@ -280,7 +302,7 @@
next_command = world.time + cooldown_meme
//STAND UP
- else if(findtext(command, "stand"))
+ else if(text_in_list(message, list("stand")))
for(var/V in listeners)
var/mob/living/L = V
if(L.buckled && istype(L.buckled, /obj/structure/chair))
@@ -288,14 +310,14 @@
next_command = world.time + cooldown_meme
//DANCE
- else if(findtext(command, "dance"))
+ else if(text_in_list(message, list("dance")))
for(var/V in listeners)
var/mob/living/L = V
L.emote("dance")
next_command = world.time + cooldown_meme
//JUMP
- else if(findtext(command, "jump"))
+ else if(text_in_list(message, list("jump")))
for(var/V in listeners)
var/mob/living/L = V
L.say("HOW HIGH?!!")
@@ -303,28 +325,28 @@
next_command = world.time + cooldown_meme
//SALUTE
- else if(findtext(command, "salute"))
+ else if(text_in_list(message, list("salute")))
for(var/V in listeners)
var/mob/living/L = V
L.emote("salute")
next_command = world.time + cooldown_meme
//PLAY DEAD
- else if(findtext(command, "play dead"))
+ else if(text_in_list(message, list("play dead")))
for(var/V in listeners)
var/mob/living/L = V
L.emote("deathgasp")
next_command = world.time + cooldown_meme
//PLEASE CLAP
- else if(findtext(command, "clap") || findtext(command, "applaud"))
+ else if(text_in_list(message, list("clap","applaud")))
for(var/V in listeners)
var/mob/living/L = V
L.emote("clap")
next_command = world.time + cooldown_meme
//HONK
- else if(findtext(command, "honk"))
+ else if(text_in_list(message, list("honk")))
playsound(get_turf(owner), "sound/items/bikehorn.ogg", 300, 1)
if(owner.mind && owner.mind.assigned_role == "Clown")
for(var/mob/living/carbon/C in listeners)
@@ -332,7 +354,7 @@
next_command = world.time + cooldown_meme
//RIGHT ROUND
- else if(findtext(command, "like a record baby"))
+ else if(text_in_list(message, list("like a record baby")))
for(var/V in listeners)
var/mob/living/L = V
L.SpinAnimation(speed = 10, loops = 5)
@@ -341,3 +363,5 @@
else
next_command = world.time + cooldown_none
+ return spoken
+
diff --git a/code/modules/surgery/organs/organ_internal.dm b/code/modules/surgery/organs/organ_internal.dm
index 02bccedf315..16e6c8abe31 100644
--- a/code/modules/surgery/organs/organ_internal.dm
+++ b/code/modules/surgery/organs/organ_internal.dm
@@ -693,3 +693,16 @@
if(inflamed)
S.reagents.add_reagent("????", 5)
return S
+
+/obj/item/organ/vocal_cords //organs that are activated through speech with the :x channel
+ name = "vocal cords"
+ icon_state = "appendix"
+ zone = "mouth"
+ slot = "vocal_cords"
+ var/list/spans = null
+
+/obj/item/organ/vocal_cords/proc/can_speak_with() //if there is any limitation to speaking with these cords
+ return TRUE
+
+/obj/item/organ/vocal_cords/proc/speak_with(message) //do what the organ does and modify speech if needed
+ return message