From 112523086b2b8e4a76c59e6502263cd6d8c0ea97 Mon Sep 17 00:00:00 2001 From: FalseIncarnate Date: Mon, 28 Mar 2016 00:53:21 -0400 Subject: [PATCH] Logic Gates WIP Commit --- code/__DEFINES/stat.dm | 14 +- code/controllers/communications.dm | 1 + code/game/machinery/constructable_frame.dm | 40 ++- code/modules/logic/logic_base.dm | 277 +++++++++++++++++++++ code/modules/logic/logic_gate.dm | 165 ++++++++++++ paradise.dme | 2 + 6 files changed, 497 insertions(+), 2 deletions(-) create mode 100644 code/modules/logic/logic_base.dm create mode 100644 code/modules/logic/logic_gate.dm diff --git a/code/__DEFINES/stat.dm b/code/__DEFINES/stat.dm index 756ae54765f..3da3a84455f 100644 --- a/code/__DEFINES/stat.dm +++ b/code/__DEFINES/stat.dm @@ -40,4 +40,16 @@ #define SHUTTLE_DOCKED 3 #define SHUTTLE_STRANDED 4 #define SHUTTLE_ESCAPE 5 -#define SHUTTLE_ENDGAME 6 \ No newline at end of file +#define SHUTTLE_ENDGAME 6 + + +/* + Logic +*/ +// State When to Use Example +#define LOGIC_OFF 0 //Use this for when you want it to stay off (continuous signal, lever) +#define LOGIC_ON 1 //Use this for when you want it to stay on (continuous signal, lever) +#define LOGIC_FLICKER 2 //Use this for when you want it to turn on and then turn off (buttons, clocks) + +//Logic-related stuff (Misc. defines for logic things, to keep it organized or something) +#define LOGIC_FLICKER_TIME 10 //number of deciseconds LOGIC_TEMP_ON will remain active before turning off \ No newline at end of file diff --git a/code/controllers/communications.dm b/code/controllers/communications.dm index fd3fa222c82..43bee48f267 100644 --- a/code/controllers/communications.dm +++ b/code/controllers/communications.dm @@ -202,6 +202,7 @@ var/const/RADIO_CLEANBOT = "10" var/const/RADIO_FLOORBOT = "11" var/const/RADIO_MEDBOT = "12" var/const/RADIO_MAGNETS = "radio_magnet" +var/const/RADIO_LOGIC = "radio_logic" var/global/datum/controller/radio/radio_controller diff --git a/code/game/machinery/constructable_frame.dm b/code/game/machinery/constructable_frame.dm index 62b090ba1db..9e65a9fc2ac 100644 --- a/code/game/machinery/constructable_frame.dm +++ b/code/game/machinery/constructable_frame.dm @@ -878,4 +878,42 @@ obj/item/weapon/circuitboard/rdserver req_components = list( /obj/item/weapon/stock_parts/matter_bin = 1, /obj/item/weapon/stock_parts/manipulator = 1, - /obj/item/weapon/stock_parts/console_screen = 1) \ No newline at end of file + /obj/item/weapon/stock_parts/console_screen = 1) + + +//Selectable mode board, like vending machine boards +/obj/item/weapon/circuitboard/logic_gate + name = "circuit board (Logic Connector)" + build_path = /obj/machinery/logic + board_type = "machine" + origin_tech = "programming=1" //This stuff is pretty much the absolute basis of programming, so it's mostly useless for research + req_components = list(/obj/item/stack/cable_coil = 1) + + var/list/names_paths = list( + "NOT Gate" = /obj/machinery/logic_gate/not, + "OR Gate" = /obj/machinery/logic_gate/or, + "AND Gate" = /obj/machinery/logic_gate/and, + "NAND Gate" = /obj/machinery/logic_gate/nand, + "NOR Gate" = /obj/machinery/logic_gate/nor, + "XOR Gate" = /obj/machinery/logic_gate/xor, + "XNOR Gate" = /obj/machinery/logic_gate/xnor + ) + +/obj/item/weapon/circuitboard/logic_gate/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/weapon/screwdriver)) + set_type(null, user) + +/obj/item/weapon/circuitboard/logic_gate/proc/set_type(typepath, mob/user) + var/new_name = "Logic Base" + if(!typepath) + new_name = input("Circuit Setting", "What would you change the board setting to?") in names_paths + typepath = names_paths[new_name] + else + for(var/name in names_paths) + if(names_paths[name] == typepath) + new_name = name + break + build_path = typepath + name = "circuit board ([new_name])" + if(user) + user << "You set the board to [new_name]." diff --git a/code/modules/logic/logic_base.dm b/code/modules/logic/logic_base.dm new file mode 100644 index 00000000000..00f50313819 --- /dev/null +++ b/code/modules/logic/logic_base.dm @@ -0,0 +1,277 @@ + +/obj/machinery/logic_gate + name = "Logic Base" + desc = "This does nothing except connect to things. Highly illogical, report to a coder at once if you see this in-game." + icon = 'icons/obj/computer3.dmi' + icon_state = "serverframe" + density = 1 + anchored = 1 + + settagwhitelist = list("input1_id_tag", "input2_id_tag", "output_id_tag") + + var/tamperproof = 0 //if set, will make the machine unable to be destroyed, adjusted, etc. via in-game interaction (USE ONLY FOR MAPPING STUFF) + var/mono_input = 0 //if set, will ignore input2 + + var/datum/radio_frequency/radio_connection + var/frequency = 0 + + /* + Some notes on Input/Output: + - Multiple things can be linked to the same input or output tag, just like how wires can connect multiple sources and receivers. + - For inputs, only the last signal received BEFORE a process() call will be used in the logic handling. + - Input states are updated immediately whenever an input signal is received, so it is possible to update multiple times within a single process cycle. + - This means if you have multiple connected inputs, but the last signal received before the process() call is OFF, it won't matter if the others are both ON. + - For this reason, please set up your logic properly. You can theoretically chain these infinitely, so there's no need to link multiple things to a single input. + - For outputs, the signal will attempt to be sent out every process() call, to ensure newly connected things are updated within one process cycle + - Connecting an output to multiple inputs should not cause issues, as long as you don't have multiple connections to a given input (see previous notes on inputs). + - The output state is determined immediately preceeding the signal broadcast, using the input states at the time of the process() call, not when a signal is received. + - Because of how the process cycle works, it is possible that it may take multiple cycles for a signal to fully propogate through a logic chain. + - This is because machines attempt to process in the order they were added to the scheduler. + - Building the logic gates at the end of the chain first may cause delays in signal propogation. + If you take all this into consideration when linking and using logic machinery, you should have no unexpected issues with input/output. Your design flaws are on you though. + */ + + var/input1_id_tag = null + var/input1_state = LOGIC_OFF + var/input2_id_tag = null + var/input2_state = LOGIC_OFF + var/output_id_tag = null + var/output_state = LOGIC_OFF + +/obj/machinery/logic_gate/New() + if(tamperproof) //doing this during New so we don't have to worry about forgetting to set these vars during editting / defining + unacidable = 1 + ..() + if(!radio_controller) + spawn(40) + set_frequency(frequency) + else + spawn(1) // For things that set the frequency directly + set_frequency(frequency) + component_parts = list() + var/obj/item/weapon/circuitboard/logic_gate/LG = new(null) + LG.set_type(type) + component_parts += LG + component_parts += new /obj/item/stack/cable_coil(null, 1) + +/obj/machinery/logic_gate/proc/set_frequency(new_frequency) + if(!radio_controller) + sleep(20) + if(!radio_controller) + return + radio_controller.remove_object(src, frequency) + frequency = new_frequency + radio_connection = radio_controller.add_object(src, frequency, RADIO_LOGIC) + return + +/obj/machinery/logic_gate/Destroy() + if(radio_controller) + radio_controller.remove_object(src,frequency) + return ..() + +/obj/machinery/logic_gate/process() + handle_logic() + handle_output() //All output will send for at least one cycle, and will attempt to send every cycle. Hopefully this won't be too taxing. + return + +/obj/machinery/logic_gate/proc/handle_logic() + return + +/obj/machinery/logic_gate/proc/handle_output() + if(!radio_connection) //can't output without this + return + + if(output_id_tag == null) //Don't output to an undefined id_tag + return + + var/datum/signal/signal = new + signal.transmission_method = 1 //radio signal + signal.source = src + + signal.data = list( + "tag" = output_id_tag, + "sigtype" = "logic", + "state" = output_state, + ) + + radio_connection.post_signal(src, signal) + return + +/obj/machinery/logic_gate/receive_signal(datum/signal/signal, receive_method, receive_param) + if(!signal.data["tag"] || ((signal.data["tag"] != input1_id_tag) && (signal.data["tag"] != input2_id_tag)) || (signal.data["sigtype"] != "logic")) + //If the signal lacks tag data, the signal's tag data doesn't match either input id tag, or is not a "logic" signal, ignore it since it's not for us + return + + if(signal.data["tag"] == input1_id_tag) //If the signal is for input1 + if(signal.data["state"] == input1_state) //If we already match, ignore the new signal since nothing changes + return + if(signal.data["state"] == LOGIC_OFF) //Shut it down and keep it off + input1_state = LOGIC_OFF + return + if(signal.data["state"] == LOGIC_ON) //Turn it on and keep it on + input1_state = LOGIC_ON + return + if(signal.data["state"] == LOGIC_FLICKER) //Turn it on then turn it off + if(input1_state == LOGIC_ON) //An existing continuous ON state overrides new flicker signals + return + input1_state = LOGIC_FLICKER + spawn(LOGIC_FLICKER_TIME) + if(input1_state == LOGIC_FLICKER) //Make sure we didn't get a new continuous signal set while we waited (those take priority) + input1_state = LOGIC_OFF + return + + //Now, you may be wondering why I included those returns. + //The answer is "If you link both inputs to the same source, you're an idiot and deserve to have it break", so yeah. Deal with it. + + if(mono_input) + //We only care about input1, so if we didn't receive a signal for that, we're done. + return + + if(signal.data["tag"] == input2_id_tag) //If the signal is for input2 (reaching this point assumes mono_input is not set) + if(signal.data["state"] == input2_state) //If we already match, ignore the new signal since nothing changes + return + if(signal.data["state"] == LOGIC_OFF) //Shut it down and keep it off + input2_state = LOGIC_OFF + return + if(signal.data["state"] == LOGIC_ON) //Turn it on and keep it on + input2_state = LOGIC_ON + return + if(signal.data["state"] == LOGIC_FLICKER) //Turn it on then turn it off + if(input2_state == LOGIC_ON) //An existing continuous ON state overrides new flicker signals + return + input2_state = LOGIC_FLICKER + spawn(LOGIC_FLICKER_TIME) + if(input2_state == LOGIC_FLICKER) //Make sure we didn't get a new continuous signal set while we waited (those take priority) + input2_state = LOGIC_OFF + return + +/obj/machinery/logic_gate/multitool_menu(var/mob/user, var/obj/item/device/multitool/P) + var/input1_state_string + var/input2_state_string + var/output_state_string + + switch(input1_state) + if(LOGIC_OFF) + input1_state_string = "OFF" + if(LOGIC_ON) + input1_state_string = "ON" + if(LOGIC_FLICKER) + input1_state_string = "FLICKER" + else + input1_state_string = "ERROR: UNKNOWN STATE" + + switch(input2_state) + if(LOGIC_OFF) + input2_state_string = "OFF" + if(LOGIC_ON) + input2_state_string = "ON" + if(LOGIC_FLICKER) + input2_state_string = "FLICKER" + else + input2_state_string = "ERROR: UNKNOWN STATE" + + switch(output_state) + if(LOGIC_OFF) + output_state_string = "OFF" + if(LOGIC_ON) + output_state_string = "ON" + if(LOGIC_FLICKER) + output_state_string = "FLICKER" + else + output_state_string = "ERROR: UNKNOWN STATE" + var/menu_contents = {" +
+
Input: [format_tag("ID Tag","input1_id_tag")]
+
Input State: [input1_state_string]
+ "} + if(!mono_input) + menu_contents = {" +
Input 1: [format_tag("ID Tag","input1_id_tag")]
+
Input 1 State: [input1_state_string]
+
Input 2: [format_tag("ID Tag","input2_id_tag")]
+
Input 2 State: [input2_state_string]
+ "} + menu_contents += {" +
Output: [format_tag("ID Tag","output_id_tag")]
+
Output State: [output_state_string]
+
+ "} + return menu_contents + +/obj/machinery/logic_gate/attackby(obj/item/O, mob/user, params) + if(tamperproof) + user << "The [src] appears to be tamperproofed! You can't interact with it!" + return 0 + if(istype(O, /obj/item/device/multitool)) + update_multitool_menu(user) + return 1 + if(istype(O, /obj/item/weapon/screwdriver)) + panel_open = !panel_open + user << "You [panel_open ? "open" : "close"] the access panel." + return 1 + if(panel_open && istype(O, /obj/item/weapon/crowbar)) + default_deconstruction_crowbar(O) + return 1 + +////////////////////////////////////// +// Attack procs // +////////////////////////////////////// + +/obj/machinery/logic_gate/attack_ai(mob/user) + if(tamperproof) + user << "The [src] appears to be tamperproofed! You can't interface with it!" + return 0 + add_hiddenprint(user) + return ui_interact(user) + +/obj/machinery/logic_gate/attack_ghost(mob/user) + if(tamperproof) + user << "The [src] appears to be tamperproofed! You can't haunt it!" + return 0 + return ui_interact(user) + +/obj/machinery/logic_gate/attack_hand(mob/user) + if(tamperproof) + user << "The [src] appears to be tamperproofed! You can't interact with it!" + return 0 + . = ..() + if(.) + return 0 + return interact(user) + +/obj/machinery/logic_gate/attack_alien(mob/user) //No xeno logic, that's too silly. + user << "The [src] appears to be too complex! You can't comprehend it and back off in fear!" + return 0 + +/obj/machinery/logic_gate/attack_animal(mob/user) //No animal logic either. + user << "The [src] appears to be beyond your comprehension! You can't fathom it!" + return 0 + +/obj/machinery/logic_gate/attack_slime(mob/user) //No slime logic. Seriously. + user << "The [src] appears to be beyond your gelatinous understanding! You ignore it!" + return 0 + +/obj/machinery/logic_gate/emp_act(severity) + if(tamperproof) + return 0 + ..() + +/obj/machinery/logic_gate/ex_act(severity) + if(tamperproof) + return 0 + ..() + +/obj/machinery/logic_gate/blob_act() + if(tamperproof) + return 0 + ..() + +/obj/machinery/logic_gate/singularity_act() + if(tamperproof) + //This is some top-level tamperproofing right here, that's for sure. It can even defy a singularity! + return 0 + +/obj/machinery/logic_gate/bullet_act() + if(tamperproof) + return 0 + ..() \ No newline at end of file diff --git a/code/modules/logic/logic_gate.dm b/code/modules/logic/logic_gate.dm new file mode 100644 index 00000000000..75a4ca489dc --- /dev/null +++ b/code/modules/logic/logic_gate.dm @@ -0,0 +1,165 @@ + +////////////////////////////////// +// Mono-Input Gates // +////////////////////////////////// + +//NOT Gate +/obj/machinery/logic_gate/not + name = "NOT Gate" + desc = "Accepts one input and outputs the reverse state." + mono_input = 1 //NOT Gates are the simplest logic gate because they only utilize one input. + output_state = LOGIC_ON //Starts with an active output, since the input will be OFF at start + +/obj/machinery/logic_gate/not/process() //This gate only handles its logic when it receives a signal, and outputs both on the process and when it receives the signal + handle_output() + return +/* + A quick note regarding NOT Gates: + - Connecting multiple things to the input of a NOT Gate can cause weird behaviour due to updating both when it receives a signal and when it calls process(). + - This means it will attempt to output once for every logic machine connected to its input's own process() call. + - It will then attempt to output an additional time based on the current state when it comes to its own process() call. + - For this reason, it is HIGHLY RECOMMENDED that you only connect a single signal source to the input of a NOT Gate to avoid signal spasms. + - Connecting multiple things to the output of a NOT Gate should not cause this unusual behavior. +*/ +/obj/machinery/logic_gate/not/receive_signal(datum/signal/signal, receive_method, receive_param) + ..() + handle_logic() + return + +/obj/machinery/logic_gate/not/handle_logic() //Our output will always be a continuous signal, even with a FLICKER, it just will update the output when the FLICKER ends + if(input1_state == LOGIC_ON) //Output is OFF while input is ON + output_state = LOGIC_OFF + else if(input1_state == LOGIC_FLICKER) //Output is OFF while input is FLICKER, then output returns to ON when input returns to OFF + output_state = LOGIC_OFF + spawn(LOGIC_FLICKER_TIME + 1) //Call handle_logic again after this delay (the input should update from the spawn(LOGIC_FLICKER_TIME) in receive_signal() by then) + handle_logic() + else //Output is ON while input is OFF + output_state = LOGIC_ON + handle_output() + return + +//STATUS Gate +/obj/machinery/logic_gate/status + name = "Status Gate" + desc = "Accepts one input and outputs the same state, showing a colored light based on current state." + mono_input = 1 //STATUS Gate doesn't actually perform logic operations, but instead acts as a testing conduit. + +/* + STATUS Gates are largely a diagnostics tool, but I'm sure someone will still make a logic gate rave with them anyways. + - There is no need to actually connect an output for these to work, they just need an input to sample from. + - STATUS Gates attempt to output both on the process cycle and whenever a signal is received. + - This may seem a little spammy, but ensures they don't hold anything up signal-wise. +*/ + +/obj/machinery/logic_gate/status/receive_signal(datum/signal/signal, receive_method, receive_params) + ..() + handle_logic() //STATUS Gate calls handle_logic() when it receives a signal to update its light and output_state + handle_output() //STATUS Gate outputs when it receives a signal, since it is just a connector piece (like a wire for power) + +/obj/machinery/logic_gate/status/handle_logic() + output_state = input1_state //Output is equal to input, since it is simply a connection with an attached light + if(output_state == LOGIC_OFF) //Red light when OFF + set_light(2,2,"#ff0000") + return + if(output_state == LOGIC_ON) //Green light when ON + set_light(2,2,"#009933") + return + if(output_state == LOGIC_FLICKER) //Orange light when FLICKER, then update after LOGIC_FLICKER_TIME + 1 to reflect the changed state + set_light(2,2,"#ff9900") + spawn(LOGIC_FLICKER_TIME + 1) + handle_logic() + handle_output() + return + +////////////////////////////////// +// Dual-Input Gates // +////////////////////////////////// + + +// OR Gate +/obj/machinery/logic_gate/or + name = "OR Gate" + desc = "Outputs ON when at least one input is ON." + +/obj/machinery/logic_gate/or/handle_logic() + if(input1_state == LOGIC_ON || input1_state == LOGIC_FLICKER || input2_state == LOGIC_ON || input2_state == LOGIC_FLICKER) + if(input1_state == LOGIC_ON || input2_state == LOGIC_ON) //continuous signal takes priority in determining what to output + output_state = LOGIC_ON + else + output_state = LOGIC_FLICKER + else //Both inputs were off, so input is off + output_state = LOGIC_OFF + return + +// AND Gate +/obj/machinery/logic_gate/and + name = "AND Gate" + desc = "Outputs ON only when both inputs are ON." + +/obj/machinery/logic_gate/and/handle_logic() + if((input1_state == LOGIC_ON || input1_state == LOGIC_FLICKER) && (input2_state == LOGIC_ON || input2_state == LOGIC_FLICKER)) + if(input1_state == LOGIC_ON && input2_state == LOGIC_ON) //only output a continuous signal when both inputs are continuous signals + output_state = LOGIC_ON + else + output_state = LOGIC_FLICKER + else //At least one input was off, so output is off + output_state = LOGIC_OFF + return + +// NAND Gate +/obj/machinery/logic_gate/nand + name = "NAND Gate" + desc = "Outputs OFF only when both inputs are ON." + output_state = LOGIC_ON + +/obj/machinery/logic_gate/nand/handle_logic() + if((input1_state == LOGIC_ON || input1_state == LOGIC_FLICKER) && (input2_state == LOGIC_ON || input2_state == LOGIC_FLICKER)) + output_state = LOGIC_OFF //This can only output continuous signals + else //At least one input was ON/FLICKER, so output is off + output_state = LOGIC_OFF + return + +// NOR Gate +/obj/machinery/logic_gate/nor + name = "NOR Gate" + desc = "Outputs OFF when at least one input is ON." + output_state = LOGIC_ON + +/obj/machinery/logic_gate/nor/handle_logic() + if(input1_state == LOGIC_OFF ||input2_state == LOGIC_OFF) + output_state = LOGIC_ON //This can only output continuous signals + else //Both inputs are ON, so output is OFF + output_state = LOGIC_OFF + return + +// XOR Gate +/obj/machinery/logic_gate/xor + name = "XOR Gate" + desc = "Outputs ON when only one input is ON." + +/obj/machinery/logic_gate/xor/handle_logic() + if((input1_state == LOGIC_ON || input1_state == LOGIC_FLICKER) || input2_state == LOGIC_OFF) //Only input1 is ON/FLICKER, so output matches input1 + output_state = input1_state + else if((input2_state == LOGIC_ON || input2_state == LOGIC_FLICKER) || input1_state == LOGIC_OFF) //Only input2 is ON/FLICKER, so output matches input2 + output_state = input2_state + else //Both inputs are ON or OFF, so output is OFF + output_state = LOGIC_OFF + return + +// XNOR Gate +/obj/machinery/logic_gate/xnor + name = "XNOR Gate" + desc = "Outputs ON when both inputs are ON or OFF." + output_state = LOGIC_ON + +/obj/machinery/logic_gate/xnor/handle_logic() + if((input1_state == LOGIC_ON || input1_state == LOGIC_FLICKER) && (input2_state == LOGIC_ON || input2_state == LOGIC_FLICKER)) //Both inputs are ON/FLICKER + if(input1_state == LOGIC_ON && input2_state == LOGIC_ON) //Only continuous signal when both inputs are ON + output_state = LOGIC_ON + else //If at least one input is FLICKER, output FLICKER + output_state = LOGIC_FLICKER + else if(input1_state == LOGIC_OFF && input2_state == LOGIC_OFF) //Both inputs are OFF + output_state = LOGIC_ON //Always continuous in this case + else //Only one input is ON/FLICKER + output_state = LOGIC_OFF + return diff --git a/paradise.dme b/paradise.dme index a05cd77e6f4..c4c7bfc1545 100644 --- a/paradise.dme +++ b/paradise.dme @@ -1272,6 +1272,8 @@ #include "code\modules\lighting\lighting_process.dm" #include "code\modules\lighting\lighting_system.dm" #include "code\modules\lighting\lighting_turf.dm" +#include "code\modules\logic\logic_base.dm" +#include "code\modules\logic\logic_gate.dm" #include "code\modules\media\jukebox.dm" #include "code\modules\media\machinery.dm" #include "code\modules\media\mediamanager.dm"