" // Tweaked for security headsets -- TLE
- var/blackbox_msg = "[part_a][M.name][part_blackbox_b][quotedmsg][part_c]"
- //var/blackbox_admin_msg = "[part_a][M.name] (Real name: [M.real_name])[part_blackbox_b][quotedmsg][part_c]"
- if(istype(blackbox))
- //BR.messages_admin += blackbox_admin_msg
- switch(display_freq)
- if(1459)
- blackbox.msg_common += blackbox_msg
- if(1351)
- blackbox.msg_science += blackbox_msg
- if(1353)
- blackbox.msg_command += blackbox_msg
- if(1355)
- blackbox.msg_medical += blackbox_msg
- if(1357)
- blackbox.msg_engineering += blackbox_msg
- if(1359)
- blackbox.msg_security += blackbox_msg
- if(1441)
- blackbox.msg_deathsquad += blackbox_msg
- if(1213)
- blackbox.msg_syndicate += blackbox_msg
- if(1347)
- blackbox.msg_cargo += blackbox_msg
- if(1349)
- blackbox.msg_service += blackbox_msg
- else
- blackbox.messages += blackbox_msg
- //End of research and feedback code.
-
- if (length(heard_masked))
- var/N = M.name
- var/J = eqjobname
- if(ishuman(M) && M.GetVoice() != M.real_name)
- N = M.GetVoice()
- J = "Unknown"
- var/rendered = "[part_a][N][part_b][quotedmsg][part_c]"
- for (var/mob/R in heard_masked)
- if(istype(R, /mob/living/silicon/ai))
- R.show_message("[part_a][N] ([J]) \[OPEN\] [part_b][quotedmsg][part_c]", 2)
- else
- R.show_message(rendered, 2)
-
- if (length(heard_normal))
- var/rendered = "[part_a][M.real_name][part_b][quotedmsg][part_c]"
-
- for (var/mob/R in heard_normal)
- if(istype(R, /mob/living/silicon/ai))
- R.show_message("[part_a][M.real_name] ([eqjobname]) \[OPEN\] [part_b][quotedmsg][part_c]", 2)
- else
- R.show_message(rendered, 2)
-
- if (length(heard_voice))
- var/rendered = "[part_a][M.voice_name][part_b][pick(M.speak_emote)][part_c]"
-
- for (var/mob/R in heard_voice)
- if(istype(R, /mob/living/silicon/ai))
- R.show_message("[part_a][M.voice_name] ([eqjobname]) \[OPEN\] [part_b][pick(M.speak_emote)][part_c]", 2)
- else
- R.show_message(rendered, 2)
-
- if (length(heard_garbled))
- quotedmsg = M.say_quote(stars(message))
- var/rendered = "[part_a][M.voice_name][part_b][quotedmsg][part_c]"
-
- for (var/mob/R in heard_voice)
- if(istype(R, /mob/living/silicon/ai))
- R.show_message("[part_a][M.voice_name]\[OPEN\] [part_b][quotedmsg][part_c]", 2)
- else
- R.show_message(rendered, 2)
-
-/obj/item/device/radio/hear_talk(mob/M as mob, msg)
+/obj/item/device/radio/hear_talk(mob/M as mob, msg, var/verb = "says", var/datum/language/speaking = null)
if (broadcasting)
if(get_dist(src, M) <= canhear_range)
- talk_into(M, msg)
+ talk_into(M, msg,null,verb,speaking)
+
+
/*
/obj/item/device/radio/proc/accept_rad(obj/item/device/radio/R as obj, message)
@@ -621,7 +432,7 @@ var/GLOBAL_RADIO_TYPE = 1 // radio type to use
// what the range is in which mobs will hear the radio
// returns: -1 if can't receive, range otherwise
- if (isWireCut(WIRE_RECEIVE))
+ if (wires.IsIndexCut(WIRE_RECEIVE))
return -1
if(!listening)
return -1
@@ -629,7 +440,7 @@ var/GLOBAL_RADIO_TYPE = 1 // radio type to use
var/turf/position = get_turf(src)
if(!position || !(position.z in level))
return -1
- if(freq == SYND_FREQ)
+ if(freq in ANTAG_FREQS)
if(!(src.syndie))//Checks to see if it's allowed on that frequency, based on the encryption keys
return -1
if (!on)
@@ -656,15 +467,13 @@ var/GLOBAL_RADIO_TYPE = 1 // radio type to use
return get_mobs_in_view(canhear_range, src)
-/obj/item/device/radio/examine()
- set src in view()
-
- ..()
- if ((in_range(src, usr) || loc == usr))
+/obj/item/device/radio/examine(mob/user)
+ . = ..()
+ if ((in_range(src, user) || loc == user))
if (b_stat)
- usr.show_message("\blue \the [src] can be attached and modified!")
+ user.show_message("\blue \the [src] can be attached and modified!")
else
- usr.show_message("\blue \the [src] can not be modified or attached!")
+ user.show_message("\blue \the [src] can not be modified or attached!")
return
/obj/item/device/radio/attackby(obj/item/weapon/W as obj, mob/user as mob)
@@ -697,8 +506,14 @@ var/GLOBAL_RADIO_TYPE = 1 // radio type to use
//Giving borgs their own radio to have some more room to work with -Sieve
/obj/item/device/radio/borg
+ var/mob/living/silicon/robot/myborg = null // Cyborg which owns this radio. Used for power checks
var/obj/item/device/encryptionkey/keyslot = null//Borg radios can handle a single encryption key
-
+ var/shut_up = 0
+ icon = 'icons/obj/robot_component.dmi' // Cyborgs radio icons should look like the component.
+ icon_state = "radio"
+ canhear_range = 3
+ subspace_transmission = 1
+
/obj/item/device/radio/borg/syndicate
syndie = 1
keyslot = new /obj/item/device/encryptionkey/syndicate
@@ -713,6 +528,13 @@ var/GLOBAL_RADIO_TYPE = 1 // radio type to use
..()
set_frequency(1441)
+/obj/item/device/radio/borg/talk_into()
+ . = ..()
+ if (isrobot(src.loc))
+ var/mob/living/silicon/robot/R = src.loc
+ var/datum/robot_component/C = R.components["radio"]
+ R.use_power(C.energy_consumption)
+
/obj/item/device/radio/borg/attackby(obj/item/weapon/W as obj, mob/user as mob)
// ..()
user.set_machine(src)
@@ -758,18 +580,25 @@ var/GLOBAL_RADIO_TYPE = 1 // radio type to use
src.channels = list()
src.syndie = 0
+ var/mob/living/silicon/robot/D = src.loc
+ if(D.module)
+ for(var/ch_name in D.module.channels)
+ if(ch_name in src.channels)
+ continue
+ src.channels += ch_name
+ src.channels[ch_name] += D.module.channels[ch_name]
if(keyslot)
for(var/ch_name in keyslot.channels)
if(ch_name in src.channels)
continue
src.channels += ch_name
- src.channels[ch_name] = keyslot.channels[ch_name]
+ src.channels[ch_name] += keyslot.channels[ch_name]
if(keyslot.syndie)
src.syndie = 1
- for (var/ch_name in channels)
+ for (var/ch_name in src.channels)
if(!radio_controller)
sleep(30) // Waiting for the radio_controller to be created.
if(!radio_controller)
@@ -784,12 +613,23 @@ var/GLOBAL_RADIO_TYPE = 1 // radio type to use
if(usr.stat || !on)
return
if (href_list["mode"])
- subspace_transmission = !subspace_transmission
- if(!subspace_transmission)//Simple as fuck, clears the channel list to prevent talking/listening over them if subspace transmission is disabled
+ if(subspace_transmission != 1)
+ subspace_transmission = 1
+ usr << "Subspace Transmission is enabled"
+ else
+ subspace_transmission = 0
+ usr << "Subspace Transmission is disabled"
+ if(subspace_transmission == 0)//Simple as fuck, clears the channel list to prevent talking/listening over them if subspace transmission is disabled
channels = list()
else
recalculateChannels()
- usr << "Subspace Transmission is [(subspace_transmission) ? "enabled" : "disabled"]"
+ if (href_list["shutup"]) // Toggle loudspeaker mode, AKA everyone around you hearing your radio.
+ shut_up = !shut_up
+ if(shut_up)
+ canhear_range = 0
+ else
+ canhear_range = 3
+
..()
/obj/item/device/radio/borg/interact(mob/user as mob)
@@ -806,6 +646,7 @@ var/GLOBAL_RADIO_TYPE = 1 // radio type to use
+
+
Toggle Broadcast Mode
+ Loudspeaker: [shut_up ? "Disengaged" : "Engaged"]
"}
if(subspace_transmission)//Don't even bother if subspace isn't turned on
@@ -830,11 +671,3 @@ var/GLOBAL_RADIO_TYPE = 1 // radio type to use
/obj/item/device/radio/off
listening = 0
-
-/obj/item/device/radio/Destroy()
- if(radio_connection)
- radio_connection.remove_listener(src)
- if(isrobot(src.loc))
- var/mob/living/silicon/robot/R = src.loc
- R.radio = null
- ..()
diff --git a/code/game/objects/items/devices/transfer_valve.dm b/code/game/objects/items/devices/transfer_valve.dm
index a3e0d2f7b78..1dc2142d31a 100644
--- a/code/game/objects/items/devices/transfer_valve.dm
+++ b/code/game/objects/items/devices/transfer_valve.dm
@@ -63,6 +63,10 @@
attached_device.HasProximity(AM)
return
+/obj/item/device/transfer_valve/hear_talk(mob/living/M as mob, msg)
+ ..()
+ for(var/obj/O in contents)
+ O.hear_talk(M, msg)
/obj/item/device/transfer_valve/attack_self(mob/user as mob)
ui_interact(user)
diff --git a/code/game/objects/items/robot/robot_upgrades.dm b/code/game/objects/items/robot/robot_upgrades.dm
index 23bed41262c..41520fabb93 100644
--- a/code/game/objects/items/robot/robot_upgrades.dm
+++ b/code/game/objects/items/robot/robot_upgrades.dm
@@ -32,9 +32,11 @@
R.icon_state = "robot"
del(R.module)
R.module = null
- R.camera.network.Remove(list("Engineering","Medical","MINE"))
+ R.camera.network.Remove(list("Engineering","Medical","Mining Outpost"))
R.updatename("Default")
R.status_flags |= CANPUSH
+ R.languages = list()
+ R.speech_synthesizer_langs = list()
R.notify_ai(2)
R.updateicon()
diff --git a/code/game/objects/structures/girders.dm b/code/game/objects/structures/girders.dm
index d8e22940639..7bf35f6c6e0 100644
--- a/code/game/objects/structures/girders.dm
+++ b/code/game/objects/structures/girders.dm
@@ -4,151 +4,83 @@
density = 1
layer = 2
var/state = 0
+ var/health = 200
- attackby(obj/item/W as obj, mob/user as mob)
- if(istype(W, /obj/item/weapon/wrench) && state == 0)
- if(anchored && !istype(src,/obj/structure/girder/displaced))
- playsound(src.loc, 'sound/items/Ratchet.ogg', 100, 1)
- user << "\blue Now disassembling the girder"
- if(do_after(user,40))
- if(!src) return
- user << "\blue You dissasembled the girder!"
- new /obj/item/stack/sheet/metal(get_turf(src))
- del(src)
- else if(!anchored)
- playsound(src.loc, 'sound/items/Ratchet.ogg', 100, 1)
- user << "\blue Now securing the girder"
- if(get_turf(user, 40))
- user << "\blue You secured the girder!"
- new/obj/structure/girder( src.loc )
- del(src)
-
- else if(istype(W, /obj/item/weapon/pickaxe/plasmacutter))
- user << "\blue Now slicing apart the girder"
- if(do_after(user,30))
+/obj/structure/girder/attackby(obj/item/W as obj, mob/user as mob)
+ if(istype(W, /obj/item/weapon/wrench) && state == 0)
+ if(anchored && !istype(src,/obj/structure/girder/displaced))
+ playsound(src.loc, 'sound/items/Ratchet.ogg', 100, 1)
+ user << "\blue Now disassembling the girder"
+ if(do_after(user,40))
if(!src) return
- user << "\blue You slice apart the girder!"
+ user << "\blue You dissasembled the girder!"
new /obj/item/stack/sheet/metal(get_turf(src))
del(src)
-
- else if(istype(W, /obj/item/weapon/pickaxe/diamonddrill))
- user << "\blue You drill through the girder!"
- new /obj/item/stack/sheet/metal(get_turf(src))
- del(src)
-
- else if(istype(W, /obj/item/weapon/screwdriver) && state == 2 && istype(src,/obj/structure/girder/reinforced))
- playsound(src.loc, 'sound/items/Screwdriver.ogg', 100, 1)
- user << "\blue Now unsecuring support struts"
- if(do_after(user,40))
- if(!src) return
- user << "\blue You unsecured the support struts!"
- state = 1
-
- else if(istype(W, /obj/item/weapon/wirecutters) && istype(src,/obj/structure/girder/reinforced) && state == 1)
- playsound(src.loc, 'sound/items/Wirecutter.ogg', 100, 1)
- user << "\blue Now removing support struts"
- if(do_after(user,40))
- if(!src) return
- user << "\blue You removed the support struts!"
+ else if(!anchored)
+ playsound(src.loc, 'sound/items/Ratchet.ogg', 100, 1)
+ user << "\blue Now securing the girder"
+ if(get_turf(user, 40))
+ user << "\blue You secured the girder!"
new/obj/structure/girder( src.loc )
del(src)
- else if(istype(W, /obj/item/weapon/crowbar) && state == 0 && anchored )
- playsound(src.loc, 'sound/items/Crowbar.ogg', 100, 1)
- user << "\blue Now dislodging the girder"
- if(do_after(user, 40))
- if(!src) return
- user << "\blue You dislodged the girder!"
- new/obj/structure/girder/displaced( src.loc )
- del(src)
+ else if(istype(W, /obj/item/weapon/pickaxe/plasmacutter))
+ user << "\blue Now slicing apart the girder"
+ if(do_after(user,30))
+ if(!src) return
+ user << "\blue You slice apart the girder!"
+ new /obj/item/stack/sheet/metal(get_turf(src))
+ del(src)
- else if(istype(W, /obj/item/stack/sheet))
+ else if(istype(W, /obj/item/weapon/pickaxe/diamonddrill))
+ user << "\blue You drill through the girder!"
+ new /obj/item/stack/sheet/metal(get_turf(src))
+ del(src)
- var/obj/item/stack/sheet/S = W
- switch(S.type)
+ else if(istype(W, /obj/item/weapon/screwdriver) && state == 2 && istype(src,/obj/structure/girder/reinforced))
+ playsound(src.loc, 'sound/items/Screwdriver.ogg', 100, 1)
+ user << "\blue Now unsecuring support struts"
+ if(do_after(user,40))
+ if(!src) return
+ user << "\blue You unsecured the support struts!"
+ state = 1
- if(/obj/item/stack/sheet/metal, /obj/item/stack/sheet/metal/cyborg)
- if(!anchored)
- if(S.amount < 2) return
- var/pdiff=performWallPressureCheck(src.loc)
- if(!pdiff)
- S.use(2)
- user << "\blue You create a false wall! Push on it to open or close the passage."
- new /obj/structure/falsewall (src.loc)
- del(src)
- else
- user << "\red There is too much air moving through the gap! The door wouldn't stay closed if you built it."
- message_admins("Attempted false wall made by [user.real_name] ([formatPlayerPanel(user,user.ckey)]) at [formatJumpTo(loc)] had a pressure difference of [pdiff]!")
- log_admin("Attempted false wall made by [user.real_name] (user.ckey) at [loc] had a pressure difference of [pdiff]!")
- return
- else
- if(S.amount < 2) return ..()
- user << "\blue Now adding plating..."
- if (do_after(user,40))
- if(!src || !S || S.amount < 2) return
- S.use(2)
- user << "\blue You added the plating!"
- var/turf/Tsrc = get_turf(src)
- Tsrc.ChangeTurf(/turf/simulated/wall)
- for(var/turf/simulated/wall/X in Tsrc.loc)
- if(X) X.add_hiddenprint(usr)
- del(src)
- return
+ else if(istype(W, /obj/item/weapon/wirecutters) && istype(src,/obj/structure/girder/reinforced) && state == 1)
+ playsound(src.loc, 'sound/items/Wirecutter.ogg', 100, 1)
+ user << "\blue Now removing support struts"
+ if(do_after(user,40))
+ if(!src) return
+ user << "\blue You removed the support struts!"
+ new/obj/structure/girder( src.loc )
+ del(src)
- if(/obj/item/stack/sheet/plasteel)
- if(!anchored)
- if(S.amount < 2) return
- var/pdiff=performWallPressureCheck(src.loc)
- if(!pdiff)
- S.use(2)
- user << "\blue You create a false wall! Push on it to open or close the passage."
- new /obj/structure/falsewall/reinforced (src.loc)
- del(src)
- else
- user << "\red There is too much air moving through the gap! The door wouldn't stay closed if you built it."
- message_admins("Attempted false rwall made by [user.real_name] ([formatPlayerPanel(user,user.ckey)]) at [formatJumpTo(loc)] had a pressure difference of [pdiff]!")
- log_admin("Attempted false rwall made by [user.real_name] ([user.ckey]) at [loc] had a pressure difference of [pdiff]!")
- return
- else
- if (src.icon_state == "reinforced") //I cant believe someone would actually write this line of code...
- if(S.amount < 1) return ..()
- user << "\blue Now finalising reinforced wall."
- if(do_after(user, 50))
- if(!src || !S || S.amount < 1) return
- S.use(1)
- user << "\blue Wall fully reinforced!"
- var/turf/Tsrc = get_turf(src)
- Tsrc.ChangeTurf(/turf/simulated/wall/r_wall)
- for(var/turf/simulated/wall/r_wall/X in Tsrc.loc)
- if(X) X.add_hiddenprint(usr)
- del(src)
- return
- else
- if(S.amount < 1) return ..()
- user << "\blue Now reinforcing girders"
- if (do_after(user,60))
- if(!src || !S || S.amount < 1) return
- S.use(1)
- user << "\blue Girders reinforced!"
- new/obj/structure/girder/reinforced( src.loc )
- del(src)
- return
+ else if(istype(W, /obj/item/weapon/crowbar) && state == 0 && anchored )
+ playsound(src.loc, 'sound/items/Crowbar.ogg', 100, 1)
+ user << "\blue Now dislodging the girder"
+ if(do_after(user, 40))
+ if(!src) return
+ user << "\blue You dislodged the girder!"
+ new/obj/structure/girder/displaced( src.loc )
+ del(src)
- if(S.sheettype)
- var/M = S.sheettype
+ else if(istype(W, /obj/item/stack/sheet))
+
+ var/obj/item/stack/sheet/S = W
+ switch(S.type)
+
+ if(/obj/item/stack/sheet/metal, /obj/item/stack/sheet/metal/cyborg)
if(!anchored)
if(S.amount < 2) return
var/pdiff=performWallPressureCheck(src.loc)
if(!pdiff)
S.use(2)
user << "\blue You create a false wall! Push on it to open or close the passage."
- var/F = text2path("/obj/structure/falsewall/[M]")
- new F (src.loc)
+ new /obj/structure/falsewall (src.loc)
del(src)
else
user << "\red There is too much air moving through the gap! The door wouldn't stay closed if you built it."
- message_admins("Attempted false [M] wall made by [user.real_name] ([formatPlayerPanel(user,user.ckey)]) at [formatJumpTo(loc)] had a pressure difference of [pdiff]!")
- log_admin("Attempted false [M] wall made by [user.real_name] ([user.ckey]) at [loc] had a pressure difference of [pdiff]!")
+ message_admins("Attempted false wall made by [user.real_name] ([formatPlayerPanel(user,user.ckey)]) at [formatJumpTo(loc)] had a pressure difference of [pdiff]!")
+ log_admin("Attempted false wall made by [user.real_name] (user.ckey) at [loc] had a pressure difference of [pdiff]!")
return
else
if(S.amount < 2) return ..()
@@ -158,67 +90,138 @@
S.use(2)
user << "\blue You added the plating!"
var/turf/Tsrc = get_turf(src)
- Tsrc.ChangeTurf(text2path("/turf/simulated/wall/mineral/[M]"))
- for(var/turf/simulated/wall/mineral/X in Tsrc.loc)
+ Tsrc.ChangeTurf(/turf/simulated/wall)
+ for(var/turf/simulated/wall/X in Tsrc.loc)
if(X) X.add_hiddenprint(usr)
del(src)
return
- add_hiddenprint(usr)
+ if(/obj/item/stack/sheet/plasteel)
+ if(!anchored)
+ if(S.amount < 2) return
+ var/pdiff=performWallPressureCheck(src.loc)
+ if(!pdiff)
+ S.use(2)
+ user << "\blue You create a false wall! Push on it to open or close the passage."
+ new /obj/structure/falsewall/reinforced (src.loc)
+ del(src)
+ else
+ user << "\red There is too much air moving through the gap! The door wouldn't stay closed if you built it."
+ message_admins("Attempted false rwall made by [user.real_name] ([formatPlayerPanel(user,user.ckey)]) at [formatJumpTo(loc)] had a pressure difference of [pdiff]!")
+ log_admin("Attempted false rwall made by [user.real_name] ([user.ckey]) at [loc] had a pressure difference of [pdiff]!")
+ return
+ else
+ if (src.icon_state == "reinforced") //I cant believe someone would actually write this line of code...
+ if(S.amount < 1) return ..()
+ user << "\blue Now finalising reinforced wall."
+ if(do_after(user, 50))
+ if(!src || !S || S.amount < 1) return
+ S.use(1)
+ user << "\blue Wall fully reinforced!"
+ var/turf/Tsrc = get_turf(src)
+ Tsrc.ChangeTurf(/turf/simulated/wall/r_wall)
+ for(var/turf/simulated/wall/r_wall/X in Tsrc.loc)
+ if(X) X.add_hiddenprint(usr)
+ del(src)
+ return
+ else
+ if(S.amount < 1) return ..()
+ user << "\blue Now reinforcing girders"
+ if (do_after(user,60))
+ if(!src || !S || S.amount < 1) return
+ S.use(1)
+ user << "\blue Girders reinforced!"
+ new/obj/structure/girder/reinforced( src.loc )
+ del(src)
+ return
- else if(istype(W, /obj/item/pipe))
- var/obj/item/pipe/P = W
- if (P.pipe_type in list(0, 1, 5)) //simple pipes, simple bends, and simple manifolds.
- user.drop_item()
- P.loc = src.loc
- user << "\blue You fit the pipe into the [src]!"
- else
- ..()
-
-
- bullet_act(var/obj/item/projectile/Proj)
- if(istype(Proj ,/obj/item/projectile/beam/pulse))
- src.ex_act(2)
- ..()
- return 0
-
- blob_act()
- if(prob(40))
- del(src)
-
- bullet_act(var/obj/item/projectile/Proj)
- if(istype(Proj ,/obj/item/projectile/beam/pulse))
- src.ex_act(2)
- ..()
- return 0
-
- ex_act(severity)
- switch(severity)
- if(1.0)
- qdel(src)
- return
- if(2.0)
- if (prob(75))
- var/remains = pick(/obj/item/stack/rods,/obj/item/stack/sheet/metal)
- new remains(loc)
- qdel(src)
- return
- if(3.0)
- if (prob(30))
- var/remains = pick(/obj/item/stack/rods,/obj/item/stack/sheet/metal)
- new remains(loc)
- qdel(src)
- return
+ if(S.sheettype)
+ var/M = S.sheettype
+ if(!anchored)
+ if(S.amount < 2) return
+ var/pdiff=performWallPressureCheck(src.loc)
+ if(!pdiff)
+ S.use(2)
+ user << "\blue You create a false wall! Push on it to open or close the passage."
+ var/F = text2path("/obj/structure/falsewall/[M]")
+ new F (src.loc)
+ del(src)
+ else
+ user << "\red There is too much air moving through the gap! The door wouldn't stay closed if you built it."
+ message_admins("Attempted false [M] wall made by [user.real_name] ([formatPlayerPanel(user,user.ckey)]) at [formatJumpTo(loc)] had a pressure difference of [pdiff]!")
+ log_admin("Attempted false [M] wall made by [user.real_name] ([user.ckey]) at [loc] had a pressure difference of [pdiff]!")
+ return
else
- return
+ if(S.amount < 2) return ..()
+ user << "\blue Now adding plating..."
+ if (do_after(user,40))
+ if(!src || !S || S.amount < 2) return
+ S.use(2)
+ user << "\blue You added the plating!"
+ var/turf/Tsrc = get_turf(src)
+ Tsrc.ChangeTurf(text2path("/turf/simulated/wall/mineral/[M]"))
+ for(var/turf/simulated/wall/mineral/X in Tsrc.loc)
+ if(X) X.add_hiddenprint(usr)
+ del(src)
+ return
+
+ add_hiddenprint(usr)
+
+ else if(istype(W, /obj/item/pipe))
+ var/obj/item/pipe/P = W
+ if (P.pipe_type in list(0, 1, 5)) //simple pipes, simple bends, and simple manifolds.
+ user.drop_item()
+ P.loc = src.loc
+ user << "\blue You fit the pipe into the [src]!"
+ else
+ ..()
+
+/obj/structure/girder/blob_act()
+ if(prob(40))
+ del(src)
+
+/obj/structure/girder/bullet_act(var/obj/item/projectile/Proj)
+ if(istype(Proj, /obj/item/projectile/beam))
+ health -= Proj.damage
+ ..()
+ if(health <= 0)
+ new /obj/item/stack/sheet/metal(get_turf(src))
+ del(src)
+
+ if(istype(Proj ,/obj/item/projectile/beam/pulse))
+ src.ex_act(2)
+ ..()
+ return 0
+
+/obj/structure/girder/ex_act(severity)
+ switch(severity)
+ if(1.0)
+ qdel(src)
+ return
+ if(2.0)
+ if (prob(75))
+ var/remains = pick(/obj/item/stack/rods,/obj/item/stack/sheet/metal)
+ new remains(loc)
+ qdel(src)
+ return
+ if(3.0)
+ if (prob(30))
+ var/remains = pick(/obj/item/stack/rods,/obj/item/stack/sheet/metal)
+ new remains(loc)
+ qdel(src)
+ return
+ else
+ return
/obj/structure/girder/displaced
icon_state = "displaced"
anchored = 0
+ health = 50
/obj/structure/girder/reinforced
icon_state = "reinforced"
state = 2
+ health = 500
/obj/structure/cultgirder
icon= 'icons/obj/cult.dmi'
@@ -226,47 +229,55 @@
anchored = 1
density = 1
layer = 2
+ var/health = 250
- attackby(obj/item/W as obj, mob/user as mob)
- if(istype(W, /obj/item/weapon/wrench))
- playsound(src.loc, 'sound/items/Ratchet.ogg', 100, 1)
- user << "\blue Now disassembling the girder"
- if(do_after(user,40))
- user << "\blue You dissasembled the girder!"
- new /obj/effect/decal/remains/human(get_turf(src))
- del(src)
+/obj/structure/cultgirder/attackby(obj/item/W as obj, mob/user as mob)
+ if(istype(W, /obj/item/weapon/wrench))
+ playsound(src.loc, 'sound/items/Ratchet.ogg', 100, 1)
+ user << "\blue Now disassembling the girder"
+ if(do_after(user,40))
+ user << "\blue You dissasembled the girder!"
+ dismantle()
- else if(istype(W, /obj/item/weapon/pickaxe/plasmacutter))
- user << "\blue Now slicing apart the girder"
- if(do_after(user,30))
- user << "\blue You slice apart the girder!"
- new /obj/effect/decal/remains/human(get_turf(src))
- del(src)
+ else if(istype(W, /obj/item/weapon/pickaxe/plasmacutter))
+ user << "\blue Now slicing apart the girder"
+ if(do_after(user,30))
+ user << "\blue You slice apart the girder!"
+ dismantle()
- else if(istype(W, /obj/item/weapon/pickaxe/diamonddrill))
- user << "\blue You drill through the girder!"
- new /obj/effect/decal/remains/human(get_turf(src))
- del(src)
+ else if(istype(W, /obj/item/weapon/pickaxe/diamonddrill))
+ user << "\blue You drill through the girder!"
+ dismantle()
+
+/obj/structure/cultgirder/proc/dismantle()
+ new /obj/effect/decal/remains/human(get_turf(src))
+ qdel(src)
- blob_act()
- if(prob(40))
- del(src)
+/obj/structure/cultgirder/blob_act()
+ if(prob(40))
+ dismantle()
+
+/obj/structure/cultgirder/bullet_act(var/obj/item/projectile/Proj) //No beam check- How else will you destroy the cult girder with silver bullets?????
+ health -= Proj.damage
+ ..()
+ if(health <= 0)
+ dismantle()
+ return
-
- ex_act(severity)
- switch(severity)
- if(1.0)
+/obj/structure/cultgirder/ex_act(severity)
+ switch(severity)
+ if(1.0)
+ qdel(src)
+ return
+ if(2.0)
+ if (prob(30))
+ new /obj/effect/decal/remains/human(loc)
qdel(src)
- return
- if(2.0)
- if (prob(30))
- new /obj/effect/decal/remains/human(loc)
- qdel(src)
- return
- if(3.0)
- if (prob(5))
- new /obj/effect/decal/remains/human(loc)
- qdel(src)
- return
- else
- return
\ No newline at end of file
+ return
+ if(3.0)
+ if (prob(5))
+ new /obj/effect/decal/remains/human(loc)
+ qdel(src)
+ return
+ else
+ return
\ No newline at end of file
diff --git a/code/game/vehicles/vehicle.dm b/code/game/vehicles/vehicle.dm
index 9924fea07d0..a61015cf6d6 100644
--- a/code/game/vehicles/vehicle.dm
+++ b/code/game/vehicles/vehicle.dm
@@ -182,9 +182,9 @@
mecha.loc.assume_air(leaked_gas)
else
del(leaked_gas)
- if(mecha.hasInternalDamage(MECHA_INT_SHORT_CIRCUIT))
+ /*if(mecha.hasInternalDamage(MECHA_INT_SHORT_CIRCUIT)) // LAG INDUCING - it'll keep on creating sparks
if(mecha.get_charge())
mecha.spark_system.start()
mecha.cell.charge -= min(20,mecha.cell.charge)
- mecha.cell.maxcharge -= min(20,mecha.cell.maxcharge)
+ mecha.cell.maxcharge -= min(20,mecha.cell.maxcharge) */
return
\ No newline at end of file
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index ce3ca49fd22..e7ab656bfb4 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -42,7 +42,7 @@ var/list/admin_verbs_admin = list(
/client/proc/admin_memo, /*admin memo system. show/delete/write. +SERVER needed to delete admin memos of others*/
/client/proc/dsay, /*talk in deadchat using our ckey/fakekey*/
/client/proc/toggleprayers, /*toggles prayers on/off*/
-// /client/proc/toggle_hear_radio, /*toggles whether we hear the radio*/
+ /client/proc/toggle_hear_radio, /*toggles whether we hear the radio*/
/client/proc/investigate_show, /*various admintools for investigation. Such as a singulo grief-log*/
/datum/admins/proc/toggleooc, /*toggles ooc on/off for everyone*/
/datum/admins/proc/toggleoocdead, /*toggles ooc on/off for everyone who is dead*/
diff --git a/code/modules/awaymissions/corpse.dm b/code/modules/awaymissions/corpse.dm
index f62cef10073..7fa81ce358e 100644
--- a/code/modules/awaymissions/corpse.dm
+++ b/code/modules/awaymissions/corpse.dm
@@ -35,7 +35,6 @@
/obj/effect/landmark/corpse/proc/createCorpse() //Creates a mob and checks for gear in each slot before attempting to equip it.
var/mob/living/carbon/human/human/M = new /mob/living/carbon/human/human (src.loc)
- M.dna.mutantrace = mutantrace
M.real_name = src.name
M.death(1) //Kills the new mob
M.timeofdeath = timeofdeath
diff --git a/code/modules/awaymissions/mission_code/academy.dm b/code/modules/awaymissions/mission_code/academy.dm
index 997276e74b8..16af3141647 100644
--- a/code/modules/awaymissions/mission_code/academy.dm
+++ b/code/modules/awaymissions/mission_code/academy.dm
@@ -3,6 +3,7 @@
/area/awaymission/academy
name = "\improper Academy Asteroids"
icon_state = "away"
+ report_alerts = 0
/area/awaymission/academy/headmaster
name = "\improper Academy Fore Block"
diff --git a/code/modules/awaymissions/mission_code/blackmarketpackers.dm b/code/modules/awaymissions/mission_code/blackmarketpackers.dm
index d2d215053af..468863f3026 100644
--- a/code/modules/awaymissions/mission_code/blackmarketpackers.dm
+++ b/code/modules/awaymissions/mission_code/blackmarketpackers.dm
@@ -4,7 +4,7 @@
name = "\improper BMP Asteroids"
icon_state = "away"
luminosity = 0
-
+ report_alerts = 0
/area/awaymission/BMPship/Aft
name = "\improper Aft Block"
diff --git a/code/modules/awaymissions/mission_code/centcomAway.dm b/code/modules/awaymissions/mission_code/centcomAway.dm
index 70be2fa7202..06f36c5bf44 100644
--- a/code/modules/awaymissions/mission_code/centcomAway.dm
+++ b/code/modules/awaymissions/mission_code/centcomAway.dm
@@ -4,7 +4,8 @@
name = "XCC-P5831"
icon_state = "away"
requires_power = 0
-
+ report_alerts = 0
+
/area/awaymission/centcomAway/general
name = "XCC-P5831"
music = "music/ambigen3.ogg"
diff --git a/code/modules/awaymissions/mission_code/challenge.dm b/code/modules/awaymissions/mission_code/challenge.dm
index f7aa861eb80..966ce6053f6 100644
--- a/code/modules/awaymissions/mission_code/challenge.dm
+++ b/code/modules/awaymissions/mission_code/challenge.dm
@@ -1,5 +1,10 @@
//Challenge Areas
+/area/awaymission/challenge
+ name = "Challenge"
+ icon_state = "away"
+ report_alerts = 0
+
/area/awaymission/challenge/start
name = "Where Am I?"
icon_state = "away"
diff --git a/code/modules/awaymissions/mission_code/clownplanet.dm b/code/modules/awaymissions/mission_code/clownplanet.dm
index 45b0dc1c7de..359037ada0a 100644
--- a/code/modules/awaymissions/mission_code/clownplanet.dm
+++ b/code/modules/awaymissions/mission_code/clownplanet.dm
@@ -6,6 +6,7 @@
luminosity = 1
lighting_use_dynamic = 0
requires_power = 0
+ report_alerts = 0
/area/awaymission/clownplanet/miningtown
name = "\improper Clown Planet - Bananium-o-Rama"
diff --git a/code/modules/awaymissions/mission_code/spacebattle.dm b/code/modules/awaymissions/mission_code/spacebattle.dm
index dc86365f4ee..3f9c9925772 100644
--- a/code/modules/awaymissions/mission_code/spacebattle.dm
+++ b/code/modules/awaymissions/mission_code/spacebattle.dm
@@ -4,6 +4,7 @@
name = "\improper Space Battle"
icon_state = "away"
requires_power = 0
+ report_alerts = 0
/area/awaymission/spacebattle/cruiser
name = "\improper Nanotrasen Cruiser"
diff --git a/code/modules/awaymissions/mission_code/stationCollision.dm b/code/modules/awaymissions/mission_code/stationCollision.dm
index fdfd016e40a..dee5ae20af6 100644
--- a/code/modules/awaymissions/mission_code/stationCollision.dm
+++ b/code/modules/awaymissions/mission_code/stationCollision.dm
@@ -19,6 +19,9 @@
* Areas
*/
//Gateroom gets its own APC specifically for the gate
+ /area/awaymission
+ report_alerts = 0
+
/area/awaymission/gateroom
//Library, medbay, storage room
diff --git a/code/modules/client/preferences_toggles.dm b/code/modules/client/preferences_toggles.dm
index 8b4b300a6b0..2960f159546 100644
--- a/code/modules/client/preferences_toggles.dm
+++ b/code/modules/client/preferences_toggles.dm
@@ -25,6 +25,16 @@
src << "As a ghost, you will now [(prefs.toggles & CHAT_GHOSTRADIO) ? "hear all radio chat in the world" : "only hear from nearby speakers"]."
prefs.save_preferences(src)
feedback_add_details("admin_verb","TGR")
+
+/client/proc/toggle_hear_radio()
+ set name = "Show/Hide RadioChatter"
+ set category = "Preferences"
+ set desc = "Toggle seeing radiochatter from radios and speakers"
+ if(!holder) return
+ prefs.toggles ^= CHAT_RADIO
+ prefs.save_preferences(src)
+ usr << "You will [(prefs.toggles & CHAT_RADIO) ? "no longer" : "now"] see radio chatter from radios or speakers"
+ feedback_add_details("admin_verb","THR") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/verb/toggleadminhelpsound()
set name = "Hear/Silence Adminhelps"
diff --git a/code/modules/clothing/masks/gasmask.dm b/code/modules/clothing/masks/gasmask.dm
index c728f0d88bb..dc584be1e73 100644
--- a/code/modules/clothing/masks/gasmask.dm
+++ b/code/modules/clothing/masks/gasmask.dm
@@ -51,44 +51,6 @@
siemens_coefficient = 0.7
species_fit = list("Vox")
-/obj/item/clothing/mask/gas/voice
- name = "gas mask"
- //desc = "A face-covering mask that can be connected to an air supply. It seems to house some odd electronics."
- var/mode = 0// 0==Scouter | 1==Night Vision | 2==Thermal | 3==Meson
- var/voice = "Unknown"
- var/vchange = 0//This didn't do anything before. It now checks if the mask has special functions/N
- origin_tech = "syndicate=4"
- species_fit = list("Vox")
-
-/obj/item/clothing/mask/gas/voice/space_ninja
- name = "ninja mask"
- desc = "A close-fitting mask that acts both as an air filter and a post-modern fashion statement."
- icon_state = "s-ninja(norm)"
- item_state = "s-ninja_mask"
- vchange = 1
- unacidable = 1
- siemens_coefficient = 0.2
- species_fit = list("Vox")
-
-/obj/item/clothing/mask/gas/voice/space_ninja/scar
- name = "ninja mask"
- desc = "A close-fitting mask that acts both as an air filter and a post-modern fashion statement. This mask appears to have already seen battle."
- icon_state = "s-ninja(scar)"
- item_state = "s-ninja_mask"
-
-/obj/item/clothing/mask/gas/voice/space_ninja/visor
- name = "ninja mask"
- desc = "A close-fitting mask that acts both as an air filter and a post-modern fashion statement. This variant appears to have a visor to increase vision."
- icon_state = "s-ninja(visor)"
- item_state = "s-ninja_mask"
-
-/obj/item/clothing/mask/gas/voice/space_ninja/monocular
- name = "ninja mask"
- desc = "A close-fitting mask that acts both as an air filter and a post-modern fashion statement. This variant appears to focus the user's vision out of a single port."
- icon_state = "s-ninja(mon)"
- item_state = "s-ninja_mask"
-
-
/obj/item/clothing/mask/gas/clown_hat
name = "clown wig and mask"
desc = "A true prankster's facial attire. A clown is incomplete without his wig and mask."
diff --git a/code/modules/clothing/masks/voice.dm b/code/modules/clothing/masks/voice.dm
new file mode 100644
index 00000000000..3d28b720d79
--- /dev/null
+++ b/code/modules/clothing/masks/voice.dm
@@ -0,0 +1,59 @@
+/obj/item/voice_changer
+ name = "voice changer"
+ desc = "A voice scrambling module. If you can see this, report it as a bug on the tracker."
+ var/voice //If set and item is present in mask/suit, this name will be used for the wearer's speech.
+ var/active
+
+/obj/item/clothing/mask/gas/voice
+ name = "gas mask"
+ desc = "A face-covering mask that can be connected to an air supply. It seems to house some odd electronics."
+ var/obj/item/voice_changer/changer
+ origin_tech = "syndicate=4"
+
+/obj/item/clothing/mask/gas/voice/verb/Toggle_Voice_Changer()
+ set category = "Object"
+ set src in usr
+
+ changer.active = !changer.active
+ usr << "You [changer.active ? "enable" : "disable"] the voice-changing module in \the [src]."
+
+/obj/item/clothing/mask/gas/voice/verb/Set_Voice(name as text)
+ set category = "Object"
+ set src in usr
+
+ var/voice = sanitize(copytext(name,1,MAX_MESSAGE_LEN))
+ if(!voice || !length(voice)) return
+ changer.voice = voice
+ usr << "You are now mimicking [changer.voice]."
+
+/obj/item/clothing/mask/gas/voice/New()
+ ..()
+ changer = new(src)
+
+/obj/item/clothing/mask/gas/voice/space_ninja
+ name = "ninja mask"
+ desc = "A close-fitting mask that acts both as an air filter and a post-modern fashion statement."
+ icon_state = "s-ninja(norm)"
+ item_state = "s-ninja_mask"
+ unacidable = 1
+ siemens_coefficient = 0.2
+ species_fit = list("Vox")
+ var/mode = 0// 0==Scouter | 1==Night Vision | 2==Thermal | 3==Meson
+
+/obj/item/clothing/mask/gas/voice/space_ninja/scar
+ name = "ninja mask"
+ desc = "A close-fitting mask that acts both as an air filter and a post-modern fashion statement. This mask appears to have already seen battle."
+ icon_state = "s-ninja(scar)"
+ item_state = "s-ninja_mask"
+
+/obj/item/clothing/mask/gas/voice/space_ninja/visor
+ name = "ninja mask"
+ desc = "A close-fitting mask that acts both as an air filter and a post-modern fashion statement. This variant appears to have a visor to increase vision."
+ icon_state = "s-ninja(visor)"
+ item_state = "s-ninja_mask"
+
+/obj/item/clothing/mask/gas/voice/space_ninja/monocular
+ name = "ninja mask"
+ desc = "A close-fitting mask that acts both as an air filter and a post-modern fashion statement. This variant appears to focus the user's vision out of a single port."
+ icon_state = "s-ninja(mon)"
+ item_state = "s-ninja_mask"
\ No newline at end of file
diff --git a/code/modules/karma/karma.dm b/code/modules/karma/karma.dm
index 0b97d51c8b3..38c101b4370 100644
--- a/code/modules/karma/karma.dm
+++ b/code/modules/karma/karma.dm
@@ -345,8 +345,8 @@ You've gained [totalkarma] total karma in your time here.
"}
message_admins("SQL ERROR during karmaspent updating (updating existing entry). Error: \[[err]\]\n")
return
else
- usr << "You have been charged [cost] karma."
- message_admins("[key_name(usr)] has been charged [cost] karma.")
+ usr << "You have been [refund ? "refunded" : "charged"] [cost] karma."
+ message_admins("[key_name(usr)] has been [refund ? "refunded" : "charged"] [cost] karma.")
return
/client/proc/karmarefund(var/type,var/name,var/cost)
diff --git a/code/modules/mining/laborcamp/laborstacker.dm b/code/modules/mining/laborcamp/laborstacker.dm
index ed588a76482..facaf65c5da 100644
--- a/code/modules/mining/laborcamp/laborstacker.dm
+++ b/code/modules/mining/laborcamp/laborstacker.dm
@@ -12,14 +12,15 @@
var/obj/item/weapon/card/id/prisoner/inserted_id
var/obj/machinery/door/airlock/release_door
var/door_tag = "prisonshuttle"
- var/obj/item/device/radio/Radio //needed to send messages to sec radio
var/use_release_door = 0
+ var/obj/item/device/radio/intercom/announcer
/obj/machinery/mineral/labor_claim_console/New()
..()
- Radio = new/obj/item/device/radio(src)
- Radio.listening = 0
+ announcer = new /obj/item/device/radio/intercom(null)
+ announcer.config(list("Security" = 0))
+
spawn(7)
src.machine = locate(/obj/machinery/mineral/stacking_machine, get_step(src, machinedir))
var/t
@@ -103,8 +104,8 @@
else
if(shuttle.location == 1)
if (shuttle.moving_status == SHUTTLE_IDLE)
- Radio.set_frequency(SEC_FREQ)
- Radio.talk_into(src, "[inserted_id.registered_name] has returned to the station. Minerals and Prisoner ID card ready for retrieval.", SEC_FREQ)
+ var/message = "[inserted_id.registered_name] has returned to the station. Minerals and Prisoner ID card ready for retrieval."
+ announcer.autosay(message, "Labor Camp Controller", "Security")
usr << "Shuttle received message and will be sent shortly."
shuttle.launch()
else
@@ -131,7 +132,7 @@
/obj/machinery/mineral/stacking_machine/laborstacker
var/points = 0 //The unclaimed value of ore stacked. Value for each ore loosely relative to its rarity.
- var/list/ore_values = list(("glass" = 1), ("metal" = 2), ("solid plasma" = 20), ("plasteel" = 23), ("reinforced glass" = 4), ("gold" = 20), ("silver" = 20), ("uranium" = 20), ("diamond" = 25), ("bananium" = 50))
+ var/list/ore_values = list(("glass" = 1), ("metal" = 2), ("iron" = 3), ("solid plasma" = 20), ("plasteel" = 23), ("reinforced glass" = 4), ("gold" = 20), ("silver" = 20), ("uranium" = 20), ("diamond" = 25), ("bananium" = 50))
/obj/machinery/mineral/stacking_machine/laborstacker/proc/get_ore_values()
var/dat = ""
@@ -155,6 +156,17 @@
S.loc = output.loc
else
O.loc = output.loc
+
+ //Output amounts that are past stack_amt.
+ for(var/sheet in stack_storage)
+ if(stack_storage[sheet] >= stack_amt)
+ var/stacktype = stack_paths[sheet]
+ var/obj/item/stack/sheet/S = new stacktype (get_turf(output))
+ S.amount = stack_amt
+ stack_storage[sheet] -= stack_amt
+
+ console.updateUsrDialog()
+ return
/**********************Point Lookup Console**************************/
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index 4be7623c8ff..e999f919091 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -24,6 +24,7 @@
var/atom/movable/following = null
var/medHUD = 0
var/secHUD = 0
+ var/anonsay = 0
/mob/dead/observer/New(var/mob/body=null, var/flags=1)
sight |= SEE_TURFS | SEE_MOBS | SEE_OBJS | SEE_SELF
@@ -490,6 +491,11 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
if(usr != src)
return
..()
+
+ if (href_list["track"])
+ var/mob/target = locate(href_list["track"]) in mob_list
+ if(target)
+ ManualFollow(target)
if (href_list["follow"])
var/mob/target = locate(href_list["follow"]) in mob_list
@@ -532,3 +538,14 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
following = null
..()
//END TELEPORT HREF CODE
+
+/mob/dead/observer/verb/toggle_anonsay()
+ set category = "Ghost"
+ set name = "Toggle Anonymous Chat"
+ set desc = "Toggles showing your key in dead chat."
+
+ src.anonsay = !src.anonsay
+ if(anonsay)
+ src << "Your key won't be shown when you speak in dead chat."
+ else
+ src << "Your key will be publicly visible again."
diff --git a/code/modules/mob/hear_say.dm b/code/modules/mob/hear_say.dm
new file mode 100644
index 00000000000..f63566a0483
--- /dev/null
+++ b/code/modules/mob/hear_say.dm
@@ -0,0 +1,215 @@
+// At minimum every mob has a hear_say proc.
+
+/mob/proc/hear_say(var/message, var/verb = "says", var/datum/language/language = null, var/alt_name = "",var/italics = 0, var/mob/speaker = null, var/sound/speech_sound, var/sound_vol)
+ if(!client)
+ return
+
+ if(speaker && !speaker.client && istype(src,/mob/dead/observer) && client.prefs.toggles & CHAT_GHOSTEARS && !speaker in view(src))
+ //Does the speaker have a client? It's either random stuff that observers won't care about (Experiment 97B says, 'EHEHEHEHEHEHEHE')
+ //Or someone snoring. So we make it where they won't hear it.
+ return
+
+ //make sure the air can transmit speech - hearer's side
+ var/turf/T = get_turf(src)
+ if ((T) && (!(istype(src, /mob/dead/observer)))) //Ghosts can hear even in vacuum.
+ var/datum/gas_mixture/environment = T.return_air()
+ var/pressure = (environment)? environment.return_pressure() : 0
+ if(pressure < SOUND_MINIMUM_PRESSURE && get_dist(speaker, src) > 1)
+ return
+
+ if (pressure < ONE_ATMOSPHERE*0.4) //sound distortion pressure, to help clue people in that the air is thin, even if it isn't a vacuum yet
+ italics = 1
+ sound_vol *= 0.5 //muffle the sound a bit, so it's like we're actually talking through contact
+
+ if(sleeping || stat == 1)
+ hear_sleep(message)
+ return
+
+ //non-verbal languages are garbled if you can't see the speaker. Yes, this includes if they are inside a closet.
+ if (language && (language.flags & NONVERBAL))
+ if (!speaker || (src.sdisabilities & BLIND || src.blinded) || !(speaker in view(src)))
+ message = stars(message)
+
+ if(!(language && (language.flags & INNATE))) // skip understanding checks for INNATE languages
+ if(!say_understands(speaker,language))
+ if(istype(speaker,/mob/living/simple_animal))
+ var/mob/living/simple_animal/S = speaker
+ message = pick(S.speak)
+ else
+ if(language)
+ message = language.scramble(message)
+ else
+ message = stars(message)
+
+ var/speaker_name = speaker.name
+ if(istype(speaker, /mob/living/carbon/human))
+ var/mob/living/carbon/human/H = speaker
+ speaker_name = H.GetVoice()
+
+ if(italics)
+ message = "[message]"
+
+ var/track = null
+ if(istype(src, /mob/dead/observer))
+ if(italics && client.prefs.toggles & CHAT_GHOSTRADIO)
+ return
+ if(speaker_name != speaker.real_name && speaker.real_name)
+ speaker_name = "[speaker.real_name] ([speaker_name])"
+ track = "(follow) "
+ if(client.prefs.toggles & CHAT_GHOSTEARS && speaker in view(src))
+ message = "[message]"
+
+ if(sdisabilities & DEAF || ear_deaf)
+ if(!language || !(language.flags & INNATE)) // INNATE is the flag for audible-emote-language, so we don't want to show an "x talks but you cannot hear them" message if it's set
+ if(speaker == src)
+ src << "You cannot hear yourself speak!"
+ else
+ src << "[speaker_name][alt_name] talks but you cannot hear \him."
+ else
+ if(language)
+ src << "[speaker_name][alt_name] [track][language.format_message(message, verb)]"
+ else
+ src << "[speaker_name][alt_name] [track][verb], \"[message]\""
+ if (speech_sound && (get_dist(speaker, src) <= world.view && src.z == speaker.z))
+ var/turf/source = speaker? get_turf(speaker) : get_turf(src)
+ src.playsound_local(source, speech_sound, sound_vol, 1)
+
+
+/mob/proc/hear_radio(var/message, var/verb="says", var/datum/language/language=null, var/part_a, var/part_b, var/mob/speaker = null, var/hard_to_hear = 0, var/vname ="")
+
+ if(!client)
+ return
+
+ if(sleeping || stat==1) //If unconscious or sleeping
+ hear_sleep(message)
+ return
+
+ var/track = null
+
+ //non-verbal languages are garbled if you can't see the speaker. Yes, this includes if they are inside a closet.
+ if (language && (language.flags & NONVERBAL))
+ if (!speaker || (src.sdisabilities & BLIND || src.blinded) || !(speaker in view(src)))
+ message = stars(message)
+
+ if(!(language && (language.flags & INNATE))) // skip understanding checks for INNATE languages
+ if(!say_understands(speaker,language))
+ if(istype(speaker,/mob/living/simple_animal))
+ var/mob/living/simple_animal/S = speaker
+ if(S.speak && S.speak.len)
+ message = pick(S.speak)
+ else
+ return
+ else
+ if(language)
+ message = language.scramble(message)
+ else
+ message = stars(message)
+
+ if(hard_to_hear)
+ message = stars(message)
+
+ var/speaker_name = speaker.name
+
+ if(vname)
+ speaker_name = vname
+
+ if(istype(speaker, /mob/living/carbon/human))
+ var/mob/living/carbon/human/H = speaker
+ if(H.voice)
+ speaker_name = H.voice
+
+ if(hard_to_hear)
+ speaker_name = "unknown"
+
+ var/changed_voice
+
+ if(istype(src, /mob/living/silicon/ai) && !hard_to_hear)
+ var/jobname // the mob's "job"
+ var/mob/living/carbon/human/impersonating //The crewmember being impersonated, if any.
+
+ if (ishuman(speaker))
+ var/mob/living/carbon/human/H = speaker
+
+ if((H.wear_id && istype(H.wear_id,/obj/item/weapon/card/id/syndicate)) && (H.wear_mask && istype(H.wear_mask,/obj/item/clothing/mask/gas/voice)))
+
+ changed_voice = 1
+ var/mob/living/carbon/human/I = locate(speaker_name)
+
+ if(I)
+ impersonating = I
+ jobname = impersonating.get_assignment()
+ else
+ jobname = "Unknown"
+ else
+ jobname = H.get_assignment()
+
+ else if (iscarbon(speaker)) // Nonhuman carbon mob
+ jobname = "No id"
+ else if (isAI(speaker))
+ jobname = "AI"
+ else if (isrobot(speaker))
+ jobname = "Cyborg"
+ else if (istype(speaker, /mob/living/silicon/pai))
+ jobname = "Personal AI"
+ else
+ jobname = "Unknown"
+
+ if(changed_voice)
+ if(impersonating)
+ track = "[speaker_name] ([jobname])"
+ else
+ track = "[speaker_name] ([jobname])"
+ else
+ track = "[speaker_name] ([jobname])"
+
+ if(istype(src, /mob/dead/observer))
+ if(speaker_name != speaker.real_name && !isAI(speaker)) //Announce computer and various stuff that broadcasts doesn't use it's real name but AI's can't pretend to be other mobs.
+ speaker_name = "[speaker.real_name] ([speaker_name])"
+ track = "[speaker_name] (follow)"
+
+ var/formatted
+ if(language)
+ formatted = language.format_message_radio(message, verb)
+ else
+ formatted = "[verb], \"[message]\""
+ if(sdisabilities & DEAF || ear_deaf)
+ if(prob(20))
+ src << "You feel your headset vibrate but can hear nothing from it!"
+ else if(track)
+ src << "[part_a][track][part_b][formatted]"
+ else
+ src << "[part_a][speaker_name][part_b][formatted]"
+
+/mob/proc/hear_signlang(var/message, var/verb = "gestures", var/datum/language/language, var/mob/speaker = null)
+ if(!client)
+ return
+
+ if(say_understands(speaker, language))
+ message = "[src] [verb], \"[message]\""
+ else
+ message = "[src] [verb]."
+
+ if(src.status_flags & PASSEMOTES)
+ for(var/obj/item/weapon/holder/H in src.contents)
+ H.show_message(message)
+ for(var/mob/living/M in src.contents)
+ M.show_message(message)
+ src.show_message(message)
+
+/mob/proc/hear_sleep(var/message)
+ var/heard = ""
+ if(prob(15))
+ var/list/punctuation = list(",", "!", ".", ";", "?")
+ var/list/messages = text2list(message, " ")
+ var/R = rand(1, messages.len)
+ var/heardword = messages[R]
+ if(copytext(heardword,1, 1) in punctuation)
+ heardword = copytext(heardword,2)
+ if(copytext(heardword,-1) in punctuation)
+ heardword = copytext(heardword,1,lentext(heardword))
+ heard = "...You hear something about...[heardword]"
+
+ else
+ heard = "...You almost hear someone talking..."
+
+ src << heard
diff --git a/code/modules/mob/language.dm b/code/modules/mob/language.dm
index 0a043db41ef..3b4f1cc6f39 100644
--- a/code/modules/mob/language.dm
+++ b/code/modules/mob/language.dm
@@ -1,3 +1,5 @@
+#define SCRAMBLE_CACHE_LEN 20
+
/*
Datum based languages. Easily editable and modular.
*/
@@ -5,13 +7,106 @@
/datum/language
var/name = "an unknown language" // Fluff name of language if any.
var/desc = "A language." // Short description for 'Check Languages'.
- var/speech_verb = "says" // 'says', 'hisses'.
- var/ask_verb = "asks" // Used when sentence ends in a ?
+ var/speech_verb = "says" // 'says', 'hisses', 'farts'.
+ var/ask_verb = "asks" // Used when sentence ends in a ?
var/exclaim_verb = "exclaims" // Used when sentence ends in a !
- var/colour = "say_quote" // CSS style to use for strings in this language.
+ var/whisper_verb // Optional. When not specified speech_verb + quietly/softly is used instead.
+ var/signlang_verb = list() // list of emotes that might be displayed if this language has NONVERBAL or SIGNLANG flags
+ var/colour = "body" // CSS style to use for strings in this language.
var/key = "x" // Character used to speak in language eg. :o for Unathi.
var/flags = 0 // Various language flags.
var/native // If set, non-native speakers will have trouble speaking.
+ var/list/syllables // Used when scrambling text for a non-speaker.
+ var/list/space_chance = 55 // Likelihood of getting a space in the random scramble string.
+
+/datum/language/proc/get_random_name(var/gender, name_count=2, syllable_count=4)
+ if(!syllables || !syllables.len)
+ if(gender==FEMALE)
+ return capitalize(pick(first_names_female)) + " " + capitalize(pick(last_names))
+ else
+ return capitalize(pick(first_names_male)) + " " + capitalize(pick(last_names))
+
+ var/full_name = ""
+ var/new_name = ""
+
+ for(var/i = 0;i0;x--)
+ new_name += pick(syllables)
+ full_name += " [capitalize(lowertext(new_name))]"
+
+ return "[trim(full_name)]"
+
+/datum/language
+ var/list/scramble_cache = list()
+
+/datum/language/proc/scramble(var/input)
+
+ if(!syllables || !syllables.len)
+ return stars(input)
+
+ // If the input is cached already, move it to the end of the cache and return it
+ if(input in scramble_cache)
+ var/n = scramble_cache[input]
+ scramble_cache -= input
+ scramble_cache[input] = n
+ return n
+
+ var/input_size = length(input)
+ var/scrambled_text = ""
+ var/capitalize = 1
+
+ while(length(scrambled_text) < input_size)
+ var/next = pick(syllables)
+ if(capitalize)
+ next = capitalize(next)
+ capitalize = 0
+ scrambled_text += next
+ var/chance = rand(100)
+ if(chance <= 5)
+ scrambled_text += ". "
+ capitalize = 1
+ else if(chance > 5 && chance <= space_chance)
+ scrambled_text += " "
+
+ scrambled_text = trim(scrambled_text)
+ var/ending = copytext(scrambled_text, length(scrambled_text))
+ if(ending == ".")
+ scrambled_text = copytext(scrambled_text,1,length(scrambled_text)-1)
+ var/input_ending = copytext(input, input_size)
+ if(input_ending in list("!","?","."))
+ scrambled_text += input_ending
+
+ // Add it to cache, cutting old entries if the list is too long
+ scramble_cache[input] = scrambled_text
+ if(scramble_cache.len > SCRAMBLE_CACHE_LEN)
+ scramble_cache.Cut(1, scramble_cache.len-SCRAMBLE_CACHE_LEN-1)
+
+
+ return scrambled_text
+
+/datum/language/proc/format_message(message, verb)
+ return "[verb], \"[capitalize(message)]\""
+
+/datum/language/proc/format_message_radio(message, verb)
+ return "[verb], \"[capitalize(message)]\""
+
+/datum/language/proc/get_talkinto_msg_range(message)
+ // if you yell, you'll be heard from two tiles over instead of one
+ return (copytext(message, length(message)) == "!") ? 2 : 1
+
+/datum/language/proc/broadcast(var/mob/living/speaker,var/message,var/speaker_mask)
+ log_say("[key_name(speaker)] : ([name]) [message]")
+
+ if(!speaker_mask) speaker_mask = speaker.name
+ var/msg = "[name], [speaker_mask] [format_message(message, get_spoken_verb(message))]"
+
+ for(var/mob/player in player_list)
+ if(istype(player,/mob/dead) || ((src in player.languages) && check_special_condition(player)))
+ player << msg
+
+/datum/language/proc/check_special_condition(var/mob/other)
+ return 1
/datum/language/proc/get_spoken_verb(var/msg_end)
switch(msg_end)
@@ -19,8 +114,25 @@
return exclaim_verb
if("?")
return ask_verb
- return speech_verb
-
+ return speech_verb
+
+// Noise "language", for audible emotes.
+/datum/language/noise
+ name = "Noise"
+ desc = "Noises"
+ key = ""
+ flags = RESTRICTED|NONGLOBAL|INNATE|NO_TALK_MSG
+
+/datum/language/noise/format_message(message, verb)
+ return "[message]"
+
+/datum/language/noise/format_message_radio(message, verb)
+ return "[message]"
+
+/datum/language/noise/get_talkinto_msg_range(message)
+ // if you make a loud noise (screams etc), you'll be heard from 4 tiles over instead of two
+ return (copytext(message, length(message)) == "!") ? 4 : 2
+
/datum/language/unathi
name = "Sinta'unathi"
desc = "The common language of Moghes, composed of sibilant hisses and rattles. Spoken natively by Unathi."
@@ -29,17 +141,38 @@
exclaim_verb = "roars"
colour = "soghun"
key = "o"
- flags = WHITELISTED
+ flags = RESTRICTED
+ syllables = list("ss","ss","ss","ss","skak","seeki","resh","las","esi","kor","sh")
+
+/datum/language/unathi/get_random_name()
+
+ var/new_name = ..()
+ while(findtextEx(new_name,"sss",1,null))
+ new_name = replacetext(new_name, "sss", "ss")
+ return capitalize(new_name)
/datum/language/tajaran
name = "Siik'tajr"
- desc = "An expressive language that combines yowls and chirps with posture, tail and ears. Native to the Tajaran."
+ desc = "The traditionally employed tongue of Ahdomai, composed of expressive yowls and chirps. Native to the Tajaran."
speech_verb = "mrowls"
ask_verb = "mrowls"
exclaim_verb = "yowls"
colour = "tajaran"
key = "j"
- flags = WHITELISTED
+ flags = RESTRICTED
+ syllables = list("rr","rr","tajr","kir","raj","kii","mir","kra","ahk","nal","vah","khaz","jri","ran","darr", \
+ "mi","jri","dynh","manq","rhe","zar","rrhaz","kal","chur","eech","thaa","dra","jurl","mah","sanu","dra","ii'r", \
+ "ka","aasi","far","wa","baq","ara","qara","zir","sam","mak","hrar","nja","rir","khan","jun","dar","rik","kah", \
+ "hal","ket","jurl","mah","tul","cresh","azu","ragh")
+
+/datum/language/tajaran/get_random_name(var/gender)
+
+ var/new_name = ..(gender,1)
+ if(prob(80))
+ new_name += " [pick(list("Hadii","Kaytam","Zhan-Khazan","Hharar","Njarir'Akhan"))]"
+ else
+ new_name += ..(gender,1)
+ return new_name
/datum/language/skrell
name = "Skrellian"
@@ -49,7 +182,8 @@
exclaim_verb = "warbles"
colour = "skrell"
key = "k"
- flags = WHITELISTED
+ flags = RESTRICTED
+ syllables = list("qr","qrr","xuq","qil","quum","xuqm","vol","xrim","zaoo","qu-uu","qix","qoo","zix","*","!")
/datum/language/vox
name = "Vox-pidgin"
@@ -59,7 +193,12 @@
exclaim_verb = "SHRIEKS"
colour = "vox"
key = "v"
- flags = RESTRICTED
+ flags = RESTRICTED | WHITELISTED
+ syllables = list("ti","ti","ti","hi","hi","ki","ki","ki","ki","ya","ta","ha","ka","ya","chi","cha","kah", \
+ "SKRE","AHK","EHK","RAWK","KRA","AAA","EEE","KI","II","KRI","KA")
+
+/datum/language/vox/get_random_name()
+ return ..(FEMALE,1,6)
/datum/language/diona
name = "Rootspeak"
@@ -67,79 +206,258 @@
speech_verb = "creaks and rustles"
ask_verb = "creaks"
exclaim_verb = "rustles"
- colour = "soghun"
+ colour = "diona"
key = "q"
flags = RESTRICTED
+ syllables = list("hs","zt","kr","st","sh")
+/datum/language/diona/get_random_name()
+ var/new_name = "[pick(list("To Sleep Beneath","Wind Over","Embrace of","Dreams of","Witnessing","To Walk Beneath","Approaching the"))]"
+ new_name += " [pick(list("the Void","the Sky","Encroaching Night","Planetsong","Starsong","the Wandering Star","the Empty Day","Daybreak","Nightfall","the Rain"))]"
+ return new_name
+
+/datum/language/trinary
+ name = "Trinary"
+ desc = "A modification of binary to allow fuzzy logic. 0 is no, 1 is maybe, 2 is yes. Credited with giving Machine People the ability to think creatively."
+ speech_verb = "states"
+ ask_verb = "queries"
+ exclaim_verb = "exclaims"
+ colour = "trinary"
+ key = "5"
+ flags = RESTRICTED | WHITELISTED
+ syllables = list("02011","01222","10100","10210","21012","02011","21200","1002","2001","0002","0012","0012","000","120","121","201","220","10","11","0")
+
/datum/language/kidan
name = "Chittin"
- desc = "The noise made by rubbing it's antennae together is actually a complex form of communication for Kidan."
- speech_verb = "rubs its antenna together"
- ask_verb = "rubs its antenna together"
- exclaim_verb = "rubs its antenna together"
- colour = "tajaran"
+ desc = "The noise made by rubbing its antennae together is actually a complex form of communication for Kidan."
+ speech_verb = "rubs its antennae together"
+ ask_verb = "rubs its antennae together"
+ exclaim_verb = "rubs its antennae together"
+ colour = "kidan"
key = "4"
- flags = RESTRICTED
-
+ flags = RESTRICTED | WHITELISTED
+ syllables = list("click","clack")
+
/datum/language/slime
name = "Bubblish"
- desc = "Languages of slimes, a mixture of bubbling noises and pops. Almost impossible to speak without mechanical aid for non slime people."
+ desc = "The language of slimes. It's a mixture of bubbling noises and pops. Very difficult to speak without mechanical aid for humans."
speech_verb = "bubbles and pops"
ask_verb = "bubbles and pops"
exclaim_verb = "bubbles and pops"
- colour = "skrell"
+ colour = "slime"
key = "f"
+ flags = RESTRICTED | WHITELISTED
+ syllables = list("blob","plop","pop","bop","boop")
+
+/datum/language/common
+ name = "Galactic Common"
+ desc = "The common galactic tongue."
+ speech_verb = "says"
+ whisper_verb = "whispers"
+ key = "0"
flags = RESTRICTED
+ syllables = list("blah","blah","blah","bleh","meh","neh","nah","wah")
+
+//TODO flag certain languages to use the mob-type specific say_quote and then get rid of these.
+/datum/language/common/get_spoken_verb(var/msg_end)
+ switch(msg_end)
+ if("!")
+ return pick("exclaims","shouts","yells") //TODO: make the basic proc handle lists of verbs.
+ if("?")
+ return ask_verb
+ return speech_verb
/datum/language/human
name = "Sol Common"
desc = "A bastardized hybrid of informal English and elements of Mandarin Chinese; the common language of the Sol system."
+ speech_verb = "says"
+ whisper_verb = "whispers"
+ colour = "solcom"
key = "1"
flags = RESTRICTED
+ syllables = list("tao","shi","tzu","yi","com","be","is","i","op","vi","ed","lec","mo","cle","te","dis","e")
-/datum/language/trinary
- name = "Trinary"
- desc = "A modification of binary to allow fuzzy logic. 0 is no, 1 maybe, 2 yes. Credited with giving Machine People the ability to think creatively."
- key = "5"
- flags = RESTRICTED
-
+/datum/language/human/get_spoken_verb(var/msg_end)
+ switch(msg_end)
+ if("!")
+ return pick("exclaims","shouts","yells") //TODO: make the basic proc handle lists of verbs.
+ if("?")
+ return ask_verb
+ return speech_verb
// Galactic common languages (systemwide accepted standards).
/datum/language/trader
name = "Tradeband"
desc = "Maintained by the various trading cartels in major systems, this elegant, structured language is used for bartering and bargaining."
speech_verb = "enunciates"
+ colour = "say_quote"
key = "2"
+ space_chance = 100
+ syllables = list("lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing", "elit",
+ "sed", "do", "eiusmod", "tempor", "incididunt", "ut", "labore", "et", "dolore",
+ "magna", "aliqua", "ut", "enim", "ad", "minim", "veniam", "quis", "nostrud",
+ "exercitation", "ullamco", "laboris", "nisi", "ut", "aliquip", "ex", "ea", "commodo",
+ "consequat", "duis", "aute", "irure", "dolor", "in", "reprehenderit", "in",
+ "voluptate", "velit", "esse", "cillum", "dolore", "eu", "fugiat", "nulla",
+ "pariatur", "excepteur", "sint", "occaecat", "cupidatat", "non", "proident", "sunt",
+ "in", "culpa", "qui", "officia", "deserunt", "mollit", "anim", "id", "est", "laborum")
/datum/language/gutter
name = "Gutter"
desc = "Much like Standard, this crude pidgin tongue descended from numerous languages and serves as Tradeband for criminal elements."
speech_verb = "growls"
+ colour = "rough"
key = "3"
+ syllables = list ("gra","ba","ba","breh","bra","rah","dur","ra","ro","gro","go","ber","bar","geh","heh", "gra")
+
+/datum/language/xenocommon
+ name = "Xenomorph"
+ colour = "alien"
+ desc = "The common tongue of the xenomorphs."
+ speech_verb = "hisses"
+ ask_verb = "hisses"
+ exclaim_verb = "hisses"
+ key = "6"
+ flags = RESTRICTED
+ syllables = list("sss","sSs","SSS")
+
+/datum/language/xenos
+ name = "Hivemind"
+ desc = "Xenomorphs have the strange ability to commune over a psychic hivemind."
+ speech_verb = "hisses"
+ ask_verb = "hisses"
+ exclaim_verb = "hisses"
+ colour = "alien"
+ key = "a"
+ flags = RESTRICTED | HIVEMIND
+
+/datum/language/xenos/check_special_condition(var/mob/other)
+ var/mob/living/carbon/M = other
+ if(!istype(M))
+ return 1
+ return 0
+
+/datum/language/ling
+ name = "Changeling"
+ desc = "Although they are normally wary and suspicious of each other, changelings can commune over a distance."
+ speech_verb = "says"
+ colour = "changeling"
+ key = "g"
+ flags = RESTRICTED | HIVEMIND
+
+/datum/language/ling/broadcast(var/mob/living/speaker,var/message,var/speaker_mask)
+
+ if(speaker.mind && speaker.mind.changeling)
+ ..(speaker,message,speaker.mind.changeling.changelingID)
+ else
+ ..(speaker,message)
+
+/datum/language/corticalborer
+ name = "Cortical Link"
+ desc = "Cortical borers possess a strange link between their tiny minds."
+ speech_verb = "sings"
+ ask_verb = "sings"
+ exclaim_verb = "sings"
+ colour = "alien"
+ key = "x"
+ flags = RESTRICTED | HIVEMIND
+
+/datum/language/corticalborer/broadcast(var/mob/living/speaker,var/message,var/speaker_mask)
+
+ var/mob/living/simple_animal/borer/B
+
+ if(istype(speaker,/mob/living/carbon))
+ var/mob/living/carbon/M = speaker
+ B = M.has_brain_worms()
+ else if(istype(speaker,/mob/living/simple_animal/borer))
+ B = speaker
+
+ if(B)
+ speaker_mask = B.truename
+ ..(speaker,message,speaker_mask)
+
+/datum/language/binary
+ name = "Robot Talk"
+ desc = "Most human stations support free-use communications protocols and routing hubs for synthetic use."
+ colour = "say_quote"
+ speech_verb = "states"
+ ask_verb = "queries"
+ exclaim_verb = "declares"
+ key = "b"
+ flags = RESTRICTED | HIVEMIND
+ var/drone_only
+
+/datum/language/binary/broadcast(var/mob/living/speaker,var/message,var/speaker_mask)
+
+ if(!speaker.binarycheck())
+ return
+
+ if (!message)
+ return
+
+ var/message_start = "[name], [speaker.name]"
+ var/message_body = "[speaker.say_quote(message)], \"[message]\""
+
+ for (var/mob/M in dead_mob_list)
+ if(!istype(M,/mob/new_player) && !istype(M,/mob/living/carbon/brain)) //No meta-evesdropping
+ M.show_message("[message_start] [message_body]", 2)
+
+ for (var/mob/living/S in living_mob_list)
+
+ if(drone_only && !istype(S,/mob/living/silicon/robot/drone))
+ continue
+ else if(istype(S , /mob/living/silicon/ai))
+ message_start = "[name], [speaker.name]"
+ else if (!S.binarycheck())
+ continue
+
+ S.show_message("[message_start] [message_body]", 2)
+
+ var/list/listening = hearers(1, src)
+ listening -= src
+
+ for (var/mob/living/M in listening)
+ if(istype(M, /mob/living/silicon) || M.binarycheck())
+ continue
+ M.show_message("synthesised voice beeps, \"beep beep beep\"",2)
+
+ //robot binary xmitter component power usage
+ if (isrobot(speaker))
+ var/mob/living/silicon/robot/R = speaker
+ var/datum/robot_component/C = R.components["comms"]
+ R.use_power(C.energy_consumption)
+
+/datum/language/binary/drone
+ name = "Drone Talk"
+ desc = "A heavily encoded damage control coordination stream."
+ speech_verb = "transmits"
+ ask_verb = "transmits"
+ exclaim_verb = "transmits"
+ colour = "say_quote"
+ key = "d"
+ flags = RESTRICTED | HIVEMIND
+ drone_only = 1
// Language handling.
/mob/proc/add_language(var/language)
- for(var/datum/language/L in languages)
- if(L && L.name == language)
- return 0
-
var/datum/language/new_language = all_languages[language]
- if(!istype(new_language,/datum/language))
+ if(!istype(new_language) || new_language in languages)
return 0
- languages += new_language
+ languages |= new_language
return 1
/mob/proc/remove_language(var/rem_language)
+ var/datum/language/L = all_languages[rem_language]
+ . = (L in languages)
+ languages.Remove(L)
- for(var/datum/language/L in languages)
- if(L && L.name == rem_language)
- languages -= L
- return 1
+// Can we speak this language, as opposed to just understanding it?
+/mob/proc/can_speak(datum/language/speaking)
- return 0
+ return (universal_speak || (speaking && speaking.flags & INNATE) || speaking in src.languages)
//TBD
/mob/verb/check_languages()
@@ -150,7 +468,10 @@
var/dat = "Known Languages
"
for(var/datum/language/L in languages)
- dat += "[L.name] (:[L.key])
[L.desc]
"
+ if(!(L.flags & NONGLOBAL))
+ dat += "[L.name] (:[L.key])
[L.desc]
"
src << browse(dat, "window=checklanguage")
- return
\ No newline at end of file
+ return
+
+#undef SCRAMBLE_CACHE_LEN
diff --git a/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm b/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm
index 0eb6fe764ee..721c2a4b3fb 100644
--- a/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm
+++ b/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm
@@ -21,6 +21,8 @@
if(name == "alien")
name = text("alien ([rand(1, 1000)])")
real_name = name
+ add_language("Xenomorph")
+ add_language("Hivemind")
..()
//This is fine, works the same as a human
diff --git a/code/modules/mob/living/carbon/alien/larva/larva.dm b/code/modules/mob/living/carbon/alien/larva/larva.dm
index 5515c951514..a918e069bb7 100644
--- a/code/modules/mob/living/carbon/alien/larva/larva.dm
+++ b/code/modules/mob/living/carbon/alien/larva/larva.dm
@@ -23,6 +23,8 @@
name = "alien larva ([rand(1, 1000)])"
real_name = name
regenerate_icons()
+ add_language("Xenomorph")
+ add_language("Hivemind")
..()
//This is fine, works the same as a human
diff --git a/code/modules/mob/living/carbon/alien/say.dm b/code/modules/mob/living/carbon/alien/say.dm
index 9604a50559d..8b4177470a4 100644
--- a/code/modules/mob/living/carbon/alien/say.dm
+++ b/code/modules/mob/living/carbon/alien/say.dm
@@ -1,69 +1,28 @@
/mob/living/carbon/alien/say(var/message)
+ var/verb = "says"
+ var/message_range = world.view
- if (silent)
- return
+ if(client)
+ if(client.prefs.muted & MUTE_IC)
+ src << "\red You cannot speak in IC (Muted)."
+ return
- if (length(message) >= 2)
- if (department_radio_keys[copytext(message, 1, 3)] == "alientalk")
- message = copytext(message, 3)
- message = trim(copytext(sanitize(message), 1, MAX_MESSAGE_LEN))
- if (stat == 2)
- return say_dead(message)
- else
- alien_talk(message)
- else
- if (copytext(message, 1, 2) != "*" && !stat)
- playsound(loc, "hiss", 25, 1, 1)//So aliens can hiss while they hiss yo/N
- return ..(message)
- else
+ message = trim_strip_html_properly(message)
-/mob/living/proc/alien_talk(var/message)
+ if(stat == 2)
+ return say_dead(message)
+
+ if(copytext(message,1,2) == "*")
+ return emote(copytext(message,2))
+
+ var/datum/language/speaking = parse_language(message)
+
+ if(speaking)
+ message = copytext(message, 2+length(speaking.key))
- log_say("[key_name(src)] : [message]")
message = trim(message)
- if (!message)
+ if(!message || stat)
return
- var/message_a = say_quote(message)
- var/rendered = "Hivemind, [name] [message_a]"
- for (var/mob/living/S in player_list)
- if(!S.stat)
- if(S.alien_talk_understand)
- if(S.alien_talk_understand == alien_talk_understand)
- S.show_message(rendered, 2)
- else if (S.hivecheck())
- S.show_message(rendered, 2)
-
- var/list/listening = hearers(1, src)
- listening -= src
- listening += src
-
- var/list/heard = list()
- for (var/mob/M in listening)
- if(!istype(M, /mob/living/carbon/alien) && !M.alien_talk_understand)
- heard += M
-
-
- if (length(heard))
- var/message_b
-
- message_b = "hsssss"
- message_b = say_quote(message_b)
- message_b = "[message_b]"
-
- rendered = "[voice_name] [message_b]"
-
- for (var/mob/M in heard)
- M.show_message(rendered, 2)
-
- message = say_quote(message)
-
- rendered = "Hivemind, [name] [message_a]"
- var/rendered2 = null
- for (var/mob/M in player_list)
- if (istype(M, /mob/new_player))
- continue
- if (M.stat > 1 && istype(M, /mob/dead/observer))
- rendered2 = "Hivemind, [name] (Follow) [message_a]"
- M.show_message(rendered2, 2)
\ No newline at end of file
+ ..(message, speaking, verb, null, null, message_range, null)
\ No newline at end of file
diff --git a/code/modules/mob/living/carbon/brain/brain.dm b/code/modules/mob/living/carbon/brain/brain.dm
index f181cc05464..bbcd527386b 100644
--- a/code/modules/mob/living/carbon/brain/brain.dm
+++ b/code/modules/mob/living/carbon/brain/brain.dm
@@ -55,4 +55,7 @@
canmove = 1
use_me = 1 //If it can move, let it emote
else canmove = 0
- return canmove
\ No newline at end of file
+ return canmove
+
+/mob/living/carbon/brain/binarycheck()
+ return istype(loc, /obj/item/device/mmi/posibrain)
\ No newline at end of file
diff --git a/code/modules/mob/living/carbon/brain/say.dm b/code/modules/mob/living/carbon/brain/say.dm
index 1e7a9da9a76..26dc7b30f3f 100644
--- a/code/modules/mob/living/carbon/brain/say.dm
+++ b/code/modules/mob/living/carbon/brain/say.dm
@@ -1,3 +1,4 @@
+//TODO: Convert this over for languages.
/mob/living/carbon/brain/say(var/message)
if (silent)
return
@@ -8,18 +9,31 @@
if(!(container && (istype(container, /obj/item/device/mmi) || istype(container, /obj/item/device/mmi/posibrain))))
return //No MMI, can't speak, bucko./N
else
- if ((department_radio_keys[copytext(message, 1, 3)] == "binary") && (container && istype(container, /obj/item/device/mmi/posibrain)))
- message = copytext(message, 3)
- message = trim(copytext(sanitize(message), 1, MAX_MESSAGE_LEN))
- robot_talk(message)
- return
+ var/datum/language/speaking = parse_language(message)
+ if(speaking)
+ message = copytext(message, 2+length(speaking.key))
+ var/verb = "says"
+ var/ending = copytext(message, length(message))
+ if (speaking)
+ verb = speaking.get_spoken_verb(ending)
+ else
+ if(ending=="!")
+ verb=pick("exclaims","shouts","yells")
+ if(ending=="?")
+ verb="asks"
+
if(prob(emp_damage*4))
if(prob(10))//10% chane to drop the message entirely
return
else
message = Gibberish(message, (emp_damage*6))//scrambles the message, gets worse when emp_damage is higher
+
+ if(speaking && speaking.flags & HIVEMIND)
+ speaking.broadcast(src,trim(message))
+ return
+
if(istype(container, /obj/item/device/mmi/radio_enabled))
var/obj/item/device/mmi/radio_enabled/R = container
if(R.radio)
- spawn(0) R.radio.hear_talk(src, sanitize(message))
- ..()
\ No newline at end of file
+ spawn(0) R.radio.hear_talk(src, trim(sanitize(message)), verb, speaking)
+ ..(trim(message), speaking, verb)
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 5f3a0cca482..ff64560fbbe 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -9,6 +9,40 @@
var/datum/species/species //Contains icon generation and language information, set during New().
var/embedded_flag //To check if we've need to roll for damage on movement while an item is imbedded in us.
+/mob/living/carbon/human/New(var/new_loc, var/new_species = null, var/delay_ready_dna=0)
+ if(!species)
+ if(new_species)
+ set_species(new_species,null,1)
+ else
+ set_species()
+
+ var/datum/reagents/R = new/datum/reagents(1000)
+ reagents = R
+ R.my_atom = src
+
+ hud_list[HEALTH_HUD] = image('icons/mob/hud.dmi', src, "hudhealth100")
+ hud_list[STATUS_HUD] = image('icons/mob/hud.dmi', src, "hudhealthy")
+ hud_list[ID_HUD] = image('icons/mob/hud.dmi', src, "hudunknown")
+ hud_list[WANTED_HUD] = image('icons/mob/hud.dmi', src, "hudblank")
+ hud_list[IMPLOYAL_HUD] = image('icons/mob/hud.dmi', src, "hudblank")
+ hud_list[IMPCHEM_HUD] = image('icons/mob/hud.dmi', src, "hudblank")
+ hud_list[IMPTRACK_HUD] = image('icons/mob/hud.dmi', src, "hudblank")
+ hud_list[SPECIALROLE_HUD] = image('icons/mob/hud.dmi', src, "hudblank")
+ hud_list[STATUS_HUD_OOC] = image('icons/mob/hud.dmi', src, "hudhealthy")
+ hud_list[NATIONS_HUD] = image('icons/mob/hud.dmi', src, "hudblank")
+
+ ..()
+
+ if(dna)
+ dna.real_name = real_name
+
+ prev_gender = gender // Debug for plural genders
+ make_blood()
+
+ // Set up DNA.
+ if(!delay_ready_dna)
+ dna.ready_dna(src)
+
/mob/living/carbon/human/dummy
real_name = "Test Dummy"
status_flags = GODMODE|CANPUSH
@@ -49,8 +83,6 @@
/mob/living/carbon/human/grey/New(var/new_loc)
..(new_loc, "Grey")
- spell_list += new /obj/effect/proc_holder/spell/wizard/targeted/remotetalk
-
/mob/living/carbon/human/human/New(var/new_loc)
..(new_loc, "Human")
@@ -63,50 +95,6 @@
h_style = "blue IPC screen"
..(new_loc, "Machine")
-/mob/living/carbon/human/New(var/new_loc, var/new_species = null, var/delay_ready_dna=0)
- if(!species)
- if(new_species)
- set_species(new_species,null,1)
- else
- set_species()
-
- if(species.language)
- var/datum/language/L = all_languages[species.language]
- if(L)
- languages += L
-
- var/datum/reagents/R = new/datum/reagents(1000)
- reagents = R
- R.my_atom = src
-
- if(!dna)
- dna = new /datum/dna(null)
- dna.species=species.name
-
- hud_list[HEALTH_HUD] = image('icons/mob/hud.dmi', src, "hudhealth100")
- hud_list[STATUS_HUD] = image('icons/mob/hud.dmi', src, "hudhealthy")
- hud_list[ID_HUD] = image('icons/mob/hud.dmi', src, "hudunknown")
- hud_list[WANTED_HUD] = image('icons/mob/hud.dmi', src, "hudblank")
- hud_list[IMPLOYAL_HUD] = image('icons/mob/hud.dmi', src, "hudblank")
- hud_list[IMPCHEM_HUD] = image('icons/mob/hud.dmi', src, "hudblank")
- hud_list[IMPTRACK_HUD] = image('icons/mob/hud.dmi', src, "hudblank")
- hud_list[SPECIALROLE_HUD] = image('icons/mob/hud.dmi', src, "hudblank")
- hud_list[STATUS_HUD_OOC] = image('icons/mob/hud.dmi', src, "hudhealthy")
- hud_list[NATIONS_HUD] = image('icons/mob/hud.dmi', src, "hudblank")
-
- ..()
-
- if(dna)
- dna.real_name = real_name
-
- prev_gender = gender // Debug for plural genders
- make_blood()
-
- // Set up DNA.
- if(!delay_ready_dna)
- dna.ready_dna(src)
-
-
/mob/living/carbon/human/Bump(atom/movable/AM as mob|obj, yes)
if ((!( yes ) || now_pushing))
return
@@ -1313,7 +1301,8 @@
usr << "\blue [self ? "Your" : "[src]'s"] pulse is [src.get_pulse(GETPULSE_HAND)]."
/mob/living/carbon/human/proc/set_species(var/new_species, var/force_organs, var/default_colour)
-
+
+ var/datum/species/oldspecies = species
if(!dna)
if(!new_species)
new_species = "Human"
@@ -1323,19 +1312,34 @@
else
dna.species = new_species
- if(species && (species.name && species.name == new_species))
- return
-
- if(species && species.language)
- remove_language(species.language)
+ if(species)
+ if(species.name && species.name == new_species)
+ return
+ if(species.language)
+ remove_language(species.language)
+
+ if(species.default_language)
+ remove_language(species.default_language)
+
species = all_species[new_species]
+
+ if(oldspecies)
+ if(oldspecies.default_genes.len)
+ oldspecies.handle_dna(src,1) // Remove any genes that belong to the old species
if(force_organs || !organs || !organs.len)
species.create_organs(src)
+
+ if(vessel)
+ vessel = null
+ make_blood()
if(species.language)
add_language(species.language)
+
+ if(species.default_language)
+ add_language(species.default_language)
see_in_dark = species.darksight
if(see_in_dark > 2)
@@ -1343,9 +1347,6 @@
else
see_invisible = SEE_INVISIBLE_LIVING
- if(species.default_mutations.len>0 || species.default_blocks.len>0)
- do_deferred_species_setup=1
-
spawn(0)
update_icons()
@@ -1358,21 +1359,18 @@
r_skin = 0
g_skin = 0
b_skin = 0
+
+ if(!dna)
+ dna = new /datum/dna(null)
+ dna.species = species.name
+ dna.real_name = real_name
+ dna.mutantrace = null
species.handle_post_spawn(src)
spawn(0)
- update_icons()
- if(species && species.flags & NO_BLOOD) //We want the var for safety but we can do without the actual blood.
- if(vessel.has_reagent("water") || vessel.has_reagent("blood"))
- var/water = vessel.get_reagent_amount("water")
- var/blood = vessel.get_reagent_amount("blood")
- vessel.remove_reagent("water",water)
- vessel.remove_reagent("blood",blood)
- if(species.bloodflags & BLOOD_SLIME)
- vessel.add_reagent("water",560-vessel.total_volume)
- else
- vessel.add_reagent("blood",560-vessel.total_volume)
+ overlays.Cut()
+ update_mutantrace(1)
fixblood()
if(species)
diff --git a/code/modules/mob/living/carbon/human/human_damage.dm b/code/modules/mob/living/carbon/human/human_damage.dm
index 4db067181cb..49b6a73143c 100644
--- a/code/modules/mob/living/carbon/human/human_damage.dm
+++ b/code/modules/mob/living/carbon/human/human_damage.dm
@@ -32,9 +32,12 @@
timeofdeath = 0
/mob/living/carbon/human/getBrainLoss()
- if(species && species.flags & NO_INTORGANS) return
+ if(species && species.flags & NO_INTORGANS)
+ return
var/res = brainloss
var/datum/organ/internal/brain/sponge = internal_organs_by_name["brain"]
+ if(!sponge)
+ return
if (sponge.is_bruised())
res += 20
if (sponge.is_broken())
@@ -197,6 +200,7 @@
UpdateDamageIcon()
hud_updateflag |= 1 << HEALTH_HUD
updatehealth()
+ speech_problem_flag = 1
//Heal MANY external organs, in random order
@@ -218,6 +222,7 @@
parts -= picked
updatehealth()
hud_updateflag |= 1 << HEALTH_HUD
+ speech_problem_flag = 1
if(update) UpdateDamageIcon()
// damage MANY external organs, in random order
diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm
index b98ea2c790f..4e373c7cdb8 100644
--- a/code/modules/mob/living/carbon/human/human_defines.dm
+++ b/code/modules/mob/living/carbon/human/human_defines.dm
@@ -50,6 +50,10 @@
var/icon/stand_icon = null
var/icon/lying_icon = null
+
+ var/voice = "" //Instead of new say code calling GetVoice() over and over and over, we're just going to ask this variable, which gets updated in Life()
+
+ var/speech_problem_flag = 0
var/miming = null //Toggle for the mime's abilities.
var/special_voice = "" // For changing our voice. Used by a symptom.
diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm
index 8319046d5d5..0f32d9d187e 100644
--- a/code/modules/mob/living/carbon/human/life.dm
+++ b/code/modules/mob/living/carbon/human/life.dm
@@ -33,27 +33,9 @@ var/global/list/brutefireloss_overlays = list("1" = image("icon" = 'icons/mob/sc
var/prev_gender = null // Debug for plural genders
var/temperature_alert = 0
var/in_stasis = 0
- var/do_deferred_species_setup=0
var/exposedtimenow = 0
var/firstexposed = 0
-// Doing this during species init breaks shit.
-/mob/living/carbon/human/proc/DeferredSpeciesSetup()
- var/mut_update=0
- if(species.default_mutations.len>0)
- for(var/mutation in species.default_mutations)
- if(!(mutation in mutations))
- mutations.Add(mutation)
- mut_update=1
- if(species.default_blocks.len>0)
- for(var/block in species.default_blocks)
- if(!dna.GetSEState(block))
- dna.SetSEState(block,1)
- mut_update=1
- if(mut_update)
- domutcheck(src,null,MUTCHK_FORCED)
- update_mutations()
-
/mob/living/carbon/human/Life()
set invisibility = 0
//set background = 1
@@ -63,10 +45,6 @@ var/global/list/brutefireloss_overlays = list("1" = image("icon" = 'icons/mob/sc
..()
- if(do_deferred_species_setup)
- DeferredSpeciesSetup()
- do_deferred_species_setup=0
-
//Apparently, the person who wrote this code designed it so that
//blinded get reset each cycle and then get activated later in the
//code. Very ugly. I dont care. Moving this stuff here so its easy
@@ -84,6 +62,8 @@ var/global/list/brutefireloss_overlays = list("1" = image("icon" = 'icons/mob/sc
if(life_tick%30==15)
hud_updateflag = 1022
+
+ voice = GetVoice()
//No need to update all of these procs if the guy is dead.
if(stat != DEAD && !in_stasis)
@@ -252,22 +232,26 @@ var/global/list/brutefireloss_overlays = list("1" = image("icon" = 'icons/mob/sc
drop_item()
emote("cough")
if (disabilities & TOURETTES)
+ speech_problem_flag = 1
if ((prob(10) && paralysis <= 1))
Stun(10)
switch(rand(1, 3))
if(1)
emote("twitch")
if(2 to 3)
- say("[prob(50) ? ";" : ""][pick("SHIT", "PISS", "FUCK", "CUNT", "COCKSUCKER", "MOTHERFUCKER", "TITS")]")
+ var/tourettes = pick("SHIT", "PISS", "FUCK", "CUNT", "COCKSUCKER", "MOTHERFUCKER", "TITS")
+ say("[prob(50) ? ";" : ""][tourettes]")
var/x_offset = pixel_x + rand(-2,2) //Should probably be moved into the twitch emote at some point.
var/y_offset = pixel_y + rand(-1,1)
animate(src, pixel_x = pixel_x + x_offset, pixel_y = pixel_y + y_offset, time = 1)
animate(pixel_x = pixel_x - x_offset, pixel_y = pixel_y - y_offset, time = 1)
if (disabilities & NERVOUS)
+ speech_problem_flag = 1
if (prob(10))
stuttering = max(10, stuttering)
if (getBrainLoss() >= 60 && stat != 2)
+ speech_problem_flag = 1
if (prob(3))
switch(pick(1,2,3))
if(1)
@@ -329,6 +313,7 @@ var/global/list/brutefireloss_overlays = list("1" = image("icon" = 'icons/mob/sc
if(gene.is_active(src))
/* if (prob(10) && prob(gene.instability))
adjustCloneLoss(1) */
+ speech_problem_flag = 1
gene.OnMobLife(src)
if (radiation)
@@ -1091,6 +1076,7 @@ var/global/list/brutefireloss_overlays = list("1" = image("icon" = 'icons/mob/sc
if(halloss > 0)
adjustHalLoss(-3)
else if(sleeping)
+ speech_problem_flag = 1
handle_dreams()
adjustStaminaLoss(-10)
adjustHalLoss(-3)
@@ -1944,7 +1930,29 @@ var/global/list/brutefireloss_overlays = list("1" = image("icon" = 'icons/mob/sc
var/client/C = client
for(var/mob/living/carbon/human/H in view(src, 14))
C.images += H.hud_list[NATIONS_HUD]
+
+/mob/living/carbon/human/handle_silent()
+ if(..())
+ speech_problem_flag = 1
+ return silent
+/mob/living/carbon/human/handle_slurring()
+ if(..())
+ speech_problem_flag = 1
+ return slurring
+
+/mob/living/carbon/human/handle_stunned()
+ if(species.flags & NO_PAIN)
+ stunned = 0
+ return 0
+ if(..())
+ speech_problem_flag = 1
+ return stunned
+
+/mob/living/carbon/human/handle_stuttering()
+ if(..())
+ speech_problem_flag = 1
+ return stuttering
// Need this in species.
//#undef HUMAN_MAX_OXYLOSS
diff --git a/code/modules/mob/living/carbon/human/say.dm b/code/modules/mob/living/carbon/human/say.dm
index 028f0efe755..4cc61b36290 100644
--- a/code/modules/mob/living/carbon/human/say.dm
+++ b/code/modules/mob/living/carbon/human/say.dm
@@ -1,81 +1,152 @@
/mob/living/carbon/human/say(var/message)
- if(miming)
- if(length(message) >= 2)
- if(mind && mind.changeling)
- if(copytext(message, 1, 2) != "*" && department_radio_keys[copytext(message, 1, 3)] != "changeling")
- return
- else
- return ..(message)
- if(stat == DEAD)
- return ..(message)
- if(length(message) >= 1) //In case people forget the '*help' command, this will slow them the message and prevent people from saying one letter at a time
- if (copytext(message, 1, 2) != "*")
- return
+ var/verb = "says"
+ var/alt_name = ""
+ var/message_range = world.view
+ var/italics = 0
- if(dna)
- if(length(message) >= 2)
- if (copytext(message, 1, 2) != "*" && department_radio_keys[copytext(message, 1, 3)] != "changeling")
- for(var/datum/dna/gene/gene in dna_genes)
- if(!gene.block)
- continue
- if(gene.is_active(src))
- message = gene.OnSay(src,message)
- /*
- if(dna.mutantrace == "lizard")
- if(copytext(message, 1, 2) != "*")
- message = replacetext(message, "s", stutter("ss"))
+ if(client)
+ if(client.prefs.muted & MUTE_IC)
+ src << "\red You cannot speak in IC (Muted)."
+ return
- if(dna.mutantrace == "slime" && prob(5))
- if(copytext(message, 1, 2) != "*")
- if(copytext(message, 1, 2) == ";")
- message = ";"
- else
- message = ""
- message += "SKR"
- var/imax = rand(5,20)
- for(var/i = 0,iYou're muzzled and cannot speak!"
+ return
+
+ var/message_mode = parse_message_mode(message, "headset")
+
+ if(copytext(message,1,2) == "*")
+ return emote(copytext(message,2))
+
+ if(name != GetVoice())
+ alt_name = "(as [get_id_name("Unknown")])"
+
+ //parse the radio code and consume it
+ if (message_mode)
+ if (message_mode == "headset")
+ message = copytext(message,2) //it would be really nice if the parse procs could do this for us.
+ else
+ message = copytext(message,3)
+
+ //parse the language code and consume it
+ var/datum/language/speaking = parse_language(message)
+ if(speaking)
+ message = copytext(message,2+length(speaking.key))
+ else if(species.default_language)
+ speaking = all_languages[species.default_language]
+
+ var/ending = copytext(message, length(message))
+ if (speaking)
+ // This is broadcast to all mobs with the language,
+ // irrespective of distance or anything else.
+ if(speaking.flags & HIVEMIND)
+ speaking.broadcast(src,trim(message))
+ return
+ //If we've gotten this far, keep going!
+ verb = speaking.get_spoken_verb(ending)
+ else
+ if(ending=="!")
+ verb=pick("exclaims","shouts","yells")
+ if(ending=="?")
+ verb="asks"
+
+ message = trim(message)
+
+ if(speech_problem_flag)
+ var/list/handle_r = handle_speech_problems(message)
+ message = handle_r[1]
+ verb = handle_r[2]
+ speech_problem_flag = handle_r[3]
+
+ if(!message || message == "")
+ return
+
+ var/list/obj/item/used_radios = new
+
+ switch (message_mode)
+ if("headset")
+ if(l_ear && istype(l_ear,/obj/item/device/radio))
+ var/obj/item/device/radio/R = l_ear
+ R.talk_into(src,message,null,verb,speaking)
+ used_radios += l_ear
+ else if(r_ear && istype(r_ear,/obj/item/device/radio))
+ var/obj/item/device/radio/R = r_ear
+ R.talk_into(src,message,null,verb,speaking)
+ used_radios += r_ear
+
+ if("right ear")
+ var/obj/item/device/radio/R
+ var/has_radio = 0
+ if(r_ear && istype(r_ear,/obj/item/device/radio))
+ R = r_ear
+ has_radio = 1
+ if(r_hand && istype(r_hand, /obj/item/device/radio))
+ R = r_hand
+ has_radio = 1
+ if(has_radio)
+ R.talk_into(src,message,null,verb,speaking)
+ used_radios += R
- if(wear_mask)
- if(istype(wear_mask, /obj/item/clothing/mask/gas/voice/space_ninja) && wear_mask:voice == "Unknown")
- if(copytext(message, 1, 2) != "*")
- var/list/temp_message = text2list(message, " ")
- var/list/pick_list = list()
- for(var/i = 1, i <= temp_message.len, i++)
- pick_list += i
- for(var/i=1, i <= abs(temp_message.len/3), i++)
- var/H = pick(pick_list)
- if(findtext(temp_message[H], "*") || findtext(temp_message[H], ";") || findtext(temp_message[H], ":")) continue
- temp_message[H] = ninjaspeak(temp_message[H])
- pick_list -= H
- message = list2text(temp_message, " ")
- message = replacetext(message, "o", "¤")
- message = replacetext(message, "p", "þ")
- message = replacetext(message, "l", "£")
- message = replacetext(message, "s", "§")
- message = replacetext(message, "u", "µ")
- message = replacetext(message, "b", "ß")
+ if("left ear")
+ var/obj/item/device/radio/R
+ var/has_radio = 0
+ if(l_ear && istype(l_ear,/obj/item/device/radio))
+ R = l_ear
+ has_radio = 1
+ if(l_hand && istype(l_hand,/obj/item/device/radio))
+ R = l_hand
+ has_radio = 1
+ if(has_radio)
+ R.talk_into(src,message,null,verb,speaking)
+ used_radios += R
- else if(istype(wear_mask, /obj/item/clothing/mask/horsehead))
- var/obj/item/clothing/mask/horsehead/hoers = wear_mask
- if(hoers.voicechange)
- if(!(copytext(message, 1, 2) == "*" || (mind && mind.changeling && department_radio_keys[copytext(message, 1, 3)] != "changeling")))
- message = pick("NEEIIGGGHHHH!", "NEEEIIIIGHH!", "NEIIIGGHH!", "HAAWWWWW!", "HAAAWWW!")
+ if("intercom")
+ for(var/obj/item/device/radio/intercom/I in view(1, null))
+ I.talk_into(src, message, verb, speaking)
+ used_radios += I
+ if("whisper")
+ whisper_say(message, speaking, alt_name)
+ return
+ else
+ if(message_mode)
+ if(l_ear && istype(l_ear,/obj/item/device/radio))
+ l_ear.talk_into(src,message, message_mode, verb, speaking)
+ used_radios += l_ear
+ else if(r_ear && istype(r_ear,/obj/item/device/radio))
+ r_ear.talk_into(src,message, message_mode, verb, speaking)
+ used_radios += r_ear
- if ((M_HULK in mutations) && health >= 25 && length(message))
- if(copytext(message, 1, 2) != "*")
- message = "[uppertext(message)]!!" //because I don't know how to code properly in getting vars from other files -Bro
+ var/sound/speech_sound
+ var/sound_vol
+ if(species.speech_sounds && prob(species.speech_chance))
+ speech_sound = sound(pick(species.speech_sounds))
+ sound_vol = 50
- if (src.slurring)
- if(copytext(message, 1, 2) != "*")
- message = slur(message)
+ //speaking into radios
+ if(used_radios.len)
+ italics = 1
+ message_range = 1
+ if(speaking)
+ message_range = speaking.get_talkinto_msg_range(message)
+ var/msg
+ if(!speaking || !(speaking.flags & NO_TALK_MSG))
+ msg = "\The [src] talks into \the [used_radios[1]]"
+ for(var/mob/living/M in hearers(5, src))
+ if((M != src) && msg)
+ M.show_message(msg)
+ if (speech_sound)
+ sound_vol *= 0.5
- if((species.name == "Vox" || species.name == "Vox Armalis") && prob(20))
- playsound(src.loc, 'sound/voice/shriek1.ogg', 50, 1)
-
- ..(message)
+ ..(message, speaking, verb, alt_name, italics, message_range, speech_sound, sound_vol) //ohgod we should really be passing a datum here.
/mob/living/carbon/human/proc/forcesay(list/append)
if(stat == CONSCIOUS)
@@ -100,6 +171,7 @@
if(findtext(temp, "*", 1, 2)) //emotes
return
temp = copytext(trim_left(temp), 1, rand(5,8))
+
var/trimmed = trim_left(temp)
if(length(trimmed))
if(append)
@@ -108,25 +180,45 @@
say(temp)
winset(client, "input", "text=[null]")
-/mob/living/carbon/human/say_understands(var/other,var/datum/language/speaking = null)
+/mob/living/carbon/human/say_understands(var/mob/other,var/datum/language/speaking = null)
if(has_brain_worms()) //Brain worms translate everything. Even mice and alien speak.
return 1
- if (istype(other, /mob/living/silicon))
- return 1
- if (istype(other, /mob/living/carbon/brain))
- return 1
- if (istype(other, /mob/living/carbon/slime))
+
+ if(species.can_understand(other))
return 1
+
+ //These only pertain to common. Languages are handled by mob/say_understands()
+ if (!speaking)
+ if (istype(other, /mob/living/carbon/monkey/diona))
+ if(other.languages.len >= 2) //They've sucked down some blood and can speak common now.
+ return 1
+ if (istype(other, /mob/living/silicon))
+ return 1
+ if (istype(other, /mob/living/carbon/brain))
+ return 1
+ if (istype(other, /mob/living/carbon/slime))
+ return 1
+
+ //This is already covered by mob/say_understands()
+ //if (istype(other, /mob/living/simple_animal))
+ // if((other.universal_speak && !speaking) || src.universal_speak || src.universal_understand)
+ // return 1
+ // return 0
+
return ..()
/mob/living/carbon/human/GetVoice()
- if(istype(src.wear_mask, /obj/item/clothing/mask/gas/voice))
- var/obj/item/clothing/mask/gas/voice/V = src.wear_mask
- if(V.vchange)
- return V.voice
- else
- return name
+
+ var/voice_sub
+ for(var/obj/item/gear in list(wear_mask,wear_suit,head))
+ if(!gear)
+ continue
+ var/obj/item/voice_changer/changer = locate() in gear
+ if(changer && changer.active && changer.voice)
+ voice_sub = changer.voice
+ if(voice_sub)
+ return voice_sub
if(mind && mind.changeling && mind.changeling.mimicing)
return mind.changeling.mimicing
if(GetSpecialVoice())
@@ -143,4 +235,81 @@
return
/mob/living/carbon/human/proc/GetSpecialVoice()
- return special_voice
\ No newline at end of file
+ return special_voice
+
+
+/*
+ ***Deprecated***
+ let this be handled at the hear_say or hear_radio proc
+ This is left in for robot speaking when humans gain binary channel access until I get around to rewriting
+ robot_talk() proc.
+ There is no language handling build into it however there is at the /mob level so we accept the call
+ for it but just ignore it.
+*/
+
+/mob/living/carbon/human/say_quote(var/message, var/datum/language/speaking = null)
+ var/verb = "says"
+ var/ending = copytext(message, length(message))
+
+ if(speaking)
+ verb = speaking.get_spoken_verb(ending)
+ else
+ if(ending == "!")
+ verb = "exclaims"
+ else if(ending == "?")
+ verb = "asks"
+
+ return verb
+
+/mob/living/carbon/human/proc/handle_speech_problems(var/message)
+
+ var/list/returns[3]
+ var/verb = "says"
+ var/handled = 0
+ if(silent || (sdisabilities & MUTE))
+ message = ""
+ handled = 1
+ if(istype(wear_mask, /obj/item/clothing/mask/horsehead))
+ var/obj/item/clothing/mask/horsehead/hoers = wear_mask
+ if(hoers.voicechange)
+ if(mind && mind.changeling && department_radio_keys[copytext(message, 1, 3)] != "changeling")
+ message = pick("NEEIIGGGHHHH!", "NEEEIIIIGHH!", "NEIIIGGHH!", "HAAWWWWW!", "HAAAWWW!")
+ verb = pick("whinnies","neighs", "says")
+ handled = 1
+
+ if(dna)
+ for(var/datum/dna/gene/gene in dna_genes)
+ if(!gene.block)
+ continue
+ if(gene.is_active(src))
+ message = gene.OnSay(src,message)
+ handled = 1
+
+ if(message != "")
+ if((M_HULK in mutations) && health >= 25 && length(message))
+ message = "[uppertext(message)]!!!"
+ verb = pick("yells","roars","hollers")
+ handled = 1
+ if(slurring)
+ message = slur(message)
+ verb = "slurs"
+ handled = 1
+ if(stuttering)
+ message = stutter(message)
+ verb = "stammers"
+ handled = 1
+
+ var/braindam = getBrainLoss()
+ if(braindam >= 60)
+ handled = 1
+ if(prob(braindam/4))
+ message = stutter(message)
+ verb = "gibbers"
+ if(prob(braindam))
+ message = uppertext(message)
+ verb = "yells loudly"
+
+ returns[1] = message
+ returns[2] = verb
+ returns[3] = handled
+ return returns
diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm
index 9a47536c759..937c85bce8e 100644
--- a/code/modules/mob/living/carbon/human/update_icons.dm
+++ b/code/modules/mob/living/carbon/human/update_icons.dm
@@ -290,11 +290,12 @@ proc/get_damage_icon_part(damage_state, body_part)
//Robotic limbs are handled in get_icon() so all we worry about are missing or dead limbs.
//No icon stored, so we need to start with a basic one.
var/datum/organ/external/chest = get_organ("chest")
- base_icon = chest.get_icon(g,fat)
+ if(chest)
+ base_icon = chest.get_icon(g,fat)
- if(chest.status & ORGAN_DEAD)
- base_icon.ColorTone(necrosis_color_mod)
- base_icon.SetIntensity(0.7)
+ if(chest.status & ORGAN_DEAD)
+ base_icon.ColorTone(necrosis_color_mod)
+ base_icon.SetIntensity(0.7)
for(var/datum/organ/external/part in organs)
diff --git a/code/modules/mob/living/carbon/human/whisper.dm b/code/modules/mob/living/carbon/human/whisper.dm
index 58bbfc54dec..43e8af94f50 100644
--- a/code/modules/mob/living/carbon/human/whisper.dm
+++ b/code/modules/mob/living/carbon/human/whisper.dm
@@ -1,11 +1,8 @@
//Lallander was here
/mob/living/carbon/human/whisper(message as text)
+ var/alt_name = ""
- message = trim(copytext(strip_html_simple(message), 1, MAX_MESSAGE_LEN))
-
- if (!message || silent || miming)
- return
-
+ message = trim_strip_html_properly(message)
log_whisper("[src.name]/[src.key] : [message]")
if (src.client)
@@ -16,28 +13,74 @@
if (src.client.handle_spam_prevention(message,MUTE_IC))
return
-
if (src.stat == 2)
return src.say_dead(message)
if (src.stat)
return
- var/alt_name = ""
- if (istype(src, /mob/living/carbon/human) && src.name != GetVoice())
- var/mob/living/carbon/human/H = src
- alt_name = " (as [H.get_id_name("Unknown")])"
- // Mute disability
- if (src.sdisabilities & MUTE)
- return
+ if(name != GetVoice())
+ alt_name = "(as [get_id_name("Unknown")])"
+
+ //parse the language code and consume it
+ var/datum/language/speaking = parse_language(message)
+ if (speaking)
+ message = copytext(message,2+length(speaking.key))
+
+ whisper_say(message, speaking, alt_name)
+
+
+//This is used by both the whisper verb and human/say() to handle whispering
+/mob/living/carbon/human/proc/whisper_say(var/message, var/datum/language/speaking = null, var/alt_name="", var/verb="whispers")
if (istype(src.wear_mask, /obj/item/clothing/mask/muzzle))
+ src << "You're muzzled and cannot speak!"
return
- var/italics = 1
var/message_range = 1
+ var/eavesdropping_range = 2
+ var/watching_range = 5
+ var/italics = 1
- if(istype(src.wear_mask, /obj/item/clothing/mask/gas/voice/space_ninja)&&src.wear_mask:voice=="Unknown")
+ var/not_heard //the message displayed to people who could not hear the whispering
+ if (speaking)
+ if (speaking.whisper_verb)
+ verb = speaking.whisper_verb
+ not_heard = "[verb] something"
+ else
+ var/adverb = pick("quietly", "softly")
+ verb = "[speaking.speech_verb] [adverb]"
+ not_heard = "[speaking.speech_verb] something [adverb]"
+ else
+ not_heard = "[verb] something" //TODO get rid of the null language and just prevent speech if language is null
+
+ message = capitalize(trim(message))
+
+ if(speech_problem_flag)
+ var/list/handle_r = handle_speech_problems(message)
+ message = handle_r[1]
+ verb = handle_r[2]
+ if(verb == "yells loudly")
+ verb = "slurs emphatically"
+ else
+ var/adverb = pick("quietly", "softly")
+ verb = "[verb] [adverb]"
+
+ speech_problem_flag = handle_r[3]
+
+ if(!message || message=="")
+ return
+
+ //looks like this only appears in whisper. Should it be elsewhere as well? Maybe handle_speech_problems?
+ var/voice_sub
+ for(var/obj/item/gear in list(wear_mask,wear_suit,head))
+ if(!gear)
+ continue
+ var/obj/item/voice_changer/changer = locate() in gear
+ if(changer && changer.active && changer.voice)
+ voice_sub = changer.voice
+
+ if(voice_sub == "Unknown")
if(copytext(message, 1, 2) != "*")
var/list/temp_message = text2list(message, " ")
var/list/pick_list = list()
@@ -49,97 +92,60 @@
temp_message[H] = ninjaspeak(temp_message[H])
pick_list -= H
message = list2text(temp_message, " ")
- message = replacetext(message, "o", "?")
- message = replacetext(message, "p", "?")
- message = replacetext(message, "l", "?")
- message = replacetext(message, "s", "?")
- message = replacetext(message, "u", "?")
- message = replacetext(message, "b", "?")
-
- if (src.stuttering)
- message = stutter(message)
-
- for (var/obj/O in view(message_range, src))
- spawn (0)
- if (O)
- O.hear_talk(src, message)
+ message = replacetext(message, "o", "�")
+ message = replacetext(message, "p", "�")
+ message = replacetext(message, "l", "�")
+ message = replacetext(message, "s", "�")
+ message = replacetext(message, "u", "�")
+ message = replacetext(message, "b", "�")
var/list/listening = hearers(message_range, src)
listening |= src
+ //ghosts
+ for (var/mob/M in dead_mob_list) //does this include players who joined as observers as well?
+ if (!(M.client))
+ continue
+ if(M.stat == DEAD && M.client && (M.client.prefs.toggles & CHAT_GHOSTEARS))
+ listening |= M
+
//Pass whispers on to anything inside the immediate listeners.
for(var/mob/L in listening)
for(var/mob/C in L.contents)
if(istype(C,/mob/living))
listening += C
- var/list/eavesdropping = hearers(2, src)
+ //pass on the message to objects that can hear us.
+ for (var/obj/O in view(message_range, src))
+ spawn (0)
+ if (O)
+ O.hear_talk(src, message, verb, speaking)
+
+ var/list/eavesdropping = hearers(eavesdropping_range, src)
eavesdropping -= src
eavesdropping -= listening
- var/list/watching = hearers(5, src)
+ var/list/watching = hearers(watching_range, src)
watching -= src
watching -= listening
watching -= eavesdropping
- var/list/heard_a = list() // understood us
- var/list/heard_b = list() // didn't understand us
+ //now mobs
+ var/speech_bubble_test = say_test(message)
+ var/image/speech_bubble = image('icons/mob/talk.dmi',src,"h[speech_bubble_test]")
+ spawn(30) del(speech_bubble)
- for (var/mob/M in listening)
- if (M.say_understands(src))
- heard_a += M
- else
- heard_b += M
+ for(var/mob/M in listening)
+ M << speech_bubble
+ M.hear_say(message, verb, speaking, alt_name, italics, src)
- var/rendered = null
+ if (eavesdropping.len)
+ var/new_message = stars(message) //hopefully passing the message twice through stars() won't hurt... I guess if you already don't understand the language, when they speak it too quietly to hear normally you would be able to catch even less.
+ for(var/mob/M in eavesdropping)
+ M << speech_bubble
+ M.hear_say(new_message, verb, speaking, alt_name, italics, src)
- for (var/mob/M in watching)
- if (M.say_understands(src))
- rendered = "[src.name] whispers something."
- else
- rendered = "[src.voice_name] whispers something."
- M.show_message(rendered, 2)
-
- if (length(heard_a))
- var/message_a = message
-
- if (italics)
- message_a = "[message_a]"
- //This appears copied from carbon/living say.dm so the istype check for mob is probably not needed. Appending for src is also not needed as the game will check that automatically.
- rendered = "[GetVoice()][alt_name] whispers, \"[message_a]\""
-
- for (var/mob/M in heard_a)
- M.show_message(rendered, 2)
-
- if (length(heard_b))
- var/message_b
-
- message_b = stars(message)
-
- if (italics)
- message_b = "[message_b]"
-
- rendered = "[src.voice_name] whispers, \"[message_b]\""
-
- for (var/mob/M in heard_b)
- M.show_message(rendered, 2)
-
- for (var/mob/M in eavesdropping)
- if (M.say_understands(src))
- var/message_c
- message_c = stars(message)
- rendered = "[GetVoice()][alt_name] whispers, \"[message_c]\""
- M.show_message(rendered, 2)
- else
- rendered = "[src.voice_name] whispers something."
- M.show_message(rendered, 2)
-
- if (italics)
- message = "[message]"
- rendered = "[GetVoice()][alt_name] whispers, \"[message]\""
-
- for (var/mob/M in dead_mob_list)
- if (!(M.client))
- continue
- if (M.stat > 1 && !(M in heard_a) && (M.client.prefs.toggles & CHAT_GHOSTEARS))
+ if (watching.len)
+ var/rendered = "[src.name] [not_heard]."
+ for (var/mob/M in watching)
M.show_message(rendered, 2)
diff --git a/code/modules/mob/living/carbon/metroid/say.dm b/code/modules/mob/living/carbon/metroid/say.dm
index 89708595da4..f4de10ceb74 100644
--- a/code/modules/mob/living/carbon/metroid/say.dm
+++ b/code/modules/mob/living/carbon/metroid/say.dm
@@ -21,3 +21,16 @@
return 1
return ..()
+/mob/living/carbon/slime/hear_say(var/message, var/verb = "says", var/datum/language/language = null, var/alt_name = "", var/italics = 0, var/mob/speaker = null, var/sound/speech_sound, var/sound_vol)
+ if (speaker in Friends)
+ speech_buffer = list()
+ speech_buffer.Add(speaker)
+ speech_buffer.Add(lowertext(html_decode(message)))
+ ..()
+
+/mob/living/carbon/slime/hear_radio(var/message, var/verb="says", var/datum/language/language=null, var/part_a, var/part_b, var/mob/speaker = null, var/hard_to_hear = 0, var/vname ="")
+ if (speaker in Friends)
+ speech_buffer = list()
+ speech_buffer.Add(speaker)
+ speech_buffer.Add(lowertext(html_decode(message)))
+ ..()
diff --git a/code/modules/mob/living/carbon/species.dm b/code/modules/mob/living/carbon/species.dm
index 6512ee05247..02587d04994 100644
--- a/code/modules/mob/living/carbon/species.dm
+++ b/code/modules/mob/living/carbon/species.dm
@@ -8,10 +8,10 @@
var/icobase = 'icons/mob/human_races/r_human.dmi' // Normal icon set.
var/deform = 'icons/mob/human_races/r_def_human.dmi' // Mutated icon set.
var/eyes = "eyes_s" // Icon for eyes.
+ var/blurb = "A completely nondescript species." // A brief lore summary for use in the chargen screen.
var/primitive // Lesser form, if any (ie. monkey for humans)
var/tail // Name of tail image in species effects icon file.
- var/language // Default racial language, if any.
var/unarmed //For empty hand harm-intent attack
var/unarmed_type = /datum/unarmed_attack
var/mutantrace // Safeguard due to old code.
@@ -41,11 +41,8 @@
var/brute_mod = null // Physical damage reduction/malus.
var/burn_mod = null // Burn damage reduction/malus.
- // For grays
var/max_hurt_damage = 9 // Max melee damage dealt + 5 if hulk
- var/list/default_mutations = list()
- var/list/default_blocks = list() // Don't touch.
- var/list/default_block_names = list() // Use this instead, using the names from setupgame.dm
+ var/list/default_genes = list()
var/flags = 0 // Various specific features.
var/bloodflags=0
@@ -60,12 +57,21 @@
//Used in icon caching.
var/race_key = 0
var/icon/icon_template
-
+
+ // Language/culture vars.
+ var/default_language = "Galactic Common" // Default language is used when 'say' is used without modifiers.
+ var/language = "Galactic Common" // Default racial language, if any.
+ var/secondary_langs = list() // The names of secondary languages that are available to this species.
+ var/list/speech_sounds // A list of sounds to potentially play when speaking.
+ var/list/speech_chance // The likelihood of a speech sound playing.
/datum/species/New()
unarmed = new unarmed_type()
-
+/datum/species/proc/get_random_name(var/gender)
+ var/datum/language/species_language = all_languages[language]
+ return species_language.get_random_name(gender)
+
/datum/species/proc/create_organs(var/mob/living/carbon/human/H) //Handles creation of mob organs.
//This is a basic humanoid limb setup.
H.organs = list()
@@ -81,7 +87,7 @@
H.organs_by_name["l_foot"] = new/datum/organ/external/l_foot(H.organs_by_name["l_leg"])
H.organs_by_name["r_foot"] = new/datum/organ/external/r_foot(H.organs_by_name["r_leg"])
- if (name!="Slime People")
+ if (name != "Slime People")
new/datum/organ/internal/heart(H)
new/datum/organ/internal/lungs(H)
new/datum/organ/internal/liver(H)
@@ -247,6 +253,10 @@
return 1
/datum/species/proc/handle_post_spawn(var/mob/living/carbon/C) //Handles anything not already covered by basic species assignment.
+ handle_dna(C)
+ return
+
+/datum/species/proc/handle_dna(var/mob/living/carbon/C, var/remove) //Handles DNA mutations, as that doesn't work at init.
return
// Used for species-specific names (Vox, etc)
@@ -262,6 +272,10 @@
return message
/datum/species/proc/equip(var/mob/living/carbon/human/H)
+ return
+
+/datum/species/proc/can_understand(var/mob/other)
+ return
/datum/species/human
name = "Human"
@@ -272,17 +286,29 @@
flags = HAS_LIPS | HAS_UNDERWEAR | CAN_BE_FAT
bodyflags = HAS_SKIN_TONE
unarmed_type = /datum/unarmed_attack/punch
+ blurb = "Humanity originated in the Sol system, and over the last five centuries has spread \
+ colonies across a wide swathe of space. They hold a wide range of forms and creeds.
\
+ While the central Sol government maintains control of its far-flung people, powerful corporate \
+ interests, rampant cyber and bio-augmentation and secretive factions make life on most human \
+ worlds tumultous at best."
/datum/species/unathi
name = "Unathi"
icobase = 'icons/mob/human_races/r_lizard.dmi'
deform = 'icons/mob/human_races/r_def_lizard.dmi'
path = /mob/living/carbon/human/unathi
+ default_language = "Galactic Common"
language = "Sinta'unathi"
tail = "sogtail"
unarmed_type = /datum/unarmed_attack/claws
primitive = /mob/living/carbon/monkey/unathi
darksight = 3
+
+ blurb = "A heavily reptillian species, Unathi (or 'Sinta as they call themselves) hail from the \
+ Uuosa-Eso system, which roughly translates to 'burning mother'.
Coming from a harsh, radioactive \
+ desert planet, they mostly hold ideals of honesty, virtue, martial combat and bravery above all \
+ else, frequently even their own lives. They prefer warmer temperatures than most species and \
+ their native tongue is a heavy hissing laungage called Sinta'Unathi."
flags = HAS_LIPS | HAS_UNDERWEAR
bodyflags = FEET_CLAWS | HAS_TAIL | HAS_SKIN_COLOR
@@ -305,10 +331,17 @@
icobase = 'icons/mob/human_races/r_tajaran.dmi'
deform = 'icons/mob/human_races/r_def_tajaran.dmi'
path = /mob/living/carbon/human/tajaran
+ default_language = "Galactic Common"
language = "Siik'tajr"
tail = "tajtail"
unarmed_type = /datum/unarmed_attack/claws
darksight = 8
+
+ blurb = "The Tajaran race is a species of feline-like bipeds hailing from the planet of Ahdomai in the \
+ S'randarr system. They have been brought up into the space age by the Humans and Skrell, and have been \
+ influenced heavily by their long history of Slavemaster rule. They have a structured, clan-influenced way \
+ of family and politics. They prefer colder environments, and speak a variety of languages, mostly Siik'Maas, \
+ using unique inflections their mouths form."
cold_level_1 = 200
cold_level_2 = 140
@@ -331,9 +364,16 @@
icobase = 'icons/mob/human_races/r_skrell.dmi'
deform = 'icons/mob/human_races/r_def_skrell.dmi'
path = /mob/living/carbon/human/skrell
+ default_language = "Galactic Common"
language = "Skrellian"
primitive = /mob/living/carbon/monkey/skrell
unarmed_type = /datum/unarmed_attack/punch
+
+ blurb = "An amphibious species, Skrell come from the star system known as Qerr'Vallis, which translates to 'Star of \
+ the royals' or 'Light of the Crown'.
Skrell are a highly advanced and logical race who live under the rule \
+ of the Qerr'Katish, a caste within their society which keeps the empire of the Skrell running smoothly. Skrell are \
+ herbivores on the whole and tend to be co-operative with the other species of the galaxy, although they rarely reveal \
+ the secrets of their empire to their allies."
flags = HAS_LIPS | HAS_UNDERWEAR
bloodflags = BLOOD_GREEN
@@ -348,8 +388,20 @@
icobase = 'icons/mob/human_races/r_vox.dmi'
deform = 'icons/mob/human_races/r_def_vox.dmi'
path = /mob/living/carbon/human/vox
- language = "Vox-pidgin"
+
+ default_language = "Galactic Common"
+ language = "Vox-pidgin"
+ speech_sounds = list('sound/voice/shriek1.ogg')
+ speech_chance = 20
+
unarmed_type = /datum/unarmed_attack/claws //I dont think it will hurt to give vox claws too.
+
+ blurb = "The Vox are the broken remnants of a once-proud race, now reduced to little more than \
+ scavenging vermin who prey on isolated stations, ships or planets to keep their own ancient arkships \
+ alive. They are four to five feet tall, reptillian, beaked, tailed and quilled; human crews often \
+ refer to them as 'shitbirds' for their violent and offensive nature, as well as their horrible \
+ smell.
Most humans will never meet a Vox raider, instead learning of this insular species through \
+ dealing with their traders and merchants; those that do rarely enjoy the experience."
warning_low_pressure = 50
hazard_low_pressure = 0
@@ -393,7 +445,6 @@
name = "Vox Armalis"
icobase = 'icons/mob/human_races/r_armalis.dmi'
deform = 'icons/mob/human_races/r_armalis.dmi'
- language = "Vox-pidgin"
path = /mob/living/carbon/human/voxarmalis
unarmed_type = /datum/unarmed_attack/claws/armalis
@@ -454,20 +505,19 @@
icobase = 'icons/mob/human_races/r_kidan.dmi'
deform = 'icons/mob/human_races/r_def_kidan.dmi'
path = /mob/living/carbon/human/kidan
+ default_language = "Galactic Common"
language = "Chittin"
unarmed_type = /datum/unarmed_attack/claws
-
brute_mod = 0.8
flags = IS_WHITELISTED
bloodflags = BLOOD_GREEN
bodyflags = FEET_CLAWS
-
-
/datum/species/slime
name = "Slime People"
+ default_language = "Galactic Common"
language = "Bubblish"
path = /mob/living/carbon/human/slime
primitive = /mob/living/carbon/slime
@@ -478,37 +528,48 @@
/datum/species/slime/handle_post_spawn(var/mob/living/carbon/human/H)
H.dna = new /datum/dna(null)
- H.dna.species=H.species.name
+ H.dna.real_name = H.real_name
+ H.dna.species = H.species.name
H.dna.mutantrace = "slime"
- H.update_mutantrace()
- return ..()
+ ..()
-/datum/species/grey // /vg/
+/datum/species/grey
name = "Grey"
icobase = 'icons/mob/human_races/r_grey.dmi'
deform = 'icons/mob/human_races/r_def_grey.dmi'
- language = "Grey"
+ default_language = "Galactic Common"
+ //language = "Grey" // Perhaps if they ever get a hivemind
unarmed_type = /datum/unarmed_attack/punch
darksight = 5 // BOOSTED from 2
eyes = "grey_eyes_s"
brute_mod = 1.25 //greys are fragile
+
+ default_genes = list(M_REMOTE_TALK)
primitive = /mob/living/carbon/monkey // TODO
flags = IS_WHITELISTED | HAS_LIPS | HAS_UNDERWEAR | CAN_BE_FAT
-
- // Both must be set or it's only a 45% chance of manifesting.
- default_mutations=list(M_REMOTE_TALK)
- default_block_names=list("REMOTETALK")
-
+
+/datum/species/grey/handle_dna(var/mob/living/carbon/C, var/remove)
+ if(!remove)
+ C.dna.SetSEState(REMOTETALKBLOCK,1,1)
+ C.mutations |= M_REMOTE_TALK
+ genemutcheck(C,REMOTETALKBLOCK,null,MUTCHK_FORCED)
+ else
+ C.dna.SetSEState(REMOTETALKBLOCK,0,1)
+ C.mutations -= M_REMOTE_TALK
+ genemutcheck(C,REMOTETALKBLOCK,null,MUTCHK_FORCED)
+ C.update_mutations()
+ ..()
/datum/species/diona
name = "Diona"
icobase = 'icons/mob/human_races/r_diona.dmi'
deform = 'icons/mob/human_races/r_def_plant.dmi'
path = /mob/living/carbon/human/diona
+ default_language = "Galactic Common"
language = "Rootspeak"
unarmed_type = /datum/unarmed_attack/diona
primitive = /mob/living/carbon/monkey/diona
@@ -523,6 +584,14 @@
heat_level_1 = 300
heat_level_2 = 350
heat_level_3 = 700
+
+ blurb = "Commonly referred to (erroneously) as 'plant people', the Dionaea are a strange space-dwelling collective \
+ species hailing from Epsilon Ursae Minoris. Each 'diona' is a cluster of numerous cat-sized organisms called nymphs; \
+ there is no effective upper limit to the number that can fuse in gestalt, and reports exist of the Epsilon Ursae \
+ Minoris primary being ringed with a cloud of singing space-station-sized entities.
The Dionaea coexist peacefully with \
+ all known species, especially the Skrell. Their communal mind makes them slow to react, and they have difficulty understanding \
+ even the simplest concepts of other minds. Their alien physiology allows them survive happily off a diet of nothing but light, \
+ water and other radiation."
flags = NO_BREATHE | REQUIRE_LIGHT | IS_PLANT | RAD_ABSORB | NO_BLOOD | IS_SLOW | NO_PAIN
@@ -532,6 +601,12 @@
flesh_color = "#907E4A"
reagent_tag = IS_DIONA
+
+/datum/species/diona/can_understand(var/mob/other)
+ var/mob/living/carbon/monkey/diona/D = other
+ if(istype(D))
+ return 1
+ return 0
/datum/species/diona/handle_post_spawn(var/mob/living/carbon/human/H)
H.gender = NEUTER
@@ -561,6 +636,7 @@
icobase = 'icons/mob/human_races/r_machine.dmi'
deform = 'icons/mob/human_races/r_machine.dmi'
path = /mob/living/carbon/human/machine
+ default_language = "Galactic Common"
language = "Trinary"
unarmed_type = /datum/unarmed_attack/punch
diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm
index bad5c10d9e0..51988feaf22 100644
--- a/code/modules/mob/living/say.dm
+++ b/code/modules/mob/living/say.dm
@@ -1,25 +1,22 @@
var/list/department_radio_keys = list(
- ":r" = "right ear", "#r" = "right ear", ".r" = "right ear", "!r" = "fake right ear",
- ":l" = "left ear", "#l" = "left ear", ".l" = "left ear", "!l" = "fake left ear",
+ ":r" = "right ear", "#r" = "right ear", ".r" = "right ear",
+ ":l" = "left ear", "#l" = "left ear", ".l" = "left ear",
":i" = "intercom", "#i" = "intercom", ".i" = "intercom",
":h" = "department", "#h" = "department", ".h" = "department",
+ ":+" = "special", "#+" = "special", ".+" = "special", //activate radio-specific special functions
":c" = "Command", "#c" = "Command", ".c" = "Command",
":n" = "Science", "#n" = "Science", ".n" = "Science",
":m" = "Medical", "#m" = "Medical", ".m" = "Medical",
":e" = "Engineering", "#e" = "Engineering", ".e" = "Engineering",
":s" = "Security", "#s" = "Security", ".s" = "Security",
":w" = "whisper", "#w" = "whisper", ".w" = "whisper",
- ":b" = "binary", "#b" = "binary", ".b" = "binary",
- ":d" = "drone", "#d" = "drone", ".d" = "drone",
- ":a" = "alientalk", "#a" = "alientalk", ".a" = "alientalk",
":t" = "Syndicate", "#t" = "Syndicate", ".t" = "Syndicate",
":u" = "Supply", "#u" = "Supply", ".u" = "Supply",
":z" = "Service", "#z" = "Service", ".z" = "Service",
- ":g" = "changeling", "#g" = "changeling", ".g" = "changeling",
+ ":p" = "AI Private", "#p" = "AI Private", ".p" = "AI Private",
-
- ":R" = "right ear", "#R" = "right ear", ".R" = "right ear", "!R" = "fake right ear",
- ":L" = "left ear", "#L" = "left ear", ".L" = "left ear", "!L" = "fake left ear",
+ ":R" = "right ear", "#R" = "right ear", ".R" = "right ear",
+ ":L" = "left ear", "#L" = "left ear", ".L" = "left ear",
":I" = "intercom", "#I" = "intercom", ".I" = "intercom",
":H" = "department", "#H" = "department", ".H" = "department",
":C" = "Command", "#C" = "Command", ".C" = "Command",
@@ -28,40 +25,35 @@ var/list/department_radio_keys = list(
":E" = "Engineering", "#E" = "Engineering", ".E" = "Engineering",
":S" = "Security", "#S" = "Security", ".S" = "Security",
":W" = "whisper", "#W" = "whisper", ".W" = "whisper",
- ":D" = "drone", "#D" = "drone", ".D" = "drone",
- ":B" = "binary", "#B" = "binary", ".B" = "binary",
- ":A" = "alientalk", "#A" = "alientalk", ".A" = "alientalk",
":T" = "Syndicate", "#T" = "Syndicate", ".T" = "Syndicate",
":U" = "Supply", "#U" = "Supply", ".U" = "Supply",
":Z" = "Service", "#Z" = "Service", ".Z" = "Service",
- ":G" = "changeling", "#G" = "changeling", ".G" = "changeling"
-
- /* //kinda localization -- rastaf0
- //same keys as above, but on russian keyboard layout. This file uses cp1251 as encoding.
- ":ê" = "right hand", "#ê" = "right hand", ".ê" = "right hand",
- ":ä" = "left hand", "#ä" = "left hand", ".ä" = "left hand",
- ":ø" = "intercom", "#ø" = "intercom", ".ø" = "intercom",
- ":ð" = "department", "#ð" = "department", ".ð" = "department",
- ":ñ" = "Command", "#ñ" = "Command", ".ñ" = "Command",
- ":ò" = "Science", "#ò" = "Science", ".ò" = "Science",
- ":ü" = "Medical", "#ü" = "Medical", ".ü" = "Medical",
- ":ó" = "Engineering", "#ó" = "Engineering", ".ó" = "Engineering",
- ":û" = "Security", "#û" = "Security", ".û" = "Security",
- ":ö" = "whisper", "#ö" = "whisper", ".ö" = "whisper",
- ":è" = "binary", "#è" = "binary", ".è" = "binary",
- ":ô" = "alientalk", "#ô" = "alientalk", ".ô" = "alientalk",
- ":Ã¥" = "Syndicate", "#Ã¥" = "Syndicate", ".Ã¥" = "Syndicate",
- ":é" = "Supply", "#é" = "Supply", ".é" = "Supply",
- ":ï" = "changeling", "#ï" = "changeling", ".ï" = "changeling" */
+ ":P" = "AI Private", "#P" = "AI Private", ".P" = "AI Private"
)
+
+var/list/channel_to_radio_key = new
+proc/get_radio_key_from_channel(var/channel)
+ var/key = channel_to_radio_key[channel]
+ if(!key)
+ for(var/radio_key in department_radio_keys)
+ if(department_radio_keys[radio_key] == channel)
+ key = radio_key
+ break
+ if(!key)
+ key = ""
+ channel_to_radio_key[channel] = key
+
+ return key
+
/mob/living/proc/binarycheck()
+
if (istype(src, /mob/living/silicon/pai))
return
- if (issilicon(src))
- return 1
+
if (!ishuman(src))
return
+
var/mob/living/carbon/human/H = src
if (H.l_ear || H.r_ear)
var/obj/item/device/radio/headset/dongle
@@ -72,446 +64,78 @@ var/list/department_radio_keys = list(
if(!istype(dongle)) return
if(dongle.translate_binary) return 1
-/mob/living/proc/hivecheck()
- if (isalien(src)) return 1
- if (!ishuman(src)) return
- var/mob/living/carbon/human/H = src
- if (H.l_ear || H.r_ear)
- var/obj/item/device/radio/headset/dongle
- if(istype(H.l_ear,/obj/item/device/radio/headset))
- dongle = H.l_ear
- else
- dongle = H.r_ear
- if(!istype(dongle)) return
- if(dongle.translate_hive) return 1
-
-/mob/living/say(var/message)
-
- /*
- Formatting and sanitizing.
- */
-
- message = trim(copytext(sanitize(message), 1, MAX_MESSAGE_LEN))
-
-
- /*
- Sanity checking and speech failure.
- */
-
- if (!message)
- return
-
- if(silent)
- return
-
- if (stat == 2) // Dead.
- return say_dead(message)
- else if (stat) // Unconcious.
- return
-
- if (src.client)
- if(client.prefs.muted & MUTE_IC)
- src << "\red You cannot speak in IC (muted)."
- return
- if (src.client.handle_spam_prevention(message,MUTE_IC))
- return
- // undo last word status.
-
- if(ishuman(src))
- var/mob/living/carbon/human/H=src
- if(H.said_last_words)
- H.said_last_words=0
-
- // Mute disability
- if (sdisabilities & MUTE)
- return
-
- // Muzzled.
- if (istype(wear_mask, /obj/item/clothing/mask/muzzle))
- return
-
- // Emotes.
- if (copytext(message, 1, 2) == "*" && !stat)
- return emote(copytext(message, 2))
-
- /*
- Identity hiding.
- */
- var/alt_name = ""
- if (istype(src, /mob/living/carbon/human) && name != GetVoice())
- var/mob/living/carbon/human/H = src
- alt_name = " (as [H.get_id_name("Unknown")])"
-
- /*
- Now we get into the real meat of the say processing. Determining the message mode.
- */
-
- var/italics = 0
- var/message_range = null
- var/message_mode = null
-
- var/datum/language/speaking = null //For use if a specific language is being spoken.
-
- var/braindam = getBrainLoss()
- if (braindam >= 60)
- if(prob(braindam/4))
- message = stutter(message)
- if(prob(braindam))
- message = uppertext(message)
-
- // General public key. Special message handling
- var/mmode
- var/cprefix = ""
- if(length(message) >= 2)
- cprefix = copytext(message, 1, 3)
- if(cprefix in department_radio_keys)
- mmode = department_radio_keys[cprefix]
- if (copytext(message, 1, 2) == ";" || (prob(braindam/2) && !mmode))
- message_mode = "headset"
- message = copytext(message, 2)
- // Begin checking for either a message mode or a language to speak.
- else if (length(message) >= 2)
- var/channel_prefix = copytext(message, 1, 3)
-
- //Check if the person is speaking a language that they know.
- if(languages.len)
- for(var/datum/language/L in languages)
- if(lowertext(channel_prefix) == ":[L.key]")
- speaking = L
- break
- message_mode = department_radio_keys[channel_prefix]
- if (message_mode || speaking || copytext(message,1,2) == ":")
- message = trim(copytext(message, 3))
- if (!(istype(src,/mob/living/carbon/human) && !isAI(src) && !isrobot(src) || istype(src,/mob/living/carbon/monkey) || istype(src, /mob/living/simple_animal/parrot) || isrobot(src) && (message_mode=="department") || isAI(src) && (message_mode=="department") || (message_mode in radiochannels)))
- message_mode = null //only humans can use headsets
-
- if(traumatic_shock > 61 && prob(50))
- if(message_mode)
- src << "You try to use your radio, but you are in too much pain."
- message_mode = null //people in shock will have a high chance of not being able to speak on radio; needed since people don't instantly pass out at 100 damage.
-
- message = capitalize(message)
-
- if (!message)
- return
-
- if (stuttering)
- message = stutter(message)
-
- ///////////////////////////////////////////////////////////
- // VIDEO KILLED THE RADIO STAR V2.0
- //
- // EXPERIMENTAL CODE BY YOUR PALS AT /vg/
- ///////////////////////////////////////////////////////////
-
- var/list/obj/item/used_radios = new
-
- // Actually speaking on the radio?
- var/is_speaking_radio = 0
-
- // Devices selected
- var/list/devices=list()
-
- // Select all always_talk devices
- // Carbon lifeforms
- //if(istype(src, /mob/living/carbon))
- for(var/obj/item/device/radio/R in contents)
- if(R.always_talk)
- devices += R
-
- //src << "Speaking on [message_mode]: [message]"
- if(message_mode)
- switch (message_mode)
- if ("right ear")
- if(iscarbon(src))
- var/mob/living/carbon/C=src
- if(C:r_ear) devices += C:r_ear
- message_mode="headset"
- message_range = 1
- italics = 1
-
- if ("left ear")
- if(iscarbon(src))
- var/mob/living/carbon/C=src
- if(C:l_ear) devices += C:l_ear
- message_mode="headset"
- message_range = 1
- italics = 1
-
- // Select a headset and speak into it without actually sending a message
- if ("fake")
- if(iscarbon(src))
- var/mob/living/carbon/C=src
- if(C:l_ear) used_radios += C:l_ear
- if(C:r_ear) used_radios += C:r_ear
- if(issilicon(src))
- var/mob/living/silicon/Ro=src
- if(Ro:radio) devices += Ro:radio
- message_range = 1
- italics = 1
- if ("fake left ear")
- if(iscarbon(src))
- var/mob/living/carbon/C=src
- if(C:l_ear) used_radios += C:l_ear
- message_range = 1
- italics = 1
- if ("fake right ear")
- if(iscarbon(src))
- var/mob/living/carbon/C=src
- if(C:r_ear) used_radios += C:r_ear
- message_range = 1
- italics = 1
-
- if ("intercom")
- for (var/obj/item/device/radio/intercom/I in view(1, null))
- devices += I
- message_mode=null
- message_range = 1
- italics = 1
-
- //I see no reason to restrict such way of whispering
- if ("whisper")
- whisper(message)
- return
-
- if ("binary")
- if(robot_talk_understand || binarycheck())
- //message = trim(copytext(sanitize(message), 1, MAX_MESSAGE_LEN)) //seems redundant
- robot_talk(message)
- return
-
- if ("alientalk")
- if(alien_talk_understand || hivecheck())
- //message = trim(copytext(sanitize(message), 1, MAX_MESSAGE_LEN)) //seems redundant
- alien_talk(message)
- return
-
- if ("pAI")
- message_range = 1
- italics = 1
-
- if("changeling")
- if(mind && mind.changeling)
- log_say("[key_name(src)] ([mind.changeling.changelingID]): [message]")
- for(var/mob/Changeling in mob_list)
- if(istype(Changeling, /mob/living/silicon)) continue //WHY IS THIS NEEDED?
- if((Changeling.mind && Changeling.mind.changeling) || istype(Changeling, /mob/dead/observer))
- Changeling << "[mind.changeling.changelingID]: [message]"
- else if(istype(Changeling,/mob/dead/observer) && (Changeling.client && Changeling.client.prefs.toggles & CHAT_GHOSTEARS))
- Changeling << "[mind.changeling.changelingID] (: (Follow) [message]"
- return
- else // headset, department channels.
- if(iscarbon(src))
- var/mob/living/carbon/C=src
- if(C:l_ear) devices += C:l_ear
- if(C:r_ear) devices += C:r_ear
- if(issilicon(src))
- if(isAI(src))//for the AI's radio. This can't be with the borg thing above due to typecasting.
- var/mob/living/silicon/ai/A = src
- if(A.aiRadio)
- A.aiRadio.talk_into(src, message, message_mode)
- used_radios += A.aiRadio
- else
- var/mob/living/silicon/Ro=src
- if(!isAI(Ro))
- if(Ro:radio)
- devices += Ro:radio
- else
- warning("[src] has no radio!")
- message_range = 1
- italics = 1
- if(devices.len>0)
- for(var/obj/item/device/radio/R in devices)
- if(istype(R))
- R.talk_into(src, message, message_mode)
- used_radios += R
- is_speaking_radio = 1
-
- /////////////////////////////////////////////////////////////////
- //
- /////////////////////////////////////////////////////////////////
-
- var/datum/gas_mixture/environment = loc.return_air()
- if(environment)
- var/pressure = environment.return_pressure()
- if(pressure < SOUND_MINIMUM_PRESSURE)
- italics = 1
- message_range = 1
-
- var/list/listening
-
- listening = get_mobs_in_view(message_range, src)
- var/list/onscreen = viewers()
- for(var/mob/M in player_list)
- if (!M.client)
- continue //skip monkeys and leavers
- if (istype(M, /mob/new_player))
- continue
- if(M.stat == DEAD && (M.client.prefs.toggles & CHAT_GHOSTEARS) && src.client) // src.client is so that ghosts don't have to listen to mice
- listening|=M
+/mob/living/say(var/message, var/datum/language/speaking = null, var/verb="says", var/alt_name="", var/italics=0, var/message_range = world.view, var/sound/speech_sound, var/sound_vol)
var/turf/T = get_turf(src)
- var/list/W = hear(message_range, T)
- for (var/obj/O in ((W | contents)-used_radios))
- W |= O
+ //handle nonverbal and sign languages here
+ if (speaking)
+ if (speaking.flags & NONVERBAL)
+ if (prob(30))
+ src.custom_emote(1, "[pick(speaking.signlang_verb)].")
- for (var/mob/M in W)
- W |= M.contents
+ if (speaking.flags & SIGNLANG)
+ return say_signlang(message, pick(speaking.signlang_verb), speaking)
- for (var/atom/A in W)
- if(istype(A, /mob/living/simple_animal/parrot)) //Parrot speech mimickry
- if(A == src)
- continue //Dont imitate ourselves
+ var/list/listening = list()
+ var/list/listening_obj = list()
- var/mob/living/simple_animal/parrot/P = A
- if(P.speech_buffer.len >= 10)
- P.speech_buffer.Remove(pick(P.speech_buffer))
- P.speech_buffer.Add(message)
+ if(T)
+ //make sure the air can transmit speech - speaker's side
+ var/datum/gas_mixture/environment = T.return_air()
+ var/pressure = (environment)? environment.return_pressure() : 0
+ if(pressure < SOUND_MINIMUM_PRESSURE)
+ message_range = 1
- if (isslime(A))
- var/mob/living/carbon/slime/S = A
- if (src in S.Friends)
- S.speech_buffer = list()
- S.speech_buffer.Add(src)
- S.speech_buffer.Add(lowertext(html_decode(message)))
+ if (pressure < ONE_ATMOSPHERE*0.4) //sound distortion pressure, to help clue people in that the air is thin, even if it isn't a vacuum yet
+ italics = 1
+ sound_vol *= 0.5 //muffle the sound a bit, so it's like we're actually talking through contact
- if(istype(A, /obj/)) //radio in pocket could work, radio in backpack wouldn't --rastaf0
- var/obj/O = A
- spawn (0)
- if(O && !istype(O.loc, /obj/item/weapon/storage))
- O.hear_talk(src, message)
+ var/list/hear = hear(message_range, T)
+ var/list/hearturfs = list()
+ for(var/I in hear)
+ if(istype(I, /mob/))
+ var/mob/M = I
+ listening += M
+ hearturfs += M.locs[1]
+ for(var/obj/O in M.contents)
+ listening_obj |= O
+ else if(istype(I, /obj/))
+ var/obj/O = I
+ hearturfs += O.locs[1]
+ listening_obj |= O
- var/list/heard_a = list() // understood us
- var/list/heard_b = list() // didn't understand us
-
- for (var/M in listening)
- if(hascall(M,"say_understands"))
- if (M:say_understands(src,speaking))
- heard_a += M
- else
- heard_b += M
- else
- heard_a += M
+ for(var/mob/M in player_list)
+ if(M.stat == DEAD && M.client && (M.client.prefs.toggles & CHAT_GHOSTEARS))
+ listening |= M
+ continue
+ if(M.loc && M.locs[1] in hearturfs)
+ listening |= M
var/speech_bubble_test = say_test(message)
var/image/speech_bubble = image('icons/mob/talk.dmi',src,"h[speech_bubble_test]")
spawn(30) del(speech_bubble)
- for(var/mob/M in hearers(5, src))
- if(M != src && is_speaking_radio)
- M:show_message("[src] talks into [used_radios.len ? used_radios[1] : "radio"]")
+ for(var/mob/M in listening)
+ M << speech_bubble
+ M.hear_say(message, verb, speaking, alt_name, italics, src, speech_sound, sound_vol)
-/* if(message_mode == null)
- var/accent = "en-us"
- var/voice = "m7"
- var/speed = 175
- var/pitch = 0
- var/echo = 10
- if(istype(src, /mob/living/silicon/ai))
- echo = 90
- if(istype(src, /mob/living/silicon/robot))
- echo = 60
- if(src.client && src.client.prefs)
- accent = src.client.prefs.accent
- voice = src.client.prefs.voice
- speed = src.client.prefs.talkspeed
- pitch = src.client.prefs.pitch
- src:texttospeech(message, speed, pitch, accent, "+[voice]", echo)*/
-
- var/rendered = null
-
- if (length(heard_a))
- var/message_a = say_quote(message,speaking)
-
- if (italics)
- message_a = "[message_a]"
-
- var/message_ghost = "[message_a]" // bold so ghosts know the person is in view.
- rendered = "[GetVoice()][alt_name] [message_a]"
- var/rendered2 = null
-
- for (var/mob/M in heard_a)
- //BEGIN TELEPORT CHANGES
-/* if(message_mode == null && fexists("sound/playervoices/[src.ckey].ogg"))
- if(M.client)
- if(M.client.prefs)
- if(M.client.prefs.sound & SOUND_VOICES)
- M.playsound_local(get_turf(src), "sound/playervoices/[src.ckey].ogg", 70, 0, 5, 1)*/
- if(!istype(M, /mob/new_player))
- if(M && M.stat == DEAD)
- if (M.client && M.client.prefs && (M.client.prefs.toggles & CHAT_GHOSTEARS) && M in onscreen)
- rendered2 = "[GetVoice()] [alt_name] (Follow) [message_ghost]"
- else
- rendered2 = "[GetVoice()] [alt_name] (Follow) [message_a]"
- M:show_message(rendered2, 2)
- continue
- //END CHANGES
-
- if(hascall(M,"show_message"))
- var/deaf_message = ""
- var/deaf_type = 1
- if(M != src)
- deaf_message = "[name][alt_name] talks but you cannot hear them."
- else
- deaf_message = "You cannot hear yourself!"
- deaf_type = 2 // Since you should be able to hear yourself without looking
- M:show_message(rendered, 2, deaf_message, deaf_type)
- M << speech_bubble
-
- if (length(heard_b))
-
- var/message_b
- message_b = stars(message)
- message_b = say_quote(message_b,speaking)
-
- if (italics)
- message_b = "[message_b]"
-
- rendered = "[name][alt_name] [message_b]" //Voice_name isn't too useful. You'd be able to tell who was talking presumably.
- var/rendered2 = null
-
- for (var/M in heard_b)
- var/mob/MM
- if(istype(M, /mob))
- MM = M
- if(!istype(MM, /mob/new_player) && MM)
- if(MM && MM.stat == DEAD)
- rendered2 = "[voice_name] (Follow) [message_b]"
- MM:show_message(rendered2, 2)
- continue
- if(hascall(M,"show_message"))
- M:show_message(rendered, 2)
- M << speech_bubble
-
- /*
- if(M.client)
-
- if(!M.client.bubbles || M == src)
- var/image/I = image('icons/effects/speechbubble.dmi', B, "override")
- I.override = 1
- M << I
- */ /*
-
- flick("[presay]say", B)
-
- if(istype(loc, /turf))
- B.loc = loc
- else
- B.loc = loc.loc
-
- spawn()
- sleep(11)
- del(B)
- */
+ for(var/obj/O in listening_obj)
+ spawn(0)
+ if(O) //It's possible that it could be deleted in the meantime.
+ O.hear_talk(src, message, verb, speaking)
log_say("[name]/[key] : [message]")
+ return 1
+
+/mob/living/proc/say_signlang(var/message, var/verb="gestures", var/datum/language/language)
+ for (var/mob/O in viewers(src, null))
+ O.hear_signlang(message, verb, language, src)
+ return 1
/obj/effect/speech_bubble
var/mob/parent
/mob/living/proc/GetVoice()
return name
-
-
diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm
index 117a2d127d5..a7fb2ada618 100644
--- a/code/modules/mob/living/silicon/ai/ai.dm
+++ b/code/modules/mob/living/silicon/ai/ai.dm
@@ -112,6 +112,20 @@ var/list/ai_list = list()
/mob/living/silicon/ai/proc/ai_statuschange, /mob/living/silicon/ai/proc/ai_hologram_change, \
/mob/living/silicon/ai/proc/toggle_camera_light, /mob/living/silicon/ai/proc/botcall, /mob/living/silicon/ai/proc/control_integrated_radio, /mob/living/silicon/ai/proc/control_hud, /mob/living/silicon/ai/proc/change_arrival_message, /mob/living/silicon/ai/proc/ai_store_location, /mob/living/silicon/ai/proc/ai_goto_location, /mob/living/silicon/ai/proc/ai_remove_location, /mob/living/silicon/ai/proc/nano_crew_monitor, /mob/living/silicon/ai/proc/ai_cancel_call)
+ //Languages
+ add_language("Robot Talk", 1)
+ add_language("Sol Common", 1)
+ add_language("Tradeband", 1)
+ add_language("Sinta'unathi", 0)
+ add_language("Siik'tajr", 0)
+ add_language("Skrellian", 0)
+ add_language("Vox-pidgin", 0)
+ add_language("Rootspeak", 0)
+ add_language("Trinary", 1)
+ add_language("Chittin", 0)
+ add_language("Bubblish", 0)
+ add_language("Gutter", 0)
+
if(!safety)//Only used by AIize() to successfully spawn an AI.
if (!B)//If there is no player/brain inside.
empty_playable_ai_cores += new/obj/structure/AIcore/deactivated(loc)//New empty terminal.
@@ -127,6 +141,7 @@ var/list/ai_list = list()
/mob/living/silicon/ai/proc/ai_statuschange, /mob/living/silicon/ai/proc/ai_hologram_change, \
/mob/living/silicon/ai/proc/toggle_camera_light,/mob/living/silicon/ai/verb/pick_icon,/mob/living/silicon/ai/proc/control_hud, /mob/living/silicon/ai/proc/change_arrival_message, /mob/living/silicon/ai/proc/ai_cancel_call)
laws = new /datum/ai_laws/alienmov
+ add_language("xenocommon", 1)
else
B.brainmob.mind.transfer_to(src)
diff --git a/code/modules/mob/living/silicon/ai/freelook/eye.dm b/code/modules/mob/living/silicon/ai/freelook/eye.dm
index ec02433c46a..833d08e7cb4 100644
--- a/code/modules/mob/living/silicon/ai/freelook/eye.dm
+++ b/code/modules/mob/living/silicon/ai/freelook/eye.dm
@@ -5,7 +5,9 @@
/mob/aiEye
name = "Inactive AI Eye"
- icon = 'icons/obj/status_display.dmi' // For AI friend secret shh :o
+ icon = 'icons/mob/AI.dmi'
+ icon_state = "eye"
+ alpha = 127
var/list/visibleCameraChunks = list()
var/mob/living/silicon/ai/ai = null
density = 0
diff --git a/code/modules/mob/living/silicon/ai/say.dm b/code/modules/mob/living/silicon/ai/say.dm
index b596fd53541..5a4398485ed 100644
--- a/code/modules/mob/living/silicon/ai/say.dm
+++ b/code/modules/mob/living/silicon/ai/say.dm
@@ -1,32 +1,8 @@
/mob/living/silicon/ai/say(var/message)
if(parent && istype(parent) && parent.stat != 2)
- parent.say(message) //If there is a defined "parent" AI, it is actually an AI, and it is alive, anything the AI tries to say is said by the parent instead.
- return
-
- ..(message)
-
-/mob/living/silicon/ai/say_understands(var/other)
- if (istype(other, /mob/living/carbon/human))
- return 1
- if (istype(other, /mob/living/silicon/robot))
- return 1
- if (istype(other, /mob/living/silicon/decoy))
- return 1
- if (istype(other, /mob/living/carbon/brain))
- return 1
- if (istype(other, /mob/living/silicon/pai))
- return 1
- return ..()
-
-/mob/living/silicon/ai/say_quote(var/text)
- var/ending = copytext(text, length(text))
-
- if (ending == "?")
- return "queries, \"[text]\"";
- else if (ending == "!")
- return "declares, \"[text]\"";
-
- return "states, \"[text]\"";
+ return parent.say(message)
+ //If there is a defined "parent" AI, it is actually an AI, and it is alive, anything the AI tries to say is said by the parent instead.
+ return ..(message)
/mob/living/silicon/ai/proc/IsVocal()
diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm
index 48a34265d43..d62c5f544d4 100644
--- a/code/modules/mob/living/silicon/pai/pai.dm
+++ b/code/modules/mob/living/silicon/pai/pai.dm
@@ -71,6 +71,7 @@
var/obj/item/radio/integrated/signal/sradio // AI's signaller
+ var/translator_on = 0 // keeps track of the translator module
/mob/living/silicon/pai/New(var/obj/item/device/paicard)
canmove = 0
@@ -81,6 +82,13 @@
if(!card.radio)
card.radio = new /obj/item/device/radio(src.card)
radio = card.radio
+
+ //Default languages without universal translator software
+ add_language("Sol Common", 1)
+ add_language("Tradeband", 1)
+ add_language("Gutter", 1)
+ add_language("Trinary", 1)
+
//Verbs for pAI mobile form, chassis and Say flavor text
verbs += /mob/living/silicon/pai/proc/choose_chassis
verbs += /mob/living/silicon/pai/proc/choose_verbs
@@ -334,7 +342,7 @@
//I'm not sure how much of this is necessary, but I would rather avoid issues.
if(istype(card.loc,/mob))
var/mob/holder = card.loc
- holder.unEquip(card)
+ holder.drop_from_inventory(card)
else if(istype(card.loc,/obj/item/clothing/suit/space/space_ninja))
var/obj/item/clothing/suit/space/space_ninja/holder = card.loc
holder.pai = null
@@ -503,3 +511,7 @@
spawn(1)
close_up()
return 2
+
+// No binary for pAIs.
+/mob/living/silicon/pai/binarycheck()
+ return 0
\ No newline at end of file
diff --git a/code/modules/mob/living/silicon/pai/software.dm b/code/modules/mob/living/silicon/pai/software.dm
index 4f7bd1a1c61..ff9b343a7b1 100644
--- a/code/modules/mob/living/silicon/pai/software.dm
+++ b/code/modules/mob/living/silicon/pai/software.dm
@@ -268,7 +268,7 @@
src.medHUD = !src.medHUD
if("translator")
if(href_list["toggle"])
- src.universal_speak = !src.universal_speak
+ src.translator_toggle()
if("doorjack")
if(href_list["jack"])
if(src.cable && src.cable.machine)
@@ -687,4 +687,36 @@
else
dat += addtext("| From | → | ", index["owner"],": ", index["message"], "
|
")
dat += "
"
- return dat
\ No newline at end of file
+ return dat
+
+/mob/living/silicon/pai/proc/translator_toggle()
+
+ // Sol Common, Tradeband, Gutter and Trinary are added with New() and are therefore the current default, always active languages
+
+ if(translator_on)
+ translator_on = 0
+
+ remove_language("Sinta'unathi")
+ remove_language("Siik'tajr")
+ remove_language("Skrellian")
+ remove_language("Vox-pidgin")
+ remove_language("Rootspeak")
+ remove_language("Trinary")
+ remove_language("Chittin")
+ remove_language("Bubblish")
+
+ src << "\blue Translator Module toggled OFF."
+
+ else
+ translator_on = 1
+
+ add_language("Sinta'unathi")
+ add_language("Siik'tajr")
+ add_language("Skrellian")
+ add_language("Vox-pidgin")
+ add_language("Rootspeak")
+ add_language("Trinary")
+ add_language("Chittin")
+ add_language("Bubblish")
+
+ src << "\blue Translator Module toggled ON."
\ No newline at end of file
diff --git a/code/modules/mob/living/silicon/robot/component.dm b/code/modules/mob/living/silicon/robot/component.dm
index 5817715ff99..e2d6c31e8f6 100644
--- a/code/modules/mob/living/silicon/robot/component.dm
+++ b/code/modules/mob/living/silicon/robot/component.dm
@@ -121,6 +121,11 @@
/mob/living/silicon/robot/proc/is_component_functioning(module_name)
var/datum/robot_component/C = components[module_name]
return C && C.installed == 1 && C.toggled && C.is_powered()
+
+// Returns component by it's string name
+/mob/living/silicon/robot/proc/get_component(var/component_name)
+ var/datum/robot_component/C = components[component_name]
+ return C
/obj/item/broken_device
name = "broken component"
diff --git a/code/modules/mob/living/silicon/robot/drone/drone.dm b/code/modules/mob/living/silicon/robot/drone/drone.dm
index e3a04f2cd0f..e83298a68eb 100644
--- a/code/modules/mob/living/silicon/robot/drone/drone.dm
+++ b/code/modules/mob/living/silicon/robot/drone/drone.dm
@@ -13,7 +13,8 @@
lawupdate = 0
density = 1
req_access = list(access_engine, access_robotics)
-
+ local_transmit = 1
+
// We need to keep track of a few module items so we don't need to do list operations
// every time we need them. These get set in New() after the module is chosen.
var/obj/item/stack/sheet/metal/cyborg/stack_metal = null
@@ -25,11 +26,13 @@
//Used for self-mailing.
var/mail_destination = 0
- //Used for pulling.
/mob/living/silicon/robot/drone/New()
..()
+
+ remove_language("Robot Talk")
+ add_language("Drone Talk", 1)
if(camera && "Robots" in camera.network)
camera.network.Add("Engineering")
@@ -88,79 +91,6 @@
/mob/living/silicon/robot/drone/pick_module()
return
-//Drones can only use binary and say emotes. NOTHING else.
-//TBD, fix up boilerplate. ~ Z
-/mob/living/silicon/robot/drone/say(var/message)
- if (!message)
- return
-
- if (src.client)
- if(client.prefs.muted & MUTE_IC)
- src << "You cannot send IC messages (muted)."
- return
- if (src.client.handle_spam_prevention(message,MUTE_IC))
- return
-
- if (stat == 2)
- message = trim(copytext(sanitize(message), 1, MAX_MESSAGE_LEN))
- return say_dead(message)
-
- //Must be concious to speak
- if (stat)
- return
-
- if(copytext(message,1,2) == "*")
- return emote(copytext(message,2))
-
- if (length(message) >= 2)
- var/prefix = copytext(message, 1, 3)
- if (department_radio_keys[prefix] == "drone")
- message = copytext(message, 3)
- message = trim(copytext(sanitize(message), 1, MAX_MESSAGE_LEN))
-
- if(istype(src, /mob/living/silicon/robot/drone))
- var/mob/living/silicon/robot/drone/R = src
- if(!R.is_component_functioning("comms"))
- src << "\red Your drone communications component isn't functional."
- return
- drone_talk(message)
- else
- var/list/listeners = hearers(5,src)
- listeners |= src
- message = trim(copytext(sanitize(message), 1, MAX_MESSAGE_LEN))
- for(var/mob/living/silicon/D in listeners)
- if(D.client)
- D << "[src] transmits, \"[message]\""
-
- for (var/mob/M in player_list)
- if (!M.client)
- continue
- if (istype(M, /mob/new_player))
- continue
- else if(M.stat == 2 && (M.client.prefs.toggles & CHAT_GHOSTEARS) && src.client)
- M << "[src] (Follow) transmits, \"[message]\""
- else if(M.stat == 2 && src.client && M in listeners)
- M << "[src] (Follow) transmits, \"[message]\""
-
-/mob/living/proc/drone_talk(var/message)
- log_say("[key_name(src)] : [message]")
- message = trim(message)
-
- if (!message)
- return
-
- var/message_a = say_quote(message)
- var/rendered = "Drone Talk, [name] [message_a]"
-
- for (var/mob/living/S in living_mob_list)
- if(istype(S, /mob/living/silicon/robot/drone))
- S.show_message(rendered, 2)
-
- for (var/mob/S in dead_mob_list)
- if(!istype(S,/mob/new_player) && !istype(S,/mob/living/carbon/brain))
- var/rendered2 = "Drone Talk, [name] (Follow) [message_a]"
- S.show_message(rendered2, 2)
-
//Drones cannot be upgraded with borg modules so we need to catch some items before they get used in ..().
/mob/living/silicon/robot/drone/attackby(obj/item/weapon/W as obj, mob/user as mob)
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index e28f9ac8b7d..ae42fbc4697 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -80,6 +80,8 @@
spark_system = new /datum/effect/effect/system/spark_spread()
spark_system.set_up(5, 0, src)
spark_system.attach(src)
+
+ add_language("Robot Talk", 1)
robot_modules_background = new()
robot_modules_background.icon_state = "block"
@@ -174,6 +176,13 @@
rbPDA.set_name_and_job(custom_name,braintype)
if(hiddenborg)
rbPDA.hidden = 1
+
+/mob/living/silicon/robot/binarycheck()
+ if(is_component_functioning("comms"))
+ var/datum/robot_component/RC = get_component("comms")
+ use_power(RC.energy_consumption)
+ return 1
+ return 0
//If there's an MMI in the robot, have it ejected when the mob goes away. --NEO
//Improved /N
@@ -197,7 +206,6 @@
modtype = input("Please, select a module!", "Robot", null, null) in modules
designation = modtype
var/module_sprites[0] //Used to store the associations between sprite names and sprite index.
- var/channels = list()
if(module)
return
@@ -205,14 +213,14 @@
switch(modtype)
if("Standard")
module = new /obj/item/weapon/robot_module/standard(src)
- channels = list("Service" = 1)
+ module.channels = list("Service" = 1)
module_sprites["Basic"] = "robot_old"
module_sprites["Android"] = "droid"
module_sprites["Default"] = "robot"
if("Service")
module = new /obj/item/weapon/robot_module/butler(src)
- channels = list("Service" = 1)
+ module.channels = list("Service" = 1)
module_sprites["Waitress"] = "Service"
module_sprites["Kent"] = "toiletbot"
module_sprites["Bro"] = "Brobot"
@@ -221,7 +229,7 @@
/*
if("Clerical")
module = new /obj/item/weapon/robot_module/clerical(src)
- channels = list("Service" = 1)
+ module.channels = list("Service" = 1)
module_sprites["Waitress"] = "Service"
module_sprites["Kent"] = "toiletbot"
module_sprites["Bro"] = "Brobot"
@@ -230,7 +238,7 @@
*/
if("Miner")
module = new /obj/item/weapon/robot_module/miner(src)
- channels = list("Supply" = 1)
+ module.channels = list("Supply" = 1)
if(camera && "Robots" in camera.network)
camera.network.Add("MINE")
module_sprites["Basic"] = "Miner_old"
@@ -239,7 +247,7 @@
if("Medical")
module = new /obj/item/weapon/robot_module/medical(src)
- channels = list("Medical" = 1)
+ module.channels = list("Medical" = 1)
if(camera && "Robots" in camera.network)
camera.network.Add("Medical")
module_sprites["Basic"] = "Medbot"
@@ -249,7 +257,7 @@
if("Security")
module = new /obj/item/weapon/robot_module/security(src)
- channels = list("Security" = 1)
+ module.channels = list("Security" = 1)
module_sprites["Basic"] = "secborg"
module_sprites["Red Knight"] = "Security"
module_sprites["Black Knight"] = "securityrobot"
@@ -257,7 +265,7 @@
if("Engineering")
module = new /obj/item/weapon/robot_module/engineering(src)
- channels = list("Engineering" = 1)
+ module.channels = list("Engineering" = 1)
if(camera && "Robots" in camera.network)
camera.network.Add("Engineering")
module_sprites["Basic"] = "Engineering"
@@ -266,14 +274,15 @@
if("Janitor")
module = new /obj/item/weapon/robot_module/janitor(src)
+ module.channels = list("Service" = 1)
module_sprites["Basic"] = "JanBot2"
module_sprites["Mopbot"] = "janitorrobot"
module_sprites["Mop Gear Rex"] = "mopgearrex"
if("Combat")
module = new /obj/item/weapon/robot_module/combat(src)
+ module.channels = list("Security" = 1)
module_sprites["Combat Android"] = "droidcombat"
- channels = list("Security" = 1)
if("Hunter")
updatename(module)
@@ -283,7 +292,9 @@
icon_state = "xenoborg-state-a"
modtype = "Xeno-Hu"
feedback_inc("xeborg_hunter",1)
-
+
+ //languages
+ module.add_languages(src)
//Custom_sprite check and entry
if (custom_sprite == 1)
@@ -297,7 +308,7 @@
status_flags &= ~CANPUSH
choose_icon(6,module_sprites)
- radio.config(channels)
+ radio.config(module.channels)
notify_ai(2)
/mob/living/silicon/robot/proc/updatename(var/prefix as text)
@@ -1467,6 +1478,16 @@
laws = new /datum/ai_laws/syndicate_override()
Namepick()
+
+/mob/living/silicon/robot/syndicate/canUseTopic(atom/movable/M)
+ if(stat || lockcharge || stunned || weakened)
+ return
+ if(z in config.admin_levels)
+ return 1
+ /*if(istype(M, /obj/machinery))
+ var/obj/machinery/Machine = M
+ return Machine.emagged*/
+ return 1
/mob/living/silicon/robot/proc/notify_ai(var/notifytype, var/oldname, var/newname)
if(!connected_ai)
diff --git a/code/modules/mob/living/silicon/robot/robot_modules.dm b/code/modules/mob/living/silicon/robot/robot_modules.dm
index a2c1fcb7347..a6b9d46f19a 100644
--- a/code/modules/mob/living/silicon/robot/robot_modules.dm
+++ b/code/modules/mob/living/silicon/robot/robot_modules.dm
@@ -10,6 +10,7 @@
var/obj/item/emag = null
var/obj/item/borg/upgrade/jetpack = null
var/list/stacktypes
+ var/channels = list()
emp_act(severity)
@@ -55,6 +56,20 @@
for(var/obj/O in temp_list)
if(O)
modules += O
+
+/obj/item/weapon/robot_module/proc/add_languages(var/mob/living/silicon/robot/R)
+ //full set of languages
+ R.add_language("Sol Common", 1)
+ R.add_language("Tradeband", 1)
+ R.add_language("Sinta'unathi", 0)
+ R.add_language("Siik'tajr", 0)
+ R.add_language("Skrellian", 0)
+ R.add_language("Vox-pidgin", 0)
+ R.add_language("Rootspeak", 0)
+ R.add_language("Trinary", 1)
+ R.add_language("Chittin", 0)
+ R.add_language("Bubblish", 0)
+ R.add_language("Gutter", 0)
/obj/item/weapon/robot_module/standard
name = "standard robot module"
@@ -248,6 +263,20 @@
if(src.emag)
var/obj/item/weapon/reagent_containers/food/drinks/cans/beer/B = src.emag
B.reagents.add_reagent("beer2", 2)
+
+/obj/item/weapon/robot_module/butler/add_languages(var/mob/living/silicon/robot/R)
+ //full set of languages
+ R.add_language("Sol Common", 1)
+ R.add_language("Tradeband", 1)
+ R.add_language("Sinta'unathi", 1)
+ R.add_language("Siik'tajr", 1)
+ R.add_language("Skrellian", 1)
+ R.add_language("Vox-pidgin", 1)
+ R.add_language("Rootspeak", 1)
+ R.add_language("Trinary", 1)
+ R.add_language("Chittin", 1)
+ R.add_language("Bubblish", 1)
+ R.add_language("Gutter", 1)
/*
/obj/item/weapon/robot_module/clerical //Whyyyyy?
@@ -341,6 +370,10 @@
src.emag = new /obj/item/weapon/reagent_containers/spray/alien/acid(src)
src.emag.reagents.add_reagent("pacid", 125)
src.emag.reagents.add_reagent("sacid", 125)
+
+/obj/item/weapon/robot_module/butler/add_languages(var/mob/living/silicon/robot/R)
+ ..()
+ R.add_language("xenocommon", 1)
/obj/item/weapon/robot_module/drone
name = "drone module"
diff --git a/code/modules/mob/living/silicon/say.dm b/code/modules/mob/living/silicon/say.dm
index 35a31a8dbe0..ba5b0ad4f82 100644
--- a/code/modules/mob/living/silicon/say.dm
+++ b/code/modules/mob/living/silicon/say.dm
@@ -2,79 +2,154 @@
var/ending = copytext(text, length(text))
if (ending == "?")
- return "[speak_query], \"[text]\"";
+ return speak_query
else if (ending == "!")
- return "[speak_exclamation], \"[text]\"";
+ return speak_exclamation
- return "[speak_statement], \"[text]\"";
+ return speak_statement
+
+#define IS_AI 1
+#define IS_ROBOT 2
+#define IS_PAI 3
+
+/mob/living/silicon/say_understands(var/other,var/datum/language/speaking = null)
+ //These only pertain to common. Languages are handled by mob/say_understands()
+ if (!speaking)
+ if (istype(other, /mob/living/carbon))
+ return 1
+ if (istype(other, /mob/living/silicon))
+ return 1
+ if (istype(other, /mob/living/carbon/brain))
+ return 1
+ return ..()
/mob/living/silicon/say(var/message)
if (!message)
- return
+ return 0
if (src.client)
if(client.prefs.muted & MUTE_IC)
src << "You cannot send IC messages (muted)."
- return
+ return 0
if (src.client.handle_spam_prevention(message,MUTE_IC))
- return
+ return 0
+
+ message = trim_strip_html_properly(message)
if (stat == 2)
- message = trim(copytext(sanitize(message), 1, MAX_MESSAGE_LEN))
return say_dead(message)
+ if(copytext(message,1,2) == "*")
+ return emote(copytext(message,2))
+
+ var/bot_type = 0 //Let's not do a fuck ton of type checks, thanks.
+ if(istype(src, /mob/living/silicon/ai))
+ bot_type = IS_AI
+ else if(istype(src, /mob/living/silicon/robot))
+ bot_type = IS_ROBOT
+ else if(istype(src, /mob/living/silicon/pai))
+ bot_type = IS_PAI
+
+ var/mob/living/silicon/ai/AI = src //and let's not declare vars over and over and over for these guys.
+ var/mob/living/silicon/robot/R = src
+ var/mob/living/silicon/pai/P = src
+
//Must be concious to speak
if (stat)
- return
+ return 0
- if (length(message) >= 2)
- var/prefix = copytext(message, 1, 3)
- if (department_radio_keys[prefix] == "binary")
- if(istype(src, /mob/living/silicon/pai))
- return ..(message)
- message = copytext(message, 3)
- message = trim(copytext(sanitize(message), 1, MAX_MESSAGE_LEN))
+ var/verb = say_quote(message)
- // TODO: move the component system up to silicon so we don't have to use this ugly hack..
- if(istype(src, /mob/living/silicon/robot))
- var/mob/living/silicon/robot/R = src
- if(!R.is_component_functioning("comms"))
- src << "\red Your binary communications component isn't functional."
- return
- robot_talk(message)
-
- else if (department_radio_keys[prefix] == "alientalk")
- if(!alien_talk_understand) return
- message = copytext(message, 3)
- message = trim(copytext(sanitize(message), 1, MAX_MESSAGE_LEN))
-
- alien_talk(message)
- else if (department_radio_keys[prefix] == "department")
- if(isAI(src)&&client) //For patching directly into AI holopads.
- var/mob/living/silicon/ai/U = src
- message = copytext(message, 3)
- message = trim(copytext(sanitize(message), 1, MAX_MESSAGE_LEN))
- U.holopad_talk(message)
- else //Will not allow anyone by an active AI to use this function.
- src << "This function is not available to you."
- return
+ //parse radio key and consume it
+ var/message_mode = parse_message_mode(message, "general")
+ if (message_mode)
+ if (message_mode == "general")
+ message = trim(copytext(message,2))
else
- var/mob/living/silicon/ai/AI = src
- if (isAI(src) && AI.aiRadio.disabledAi)
- src << "\red System Error - Transceiver Disabled"
- return
- else
- return ..(message)
- else
- var/mob/living/silicon/ai/AI = src
- if (isAI(src) && AI.aiRadio.disabledAi)
- src << "\red System Error - Transceiver Disabled"
- return
+ message = trim(copytext(message,3))
+
+ //parse language key and consume it
+ var/datum/language/speaking = parse_language(message)
+ if (speaking)
+ verb = speaking.speech_verb
+ message = trim(copytext(message,2+length(speaking.key)))
+
+ if(speaking.flags & HIVEMIND)
+ speaking.broadcast(src,trim(message))
+ return 1
+
+ // Currently used by drones.
+ if(local_transmit)
+ var/list/listeners = hearers(5,src)
+ listeners |= src
+
+ for(var/mob/living/silicon/D in listeners)
+ if(D.client && istype(D,src.type))
+ D << "[src] transmits, \"[message]\""
+
+ for (var/mob/M in player_list)
+ if (istype(M, /mob/new_player))
+ continue
+ else if(M.stat == 2 && M.client.prefs.toggles & CHAT_GHOSTEARS)
+ if(M.client) M << "[src] transmits, \"[message]\""
+ return 1
+
+ if(message_mode && bot_type == IS_ROBOT && !R.is_component_functioning("radio"))
+ src << "\red Your radio isn't functional at this time."
+ return 0
+
+ switch(message_mode)
+ if("department")
+ switch(bot_type)
+ if(IS_AI)
+ return AI.holopad_talk(message, verb, speaking)
+ if(IS_ROBOT)
+ log_say("[key_name(src)] : [message]")
+ return R.radio.talk_into(src,message,message_mode,verb,speaking)
+ if(IS_PAI)
+ log_say("[key_name(src)] : [message]")
+ return P.radio.talk_into(src,message,message_mode,verb,speaking)
+ return 0
+
+ if("general")
+ switch(bot_type)
+ if(IS_AI)
+ if (AI.aiRadio.disabledAi || AI.aiRestorePowerRoutine || AI.stat)
+ src << "\red System Error - Transceiver Disabled"
+ return 0
+ else
+ log_say("[key_name(src)] : [message]")
+ return AI.aiRadio.talk_into(src,message,null,verb,speaking)
+ if(IS_ROBOT)
+ log_say("[key_name(src)] : [message]")
+ return R.radio.talk_into(src,message,null,verb,speaking)
+ if(IS_PAI)
+ log_say("[key_name(src)] : [message]")
+ return P.radio.talk_into(src,message,null,verb,speaking)
+ return 0
+
else
- return ..(message)
+ if(message_mode)
+ switch(bot_type)
+ if(IS_AI)
+ if (AI.aiRadio.disabledAi || AI.aiRestorePowerRoutine || AI.stat)
+ src << "\red System Error - Transceiver Disabled"
+ return 0
+ else
+ log_say("[key_name(src)] : [message]")
+ return AI.aiRadio.talk_into(src,message,message_mode,verb,speaking)
+ if(IS_ROBOT)
+ log_say("[key_name(src)] : [message]")
+ return R.radio.talk_into(src,message,message_mode,verb,speaking)
+ if(IS_PAI)
+ log_say("[key_name(src)] : [message]")
+ return P.radio.talk_into(src,message,message_mode,verb,speaking)
+ return 0
+
+ return ..(message,speaking,verb)
//For holopads only. Usable by AI.
-/mob/living/silicon/ai/proc/holopad_talk(var/message)
+/mob/living/silicon/ai/proc/holopad_talk(var/message, verb, datum/language/speaking)
log_say("[key_name(src)] : [message]")
@@ -84,18 +159,23 @@
return
var/obj/machinery/hologram/holopad/T = src.holo
- if(istype(T) && T.hologram && T.master == src)//If there is a hologram and its master is the user.
- var/message_a = say_quote(message)
+ if(T && T.hologram && T.master == src)//If there is a hologram and its master is the user.
//Human-like, sorta, heard by those who understand humans.
- var/rendered_a = "[name] [message_a]"
-
+ var/rendered_a
//Speach distorted, heard by those who do not understand AIs.
- message = stars(message)
- var/message_b = say_quote(message)
- var/rendered_b = "[voice_name] [message_b]"
+ var/message_stars = stars(message)
+ var/rendered_b
+
+ if(speaking)
+ rendered_a = "[name] [speaking.format_message(message, verb)]"
+ rendered_b = "[voice_name] [speaking.format_message(message_stars, verb)]"
+ src << "Holopad transmitted, [real_name] [speaking.format_message(message, verb)]"//The AI can "hear" its own message.
+ else
+ rendered_a = "[name] [verb], \"[message]\""
+ rendered_b = "[voice_name] [verb], \"[message_stars]\""
+ src << "Holopad transmitted, [real_name] [verb], \"[message]\""//The AI can "hear" its own message.
- src << "Holopad transmitted, [real_name] [message_a]"//The AI can "hear" its own message.
for(var/mob/M in hearers(T.loc))//The location is the object, default distance.
if(M.say_understands(src))//If they understand AI speak. Humans and the like will be able to.
M.show_message(rendered_a, 2)
@@ -105,79 +185,37 @@
This is another way of saying that we won't bother dealing with them.*/
else
src << "No holopad connected."
- return
+ return
+ return 1
-/mob/living/proc/robot_talk(var/message)
+/mob/living/silicon/ai/proc/holopad_emote(var/message) //This is called when the AI uses the 'me' verb while using a holopad.
- log_say("[key_name(src)] : [message]")
-
- var/desig = "Unknown" //ezmode for taters
- if(isrobot(src))
- var/mob/living/silicon/S = src
- if(S.designation)
- desig = trim_left(S.designation)
- else
- desig = "Default"
- else if(isAI(src))
- desig = "AI"
+ log_emote("[key_name(src)] : [message]")
message = trim(message)
if (!message)
return
- var/message_a = say_quote(message)
- var/rendered = "Robotic Talk, [name] ([desig]) [message_a]"
+ var/obj/machinery/hologram/holopad/T = src.holo
+ if(T && T.hologram && T.master == src)
+ var/rendered = "[name] [message]"
+ src << "Holopad action relayed, [real_name] [message]"
- for (var/mob/living/S in living_mob_list)
- if(S.robot_talk_understand && (S.robot_talk_understand == robot_talk_understand)) // This SHOULD catch everything caught by the one below, but I'm not going to change it.
- if(istype(S , /mob/living/silicon/ai))
- var/renderedAI = "Robotic Talk, [name] ([desig]) [message_a]"
- S.show_message(renderedAI, 2)
- else if(istype(S , /mob/dead/observer) && S.stat == DEAD)
- var/rendered2 = "Robotic Talk, [name] ([desig]) (Follow) [message_a]"
- S.show_message(rendered2, 2)
- else
- S.show_message(rendered, 2)
-
-
- else if (S.binarycheck())
- if(istype(S , /mob/living/silicon/ai))
- var/renderedAI = "Robotic Talk, [name] [message_a]"
- S.show_message(renderedAI, 2)
- else if(istype(S , /mob/dead/observer) && S.stat == DEAD)
- var/rendered2 = "Robotic Talk, [name] (Follow) [message_a]"
- S.show_message(rendered2, 2)
- else
- S.show_message(rendered, 2)
-
- var/list/listening = hearers(1, src)
- listening -= src
- listening += src
-
- var/list/heard = list()
- for (var/mob/M in listening)
- if(!istype(M, /mob/living/silicon) && !M.robot_talk_understand)
- heard += M
-
- if (length(heard))
- var/message_b
-
- message_b = "beep beep beep"
- message_b = say_quote(message_b)
- message_b = "[message_b]"
-
- rendered = "[voice_name] [message_b]"
-
- for (var/mob/M in heard)
+ for(var/mob/M in viewers(T.loc))
M.show_message(rendered, 2)
+ else //This shouldn't occur, but better safe then sorry.
+ src << "No holopad connected."
+ return
+ return 1
- message = say_quote(message)
+/mob/living/silicon/ai/emote(var/act, var/type, var/message)
+ var/obj/machinery/hologram/holopad/T = src.holo
+ if(T && T.hologram && T.master == src) //Is the AI using a holopad?
+ src.holopad_emote(message)
+ else //Emote normally, then.
+ ..()
-// rendered = "Robotic Talk, [name] [message_a]"
- rendered = null
-
- for (var/mob/M in dead_mob_list)
- if(!istype(M,/mob/new_player) && !istype(M,/mob/living/carbon/brain)) //No meta-evesdropping
- rendered = "Robotic Talk, [name] (Follow) [message_a]"
- M.show_message(rendered, 2)
\ No newline at end of file
+#undef IS_AI
+#undef IS_ROBOT
+#undef IS_PAI
diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm
index 6f88c99c71e..86da14df0e7 100644
--- a/code/modules/mob/living/silicon/silicon.dm
+++ b/code/modules/mob/living/silicon/silicon.dm
@@ -8,6 +8,7 @@
var/list/alarms_to_clear = list()
immune_to_ssd = 1
var/list/hud_list[10]
+ var/list/speech_synthesizer_langs = list() //which languages can be vocalized by the speech synthesizer
var/list/alarm_types_show = list("Motion" = 0, "Fire" = 0, "Atmosphere" = 0, "Power" = 0, "Camera" = 0)
var/list/alarm_types_clear = list("Motion" = 0, "Fire" = 0, "Atmosphere" = 0, "Power" = 0, "Camera" = 0)
var/designation = ""
@@ -20,6 +21,7 @@
var/sensor_mode = 0 //Determines the current HUD.
#define SEC_HUD 1 //Security HUD mode
#define MED_HUD 2 //Medical HUD mode
+ var/local_transmit //If set, can only speak to others of the same type within a short range.
/mob/living/silicon/proc/cancelAlarm()
return
@@ -205,6 +207,37 @@
show_emergency_shuttle_eta()
show_system_integrity()
show_malf_ai()
+
+//Silicon mob language procs
+
+/mob/living/silicon/can_speak(datum/language/speaking)
+ return universal_speak || (speaking in src.speech_synthesizer_langs) //need speech synthesizer support to vocalize a language
+
+/mob/living/silicon/add_language(var/language, var/can_speak=1)
+ if (..(language) && can_speak)
+ speech_synthesizer_langs.Add(all_languages[language])
+ return 1
+
+/mob/living/silicon/remove_language(var/rem_language)
+ ..(rem_language)
+
+ for (var/datum/language/L in speech_synthesizer_langs)
+ if (L.name == rem_language)
+ speech_synthesizer_langs -= L
+
+/mob/living/silicon/check_languages()
+ set name = "Check Known Languages"
+ set category = "IC"
+ set src = usr
+
+ var/dat = "Known Languages
"
+
+ for(var/datum/language/L in languages)
+ if(!(L.flags & NONGLOBAL))
+ dat += "[L.name] (:[L.key])
Speech Synthesizer: [(L in speech_synthesizer_langs)? "YES":"NOT SUPPORTED"]
[L.desc]
"
+
+ src << browse(dat, "window=checklanguage")
+ return
// this function displays the stations manifest in a separate window
/mob/living/silicon/proc/show_station_manifest()
@@ -257,6 +290,9 @@
set category = "IC"
flavor_text = copytext(sanitize(input(usr, "Please enter your new flavour text.", "Flavour text", null) as text), 1)
+
+/mob/living/silicon/binarycheck()
+ return 1
/mob/living/silicon/proc/toggle_sensor_mode()
var/sensor_type = input("Please select sensor type.", "Sensor Integration", null) in list("Security", "Medical","Disable")
diff --git a/code/modules/mob/living/simple_animal/borer.dm b/code/modules/mob/living/simple_animal/borer.dm
index 8dae4be093b..50334dbdf27 100644
--- a/code/modules/mob/living/simple_animal/borer.dm
+++ b/code/modules/mob/living/simple_animal/borer.dm
@@ -100,51 +100,12 @@
/mob/living/simple_animal/borer/New(var/by_gamemode=0)
..()
+ add_language("Cortical Link")
truename = "[pick("Primary","Secondary","Tertiary","Quaternary")] [rand(1000,9999)]"
if(!by_gamemode)
request_player()
-
-/mob/living/simple_animal/borer/say(var/message)
-
- message = trim(copytext(sanitize(message), 1, MAX_MESSAGE_LEN))
- message = capitalize(message)
-
- if(!message)
- return
-
- if (stat == 2)
- return say_dead(message)
-
- if (stat)
- return
-
- if (src.client)
- if(client.prefs.muted & MUTE_IC)
- src << "\red You cannot speak in IC (muted)."
- return
- if (src.client.handle_spam_prevention(message,MUTE_IC))
- return
-
- if (copytext(message, 1, 2) == "*")
- return emote(copytext(message, 2))
-
- if (copytext(message, 1, 2) == ";") //Brain borer hivemind.
- return borer_speak(message)
-
- if(!host)
- src << "You have no host to speak to."
- return //No host, no audible speech.
-
- src << "You drop words into [host]'s mind: \"[message]\""
- host << "Your own thoughts speak: \"[message]\""
-
- for(var/mob/M in mob_list)
- if(M.mind && (istype(M, /mob/dead/observer)))
- M << "Thought-speech, [truename] -> [host]: [copytext(message, 2)]"
-
-
/mob/living/simple_animal/borer/Stat()
..()
statpanel("Status")
diff --git a/code/modules/mob/living/simple_animal/parrot.dm b/code/modules/mob/living/simple_animal/parrot.dm
index 48fd6943658..a62f0640a54 100644
--- a/code/modules/mob/living/simple_animal/parrot.dm
+++ b/code/modules/mob/living/simple_animal/parrot.dm
@@ -48,6 +48,7 @@
response_disarm = "gently moves aside the"
response_harm = "swats the"
stop_automated_movement = 1
+ universal_speak = 1
var/parrot_state = PARROT_WANDER //Hunt for a perch when created
var/parrot_sleep_max = 25 //The time the parrot sits while perched before looking around. Mosly a way to avoid the parrot's AI in life() being run every single tick.
@@ -61,7 +62,7 @@
var/list/available_channels = list()
//Headset for Poly to yell at engineers :)
- var/obj/item/device/radio/headset/l_ear = null
+ var/obj/item/device/radio/headset/ears = null
//The thing the parrot is currently interested in. This gets used for items the parrot wants to pick up, mobs it wants to steal from,
//mobs it wants to attack or mobs that have attacked it
@@ -84,13 +85,13 @@
/mob/living/simple_animal/parrot/New()
..()
- if(!l_ear)
+ if(!ears)
var/headset = pick(/obj/item/device/radio/headset/headset_sec, \
/obj/item/device/radio/headset/headset_eng, \
/obj/item/device/radio/headset/headset_med, \
/obj/item/device/radio/headset/headset_sci, \
/obj/item/device/radio/headset/headset_cargo)
- l_ear = new headset(src)
+ ears = new headset(src)
parrot_sleep_dur = parrot_sleep_max //In case someone decides to change the max without changing the duration var
@@ -100,7 +101,7 @@
/mob/living/simple_animal/parrot/proc/perch_player)
-/mob/living/simple_animal/parrot/Die()
+/mob/living/simple_animal/parrot/death()
if(held_item)
held_item.loc = src.loc
held_item = null
@@ -119,10 +120,10 @@
if(user.stat) return
var/dat = "Inventory of [name]
"
- if(l_ear)
- dat += "
Headset: [l_ear] (Remove)"
+ if(ears)
+ dat += "
Headset: [ears] (Remove)"
else
- dat += "
Headset: Nothing"
+ dat += "
Headset: Nothing"
user << browse(dat, text("window=mob[];size=325x500", name))
onclose(user, "mob[real_name]")
@@ -134,21 +135,21 @@
if(!usr.canmove || usr.stat || usr.restrained() || !in_range(loc, usr))
return
- //Is the usr's mob type able to do this? (lolaliens)
- if(ishuman(usr) || ismonkey(usr) || isrobot(usr) || isalienadult(usr))
+ //Is the usr's mob type able to do this?
+ if(ishuman(usr) || ismonkey(usr) || isrobot(usr))
//Removing from inventory
if(href_list["remove_inv"])
var/remove_from = href_list["remove_inv"]
switch(remove_from)
- if("l_ear")
- if(l_ear)
+ if("ears")
+ if(ears)
if(available_channels.len)
src.say("[pick(available_channels)] BAWWWWWK LEAVE THE HEADSET BAWKKKKK!")
else
src.say("BAWWWWWK LEAVE THE HEADSET BAWKKKKK!")
- l_ear.loc = src.loc
- l_ear = null
+ ears.loc = src.loc
+ ears = null
for(var/possible_phrase in speak)
if(copytext(possible_phrase,1,3) in department_radio_keys)
possible_phrase = copytext(possible_phrase,3,length(possible_phrase))
@@ -163,8 +164,8 @@
usr << "\red You have nothing in your hand to put on its [add_to]."
return
switch(add_to)
- if("l_ear")
- if(l_ear)
+ if("ears")
+ if(ears)
usr << "\red It's already wearing something."
return
else
@@ -180,7 +181,7 @@
usr.drop_item()
headset_to_add.loc = src
- src.l_ear = headset_to_add
+ src.ears = headset_to_add
usr << "You fit the headset onto [src]."
clearlist(available_channels)
@@ -214,7 +215,7 @@
/mob/living/simple_animal/parrot/attack_hand(mob/living/carbon/M as mob)
..()
if(client) return
- if(!stat && M.a_intent == "harm")
+ if(!stat && M.a_intent == "hurt")
icon_state = "parrot_fly" //It is going to be flying regardless of whether it flees or attacks
@@ -231,25 +232,6 @@
drop_held_item(0)
return
-/mob/living/simple_animal/parrot/attack_paw(mob/living/carbon/monkey/M as mob)
- attack_hand(M)
-
-/mob/living/simple_animal/parrot/attack_alien(mob/living/carbon/monkey/M as mob)
- attack_hand(M)
-
-//Simple animals
-/mob/living/simple_animal/parrot/attack_animal(mob/living/simple_animal/M as mob)
- if(client) return
-
-
- if(parrot_state == PARROT_PERCH)
- parrot_sleep_dur = parrot_sleep_max //Reset it's sleep timer if it was perched
-
- if(M.melee_damage_upper > 0)
- parrot_interest = M
- parrot_state = PARROT_SWOOP | PARROT_ATTACK //Attack other animals regardless
- icon_state = "parrot_fly"
-
//Mobs with objects
/mob/living/simple_animal/parrot/attackby(var/obj/item/O as obj, var/mob/user as mob)
..()
@@ -335,7 +317,7 @@
if(speak.len)
var/list/newspeak = list()
- if(available_channels.len && src.l_ear)
+ if(available_channels.len && src.ears)
for(var/possible_phrase in speak)
//50/50 chance to not use the radio at all
@@ -496,11 +478,11 @@
var/datum/organ/external/affecting = H.get_organ(ran_zone(pick(parrot_dam_zone)))
H.apply_damage(damage, BRUTE, affecting, H.run_armor_check(affecting, "melee"), sharp=1)
- emote(pick("pecks [H]'s [affecting]", "cuts [H]'s [affecting] with its talons"))
+ emote(pick("pecks [H]'s [affecting].", "cuts [H]'s [affecting] with its talons."))
else
L.adjustBruteLoss(damage)
- emote(pick("pecks at [L]", "claws [L]"))
+ emote(pick("pecks at [L].", "claws [L]."))
return
//Otherwise, fly towards the mob!
@@ -700,6 +682,71 @@
speak = list("Poly wanna cracker!", ":e Check the singlo, you chucklefucks!",":e Wire the solars, you lazy bums!",":e WHO TOOK THE DAMN HARDSUITS?",":e OH GOD ITS FREE CALL THE SHUTTLE")
/mob/living/simple_animal/parrot/Poly/New()
- l_ear = new /obj/item/device/radio/headset/headset_eng(src)
+ ears = new /obj/item/device/radio/headset/headset_eng(src)
available_channels = list(":e")
..()
+
+/mob/living/simple_animal/parrot/say(var/message)
+
+ if(stat)
+ return
+
+ var/verb = "says"
+ if(speak_emote.len)
+ verb = pick(speak_emote)
+
+ var/message_mode = null
+ if(copytext(message,1,2) == ";")
+ message_mode = "headset"
+ message = copytext(message,2)
+
+ if(copytext(message,1,2) == ":")
+ var/channel_prefix = copytext(message, 1 ,3)
+ message_mode = department_radio_keys[channel_prefix]
+ var/positioncut = 3
+ message = trim(copytext(message,positioncut))
+
+ message = capitalize(trim_left(message))
+ if(message_mode)
+ if(message_mode in radiochannels)
+ if(ears && istype(ears,/obj/item/device/radio))
+ ears.talk_into(src, message, message_mode, verb, null)
+
+ ..(message)
+
+
+/mob/living/simple_animal/parrot/hear_say(var/message, var/verb = "says", var/datum/language/language = null, var/alt_name = "",var/italics = 0, var/mob/speaker = null)
+ if(prob(50))
+ parrot_hear(message)
+ ..(message,verb,language,alt_name,italics,speaker)
+
+
+
+/mob/living/simple_animal/parrot/hear_radio(var/message, var/verb="says", var/datum/language/language=null, var/part_a, var/part_b, var/mob/speaker = null, var/hard_to_hear = 0)
+ if(prob(50))
+ parrot_hear("[pick(available_channels)] [message]")
+ ..(message,verb,language,part_a,part_b,speaker,hard_to_hear)
+
+
+/mob/living/simple_animal/parrot/proc/parrot_hear(var/message="")
+ if(!message || stat)
+ return
+ speech_buffer.Add(message)
+
+/mob/living/simple_animal/parrot/attack_paw(var/mob/user, var/damage, var/attack_message)
+
+ var/success = ..()
+
+ if(client)
+ return success
+
+ if(parrot_state == PARROT_PERCH)
+ parrot_sleep_dur = parrot_sleep_max //Reset it's sleep timer if it was perched
+
+ if(!success)
+ return 0
+
+ parrot_interest = user
+ parrot_state = PARROT_SWOOP | PARROT_ATTACK //Attack other animals regardless
+ icon_state = "parrot_fly"
+ return success
diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm
index 800d9967ca7..6d0694c3073 100644
--- a/code/modules/mob/living/simple_animal/simple_animal.dm
+++ b/code/modules/mob/living/simple_animal/simple_animal.dm
@@ -269,6 +269,7 @@
for(var/mob/O in viewers(src, null))
if ((O.client && !( O.blinded )))
O.show_message("\blue [M] [response_help] [src].")
+ playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1)
if("grab")
if (M == src || anchored)
@@ -288,12 +289,14 @@
for(var/mob/O in viewers(src, null))
if ((O.client && !( O.blinded )))
O.show_message(text("\red [] has grabbed [] passively!", M, src), 1)
+ playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1)
if("harm", "disarm")
adjustBruteLoss(harm_intent_damage)
for(var/mob/O in viewers(src, null))
if ((O.client && !( O.blinded )))
O.show_message("\red [M] [response_harm] [src]!")
+ playsound(loc, "punch", 25, 1, -1)
return
@@ -537,3 +540,22 @@
if (S.occupant || S.occupant2)
return 0
return 1
+
+/mob/living/simple_animal/say(var/message)
+ if(stat)
+ return
+
+ if(copytext(message,1,2) == "*")
+ return emote(copytext(message,2))
+
+ if(stat)
+ return
+
+ var/verb = "says"
+
+ if(speak_emote.len)
+ verb = pick(speak_emote)
+
+ message = capitalize(trim_left(message))
+
+ ..(message, null, verb)
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 7b197aafcb2..a3d5932e10b 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -93,6 +93,7 @@
return 0
/mob/proc/Life()
+ handle_typing_indicator()
// if(organStructure)
// organStructure.ProcessOrgans()
return
diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm
index e23014f9f52..d3abbfc1776 100644
--- a/code/modules/mob/mob_helpers.dm
+++ b/code/modules/mob/mob_helpers.dm
@@ -446,3 +446,49 @@ var/list/intents = list("help","disarm","grab","harm")
/proc/get_both_hands(mob/living/carbon/M)
var/list/hands = list(M.l_hand, M.r_hand)
return hands
+
+
+//Direct dead say used both by emote and say
+//It is somewhat messy. I don't know what to do.
+//I know you can't see the change, but I rewrote the name code. It is significantly less messy now
+/proc/say_dead_direct(var/message, var/mob/subject = null)
+ var/name
+ var/keyname
+ if(subject && subject.client)
+ var/client/C = subject.client
+ keyname = (C.holder && C.holder.fakekey) ? C.holder.fakekey : C.key
+ if(C.mob) //Most of the time this is the dead/observer mob; we can totally use him if there is no better name
+ var/mindname
+ var/realname = C.mob.real_name
+ if(C.mob.mind)
+ mindname = C.mob.mind.name
+ if(C.mob.mind.original && C.mob.mind.original.real_name)
+ realname = C.mob.mind.original.real_name
+ if(mindname && mindname != realname)
+ name = "[realname] died as [mindname]"
+ else
+ name = realname
+
+ for(var/mob/M in player_list)
+ if(M.client && ((!istype(M, /mob/new_player) && M.stat == DEAD) || (M.client.holder && M.client.holder.rights == R_MOD)) && (M.client.prefs.toggles & CHAT_DEAD))
+ var/follow
+ var/lname
+ if(subject)
+ if(subject != M)
+ follow = "(follow) "
+ if(M.stat != DEAD && M.client.holder)
+ follow = "(JMP) "
+ var/mob/dead/observer/DM
+ if(istype(subject, /mob/dead/observer))
+ DM = subject
+ if(M.client.holder) // What admins see
+ lname = "[keyname][(DM && DM.anonsay) ? "*" : (DM ? "" : "^")] ([name])"
+ else
+ if(DM && DM.anonsay) // If the person is actually observer they have the option to be anonymous
+ lname = "Ghost of [name]"
+ else if(DM) // Non-anons
+ lname = "[keyname] ([name])"
+ else // Everyone else (dead people who didn't ghost yet, etc.)
+ lname = name
+ lname = "[lname] "
+ M << "[lname][follow][message]"
diff --git a/code/modules/mob/new_player/new_player.dm b/code/modules/mob/new_player/new_player.dm
index 6ecdfdc4e67..4810c0b10f7 100644
--- a/code/modules/mob/new_player/new_player.dm
+++ b/code/modules/mob/new_player/new_player.dm
@@ -444,46 +444,22 @@
proc/create_character()
spawning = 1
close_spawn_windows()
+
var/mob/living/carbon/human/new_character
+
var/datum/species/chosen_species
if(client.prefs.species)
chosen_species = all_species[client.prefs.species]
if(chosen_species)
// Have to recheck admin due to no usr at roundstart. Latejoins are fine though.
if(is_species_whitelisted(chosen_species) || has_admin_rights())
- switch(chosen_species.name)
- if("Slime People")
- new_character = new /mob/living/carbon/human/slime(loc)
- if("Tajaran")
- new_character = new /mob/living/carbon/human/tajaran(loc)
- if("Unathi")
- new_character = new /mob/living/carbon/human/unathi(loc)
- if("Skrell")
- new_character = new /mob/living/carbon/human/skrell(loc)
- if("Diona")
- new_character = new /mob/living/carbon/human/diona(loc)
- if("Vox")
- new_character = new /mob/living/carbon/human/vox(loc)
- if("Vox Armalis")
- new_character = new /mob/living/carbon/human/voxarmalis(loc)
- if("Kidan")
- new_character = new /mob/living/carbon/human/kidan(loc)
- if("Grey")
- new_character = new /mob/living/carbon/human/grey(loc)
- if("Machine")
- new_character = new /mob/living/carbon/human/machine(loc)
- if("Plasmaman")
- new_character = new /mob/living/carbon/human/plasma(loc)
- if("Human")
- new_character = new /mob/living/carbon/human/human(loc)
-// new_character.set_species(client.prefs.species)
- if(chosen_species.language)
- new_character.add_language(chosen_species.language)
- else
- new_character = new /mob/living/carbon/human(loc)
+ new_character = new(loc, client.prefs.species)
+
+ if(!new_character)
+ new_character = new(loc)
+
new_character.lastarea = get_area(loc)
-
var/datum/language/chosen_language
if(client.prefs.language)
chosen_language = all_languages[client.prefs.language]
@@ -529,6 +505,8 @@
if(client.prefs.disabilities & DISABILITY_FLAG_DEAF)
new_character.dna.SetSEState(DEAFBLOCK,1,1)
new_character.sdisabilities |= DEAF
+
+ chosen_species.handle_dna(new_character)
domutcheck(new_character)
new_character.dna.UpdateSE()
diff --git a/code/modules/mob/say.dm b/code/modules/mob/say.dm
index 41612819446..38328b16bb7 100644
--- a/code/modules/mob/say.dm
+++ b/code/modules/mob/say.dm
@@ -9,114 +9,97 @@
/mob/verb/say_verb(message as text)
set name = "Say"
set category = "IC"
+
+ //Let's try to make users fix their errors - we try to detect single, out-of-place letters and 'unintended' words
+ /*
+ var/first_letter = copytext(message,1,2)
+ if((copytext(message,2,3) == " " && first_letter != "I" && first_letter != "A" && first_letter != ";") || cmptext(copytext(message,1,5), "say ") || cmptext(copytext(message,1,4), "me ") || cmptext(copytext(message,1,6), "looc ") || cmptext(copytext(message,1,5), "ooc ") || cmptext(copytext(message,2,6), "say "))
+ var/response = alert(usr, "Do you really want to say this using the *say* verb?\n\n[message]\n", "Confirm your message", "Yes", "Edit message", "No")
+ if(response == "Edit message")
+ message = input(usr, "Please edit your message carefully:", "Edit message", message)
+ if(!message)
+ return
+ else if(response == "No")
+ return
+ */
+
+ set_typing_indicator(0)
usr.say(message)
/mob/verb/me_verb(message as text)
set name = "Me"
set category = "IC"
- message = trim(copytext(sanitize(message), 1, MAX_MESSAGE_LEN))
+ message = strip_html_properly(message)
+ set_typing_indicator(0)
if(use_me)
usr.emote("me",usr.emote_type,message)
else
usr.emote(message)
/mob/proc/say_dead(var/message)
- var/name = src.real_name
- var/alt_name = ""
-
-
if(!src.client.holder)
if(!dsay_allowed)
- src << "\red Deadchat is globally muted"
+ src << "Deadchat is globally muted."
return
if(client && !(client.prefs.toggles & CHAT_DEAD))
- usr << "\red You have deadchat muted."
+ usr << "You have deadchat muted."
return
- if(mind && mind.name)
- name = "[mind.name]"
- else
- name = real_name
- if(name != real_name)
- alt_name = " (died as [real_name])"
-
- message = src.say_quote(message)
- //var/rendered = "DEAD: [name][alt_name] [message]"
- var/rendered2 = null//edited
- for(var/mob/M in player_list)
- rendered2 = "DEAD: [name][alt_name] (Follow) [message]"//edited
- if(istype(M, /mob/new_player))
- continue
- if(M.client && M.client.holder && (M.client.holder.rights & R_ADMIN|R_MOD) && (M.client.prefs.toggles & CHAT_DEAD)) // Show the message to admins/mods with deadchat toggled on
- M << rendered2 //Admins can hear deadchat, if they choose to, no matter if they're blind/deaf or not.
-
- else if(M.client && M.stat == DEAD && (M.client.prefs.toggles & CHAT_DEAD)) // Show the message to regular ghosts with deadchat toggled on.
- M.show_message(rendered2, 2) //Takes into account blindness and such.
- return
+ say_dead_direct("[pick("complains","moans","whines","laments","blubbers")], \"[message]\"", src)
/mob/proc/say_understands(var/mob/other,var/datum/language/speaking = null)
- if(!other)
- return 1
- //Universal speak makes everything understandable, for obvious reasons.
- if(other.universal_speak || src.universal_speak || src.universal_understand)
- return 1
- if (src.stat == 2)
+ if (src.stat == 2) //Dead
return 1
- if(!speaking) //Handle languages later
- if(other.universal_speak || src.universal_speak)
+ //Universal speak makes everything understandable, for obvious reasons.
+ else if(src.universal_speak || src.universal_understand)
+ return 1
+
+ //Languages are handled after.
+ if (!speaking)
+ if(!other)
+ return 1
+ if(other.universal_speak)
return 1
if(isAI(src) && ispAI(other))
return 1
if (istype(other, src.type) || istype(src, other.type))
return 1
- if (istype(other, /mob/living/carbon/human) && (istype(src, /mob/living/carbon/human) || istype(src, /mob/living/silicon/pai)))
- return 1
- if(istype(other, /mob/living/carbon/alien) && istype(src, /mob/living/carbon/alien))
- return 1
return 0
- for(var/datum/language/L in src.languages) //Handling languages
+ if(speaking.flags & INNATE)
+ return 1
+
+ //Language check.
+ for(var/datum/language/L in src.languages)
if(speaking.name == L.name)
return 1
return 0
-/mob/proc/say_quote(var/text,var/datum/language/speaking)
+/*
+ ***Deprecated***
+ let this be handled at the hear_say or hear_radio proc
+ This is left in for robot speaking when humans gain binary channel access until I get around to rewriting
+ robot_talk() proc.
+ There is no language handling build into it however there is at the /mob level so we accept the call
+ for it but just ignore it.
+*/
- if(!text)
- return "says, \"...\""; //not the best solution, but it will stop a large number of runtimes. The cause is somewhere in the Tcomms code
- //tcomms code is still runtiming somewhere here
- var/ending = copytext(text, length(text))
+/mob/proc/say_quote(var/message, var/datum/language/speaking = null)
+ var/verb = "says"
+ var/ending = copytext(message, length(message))
+ if(ending=="!")
+ verb=pick("exclaims","shouts","yells")
+ else if(ending=="?")
+ verb="asks"
- var/speechverb = ""
+ return verb
- if (speaking)
- speechverb = "[speaking.speech_verb], \""
- else if(speak_emote && speak_emote.len)
- speechverb = "[pick(speak_emote)], \""
- else if (src.stuttering)
- speechverb = "stammers, \""
- else if (src.slurring)
- speechverb = "slurrs, \""
- else if (ending == "?")
- speechverb = "asks, \""
- else if (ending == "!")
- speechverb = "exclaims, \""
- else if(isliving(src))
- var/mob/living/L = src
- if (L.getBrainLoss() >= 60)
- speechverb = "gibbers, \""
- else
- speechverb = "says, \""
- else
- speechverb = "says, \""
-
- return "[speechverb][text]\""
/mob/proc/emote(var/act, var/type, var/message)
if(act == "me")
@@ -133,21 +116,34 @@
/mob/proc/say_test(var/text)
var/ending = copytext(text, length(text))
if (ending == "?")
- if(isalien(src))
- return "A1"
- if(isrobot(src))
- return "R1"
- else
- return "1"
+ return "1"
else if (ending == "!")
- if(isalien(src))
- return "A2"
- if(isrobot(src))
- return "R2"
- else
- return "2"
- else if(isalien(src))
- return "A0"
- else if(isrobot(src))
- return "R0"
+ return "2"
return "0"
+
+//parses the message mode code (e.g. :h, :w) from text, such as that supplied to say.
+//returns the message mode string or null for no message mode.
+//standard mode is the mode returned for the special ';' radio code.
+/mob/proc/parse_message_mode(var/message, var/standard_mode="headset")
+ if(length(message) >= 1 && copytext(message,1,2) == ";")
+ return standard_mode
+
+ if(length(message) >= 2)
+ var/channel_prefix = copytext(message, 1 ,3)
+ return department_radio_keys[channel_prefix]
+
+ return null
+
+//parses the language code (e.g. :j) from text, such as that supplied to say.
+//returns the language object only if the code corresponds to a language that src can speak, otherwise null.
+/mob/proc/parse_language(var/message)
+ if(length(message) >= 1 && copytext(message,1,2) == "!")
+ return all_languages["Noise"]
+
+ if(length(message) >= 2)
+ var/language_prefix = lowertext(copytext(message, 1 ,3))
+ var/datum/language/L = language_keys[language_prefix]
+ if (can_speak(L))
+ return L
+
+ return null
diff --git a/code/modules/mob/typing_indicator.dm b/code/modules/mob/typing_indicator.dm
new file mode 100644
index 00000000000..c104aa839a4
--- /dev/null
+++ b/code/modules/mob/typing_indicator.dm
@@ -0,0 +1,85 @@
+#define TYPING_INDICATOR_LIFETIME 30 * 10 //grace period after which typing indicator disappears regardless of text in chatbar
+
+mob/var/hud_typing = 0 //set when typing in an input window instead of chatline
+mob/var/typing
+mob/var/last_typed
+mob/var/last_typed_time
+
+var/global/image/typing_indicator
+
+/mob/proc/set_typing_indicator(var/state)
+
+ if(!typing_indicator)
+ typing_indicator = image('icons/mob/talk.dmi',null,"typing")
+
+ if(client)
+ if(client.prefs.toggles & SHOW_TYPING)
+ overlays -= typing_indicator
+ else
+ if(state)
+ if(!typing)
+ overlays += typing_indicator
+ typing = 1
+ else
+ if(typing)
+ overlays -= typing_indicator
+ typing = 0
+ return state
+
+/mob/verb/say_wrapper()
+ set name = ".Say"
+ set hidden = 1
+
+ set_typing_indicator(1)
+ hud_typing = 1
+ var/message = input("","say (text)") as text
+ hud_typing = 0
+ set_typing_indicator(0)
+ if(message)
+ say_verb(message)
+
+/mob/verb/me_wrapper()
+ set name = ".Me"
+ set hidden = 1
+
+ set_typing_indicator(1)
+ hud_typing = 1
+ var/message = input("","me (text)") as text
+ hud_typing = 0
+ set_typing_indicator(0)
+ if(message)
+ me_verb(message)
+
+/mob/proc/handle_typing_indicator()
+ if(client)
+ if(!(client.prefs.toggles & SHOW_TYPING) && !hud_typing)
+ var/temp = winget(client, "input", "text")
+
+ if (temp != last_typed)
+ last_typed = temp
+ last_typed_time = world.time
+
+ if (world.time > last_typed_time + TYPING_INDICATOR_LIFETIME)
+ set_typing_indicator(0)
+ return
+ if(length(temp) > 5 && findtext(temp, "Say \"", 1, 7))
+ set_typing_indicator(1)
+ else if(length(temp) > 3 && findtext(temp, "Me ", 1, 5))
+ set_typing_indicator(1)
+
+ else
+ set_typing_indicator(0)
+
+/client/verb/typing_indicator()
+ set name = "Show/Hide Typing Indicator"
+ set category = "Preferences"
+ set desc = "Toggles showing an indicator when you are typing emote or say message."
+ prefs.toggles ^= SHOW_TYPING
+ prefs.save_preferences(src)
+ src << "You will [(prefs.toggles & SHOW_TYPING) ? "no longer" : "now"] display a typing indicator."
+
+ // Clear out any existing typing indicator.
+ if(prefs.toggles & SHOW_TYPING)
+ if(istype(mob)) mob.set_typing_indicator(0)
+
+ feedback_add_details("admin_verb","TID") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
\ No newline at end of file
diff --git a/code/modules/paperwork/faxmachine.dm b/code/modules/paperwork/faxmachine.dm
index 98726004ea4..7beff751760 100644
--- a/code/modules/paperwork/faxmachine.dm
+++ b/code/modules/paperwork/faxmachine.dm
@@ -21,8 +21,6 @@ var/list/alldepartments = list()
var/department = "Unknown" // our department
var/destination = "Central Command" // the department we're sending to
-
- var/data[0]
/obj/machinery/photocopier/faxmachine/New()
..()
@@ -51,6 +49,7 @@ var/list/alldepartments = list()
user << "You swipe the card through [src], but nothing happens."
/obj/machinery/photocopier/faxmachine/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
+ var/data[0]
if(scan)
data["scan_name"] = scan.name
else
@@ -142,8 +141,7 @@ var/list/alldepartments = list()
else if(istype(copyitem, /obj/item/weapon/photo))
copyitem.name = "[(n_name ? text("[n_name]") : "photo")]"
else if(istype(copyitem, /obj/item/weapon/paper_bundle))
- copyitem.name = "[(n_name ? text("[n_name]") : "paper")]"
- data["name"] = copyitem.name
+ copyitem.name = "[(n_name ? text("[n_name]") : "paper")]"
nanomanager.update_uis(src)
diff --git a/code/modules/research/designs/machine_designs.dm b/code/modules/research/designs/machine_designs.dm
index 4567d00bfde..ab5653e0d1a 100644
--- a/code/modules/research/designs/machine_designs.dm
+++ b/code/modules/research/designs/machine_designs.dm
@@ -62,7 +62,7 @@
build_path = /obj/item/weapon/circuitboard/bodyscanner
category = list("Medical Machinery")
-/datum/design/sleep_console
+/datum/design/bodyscanner_console
name = "Machine Board (Body Scanner Console)"
desc = "Allows for the construction of circuit boards used to build a Body Scanner Console."
id = "bodyscanner_console"
diff --git a/code/modules/surgery/headreattach.dm b/code/modules/surgery/headreattach.dm
index 3c49a9e8c0a..c493607c52c 100644
--- a/code/modules/surgery/headreattach.dm
+++ b/code/modules/surgery/headreattach.dm
@@ -196,7 +196,7 @@
user.visible_message("\blue [user] has attached [target]'s head to the body.", \
"\blue You have attached [target]'s head to the body.")
affected.status = 0
- if(istype(target,/mob/living/carbon/human/machine))
+ if(target.species && (target.species.flags & IS_SYNTHETIC))
affected.status = 128
affected.amputated = 0
affected.destspawn = 0
diff --git a/code/setup.dm b/code/setup.dm
index ca1c664815a..c2981325856 100644
--- a/code/setup.dm
+++ b/code/setup.dm
@@ -711,11 +711,12 @@ var/list/TAGGERLOCATIONS = list("Disposals",
#define CHAT_GHOSTEARS 4
#define CHAT_GHOSTSIGHT 8
#define CHAT_PRAYER 16
-//#define CHAT_RADIO 32
+#define CHAT_RADIO 32
#define CHAT_ATTACKLOGS 64
#define CHAT_DEBUGLOGS 128
#define CHAT_LOOC 256
#define CHAT_GHOSTRADIO 512
+#define SHOW_TYPING 1024
#define SOUND_ADMINHELP 1
#define SOUND_MIDI 2
@@ -843,12 +844,15 @@ var/list/restricted_camera_networks = list( //Those networks can only be accesse
#define HAS_SKIN_TONE 16
#define HAS_SKIN_COLOR 32
-
//Language flags.
-#define WHITELISTED 1 // Language is available if the speaker is whitelisted.
-#define RESTRICTED 2 // Language can only be accquired by spawning or an admin.
-#define NONVERBAL 4 // Language has a significant non-verbal component. Speech is garbled without line-of-sight
-#define SIGNLANG 8 // Language is completely non-verbal. Speech is displayed through emotes for those who can understand.
+#define WHITELISTED 1 // Language is available if the speaker is whitelisted.
+#define RESTRICTED 2 // Language can only be accquired by spawning or an admin.
+#define NONVERBAL 4 // Language has a significant non-verbal component. Speech is garbled without line-of-sight
+#define SIGNLANG 8 // Language is completely non-verbal. Speech is displayed through emotes for those who can understand.
+#define HIVEMIND 16 // Broadcast to all mobs with this language.
+#define NONGLOBAL 32 // Do not add to general languages list
+#define INNATE 64 // All mobs can be assumed to speak and understand this language (audible emotes)
+#define NO_TALK_MSG 128 // Do not show the "\The [speaker] talks into \the [radio]" message
//Flags for zone sleeping
#define ZONE_ACTIVE 1
diff --git a/code/stylesheet.dm b/code/stylesheet.dm
index 441ffc17000..b7111e1e5a1 100644
--- a/code/stylesheet.dm
+++ b/code/stylesheet.dm
@@ -67,9 +67,18 @@ h1.alert, h2.alert {color: #000000;}
.noticealien {color: #00c000;}
.alertalien {color: #00c000; font-weight: bold;}
.tajaran {color: #803B56;}
+.tajaran_signlang {color: #941C1C;}
.skrell {color: #00CED1;}
.soghun {color: #228B22;}
+.solcom {color: #22228B;}
+.changeling {color: #800080;}
.vox {color: #AA00AA;}
+.diona {color: #007200;}
+.trinary {color: #727272;}
+.kidan {color: #664205;}
+.slime {color: #0077AA;}
+.rough {font-family: "Trebuchet MS", cursive, sans-serif;}
+.say_quote {font-family: Georgia, Verdana, sans-serif;}
.say_quote {font-family: Georgia, Verdana, sans-serif;}
diff --git a/config/example/config.txt b/config/example/config.txt
index a26aa4575d6..454dcca2d0c 100644
--- a/config/example/config.txt
+++ b/config/example/config.txt
@@ -256,7 +256,7 @@ ADMIN_LEVELS 2
CONTACT_LEVELS 1;5
## Defines all Z-levels a character can typically reach
-PLAYER_LEVELS 1;3;4;5;6
+PLAYER_LEVELS 1;3;4;5;6;7
## Expected round length in minutes
EXPECTED_ROUND_LENGTH 120
diff --git a/icons/mob/human_races/r_golem.dmi b/icons/mob/human_races/r_golem.dmi
new file mode 100644
index 00000000000..f9cfb8e157c
Binary files /dev/null and b/icons/mob/human_races/r_golem.dmi differ
diff --git a/icons/mob/human_races/r_shadow.dmi b/icons/mob/human_races/r_shadow.dmi
new file mode 100644
index 00000000000..70450af6902
Binary files /dev/null and b/icons/mob/human_races/r_shadow.dmi differ
diff --git a/icons/mob/human_races/r_slime.dmi b/icons/mob/human_races/r_slime.dmi
new file mode 100644
index 00000000000..287779d3c7b
Binary files /dev/null and b/icons/mob/human_races/r_slime.dmi differ
diff --git a/icons/mob/talk.dmi b/icons/mob/talk.dmi
index 2cd0b40bb18..86534d162d0 100644
Binary files a/icons/mob/talk.dmi and b/icons/mob/talk.dmi differ
diff --git a/icons/obj/robot_storage.dmi b/icons/obj/robot_storage.dmi
index 5a996c01e7e..2382586eee7 100644
Binary files a/icons/obj/robot_storage.dmi and b/icons/obj/robot_storage.dmi differ
diff --git a/interface/interface.dm b/interface/interface.dm
index e4d6a082d8e..4362cad34d2 100644
--- a/interface/interface.dm
+++ b/interface/interface.dm
@@ -124,7 +124,7 @@ Admin:
//adv. hotkey mode verbs, vars located in /code/modules/client/client defines.dm
/client/verb/hotkey_toggle()//toggles hotkey mode between on and off, respects selected type
- set name = ".Toggle hotkey mode"
+ set name = ".Toggle Hotkey Mode"
hotkeyon = !hotkeyon//toggle the var
diff --git a/interface/skin.dmf b/interface/skin.dmf
index 96ab4aea9bc..52f9a07309c 100644
--- a/interface/skin.dmf
+++ b/interface/skin.dmf
@@ -149,11 +149,11 @@ macro "AZERTYoff"
is-disabled = false
elem
name = "F3"
- command = "say"
+ command = ".say"
is-disabled = false
elem
name = "F4"
- command = "me"
+ command = ".me"
is-disabled = false
elem
name = "F5"
@@ -347,7 +347,7 @@ macro "AZERTYon"
is-disabled = false
elem
name = "T"
- command = "say"
+ command = ".say"
is-disabled = false
elem
name = "W"
@@ -403,11 +403,11 @@ macro "AZERTYon"
is-disabled = false
elem
name = "F3"
- command = "say"
+ command = ".say"
is-disabled = false
elem
name = "F4"
- command = "me"
+ command = ".me"
is-disabled = false
elem
name = "F5"
@@ -705,6 +705,10 @@ macro "hotkeymode"
name = "CTRL+4"
command = "a-intent harm"
is-disabled = false
+ elem
+ name = "5"
+ command = ".me"
+ is-disabled = false
elem
name = "A+REP"
command = ".west"
diff --git a/maps/cyberiad.dmm b/maps/cyberiad.dmm
index 75a770d8900..77c8ce078cd 100644
--- a/maps/cyberiad.dmm
+++ b/maps/cyberiad.dmm
@@ -10987,7 +10987,7 @@
"edo" = (/obj/machinery/door/airlock/external{frequency = 1379; icon_state = "door_locked"; id_tag = "labor_camp_north_outer"; locked = 1; name = "Labor Camp Airlock"; opacity = 1; req_access_txt = "2"},/turf/simulated/floor{icon_state = "floorgrime"; temperature = 273.15},/area/mine/laborcamp)
"edp" = (/obj/machinery/light/small{dir = 1},/obj/machinery/airlock_sensor{frequency = 1379; id_tag = "labor_camp_north_sensor"; pixel_x = -8; pixel_y = 30},/obj/machinery/atmospherics/unary/vent_pump/high_volume{dir = 4; frequency = 1379; id_tag = "labor_camp_north_pump"},/obj/machinery/door_control{id = "Labor"; name = "Labor Camp Lockdown"; pixel_x = 0; pixel_y = -28; req_access_txt = "2"},/obj/machinery/embedded_controller/radio/airlock/docking_port_multi{id_tag = "labor_camp_north_airlock"; master_tag = "labor_camp_dock"; pixel_y = 30; req_access_txt = "2"; req_one_access_txt = "0"; tag_airpump = "labor_camp_north_pump"; tag_chamber_sensor = "labor_camp_north_sensor"; tag_exterior_door = "labor_camp_north_outer"; tag_interior_door = "labor_camp_north_inner"},/turf/simulated/floor{icon_state = "floorgrime"; temperature = 273.15},/area/mine/laborcamp)
"edq" = (/obj/machinery/door/airlock/external{frequency = 1379; icon_state = "door_locked"; id_tag = "labor_camp_north_inner"; locked = 1; name = "Labor Camp Airlock"; opacity = 1; req_access_txt = "2"},/obj/machinery/atmospherics/pipe/simple/hidden{dir = 4},/turf/simulated/floor{icon_state = "floorgrime"; temperature = 273.15},/area/mine/laborcamp)
-"edr" = (/obj/machinery/door/poddoor/preopen{id = "Labor"; name = "labor camp blast door"},/obj/machinery/access_button{command = "cycle_interior"; frequency = 1379; master_tag = "labor_camp_north_airlock"; name = "interior access button"; pixel_x = -25; pixel_y = 25; req_access_txt = "2"},/obj/machinery/atmospherics/pipe/simple/hidden{dir = 10; initialize_directions = 10},/turf/simulated/floor{icon_state = "floorgrime"; temperature = 273.15},/area/mine/laborcamp)
+"edr" = (/obj/machinery/door/poddoor/preopen{id = "Labor"; name = "labor camp blast door"},/obj/machinery/access_button{command = "cycle_interior"; frequency = 1379; master_tag = "labor_camp_north_airlock"; name = "interior access button"; pixel_x = -25; pixel_y = 25; req_access_txt = "2"},/obj/machinery/atmospherics/pipe/simple/hidden{dir = 10; initialize_directions = 10},/obj/machinery/door/airlock/glass_security{name = "Labor Camp Shuttle Cockpit"; req_access_txt = "2"},/turf/simulated/floor{icon_state = "floorgrime"; temperature = 273.15},/area/mine/laborcamp)
"eds" = (/turf/simulated/floor,/area/mine/laborcamp)
"edt" = (/obj/machinery/mineral/processing_unit_console{machinedir = 6},/turf/simulated/wall,/area/mine/laborcamp)
"edu" = (/obj/structure/plasticflaps,/obj/machinery/conveyor{dir = 2; id = "gulag"},/obj/machinery/mineral/input,/turf/simulated/floor{icon_state = "floorgrime"; temperature = 273.15},/area/mine/laborcamp)
diff --git a/nano/images/uiBackground-Syndicate.png b/nano/images/uiBackground-Syndicate.png
index 241de891ece..3ca932cb835 100644
Binary files a/nano/images/uiBackground-Syndicate.png and b/nano/images/uiBackground-Syndicate.png differ
diff --git a/nano/images/uiTitleFluff-Syndicate.png b/nano/images/uiTitleFluff-Syndicate.png
index b2a32013f86..b09f5ba6b29 100644
Binary files a/nano/images/uiTitleFluff-Syndicate.png and b/nano/images/uiTitleFluff-Syndicate.png differ
diff --git a/paradise.dme b/paradise.dme
index fa33cb1324b..3b01e089723 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -239,6 +239,7 @@
#include "code\defines\procs\AStar.dm"
#include "code\defines\procs\dbcore.dm"
#include "code\defines\procs\hud.dm"
+#include "code\defines\procs\radio.dm"
#include "code\defines\procs\sd_Alert.dm"
#include "code\defines\procs\statistics.dm"
#include "code\game\asteroid.dm"
@@ -959,6 +960,7 @@
#include "code\modules\clothing\masks\breath.dm"
#include "code\modules\clothing\masks\gasmask.dm"
#include "code\modules\clothing\masks\miscellaneous.dm"
+#include "code\modules\clothing\masks\voice.dm"
#include "code\modules\clothing\shoes\colour.dm"
#include "code\modules\clothing\shoes\magboots.dm"
#include "code\modules\clothing\shoes\miscellaneous.dm"
@@ -1159,6 +1161,7 @@
#include "code\modules\mob\abilities.dm"
#include "code\modules\mob\death.dm"
#include "code\modules\mob\emote.dm"
+#include "code\modules\mob\hear_say.dm"
#include "code\modules\mob\holder.dm"
#include "code\modules\mob\inventory.dm"
#include "code\modules\mob\language.dm"
@@ -1172,6 +1175,7 @@
#include "code\modules\mob\mob_transformation_simple.dm"
#include "code\modules\mob\say.dm"
#include "code\modules\mob\transform_procs.dm"
+#include "code\modules\mob\typing_indicator.dm"
#include "code\modules\mob\update_icons.dm"
#include "code\modules\mob\camera\camera.dm"
#include "code\modules\mob\dead\death.dm"