diff --git a/code/datums/EPv2.dm b/code/datums/EPv2.dm new file mode 100644 index 0000000000000..f0e912fa55a95 --- /dev/null +++ b/code/datums/EPv2.dm @@ -0,0 +1,127 @@ +/* +Exonet Protocol Version 2 + +This is designed to be a fairly simple fake-networking system, allowing you to send and receive messages +between the exonet_protocol datums, and for atoms to react to those messages, based on the contents of the message. +Hopefully, this can evolve to be a more robust fake-networking system and allow for some devious network hacking in the future. + +Version 1 never existed. + +*Setting up* + +To set up the exonet link, define a variable on your desired atom it is like this; + var/datum/exonet_protocol/exonet = null +Afterwards, before you want to do networking, call exonet = New(src), then exonet.make_address(string), and give it a string to hash into the new IP. +The reason it needs a string is so you can have the addresses be persistant, assuming no-one already took it first. + +When you're no longer wanting to use the address and want to free it up, like when you want to Destroy() it, you need to call remove_address() + +*Sending messages* + +To send a message to another datum, you need to know it's EPv2 (fake IP) address. Once you know that, call send_message(), place your +intended address in the first argument, then the message in the second. For example, send_message(exonet.address, "ping") will make you +ping yourself. + +*Receiving messages* +You don't need to do anything special to receive the messages, other than give your target exonet datum an address as well. Once something hits +your datum with send_message(), receive_message() is called, and the default action is to call receive_exonet_message() on the datum's holder. +You'll want to override receive_exonet_message() on your atom, and define what will occur when the message is received. +The receiving atom will receive the origin atom (the atom that sent the message), the origin address, and finally the message itself. +It's suggested to start with an if or switch statement for the message, to determine what to do. +*/ + +var/global/list/all_exonet_connections = list() + +/datum/exonet_protocol + var/address = "" //Resembles IPv6, but with only five 'groups', e.g. XXXX:XXXX:XXXX:XXXX:XXXX + var/atom/movable/holder = null + +/datum/exonet_protocol/New(var/atom/holder) + src.holder = holder + ..() + + +// Proc: make_address() +// Parameters: 1 (string - used to make into a hash that will be part of the new address) +// Description: Allocates a new address based on the string supplied. It results in consistant addresses for each round assuming it is not already taken.. +/datum/exonet_protocol/proc/make_address(var/string) + if(string) + var/new_address = null + while(new_address == find_address(new_address)) //Collision test. + var/hash = md5(string) + var/raw_address = copytext(hash,1,25) + var/addr_0 = "fc00" //Used for unique local address in real-life IPv6. + var/addr_1 = hexadecimal_to_EPv2(raw_address) + + new_address = "[addr_0]:[addr_1]" + string = "[string]0" //If we did get a collision, this should make the next attempt not have one. + sleep(1) + address = new_address + all_exonet_connections |= src + + +// Proc: make_arbitrary_address() +// Parameters: 1 (new_address - the desired address) +// Description: Allocates that specific address, if it is available. +/datum/exonet_protocol/proc/make_arbitrary_address(var/new_address) + if(new_address) + if(new_address == find_address(new_address) ) //Collision test. + return 0 + address = new_address + all_exonet_connections |= src + return 1 + +// Proc: hexadecimal_to_EPv2() +// Parameters: 1 (hex - a string of hexadecimals to convert) +// Description: Helper proc to add colons to a string in the right places. +/proc/hexadecimal_to_EPv2(var/hex) + if(!hex) + return null + var/addr_1 = copytext(hex,1,5) + var/addr_2 = copytext(hex,5,9) + var/addr_3 = copytext(hex,9,13) + var/addr_4 = copytext(hex,13,17) + var/new_address = "[addr_1]:[addr_2]:[addr_3]:[addr_4]" + return new_address + + +// Proc: remove_address() +// Parameters: None +// Description: Deallocates the address, freeing it for use. +/datum/exonet_protocol/proc/remove_address() + address = "" + all_exonet_connections.Remove(src) + + +// Proc: find_address() +// Parameters: 1 (target_address - the desired address to find) +// Description: Searches the global list all_exonet_connections for a specific address, and returns it if found, otherwise returns null. +/datum/exonet_protocol/proc/find_address(var/target_address) + for(var/datum/exonet_protocol/exonet in all_exonet_connections) + if(exonet.address == target_address) + return exonet.address + return null + +// Proc: send_message() +// Parameters: 2 (target_address - the desired address to send the message to, message - the message to send) +// Description: Sends the message to target_address, by calling receive_message() on the desired datum. +/datum/exonet_protocol/proc/send_message(var/target_address, var/message) + if(!address) + return 0 + for(var/datum/exonet_protocol/exonet in all_exonet_connections) + if(exonet.address == target_address) + exonet.receive_message(holder, address, message) + break + +// Proc: receive_message() +// Parameters: 3 (origin_atom - the origin datum's holder, origin_address - the address the message originated from, message - the message that was sent) +// Description: Called when send_message() successfully reaches the intended datum. By default, calls receive_exonet_message() on the holder atom. +/datum/exonet_protocol/proc/receive_message(var/atom/origin_atom, var/origin_address, var/message) + holder.receive_exonet_message(origin_atom, origin_address, message) + return + +// Proc: receive_exonet_message() +// Parameters: 3 (origin_atom - the origin datum's holder, origin_address - the address the message originated from, message - the message that was sent) +// Description: Override this to make your atom do something when a message is received. +/atom/proc/receive_exonet_message(var/atom/origin_atom, var/origin_address, var/message) + return diff --git a/code/game/machinery/exonet_node.dm b/code/game/machinery/exonet_node.dm new file mode 100644 index 0000000000000..682df50e60050 --- /dev/null +++ b/code/game/machinery/exonet_node.dm @@ -0,0 +1,173 @@ +/obj/machinery/exonet_node + name = "exonet node" + desc = "This machine is one of many, many nodes inside Vir's section of the Exonet, connecting the Northern Star to the rest of the system, at least \ + electronically." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "exonet_node" + idle_power_usage = 2500 + density = 1 + anchored = 1 + var/on = 1 + var/toggle = 1 + + var/allow_external_PDAs = 1 + var/allow_external_communicators = 1 + var/allow_external_newscasters = 1 + + var/opened = 0 + +// Proc: New() +// Parameters: None +// Description: Adds components to the machine for deconstruction. +/obj/machinery/exonet_node/New() + ..() + + component_parts = list() + component_parts += new /obj/item/weapon/circuitboard/telecomms/exonet_node(src) + component_parts += new /obj/item/weapon/stock_parts/subspace/ansible(src) + component_parts += new /obj/item/weapon/stock_parts/subspace/filter(src) + component_parts += new /obj/item/weapon/stock_parts/manipulator(src) + component_parts += new /obj/item/weapon/stock_parts/manipulator(src) + component_parts += new /obj/item/weapon/stock_parts/micro_laser(src) + component_parts += new /obj/item/weapon/stock_parts/subspace/crystal(src) + component_parts += new /obj/item/weapon/stock_parts/subspace/treatment(src) + component_parts += new /obj/item/weapon/stock_parts/subspace/treatment(src) + component_parts += new /obj/item/stack/cable_coil(src, 2) + RefreshParts() + +// Proc: update_icon() +// Parameters: None +// Description: Self explanatory. +/obj/machinery/exonet_node/update_icon() + if(on) + if(!allow_external_PDAs && !allow_external_communicators && !allow_external_newscasters) + icon_state = "[initial(icon_state)]_idle" + else + icon_state = initial(icon_state) + else + icon_state = "[initial(icon_state)]_off" + +// Proc: update_power() +// Parameters: None +// Description: Sets the device on/off and adjusts power draw based on stat and toggle variables. +/obj/machinery/exonet_node/proc/update_power() + if(toggle) + if(stat & (BROKEN|NOPOWER|EMPED)) + on = 0 + idle_power_usage = 0 + else + on = 1 + idle_power_usage = 2500 + else + on = 0 + idle_power_usage = 0 + +// Proc: emp_act() +// Parameters: 1 (severity - how strong the EMP is, with lower numbers being stronger) +// Description: Shuts off the machine for awhile if an EMP hits it. Ion anomalies also call this to turn it off. +/obj/machinery/exonet_node/emp_act(severity) + if(!(stat & EMPED)) + stat |= EMPED + var/duration = (300 * 10)/severity + spawn(rand(duration - 20, duration + 20)) + stat &= ~EMPED + update_icon() + ..() + +// Proc: process() +// Parameters: None +// Description: Calls the procs below every tick. +/obj/machinery/exonet_node/process() + update_power() + +// Proc: attackby() +// Parameters: 2 (I - the item being whacked against the machine, user - the person doing the whacking) +// Description: Handles deconstruction. +/obj/machinery/exonet_node/attackby(obj/item/I, mob/user) + if(istype(I, /obj/item/weapon/screwdriver)) + default_deconstruction_screwdriver(user, I) + else if(istype(I, /obj/item/weapon/crowbar)) + default_deconstruction_crowbar(user, I) + else + ..() + +// Proc: attack_ai() +// Parameters: 1 (user - the AI clicking on the machine) +// Description: Redirects to attack_hand() +/obj/machinery/exonet_node/attack_ai(mob/user) + attack_hand(user) + +// Proc: attack_hand() +// Parameters: 1 (user - the person clicking on the machine) +// Description: Opens the NanoUI interface with ui_interact() +/obj/machinery/exonet_node/attack_hand(mob/user) + ui_interact(user) + +// Proc: ui_interact() +// Parameters: 4 (standard NanoUI arguments) +// Description: Allows the user to turn the machine on or off, or open or close certain 'ports' for things like external PDA messages, newscasters, etc. +/obj/machinery/exonet_node/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) + // this is the data which will be sent to the ui + var/data = list() + + + data["on"] = on ? 1 : 0 + data["allowPDAs"] = allow_external_PDAs + data["allowCommunicators"] = allow_external_communicators + data["allowNewscasters"] = allow_external_newscasters + + // update the ui if it exists, returns null if no ui is passed/found + ui = SSnano.try_update_ui(user, src, ui_key, ui, data, force_open) + if(!ui) + // the ui does not exist, so we'll create a new() one + // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm + ui = new(user, src, ui_key, "exonet_node.tmpl", "Exonet Node #157", 400, 400) + // when the ui is first opened this is the data it will use + ui.set_initial_data(data) + // open the new ui window + ui.open() + // auto update every Master Controller tick + ui.set_auto_update(1) + +// Proc: Topic() +// Parameters: 2 (standard Topic arguments) +// Description: Responds to button presses on the NanoUI interface. +/obj/machinery/exonet_node/Topic(href, href_list) + if(..()) + return 1 + if(href_list["toggle_power"]) + toggle = !toggle + update_power() + if(!toggle) + var/msg = "[usr.client.key] ([usr]) has turned [src] off, at [x],[y],[z]." + message_admins(msg) + log_game(msg) + + if(href_list["toggle_PDA_port"]) + allow_external_PDAs = !allow_external_PDAs + + if(href_list["toggle_communicator_port"]) + allow_external_communicators = !allow_external_communicators + if(!allow_external_communicators) + var/msg = "[usr.client.key] ([usr]) has turned [src]'s communicator port off, at [x],[y],[z]." + message_admins(msg) + log_game(msg) + + if(href_list["toggle_newscaster_port"]) + allow_external_newscasters = !allow_external_newscasters + if(!allow_external_newscasters) + var/msg = "[usr.client.key] ([usr]) has turned [src]'s newscaster port off, at [x],[y],[z]." + message_admins(msg) + log_game(msg) + + update_icon() + SSnano.update_uis(src) + add_fingerprint(usr) + +// Proc: get_exonet_node() +// Parameters: None +// Description: Helper proc to get a reference to an Exonet node. +/proc/get_exonet_node() + for(var/obj/machinery/exonet_node/E in machines) + if(E.on) + return E diff --git a/code/game/machinery/telecomms/machines/relay.dm b/code/game/machinery/telecomms/machines/relay.dm index 51d76d47255a4..1326e569d5f3e 100644 --- a/code/game/machinery/telecomms/machines/relay.dm +++ b/code/game/machinery/telecomms/machines/relay.dm @@ -84,3 +84,17 @@ hide = 1 toggled = 0 autolinkers = list("r_relay") + +//This isn't a real telecomms board but I don't want to make a whole file to hold only one circuitboard. +/obj/item/weapon/circuitboard/telecomms/exonet_node + name = "exonet node" + build_path = "/obj/machinery/exonet_node" + origin_tech = list(TECH_DATA = 5, TECH_ENGINEERING = 5, TECH_BLUESPACE = 4) + req_components = list( + "/obj/item/weapon/stock_parts/subspace/ansible" = 1, + "/obj/item/weapon/stock_parts/subspace/filter" = 1, + "/obj/item/weapon/stock_parts/manipulator" = 2, + "/obj/item/weapon/stock_parts/micro_laser" = 1, + "/obj/item/weapon/stock_parts/subspace/crystal" = 1, + "/obj/item/weapon/stock_parts/subspace/treatment" = 2, + "/obj/item/stack/cable_coil" = 2) diff --git a/code/modules/research/designs.dm b/code/modules/research/designs.dm index 1810bf3fd1727..12946977867e5 100644 --- a/code/modules/research/designs.dm +++ b/code/modules/research/designs.dm @@ -806,4 +806,11 @@ other types of metals and chemistry for reagents). /datum/design/circuit/integrated_circuit/time/clock id = "cc-clock" - build_path = /obj/item/integrated_circuit/time/clock \ No newline at end of file + build_path = /obj/item/integrated_circuit/time/clock + + +/datum/design/circuit/tcom/exonet_node + name = "exonet node" + id = "tcom-exonet_node" + req_tech = list(TECH_DATA = 5, TECH_ENGINEERING = 5, TECH_BLUESPACE = 4) + build_path = /obj/item/weapon/circuitboard/telecomms/exonet_node diff --git a/icons/obj/stationobjs.dmi b/icons/obj/stationobjs.dmi index f9a90f670fd56..c20a6d3113d42 100644 Binary files a/icons/obj/stationobjs.dmi and b/icons/obj/stationobjs.dmi differ diff --git a/nano/templates/exonet_node.tmpl b/nano/templates/exonet_node.tmpl new file mode 100644 index 0000000000000..f9d81d4e92f98 --- /dev/null +++ b/nano/templates/exonet_node.tmpl @@ -0,0 +1,40 @@ + + +

Status

+
+
+ Power: +
+
+ {{:helper.link('On', 'power', {'toggle_power' : 1}, data.on ? 'selected' : null)}}{{:helper.link('Off', 'close', {'toggle_power' : 1}, data.on ? null : 'selected', data.on ? 'redButton' : null)}} +
+
+ +

Ports

+
+
+ Incoming PDA Messages: +
+
+ {{:helper.link('Open', 'check', {'toggle_PDA_port' : 1}, data.allowPDAs ? 'selected' : null)}}{{:helper.link('Close', 'close', {'toggle_PDA_port' : 1}, data.allowPDAs ? null : 'selected')}} +
+
+
+
+ Incoming Communicators: +
+
+ {{:helper.link('Open', 'check', {'toggle_communicator_port' : 1}, data.allowCommunicators ? 'selected' : null)}}{{:helper.link('Close', 'close', {'toggle_communicator_port' : 1}, data.allowCommunicators ? null : 'selected')}} +
+
+
+
+ Incoming Newscaster Content: +
+
+ {{:helper.link('Open', 'check', {'toggle_newscaster_port' : 1}, data.allowNewscasters ? 'selected' : null)}}{{:helper.link('Close', 'close', {'toggle_newscaster_port' : 1}, data.allowNewscasters ? null : 'selected')}} +
+
diff --git a/tgstation.dme b/tgstation.dme index eaa7f073fa9fd..d73ccd96065a1 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -166,6 +166,7 @@ #include "code\datums\computerfiles.dm" #include "code\datums\datacore.dm" #include "code\datums\datumvars.dm" +#include "code\datums\EPv2.dm" #include "code\datums\gas_mixture.dm" #include "code\datums\hud.dm" #include "code\datums\martial.dm"