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 = {" +