diff --git a/baystation12.dme b/baystation12.dme index 9953cacd6e..beed9a343a 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 df605dd7d0..d6fce9d1d9 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 e058b35984..2eb032eaca 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 dcc30eceec..7a60d10ecd 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 dca6d7b4b4..0a0b60d236 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 c3ac2943d8..0a923bf110 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 03665dda37..a9934dd7b7 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 2fc5a6c975..2ead66f3eb 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 e1a8bab184..a84aff8bed 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 a7ee7e827e..1866477d7a 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 0000000000..5f0ede226a --- /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 Control
      Telecommunications 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 7f35b5c268..1b0503a40a 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 340d32c847..4470eae3ad 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 488db44b81..8b5b524969 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 37d0b54cf0..8639893257 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 4f5b5d0763..512a8cd478 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 0000000000..2758ddf909 --- /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 233c256bb7..dfd8fd0869 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 ed3bbe0d64..e183ff2d46 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 f9238d5f67..b6dfc498cc 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 9a67475901..41c9033eae 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 8da08eacb1..efdcb7b2fe 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 8ba9a6c4bc..81c9f08185 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 3ccc5ff58b..879502d8d3 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 87230391ed..6a60134456 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 682b11dd7b..06407c5f56 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 53577373c0..fa8ae87b1f 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 0a4baebcae..327bcbb0c1 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 c84b909cd3..3539ddf808 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 cf605048c7..9c0ea592f2 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 0000000000..6853697ebf --- /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 0000000000..18f408f1f1 --- /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 0000000000..138ff00c3b --- /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 0000000000..3a5f3ffb96 --- /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 0000000000..e60fcb389c --- /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 0000000000..361d762048 --- /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 0000000000..f45ad8d400 --- /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 0000000000..d9f6e2ee85 --- /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 0000000000..e6ae13fded --- /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 0000000000..02cdc929c5 --- /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 0000000000..0dda1656d6 --- /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 0000000000..f13c8f3b03 --- /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 0000000000..50da804707 --- /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 0000000000..22774455dc --- /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 0000000000..3245a4a28f --- /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 0000000000..05aa0cd6a4 --- /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 0000000000..9f88fd2227 --- /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 0000000000..173b50b453 --- /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 0000000000..6bb853518f --- /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 0000000000..be4147c7ec --- /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 ac9864fe2d..857fe21d01 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 d066b144ab..69180436a7 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 3095c50375..913295eaf6 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 0a20c2a156..adab69791f 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 e728b7f120..8e0ce36ac4 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)