diff --git a/baystation12.dme b/baystation12.dme
index 9953cacd6e1..beed9a343a4 100644
--- a/baystation12.dme
+++ b/baystation12.dme
@@ -133,6 +133,13 @@
#define FILE_DIR "code/modules/projectiles/projectile"
#define FILE_DIR "code/modules/recycling"
#define FILE_DIR "code/modules/research"
+#define FILE_DIR "code/modules/scripting"
+#define FILE_DIR "code/modules/scripting/AST"
+#define FILE_DIR "code/modules/scripting/AST/Operators"
+#define FILE_DIR "code/modules/scripting/Implementations"
+#define FILE_DIR "code/modules/scripting/Interpreter"
+#define FILE_DIR "code/modules/scripting/Parser"
+#define FILE_DIR "code/modules/scripting/Scanner"
#define FILE_DIR "code/modules/security levels"
#define FILE_DIR "code/unused"
#define FILE_DIR "code/unused/beast"
@@ -549,6 +556,7 @@
#include "code\game\machinery\telecomms\machine_interactions.dm"
#include "code\game\machinery\telecomms\telecommunications.dm"
#include "code\game\machinery\telecomms\telemonitor.dm"
+#include "code\game\machinery\telecomms\traffic_control.dm"
#include "code\game\magic\archived_book.dm"
#include "code\game\magic\library.dm"
#include "code\game\magic\musician.dm"
@@ -702,6 +710,7 @@
#include "code\game\objects\items\weapons\implants\implantpad.dm"
#include "code\game\objects\radio\beacon.dm"
#include "code\game\objects\radio\electropack.dm"
+#include "code\game\objects\radio\encryptionkey.dm"
#include "code\game\objects\radio\headset.dm"
#include "code\game\objects\radio\intercom.dm"
#include "code\game\objects\radio\radio.dm"
@@ -1026,6 +1035,26 @@
#include "code\modules\research\rdmachines.dm"
#include "code\modules\research\research.dm"
#include "code\modules\research\server.dm"
+#include "code\modules\scripting\Errors.dm"
+#include "code\modules\scripting\IDE.dm"
+#include "code\modules\scripting\Options.dm"
+#include "code\modules\scripting\stack.dm"
+#include "code\modules\scripting\AST\AST Nodes.dm"
+#include "code\modules\scripting\AST\Blocks.dm"
+#include "code\modules\scripting\AST\Statements.dm"
+#include "code\modules\scripting\AST\Operators\Binary Operators.dm"
+#include "code\modules\scripting\AST\Operators\Unary Operators.dm"
+#include "code\modules\scripting\Implementations\_Logic.dm"
+#include "code\modules\scripting\Implementations\Telecomms.dm"
+#include "code\modules\scripting\Interpreter\Evaluation.dm"
+#include "code\modules\scripting\Interpreter\Interaction.dm"
+#include "code\modules\scripting\Interpreter\Interpreter.dm"
+#include "code\modules\scripting\Interpreter\Scope.dm"
+#include "code\modules\scripting\Parser\Expressions.dm"
+#include "code\modules\scripting\Parser\Keywords.dm"
+#include "code\modules\scripting\Parser\Parser.dm"
+#include "code\modules\scripting\Scanner\Scanner.dm"
+#include "code\modules\scripting\Scanner\Tokens.dm"
#include "code\modules\security levels\keycard authentication.dm"
#include "code\modules\security levels\security levels.dm"
#include "code\WorkInProgress\AI_Visibility.dm"
diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm
index df605dd7d09..d6fce9d1d9a 100644
--- a/code/game/gamemodes/game_mode.dm
+++ b/code/game/gamemodes/game_mode.dm
@@ -45,6 +45,8 @@ Stealth and Camouflage Items;
/obj/item/clothing/mask/gas/voice:4:Voice Changer;
/obj/item/clothing/glasses/thermal:4:Thermal Imaging Glasses;
/obj/item/device/chameleon:4:Chameleon-Projector;
+/obj/item/device/encryptionkey/traitor:3:Traitor Radio Key;
+/obj/item/device/encryptionkey/binary:3:Binary Translator Key;
/obj/item/weapon/stamperaser:1:Stamp Remover;
Whitespace:Seperator;
Devices and Tools;
diff --git a/code/game/gamemodes/nuclear/nuclear.dm b/code/game/gamemodes/nuclear/nuclear.dm
index e058b359845..2eb032eacaf 100644
--- a/code/game/gamemodes/nuclear/nuclear.dm
+++ b/code/game/gamemodes/nuclear/nuclear.dm
@@ -218,10 +218,7 @@
return tempfreq
/datum/game_mode/proc/equip_syndicate(mob/living/carbon/human/synd_mob,radio_freq)
- var/obj/item/device/radio/R = new /obj/item/device/radio/headset(synd_mob)
- R.freerange = 1
- R.listening = 0
- R.config(list("Nuclear" = 1))
+ var/obj/item/device/radio/R = new /obj/item/device/radio/headset/nuclear(synd_mob)
synd_mob.equip_if_possible(R, synd_mob.slot_ears)
synd_mob.equip_if_possible(new /obj/item/clothing/under/syndicate(synd_mob), synd_mob.slot_w_uniform)
synd_mob.equip_if_possible(new /obj/item/clothing/shoes/black(synd_mob), synd_mob.slot_shoes)
diff --git a/code/game/gamemodes/nuclear/nuclearbomb.dm b/code/game/gamemodes/nuclear/nuclearbomb.dm
index dcc30eceecc..7a60d10ecd1 100644
--- a/code/game/gamemodes/nuclear/nuclearbomb.dm
+++ b/code/game/gamemodes/nuclear/nuclearbomb.dm
@@ -46,12 +46,12 @@
if (src.yes_code)
dat += text("\nStatus: []-[]
\nTimer: []
\n
\nTimer: [] Toggle
\nTime: --- [] +++
\n
\nSafety: [] Toggle
\nAnchor: [] Toggle
\n", (src.timing ? "Func/Set" : "Functional"), (src.safety ? "Safe" : "Engaged"), time_left, (src.timing ? "On" : "Off"), src, src, src, src, time_left, src, src, src, (src.safety ? "On" : "Off"), src, (src.anchored ? "Engaged" : "Off"), src)
else
- dat += text("\nStatus: Auth. S2-[]
\nTimer: []
\n
\nTimer: [] Toggle
\nTime: - - [] + +
\n
\n[] Safety: Toggle
\nAnchor: [] Toggle
\n", (src.safety ? "Safe" : "Engaged"), time_left, (src.timing ? "On" : "Off"), time_left, (src.safety ? "On" : "Off"), (src.anchored ? "Engaged" : "Off"))
+ dat += text("\nStatus: Auth. S2-[]
\nTimer: []
\n
\nTimer: [] Toggle
\nTime: --- [] +++
\n
\n[] Safety: Toggle
\nAnchor: [] Toggle
\n", (src.safety ? "Safe" : "Engaged"), time_left, (src.timing ? "On" : "Off"), time_left, (src.safety ? "On" : "Off"), (src.anchored ? "Engaged" : "Off"))
else
if (src.timing)
- dat += text("\nStatus: Set-[]
\nTimer: []
\n
\nTimer: [] Toggle
\nTime: - - [] + +
\n
\nSafety: [] Toggle
\nAnchor: [] Toggle
\n", (src.safety ? "Safe" : "Engaged"), time_left, (src.timing ? "On" : "Off"), time_left, (src.safety ? "On" : "Off"), (src.anchored ? "Engaged" : "Off"))
+ dat += text("\nStatus: Set-[]
\nTimer: []
\n
\nTimer: [] Toggle
\nTime: --- [] +++
\n
\nSafety: [] Toggle
\nAnchor: [] Toggle
\n", (src.safety ? "Safe" : "Engaged"), time_left, (src.timing ? "On" : "Off"), time_left, (src.safety ? "On" : "Off"), (src.anchored ? "Engaged" : "Off"))
else
- dat += text("\nStatus: Auth. S1-[]
\nTimer: []
\n
\nTimer: [] Toggle
\nTime: - - [] + +
\n
\nSafety: [] Toggle
\nAnchor: [] Toggle
\n", (src.safety ? "Safe" : "Engaged"), time_left, (src.timing ? "On" : "Off"), time_left, (src.safety ? "On" : "Off"), (src.anchored ? "Engaged" : "Off"))
+ dat += text("\nStatus: Auth. S1-[]
\nTimer: []
\n
\nTimer: [] Toggle
\nTime: --- [] +++
\n
\nSafety: [] Toggle
\nAnchor: [] Toggle
\n", (src.safety ? "Safe" : "Engaged"), time_left, (src.timing ? "On" : "Off"), time_left, (src.safety ? "On" : "Off"), (src.anchored ? "Engaged" : "Off"))
var/message = "AUTH"
if (src.auth)
message = text("[]", src.code)
diff --git a/code/game/gamemodes/traitor/traitor.dm b/code/game/gamemodes/traitor/traitor.dm
index dca6d7b4b41..0a0b60d2367 100644
--- a/code/game/gamemodes/traitor/traitor.dm
+++ b/code/game/gamemodes/traitor/traitor.dm
@@ -5,7 +5,7 @@
/datum/game_mode/traitor
name = "traitor"
config_tag = "traitor"
- restricted_jobs = list("Cyborg")//Approved by headmins for a week test, if you see this it would be nice if you didn't spread it everywhere
+ restricted_jobs = list("Cyborg", "AI")//Approved by headmins for a week test, if you see this it would be nice if you didn't spread it everywhere
required_players = 0
required_enemies = 1
diff --git a/code/game/machinery/telecomms/broadcaster.dm b/code/game/machinery/telecomms/broadcaster.dm
index c3ac2943d8c..0a923bf1108 100644
--- a/code/game/machinery/telecomms/broadcaster.dm
+++ b/code/game/machinery/telecomms/broadcaster.dm
@@ -8,6 +8,7 @@
var
list/recentmessages = list() // global list of recent messages broadcasted : used to circumvent massive radio spam
+
/obj/machinery/telecomms/broadcaster
name = "Subspace Broadcaster"
icon = 'stationobjs.dmi'
@@ -21,16 +22,21 @@ var
heatgen = 60
delay = 7
circuitboard = "/obj/item/weapon/circuitboard/telecomms/broadcaster"
+
receive_information(datum/signal/signal, obj/machinery/telecomms/machine_from)
+ // Don't broadcast rejected signals
+ if(signal.data["reject"])
+ return
if(signal.data["message"])
- // Kind of lame way to prevent MASSIVE RADIO SPAM but it works
+ // Prevents massive radio spam
if("[signal.data["message"]]:[signal.data["realname"]]" in recentmessages)
return
recentmessages.Add( "[signal.data["message"]]:[signal.data["realname"]]" )
+
signal.data["done"] = 1 // mark the signal as being broadcasted
// Search for the original signal and mark it as done as well
@@ -41,19 +47,48 @@ var
if(signal.data["slow"] > 0)
sleep(signal.data["slow"]) // simulate the network lag if necessary
- /* ###### Broadcast a message using signal.data ###### */
- Broadcast_Message(signal.data["connection"], signal.data["mob"],
- signal.data["vmask"], signal.data["vmessage"],
- signal.data["radio"], signal.data["message"],
- signal.data["name"], signal.data["job"],
- signal.data["realname"], signal.data["vname"],, signal.data["compression"])
- /* --- Do a snazzy animation! --- */
- flick("broadcaster_send", src)
+ /** #### - Normal Broadcast - #### **/
+
+ if(signal.data["type"] == 0)
+
+ /* ###### Broadcast a message using signal.data ###### */
+ Broadcast_Message(signal.data["connection"], signal.data["mob"],
+ signal.data["vmask"], signal.data["vmessage"],
+ signal.data["radio"], signal.data["message"],
+ signal.data["name"], signal.data["job"],
+ signal.data["realname"], signal.data["vname"],, signal.data["compression"])
+
+
+ /** #### - Simple Broadcast - #### **/
+
+ if(signal.data["type"] == 1)
+
+ /* ###### Broadcast a message using signal.data ###### */
+ Broadcast_SimpleMessage(signal.data["name"], signal.frequency,
+ signal.data["message"],null, null,
+ signal.data["compression"])
+
+
+ /** #### - Artificial Broadcast - #### **/
+ // (Imitates a mob)
+
+ if(signal.data["type"] == 2)
+
+ /* ###### Broadcast a message using signal.data ###### */
+ // Parameter "data" as 4: AI can't track this person/mob
+
+ Broadcast_Message(signal.data["connection"], signal.data["mob"],
+ signal.data["vmask"], signal.data["vmessage"],
+ signal.data["radio"], signal.data["message"],
+ signal.data["name"], signal.data["job"],
+ signal.data["realname"], signal.data["vname"], 4, signal.data["compression"])
spawn(1)
recentmessages = list()
+ /* --- Do a snazzy animation! --- */
+ flick("broadcaster_send", src)
/*
Basically just an empty shell for receiving and broadcasting radio messages. Not
@@ -72,8 +107,8 @@ var
machinetype = 6
heatgen = 0
var/intercept = 0 // if nonzero, broadcasts all messages to syndicate channel
- var/frequency = 1439
var/nuke = 0
+ var/frequency = 1439
receive_signal(datum/signal/signal)
@@ -169,6 +204,7 @@ var
1 -- Will only broadcast to intercoms
2 -- Will only broadcast to intercoms and station-bounced radios
3 -- Broadcast to syndicate frequency
+ 4 -- AI can't track down this person. Useful for imitation broadcasts where you can't find the actual mob
@param compression:
If 0, the signal is audible
@@ -211,22 +247,21 @@ var
// --- Broadcast to syndicate radio! ---
else if(data == 3)
- var/datum/radio_frequency/syndicateconnection = radio_controller.return_frequency(NUKE_FREQ)
+ var/datum/radio_frequency/syndicateconnection = radio_controller.return_frequency(SYND_FREQ)
for (var/obj/item/device/radio/R in syndicateconnection.devices["[RADIO_CHAT]"])
- receive |= R.send_hear(NUKE_FREQ)
+ receive |= R.send_hear(SYND_FREQ)
// --- Broadcast to response team radio! ---
- else if(data == 4)
+ else if(data == 5)
var/datum/radio_frequency/interceptconnection = radio_controller.return_frequency(intercept_frequency)
for (var/obj/item/device/radio/R in interceptconnection.devices["[RADIO_CHAT]"])
receive |= R.send_hear(intercept_frequency)
-
// --- Broadcast to ALL radio devices ---
else
@@ -281,7 +316,6 @@ var
// - Just display a garbled message -
else
heard_garbled += R
-
for(var/obj/item/weapon/implant/imp in R)
imp.hear(message,M)
@@ -312,8 +346,6 @@ var
freq_text = "Mining"
if(1347)
freq_text = "Cargo"
- if(1439)
- freq_text = "Response Team"
if(connection.frequency == NUKE_FREQ)
freq_text = "Agent"
@@ -335,12 +367,12 @@ var
if (display_freq==SYND_FREQ)
part_a = ""
- else if (display_freq==NUKE_FREQ)
- part_a = ""
else if (display_freq==COMM_FREQ)
part_a = ""
else if (display_freq in DEPT_FREQS)
part_a = ""
+ else if (display_freq==NUKE_FREQ)
+ part_a = ""
// --- Filter the message; place it in quotes apply a verb ---
@@ -384,6 +416,8 @@ var
//End of research and feedback code.
+ var/aitrack = ""
+
/* ###### Send the message ###### */
@@ -394,19 +428,27 @@ var
var/J = job
var/rendered = "[part_a][N][part_b][quotedmsg][part_c]"
for (var/mob/R in heard_masked)
+ aitrack = ""
+ if(data == 4)
+ aitrack = ""
+
if(istype(R, /mob/living/silicon/ai) && job != "Automated Announcement")
- R.show_message("[part_a][N] ([J]) [part_b][quotedmsg][part_c]", 2)
+ R.show_message("[part_a][aitrack][N] ([J]) [part_b][quotedmsg][part_c]", 2)
else
R.show_message(rendered, 2)
/* --- Process all the mobs that heard the voice normally (understood) --- */
if (length(heard_normal))
- var/rendered = "[part_a][M.real_name][part_b][quotedmsg][part_c]"
+ var/rendered = "[part_a][realname][part_b][quotedmsg][part_c]"
for (var/mob/R in heard_normal)
+ aitrack = ""
+ if(data == 4)
+ aitrack = ""
+
if(istype(R, /mob/living/silicon/ai) && job != "Automated Announcement")
- R.show_message("[part_a][realname] ([job]) [part_b][quotedmsg][part_c]", 2)
+ R.show_message("[part_a][aitrack][realname] ([job]) [part_b][quotedmsg][part_c]", 2)
else
R.show_message(rendered, 2)
@@ -414,13 +456,16 @@ var
// Does not display message; displayes the mob's voice_message (ie "chimpers")
if (length(heard_voice))
- var/rendered = ""
- if(M)
- rendered = "[part_a][vname][part_b][M.voice_message][part_c]"
+ var/rendered = "[part_a][vname][part_b][M.voice_message][part_c]"
for (var/mob/R in heard_voice)
+ aitrack = ""
+ if(data == 4)
+ aitrack = ""
+
+
if(istype(R, /mob/living/silicon/ai) && job != "Automated Announcement")
- R.show_message("[part_a][vname] ([job]) [part_b][vmessage]][part_c]", 2)
+ R.show_message("[part_a][aitrack][vname] ([job]) [part_b][vmessage]][part_c]", 2)
else
R.show_message(rendered, 2)
@@ -428,15 +473,17 @@ var
// Displays garbled message (ie "f*c* **u, **i*er!")
if (length(heard_garbled))
- if(M)
- quotedmsg = M.say_quote(stars(message))
- else
- quotedmsg = "\"" + stars(message) + "\""
+ quotedmsg = M.say_quote(stars(message))
var/rendered = "[part_a][vname][part_b][quotedmsg][part_c]"
for (var/mob/R in heard_garbled)
+ aitrack = ""
+ if(data == 4)
+ aitrack = ""
+
+
if(istype(R, /mob/living/silicon/ai) && job != "Automated Announcement")
- R.show_message("[part_a][vname][part_b][quotedmsg][part_c]", 2)
+ R.show_message("[part_a][aitrack][vname][part_b][quotedmsg][part_c]", 2)
else
R.show_message(rendered, 2)
@@ -444,16 +491,239 @@ var
/* --- Complete gibberish. Usually happens when there's a compressed message --- */
if (length(heard_gibberish))
- if(M)
- quotedmsg = M.say_quote(Gibberish(message, compression + 50))
- else
- quotedmsg = "\"" + Gibberish(message, compression + 50) + "\""
+ quotedmsg = M.say_quote(Gibberish(message, compression + 50))
var/rendered = "[part_a][Gibberish(M.real_name, compression + 50)][part_b][quotedmsg][part_c]"
for (var/mob/R in heard_gibberish)
- if(istype(R, /mob/living/silicon/ai))
- R.show_message("[part_a][Gibberish(realname, compression + 50)] ([Gibberish(job, compression + 50)]) [part_b][quotedmsg][part_c]", 2)
+ aitrack = ""
+ if(data == 4)
+ aitrack = ""
+
+
+ if(istype(R, /mob/living/silicon/ai) && job != "Automated Announcement")
+ R.show_message("[part_a][aitrack][Gibberish(realname, compression + 50)] ([Gibberish(job, compression + 50)]) [part_b][quotedmsg][part_c]", 2)
else
R.show_message(rendered, 2)
+
+/proc/Broadcast_SimpleMessage(var/source, var/frequency, var/text, var/data, var/mob/M, var/compression)
+
+
+ /* ###### Prepare the radio connection ###### */
+
+ if(!M)
+ var/mob/living/carbon/human/H = new
+ M = H
+
+ var/datum/radio_frequency/connection = radio_controller.return_frequency(frequency)
+
+ var/display_freq = connection.frequency
+
+ var/list/receive = list()
+
+
+ // --- Broadcast only to intercom devices ---
+
+ if(data == 1)
+ for (var/obj/item/device/radio/intercom/R in connection.devices["[RADIO_CHAT]"])
+
+ receive |= R.send_hear(display_freq)
+
+
+ // --- Broadcast only to intercoms and station-bounced radios ---
+
+ else if(data == 2)
+ for (var/obj/item/device/radio/R in connection.devices["[RADIO_CHAT]"])
+
+ if(istype(R, /obj/item/device/radio/headset))
+ continue
+
+ receive |= R.send_hear(display_freq)
+
+
+ // --- Broadcast to syndicate radio! ---
+
+ else if(data == 3)
+ var/datum/radio_frequency/syndicateconnection = radio_controller.return_frequency(SYND_FREQ)
+
+ for (var/obj/item/device/radio/R in syndicateconnection.devices["[RADIO_CHAT]"])
+
+ receive |= R.send_hear(SYND_FREQ)
+
+
+ // --- Broadcast to ALL radio devices ---
+
+ else
+ for (var/obj/item/device/radio/R in connection.devices["[RADIO_CHAT]"])
+
+ receive |= R.send_hear(display_freq)
+
+
+ /* ###### Organize the receivers into categories for displaying the message ###### */
+
+ // Understood the message:
+ var/list/heard_normal = list() // normal message
+
+ // Did not understand the message:
+ var/list/heard_garbled = list() // garbled message (ie "f*c* **u, **i*er!")
+ var/list/heard_gibberish= list() // completely screwed over message (ie "F%! (O*# *#!<>&**%!")
+
+ for (var/mob/R in receive)
+
+ /* --- Loop through the receivers and categorize them --- */
+
+ if (R.client && R.client.STFU_radio) //Adminning with 80 people on can be fun when you're trying to talk and all you can hear is radios.
+ continue
+
+
+ // --- Check for compression ---
+ if(compression > 0)
+
+ heard_gibberish += R
+ continue
+
+ // --- Can understand the speech ---
+
+ if (R.say_understands(M))
+
+ heard_normal += R
+
+ // --- Can't understand the speech ---
+
+ else
+ // - Just display a garbled message -
+
+ heard_garbled += R
+ for(var/obj/item/weapon/implant/imp in R)
+ imp.hear(text,M)
+
+
+ /* ###### Begin formatting and sending the message ###### */
+ if (length(heard_normal) || length(heard_garbled) || length(heard_gibberish))
+
+ /* --- Some miscellaneous variables to format the string output --- */
+ var/part_a = "" // goes in the actual output
+ var/freq_text // the name of the channel
+
+ // --- Set the name of the channel ---
+ switch(display_freq)
+
+ if(SYND_FREQ)
+ freq_text = "#unkn"
+ if(COMM_FREQ)
+ freq_text = "Command"
+ if(1351)
+ freq_text = "Science"
+ if(1355)
+ freq_text = "Medical"
+ if(1357)
+ freq_text = "Engineering"
+ if(1359)
+ freq_text = "Security"
+ if(1349)
+ freq_text = "Mining"
+ if(1347)
+ freq_text = "Cargo"
+
+ if(connection.frequency == NUKE_FREQ)
+ freq_text = "Agent"
+ //There's probably a way to use the list var of channels in code\game\communications.dm to make the dept channels non-hardcoded, but I wasn't in an experimentive mood. --NEO
+
+
+ // --- If the frequency has not been assigned a name, just use the frequency as the name ---
+
+ if(!freq_text)
+ freq_text = format_frequency(display_freq)
+
+ // --- Some more pre-message formatting ---
+
+ var/part_b_extra = ""
+ if(data == 3) // intercepted radio message
+ part_b_extra = " (Intercepted)"
+
+ // Create a radio headset for the sole purpose of using its icon
+ var/obj/item/device/radio/headset/radio = new
+
+ var/part_b = " \icon[radio]\[[freq_text]\][part_b_extra] " // Tweaked for security headsets -- TLE
+ var/part_c = ""
+
+ if (display_freq==SYND_FREQ)
+ part_a = ""
+ else if (display_freq==COMM_FREQ)
+ part_a = ""
+ else if (display_freq in DEPT_FREQS)
+ part_a = ""
+ else if (display_freq==NUKE_FREQ)
+ part_a = ""
+
+ // --- This following recording is intended for research and feedback in the use of department radio channels ---
+
+ var/part_blackbox_b = " \[[freq_text]\] " // Tweaked for security headsets -- TLE
+ var/blackbox_msg = "[part_a][source][part_blackbox_b]\"[text]\"[part_c]"
+ //var/blackbox_admin_msg = "[part_a][M.name] (Real name: [M.real_name])[part_blackbox_b][quotedmsg][part_c]"
+ for (var/obj/machinery/blackbox_recorder/BR in world)
+ //BR.messages_admin += blackbox_admin_msg
+ switch(display_freq)
+ if(1459)
+ BR.msg_common += blackbox_msg
+ if(1351)
+ BR.msg_science += blackbox_msg
+ if(1353)
+ BR.msg_command += blackbox_msg
+ if(1355)
+ BR.msg_medical += blackbox_msg
+ if(1357)
+ BR.msg_engineering += blackbox_msg
+ if(1359)
+ BR.msg_security += blackbox_msg
+ if(1441)
+ BR.msg_deathsquad += blackbox_msg
+ if(1213)
+ BR.msg_syndicate += blackbox_msg
+ if(1349)
+ BR.msg_mining += blackbox_msg
+ if(1347)
+ BR.msg_cargo += blackbox_msg
+ else
+ BR.messages += blackbox_msg
+
+ //End of research and feedback code.
+
+ /* ###### Send the message ###### */
+
+ /* --- Process all the mobs that heard the voice normally (understood) --- */
+
+ if (length(heard_normal))
+ var/rendered = "[part_a][source][part_b]\"[text]\"[part_c]"
+ if(M.job == "Automated Announcement")
+ rendered = "[part_a][source][part_b][text][part_c]"
+
+ for (var/mob/R in heard_normal)
+ R.show_message(rendered, 2)
+
+ /* --- Process all the mobs that heard a garbled voice (did not understand) --- */
+ // Displays garbled message (ie "f*c* **u, **i*er!")
+
+ if (length(heard_garbled))
+ var/quotedmsg = "\"[stars(text)]\""
+ if(M.job == "Automated Announcement")
+ quotedmsg = "[stars(text)]"
+ var/rendered = "[part_a][source][part_b][quotedmsg][part_c]"
+
+ for (var/mob/R in heard_garbled)
+ R.show_message(rendered, 2)
+
+
+ /* --- Complete gibberish. Usually happens when there's a compressed message --- */
+
+ if (length(heard_gibberish))
+ var/quotedmsg = "\"[Gibberish(text, compression + 50)]\""
+ if(M.job == "Automated Announcement")
+ quotedmsg = "[Gibberish(text, compression + 50)]"
+ var/rendered = "[part_a][Gibberish(source, compression + 50)][part_b][quotedmsg][part_c]"
+
+ for (var/mob/R in heard_gibberish)
+ R.show_message(rendered, 2)
+
+
diff --git a/code/game/machinery/telecomms/logbrowser.dm b/code/game/machinery/telecomms/logbrowser.dm
index 03665dda37f..a9934dd7b78 100644
--- a/code/game/machinery/telecomms/logbrowser.dm
+++ b/code/game/machinery/telecomms/logbrowser.dm
@@ -27,7 +27,7 @@
if(0)
dat += "
[temp]
"
- dat += "
Current Network: [network]
"
+ dat += "
Current Network: [network]
"
if(servers.len)
dat += "
Detected Telecommunication Servers:"
for(var/obj/machinery/telecomms/T in servers)
@@ -60,56 +60,64 @@
dat += "- [C.name] \[X\]
"
- // -- Determine race of orator --
+ // If the log is a speech file
+ if(C.input_type == "Speech File")
+ // -- Determine race of orator --
- var/race // The actual race of the mob
- var/language = "Human" // MMIs, pAIs, Cyborgs and humans all speak Human
- var/mobtype = C.parameters["mobtype"]
- var/mob/M = new mobtype
+ var/race // The actual race of the mob
+ var/language = "Human" // MMIs, pAIs, Cyborgs and humans all speak Human
+ var/mobtype = C.parameters["mobtype"]
+ var/mob/M = new mobtype
- if(ishuman(M) || isbrain(M))
- race = "Human"
+ if(ishuman(M) || isbrain(M))
+ race = "Human"
- else if(ismonkey(M))
- race = "Monkey"
- language = race
+ else if(ismonkey(M))
+ race = "Monkey"
+ language = race
- else if(issilicon(M) || C.parameters["job"] == "AI") // sometimes M gets deleted prematurely for AIs... just check the job
- race = "Artificial Life"
+ else if(issilicon(M) || C.parameters["job"] == "AI") // sometimes M gets deleted prematurely for AIs... just check the job
+ race = "Artificial Life"
- else if(ismetroid(M)) // NT knows a lot about metroids, but not aliens. Can identify metroids
- race = "Metroid"
- language = race
+ else if(ismetroid(M)) // NT knows a lot about metroids, but not aliens. Can identify metroids
+ race = "Metroid"
+ language = race
- else if(isanimal(M))
- race = "Domestic Animal"
- language = race
+ else if(isanimal(M))
+ race = "Domestic Animal"
+ language = race
- else
- race = "Unidentifiable"
- language = race
+ else
+ race = "Unidentifiable"
+ language = race
- del(M)
+ del(M)
- // -- If the orator is a human, or universal translate is active, OR mob has universal speech on --
+ // -- If the orator is a human, or universal translate is active, OR mob has universal speech on --
- if(language == "Human" || universal_translate || C.parameters["uspeech"])
+ if(language == "Human" || universal_translate || C.parameters["uspeech"])
+ dat += "Data type: [C.input_type]
"
+ dat += "Source: [C.parameters["name"]] (Job: [C.parameters["job"]])
"
+ dat += "Class: [race]
"
+ dat += "Contents: \"[C.parameters["message"]]\"
"
+
+
+ // -- Orator is not human and universal translate not active --
+
+ else
+ dat += "Data type: Audio File
"
+ dat += "Source: Unidentifiable
"
+ dat += "Class: [race]
"
+ dat += "Contents: Unintelligble
"
+
+ dat += "
"
+
+ else if(C.input_type == "Execution Error")
dat += "Data type: [C.input_type]
"
- dat += "Source: [C.parameters["name"]] (Job: [C.parameters["job"]])
"
- dat += "Class: [race]
"
+ dat += "Source: Internal server code
"
dat += "Contents: \"[C.parameters["message"]]\"
"
- // -- Orator is not human and universal translate not active --
-
- else
- dat += "Data type: Audio File
"
- dat += "Source: Unidentifiable
"
- dat += "Class: [race]
"
- dat += "Contents: Unintelligble
"
-
- dat += "
"
-
dat += ""
@@ -128,9 +136,6 @@
add_fingerprint(usr)
usr.machine = src
- if(!src.allowed(usr) && !emagged)
- usr << "\red ACCESS DENIED."
- return
if(href_list["viewserver"])
screen = 1
@@ -166,6 +171,11 @@
screen = 0
if(href_list["delete"])
+
+ if(!src.allowed(usr) && !emagged)
+ usr << "\red ACCESS DENIED."
+ return
+
if(SelectedServer)
var/datum/comm_log_entry/D = SelectedServer.log_entries[text2num(href_list["delete"])]
@@ -178,11 +188,11 @@
else
temp = "- FAILED: NO SELECTED MACHINE -"
- if(href_list["input"])
+ if(href_list["network"])
var/newnet = input(usr, "Which network do you want to view?", "Comm Monitor", network) as null|text
- if(newnet && usr in range(1, src) && newnet != network)
+ if(newnet && usr in range(1, src))
if(length(newnet) > 15)
temp = "- FAILED: NETWORK TAG STRING TOO LENGHTLY -"
@@ -224,11 +234,6 @@
A.anchored = 1
del(src)
else if(istype(D, /obj/item/weapon/card/emag) && !emagged)
- var/obj/item/weapon/card/emag/E = D
- if(E.uses)
- E.uses--
- else
- return
playsound(src.loc, 'sparks4.ogg', 75, 1)
emagged = 1
user << "\blue You you disable the security protocols"
diff --git a/code/game/machinery/telecomms/machine_interactions.dm b/code/game/machinery/telecomms/machine_interactions.dm
index 2fc5a6c975c..2ead66f3eb7 100644
--- a/code/game/machinery/telecomms/machine_interactions.dm
+++ b/code/game/machinery/telecomms/machine_interactions.dm
@@ -139,13 +139,13 @@
dat += "
\[Add Filter\]"
dat += "
"
if(P.buffer)
- dat += "
MULTITOOL BUFFER: \ref[P.buffer] [P.buffer] \[Link\] \[Flush\]"
+ dat += "
MULTITOOL BUFFER: [P.buffer] ([P.buffer.id]) \[Link\] \[Flush\]"
else
dat += "
MULTITOOL BUFFER: \[Add Machine\]"
dat += ""
temp = ""
- user << browse(dat, "window=[src.name];size=520x500;can_resize=0")
+ user << browse(dat, "window=tcommachine;size=520x500;can_resize=0")
onclose(user, "dormitory")
Topic(href, href_list)
diff --git a/code/game/machinery/telecomms/telecommunications.dm b/code/game/machinery/telecomms/telecommunications.dm
index e1a8bab184e..a84aff8bede 100644
--- a/code/game/machinery/telecomms/telecommunications.dm
+++ b/code/game/machinery/telecomms/telecommunications.dm
@@ -83,7 +83,10 @@
"connection" = signal.data["connection"],
"radio" = signal.data["radio"],
"slow" = signal.data["slow"],
- "traffic" = signal.data["traffic"]
+ "traffic" = signal.data["traffic"],
+ "type" = signal.data["type"],
+ "server" = signal.data["server"],
+ "reject" = signal.data["reject"]
)
// Keep the "original" signal constant
@@ -137,6 +140,11 @@
if(!(T in links) && machinetype != T.machinetype)
links.Add(T)
+ if(istype(src, /obj/machinery/telecomms/server))
+ var/obj/machinery/telecomms/server/S = src
+ S.Compiler = new()
+ S.Compiler.Holder = src
+
update_icon()
if(on)
@@ -355,8 +363,21 @@
circuitboard = "/obj/item/weapon/circuitboard/telecomms/server"
var
list/log_entries = list()
+ list/stored_names = list()
+ list/TrafficActions = list()
+ logs = 0 // number of logs
totaltraffic = 0 // gigabytes (if > 1024, divide by 1024 -> terrabytes)
+ list/memory = list() // stored memory
+ rawcode = "" // the code to compile (raw text)
+ datum/TCS_Compiler/Compiler // the compiler that compiles and runs the code
+ autoruncode = 0 // 1 if the code is set to run every time a signal is picked up
+
+ encryption = "null" // encryption key: ie "password"
+ salt = "null" // encryption salt: ie "123comsat"
+ // would add up to md5("password123comsat")
+ language = "human"
+
receive_information(datum/signal/signal, obj/machinery/telecomms/machine_from)
if(signal.data["message"])
@@ -382,10 +403,7 @@
log.parameters["message"] = signal.data["message"]
log.parameters["name"] = signal.data["name"]
log.parameters["realname"] = signal.data["realname"]
- if(M)
- log.parameters["uspeech"] = M.universal_speak
- else
- log.parameters["uspeech"] = 0
+ log.parameters["uspeech"] = M.universal_speak
// If the signal is still compressed, make the log entry gibberish
if(signal.data["compression"] > 0)
@@ -396,25 +414,50 @@
log.parameters["vname"] = Gibberish(signal.data["vname"], signal.data["compression"] + 50)
log.input_type = "Corrupt File"
+ // Log and store everything that needs to be logged
log_entries.Add(log)
+ if(!(signal.data["name"] in stored_names))
+ stored_names.Add(signal.data["name"])
+ logs++
+ signal.data["server"] = src
+ // Give the log a name
var/identifier = num2text( rand(-1000,1000) + world.time )
log.name = "data packet ([md5(identifier)])"
- relay_information(signal, "/obj/machinery/telecomms/broadcaster") // send to all broadcasters
+ if(Compiler && autoruncode)
+ Compiler.Run(signal) // execute the code
+
+ relay_information(signal, "/obj/machinery/telecomms/broadcaster")
+
+
+ proc/setcode(var/t)
+ if(t)
+ if(istext(t))
+ rawcode = t
+
+ proc/compile()
+ if(Compiler)
+ return Compiler.Compile(rawcode)
proc/update_logs()
- // deletes all logs when there are 100
- if(log_entries.len >= 100)
- var/list/restore = list()
- for(var/datum/comm_log_entry/log in log_entries)
- if(log.garbage_collector) // if garbage collector is set to 1, delete
- del(log)
- else
- restore.Add(log)
+ // start deleting the very first log entry
+ if(logs >= 400)
+ for(var/i = 1, i <= logs, i++) // locate the first garbage collectable log entry and remove it
+ var/datum/comm_log_entry/L = log_entries[i]
+ if(L.garbage_collector)
+ log_entries.Remove(L)
+ logs--
+ break
+
+ proc/add_entry(var/content, var/input)
+ var/datum/comm_log_entry/log = new
+ var/identifier = num2text( rand(-1000,1000) + world.time )
+ log.name = "data packet ([md5(identifier)])"
+ log.input_type = input
+ log.parameters["message"] = content
+
- log_entries.len = 0
- log_entries.Add(restore)
// Simple log entry datum
@@ -493,42 +536,42 @@
network = "tcommsat"
science
- id = "science server"
+ id = "Science Server"
freq_listening = list(1351)
autolinkers = list("science", "broadcasterA")
medical
- id = "medical server"
+ id = "Medical Server"
freq_listening = list(1355)
autolinkers = list("medical", "broadcasterA")
cargo
- id = "cargo server"
+ id = "Cargo Server"
freq_listening = list(1347)
autolinkers = list("cargo", "broadcasterA")
mining
- id = "mining server"
+ id = "Mining Server"
freq_listening = list(1349)
autolinkers = list("mining", "broadcasterA")
common
- id = "common server"
+ id = "Common Server"
freq_listening = list(1459)
autolinkers = list("common", "broadcasterB")
command
- id = "command server"
+ id = "Command Server"
freq_listening = list(1353)
autolinkers = list("command", "broadcasterB")
engineering
- id = "engineering server"
+ id = "Engineering Server"
freq_listening = list(1357)
autolinkers = list("engineering", "broadcasterB")
security
- id = "security server"
+ id = "Security Server"
freq_listening = list(1359)
autolinkers = list("security", "broadcasterB")
diff --git a/code/game/machinery/telecomms/telemonitor.dm b/code/game/machinery/telecomms/telemonitor.dm
index a7ee7e827e8..1866477d7a8 100644
--- a/code/game/machinery/telecomms/telemonitor.dm
+++ b/code/game/machinery/telecomms/telemonitor.dm
@@ -18,8 +18,6 @@
temp = "" // temporary feedback messages
- req_access = list(access_tcomsat)
-
attack_hand(mob/user as mob)
if(stat & (BROKEN|NOPOWER))
return
@@ -33,7 +31,7 @@
if(0)
dat += "
[temp]
"
- dat += "
Current Network: [network]
"
+ dat += "
Current Network: [network]
"
if(machines.len)
dat += "
Detected Network Entities:"
for(var/obj/machinery/telecomms/T in machines)
@@ -72,9 +70,6 @@
add_fingerprint(usr)
usr.machine = src
- if(!src.allowed(usr) && !emagged)
- usr << "\red ACCESS DENIED."
- return
if(href_list["viewmachine"])
screen = 1
@@ -110,10 +105,10 @@
screen = 0
- if(href_list["input"])
+ if(href_list["network"])
var/newnet = input(usr, "Which network do you want to view?", "Comm Monitor", network) as null|text
- if(newnet && usr in range(1, src) && newnet != network)
+ if(newnet && usr in range(1, src))
if(length(newnet) > 15)
temp = "- FAILED: NETWORK TAG STRING TOO LENGHTLY -"
@@ -154,11 +149,6 @@
A.anchored = 1
del(src)
else if(istype(D, /obj/item/weapon/card/emag) && !emagged)
- var/obj/item/weapon/card/emag/E = D
- if(E.uses)
- E.uses--
- else
- return
playsound(src.loc, 'sparks4.ogg', 75, 1)
emagged = 1
user << "\blue You you disable the security protocols"
diff --git a/code/game/machinery/telecomms/traffic_control.dm b/code/game/machinery/telecomms/traffic_control.dm
new file mode 100644
index 00000000000..5f0ede226ad
--- /dev/null
+++ b/code/game/machinery/telecomms/traffic_control.dm
@@ -0,0 +1,233 @@
+
+
+
+
+/obj/machinery/computer/telecomms/traffic
+ name = "Telecommunications Traffic Control"
+ icon_state = "computer_generic"
+
+ var
+ screen = 0 // the screen number:
+ list/servers = list() // the servers located by the computer
+ mob/editingcode
+ list/viewingcode = list()
+ obj/machinery/telecomms/server/SelectedServer
+
+ network = "NULL" // the network to probe
+ temp = "" // temporary feedback messages
+
+ storedcode = "" // code stored
+
+
+ proc/update_ide()
+
+ // loop if there's someone manning the keyboard
+ while(editingcode)
+ if(!editingcode.client)
+ editingcode = null
+ break
+
+ // For the typer, the input is enabled. Buffer the typed text
+ if(editingcode)
+ storedcode = "[winget(editingcode, "tcscode", "text")]"
+ if(editingcode) // double if's to work around a runtime error
+ winset(editingcode, "tcscode", "is-disabled=false")
+
+ // If the player's not manning the keyboard anymore, adjust everything
+ if(!(editingcode in range(1, src)) || editingcode.machine != src)
+ if(editingcode)
+ winshow(editingcode, "Telecomms IDE", 0) // hide the window!
+ editingcode = null
+ break
+
+ // For other people viewing the typer type code, the input is disabled and they can only view the code
+ // (this is put in place so that there's not any magical shenanigans with 50 people inputting different code all at once)
+
+ if(length(viewingcode))
+ // This piece of code is very important - it escapes quotation marks so string aren't cut off by the input element
+ var/showcode = dd_replacetext(storedcode, "\\\"", "\\\\\"")
+ showcode = dd_replacetext(storedcode, "\"", "\\\"")
+
+ for(var/mob/M in viewingcode)
+ if(M.machine == src && M in view(1, src))
+ winset(M, "tcscode", "is-disabled=true")
+ winset(M, "tcscode", "text=\"[showcode]\"")
+ else
+ if(!issilicon(M))
+ viewingcode.Remove(M)
+ winshow(M, "Telecomms IDE", 0) // hide the window!
+
+ sleep(5)
+
+ if(length(viewingcode) > 0)
+ editingcode = pick(viewingcode)
+ viewingcode.Remove(editingcode)
+ update_ide()
+
+
+
+ req_access = list(access_tcomsat)
+
+ attack_hand(mob/user as mob)
+ if(stat & (BROKEN|NOPOWER))
+ return
+ user.machine = src
+ var/dat = "Telecommunication Traffic ControlTelecommunications Traffic Control"
+
+ switch(screen)
+
+
+ // --- Main Menu ---
+
+ if(0)
+ dat += "
[temp]
"
+ dat += "
Current Network: [network]
"
+ if(servers.len)
+ dat += "
Detected Telecommunication Servers:"
+ for(var/obj/machinery/telecomms/T in servers)
+ dat += "- \ref[T] [T.name] ([T.id])
"
+ dat += "
"
+ dat += "
\[Flush Buffer\]"
+
+ else
+ dat += "
No servers detected. Scan for servers: \[Scan\]"
+
+
+ // --- Viewing Server ---
+
+ if(1)
+ dat += "
[temp]
"
+ dat += "\[Main Menu\] \[Refresh\]"
+ dat += "
Current Network: [network]"
+ dat += "
Selected Server: [SelectedServer.id]
"
+ dat += "
\[Edit Code\]"
+ dat += "
Signal Execution: "
+ if(SelectedServer.autoruncode)
+ dat += "ALWAYS"
+ else
+ dat += "NEVER"
+
+
+ user << browse(dat, "window=traffic_control;size=575x400")
+ onclose(user, "server_control")
+
+ temp = ""
+ return
+
+
+ Topic(href, href_list)
+ if(..())
+ return
+
+
+ add_fingerprint(usr)
+ usr.machine = src
+ if(!src.allowed(usr) && !emagged)
+ usr << "\red ACCESS DENIED."
+ return
+
+ if(href_list["viewserver"])
+ screen = 1
+ for(var/obj/machinery/telecomms/T in servers)
+ if(T.id == href_list["viewserver"])
+ SelectedServer = T
+ break
+
+ if(href_list["operation"])
+ switch(href_list["operation"])
+
+ if("release")
+ servers = list()
+ screen = 0
+
+ if("mainmenu")
+ screen = 0
+
+ if("scan")
+ if(servers.len > 0)
+ temp = "- FAILED: CANNOT PROBE WHEN BUFFER FULL -"
+
+ else
+ for(var/obj/machinery/telecomms/server/T in range(25, src))
+ if(T.network == network)
+ servers.Add(T)
+
+ if(!servers.len)
+ temp = "- FAILED: UNABLE TO LOCATE SERVERS IN \[[network]\] -"
+ else
+ temp = "- [servers.len] SERVERS PROBED & BUFFERED -"
+
+ screen = 0
+
+ if("editcode")
+ if(!editingcode)
+ editingcode = usr
+ winshow(editingcode, "Telecomms IDE", 1) // show the IDE
+ winset(editingcode, "tcscode", "is-disabled=false")
+ var/showcode = dd_replacetext(storedcode, "\\\"", "\\\\\"")
+ showcode = dd_replacetext(storedcode, "\"", "\\\"")
+ winset(editingcode, "tcscode", "text=\"[showcode]\"")
+ spawn()
+ update_ide()
+
+ else
+ viewingcode.Add(usr)
+ winshow(usr, "Telecomms IDE", 1) // show the IDE
+ winset(usr, "tcscode", "is-disabled=true")
+ var/showcode = dd_replacetext(storedcode, "\"", "\\\"")
+ winset(usr, "tcscode", "text=\"[showcode]\"")
+
+ if("togglerun")
+ SelectedServer.autoruncode = !(SelectedServer.autoruncode)
+
+ if(href_list["network"])
+
+ var/newnet = input(usr, "Which network do you want to view?", "Comm Monitor", network) as null|text
+
+ if(newnet && usr in range(1, src))
+ if(length(newnet) > 15)
+ temp = "- FAILED: NETWORK TAG STRING TOO LENGHTLY -"
+
+ else
+
+ network = newnet
+ screen = 0
+ machines = list()
+ temp = "- NEW NETWORK TAG SET IN ADDRESS \[[network]\] -"
+
+ updateUsrDialog()
+ return
+
+ attackby(var/obj/item/weapon/D as obj, var/mob/user as mob)
+ if(istype(D, /obj/item/weapon/screwdriver))
+ playsound(src.loc, 'Screwdriver.ogg', 50, 1)
+ if(do_after(user, 20))
+ if (src.stat & BROKEN)
+ user << "\blue The broken glass falls out."
+ var/obj/structure/computerframe/A = new /obj/structure/computerframe( src.loc )
+ new /obj/item/weapon/shard( src.loc )
+ var/obj/item/weapon/circuitboard/comm_server/M = new /obj/item/weapon/circuitboard/comm_server( A )
+ for (var/obj/C in src)
+ C.loc = src.loc
+ A.circuit = M
+ A.state = 3
+ A.icon_state = "3"
+ A.anchored = 1
+ del(src)
+ else
+ user << "\blue You disconnect the monitor."
+ var/obj/structure/computerframe/A = new /obj/structure/computerframe( src.loc )
+ var/obj/item/weapon/circuitboard/comm_server/M = new /obj/item/weapon/circuitboard/comm_server( A )
+ for (var/obj/C in src)
+ C.loc = src.loc
+ A.circuit = M
+ A.state = 4
+ A.icon_state = "4"
+ A.anchored = 1
+ del(src)
+ else if(istype(D, /obj/item/weapon/card/emag) && !emagged)
+ playsound(src.loc, 'sparks4.ogg', 75, 1)
+ emagged = 1
+ user << "\blue You you disable the security protocols"
+ src.updateUsrDialog()
+ return
\ No newline at end of file
diff --git a/code/game/mecha/mech_fabricator.dm b/code/game/mecha/mech_fabricator.dm
index 7f35b5c2681..1b0503a40a7 100644
--- a/code/game/mecha/mech_fabricator.dm
+++ b/code/game/mecha/mech_fabricator.dm
@@ -213,13 +213,14 @@
return output
proc/remove_resources(var/obj/item/mecha_parts/part)
- for(var/resource in part.construction_cost)
- if(resource in src.resources)
- src.resources[resource] -= get_resource_cost_w_coeff(part,resource)
+ if(istype(part, /obj/item/mecha_parts/part))
+ for(var/resource in part.construction_cost)
+ if(resource in src.resources)
+ src.resources[resource] -= get_resource_cost_w_coeff(part,resource)
return
proc/check_resources(var/obj/item/mecha_parts/part)
- if(istype(part, /obj/item))
+ if(istype(part, /obj/item/mecha_parts/part))
for(var/resource in part.construction_cost)
if(resource in src.resources)
if(src.resources[resource] < get_resource_cost_w_coeff(part,resource))
diff --git a/code/game/objects/devices/PDA/PDA.dm b/code/game/objects/devices/PDA/PDA.dm
index 340d32c8475..4470eae3ade 100755
--- a/code/game/objects/devices/PDA/PDA.dm
+++ b/code/game/objects/devices/PDA/PDA.dm
@@ -176,12 +176,11 @@
dat += ""
@@ -577,7 +576,6 @@
difficulty += P.cartridge.access_engine
difficulty += P.cartridge.access_clown
difficulty += P.cartridge.access_janitor
- difficulty += P.cartridge.access_manifest * 2
else
difficulty += 2
diff --git a/code/game/objects/devices/PDA/cart.dm b/code/game/objects/devices/PDA/cart.dm
index 488db44b814..8b5b5249695 100644
--- a/code/game/objects/devices/PDA/cart.dm
+++ b/code/game/objects/devices/PDA/cart.dm
@@ -10,7 +10,6 @@
var/access_security = 0
var/access_engine = 0
var/access_medical = 0
- var/access_manifest = 1
var/access_clown = 0
var/access_mime = 0
var/access_janitor = 0
@@ -167,7 +166,6 @@
name = "Detomatix Cartridge"
icon_state = "cart"
access_remote_door = 1
- access_manifest = 0
remote_door_id = "syndicate" //Make sure this matches the syndicate shuttle's shield/door id!!
var/shock_charges = 4
diff --git a/code/game/objects/items/food.dm b/code/game/objects/items/food.dm
index 37d0b54cf0c..8639893257c 100644
--- a/code/game/objects/items/food.dm
+++ b/code/game/objects/items/food.dm
@@ -36,16 +36,17 @@ MONKEY CUBE BOX
/obj/item/kitchen/donut_box/MouseDrop(mob/user as mob)
if ((user == usr && (!( usr.restrained() ) && (!( usr.stat ) && (usr.contents.Find(src) || in_range(src, usr))))))
- if (usr.hand)
- if (!( usr.l_hand ))
- spawn( 0 )
- src.attack_hand(usr, 1, 1)
- return
- else
- if (!( usr.r_hand ))
- spawn( 0 )
- src.attack_hand(usr, 0, 1)
- return
+ if(ishuman(user))
+ if (usr.hand)
+ if (!( usr.l_hand ))
+ spawn( 0 )
+ src.attack_hand(usr, 1, 1)
+ return
+ else
+ if (!( usr.r_hand ))
+ spawn( 0 )
+ src.attack_hand(usr, 0, 1)
+ return
return
/obj/item/kitchen/donut_box/attack_paw(mob/user as mob)
diff --git a/code/game/objects/radio/electropack.dm b/code/game/objects/radio/electropack.dm
index 4f5b5d07632..512a8cd4788 100644
--- a/code/game/objects/radio/electropack.dm
+++ b/code/game/objects/radio/electropack.dm
@@ -3,7 +3,6 @@
desc = "Dance my monkeys! DANCE!!!"
icon_state = "electropack0"
var/code = 2
- var/on = 0
var/e_pads = 0.0
g_amt = 2500
m_amt = 10000
diff --git a/code/game/objects/radio/encryptionkey.dm b/code/game/objects/radio/encryptionkey.dm
new file mode 100644
index 00000000000..2758ddf9099
--- /dev/null
+++ b/code/game/objects/radio/encryptionkey.dm
@@ -0,0 +1,110 @@
+/obj/item/device/encryptionkey/
+ name = "Standard Encrpytion Key"
+ desc = "An encyption key for a radio headset. Has no special codes in it. WHY DOES IT EXIST? ASK NANOTRASEN."
+ icon = 'radio.dmi'
+ icon_state = "cypherkey"
+ item_state = ""
+ var/channels = list()
+ var/translate_hive = 0
+ var/translate_binary = 0
+
+/obj/item/device/encryptionkey/New()
+
+/obj/item/device/radio/headset/attackby(obj/item/weapon/W as obj, mob/user as mob)
+
+/obj/item/device/encryptionkey/traitor
+ channels = list("Syndicate" = 1)
+ origin_tech = "syndicate=3"
+
+/obj/item/device/encryptionkey/nuclear
+ channels = list("Nuclear" = 1)
+ origin_tech = "syndicate=3"
+
+/obj/item/device/encryptionkey/ert
+ name = "NanoTrasen ERT Radio Encryption Key"
+ desc = "An encyption key for a radio headset. Contains cypherkeys."
+ channels = list("Response Team" = 1, "Science" = 1, "Command" = 1, "Medical" = 1, "Engineering" = 1, "Security" = 1, "Mining" = 1, "Cargo" = 1,)
+
+/obj/item/device/encryptionkey/binary
+ translate_binary = 1
+ origin_tech = "syndicate=3"
+
+/obj/item/device/encryptionkey/headset_sec
+ name = "Security Radio Encryption Key"
+
+ channels = list("Security" = 1)
+
+/obj/item/device/encryptionkey/headset_eng
+ name = "Engineering Radio Encryption Key"
+ desc = "An encyption key for a radio headset. Contains cypherkeys."
+ channels = list("Engineering" = 1)
+
+/obj/item/device/encryptionkey/headset_rob
+ name = "Robotics Radio Encryption Key"
+ desc = "An encyption key for a radio headset. Contains cypherkeys."
+ channels = list("Engineering" = 1, "Science" = 1)
+
+/obj/item/device/encryptionkey/headset_med
+ name = "Medical Radio Encryption Key"
+ desc = "An encyption key for a radio headset. Contains cypherkeys."
+ channels = list("Medical" = 1)
+
+/obj/item/device/encryptionkey/headset_sci
+ name = "Science Radio Encryption Key"
+ desc = "An encyption key for a radio headset. Contains cypherkeys."
+ channels = list("Science" = 1)
+
+/obj/item/device/encryptionkey/headset_medsci
+ name = "Medical Research Radio Encryption Key"
+ desc = "An encyption key for a radio headset. Contains cypherkeys."
+ channels = list("Medical" = 1, "Science" = 1)
+
+/obj/item/device/encryptionkey/headset_com
+ name = "Command Radio Encryption Key"
+ desc = "An encyption key for a radio headset. Contains cypherkeys."
+ channels = list("Command" = 1)
+
+/obj/item/device/encryptionkey/heads/captain
+ name = "Captain's Encryption Key"
+ desc = "An encyption key for a radio headset. Contains cypherkeys."
+ channels = list("Command" = 1, "Science" = 0, "Medical" = 0, "Security" = 1, "Engineering" = 0, "Mining" = 0, "Cargo" = 0)
+
+/obj/item/device/encryptionkey/heads/rd
+ name = "Research Director's Encryption Key"
+ desc = "An encyption key for a radio headset. Contains cypherkeys."
+ channels = list("Science" = 1, "Command" = 1)
+
+/obj/item/device/encryptionkey/heads/hos
+ name = "Head of Security's Encryption Key"
+ desc = "An encyption key for a radio headset. Contains cypherkeys."
+ channels = list("Security" = 1, "Command" = 1)
+
+/obj/item/device/encryptionkey/heads/ce
+ name = "Chief Engineer's Encryption Key"
+ desc = "An encyption key for a radio headset. Contains cypherkeys."
+ channels = list("Engineering" = 1, "Command" = 1)
+
+/obj/item/device/encryptionkey/heads/cmo
+ name = "Chief Medical Officer's Encryption Key"
+ desc = "An encyption key for a radio headset. Contains cypherkeys."
+ channels = list("Medical" = 1, "Command" = 1)
+
+/obj/item/device/encryptionkey/heads/hop
+ name = "Head of Personnel's Encryption Key"
+ desc = "An encyption key for a radio headset. Contains cypherkeys."
+ channels = list("Command" = 1, "Security" = 0, "Cargo" = 1, "Mining" = 0)
+
+/obj/item/device/encryptionkey/headset_mine
+ name = "Mining Radio Encryption Key"
+ desc = "An encyption key for a radio headset. Contains cypherkeys."
+ channels = list("Mining" = 1)
+
+/obj/item/device/encryptionkey/heads/qm
+ name = "Quartermaster's Encryption Key"
+ desc = "An encyption key for a radio headset. Contains cypherkeys."
+ channels = list("Cargo" = 1, "Mining" = 1)
+
+/obj/item/device/encryptionkey/headset_cargo
+ name = "Cargo Radio Encryption Key"
+ desc = "An encyption key for a radio headset. Contains cypherkeys."
+ channels = list("Cargo" = 1)
\ No newline at end of file
diff --git a/code/game/objects/radio/headset.dm b/code/game/objects/radio/headset.dm
index 233c256bb77..dfd8fd08690 100644
--- a/code/game/objects/radio/headset.dm
+++ b/code/game/objects/radio/headset.dm
@@ -6,127 +6,231 @@
g_amt = 0
m_amt = 75
subspace_transmission = 1
+ protective_temperature = 0
+ canhear_range = 1
var
translate_binary = 0
translate_hive = 0
+ obj/item/device/encryptionkey/keyslot1 = null
+ obj/item/device/encryptionkey/keyslot2 = null
+
+/obj/item/device/radio/headset/New()
+ ..()
+ keyslot1 = new /obj/item/device/encryptionkey
+ recalculateChannels()
/obj/item/device/radio/headset/traitor
- translate_binary = 1
- channels = list("Syndicate" = 1)
origin_tech = "syndicate=3"
+ canhear_range = 0
+
+ New()
+ ..()
+ del(keyslot1)
+ keyslot1 = new /obj/item/device/encryptionkey/traitor
+
+/obj/item/device/radio/headset/binary
+ origin_tech = "syndicate=3"
+ canhear_range = 0
+
+ New()
+ ..()
+ del(keyslot1)
+ keyslot1 = new /obj/item/device/encryptionkey/binary
/obj/item/device/radio/headset/headset_sec
name = "Security Radio Headset"
desc = "This is used by your elite security force. To access the security channel, use :s."
icon_state = "sec_headset"
item_state = "headset"
- channels = list("Security" = 1)
+ keyslot2 = new /obj/item/device/encryptionkey/headset_sec
/obj/item/device/radio/headset/headset_eng
name = "Engineering Radio Headset"
desc = "When the engineers wish to chat like girls. To access the engineering channel, use :e. "
icon_state = "eng_headset"
item_state = "headset"
- channels = list("Engineering" = 1)
+ keyslot2 = new /obj/item/device/encryptionkey/headset_eng
/obj/item/device/radio/headset/headset_rob
name = "Robotics Radio Headset"
desc = "Made specifically for the roboticists who cannot decide between departments. To access the engineering channel, use :e. For research, use :n."
icon_state = "rob_headset"
item_state = "headset"
- channels = list("Engineering" = 1, "Science" = 1)
+ keyslot2 = new /obj/item/device/encryptionkey/headset_rob
/obj/item/device/radio/headset/headset_med
name = "Medical Radio Headset"
desc = "A headset for the trained staff of the medbay. To access the medical channel, use :m."
icon_state = "med_headset"
item_state = "headset"
- channels = list("Medical" = 1)
+ keyslot2 = new /obj/item/device/encryptionkey/headset_med
/obj/item/device/radio/headset/headset_sci
name = "Science Radio Headset"
desc = "A sciency headset. Like usual. To access the science channel, use :n."
icon_state = "com_headset"
item_state = "headset"
- channels = list("Science" = 1)
+ keyslot2 = new /obj/item/device/encryptionkey/headset_sci
/obj/item/device/radio/headset/headset_medsci
name = "Medical Research Radio Headset"
desc = "A headset that is a result of the mating between medical and science. To access the medical channel, use :m. For science, use :n."
icon_state = "med_headset"
item_state = "headset"
- channels = list("Medical" = 1, "Science" = 1)
+ keyslot2 = new /obj/item/device/encryptionkey/headset_medsci
/obj/item/device/radio/headset/headset_com
name = "Command Radio Headset"
desc = "A headset with a commanding channel. To access the command channel, use :c."
icon_state = "com_headset"
item_state = "headset"
- channels = list("Command" = 1)
+ keyslot2 = new /obj/item/device/encryptionkey/headset_com
/obj/item/device/radio/headset/heads/captain
name = "Captain's Headset"
desc = "The headset of the boss. Channels are as follows: :c - command, :s - security, :e - engineering, :d - mining, :q - cargo, :m - medical, :n - science."
icon_state = "com_headset"
item_state = "headset"
- channels = list("Command" = 1, "Science" = 0, "Medical" = 0, "Security" = 1, "Engineering" = 0, "Mining" = 0, "Cargo" = 0)
+ keyslot2 = new /obj/item/device/encryptionkey/heads/captain
/obj/item/device/radio/headset/heads/rd
name = "Research Director's Headset"
desc = "Headset of the researching God. To access the science channel, use :n. For command, use :c."
icon_state = "com_headset"
item_state = "headset"
- channels = list("Science" = 1, "Command" = 1)
+ keyslot2 = new /obj/item/device/encryptionkey/heads/rd
/obj/item/device/radio/headset/heads/hos
name = "Head of Security's Headset"
desc = "The headset of the man who protects your worthless lifes. To access the security channel, use :s. For command, use :c."
icon_state = "com_headset"
item_state = "headset"
- channels = list("Security" = 1, "Command" = 1)
+ keyslot2 = new /obj/item/device/encryptionkey/heads/hos
/obj/item/device/radio/headset/heads/ce
name = "Chief Engineer's Headset"
desc = "The headset of the guy who is in charge of morons. To access the engineering channel, use :e. For command, use :c."
icon_state = "com_headset"
item_state = "headset"
- channels = list("Engineering" = 1, "Command" = 1)
+ keyslot2 = new /obj/item/device/encryptionkey/heads/ce
/obj/item/device/radio/headset/heads/cmo
name = "Chief Medical Officer's Headset"
desc = "The headset of the highly trained medical chief. To access the medical channel, use :m. For command, use :c."
icon_state = "com_headset"
item_state = "headset"
- channels = list("Medical" = 1, "Command" = 1)
+ keyslot2 = new /obj/item/device/encryptionkey/heads/cmo
/obj/item/device/radio/headset/heads/hop
name = "Head of Personnel's Headset"
desc = "The headset of the guy who will one day be captain. Channels are as follows: :c - command, :s - security, :q - cargo, :d - mining."
icon_state = "com_headset"
item_state = "headset"
- channels = list("Command" = 1, "Security" = 0, "Cargo" = 1, "Mining" = 0)
-
+ keyslot2 = new /obj/item/device/encryptionkey/heads/hop
/obj/item/device/radio/headset/headset_mine
name = "Mining Radio Headset"
desc = "Headset used by miners. How useless. To access the mining channel, use :d."
icon_state = "mine_headset"
item_state = "headset"
- channels = list("Mining" = 1)
-
-
+ keyslot2 = new /obj/item/device/encryptionkey/headset_mine
/obj/item/device/radio/headset/heads/qm
name = "Quartermaster's Headset"
desc = "The headset of the man who control your toiletpaper supply. To access the cargo channel, use :q. For mining, use :d."
icon_state = "cargo_headset"
item_state = "headset"
- channels = list("Cargo" = 1, "Mining" = 1)
+ keyslot2 = new /obj/item/device/encryptionkey/heads/qm
+/obj/item/device/radio/headset/nuclear
+ freerange = 1
+ listening = 0
+ keyslot2 = new /obj/item/device/encryptionkey/nuclear
+
+/obj/item/device/radio/headset/ert
+ name = "CentCom Response Team headset"
+ desc = "The headset of the boss's boss. Channels are as follows: :h - Response Team :c - command, :s - security, :e - engineering, :d - mining, :q - cargo, :m - medical, :n - science."
+ freerange = 1
+ keyslot2 = new /obj/item/device/encryptionkey/ert
/obj/item/device/radio/headset/headset_cargo
name = "Cargo Radio Headset"
desc = "Headset used by the QM's slaves. To access the cargo channel, use :q."
icon_state = "cargo_headset"
item_state = "headset"
- channels = list("Cargo" = 1)
\ No newline at end of file
+ keyslot2 = new /obj/item/device/encryptionkey/headset_cargo
+
+/obj/item/device/radio/headset/attackby(obj/item/weapon/W as obj, mob/user as mob)
+// ..()
+ user.machine = src
+ if (!istype(W, /obj/item/weapon/screwdriver) && !istype(W, /obj/item/device/encryptionkey/))
+ return
+
+ if(istype(W, /obj/item/weapon/screwdriver))
+ if(keyslot1 || keyslot2)
+
+ if(keyslot1)
+ user.put_in_hands(keyslot1)
+
+ if(keyslot2)
+ user.put_in_hands(keyslot2)
+
+ recalculateChannels()
+ user << "You pop out the encryption keys in the headset!"
+
+ else
+ user << "This headset doesn't have any encryption keys! How useless..."
+
+ if(istype(W, /obj/item/device/encryptionkey/))
+ if(keyslot1 && keyslot2)
+ user << "The headset can't hold another key!"
+ return
+
+ if(!keyslot1)
+ user.drop_item()
+ W.loc = src
+ keyslot1 = W
+
+ else
+ user.drop_item()
+ W.loc = src
+ keyslot2 = W
+
+ recalculateChannels()
+ return
+
+
+/obj/item/device/radio/headset/proc/recalculateChannels()
+ src.channels = list()
+ src.translate_binary = 0
+ src.translate_hive = 0
+ var/temp_channels = list()
+
+ if(keyslot1)
+ temp_channels += keyslot1.channels
+
+ if(keyslot1.translate_binary)
+ src.translate_binary = 1
+
+ if(keyslot1.translate_hive)
+ src.translate_hive = 1
+
+ if(keyslot2)
+ temp_channels += keyslot2.channels
+
+ if(keyslot2.translate_binary)
+ src.translate_binary = 1
+
+ if(keyslot2.translate_hive)
+ src.translate_hive = 1
+
+ config(temp_channels)
+
+ for (var/ch_name in channels)
+ if(!radio_controller)
+ sleep(30) // Waiting for the radio_controller to be created.
+ if(!radio_controller)
+ src.name = "Broken Radio Headset"
+ return
+
+ return
\ No newline at end of file
diff --git a/code/game/objects/radio/radio.dm b/code/game/objects/radio/radio.dm
index ed3bbe0d647..e183ff2d46e 100644
--- a/code/game/objects/radio/radio.dm
+++ b/code/game/objects/radio/radio.dm
@@ -10,9 +10,11 @@ var/GLOBAL_RADIO_TYPE = 1 // radio type to use
icon_state = "walkietalkie"
item_state = "walkietalkie"
var
+ on = 1 // 0 for off
last_transmission
frequency = 1459 //common chat
traitor_frequency = 0 //tune to frequency to unlock traitor supplies
+ canhear_range = 3
obj/item/device/radio/patch_link = null
obj/item/device/uplink/radio/traitorradio = null
wires = WIRE_SIGNAL | WIRE_RECEIVE | WIRE_TRANSMIT
@@ -55,6 +57,11 @@ var/GLOBAL_RADIO_TYPE = 1 // radio type to use
if(radio_controller)
initialize()
+ // If intercom: do a local power check loop
+ if(istype(src, /obj/item/device/radio/intercom))
+ spawn(5)
+ checkpower()
+
/obj/item/device/radio/initialize()
if(freerange)
if(frequency < 1200 || frequency > 1600)
@@ -74,6 +81,9 @@ var/GLOBAL_RADIO_TYPE = 1 // radio type to use
interact(user)
/obj/item/device/radio/proc/interact(mob/user as mob)
+ if(!on)
+ return
+
var/dat = {"
[src]
Microphone: [broadcasting ? "Engaged" : "Disengaged"]
@@ -111,9 +121,29 @@ var/GLOBAL_RADIO_TYPE = 1 // radio type to use
Speaker: [list ? "Engaged" : "Disengaged"]
"}
+/obj/item/device/radio/proc/checkpower()
+
+ // Simple loop, checks for power. Strictly for intercoms
+ while(src)
+
+ if(!src.loc)
+ on = 0
+ var/area/A = src.loc.loc
+ if(!A || !isarea(A) || !A.master)
+ on = 0
+ else
+ on = A.master.powered(EQUIP) // set "on" to the power status
+
+ if(!on)
+ icon_state = "intercom-p"
+ else
+ icon_state = "intercom"
+
+ sleep(30)
+
/obj/item/device/radio/Topic(href, href_list)
//..()
- if (usr.stat)
+ if (usr.stat || !on)
return
if (!(issilicon(usr) || (usr.contents.Find(src) || ( in_range(src, usr) && istype(loc, /turf) ))))
usr << browse(null, "window=radio")
@@ -125,6 +155,21 @@ var/GLOBAL_RADIO_TYPE = 1 // radio type to use
if(A && target)
A.ai_actual_track(target)
return
+ else if (href_list["faketrack"])
+ var/mob/target = locate(href_list["track"])
+ var/mob/living/silicon/ai/A = locate(href_list["track2"])
+ if(A && target)
+
+ A:cameraFollow = target
+ A << text("Now tracking [] on camera.", target.name)
+ if (usr.machine == null)
+ usr.machine = usr
+
+ while (usr:cameraFollow == target)
+ usr << "Target is not on or near any active cameras on the station. We'll check again in 5 seconds (unless you use the cancel-camera verb)."
+ sleep(40)
+ continue
+ return
else if (href_list["freq"])
var/new_frequency = (frequency + text2num(href_list["freq"]))
if (!freerange || (frequency < 1200 || frequency > 1600))
@@ -195,6 +240,8 @@ var/GLOBAL_RADIO_TYPE = 1 // radio type to use
channel = null
if (!istype(connection))
return
+ if (!connection)
+ return
if(subspace_transmission)
// First, we want to generate a new radio signal
@@ -299,6 +346,7 @@ var/GLOBAL_RADIO_TYPE = 1 // radio type to use
return
/obj/item/device/radio/talk_into(mob/M as mob, message, channel)
+ if(!on) return // the device has to be on
if(GLOBAL_RADIO_TYPE == 1) // NEW RADIO SYSTEMS: By Doohl
@@ -325,6 +373,8 @@ var/GLOBAL_RADIO_TYPE = 1 // radio type to use
channel = null
if (!istype(connection))
return
+ if (!connection)
+ return
@@ -407,7 +457,10 @@ var/GLOBAL_RADIO_TYPE = 1 // radio type to use
"connection" = connection, // the radio connection to use
"radio" = src, // stores the radio used for transmission
"slow" = 0, // how much to sleep() before broadcasting - simulates net lag
- "traffic" = 0 // dictates the total traffic sum that the signal went through
+ "traffic" = 0, // dictates the total traffic sum that the signal went through
+ "type" = 0, // determines what type of radio input it is: normal broadcast
+ "server" = null, // the last server to log this signal
+ "reject" = 0 // if nonzero, the signal will not be accepted by any broadcasting machinery
)
signal.frequency = connection.frequency // Quick frequency set
@@ -455,7 +508,10 @@ var/GLOBAL_RADIO_TYPE = 1 // radio type to use
"connection" = connection, // the radio connection to use
"radio" = src, // stores the radio used for transmission
"slow" = 0,
- "traffic" = 0
+ "traffic" = 0,
+ "type" = 0,
+ "server" = null,
+ "reject" = 0
)
signal.frequency = connection.frequency // Quick frequency set
@@ -466,7 +522,7 @@ var/GLOBAL_RADIO_TYPE = 1 // radio type to use
sleep(rand(10,25)) // wait a little...
if(signal.data["done"])
- del(signal) // delete the signal - we're done here.
+ //we're done here.
return
// Oh my god; the comms are down or something because the signal hasn't been broadcasted yet.
@@ -491,6 +547,8 @@ var/GLOBAL_RADIO_TYPE = 1 // radio type to use
channel = null
if (!istype(connection))
return
+ if (!connection)
+ return
var/display_freq = connection.frequency
//world << "DEBUG: used channel=\"[channel]\" frequency= \"[display_freq]\" connection.devices.len = [connection.devices.len]"
@@ -681,6 +739,8 @@ var/GLOBAL_RADIO_TYPE = 1 // radio type to use
*/
if (!(wires & WIRE_RECEIVE))
return
+ if (!on)
+ return
if (!freq) //recieved on main frequency
if (!listening)
return
@@ -714,7 +774,7 @@ var/GLOBAL_RADIO_TYPE = 1 // radio type to use
/* Instead, let's individually search potential containers for mobs! More verbose but a LOT more efficient and less laggy */
// Check gamehelpers.dm for the proc definition:
- return get_mobs_in_view(1, src)
+ return get_mobs_in_view(canhear_range, src)
/obj/item/device/radio/examine()
set src in view()
diff --git a/code/game/response_team.dm b/code/game/response_team.dm
index f9238d5f67a..b6dfc498cc2 100644
--- a/code/game/response_team.dm
+++ b/code/game/response_team.dm
@@ -194,12 +194,7 @@ proc/trigger_armed_response_team()
/mob/living/carbon/human/proc/equip_strike_team(leader_selected = 0)
//Special radio setup
- var/obj/item/device/radio/R = new /obj/item/device/radio/headset(src)
- R.name = "CentCom Response Team headset"
- R.desc = "The headset of the boss's boss. Channels are as follows: :h - Response Team :c - command, :s - security, :e - engineering, :d - mining, :q - cargo, :m - medical, :n - science."
- R.freerange = 1
- R.config(list("Response Team" = 1, "Science" = 1, "Command" = 1, "Medical" = 1, "Engineering" = 1, "Security" = 1, "Mining" = 1, "Cargo" = 1,))
- equip_if_possible(R, slot_ears)
+ equip_if_possible(new /obj/item/device/radio/headset/ert, slot_ears)
//Adding Camera Network
var/obj/machinery/camera/camera = new /obj/machinery/camera(src) //Gives all the commandos internals cameras.
diff --git a/code/game/sound.dm b/code/game/sound.dm
index 9a67475901a..41c9033eae4 100644
--- a/code/game/sound.dm
+++ b/code/game/sound.dm
@@ -63,9 +63,9 @@ client/verb/Toggle_Soundscape()
set name = "Toggle Ambience"
usr:client:no_ambi = !usr:client:no_ambi
if(usr:client:no_ambi)
- usr << sound('shipambience.ogg', repeat = 0, wait = 0, volume = 0, channel = 2)
+ usr << sound(pick('shipambience.ogg'), repeat = 0, wait = 0, volume = 0, channel = 2)
else
- usr << sound('shipambience.ogg', repeat = 1, wait = 0, volume = 35, channel = 2)
+ usr << sound(pick('shipambience.ogg'), repeat = 1, wait = 0, volume = 35, channel = 2)
usr << "Toggled ambience sound."
return
diff --git a/code/game/turf.dm b/code/game/turf.dm
index 8da08eacb13..efdcb7b2fe0 100644
--- a/code/game/turf.dm
+++ b/code/game/turf.dm
@@ -459,6 +459,9 @@
usr << "\red You don't have the dexterity to do this!"
return
+ if(!istype(src, /turf/simulated/wall/r_wall))
+ return // this may seem stupid and redundant but apparently floors can call this attackby() proc, it was spamming shit up. -- Doohl
+
if (istype(W, /obj/item/weapon/weldingtool) && W:welding)
W:eyecheck(user)
var/turf/T = user.loc
diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm
index 8ba9a6c4bcf..81c9f081852 100644
--- a/code/modules/admin/admin.dm
+++ b/code/modules/admin/admin.dm
@@ -2556,6 +2556,7 @@
if (!M.mind)
usr << "Sorry, this mob has no mind!"
+ return
M.mind.edit_memory()
diff --git a/code/modules/admin/player_panel.dm b/code/modules/admin/player_panel.dm
index 3ccc5ff58b1..879502d8d33 100644
--- a/code/modules/admin/player_panel.dm
+++ b/code/modules/admin/player_panel.dm
@@ -92,8 +92,7 @@
PP |
VV |
SM |
- JMP |
- CA
+ JMP
[muting]
Warn | Boot | Ban | Jobban
"}
diff --git a/code/modules/chemical/Chemistry-Tools.dm b/code/modules/chemical/Chemistry-Tools.dm
index 87230391edb..6a601344567 100644
--- a/code/modules/chemical/Chemistry-Tools.dm
+++ b/code/modules/chemical/Chemistry-Tools.dm
@@ -534,15 +534,16 @@
if(D.loc == trg) break
step_towards(D,trg)
- for(var/mob/living/carbon/M in D.loc)
- if(!istype(M,/mob/living/carbon)) continue
- if(M == user) continue
- D.reagents.trans_to(M, 15)
- M.take_organ_damage(5)
- for(var/mob/O in viewers(world.view, D))
- O.show_message(text("\red [] was hit by the syringe!", M), 1)
+ if(D)
+ for(var/mob/living/carbon/M in D.loc)
+ if(!istype(M,/mob/living/carbon)) continue
+ if(M == user) continue
+ D.reagents.trans_to(M, 15)
+ M.take_organ_damage(5)
+ for(var/mob/O in viewers(world.view, D))
+ O.show_message(text("\red [] was hit by the syringe!", M), 1)
- del(D)
+ del(D)
if(D)
for(var/atom/A in D.loc)
if(A == user) continue
diff --git a/code/modules/mob/living/silicon/ai/laws.dm b/code/modules/mob/living/silicon/ai/laws.dm
index 682b11dd7bf..06407c5f562 100755
--- a/code/modules/mob/living/silicon/ai/laws.dm
+++ b/code/modules/mob/living/silicon/ai/laws.dm
@@ -108,10 +108,11 @@
var/law = src.laws.supplied[index]
if (length(law) > 0)
- if (src.lawcheck[number+1] == "Yes")
- src.say("[number]. [law]")
- sleep(10)
- number++
+ if(src.lawcheck.len >= number+1)
+ if (src.lawcheck[number+1] == "Yes")
+ src.say("[number]. [law]")
+ sleep(10)
+ number++
/mob/living/silicon/ai/verb/checklaws() //Gives you a link-driven interface for deciding what laws the statelaws() proc will share with the crew. --NeoFite
diff --git a/code/modules/mob/living/silicon/say.dm b/code/modules/mob/living/silicon/say.dm
index 53577373c05..fa8ae87b1fd 100644
--- a/code/modules/mob/living/silicon/say.dm
+++ b/code/modules/mob/living/silicon/say.dm
@@ -85,9 +85,8 @@
var/rendered = "Robotic Talk, [name] [message_a]"
for (var/mob/living/S in world)
if(!S.stat)
- if(S.robot_talk_understand)
- if(S.robot_talk_understand == robot_talk_understand)
- S.show_message(rendered, 2)
+ if(S.robot_talk_understand && (S.robot_talk_understand == robot_talk_understand))
+ S.show_message(rendered, 2)
else if (S.binarycheck())
S.show_message(rendered, 2)
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 0a4baebcaec..327bcbb0c14 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -614,7 +614,7 @@
//This should have a check to prevent the player to player chat but I am too tired atm to add it.
var/t = input("Message:", text("Private message to [recipient_name]")) as text|null
- if (!t || !usr || !M)
+ if (!t || !usr || !usr.client)
return
if (usr.client && usr.client.holder)
M << "\red Admin PM from-[key_name(usr, M, 0)]: [t]"
diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm
index c84b909cd33..3539ddf8080 100644
--- a/code/modules/mob/mob_helpers.dm
+++ b/code/modules/mob/mob_helpers.dm
@@ -313,3 +313,15 @@ It's fairly easy to fix if dealing with single letters but not so much with comp
return 0
+/mob/proc/put_in_hands(var/obj/item/I)
+ if(!r_hand)
+ I.loc = src
+ r_hand = I
+ I.layer = 20
+ else if(!l_hand)
+ I.loc = src
+ l_hand = I
+ I.layer = 20
+ else
+ I.loc = get_turf(src)
+ update_clothing()
diff --git a/code/modules/recycling/disposal.dm b/code/modules/recycling/disposal.dm
index cf605048c7e..9c0ea592f27 100644
--- a/code/modules/recycling/disposal.dm
+++ b/code/modules/recycling/disposal.dm
@@ -55,8 +55,8 @@
if(istype(I, /obj/item/weapon/trashbag))
user << "\blue You empty the bag."
for(var/obj/item/O in I.contents)
- I.contents -= O
O.loc = src
+ I.contents -= O
I.update_icon()
update()
return
@@ -101,8 +101,9 @@
return // No idea why, but somehow I gets nulled before it goes into the else, and that leads to a lot of spam with runtime errors.
user.drop_item()
+ if(I)
+ I.loc = src
- I.loc = src
user << "You place \the [I] into the [src]."
for(var/mob/M in viewers(src))
if(M == user)
@@ -305,6 +306,7 @@
for(var/atom/movable/AM in src)
AM.loc = src.loc
AM.pipe_eject(0)
+ islarge = 0
update()
// update the icon & overlays to reflect mode & status
diff --git a/code/modules/scripting/AST/AST Nodes.dm b/code/modules/scripting/AST/AST Nodes.dm
new file mode 100644
index 00000000000..6853697ebf9
--- /dev/null
+++ b/code/modules/scripting/AST/AST Nodes.dm
@@ -0,0 +1,139 @@
+/*
+ File: AST Nodes
+ An abstract syntax tree (AST) is a representation of source code in a computer-friendly format. It is composed of nodes,
+ each of which represents a certain part of the source code. For example, an node represents an if statement in the
+ script's source code. Because it is a representation of the source code in memory, it is independent of any specific scripting language.
+ This allows a script in any language for which a parser exists to be run by the interpreter.
+
+ The AST is produced by an object. It consists of a with an arbitrary amount of statements. These statements are
+ run in order by an object. A statement may in turn run another block (such as an if statement might if its condition is
+ met).
+
+ Articles:
+ -
+*/
+var
+ const
+/*
+ Constants: Operator Precedence
+ OOP_OR - Logical or
+ OOP_AND - Logical and
+ OOP_BIT - Bitwise operations
+ OOP_EQUAL - Equality checks
+ OOP_COMPARE - Greater than, less then, etc
+ OOP_ADD - Addition and subtraction
+ OOP_MULTIPLY - Multiplication and division
+ OOP_POW - Exponents
+ OOP_UNARY - Unary Operators
+ OOP_GROUP - Parentheses
+*/
+ OOP_OR = 1 //||
+ OOP_AND = OOP_OR + 1 //&&
+ OOP_BIT = OOP_AND + 1 //&, |
+ OOP_EQUAL = OOP_BIT + 1 //==, !=
+ OOP_COMPARE = OOP_EQUAL + 1 //>, <, >=, <=
+ OOP_ADD = OOP_COMPARE + 1 //+, -
+ OOP_MULTIPLY= OOP_ADD + 1 //*, /, %
+ OOP_POW = OOP_MULTIPLY+ 1 //^
+ OOP_UNARY = OOP_POW + 1 //!
+ OOP_GROUP = OOP_UNARY + 1 //()
+
+/*
+ Class: node
+*/
+/node
+ proc
+ ToString()
+ return "[src.type]"
+/*
+ Class: identifier
+*/
+/node/identifier
+ var
+ id_name
+
+ New(id)
+ .=..()
+ src.id_name=id
+
+ ToString()
+ return id_name
+
+/*
+ Class: expression
+*/
+/node/expression
+/*
+ Class: operator
+ See and for subtypes.
+*/
+/node/expression/operator
+ var
+ node/expression/exp
+ tmp
+ name
+ precedence
+
+ New()
+ .=..()
+ if(!src.name) src.name="[src.type]"
+
+ ToString()
+ return "operator: [name]"
+
+/*
+ Class: FunctionCall
+*/
+/node/expression/FunctionCall
+ //Function calls can also be expressions or statements.
+ var
+ func_name
+ node/identifier/object
+ list/parameters=new
+
+/*
+ Class: literal
+*/
+/node/expression/value/literal
+ var
+ value
+
+ New(value)
+ .=..()
+ src.value=value
+
+ ToString()
+ return src.value
+
+/*
+ Class: variable
+*/
+/node/expression/value/variable
+ var
+ node
+ object //Either a node/identifier or another node/expression/value/variable which points to the object
+ node/identifier
+ id
+
+
+ New(ident)
+ .=..()
+ id=ident
+ if(istext(id))id=new(id)
+
+ ToString()
+ return src.id.ToString()
+
+/*
+ Class: reference
+*/
+/node/expression/value/reference
+ var
+ datum/value
+
+ New(value)
+ .=..()
+ src.value=value
+
+ ToString()
+ return "ref: [src.value] ([src.value.type])"
\ No newline at end of file
diff --git a/code/modules/scripting/AST/Blocks.dm b/code/modules/scripting/AST/Blocks.dm
new file mode 100644
index 00000000000..18f408f1f1b
--- /dev/null
+++ b/code/modules/scripting/AST/Blocks.dm
@@ -0,0 +1,48 @@
+/*
+ File: Block Types
+*/
+/*
+ Class: BlockDefinition
+ An object representing a set of actions to perform independently from the rest of the script. Blocks are basically just
+ lists of statements to execute which also contain some local variables and methods. Note that since functions are local to a block,
+ it is possible to have a function definition inside of any type of block (such as in an if statement or another function),
+ and not just in the global scope as in many languages.
+*/
+/node/BlockDefinition
+ var/list
+ statements = new
+ functions = new
+ initial_variables = new
+
+ proc
+/*
+ Proc: SetVar
+ Defines a permanent variable. The variable will not be deleted when it goes out of scope.
+
+ Notes:
+ Since all pre-existing temporary variables are deleted, it is not generally desirable to use this proc after the interpreter has been instantiated.
+ Instead, use .
+
+ See Also:
+ -
+*/
+ SetVar(name, value)
+ initial_variables[name]=value
+
+
+/*
+ Class: GlobalBlock
+ A block object representing the global scope.
+*/
+//
+ GlobalBlock
+ New()
+ initial_variables["null"]=null
+ return ..()
+
+/*
+ Class: FunctionBlock
+ A block representing a function body.
+*/
+//
+ FunctionBlock
\ No newline at end of file
diff --git a/code/modules/scripting/AST/Operators/Binary Operators.dm b/code/modules/scripting/AST/Operators/Binary Operators.dm
new file mode 100644
index 00000000000..138ff00c3b3
--- /dev/null
+++ b/code/modules/scripting/AST/Operators/Binary Operators.dm
@@ -0,0 +1,173 @@
+/*
+ File: Binary Operators
+*/
+/*
+ Class: binary
+ Represents a binary operator in the AST. A binary operator takes two operands (ie x and y) and returns a value.
+*/
+/node/expression/operator/binary
+ var
+ node/expression/exp2
+
+////////// Comparison Operators //////////
+/*
+ Class: Equal
+ Returns true if x = y.
+*/
+//
+ Equal
+ precedence=OOP_EQUAL
+
+/*
+ Class: NotEqual
+ Returns true if x and y aren't equal.
+*/
+//
+ NotEqual
+ precedence=OOP_EQUAL
+
+/*
+ Class: Greater
+ Returns true if x > y.
+*/
+//
+ Greater
+ precedence=OOP_COMPARE
+
+/*
+ Class: Less
+ Returns true if x < y.
+*/
+//
+ Less
+ precedence=OOP_COMPARE
+
+/*
+ Class: GreaterOrEqual
+ Returns true if x >= y.
+*/
+//
+ GreaterOrEqual
+ precedence=OOP_COMPARE
+
+/*
+ Class: LessOrEqual
+ Returns true if x <= y.
+*/
+//
+ LessOrEqual
+ precedence=OOP_COMPARE
+
+
+////////// Logical Operators //////////
+
+/*
+ Class: LogicalAnd
+ Returns true if x and y are true.
+*/
+//
+ LogicalAnd
+ precedence=OOP_AND
+
+/*
+ Class: LogicalOr
+ Returns true if x, y, or both are true.
+*/
+//
+ LogicalOr
+ precedence=OOP_OR
+
+/*
+ Class: LogicalXor
+ Returns true if either x or y but not both are true.
+*/
+//
+ LogicalXor //Not implemented in nS
+ precedence=OOP_OR
+
+
+////////// Bitwise Operators //////////
+
+/*
+ Class: BitwiseAnd
+ Performs a bitwise and operation.
+
+ Example:
+ 011 & 110 = 010
+*/
+//
+ BitwiseAnd
+ precedence=OOP_BIT
+
+/*
+ Class: BitwiseOr
+ Performs a bitwise or operation.
+
+ Example:
+ 011 | 110 = 111
+*/
+//
+ BitwiseOr
+ precedence=OOP_BIT
+
+/*
+ Class: BitwiseXor
+ Performs a bitwise exclusive or operation.
+
+ Example:
+ 011 xor 110 = 101
+*/
+//
+ BitwiseXor
+ precedence=OOP_BIT
+
+
+////////// Arithmetic Operators //////////
+
+/*
+ Class: Add
+ Returns the sum of x and y.
+*/
+//
+ Add
+ precedence=OOP_ADD
+
+/*
+ Class: Subtract
+ Returns the difference of x and y.
+*/
+//
+ Subtract
+ precedence=OOP_ADD
+
+/*
+ Class: Multiply
+ Returns the product of x and y.
+*/
+//
+ Multiply
+ precedence=OOP_MULTIPLY
+
+/*
+ Class: Divide
+ Returns the quotient of x and y.
+*/
+//
+ Divide
+ precedence=OOP_MULTIPLY
+
+/*
+ Class: Power
+ Returns x raised to the power of y.
+*/
+//
+ Power
+ precedence=OOP_POW
+
+/*
+ Class: Modulo
+ Returns the remainder of x / y.
+*/
+//
+ Modulo
+ precedence=OOP_MULTIPLY
\ No newline at end of file
diff --git a/code/modules/scripting/AST/Operators/Unary Operators.dm b/code/modules/scripting/AST/Operators/Unary Operators.dm
new file mode 100644
index 00000000000..3a5f3ffb963
--- /dev/null
+++ b/code/modules/scripting/AST/Operators/Unary Operators.dm
@@ -0,0 +1,51 @@
+/*
+ File: Unary Operators
+*/
+/*
+ Class: unary
+ Represents a unary operator in the AST. Unary operators take a single operand (referred to as x below) and return a value.
+*/
+/node/expression/operator/unary
+ precedence=OOP_UNARY
+
+/*
+ Class: LogicalNot
+ Returns !x.
+
+ Example:
+ !true = false and !false = true
+*/
+//
+ LogicalNot
+ name="logical not"
+
+/*
+ Class: BitwiseNot
+ Returns the value of a bitwise not operation performed on x.
+
+ Example:
+ ~10 (decimal 2) = 01 (decimal 1).
+*/
+//
+ BitwiseNot
+ name="bitwise not"
+
+/*
+ Class: Minus
+ Returns -x.
+*/
+//
+ Minus
+ name="minus"
+
+/*
+ Class: group
+ A special unary operator representing a value in parentheses.
+*/
+//
+ group
+ precedence=OOP_GROUP
+
+ New(node/expression/exp)
+ src.exp=exp
+ return ..()
diff --git a/code/modules/scripting/AST/Statements.dm b/code/modules/scripting/AST/Statements.dm
new file mode 100644
index 00000000000..e60fcb389c5
--- /dev/null
+++ b/code/modules/scripting/AST/Statements.dm
@@ -0,0 +1,122 @@
+/*
+ File: Statement Types
+*/
+/*
+ Class: statement
+ An object representing a single instruction run by an interpreter.
+*/
+/node/statement
+/*
+ Class: FunctionCall
+ Represents a call to a function.
+*/
+//
+ FunctionCall
+ var
+ func_name
+ node/identifier/object
+ list/parameters=new
+
+/*
+ Class: FunctionDefinition
+ Defines a function.
+*/
+//
+ FunctionDefinition
+ var
+ func_name
+ list/parameters=new
+ node/BlockDefinition/FunctionBlock/block
+
+/*
+ Class: VariableAssignment
+ Sets a variable in an accessible scope to the given value if one exists, otherwise initializes a new local variable to the given value.
+
+ Notes:
+ If a variable with the same name exists in a higher block, the value will be assigned to it. If not,
+ a new variable is created in the current block. To force creation of a new variable, use .
+
+ See Also:
+ -
+*/
+//
+ VariableAssignment
+ var
+ node
+ identifier
+ object
+ var_name
+ expression/value
+
+/*
+ Class: VariableDeclaration
+ Intializes a local variable to a null value.
+
+ See Also:
+ -
+*/
+//
+ VariableDeclaration
+ var
+ node
+ identifier
+ object
+ var_name
+
+/*
+ Class: IfStatement
+*/
+//
+ IfStatement
+ var
+ node
+ BlockDefinition
+ block
+ else_block //may be null
+ expression/cond
+
+/*
+ Class: WhileLoop
+ Loops while a given condition is true.
+*/
+//
+ WhileLoop
+ var
+ node
+ BlockDefinition/block
+ expression/cond
+
+/*
+ Class: ForLoop
+ Loops while test is true, initializing a variable, increasing the variable
+*/
+ ForLoop
+ var
+ node
+ BlockDefinition/block
+ expression/test
+ expression/init
+ expression/increment
+
+/*
+ Class: BreakStatement
+ Ends a loop.
+*/
+//
+ BreakStatement
+
+/*
+ Class: ContinueStatement
+ Skips to the next iteration of a loop.
+*/
+//
+ ContinueStatement
+
+/*
+ Class: ReturnStatement
+ Ends the function and returns a value.
+*/
+//
+ ReturnStatement
+ var
+ node/expression/value
\ No newline at end of file
diff --git a/code/modules/scripting/Errors.dm b/code/modules/scripting/Errors.dm
new file mode 100644
index 00000000000..361d762048f
--- /dev/null
+++ b/code/modules/scripting/Errors.dm
@@ -0,0 +1,124 @@
+/*
+ File: Errors
+*/
+/*
+ Class: scriptError
+ An error scanning or parsing the source code.
+*/
+/scriptError
+ var
+/*
+ Var: message
+ A message describing the problem.
+*/
+ message
+ New(msg=null)
+ if(msg)message=msg
+
+ BadToken
+ message="Unexpected token: "
+ var/token/token
+ New(token/t)
+ token=t
+ if(t&&t.line) message="[t.line]: [message]"
+ if(istype(t))message+="[t.value]"
+ else message+="[t]"
+
+ InvalidID
+ parent_type=/scriptError/BadToken
+ message="Invalid identifier name: "
+
+ ReservedWord
+ parent_type=/scriptError/BadToken
+ message="Identifer using reserved word: "
+
+ BadNumber
+ parent_type=/scriptError/BadToken
+ message = "Bad number: "
+
+ BadReturn
+ var/token/token
+ message = "Unexpected return statement outside of a function."
+ New(token/t)
+ src.token=t
+
+ EndOfFile
+ message = "Unexpected end of file."
+
+ ExpectedToken
+ message="Expected: '"
+ New(id, token/T)
+ if(T && T.line) message="[T.line]: [message]"
+ message+="[id]'. "
+ if(T)message+="Found '[T.value]'."
+
+ DuplicateFunction
+ New(name, token/t)
+ message="Function '[name]' defined twice."
+
+/*
+ Class: runtimeError
+ An error thrown by the interpreter in running the script.
+*/
+/runtimeError
+ var
+ name
+/*
+ Var: message
+ A basic description as to what went wrong.
+*/
+ message
+ stack/stack
+
+ proc
+/*
+ Proc: ToString
+ Returns a description of the error suitable for showing to the user.
+*/
+ ToString()
+ . = "[name]: [message]"
+ if(!stack.Top()) return
+ .+="\nStack:"
+ while(stack.Top())
+ var/node/statement/FunctionCall/stmt=stack.Pop()
+ . += "\n\t [stmt.func_name]()"
+
+ TypeMismatch
+ name="TypeMismatchError"
+ New(op, a, b)
+ message="Type mismatch: '[a]' [op] '[b]'"
+
+ UnexpectedReturn
+ name="UnexpectedReturnError"
+ message="Unexpected return statement."
+
+ UnknownInstruction
+ name="UnknownInstructionError"
+ message="Unknown instruction type. This may be due to incompatible compiler and interpreter versions or a lack of implementation."
+
+ UndefinedVariable
+ name="UndefinedVariableError"
+ New(variable)
+ message="Variable '[variable]' has not been declared."
+
+ UndefinedFunction
+ name="UndefinedFunctionError"
+ New(function)
+ message="Function '[function]()' has not been defined."
+
+ DuplicateVariableDeclaration
+ name="DuplicateVariableError"
+ New(variable)
+ message="Variable '[variable]' was already declared."
+
+ IterationLimitReached
+ name="MaxIterationError"
+ message="A loop has reached its maximum number of iterations."
+
+ RecursionLimitReached
+ name="MaxRecursionError"
+ message="The maximum amount of recursion has been reached."
+
+ DivisionByZero
+ name="DivideByZeroError"
+ message="Division by zero attempted."
\ No newline at end of file
diff --git a/code/modules/scripting/IDE.dm b/code/modules/scripting/IDE.dm
new file mode 100644
index 00000000000..f45ad8d400c
--- /dev/null
+++ b/code/modules/scripting/IDE.dm
@@ -0,0 +1,182 @@
+client/verb/tcssave()
+ set hidden = 1
+ if(mob.machine)
+ if(istype(mob.machine, /obj/machinery/computer/telecomms/traffic) && mob.machine in view(1, mob))
+ var/obj/machinery/computer/telecomms/traffic/Machine = mob.machine
+ if(Machine.editingcode != mob)
+ return
+
+ if(Machine.SelectedServer)
+ var/obj/machinery/telecomms/server/Server = Machine.SelectedServer
+ Server.setcode( winget(src, "tcscode", "text") ) // this actually saves the code from input to the server
+ src << output(null, "tcserror") // clear the errors
+ else
+ src << output(null, "tcserror")
+ src << output("Failed to save: Unable to locate server machine. (Back up your code before exiting the window!)", "tcserror")
+ else
+ src << output(null, "tcserror")
+ src << output("Failed to save: Unable to locate machine. (Back up your code before exiting the window!)", "tcserror")
+ else
+ src << output(null, "tcserror")
+ src << output("Failed to save: Unable to locate machine. (Back up your code before exiting the window!)", "tcserror")
+
+
+client/verb/tcscompile()
+ set hidden = 1
+ if(mob.machine)
+ if(istype(mob.machine, /obj/machinery/computer/telecomms/traffic) && mob.machine in view(1, mob))
+ var/obj/machinery/computer/telecomms/traffic/Machine = mob.machine
+ if(Machine.editingcode != mob)
+ return
+
+ if(Machine.SelectedServer)
+ var/obj/machinery/telecomms/server/Server = Machine.SelectedServer
+ Server.setcode( winget(src, "tcscode", "text") ) // save code first
+ var/list/compileerrors = Server.compile() // then compile the code!
+
+ // Output all the compile-time errors
+ src << output(null, "tcserror")
+
+ if(compileerrors.len)
+ src << output("Compile Errors", "tcserror")
+ for(var/scriptError/e in compileerrors)
+ src << output("\t>[e.message]", "tcserror")
+ src << output("([compileerrors.len] errors)", "tcserror")
+
+ // Output compile errors to all other people viewing the code too
+ for(var/mob/M in Machine.viewingcode)
+ if(M.client)
+ M << output(null, "tcserror")
+ M << output("Compile Errors", "tcserror")
+ for(var/scriptError/e in compileerrors)
+ M << output("\t>[e.message]", "tcserror")
+ M << output("([compileerrors.len] errors)", "tcserror")
+
+
+ else
+ src << output("TCS compilation successful!", "tcserror")
+ src << output("(0 errors)", "tcserror")
+
+ for(var/mob/M in Machine.viewingcode)
+ if(M.client)
+ M << output("TCS compilation successful!", "tcserror")
+ M << output("(0 errors)", "tcserror")
+
+ else
+ src << output(null, "tcserror")
+ src << output("Failed to compile: Unable to locate server machine. (Back up your code before exiting the window!)", "tcserror")
+ else
+ src << output(null, "tcserror")
+ src << output("Failed to compile: Unable to locate machine. (Back up your code before exiting the window!)", "tcserror")
+ else
+ src << output(null, "tcserror")
+ src << output("Failed to compile: Unable to locate machine. (Back up your code before exiting the window!)", "tcserror")
+
+client/verb/tcsrun()
+ set hidden = 1
+ if(mob.machine)
+ if(istype(mob.machine, /obj/machinery/computer/telecomms/traffic) && mob.machine in view(1, mob))
+ var/obj/machinery/computer/telecomms/traffic/Machine = mob.machine
+ if(Machine.editingcode != mob)
+ return
+
+ if(Machine.SelectedServer)
+ var/obj/machinery/telecomms/server/Server = Machine.SelectedServer
+ Server.setcode( winget(src, "tcscode", "text") ) // save code first
+ var/list/compileerrors = Server.compile() // then compile the code!
+
+ // Output all the compile-time errors
+ src << output(null, "tcserror")
+
+ if(compileerrors.len)
+ src << output("Compile Errors", "tcserror")
+ for(var/scriptError/e in compileerrors)
+ src << output("\t>[e.message]", "tcserror")
+ src << output("([compileerrors.len] errors)", "tcserror")
+
+ // Output compile errors to all other people viewing the code too
+ for(var/mob/M in Machine.viewingcode)
+ if(M.client)
+ M << output(null, "tcserror")
+ M << output("Compile Errors", "tcserror")
+ for(var/scriptError/e in compileerrors)
+ M << output("\t>[e.message]", "tcserror")
+ M << output("([compileerrors.len] errors)", "tcserror")
+
+ else
+ // Finally, we run the code!
+ src << output("TCS compilation successful! Code executed.", "tcserror")
+ src << output("(0 errors)", "tcserror")
+
+ for(var/mob/M in Machine.viewingcode)
+ if(M.client)
+ M << output("TCS compilation successful!", "tcserror")
+ M << output("(0 errors)", "tcserror")
+
+ var/datum/signal/signal = new()
+ signal.data["message"] = ""
+ if(Server.freq_listening.len > 0)
+ signal.frequency = Server.freq_listening[1]
+ else
+ signal.frequency = 1459
+ signal.data["name"] = ""
+ signal.data["job"] = ""
+ signal.data["reject"] = 0
+ signal.data["server"] = Server
+
+ Server.Compiler.Run(signal)
+
+
+ else
+ src << output(null, "tcserror")
+ src << output("Failed to run: Unable to locate server machine. (Back up your code before exiting the window!)", "tcserror")
+ else
+ src << output(null, "tcserror")
+ src << output("Failed to run: Unable to locate machine. (Back up your code before exiting the window!)", "tcserror")
+ else
+ src << output(null, "tcserror")
+ src << output("Failed to run: Unable to locate machine. (Back up your code before exiting the window!)", "tcserror")
+
+
+client/verb/exittcs()
+ set hidden = 1
+ if(mob.machine)
+ if(istype(mob.machine, /obj/machinery/computer/telecomms/traffic))
+ var/obj/machinery/computer/telecomms/traffic/Machine = mob.machine
+ if(Machine.editingcode == mob)
+ Machine.storedcode = "[winget(mob, "tcscode", "text")]"
+ Machine.editingcode = null
+ else
+ if(mob in Machine.viewingcode)
+ Machine.viewingcode.Remove(mob)
+
+client/verb/tcsrevert()
+ set hidden = 1
+ if(mob.machine)
+ if(istype(mob.machine, /obj/machinery/computer/telecomms/traffic) && mob.machine in view(1, mob))
+ var/obj/machinery/computer/telecomms/traffic/Machine = mob.machine
+ if(Machine.editingcode != mob)
+ return
+
+ if(Machine.SelectedServer)
+ var/obj/machinery/telecomms/server/Server = Machine.SelectedServer
+
+ // Replace quotation marks with quotation macros for proper winset() compatibility
+ var/showcode = dd_replacetext(Server.rawcode, "\\\"", "\\\\\"")
+ showcode = dd_replacetext(showcode, "\"", "\\\"")
+
+ winset(mob, "tcscode", "text=\"[showcode]\"")
+
+ src << output(null, "tcserror") // clear the errors
+ else
+ src << output(null, "tcserror")
+ src << output("Failed to revert: Unable to locate server machine.", "tcserror")
+ else
+ src << output(null, "tcserror")
+ src << output("Failed to revert: Unable to locate machine.", "tcserror")
+ else
+ src << output(null, "tcserror")
+ src << output("Failed to revert: Unable to locate machine.", "tcserror")
+
+
+
diff --git a/code/modules/scripting/Implementations/Telecomms.dm b/code/modules/scripting/Implementations/Telecomms.dm
new file mode 100644
index 00000000000..d9f6e2ee851
--- /dev/null
+++ b/code/modules/scripting/Implementations/Telecomms.dm
@@ -0,0 +1,236 @@
+
+/* --- Traffic Control Scripting Language --- */
+ // Nanotrasen TCS Language - Made by Doohl
+
+/n_Interpreter/TCS_Interpreter
+ var/datum/TCS_Compiler/Compiler
+
+ HandleError(runtimeError/e)
+ Compiler.Holder.add_entry(e.ToString(), "Execution Error")
+
+/datum/TCS_Compiler
+ var/n_Interpreter/TCS_Interpreter/interpreter
+ var/obj/machinery/telecomms/server/Holder // the server that is running the code
+ var/ready = 1 // 1 if ready to run code
+
+ /* -- Compile a raw block of text -- */
+
+ proc/Compile(code as message)
+ var
+ n_scriptOptions/nS_Options/options = new()
+ n_Scanner/nS_Scanner/scanner = new(code, options)
+ list/tokens = scanner.Scan()
+ n_Parser/nS_Parser/parser = new(tokens, options)
+ node/BlockDefinition/GlobalBlock/program = parser.Parse()
+
+ list/returnerrors = list()
+
+ returnerrors += scanner.errors
+ returnerrors += parser.errors
+
+ if(returnerrors.len)
+ return returnerrors
+
+ interpreter = new(program)
+ interpreter.persist = 1
+ interpreter.Compiler= src
+
+ // Set up all the preprocessor bullshit
+ //TCS_Setup(program)
+ // Apply preprocessor global variables
+ program.SetVar("PI" , 3.141592653) // value of pi
+ program.SetVar("E" , 2.718281828) // value of e
+ program.SetVar("SQURT2" , 1.414213562) // value of the square root of 2
+ program.SetVar("FALSE" , 0) // boolean shortcut to 0
+ program.SetVar("TRUE" , 1) // boolean shortcut to 1
+
+ program.SetVar("NORTH" , NORTH) // NORTH (1)
+ program.SetVar("SOUTH" , SOUTH) // SOUTH (2)
+ program.SetVar("EAST" , EAST) // EAST (4)
+ program.SetVar("WEST" , WEST) // WEST (8)
+
+ program.SetVar("HONK" , "clown griff u")
+ program.SetVar("CODERS" , "hide the fun")
+ program.SetVar("GRIFF" , pick("HALP IM BEING GRIFFED", "HALP AI IS MALF", "HALP GRIFFE", "HALP TRAITORS", "HALP WIZ GRIEFE ME"))
+
+
+ return returnerrors
+
+ /* -- Execute the compiled code -- */
+
+ proc/Run(var/datum/signal/signal)
+
+ if(!ready)
+ return
+
+ interpreter.SetVar("$content", signal.data["message"])
+ interpreter.SetVar("$freq" , signal.frequency)
+ interpreter.SetVar("$source" , signal.data["name"])
+ interpreter.SetVar("$job" , signal.data["job"])
+ interpreter.SetVar("$sign" , signal)
+ interpreter.SetVar("$pass" , !(signal.data["reject"])) // if the signal isn't rejected, pass = 1; if the signal IS rejected, pass = 0
+
+ // Set up the script procs
+
+ /*
+ -> Send another signal to a server
+ @format: broadcast(content, frequency, source, job)
+
+ @param content: Message to broadcast
+ @param frequency: Frequency to broadcast to
+ @param source: The name of the source you wish to imitate. Must be stored in stored_names list.
+ @param job: The name of the job.
+ */
+ interpreter.SetProc("broadcast", "tcombroadcast", signal, list("message", "freq", "source", "job"))
+
+ /*
+ -> Store a value permanently to the server machine (not the actual game hosting machine, the ingame machine)
+ @format: mem(address, value)
+
+ @param address: The memory address (string index) to store a value to
+ @param value: The value to store to the memory address
+ */
+ interpreter.SetProc("mem", "mem", signal, list("address", "value"))
+
+ /*
+ -> Delay code for a given amount of deciseconds
+ @format: sleep(time)
+
+ @param time: time to sleep in deciseconds (1/10th second)
+ */
+ interpreter.SetProc("sleep", /proc/delay)
+
+ /*
+ -> Replaces a string with another string
+ @format: replace(string, substring, replacestring)
+
+ @param string: the string to search for substrings (best used with $content$ constant)
+ @param substring: the substring to search for
+ @param replacestring: the string to replace the substring with
+
+ */
+ interpreter.SetProc("replace", /proc/dd_replacetext)
+
+ /*
+ -> Locates an element/substring inside of a list or string
+ @format: find(haystack, needle, start = 1, end = 0)
+
+ @param haystack: the container to search
+ @param needle: the element to search for
+ @param start: the position to start in
+ @param end: the position to end in
+
+ */
+ interpreter.SetProc("find", /proc/smartfind)
+
+ /*
+ -> Finds the length of a string or list
+ @format: length(container)
+
+ @param container: the list or container to measure
+
+ */
+ interpreter.SetProc("length", /proc/smartfind)
+
+ /* -- Clone functions, carried from default BYOND procs --- */
+
+ // vector namespace
+ interpreter.SetProc("vector", /proc/n_list)
+ interpreter.SetProc("at", /proc/n_listpos)
+ interpreter.SetProc("copy", /proc/n_listcopy)
+ interpreter.SetProc("push_back", /proc/n_listadd)
+ interpreter.SetProc("remove", /proc/n_listremove)
+ interpreter.SetProc("cut", /proc/n_listcut)
+ interpreter.SetProc("swap", /proc/n_listswap)
+ interpreter.SetProc("insert", /proc/n_listinsert)
+
+ interpreter.SetProc("pick", /proc/n_pick)
+ interpreter.SetProc("prob", /proc/prob_chance)
+ interpreter.SetProc("substr", /proc/docopytext)
+
+
+
+ // Run the compiled code
+ interpreter.Run()
+
+ // Backwards-apply variables onto signal data
+ /* html_encode() EVERYTHING. fucking players can't be trusted with SHIT */
+
+ signal.data["message"] = html_encode(interpreter.GetVar("$content"))
+ signal.frequency = interpreter.GetVar("$freq")
+
+ var/setname = ""
+ var/obj/machinery/telecomms/server/S = signal.data["server"]
+ if(interpreter.GetVar("$source") in S.stored_names)
+ setname = html_encode(interpreter.GetVar("$source"))
+ else
+ setname = "[html_encode(interpreter.GetVar("$source"))]"
+
+ if(signal.data["name"] != setname)
+ signal.data["realname"] = setname
+ signal.data["name"] = setname
+ signal.data["job"] = html_encode(interpreter.GetVar("$job"))
+ signal.data["reject"] = !(interpreter.GetVar("$pass")) // set reject to the opposite of $pass
+
+
+/* -- Actual language proc code -- */
+
+datum/signal
+
+ proc/mem(var/address, var/value)
+
+ if(istext(address))
+ var/obj/machinery/telecomms/server/S = data["server"]
+
+ if(!value)
+ return S.memory[address]
+
+ else
+ S.memory[address] = value
+
+
+ proc/tcombroadcast(var/message, var/freq, var/source, var/job)
+
+ var/mob/living/carbon/human/H = new
+ var/datum/signal/newsign = new
+ var/obj/machinery/telecomms/server/S = data["server"]
+ var/obj/item/device/radio/hradio
+
+ if(!message)
+ message = "*beep*"
+ if(!source)
+ source = "[html_encode(uppertext(S.id))]"
+ hradio = new // sets the hradio as a radio intercom
+ if(!freq)
+ freq = 1459
+ if(!job)
+ job = "None"
+
+ newsign.data["mob"] = H
+ newsign.data["mobtype"] = H.type
+ if(source in S.stored_names)
+ newsign.data["name"] = source
+ else
+ newsign.data["name"] = "[html_encode(uppertext(source))]"
+ newsign.data["realname"] = newsign.data["name"]
+ newsign.data["job"] = html_encode(job)
+ newsign.data["compression"] = 0
+ newsign.data["message"] = html_encode(message)
+ newsign.data["type"] = 2 // artificial broadcast
+ if(!isnum(freq))
+ freq = text2num(freq)
+ newsign.frequency = freq
+
+ var/datum/radio_frequency/connection = radio_controller.return_frequency(freq)
+ newsign.data["connection"] = connection
+
+ // The radio is a radio headset!
+
+ if(!hradio)
+ hradio = new /obj/item/device/radio/headset
+
+ newsign.data["radio"] = hradio
+ newsign.data["vmessage"] = H.voice_message
+ newsign.data["vname"] = H.voice_name
+ newsign.data["vmask"] = 0
+ S.relay_information(newsign, "/obj/machinery/telecomms/broadcaster") // send this simple message to broadcasters
diff --git a/code/modules/scripting/Implementations/_Logic.dm b/code/modules/scripting/Implementations/_Logic.dm
new file mode 100644
index 00000000000..e6ae13fdedf
--- /dev/null
+++ b/code/modules/scripting/Implementations/_Logic.dm
@@ -0,0 +1,123 @@
+// Script -> BYOND code procs
+
+// --- List operations (lists known as vectors in n_script) ---
+
+// Clone of list()
+/proc/n_list()
+ var/list/returnlist = list()
+ for(var/e in args)
+ returnlist.Add(e)
+ return returnlist
+
+// Clone of pick()
+/proc/n_pick()
+ var/list/finalpick = list()
+ for(var/e in args)
+ if(isobject(e))
+ if(istype(e, /list))
+ var/list/sublist = e
+ for(var/sube in sublist)
+ finalpick.Add(sube)
+ continue
+ finalpick.Add(e)
+
+ return pick(finalpick)
+
+// Clone of list[]
+/proc/n_listpos(var/list/L, var/pos, var/value)
+ if(!istype(L, /list)) return
+ if(isnum(pos))
+ if(!value)
+ if(L.len >= pos)
+ return L[pos]
+ else
+ if(L.len >= pos)
+ L[pos] = value
+ else if(istext(pos))
+ if(!value)
+ return L[pos]
+ else
+ L[pos] = value
+
+// Clone of list.Copy()
+/proc/n_listcopy(var/list/L, var/start, var/end)
+ if(!istype(L, /list)) return
+ return L.Copy(start, end)
+
+// Clone of list.Add()
+/proc/n_listadd()
+ var/list/chosenlist
+ var/i = 1
+ for(var/e in args)
+ if(i == 1)
+ if(isobject(e))
+ if(istype(e, /list))
+ chosenlist = e
+ else
+ if(chosenlist)
+ chosenlist.Add(e)
+
+// Clone of list.Remove()
+/proc/n_listremove()
+ var/list/chosenlist
+ var/i = 1
+ for(var/e in args)
+ if(i == 1)
+ if(isobject(e))
+ if(istype(e, /list))
+ chosenlist = e
+ else
+ if(chosenlist)
+ chosenlist.Remove(e)
+
+// Clone of list.Cut()
+/proc/n_listcut(var/list/L, var/start, var/end)
+ if(!istype(L, /list)) return
+ return L.Cut(start, end)
+
+// Clone of list.Swap()
+/proc/n_listswap(var/list/L, var/firstindex, var/secondindex)
+ if(!istype(L, /list)) return
+ if(L.len >= secondindex && L.len >= firstindex)
+ return L.Swap(firstindex, secondindex)
+
+// Clone of list.Insert()
+/proc/n_listinsert(var/list/L, var/index, var/element)
+ if(!istype(L, /list)) return
+ return L.Insert(index, element)
+
+// --- Miscellaneous functions ---
+
+// Clone of sleep()
+/proc/delay(var/time)
+ sleep(time)
+
+// Clone of prob()
+/proc/prob_chance(var/chance)
+ return prob(chance)
+
+// Merge of list.Find() and findtext()
+/proc/smartfind(var/haystack, var/needle, var/start = 1, var/end = 0)
+ if(haystack && needle)
+ if(isobject(haystack))
+ if(istype(haystack, /list))
+ if(length(haystack) >= end && start > 0)
+ var/list/listhaystack = haystack
+ return listhaystack.Find(needle, start, end)
+
+ else
+ if(istext(haystack))
+ if(length(haystack) >= end && start > 0)
+ return findtext(haystack, needle, start, end)
+
+// Clone of copytext()
+/proc/docopytext(var/string, var/start = 1, var/end = 0)
+ if(istext(string) && isnum(start) && isnum(end))
+ if(length(string) >= end && start > 0)
+ return copytext(string, start, end)
+
+// Clone of length()
+/proc/smartlength(var/container)
+ if(container)
+ if(istype(container, /list) || istext(container))
+ return length(container)
diff --git a/code/modules/scripting/Interpreter/Evaluation.dm b/code/modules/scripting/Interpreter/Evaluation.dm
new file mode 100644
index 00000000000..02cdc929c5c
--- /dev/null
+++ b/code/modules/scripting/Interpreter/Evaluation.dm
@@ -0,0 +1,169 @@
+/proc/isobject(x)
+ return (istype(x, /datum) || istype(x, /list) || istype(x, /savefile) || istype(x, /client) || (x==world))
+
+/n_Interpreter
+ proc
+ Eval(node/expression/exp)
+ if(istype(exp, /node/expression/FunctionCall))
+ return RunFunction(exp)
+ else if(istype(exp, /node/expression/operator))
+ return EvalOperator(exp)
+ else if(istype(exp, /node/expression/value/literal))
+ var/node/expression/value/literal/lit=exp
+ return lit.value
+ else if(istype(exp, /node/expression/value/reference))
+ var/node/expression/value/reference/ref=exp
+ return ref.value
+ else if(istype(exp, /node/expression/value/variable))
+ var/node/expression/value/variable/v=exp
+ if(!v.object)
+ return Eval(GetVariable(v.id.id_name))
+ else
+ var/datum/D
+ if(istype(v.object, /node/identifier))
+ D=GetVariable(v.object:id_name)
+ else
+ D=v.object
+ D=Eval(D)
+ if(!isobject(D))
+ return null
+ if(!D.vars.Find(v.id.id_name))
+ RaiseError(new/runtimeError/UndefinedVariable("[v.object.ToString()].[v.id.id_name]"))
+ return null
+ return Eval(D.vars[v.id.id_name])
+ else if(istype(exp, /node/expression))
+ RaiseError(new/runtimeError/UnknownInstruction())
+ else
+ return exp
+
+ EvalOperator(node/expression/operator/exp)
+ if(istype(exp, /node/expression/operator/binary))
+ var/node/expression/operator/binary/bin=exp
+ switch(bin.type)
+ if(/node/expression/operator/binary/Equal)
+ return Equal(Eval(bin.exp), Eval(bin.exp2))
+ if(/node/expression/operator/binary/NotEqual)
+ return NotEqual(Eval(bin.exp), Eval(bin.exp2))
+ if(/node/expression/operator/binary/Greater)
+ return Greater(Eval(bin.exp), Eval(bin.exp2))
+ if(/node/expression/operator/binary/Less)
+ return Less(Eval(bin.exp), Eval(bin.exp2))
+ if(/node/expression/operator/binary/GreaterOrEqual)
+ return GreaterOrEqual(Eval(bin.exp), Eval(bin.exp2))
+ if(/node/expression/operator/binary/LessOrEqual)
+ return LessOrEqual(Eval(bin.exp), Eval(bin.exp2))
+ if(/node/expression/operator/binary/LogicalAnd)
+ return LogicalAnd(Eval(bin.exp), Eval(bin.exp2))
+ if(/node/expression/operator/binary/LogicalOr)
+ return LogicalOr(Eval(bin.exp), Eval(bin.exp2))
+ if(/node/expression/operator/binary/LogicalXor)
+ return LogicalXor(Eval(bin.exp), Eval(bin.exp2))
+ if(/node/expression/operator/binary/BitwiseAnd)
+ return BitwiseAnd(Eval(bin.exp), Eval(bin.exp2))
+ if(/node/expression/operator/binary/BitwiseOr)
+ return BitwiseOr(Eval(bin.exp), Eval(bin.exp2))
+ if(/node/expression/operator/binary/BitwiseXor)
+ return BitwiseXor(Eval(bin.exp), Eval(bin.exp2))
+ if(/node/expression/operator/binary/Add)
+ return Add(Eval(bin.exp), Eval(bin.exp2))
+ if(/node/expression/operator/binary/Subtract)
+ return Subtract(Eval(bin.exp), Eval(bin.exp2))
+ if(/node/expression/operator/binary/Multiply)
+ return Multiply(Eval(bin.exp), Eval(bin.exp2))
+ if(/node/expression/operator/binary/Divide)
+ return Divide(Eval(bin.exp), Eval(bin.exp2))
+ if(/node/expression/operator/binary/Power)
+ return Power(Eval(bin.exp), Eval(bin.exp2))
+ if(/node/expression/operator/binary/Modulo)
+ return Modulo(Eval(bin.exp), Eval(bin.exp2))
+ else
+ RaiseError(new/runtimeError/UnknownInstruction())
+ else
+ switch(exp.type)
+ if(/node/expression/operator/unary/Minus)
+ return Minus(Eval(exp.exp))
+ if(/node/expression/operator/unary/LogicalNot)
+ return LogicalNot(Eval(exp.exp))
+ if(/node/expression/operator/unary/BitwiseNot)
+ return BitwiseNot(Eval(exp.exp))
+ if(/node/expression/operator/unary/group)
+ return Eval(exp.exp)
+ else
+ RaiseError(new/runtimeError/UnknownInstruction())
+
+
+ //Binary//
+ //Comparison operators
+ Equal(a, b) return a==b
+ NotEqual(a, b) return a!=b //LogicalNot(Equal(a, b))
+ Greater(a, b) return a>b
+ Less(a, b) return a=b
+ LessOrEqual(a, b) return a<=b
+ //Logical Operators
+ LogicalAnd(a, b) return a&&b
+ LogicalOr(a, b) return a||b
+ LogicalXor(a, b) return (a||b) && !(a&&b)
+ //Bitwise Operators
+ BitwiseAnd(a, b) return a&b
+ BitwiseOr(a, b) return a|b
+ BitwiseXor(a, b) return a^b
+ //Arithmetic Operators
+ Add(a, b)
+ if(istext(a)&&!istext(b)) b="[b]"
+ else if(istext(b)&&!istext(a)) a="[a]"
+ if(isobject(a) && !isobject(b))
+ RaiseError(new/runtimeError/TypeMismatch("+", a, b))
+ return null
+ else if(isobject(b) && !isobject(a))
+ RaiseError(new/runtimeError/TypeMismatch("+", a, b))
+ return null
+ return a+b
+ Subtract(a, b)
+ if(isobject(a) && !isobject(b))
+ RaiseError(new/runtimeError/TypeMismatch("-", a, b))
+ return null
+ else if(isobject(b) && !isobject(a))
+ RaiseError(new/runtimeError/TypeMismatch("-", a, b))
+ return null
+ return a-b
+ Divide(a, b)
+ if(isobject(a) && !isobject(b))
+ RaiseError(new/runtimeError/TypeMismatch("/", a, b))
+ return null
+ else if(isobject(b) && !isobject(a))
+ RaiseError(new/runtimeError/TypeMismatch("/", a, b))
+ return null
+ if(b==0)
+ RaiseError(new/runtimeError/DivisionByZero())
+ return null
+ return a/b
+ Multiply(a, b)
+ if(isobject(a) && !isobject(b))
+ RaiseError(new/runtimeError/TypeMismatch("*", a, b))
+ return null
+ else if(isobject(b) && !isobject(a))
+ RaiseError(new/runtimeError/TypeMismatch("*", a, b))
+ return null
+ return a*b
+ Modulo(a, b)
+ if(isobject(a) && !isobject(b))
+ RaiseError(new/runtimeError/TypeMismatch("%", a, b))
+ return null
+ else if(isobject(b) && !isobject(a))
+ RaiseError(new/runtimeError/TypeMismatch("%", a, b))
+ return null
+ return a%b
+ Power(a, b)
+ if(isobject(a) && !isobject(b))
+ RaiseError(new/runtimeError/TypeMismatch("**", a, b))
+ return null
+ else if(isobject(b) && !isobject(a))
+ RaiseError(new/runtimeError/TypeMismatch("**", a, b))
+ return null
+ return a**b
+
+ //Unary//
+ Minus(a) return -a
+ LogicalNot(a) return !a
+ BitwiseNot(a) return ~a
\ No newline at end of file
diff --git a/code/modules/scripting/Interpreter/Interaction.dm b/code/modules/scripting/Interpreter/Interaction.dm
new file mode 100644
index 00000000000..0dda1656d6c
--- /dev/null
+++ b/code/modules/scripting/Interpreter/Interaction.dm
@@ -0,0 +1,140 @@
+/*
+ File: Interpreter (Public)
+ Contains methods for interacting with the interpreter.
+*/
+/*
+ Class: n_Interpreter
+ Procedures allowing for interaction with the script that is being run by the interpreter object.
+*/
+
+/n_Interpreter
+ proc
+
+/*
+ Proc: Load
+ Loads a 'compiled' script into memory.
+
+ Parameters:
+ program - A object which represents the script's global scope.
+*/
+ Load(node/BlockDefinition/GlobalBlock/program)
+ ASSERT(program)
+ src.program = program
+ CreateGlobalScope()
+
+/*
+ Proc: Run
+ Runs the script.
+*/
+ Run()
+ cur_recursion = 0 // reset recursion
+
+ ASSERT(src.program)
+ RunBlock(src.program)
+
+/*
+ Proc: SetVar
+ Defines a global variable for the duration of the next execution of a script.
+
+ Notes:
+ This differs from in that variables set using this procedure only last for the session,
+ while those defined from the block object persist if it is ran multiple times.
+
+ See Also:
+ -
+*/
+ SetVar(name, value)
+ if(!istext(name))
+ //CRASH("Invalid variable name")
+ return
+ AssignVariable(name, value)
+
+/*
+ Proc: SetProc
+ Defines a procedure to be available to the script.
+
+ Parameters:
+ name - The name of the procedure as exposed to the script.
+ path - The typepath of a proc to be called when the function call is read by the interpreter, or, if object is specified, a string representing the procedure's name.
+ object - (Optional) An object which will the be target of a function call.
+ params - Only required if object is not null, a list of the names of parameters the proc takes.
+*/
+ SetProc(name, path, object=null, list/params=null)
+ if(!istext(name))
+ //CRASH("Invalid function name")
+ return
+ if(!object)
+ globalScope.functions[name] = path
+ else
+ var/node/statement/FunctionDefinition/S = new()
+ S.func_name = name
+ S.parameters = params
+ S.block = new()
+ S.block.SetVar("src", object)
+ var/node/expression/FunctionCall/C = new()
+ C.func_name = path
+ C.object = new("src")
+ for(var/p in params)
+ C.parameters += new/node/expression/value/variable(p)
+ var/node/statement/ReturnStatement/R=new()
+ R.value=C
+ S.block.statements += R
+ globalScope.functions[name] = S
+/*
+ Proc: VarExists
+ Checks whether a global variable with the specified name exists.
+*/
+ VarExists(name)
+ return globalScope.variables.Find(name) //convert to 1/0 first?
+
+/*
+ Proc: ProcExists
+ Checks whether a global function with the specified name exists.
+*/
+ ProcExists(name)
+ return globalScope.functions.Find(name)
+
+/*
+ Proc: GetVar
+ Returns the value of a global variable in the script. Remember to ensure that the variable exists before calling this procedure.
+
+ See Also:
+ -
+*/
+ GetVar(name)
+ if(!VarExists(name))
+ //CRASH("No variable named '[name]'.")
+ return
+ var/x = globalScope.variables[name]
+ return Eval(x)
+
+/*
+ Proc: CallProc
+ Calls a global function defined in the script and, amazingly enough, returns its return value. Remember to ensure that the function
+ exists before calling this procedure.
+
+ See Also:
+ -
+*/
+ CallProc(name, params[]=null)
+ if(!ProcExists(name))
+ //CRASH("No function named '[name]'.")
+ return
+ var/node/statement/FunctionDefinition/func = globalScope.functions[name]
+ if(istype(func))
+ var/node/statement/FunctionCall/stmt = new
+ stmt.func_name = func.func_name
+ stmt.parameters = params
+ return RunFunction(stmt)
+ else
+ return call(func)(arglist(params))
+ //CRASH("Unknown function type '[name]'.")
+
+/*
+ Event: HandleError
+ Called when the interpreter throws a runtime error.
+
+ See Also:
+ -
+*/
+ HandleError(runtimeError/e)
\ No newline at end of file
diff --git a/code/modules/scripting/Interpreter/Interpreter.dm b/code/modules/scripting/Interpreter/Interpreter.dm
new file mode 100644
index 00000000000..f13c8f3b030
--- /dev/null
+++ b/code/modules/scripting/Interpreter/Interpreter.dm
@@ -0,0 +1,291 @@
+/*
+ File: Interpreter (Internal)
+*/
+/*
+ Class: n_Interpreter
+*/
+/*
+ Macros: Status Macros
+ RETURNING - Indicates that the current function is returning a value.
+ BREAKING - Indicates that the current loop is being terminated.
+ CONTINUING - Indicates that the rest of the current iteration of a loop is being skipped.
+*/
+#define RETURNING 1
+#define BREAKING 2
+#define CONTINUING 4
+/n_Interpreter
+ var
+ scope
+ curScope
+ globalScope
+ node
+ BlockDefinition/program
+ statement/FunctionDefinition/curFunction
+ stack
+ scopes = new()
+ functions = new()
+/*
+ Var: status
+ A variable indicating that the rest of the current block should be skipped. This may be set to any combination of .
+*/
+ status=0
+ returnVal
+
+ max_iterations=100 // max iterations without any kind of delay
+ max_recursion=50 // max recursions without returning anything (or completing the code block)
+ cur_recursion=0 // current amount of recursion
+/*
+ Var: persist
+ If 0, global variables will be reset after Run() finishes.
+*/
+ persist=1
+ paused=0
+
+/*
+ Constructor: New
+ Calls with the given parameters.
+*/
+ New(node/BlockDefinition/GlobalBlock/program=null)
+ .=..()
+ if(program)Load(program)
+
+ proc
+/*
+ Proc: RaiseError
+ Raises a runtime error.
+*/
+ RaiseError(runtimeError/e)
+ e.stack=functions.Copy()
+ e.stack.Push(curFunction)
+ src.HandleError(e)
+
+ CreateScope(node/BlockDefinition/B)
+ var/scope/S = new(B, curScope)
+ scopes.Push(curScope)
+ curScope = S
+ return S
+
+ CreateGlobalScope()
+ scopes.Clear()
+ var/scope/S = new(program, null)
+ globalScope = S
+ return S
+
+/*
+ Proc: RunBlock
+ Runs each statement in a block of code.
+*/
+ RunBlock(node/BlockDefinition/Block, scope/scope = null)
+ var/is_global = istype(Block, /node/BlockDefinition/GlobalBlock)
+ if(!is_global)
+ if(scope)
+ curScope = scope
+ else
+ CreateScope(Block)
+ else
+ if(!persist)
+ CreateGlobalScope()
+ curScope = globalScope
+
+ for(var/node/statement/S in Block.statements)
+ while(paused) sleep(10)
+ if(istype(S, /node/statement/VariableAssignment))
+ var/node/statement/VariableAssignment/stmt = S
+ var/name = stmt.var_name.id_name
+ if(!stmt.object)
+ // Below we assign the variable first to null if it doesn't already exist.
+ // This is necessary for assignments like +=, and when the variable is used in a function
+ // If the variable already exists in a different block, then AssignVariable will automatically use that one.
+ if(!IsVariableAccessible(name))
+ AssignVariable(name, null)
+ AssignVariable(name, Eval(stmt.value))
+ else
+ var/datum/D = Eval(GetVariable(stmt.object.id_name))
+ if(!D) return
+ D.vars[stmt.var_name.id_name] = Eval(stmt.value)
+ else if(istype(S, /node/statement/VariableDeclaration))
+ //VariableDeclaration nodes are used to forcibly declare a local variable so that one in a higher scope isn't used by default.
+ var/node/statement/VariableDeclaration/dec=S
+ if(!dec.object)
+ AssignVariable(dec.var_name.id_name, null, curScope)
+ else
+ var/datum/D = Eval(GetVariable(dec.object.id_name))
+ if(!D) return
+ D.vars[dec.var_name.id_name] = null
+ else if(istype(S, /node/statement/FunctionCall))
+ RunFunction(S)
+ else if(istype(S, /node/statement/FunctionDefinition))
+ //do nothing
+ else if(istype(S, /node/statement/WhileLoop))
+ RunWhile(S)
+ else if(istype(S, /node/statement/IfStatement))
+ RunIf(S)
+ else if(istype(S, /node/statement/ReturnStatement))
+ if(!curFunction)
+ RaiseError(new/runtimeError/UnexpectedReturn())
+ continue
+ status |= RETURNING
+ returnVal=Eval(S:value)
+ break
+ else if(istype(S, /node/statement/BreakStatement))
+ status |= BREAKING
+ break
+ else if(istype(S, /node/statement/ContinueStatement))
+ status |= CONTINUING
+ break
+ else
+ RaiseError(new/runtimeError/UnknownInstruction())
+ if(status)
+ break
+ curScope = scopes.Pop()
+
+/*
+ Proc: RunFunction
+ Runs a function block or a proc with the arguments specified in the script.
+*/
+ RunFunction(node/statement/FunctionCall/stmt)
+ //Note that anywhere /node/statement/FunctionCall/stmt is used so may /node/expression/FunctionCall
+
+ // If recursion gets too high (max 50 nested functions) throw an error
+ if(cur_recursion >= max_recursion)
+ RaiseError(new/runtimeError/RecursionLimitReached())
+ return 0
+
+ var/node/statement/FunctionDefinition/def
+ if(!stmt.object) //A scope's function is being called, stmt.object is null
+ def = GetFunction(stmt.func_name)
+ else if(istype(stmt.object)) //A method of an object exposed as a variable is being called, stmt.object is a /node/identifier
+ var/O = GetVariable(stmt.object.id_name) //Gets a reference to the object which is the target of the function call.
+ if(!O) return //Error already thrown in GetVariable()
+ def = Eval(O)
+
+ if(!def) return
+
+ cur_recursion++ // add recursion
+ if(istype(def))
+ if(curFunction) functions.Push(curFunction)
+ var/scope/S = CreateScope(def.block)
+ for(var/i=1 to def.parameters.len)
+ var/val
+ if(stmt.parameters.len>=i)
+ val = stmt.parameters[i]
+ //else
+ // unspecified param
+ AssignVariable(def.parameters[i], new/node/expression/value/literal(Eval(val)), S)
+ curFunction=stmt
+ RunBlock(def.block, S)
+ //Handle return value
+ . = returnVal
+ status &= ~RETURNING
+ returnVal=null
+ curFunction=functions.Pop()
+ cur_recursion--
+ else
+ cur_recursion--
+ var/list/params=new
+ for(var/node/expression/P in stmt.parameters)
+ params+=list(Eval(P))
+ if(isobject(def)) //def is an object which is the target of a function call
+ if( !hascall(def, stmt.func_name) )
+ RaiseError(new/runtimeError/UndefinedFunction("[stmt.object.id_name].[stmt.func_name]"))
+ return
+ return call(def, stmt.func_name)(arglist(params))
+ else //def is a path to a global proc
+ return call(def)(arglist(params))
+ //else
+ // RaiseError(new/runtimeError/UnknownInstruction())
+
+/*
+ Proc: RunIf
+ Checks a condition and runs either the if block or else block.
+*/
+ RunIf(node/statement/IfStatement/stmt)
+ if(Eval(stmt.cond))
+ RunBlock(stmt.block)
+ else if(stmt.else_block)
+ RunBlock(stmt.else_block)
+
+/*
+ Proc: RunWhile
+ Runs a while loop.
+*/
+ RunWhile(node/statement/WhileLoop/stmt)
+ var/i=1
+ while(Eval(stmt.cond) && Iterate(stmt.block, i++))
+ continue
+ status &= ~BREAKING
+
+/*
+ Proc:Iterate
+ Runs a single iteration of a loop. Returns a value indicating whether or not to continue looping.
+*/
+ Iterate(node/BlockDefinition/block, count)
+ RunBlock(block)
+ if(max_iterations > 0 && count >= max_iterations)
+ RaiseError(new/runtimeError/IterationLimitReached())
+ return 0
+ if(status & (BREAKING|RETURNING))
+ return 0
+ status &= ~CONTINUING
+ return 1
+
+/*
+ Proc: GetFunction
+ Finds a function in an accessible scope with the given name. Returns a .
+*/
+ GetFunction(name)
+ var/scope/S = curScope
+ while(S)
+ if(S.functions.Find(name))
+ return S.functions[name]
+ S = S.parent
+ RaiseError(new/runtimeError/UndefinedFunction(name))
+
+/*
+ Proc: GetVariable
+ Finds a variable in an accessible scope and returns its value.
+*/
+ GetVariable(name)
+ var/scope/S = curScope
+ while(S)
+ if(S.variables.Find(name))
+ return S.variables[name]
+ S = S.parent
+ RaiseError(new/runtimeError/UndefinedVariable(name))
+
+ GetVariableScope(name) //needed for when you reassign a variable in a higher scope
+ var/scope/S = curScope
+ while(S)
+ if(S.variables.Find(name))
+ return S
+ S = S.parent
+
+
+ IsVariableAccessible(name)
+ var/scope/S = curScope
+ while(S)
+ if(S.variables.Find(name))
+ return TRUE
+ S = S.parent
+ return FALSE
+
+
+/*
+ Proc: AssignVariable
+ Assigns a value to a variable in a specific block.
+
+ Parameters:
+ name - The name of the variable to assign.
+ value - The value to assign to it.
+ S - The scope the variable resides in. If it is null, a scope with the variable already existing is found. If no scopes have a variable of the given name, the current scope is used.
+*/
+ AssignVariable(name, node/expression/value, scope/S=null)
+ if(!S) S = GetVariableScope(name)
+ if(!S) S = curScope
+ if(!S) S = globalScope
+ ASSERT(istype(S))
+ if(istext(value) || isnum(value) || isnull(value)) value = new/node/expression/value/literal(value)
+ else if(!istype(value) && isobject(value)) value = new/node/expression/value/reference(value)
+ //TODO: check for invalid name
+ S.variables["[name]"] = value
+
diff --git a/code/modules/scripting/Interpreter/Scope.dm b/code/modules/scripting/Interpreter/Scope.dm
new file mode 100644
index 00000000000..50da8047079
--- /dev/null
+++ b/code/modules/scripting/Interpreter/Scope.dm
@@ -0,0 +1,18 @@
+/*
+ Class: scope
+ A runtime instance of a block. Used internally by the interpreter.
+*/
+scope
+ var
+ scope/parent = null
+ node/BlockDefinition/block
+ list
+ functions
+ variables
+
+ New(node/BlockDefinition/B, scope/parent)
+ src.block = B
+ src.parent = parent
+ src.variables = B.initial_variables.Copy()
+ src.functions = B.functions.Copy()
+ .=..()
\ No newline at end of file
diff --git a/code/modules/scripting/Options.dm b/code/modules/scripting/Options.dm
new file mode 100644
index 00000000000..22774455dc4
--- /dev/null
+++ b/code/modules/scripting/Options.dm
@@ -0,0 +1,82 @@
+/*
+File: Options
+*/
+var/const //Ascii values of characters
+ ascii_A =65
+ ascii_Z =90
+ ascii_a =97
+ ascii_z =122
+ ascii_DOLLAR = 36 // $
+ ascii_ZERO=48
+ ascii_NINE=57
+ ascii_UNDERSCORE=95 // _
+
+/*
+ Class: n_scriptOptions
+*/
+n_scriptOptions
+ proc
+ CanStartID(char) //returns true if the character can start a variable, function, or keyword name (by default letters or an underscore)
+ if(!isnum(char))char=text2ascii(char)
+ return (char in ascii_A to ascii_Z) || (char in ascii_a to ascii_z) || char==ascii_UNDERSCORE || char==ascii_DOLLAR
+
+ IsValidIDChar(char) //returns true if the character can be in the body of a variable, function, or keyword name (by default letters, numbers, and underscore)
+ if(!isnum(char))char=text2ascii(char)
+ return CanStartID(char) || IsDigit(char)
+
+ IsDigit(char)
+ if(!isnum(char))char=text2ascii(char)
+ return char in ascii_ZERO to ascii_NINE
+
+ IsValidID(id) //returns true if all the characters in the string are okay to be in an identifier name
+ if(!CanStartID(id)) //don't need to grab first char in id, since text2ascii does it automatically
+ return 0
+ if(lentext(id)==1) return 1
+ for(var/i=2 to lentext(id))
+ if(!IsValidIDChar(copytext(id, i, i+1)))
+ return 0
+ return 1
+
+/*
+ Class: nS_Options
+ An implementation of for the n_Script language.
+*/
+ nS_Options
+ var
+ list
+ symbols = list("(", ")", "\[", "]", ";", ",", "{", "}") //scanner - Characters that can be in symbols
+/*
+ Var: keywords
+ An associative list used by the parser to parse keywords. Indices are strings which will trigger the keyword when parsed and the
+ associated values are types of which the proc will be called.
+*/
+ keywords = list("if" = /n_Keyword/nS_Keyword/kwIf, "else" = /n_Keyword/nS_Keyword/kwElse, \
+ "while" = /n_Keyword/nS_Keyword/kwWhile, "break" = /n_Keyword/nS_Keyword/kwBreak, \
+ "continue" = /n_Keyword/nS_Keyword/kwContinue, \
+ "return" = /n_Keyword/nS_Keyword/kwReturn, "def" = /n_Keyword/nS_Keyword/kwDef)
+
+ list
+ assign_operators=list("=" = null, "&=" = "&",
+ "|=" = "|", "`=" = "`",
+ "+=" = "+", "-=" = "-",
+ "*=" = "*", "/=" = "/",
+ "^=" = "^",
+ "%=" = "%")
+
+ unary_operators =list("!" = /node/expression/operator/unary/LogicalNot, "~" = /node/expression/operator/unary/BitwiseNot,
+ "-" = /node/expression/operator/unary/Minus)
+
+ binary_operators=list("==" = /node/expression/operator/binary/Equal, "!=" = /node/expression/operator/binary/NotEqual,
+ ">" = /node/expression/operator/binary/Greater, "<" = /node/expression/operator/binary/Less,
+ ">=" = /node/expression/operator/binary/GreaterOrEqual,"<=" = /node/expression/operator/binary/LessOrEqual,
+ "&&" = /node/expression/operator/binary/LogicalAnd, "||" = /node/expression/operator/binary/LogicalOr,
+ "&" = /node/expression/operator/binary/BitwiseAnd, "|" = /node/expression/operator/binary/BitwiseOr,
+ "`" = /node/expression/operator/binary/BitwiseXor, "+" = /node/expression/operator/binary/Add,
+ "-" = /node/expression/operator/binary/Subtract, "*" = /node/expression/operator/binary/Multiply,
+ "/" = /node/expression/operator/binary/Divide, "^" = /node/expression/operator/binary/Power,
+ "%" = /node/expression/operator/binary/Modulo)
+
+ New()
+ .=..()
+ for(var/O in assign_operators+binary_operators+unary_operators)
+ if(!symbols.Find(O)) symbols+=O
\ No newline at end of file
diff --git a/code/modules/scripting/Parser/Expressions.dm b/code/modules/scripting/Parser/Expressions.dm
new file mode 100644
index 00000000000..3245a4a28f8
--- /dev/null
+++ b/code/modules/scripting/Parser/Expressions.dm
@@ -0,0 +1,308 @@
+/*
+ File: Expressions
+ Procedures for parsing expressions.
+*/
+
+/*
+ Macros: Expression Macros
+ OPERATOR - A value indicating the parser currently expects a binary operator.
+ VALUE - A value indicating the parser currently expects a value.
+ SHIFT - Tells the parser to push the current operator onto the stack.
+ REDUCE - Tells the parser to reduce the stack.
+*/
+#define OPERATOR 1
+#define VALUE 2
+#define SHIFT 0
+#define REDUCE 1
+
+/*
+ Class: nS_Parser
+*/
+/n_Parser/nS_Parser
+ var
+/*
+ Var: expecting
+ A variable which keeps track of whether an operator or value is expected. It should be either or . See
+ for more information.
+*/
+ expecting=VALUE
+
+ proc
+/*
+ Proc: Precedence
+ Compares two operators, decides which is higher in the order of operations, and returns or .
+*/
+ Precedence(node/expression/operator/top, node/expression/operator/input)
+ if(istype(top))
+ top=top.precedence
+ if(istype(input))
+ input=input:precedence
+ if(top>=input)
+ return REDUCE
+ return SHIFT
+
+/*
+ Proc: GetExpression
+ Takes a token expected to represent a value and returns an node.
+*/
+ GetExpression(token/T)
+ if(!T) return
+ if(istype(T, /node/expression))
+ return T
+ switch(T.type)
+ if(/token/word)
+ return new/node/expression/value/variable(T.value)
+ if(/token/accessor)
+ var
+ token/accessor/A=T
+ node/expression/value/variable/E//=new(A.member)
+ stack/S=new()
+ while(istype(A.object, /token/accessor))
+ S.Push(A)
+ A=A.object
+ ASSERT(istext(A.object))
+
+ while(A)
+ var/node/expression/value/variable/V=new()
+ V.id=new(A.member)
+ if(E)
+ V.object=E
+ else
+ V.object=new/node/identifier(A.object)
+ E=V
+ A=S.Pop()
+ return E
+
+ if(/token/number, /token/string)
+ return new/node/expression/value/literal(T.value)
+
+/*
+ Proc: GetOperator
+ Gets a path related to a token or string and returns an instance of the given type. This is used to get an instance of either a binary or unary
+ operator from a token.
+
+ Parameters:
+ O - The input value. If this is a token, O is reset to the token's value.
+ When O is a string and is in L, its associated value is used as the path to instantiate.
+ type - The desired type of the returned object.
+ L - The list in which to search for O.
+
+ See Also:
+ -
+ -
+*/
+ GetOperator(O, type=/node/expression/operator, L[])
+ if(istype(O, type)) return O //O is already the desired type
+ if(istype(O, /token)) O=O:value //sets O to text
+ if(istext(O)) //sets O to path
+ if(L.Find(O)) O=L[O]
+ else return null
+ if(ispath(O))O=new O //catches path from last check
+ else return null //Unknown type
+ return O
+
+/*
+ Proc: GetBinaryOperator
+ Uses to search for an instance of a binary operator type with which the given string is associated. For example, if
+ O is set to "+", an node is returned.
+
+ See Also:
+ -
+ -
+*/
+ GetBinaryOperator(O)
+ return GetOperator(O, /node/expression/operator/binary, options.binary_operators)
+
+/*
+ Proc: GetUnaryOperator
+ Uses to search for an instance of a unary operator type with which the given string is associated. For example, if
+ O is set to "!", a node is returned.
+
+ See Also:
+ -
+ -
+*/
+ GetUnaryOperator(O)
+ return GetOperator(O, /node/expression/operator/unary, options.unary_operators)
+
+/*
+ Proc: Reduce
+ Takes the operator on top of the opr stack and assigns its operand(s). Then this proc pushes the value of that operation to the top
+ of the val stack.
+*/
+ Reduce(stack/opr, stack/val)
+ var/node/expression/operator/O=opr.Pop()
+ if(!O) return
+ if(!istype(O))
+ errors+=new/scriptError("Error reducing expression - invalid operator.")
+ return
+ //Take O and assign its operands, popping one or two values from the val stack
+ //depending on whether O is a binary or unary operator.
+ if(istype(O, /node/expression/operator/binary))
+ var/node/expression/operator/binary/B=O
+ B.exp2=val.Pop()
+ B.exp =val.Pop()
+ val.Push(B)
+ else
+ O.exp=val.Pop()
+ val.Push(O)
+
+/*
+ Proc: EndOfExpression
+ Returns true if the current token represents the end of an expression.
+
+ Parameters:
+ end - A list of values to compare the current token to.
+*/
+ EndOfExpression(end[])
+ if(!curToken)
+ return 1
+ if(istype(curToken, /token/symbol) && end.Find(curToken.value))
+ return 1
+ if(istype(curToken, /token/end) && end.Find(/token/end))
+ return 1
+ return 0
+
+/*
+ Proc: ParseExpression
+ Uses the Shunting-yard algorithm to parse expressions.
+
+ Notes:
+ - When an opening parenthesis is found, then is called to handle it.
+ - The variable helps distinguish unary operators from binary operators (for cases like the - operator, which can be either).
+
+ Articles:
+ -
+ -
+
+ See Also:
+ -
+ -
+ -
+*/
+ ParseExpression(list/end=list(/token/end), list/ErrChars=list("{", "}"))
+ var/stack
+ opr=new
+ val=new
+ src.expecting=VALUE
+ for()
+ if(EndOfExpression(end))
+ break
+ if(istype(curToken, /token/symbol) && ErrChars.Find(curToken.value))
+ errors+=new/scriptError/BadToken(curToken)
+ break
+
+
+ if(index>tokens.len) //End of File
+ errors+=new/scriptError/EndOfFile()
+ break
+ var/token/ntok
+ if(index+1<=tokens.len)
+ ntok=tokens[index+1]
+
+ if(istype(curToken, /token/symbol) && curToken.value=="(") //Parse parentheses expression
+ if(expecting!=VALUE)
+ errors+=new/scriptError/ExpectedToken("operator", curToken)
+ NextToken()
+ continue
+ val.Push(ParseParenExpression())
+ else if(istype(curToken, /token/symbol)) //Operator found.
+ var/node/expression/operator/curOperator //Figure out whether it is unary or binary and get a new instance.
+ if(src.expecting==OPERATOR)
+ curOperator=GetBinaryOperator(curToken)
+ if(!curOperator)
+ errors+=new/scriptError/ExpectedToken("operator", curToken)
+ NextToken()
+ continue
+ else
+ curOperator=GetUnaryOperator(curToken)
+ if(!curOperator) //given symbol isn't a unary operator
+ errors+=new/scriptError/ExpectedToken("expression", curToken)
+ NextToken()
+ continue
+
+ if(opr.Top() && Precedence(opr.Top(), curOperator)==REDUCE) //Check order of operations and reduce if necessary
+ Reduce(opr, val)
+ continue
+ opr.Push(curOperator)
+ src.expecting=VALUE
+ else if(ntok && ntok.value=="(" && istype(ntok, /token/symbol)\
+ && istype(curToken, /token/word)) //Parse function call
+ var/token/preToken=curToken
+ var/old_expect=src.expecting
+ var/fex=ParseFunctionExpression()
+ if(old_expect!=VALUE)
+ errors+=new/scriptError/ExpectedToken("operator", preToken)
+ NextToken()
+ continue
+ val.Push(fex)
+ else if(istype(curToken, /token/keyword)) //inline keywords
+ var/n_Keyword/kw=options.keywords[curToken.value]
+ kw=new kw(inline=1)
+ if(kw)
+ if(!kw.Parse(src))
+ return
+ else
+ errors+=new/scriptError/BadToken(curToken)
+ else if(istype(curToken, /token/end)) //semicolon found where it wasn't expected
+ errors+=new/scriptError/BadToken(curToken)
+ NextToken()
+ continue
+ else
+ if(expecting!=VALUE)
+ errors+=new/scriptError/ExpectedToken("operator", curToken)
+ NextToken()
+ continue
+ val.Push(GetExpression(curToken))
+ src.expecting=OPERATOR
+ NextToken()
+
+ while(opr.Top()) Reduce(opr, val) //Reduce the value stack completely
+ .=val.Pop() //Return what should be the last value on the stack
+ if(val.Top()) //
+ var/node/N=val.Pop()
+ errors+=new/scriptError("Error parsing expression. Unexpected value left on stack: [N.ToString()].")
+ return null
+
+/*
+ Proc: ParseFunctionExpression
+ Parses a function call inside of an expression.
+
+ See Also:
+ -
+*/
+ ParseFunctionExpression()
+ var/node/expression/FunctionCall/exp=new
+ exp.func_name=curToken.value
+ NextToken() //skip function name
+ NextToken() //skip open parenthesis, already found
+ for()
+ if(istype(curToken, /token/symbol) && curToken.value==")")
+ return exp
+ exp.parameters+=ParseParamExpression()
+ if(curToken.value==","&&istype(curToken, /token/symbol))NextToken() //skip comma
+ if(istype(curToken, /token/end)) //Prevents infinite loop...
+ errors+=new/scriptError/ExpectedToken(")")
+ return exp
+
+/*
+ Proc: ParseParenExpression
+ Parses an expression that ends with a close parenthesis. This is used for parsing expressions inside of parentheses.
+
+ See Also:
+ -
+*/
+ ParseParenExpression()
+ if(!CheckToken("(", /token/symbol))
+ return
+ return new/node/expression/operator/unary/group(ParseExpression(list(")")))
+
+/*
+ Proc: ParseParamExpression
+ Parses an expression that ends with either a comma or close parenthesis. This is used for parsing the parameters passed to a function call.
+
+ See Also:
+ -
+*/
+ ParseParamExpression()
+ return ParseExpression(list(",", ")"))
\ No newline at end of file
diff --git a/code/modules/scripting/Parser/Keywords.dm b/code/modules/scripting/Parser/Keywords.dm
new file mode 100644
index 00000000000..05aa0cd6a46
--- /dev/null
+++ b/code/modules/scripting/Parser/Keywords.dm
@@ -0,0 +1,166 @@
+/*
+ File: Keywords
+*/
+var/const
+ KW_FAIL = 0 //Fatal error; stop parsing entire script.
+ KW_PASS = 1 //OK
+ KW_ERR = 2 //Non-fatal error, keyword couldn't be handled properly. Ignore keyword but continue on.
+ KW_WARN = 3 //Warning
+
+/*
+ Class: n_Keyword
+ Represents a special statement in the code triggered by a keyword.
+*/
+/n_Keyword
+ New(inline=0)
+ src.inline=inline
+ return ..()
+
+ var
+/*
+ Var: inline
+ 1 if the keyword is in an expression (e.g. the new keyword in many languages), 0 otherwise (such as the if and else keywords).
+*/
+ inline
+
+/*
+ Proc: Parse
+ Called when the parser finds a keyword in the code.
+
+ Parameters:
+ parser - The parser that created this object. You can use the parameter to manipulate the parser in order to add statements and blocks
+ to its AST.
+*/
+ proc/Parse(n_Parser/parser)
+
+/*
+ Class: nS_Keyword
+ A keyword in n_Script. By default these include return, if, else, while, and def. To enable or disable a keyword, change the
+ list.
+
+ Behavior:
+ When a parser is expecting a new statement, and a keyword listed in is found, it will call the keyword's
+ proc.
+*/
+//
+ nS_Keyword
+ New(inline=0)
+ if(inline)
+ del src
+
+ kwReturn
+ Parse(n_Parser/nS_Parser/parser)
+ .=KW_PASS
+ if(istype(parser.curBlock, /node/BlockDefinition/GlobalBlock))
+ parser.errors+=new/scriptError/BadReturn(parser.curToken)
+ . = KW_WARN
+ var/node/statement/ReturnStatement/stmt=new
+ parser.NextToken() //skip 'return' token
+ stmt.value=parser.ParseExpression()
+ parser.curBlock.statements+=stmt
+
+ kwIf
+ Parse(n_Parser/nS_Parser/parser)
+ .=KW_PASS
+ var/node/statement/IfStatement/stmt=new
+ parser.NextToken() //skip 'if' token
+ stmt.cond=parser.ParseParenExpression()
+ if(!parser.CheckToken(")", /token/symbol))
+ return KW_FAIL
+ if(!parser.CheckToken("{", /token/symbol, skip=0)) //Token needs to be preserved for parse loop, so skip=0
+ return KW_ERR
+ parser.curBlock.statements+=stmt
+ stmt.block=new
+ parser.AddBlock(stmt.block)
+
+ kwElse
+ Parse(n_Parser/nS_Parser/parser)
+ .=KW_PASS
+ var/list/L=parser.curBlock.statements
+ var/node/statement/IfStatement/stmt
+ if(L&&L.len) stmt=L[L.len] //Get the last statement in the current block
+ if(!stmt || !istype(stmt) || stmt.else_block) //Ensure that it is an if statement
+ parser.errors+=new/scriptError/ExpectedToken("if statement",parser.curToken)
+ return KW_FAIL
+ parser.NextToken() //skip 'else' token
+ if(!parser.CheckToken("{", /token/symbol, skip=0))
+ return KW_ERR
+ stmt.else_block=new()
+ parser.AddBlock(stmt.else_block)
+
+ kwWhile
+ Parse(n_Parser/nS_Parser/parser)
+ .=KW_PASS
+ var/node/statement/WhileLoop/stmt=new
+ parser.NextToken() //skip 'while' token
+ stmt.cond=parser.ParseParenExpression()
+ if(!parser.CheckToken(")", /token/symbol))
+ return KW_FAIL
+ if(!parser.CheckToken("{", /token/symbol, skip=0))
+ return KW_ERR
+ parser.curBlock.statements+=stmt
+ stmt.block=new
+ parser.AddBlock(stmt.block)
+
+ kwBreak
+ Parse(n_Parser/nS_Parser/parser)
+ .=KW_PASS
+ if(istype(parser.curBlock, /node/BlockDefinition/GlobalBlock))
+ parser.errors+=new/scriptError/BadToken(parser.curToken)
+ . = KW_WARN
+ var/node/statement/BreakStatement/stmt=new
+ parser.NextToken() //skip 'break' token
+ parser.curBlock.statements+=stmt
+
+ kwContinue
+ Parse(n_Parser/nS_Parser/parser)
+ .=KW_PASS
+ if(istype(parser.curBlock, /node/BlockDefinition/GlobalBlock))
+ parser.errors+=new/scriptError/BadToken(parser.curToken)
+ . = KW_WARN
+ var/node/statement/ContinueStatement/stmt=new
+ parser.NextToken() //skip 'break' token
+ parser.curBlock.statements+=stmt
+
+ kwDef
+ Parse(n_Parser/nS_Parser/parser)
+ .=KW_PASS
+ var/node/statement/FunctionDefinition/def=new
+ parser.NextToken() //skip 'def' token
+ if(!parser.options.IsValidID(parser.curToken.value))
+ parser.errors+=new/scriptError/InvalidID(parser.curToken)
+ return KW_FAIL
+ def.func_name=parser.curToken.value
+ parser.NextToken()
+ if(!parser.CheckToken("(", /token/symbol))
+ return KW_FAIL
+ for() //for now parameters can be separated by whitespace - they don't need a comma in between
+ if(istype(parser.curToken, /token/symbol))
+ switch(parser.curToken.value)
+ if(",")
+ parser.NextToken()
+ if(")")
+ break
+ else
+ parser.errors+=new/scriptError/BadToken(parser.curToken)
+ return KW_ERR
+
+ else if(istype(parser.curToken, /token/word))
+ def.parameters+=parser.curToken.value
+ parser.NextToken()
+ else
+ parser.errors+=new/scriptError/InvalidID(parser.curToken)
+ return KW_ERR
+ if(!parser.CheckToken(")", /token/symbol))
+ return KW_FAIL
+
+ if(istype(parser.curToken, /token/end)) //Function prototype
+ parser.curBlock.statements+=def
+ else if(parser.curToken.value=="{" && istype(parser.curToken, /token/symbol))
+ def.block = new
+ parser.curBlock.statements+=def
+ parser.curBlock.functions[def.func_name]=def
+ parser.AddBlock(def.block)
+ else
+ parser.errors+=new/scriptError/BadToken(parser.curToken)
+ return KW_FAIL
\ No newline at end of file
diff --git a/code/modules/scripting/Parser/Parser.dm b/code/modules/scripting/Parser/Parser.dm
new file mode 100644
index 00000000000..9f88fd2227e
--- /dev/null
+++ b/code/modules/scripting/Parser/Parser.dm
@@ -0,0 +1,184 @@
+/*
+ File: Parser
+*/
+/*
+ Class: n_Parser
+ An object that reads tokens and produces an AST (abstract syntax tree).
+*/
+/n_Parser
+ var
+/*
+ Var: index
+ The parser's current position in the token's list.
+*/
+ index = 1
+ list
+/*
+ Var: tokens
+ A list of tokens in the source code generated by a scanner.
+*/
+ tokens = new
+/*
+ Var: errors
+ A list of fatal errors found by the parser. If there are any items in this list, then it is not safe to run the returned AST.
+
+ See Also:
+ -
+*/
+ errors = new
+/*
+ Var: warnings
+ A list of non-fatal problems in the script.
+*/
+ warnings = new
+ token
+/*
+ Var: curToken
+ The token at in .
+*/
+ curToken
+ stack
+ blocks=new
+ node/BlockDefinition
+ GlobalBlock/global_block=new
+ curBlock
+
+ proc
+/*
+ Proc: Parse
+ Reads the tokens and returns the AST's node. Be sure to populate the tokens list before calling this procedure.
+*/
+ Parse()
+
+/*
+ Proc: NextToken
+ Sets to the next token in the list, or null if there are no more tokens.
+*/
+ NextToken()
+ if(index>=tokens.len)
+ curToken=null
+ else
+ curToken=tokens[++index]
+ return curToken
+
+/*
+ Class: nS_Parser
+ An implmentation of a parser for n_Script.
+*/
+/n_Parser/nS_Parser
+ var/n_scriptOptions/nS_Options/options
+/*
+ Constructor: New
+
+ Parameters:
+ tokens - A list of tokens to parse.
+ options - An object used for configuration.
+*/
+ New(tokens[], n_scriptOptions/options)
+ src.tokens=tokens
+ src.options=options
+ curBlock=global_block
+ return ..()
+
+ Parse()
+ ASSERT(tokens)
+ for(,src.index<=src.tokens.len, src.index++)
+ curToken=tokens[index]
+ switch(curToken.type)
+ if(/token/keyword)
+ var/n_Keyword/kw=options.keywords[curToken.value]
+ kw=new kw()
+ if(kw)
+ if(!kw.Parse(src))
+ return
+ if(/token/word)
+ var/token/ntok
+ if(index+1>tokens.len)
+ errors+=new/scriptError/BadToken(curToken)
+ continue
+ ntok=tokens[index+1]
+ if(!istype(ntok, /token/symbol))
+ errors+=new/scriptError/BadToken(ntok)
+ continue
+ if(ntok.value=="(")
+ ParseFunctionStatement()
+ else if(options.assign_operators.Find(ntok.value))
+ ParseAssignment()
+ else
+ errors+=new/scriptError/BadToken(ntok)
+ continue
+ if(!istype(curToken, /token/end))
+ errors+=new/scriptError/ExpectedToken(";", curToken)
+ continue
+ if(/token/symbol)
+ if(curToken.value=="}")
+ if(!EndBlock())
+ errors+=new/scriptError/BadToken(curToken)
+ continue
+ else
+ errors+=new/scriptError/BadToken(curToken)
+ continue
+ if(/token/end)
+ warnings+=new/scriptError/BadToken(curToken)
+ continue
+ else
+ errors+=new/scriptError/BadToken(curToken)
+ return
+ return global_block
+
+ proc
+ CheckToken(val, type, err=1, skip=1)
+ if(curToken.value!=val || !istype(curToken,type))
+ if(err)
+ errors+=new/scriptError/ExpectedToken(val, curToken)
+ return 0
+ if(skip)NextToken()
+ return 1
+
+ AddBlock(node/BlockDefinition/B)
+ blocks.Push(curBlock)
+ curBlock=B
+
+ EndBlock()
+ if(curBlock==global_block) return 0
+ curBlock=blocks.Pop()
+ return 1
+
+ ParseAssignment()
+ var/name=curToken.value
+ if(!options.IsValidID(name))
+ errors+=new/scriptError/InvalidID(curToken)
+ return
+ NextToken()
+ var/t=options.binary_operators[options.assign_operators[curToken.value]]
+ var/node/statement/VariableAssignment/stmt=new()
+ stmt.var_name=new(name)
+ NextToken()
+ if(t)
+ stmt.value=new t()
+ stmt.value:exp=new/node/expression/value/variable(stmt.var_name)
+ stmt.value:exp2=ParseExpression()
+ else
+ stmt.value=ParseExpression()
+ curBlock.statements+=stmt
+
+ ParseFunctionStatement()
+ if(!istype(curToken, /token/word))
+ errors+=new/scriptError("Bad identifier in function call.")
+ return
+ var/node/statement/FunctionCall/stmt=new
+ stmt.func_name=curToken.value
+ NextToken() //skip function name
+ if(!CheckToken("(", /token/symbol)) //Check for and skip open parenthesis
+ return
+ for()
+ if(!curToken)
+ errors+=new/scriptError/EndOfFile()
+ return
+ if(istype(curToken, /token/symbol) && curToken.value==")")
+ curBlock.statements+=stmt
+ NextToken() //Skip close parenthesis
+ return
+ var/node/expression/P=ParseParamExpression()
+ stmt.parameters+=P
+ if(istype(curToken, /token/symbol) && curToken.value==",") NextToken()
\ No newline at end of file
diff --git a/code/modules/scripting/Scanner/Scanner.dm b/code/modules/scripting/Scanner/Scanner.dm
new file mode 100644
index 00000000000..173b50b4535
--- /dev/null
+++ b/code/modules/scripting/Scanner/Scanner.dm
@@ -0,0 +1,231 @@
+/*
+ File: Scanner
+*/
+/*
+ Class: n_Scanner
+ An object responsible for breaking up source code into tokens for use by the parser.
+*/
+/n_Scanner
+ var
+ code
+ list
+/*
+ Var: errors
+ A list of fatal errors found by the scanner. If there are any items in this list, then it is not safe to parse the returned tokens.
+
+ See Also:
+ -
+*/
+ errors = new
+/*
+ Var: warnings
+ A list of non-fatal problems in the source code found by the scanner.
+*/
+ warnings = new
+
+ proc
+/*
+ Proc: LoadCode
+ Loads source code.
+*/
+ LoadCode(c)
+ code=c
+
+/*
+ Proc: LoadCodeFromFile
+ Gets the code from a file and calls .
+*/
+ LoadCodeFromFile(f)
+ LoadCode(file2text(f))
+
+/*
+ Proc: Scan
+ Runs the scanner and returns the resulting list of tokens. Ensure that has been called first.
+*/
+ Scan()
+
+/*
+ Class: nS_Scanner
+ A scanner implementation for n_Script.
+*/
+/n_Scanner/nS_Scanner
+
+ var
+/*
+ Variable: codepos
+ The scanner's position in the source code.
+*/
+ codepos = 1
+ line = 1
+ linepos = 0 //column=codepos-linepos
+ n_scriptOptions/nS_Options/options
+ list
+/*
+ Variable: ignore
+ A list of characters that are ignored by the scanner.
+
+ Default Value:
+ Whitespace
+*/
+ ignore = list(" ", "\t", "\n") //Don't add tokens for whitespace
+/*
+ Variable: end_stmt
+ A list of characters that end a statement. Each item may only be one character long.
+
+ Default Value:
+ Semicolon
+*/
+ end_stmt = list(";")
+/*
+ Variable: string_delim
+ A list of characters that can start and end strings.
+
+ Default Value:
+ Double and single quotes.
+*/
+ string_delim = list("\"", "'")
+/*
+ Variable: delim
+ A list of characters that denote the start of a new token. This list is automatically populated.
+*/
+ delim = new
+
+/*
+ Macro: COL
+ The current column number.
+*/
+ #define COL codepos-linepos
+
+/*
+ Constructor: New
+ Parameters:
+ code - The source code to tokenize.
+ options - An object used to configure the scanner.
+*/
+ New(code, n_scriptOptions/nS_Options/options)
+ .=..()
+ ignore+= ascii2text(13) //Carriage return
+ delim += ignore + options.symbols + end_stmt + string_delim
+ src.options=options
+ LoadCode(code)
+
+ Scan() //Creates a list of tokens from source code
+ var/list/tokens=new
+ for(, src.codepos<=lentext(code), src.codepos++)
+ var/char=copytext(code, codepos, codepos+1)
+ if(char=="\n")
+ line++
+ linepos=codepos
+ if(ignore.Find(char))
+ continue
+ else if(end_stmt.Find(char))
+ tokens+=new /token/end(char, line, COL)
+ else if(string_delim.Find(char))
+ codepos++ //skip string delimiter
+ tokens+=ReadString(char)
+ else if(options.CanStartID(char))
+ tokens+=ReadWord()
+ else if(options.IsDigit(char))
+ tokens+=ReadNumber()
+ else if(options.symbols.Find(char))
+ tokens+=ReadSymbol()
+ codepos=initial(codepos)
+ line=initial(line)
+ linepos=initial(linepos)
+ return tokens
+
+ proc
+/*
+ Proc: ReadString
+ Reads a string in the source code into a token.
+
+ Parameters:
+ start - The character used to start the string.
+*/
+ ReadString(start)
+ var
+ buf
+ for(, codepos <= lentext(code), codepos++)//codepos to lentext(code))
+ var/char=copytext(code, codepos, codepos+1)
+ switch(char)
+ if("\\") //Backslash (\) encountered in string
+ codepos++ //Skip next character in string, since it was escaped by a backslash
+ char=copytext(code, codepos, codepos+1)
+ switch(char)
+ if("\\") //Double backslash
+ buf+="\\"
+ if("n") //\n Newline
+ buf+="\n"
+ else
+ if(char==start) //\" Doublequote
+ buf+=start
+ else //Unknown escaped text
+ buf+=char
+ if("\n")
+ . = new/token/string(buf, line, COL)
+ errors+=new/scriptError("Unterminated string. Newline reached.", .)
+ line++
+ linepos=codepos
+ break
+ else
+ if(char==start) //string delimiter found, end string
+ break
+ else
+ buf+=char //Just a normal character in a string
+ if(!.) return new/token/string(buf, line, COL)
+
+/*
+ Proc: ReadWord
+ Reads characters separated by an item in into a token.
+*/
+ ReadWord()
+ var
+ char=copytext(code, codepos, codepos+1)
+ buf
+ while(!delim.Find(char) && codepos<=lentext(code))
+ buf+=char
+ char=copytext(code, ++codepos, codepos+1)
+ codepos-- //allow main Scan() proc to read the delimiter
+ if(options.keywords.Find(buf))
+ return new /token/keyword(buf, line, COL)
+ else
+ return new /token/word(buf, line, COL)
+
+/*
+ Proc: ReadSymbol
+ Reads a symbol into a token.
+*/
+ ReadSymbol()
+ var
+ char=copytext(code, codepos, codepos+1)
+ buf
+
+ while(options.symbols.Find(buf+char))
+ buf+=char
+ if(++codepos>lentext(code)) break
+ char=copytext(code, codepos, codepos+1)
+
+ codepos-- //allow main Scan() proc to read the next character
+ return new /token/symbol(buf, line, COL)
+
+/*
+ Proc: ReadNumber
+ Reads a number into a token.
+*/
+ ReadNumber()
+ var
+ char=copytext(code, codepos, codepos+1)
+ buf
+ dec=0
+
+ while(options.IsDigit(char) || (char=="." && !dec))
+ if(char==".") dec=1
+ buf+=char
+ codepos++
+ char=copytext(code, codepos, codepos+1)
+ var/token/number/T=new(buf, line, COL)
+ if(isnull(text2num(buf)))
+ errors+=new/scriptError("Bad number: ", T)
+ T.value=0
+ codepos-- //allow main Scan() proc to read the next character
+ return T
\ No newline at end of file
diff --git a/code/modules/scripting/Scanner/Tokens.dm b/code/modules/scripting/Scanner/Tokens.dm
new file mode 100644
index 00000000000..6bb853518f0
--- /dev/null
+++ b/code/modules/scripting/Scanner/Tokens.dm
@@ -0,0 +1,38 @@
+/*
+ Class: Token
+ Represents an entity and position in the source code.
+*/
+/token
+ var
+ value
+ line
+ column
+
+ New(v, l=0, c=0)
+ value=v
+ line=l
+ column=c
+
+ string
+ symbol
+ word
+ keyword
+ number
+ New()
+ .=..()
+ if(!isnum(value))
+ value=text2num(value)
+ ASSERT(!isnull(value))
+ accessor
+ var
+ object
+ member
+
+ New(object, member, l=0, c=0)
+ src.object=object
+ src.member=member
+ src.value="[object].[member]" //for debugging only
+ src.line=l
+ src.column=c
+
+ end
\ No newline at end of file
diff --git a/code/modules/scripting/stack.dm b/code/modules/scripting/stack.dm
new file mode 100644
index 00000000000..be4147c7ec6
--- /dev/null
+++ b/code/modules/scripting/stack.dm
@@ -0,0 +1,23 @@
+/stack
+ var/list
+ contents=new
+ proc
+ Push(value)
+ contents+=value
+
+ Pop()
+ if(!contents.len) return null
+ . = contents[contents.len]
+ contents.len--
+
+ Top() //returns the item on the top of the stack without removing it
+ if(!contents.len) return null
+ return contents[contents.len]
+
+ Copy()
+ var/stack/S=new()
+ S.contents=src.contents.Copy()
+ return S
+
+ Clear()
+ contents.Cut()
\ No newline at end of file
diff --git a/icons/obj/projectiles.dmi b/icons/obj/projectiles.dmi
index ac9864fe2d5..857fe21d010 100644
Binary files a/icons/obj/projectiles.dmi and b/icons/obj/projectiles.dmi differ
diff --git a/icons/obj/radio.dmi b/icons/obj/radio.dmi
index d066b144abb..69180436a7c 100644
Binary files a/icons/obj/radio.dmi and b/icons/obj/radio.dmi differ
diff --git a/icons/obj/stationobjs.dmi b/icons/obj/stationobjs.dmi
index 3095c50375f..913295eaf69 100755
Binary files a/icons/obj/stationobjs.dmi and b/icons/obj/stationobjs.dmi differ
diff --git a/interface/skin.dmf b/interface/skin.dmf
index 0a20c2a1560..adab69791fe 100644
--- a/interface/skin.dmf
+++ b/interface/skin.dmf
@@ -136,6 +136,8 @@ macro "macro"
command = "asay"
is-disabled = false
+macro "tcside"
+
macro "vending"
elem
name = "NORTHEAST"
@@ -290,6 +292,208 @@ menu "menu"
is-disabled = false
saved-params = "is-checked"
+window "Telecomms IDE"
+ elem "Telecomms IDE"
+ type = MAIN
+ pos = 281,0
+ size = 652x582
+ anchor1 = none
+ anchor2 = none
+ font-family = ""
+ font-size = 0
+ font-style = ""
+ text-color = #000000
+ background-color = #ffffff
+ is-visible = false
+ is-disabled = false
+ is-transparent = false
+ is-default = false
+ border = none
+ drop-zone = false
+ right-click = false
+ saved-params = "pos;size;is-minimized;is-maximized"
+ on-size = ""
+ title = "TCS IDE"
+ titlebar = true
+ statusbar = false
+ can-close = true
+ can-minimize = true
+ can-resize = true
+ is-pane = false
+ is-minimized = false
+ is-maximized = false
+ can-scroll = none
+ icon = ""
+ image = ""
+ image-mode = stretch
+ keep-aspect = false
+ transparent-color = none
+ alpha = 255
+ macro = ""
+ menu = ""
+ on-close = "exittcs"
+ elem "button4"
+ type = BUTTON
+ pos = 180,464
+ size = 60x20
+ anchor1 = none
+ anchor2 = none
+ font-family = ""
+ font-size = 0
+ font-style = ""
+ text-color = #000000
+ background-color = none
+ is-visible = true
+ is-disabled = false
+ is-transparent = false
+ is-default = false
+ border = none
+ drop-zone = false
+ right-click = false
+ saved-params = "is-checked"
+ on-size = ""
+ text = "Revert"
+ image = ""
+ command = "tcsrevert"
+ is-flat = false
+ stretch = false
+ is-checked = false
+ group = ""
+ button-type = pushbutton
+ elem "button3"
+ type = BUTTON
+ pos = 120,464
+ size = 60x20
+ anchor1 = none
+ anchor2 = none
+ font-family = ""
+ font-size = 0
+ font-style = ""
+ text-color = #000000
+ background-color = none
+ is-visible = true
+ is-disabled = false
+ is-transparent = false
+ is-default = false
+ border = none
+ drop-zone = false
+ right-click = false
+ saved-params = "is-checked"
+ on-size = ""
+ text = "Execute"
+ image = ""
+ command = "tcsrun"
+ is-flat = false
+ stretch = false
+ is-checked = false
+ group = ""
+ button-type = pushbutton
+ elem "tcserror"
+ type = OUTPUT
+ pos = 0,488
+ size = 648x94
+ anchor1 = none
+ anchor2 = none
+ font-family = "sans-serif"
+ font-size = 9
+ font-style = ""
+ text-color = #000000
+ background-color = #ffffff
+ is-visible = true
+ is-disabled = false
+ is-transparent = false
+ is-default = false
+ border = none
+ drop-zone = false
+ right-click = false
+ saved-params = "max-lines"
+ on-size = ""
+ link-color = #0000ff
+ visited-color = #ff00ff
+ style = ""
+ enable-http-images = false
+ max-lines = 1000
+ image = ""
+ elem "button2"
+ type = BUTTON
+ pos = 60,464
+ size = 60x20
+ anchor1 = none
+ anchor2 = none
+ font-family = ""
+ font-size = 0
+ font-style = ""
+ text-color = #000000
+ background-color = none
+ is-visible = true
+ is-disabled = false
+ is-transparent = false
+ is-default = false
+ border = none
+ drop-zone = false
+ right-click = false
+ saved-params = "is-checked"
+ on-size = ""
+ text = "Compile"
+ image = ""
+ command = "tcscompile"
+ is-flat = false
+ stretch = false
+ is-checked = false
+ group = ""
+ button-type = pushbutton
+ elem "button1"
+ type = BUTTON
+ pos = 0,464
+ size = 60x20
+ anchor1 = none
+ anchor2 = none
+ font-family = ""
+ font-size = 0
+ font-style = ""
+ text-color = #000000
+ background-color = none
+ is-visible = true
+ is-disabled = false
+ is-transparent = false
+ is-default = false
+ border = none
+ drop-zone = false
+ right-click = false
+ saved-params = "is-checked"
+ on-size = ""
+ text = "Apply"
+ image = ""
+ command = "tcssave"
+ is-flat = false
+ stretch = false
+ is-checked = false
+ group = ""
+ button-type = pushbutton
+ elem "tcscode"
+ type = INPUT
+ pos = 0,0
+ size = 652x464
+ anchor1 = none
+ anchor2 = none
+ font-family = "Courier"
+ font-size = 10
+ font-style = ""
+ text-color = #000000
+ background-color = #ffffff
+ is-visible = true
+ is-disabled = false
+ is-transparent = false
+ is-default = false
+ border = none
+ drop-zone = false
+ right-click = false
+ saved-params = ""
+ on-size = ""
+ command = "cancel"
+ multi-line = true
+ is-password = false
+ no-command = true
window "chemdispenser"
elem "chemdispenser"
diff --git a/maps/tgstation.2.0.8.dmm b/maps/tgstation.2.0.8.dmm
index e728b7f120b..8e0ce36ac47 100755
--- a/maps/tgstation.2.0.8.dmm
+++ b/maps/tgstation.2.0.8.dmm
@@ -3966,7 +3966,7 @@
"byn" = (/obj/structure/reagent_dispensers/watertank,/obj/machinery/atmospherics/pipe/simple/supply/hidden{dir = 4},/turf/simulated/floor/plating,/area/maintenance/asmaint)
"byo" = (/obj/machinery/atmospherics/pipe/manifold{color = "blue"; icon_state = "manifold-b-f"; level = 1; name = "pipe manifold"},/turf/simulated/wall,/area/maintenance/asmaint)
"byp" = (/obj/structure/cable{d1 = 1; d2 = 2; icon_state = "1-2"; pixel_y = 0; tag = "Streight"},/obj/machinery/atmospherics/pipe/simple/supply/hidden{dir = 4},/turf/simulated/floor/plating,/area/maintenance/asmaint)
-"byq" = (/obj/machinery/atmospherics/pipe/simple/supply/hidden{dir = 10},/obj/structure/disposalpipe/segment,/obj/effect/decal/cleanable/blood/splatter{icon_state = "floor2"},/turf/simulated/floor/plating,/area/maintenance/asmaint)
+"byq" = (/obj/machinery/atmospherics/pipe/simple/supply/hidden{dir = 10},/obj/structure/disposalpipe/segment,/turf/simulated/floor/plating,/area/maintenance/asmaint)
"byr" = (/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden,/obj/structure/cable{d1 = 1; d2 = 2; icon_state = "1-2"; pixel_y = 0; tag = "Streight"},/obj/machinery/door/airlock/maintenance{req_access_txt = "12"},/obj/structure/disposalpipe/segment{dir = 1},/turf/simulated/floor/plating,/area/maintenance/maintcentral)
"bys" = (/obj/machinery/atmospherics/pipe/simple/supply/hidden,/turf/simulated/wall,/area/medical/cryo)
"byt" = (/turf/simulated/wall,/area/medical/cryo)