diff --git a/WorkInProgress/francinum/cphones_new/_defines.dm b/WorkInProgress/francinum/cphones_new/_defines.dm new file mode 100644 index 000000000000..bd621c168f1b --- /dev/null +++ b/WorkInProgress/francinum/cphones_new/_defines.dm @@ -0,0 +1,45 @@ +#define NETCLASS_EXCHANGE "PNET_TELEX" +#define NETCLASS_SIPPHONE "PNET_TELSUB" + +/// Ping Reply staple for telephone office identification +#define PACKET_FIELD_OFFICE_CODE "office_code" + +/// data[command] 2.0 +#define PACKET_FIELD_VERB "verb" + +/// A high level 'group' of verbs +#define PACKET_FIELD_PROTOCOL "proto" + #define PACKET_PROTOCOL_SIP "sip" + // ----REGISTRATION---- + #define PACKET_SIP_VERB_REGISTER "register" + #define PACKET_SIP_FIELD_USER "user" + #define PACKET_SIP_FIELD_REGISTER_SECRET "auth" + #define PACKET_SIP_VERB_REAUTH "reauth" + #define PACKET_SIP_FIELD_AUTH_TOKEN "auth_token" + #define PACKET_SIP_VERB_ACKNOWLEDGE "ack" + //shares PACKET_SIP_FIELD_AUTH_TOKEN + #define PACKET_SIP_VERB_NEGATIVE_ACKNOWLEDGE "nack" + #define PACKET_SIP_FIELD_NACK_CAUSE "cause" + #define PACKET_SIP_NACK_CAUSE_BAD_SECRET "badsecret" + + // ----CALL CONTROL---- + #define PACKET_SIP_FIELD_CALLID "call-id" + #define PACKET_SIP_VERB_INVITE "invite" + /// Sender + #define PACKET_SIP_INVITE_FIELD_FROM "from" + /// Invite recipient number. + #define PACKET_SIP_INVITE_FIELD_TO "to" + #define PACKET_SIP_VERB_SESSION "session" + #define PACKET_SIP_SESSION_ + #define PACKET_PROTOCOL_RTP "rtp" + + +// SIP State values +#define SIP_STATE_CODE 1 //List Index + #define SIP_STATE_CODE_START_RINGING 1 //No data + #define SIP_STATE_CODE_STOP_RINGING 2 //No data + #define SIP_STATE_TERMINATE 3 // data: Cause Code + #define SIP_STATE_REG_FAILURE 4 //No data +#define SIP_STATE_DATA 2 //List Index + +#error HEY DIPSHIT TEAR OUT A LOT OF YOUR SHIT AND IMPLIMENT STATUS CODES INSTEAD OF SHITTING YOURSELF LIKE A MONKEY diff --git a/WorkInProgress/francinum/cphones_new/exchange.dm b/WorkInProgress/francinum/cphones_new/exchange.dm new file mode 100644 index 000000000000..df92e2cb6547 --- /dev/null +++ b/WorkInProgress/francinum/cphones_new/exchange.dm @@ -0,0 +1,64 @@ +/obj/machinery/telephony/exchange + name = "Telephone Exchange" + icon_state = "blackbox_b" + network_flags = NETWORK_FLAGS_STANDARD_CONNECTION + net_class = NETCLASS_EXCHANGE + + // This is... A complicated datastructure + // see extensions_info.txt in this folder for the layout. + var/list/datum/tel_extension/extensions + + /// Defaults to 000, Used to select an exchange to register to in 'complex environments' + var/office_number = "000" + +/obj/machinery/telephony/exchange/Initialize(mapload) + . = ..() + // Request NetInit for autodiscovery. + SSpackets.request_network_initialize(src) + extensions = list() + + +/obj/machinery/telephony/exchange/NetworkInitialize() + . = ..() + // Load ping_additional with the office code, so pinging devices can find us. + ping_addition = list("office_code" = office_number) + // The network is ready, we can now use it to resolve networked phones. + if(!netjack) + return //Or we can just go fuck ourselves that works too. + + //Determine all phones on our network. + for(var/obj/machinery/power/data_terminal/possible_jack as anything in netjack.powernet.data_nodes) + var/obj/machinery/telephony/telephone/registrant = possible_jack.connected_machine + if(!istype(registrant) || (registrant.init_office_code && (registrant.init_office_code != office_number))) + continue //Not you. + + var/datum/tel_extension/ext_record = assert_extension(registrant.init_extension) + + registrant.autoconf_secret = ext_record.auth_secret + //Program the office number so it knows who to care about. + registrant.init_office_code = office_number + if(!registrant.init_cnam) + continue // Not setting or writing a CNAM. + if(ext_record.cnam && (ext_record.cnam != registrant.init_cnam)) + stack_trace("Init phone CNAM conflict for extension [registrant.init_extension].") + ext_record.cnam = registrant.init_cnam + + +/// Creates a new extension. Returns the generated tel_extension record. +/// If the extension already exists, Return the existing record. +/obj/machinery/telephony/exchange/proc/assert_extension(new_ext) as /datum/tel_extension + RETURN_TYPE(/datum/tel_extension) + // Secrets must be numeric so they can be entered via config mode. + if(extensions[new_ext]) + return extensions[new_ext] + var/datum/tel_extension/record = new() + + record.name = new_ext + record.auth_secret = random_string(8, GLOB.numerals) + + extensions[new_ext] = record + + return record + +/obj/machinery/telephony/exchange/receive_signal(datum/signal/signal) + . = ..() diff --git a/WorkInProgress/francinum/cphones_new/extensions_info.txt b/WorkInProgress/francinum/cphones_new/extensions_info.txt new file mode 100644 index 000000000000..50ea1ac39ab9 --- /dev/null +++ b/WorkInProgress/francinum/cphones_new/extensions_info.txt @@ -0,0 +1,67 @@ +//Var +list( + //Extension + "0003" = list( + //Authentication secret. + "secret" = "auth_secret", + //Caller Name. Replacement for p2p_phone friendly_name. Optional, Uses the extension number otherwise. + "cnam" = "Bar", + //Registered phones. + "reg" = list( + "net_addr_here" = bool:busy + ) + ) +) + + +// signal protocol + +// Skeleton: +{ + "proto":"sip", + "verb": VERB, + "seq": $RAND_SEQ // Used to associate ACKs with various flows. - NOT ANYMORE THIS IS ANNOYING +} + +//Verbs: +/* + * REGISTER: Grants a reauth token upon login, allows placing calls via INVITE + * REAUTH: like register, returns a new auth token (via ACK) upon successful reauth, else NACK + * INVITE: Request to start a call with another extension. + * RINGING: Informs the station that the far side is now ringing. + * CANCEL: Sent by the requesting client. Cancel the call, free all equipment. + * + */ + +// Registration +user send: +{ + "proto":"sip", + "verb":"register", + "user":"$EXTNUM", + "auth":"$SECRET", +} +exchange reply: +{ + "proto":"sip", + "verb":"ack", + "user":"$EXTNUM", + "auth_token":"$token", +} + + +//keepalive, Use the auth token instead. +{ + "proto":"sip", + "verb":"reauth", + "user":"$EXTNUM", + "auth_token":"$REAUTHTOKEN", +} + +// Start Call +{ + "proto":"sip", + "verb": "invite", + "to" : "$EXT", + "auth_token":"$token", +} diff --git a/WorkInProgress/francinum/cphones_new/phone/_packet_handler.dm b/WorkInProgress/francinum/cphones_new/phone/_packet_handler.dm new file mode 100644 index 000000000000..a72fb1c00a15 --- /dev/null +++ b/WorkInProgress/francinum/cphones_new/phone/_packet_handler.dm @@ -0,0 +1,30 @@ +/datum/packet_handler + /// Who owns us? + var/obj/machinery/master + +/* + * Handlers are passed signals via receive_signal. + * If false, the packet is unhandled and should be passed to the next handler. + * + * We're just gonna trust this is only ever being called by the master, + * if it's not, go fuck yourself. seriously. + * + * Standard packet output function: + * receive_handler_packet(datum/packet_handler/sender, datum/signal/signal) + * + * Other handlers may define more required functions. + */ + +/obj/machinery/proc/receive_handler_packet(datum/packet_handler/sender, /datum/signal/signal, ...) + return + +/datum/packet_handler/New(_master) + . = ..() + master = _master + +/datum/packet_handler/Destroy(force, ...) + master = null + return ..() + +/datum/packet_handler/process(delta_time) + return diff --git a/WorkInProgress/francinum/cphones_new/phone/phone.dm b/WorkInProgress/francinum/cphones_new/phone/phone.dm new file mode 100644 index 000000000000..55d5d1e7c5f8 --- /dev/null +++ b/WorkInProgress/francinum/cphones_new/phone/phone.dm @@ -0,0 +1,102 @@ +#define PACKET_HANDLER_SIGNALLING "sip" +#define PACKET_HANDLER_VOICE_DATA "rtp" + +/obj/machinery/telephony/telephone + + icon = 'goon/icons/obj/phones.dmi' + icon_state = "phone" + + // Mapload/setup/autodiscovery vars. These are only used to configure the signalling datum during "bootup" + // At all other times, all signalling state is stored on the signalling datum. + + /// The phone's assigned number, Used for mapload discovery + var/init_extension = "0000" + /// The phone's initial Caller NAMe. Used to give a name to the voice on the other side. + /// This is only used to configure the CNAM on the exchange. + /// If unset, will not attempt to change an existing CNAM, or if none is ever registered + /// for an extension, will display the calling phone number instead. + var/init_cnam + /// Used to select which exchange to register to. if unset, first come first serve. + /// Defaults to `"NEVER"`, which will not autoconfigure at all. + var/init_office_code = "NEVER" + /// Used to authenticate with the exchange. + var/tmp/autoconf_secret = "" + + /// Is our write-protect screw installed? If not, Allow entering configuration mode. + var/config_screwed = TRUE + /// Are we currently in the config handler? If so, what state are we in. + var/tmp/configuring = FALSE + + // Queue of packets, We drain this on process. Only broadcast or packets meant for us get queued. + var/tmp/list/datum/signal/packet_queue + + // Our packet handlers, We use these to compartmentalize behaviour so I don't go fucking insane. + var/tmp/list/datum/packet_handler/packet_handlers + + var/tmp/obj/item/food/grown/banana/handset + +/obj/machinery/telephony/telephone/LateInitialize() + . = ..() + packet_queue = list() + packet_handlers = list() + //Init our various handlers. + packet_handlers[PACKET_HANDLER_VOICE_DATA] = new /datum/packet_handler/voice_data(src, /*speaker*/) + +/obj/machinery/telephony/telephone/Destroy() + . = ..() + QDEL_LIST_ASSOC(packet_handlers) + +/obj/machinery/telephony/telephone/examine(mob/user) + . = ..() + if(config_screwed) + . += span_info("Odd. One of the screws is [span_alert("red")].") + else + . += span_info("One of the screws is very loose. It's [span_alert("red")], unlike the rest.") + + + +/obj/machinery/telephony/telephone/process() + // We intentionally delay this until game start. + if(!packet_handlers[PACKET_HANDLER_SIGNALLING]) + packet_handlers[PACKET_HANDLER_SIGNALLING] = new /datum/packet_handler/sip_registration( + src, + init_extension, + init_office_code, + autoconf_secret + ) + else + packet_handlers[PACKET_HANDLER_SIGNALLING].process() + if(length(packet_queue)) + handle_packet_queue() + +/obj/machinery/telephony/telephone/receive_signal(datum/signal/signal) + if(..() == RECEIVE_SIGNAL_FINISHED)//Handled by default. + return + var/datum/signal/storable = signal.Copy() + packet_queue += storable + +/obj/machinery/telephony/telephone/receive_handler_packet(datum/packet_handler/sender, datum/signal/signal, list/sip_state) + switch(sender.type) + if(/datum/packet_handler/sip_registration) + //SIP data handler can also send control information instead, it'll only send one or the other. + if(sip_state) + switch(sip_state[SIP_STATE_CODE]) + if(SIP_STATE_CODE_START_RINGING) + else + if(/datum/packet_handler/voice_data) + +// time to play dress-up as a subsystem +/obj/machinery/telephony/telephone/proc/handle_packet_queue() + for(var/datum/signal/packet as anything in packet_queue) + var/list/data = packet.data + switch(data[PACKET_FIELD_PROTOCOL]) + if(PACKET_PROTOCOL_SIP) + packet_handlers[PACKET_HANDLER_SIGNALLING]?.receive_signal(packet) + if(PACKET_PROTOCOL_RTP) + packet_handlers[PACKET_HANDLER_VOICE_DATA].receive_signal(packet) + //else {drop_packet}; + if(data[PACKET_CMD] == NET_COMMAND_PING_REPLY) //If this is a ping reply, it also goes to the SIP handler. + packet_handlers[PACKET_HANDLER_SIGNALLING]?.receive_signal(packet) + packet_queue -= packet + if(TICK_CHECK) + return diff --git a/WorkInProgress/francinum/cphones_new/phone/sip_call.dm b/WorkInProgress/francinum/cphones_new/phone/sip_call.dm new file mode 100644 index 000000000000..5cf3e7978ce3 --- /dev/null +++ b/WorkInProgress/francinum/cphones_new/phone/sip_call.dm @@ -0,0 +1,73 @@ +// Holds state about an individual SIP call. + +/datum/sip_call + + /// The controlling SIP handler. + var/datum/sip_handler/owner + + /// Are we originating this call? + /// Behaviour basically flips if this is false. + var/originating = TRUE + + /// Randomly generated call ID. Used for tracing and for identification. md5(world.time+ref(src)) + var/call_id + + /// ORIGINATE: The number we are calling. + /// ANSWER: The number calling us. + var/calling_number + + var/our_number + + /// Server address + var/target_addr + + /* Call Setup */ + /// ORIGINATE: Call is being set up, Play no comfort noises. If timeout, cancel and terminate self. + #define CALLSTATE_SETUP 0 + /// ORIGINATE: Far side is ringing, play ringback. + /// ANSWER: We are ringing, Instruct the phone to start ringing. + #define CALLSTATE_RINGING 1 + + + /// We are talking, Stop noises, instruct the SIP handler to get everything ready for audio. + #define CALLSTATE_TALKING 2 + + /* Call Teardown/Error */ + /// The connection was closed, check cause_code and go from there. + #define CALLSTATE_TERMINATE 3 + + /// Current call state. + var/state = 0 + + /// Q.850 Cause Code (Beats coming up with my own system) for which our call was terminated. + var/cause_code + +/datum/sip_call/New(_master, _our_number, _calling_number, _target_addr, _call_id) + . = ..() + call_id = _call_id || md5("[world.time][ref(src)]") + our_number = _our_number + calling_number = _calling_number + target_addr = _target_addr + if(_call_id) + // Being supplied a call ID means we are NOT originating. + originating = FALSE + +/datum/sip_call/receive_signal(datum/signal/signal) + . = ..() + +// +/datum/sip_call/proc/place_call(destination = calling_number) + if(!calling_number) + calling_number = destination + var/datum/signal/invite = new( + null, + list( + PACKET_DESTINATION_ADDRESS = target_addr, + PACKET_FIELD_PROTOCOL = PACKET_PROTOCOL_SIP, + PACKET_FIELD_VERB = PACKET_SIP_VERB_INVITE, + PACKET_SIP_INVITE_FIELD_FROM = our_number, + PACKET_SIP_INVITE_FIELD_TO = calling_number, + PACKET_SIP_FIELD_CALLID = call_id + ) + ) + return invite diff --git a/WorkInProgress/francinum/cphones_new/phone/sip_handler.dm b/WorkInProgress/francinum/cphones_new/phone/sip_handler.dm new file mode 100644 index 000000000000..03e642c4bc03 --- /dev/null +++ b/WorkInProgress/francinum/cphones_new/phone/sip_handler.dm @@ -0,0 +1,200 @@ +/datum/packet_handler/sip_registration + /// Cooldown for sending the next keepalive/heartbeat to the exchange to keep registered. + COOLDOWN_DECLARE(keepalive_timeout) + /// Cooldown for registration. We will always attempt to re-register if we go off-hook. + COOLDOWN_DECLARE(registration_timeout) + + /// Office number to look for. + VAR_PRIVATE/office_code + /// Our extension number. + VAR_PRIVATE/auth_name + /// Our login secret. + VAR_PRIVATE/auth_secret + + /// Our authentication refresh token, think of it as a second, temporary password. + /// if null, we are not registered at all. Start trying to register again with slowly increasing backoff. + VAR_PRIVATE/auth_refresh_token + /// Exponential backoff factor. + VAR_PRIVATE/backoff_factor = 0 + /// Secondary backoff factor for waiting for a ping. This curve starts shallow before getting very strong. + VAR_PRIVATE/ping_backoff_factor = 0 + /// Reauth count, after some amount, just give up and assume de-registration. + VAR_PRIVATE/reauth_attempts = 0 + + /// Address of our server. + VAR_PRIVATE/server_address + /// Owner device address. + VAR_PRIVATE/master_netid + + VAR_PRIVATE/datum/sip_call/current_call + + /* Master Interface: + * + * output: + * receive_handler_update(datum/packet_handler/sender, datum/signal/signal, sip_state) + * > Receives a data-complete SIP signalling packet. + * d_addr is already set, so it should mostly be sent + * straight out of post_signal. + * + * > Can also send control state information via 'sip_state' + * as a list of 2 values, a code, and free additional data. + * list(SIP_STATE_CODE = $NUM, SIP_STATE_DATA = $WHATEVER) + * + * + * control: + * New(extension, officecode, auth_secret) + * > Load map-varedit and autoconf information. + * The phone still has to 'boot' and register itself. + * This just provides it the correct information to start with. + * + * + * + */ + + +/datum/packet_handler/sip_registration/New(_master, _ext, _office, _auth) + . = ..() + auth_name = _ext + auth_secret = _auth + office_code = _office + // Give us a random cooldown between 1 and 5 seconds in half-second increments. Just to spread out when these things fully wake up. + COOLDOWN_START(src, registration_timeout, (rand(2,10)/2) SECONDS) + +/datum/packet_handler/sip_registration/process(delta_time) + // If we aren't registered and we're off cooldown for attempting to. + if(!auth_refresh_token && COOLDOWN_FINISHED(src, registration_timeout)) + // Do we know which server to try to register to? + if(!server_address) + locate_server() + return + // If we do, try and connect to it. + attempt_registration() + return + + //We either have a token or the cooldown hasn't finished yet, if we do have one and the keepalive timeout is finished... + else if(auth_refresh_token && COOLDOWN_FINISHED(src, keepalive_timeout)) + // If we've not tried this enough + if(reauth_attempts < 10) + do_keepalive() + else + // Give up. + clear_registration() + +// Send out a ping +/datum/packet_handler/sip_registration/proc/locate_server() + +/datum/packet_handler/sip_registration/proc/attempt_registration() + var/datum/signal/reg_packet = new( + null, + list( + PACKET_DESTINATION_ADDRESS = master_netid, + PACKET_FIELD_PROTOCOL = PACKET_PROTOCOL_SIP, + PACKET_FIELD_VERB = PACKET_SIP_VERB_REGISTER, + PACKET_SIP_FIELD_USER = auth_name, + PACKET_SIP_FIELD_REGISTER_SECRET = auth_secret + ) + ) + master.receive_handler_packet(reg_packet) + ping_backoff_factor++ + /* This factor will, after a few attmps, drastically slow down attempts to contact the exchange. + * https://www.desmos.com/calculator/hmctyrgrux + * For cases where the phone is physically disconnected from the exchange. + * These numbers were chosen through the advanced process of ass.pull() + */ + COOLDOWN_START(src, registration_timeout, (floor(0.5*(2.1**backoff_factor))) SECONDS) + +/// Reset all state, start over. +/datum/packet_handler/sip_registration/proc/clear_registration() + // Clear the registration. + auth_refresh_token = null + // Forget about our server. + server_address = null + // Reset our timeouts and scaling factors. + ping_backoff_factor = 0 + backoff_factor = 0 + reauth_attempts = 0 + COOLDOWN_RESET(src, keepalive_timeout) + COOLDOWN_RESET(src, registration_timeout) + + + +/datum/packet_handler/sip_registration/proc/do_keepalive() + var/datum/signal/reg_packet = new( + null, + list( + PACKET_DESTINATION_ADDRESS = master_netid, + PACKET_FIELD_PROTOCOL = PACKET_PROTOCOL_SIP, + PACKET_FIELD_VERB = PACKET_SIP_VERB_REAUTH, + PACKET_SIP_FIELD_USER = auth_name, + PACKET_SIP_FIELD_AUTH_TOKEN = auth_refresh_token + ) + ) + master.receive_handler_packet(reg_packet) + // This does NOT get a backoff period, we re-attempt every 2 seconds for 20 seconds, and then give up, assuming de-registration. + COOLDOWN_START(src, keepalive_timeout, 2 SECONDS) + + +/datum/packet_handler/sip_registration/receive_signal(datum/signal/signal) + var/list/data = signal.data + //Are you our SIP server? + if(data[PACKET_SOURCE_ADDRESS] == server_address) + // Do you have a token for us? + if((data[PACKET_FIELD_VERB] == PACKET_SIP_VERB_ACKNOWLEDGE) && (data[PACKET_SIP_FIELD_AUTH_TOKEN])) + // Our registered server is issuing us an auth token, sweet. + refresh_auth(data[PACKET_SIP_FIELD_AUTH_TOKEN]) + return + + // Do we have a token? + if(!auth_refresh_token) + //NACK from server before full auth? We probably did something wrong, Lock up and pass the cause up to the master to alert. + if((data[PACKET_FIELD_VERB] == PACKET_SIP_VERB_NEGATIVE_ACKNOWLEDGE) && (data[PACKET_SIP_FIELD_NACK_CAUSE])) + COOLDOWN_START(src, registration_timeout, INFINITY) + master.receive_handler_packet(src, null, list(SIP_STATE_REG_FAILURE, data[PACKET_SIP_FIELD_NACK_CAUSE])) + return + return + + // We have a registration, and the server isn't here to renew one. Do we have an active call? If so, pass the packet to it. + if(current_call) + current_call.receive_signal(signal) + return + // No current call, are they trying to place one? + if((data[PACKET_FIELD_VERB] == PACKET_SIP_VERB_INVITE)) + incoming_call(signal) + + //Is this an *INCOMING* invite? + if(data[PACKET_FIELD_VERB] == PACKET_SIP_VERB_ACKNOWLEDGE) + else + // No? Who cares. + return + +/// Fully refresh the keepalive timeout, usually as part of initial registration or during a timeout. +/datum/packet_handler/sip_registration/proc/refresh_auth(new_token) + auth_refresh_token = new_token + ping_backoff_factor = 0 + backoff_factor = 0 + reauth_attempts = 0 + COOLDOWN_START(src, keepalive_timeout, (2 MINUTES)+(rand(0,60) SECONDS)) + + + +/// Place a call to a designated extension, Sets the active call, and sends an INVITE packet out the master. +/datum/packet_handler/sip_registration/proc/place_call(destination) + current_call = new( + src, + auth_name, + destination, + server_address, + ) + // Ask the call datum to 'place' the call, and give us the invite packet to send off. + var/datum/signal/invite = current_call.place_call() + master.receive_handler_packet(src, invite) + +/datum/packet_handler/sip_registration/proc/incoming_call(datum/signal/signal) + current_call = new( + src, + auth_name, + signal[PACKET_SIP_INVITE_FIELD_FROM], + server_address, //You think I'm gonna bother with direct media? You're high as fuck. + signal[PACKET_SIP_FIELD_CALLID] + ) + master.receive_handler_packet(src, invite) diff --git a/WorkInProgress/francinum/cphones_new/phone/voice_data_handler.dm b/WorkInProgress/francinum/cphones_new/phone/voice_data_handler.dm new file mode 100644 index 000000000000..2af6a7d09ece --- /dev/null +++ b/WorkInProgress/francinum/cphones_new/phone/voice_data_handler.dm @@ -0,0 +1,159 @@ +/datum/packet_handler/voice_data + /// What atom do we make speak? Think: Phone Handset. + VAR_PRIVATE/atom/movable/speaker + /// Are we active and doing things, pretty much noops every function if false. + VAR_PRIVATE/active = FALSE + /// Are we mute? We'll still hear packets coming in, we just can't send. + VAR_PRIVATE/mute = FALSE + /// Are we *allowed* to mute, useful for + VAR_PRIVATE/allow_mute = TRUE + //Visual Configuration, used to pretend to be a radio. + VAR_PRIVATE/vis_span = "radio" + VAR_PRIVATE/vis_name = "UNSET_NAME" + + // How far should we listen or broadcast heard messages. + VAR_PRIVATE/hear_range = 1 + VAR_PRIVATE/broadcast_range = 2 + + /* Master Interface: + * + * output: receive_handler_packet(datum/packet_handler/sender, datum/signal/signal) + * > Receives a data-complete voice packet, the master is + * expected to make any modifications they need (addressing, etc) + * before sending it out via the usual means. + * + * control: + * New(speaker,allow_mute, hear_range, broadcast_range) + * > Provide a speaker atom for this handler to parasitize. + * > allow_mute allows you to enable or disable use-inhand mute. + * > It is not required to mute via proccall. (Think: PA speakers.) + * > You can also control the range the speaker can hear/broadcast relayed speech. + * handler.activate(vis_name) + * > Begin + * + */ + + +/datum/packet_handler/voice_data/New(_master, _speaker, _allow_mute) + . = ..() + allow_mute = _allow_mute + set_speaker(_speaker) + +/datum/packet_handler/voice_data/Destroy(force, ...) + release_speaker() + return ..() + +/datum/packet_handler/voice_data/proc/set_visuals(_span, _name) + vis_span = _span || vis_span + vis_name = _name || vis_name + +/datum/packet_handler/voice_data/proc/set_speaker(atom/movable/new_speaker) + // Do we already have a speaker? + if(!new_speaker || (speaker && speaker != new_speaker)) + //Release the old one. + release_speaker() + + speaker = new_speaker + speaker.become_hearing_sensitive(ref(src)) + RegisterSignal(speaker, COMSIG_MOVABLE_HEAR, PROC_REF(handle_speaker_hearing)) + if(allow_mute) + RegisterSignal(speaker, COMSIG_ITEM_ATTACK_SELF, PROC_REF(handle_attack_self)) + +//Release a speaker +/datum/packet_handler/voice_data/proc/release_speaker() + UnregisterSignal(speaker, list(COMSIG_MOVABLE_HEAR,COMSIG_ITEM_ATTACK_SELF)) + speaker.lose_hearing_sensitivity(ref(src)) + +/datum/packet_handler/voice_data/proc/handle_attack_self(mob/user) + SIGNAL_HANDLER + if(mute) + to_chat(user, span_notice("You mute the [speaker].")) + else + to_chat(user, span_notice("You unmute the [speaker].")) + set_mute(!mute) + return COMPONENT_CANCEL_ATTACK_CHAIN + +/datum/packet_handler/voice_data/proc/handle_speaker_hearing(datum/source, list/hearing_args) + SIGNAL_HANDLER + if(mute) + return + + // Unpack the vars we need. + var/atom/movable/heard_speaker = hearing_args[HEARING_SPEAKER] + var/sound_loc = hearing_args[HEARING_SOUND_LOC] + var/list/message_mods = hearing_args[HEARING_MESSAGE_MODE] + + var/atom/movable/checked_thing = sound_loc || heard_speaker //If we have a location, we care about that, otherwise we're speaking directly from something. + if(!IN_GIVEN_RANGE(get_turf(speaker), get_turf(checked_thing), hear_range)) + return + + //START SHAMELESS RADIO CARGOCULTING + var/filtered_mods = list() + if (message_mods[MODE_CUSTOM_SAY_EMOTE]) + filtered_mods[MODE_CUSTOM_SAY_EMOTE] = message_mods[MODE_CUSTOM_SAY_EMOTE] + filtered_mods[MODE_CUSTOM_SAY_ERASE_INPUT] = message_mods[MODE_CUSTOM_SAY_ERASE_INPUT] + + encode_voice(heard_speaker, hearing_args[HEARING_RAW_MESSAGE], , hearing_args[HEARING_SPANS], language=hearing_args[HEARING_LANGUAGE], message_mods=filtered_mods) + //END SHAMELESS RADIO CARGOCULTING + +/datum/packet_handler/voice_data/proc/encode_voice(atom/movable/talking_movable, message, channel, list/spans, datum/language/language, list/message_mods) + if(mute || !active || !talking_movable || !message || !talking_movable.IsVocal()) + return + + //The third var is the 'radio'. It's null. Go fuck yourself. + var/atom/movable/virtualspeaker/v_speaker = new(null, talking_movable, null) + if(isliving(talking_movable)) + var/mob/living/libbing = v_speaker + v_speaker.voice_type = libbing.voice_type + + + +/datum/packet_handler/voice_data/proc/set_mute(new_mute) + if(mute == new_mute) + return + + if(mute) + mute = FALSE + else + mute = TRUE + +/// Enable the voice relay behaviour. Will always unmute. +/datum/packet_handler/voice_data/proc/activate(voice_name = null) + set_visuals(_name = voice_name) + set_mute(FALSE) + active = TRUE + +/datum/packet_handler/voice_data/proc/deactivate() + active = FALSE + +/datum/packet_handler/voice_data/receive_signal(datum/signal/signal) + if(!active) + return TRUE + if(!speaker) + CRASH("No speaker. Fix your bullshit.") + + var/list/data = signal.data + + var/list/radio_bullshit_override = list("span"=vis_span, "name"=vis_name) + + var/atom/movable/virtualspeaker/admission_of_defeat = data["virtualspeaker"] + var/sound/funnysound + if(admission_of_defeat.voice_type) + var/funnysound_index = copytext_char(data["message"], -1) + switch(funnysound_index) + if("?") + funnysound = voice_type2sound[admission_of_defeat.voice_type]["?"] + if("!") + funnysound = voice_type2sound[admission_of_defeat.voice_type]["!"] + else + funnysound = voice_type2sound[admission_of_defeat.voice_type][admission_of_defeat.voice_type] + + + playsound(speaker, funnysound || 'modular_pariah/modules/radiosound/sound/radio/syndie.ogg', funnysound ? 300 : 30, TRUE, SHORT_RANGE_SOUND_EXTRARANGE, falloff_exponent = 0) + var/rendered = speaker.compose_message(data["virtualspeaker"], data["language"], data["message"], radio_bullshit_override, data["spans"], data["message_mods"]) + for(var/atom/movable/hearing_movable as anything in get_hearers_in_view(2, speaker)-speaker) + if(!hearing_movable)//theoretically this should use as anything because it shouldnt be able to get nulls but there are reports that it does. + stack_trace("somehow theres a null returned from get_hearers_in_view() in send_speech!") + continue + + hearing_movable.Hear(rendered, data["virtualspeaker"], data["language"], data["message"], radio_bullshit_override, data["spans"], data["message_mods"], speaker.speaker_location(), message_range = INFINITY) diff --git a/WorkInProgress/francinum/cphones_new/reg_info.dm b/WorkInProgress/francinum/cphones_new/reg_info.dm new file mode 100644 index 000000000000..a0a9054c90d3 --- /dev/null +++ b/WorkInProgress/francinum/cphones_new/reg_info.dm @@ -0,0 +1,12 @@ +/datum/tel_extension + /// 'name' - the extension number. Used mostly for VV. Canon extension is this thing's key in an exchanges' extensions list. + var/name + /// Auth secret. Required to act as this extension. + var/auth_secret + /// Caller Name. Replacement for p2p_phone friendly_name. Optional, Uses the extension number otherwise. + var/cnam + /// Registered phones, k,v netaddr=exp_time, Registration will expire if not renewed every minute. + var/list/registered = list() + /// Registration tokens, k,v netaddr=token. Used for keepalive. + var/list/reg_tokens = list() + diff --git a/code/controllers/subsystem/packetnets.dm b/code/controllers/subsystem/packetnets.dm index e30bee6fa002..50dee0ca369f 100644 --- a/code/controllers/subsystem/packetnets.dm +++ b/code/controllers/subsystem/packetnets.dm @@ -54,6 +54,10 @@ SUBSYSTEM_DEF(packets) /// @everyone broadcast key var/gprs_broadcast_packet + /// NetworkInitialize requesters + /// For objects that need the full network ready. You probably don't need this. + VAR_PRIVATE/list/atom/network_initializers + /// Generates a unique (at time of read) ID for an atom, It just plays silly with the ref. /// Pass the target atom in as arg[1] /datum/controller/subsystem/packets/proc/generate_net_id(invoker) @@ -78,12 +82,23 @@ SUBSYSTEM_DEF(packets) return ..() /datum/controller/subsystem/packets/Initialize(start_timeofday) + + //Calculate the stupid magic bullshit detomatix_magic_packet = random_string(rand(16,32), GLOB.hex_characters) clownvirus_magic_packet = random_string(rand(16,32), GLOB.hex_characters) mimevirus_magic_packet = random_string(rand(16,32), GLOB.hex_characters) framevirus_magic_packet = random_string(rand(16,32), GLOB.hex_characters) gprs_broadcast_packet = random_string(rand(16,32), GLOB.hex_characters) pda_exploitable_register = pick_list(PACKET_STRING_FILE, "packet_field_names") + + // We're late enough in init order that all network devices have late initialized, + // so the network *should* be stable, we can now safely re-wake anything that has requested network readiness. + if(network_initializers) //...If there are any, of course + for(var/atom/initializer in network_initializers) + initializer.NetworkInitialize() + CHECK_TICK + network_initializers.Cut() //Drop the refs. + . = ..() /datum/controller/subsystem/packets/Recover() @@ -504,3 +519,11 @@ SUBSYSTEM_DEF(packets) ASSOC_UNSETEMPTY(recursive_contents, RECURSIVE_CONTENTS_RADIO_NONATMOS) UNSETEMPTY(location.important_recursive_contents) +/datum/controller/subsystem/packets/proc/request_network_initialize(atom/initializer) + if(initialized) + //Hi. If you're here, you might have tried to load a device that requests this as part of a + //post-roundstart map load. I apologize that the idea of touching the code required to support that + //at the current time of 02:09:24 EST is... Not on the table. Suck my dick. + CRASH("Attempted to request NetworkInitialize() after SSpackets has initialized!") + + LAZYADD(network_initializers, initializer) diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 925df00ded57..492e360b0be3 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -293,6 +293,18 @@ /atom/proc/LateInitialize() set waitfor = FALSE +/** + * 'Network' Intialization, for code that should run after the packet network has stabilized. + * + * To have your NetworkInitialize proc be called, you must call SSpackets.request_network_initialize(src) + * in your Initialize() or LateInitialize() procs. + * + * This has limited usefulness for most atoms, Mostly used for direct-access based autoconfiguration of + * major network equipment, such as telephone exchanges or packet routing equipment (some day, I promise) + */ +/atom/proc/NetworkInitialize() + set waitfor = FALSE + /** * Top level of the destroy chain for most atoms * diff --git a/code/game/communications.dm b/code/game/communications.dm index 23e953b6b8c6..8a0c4e4b3000 100644 --- a/code/game/communications.dm +++ b/code/game/communications.dm @@ -226,3 +226,15 @@ GLOBAL_LIST_INIT(freq2icon, list( src.data = data || list() src.transmission_method = transmission_method src.logging_data = logging_data + +/// Create a duplicate of the signal that's safe for store-and-forward situations. +/// No assurance is made that the *data* can survive this, of course. +/datum/signal/proc/Copy() + var/datum/signal/duplicate = new(null, data.Copy(), transmission_method, logging_data) + // Now for the vars new doesn't do for us. + duplicate.author = author + duplicate.frequency_datum = frequency_datum + duplicate.frequency = frequency + duplicate.range = range + duplicate.filter_list = filter_list + duplicate.has_magic_data = has_magic_data diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm index e0f163acde1a..23aef6438471 100644 --- a/code/game/machinery/_machinery.dm +++ b/code/game/machinery/_machinery.dm @@ -171,7 +171,7 @@ /// A short string shown to players fingerprinting the device type as part of `command:ping` var/net_class = "PNET_CALL_A_PRIEST" /// Additional data stapled to pings, reduces network usage for some machines. - var/ping_addition = null + var/list/ping_addition = null ///Used by SSairmachines for optimizing scrubbers and vent pumps. COOLDOWN_DECLARE(hibernating) diff --git a/code/game/machinery/datanet/_networked.dm b/code/game/machinery/datanet/_networked.dm index f46bc7aaf335..6cef38e848d3 100644 --- a/code/game/machinery/datanet/_networked.dm +++ b/code/game/machinery/datanet/_networked.dm @@ -29,7 +29,9 @@ SHOULD_CALL_PARENT(TRUE) . = ..() //Should the subtype *probably* stop caring about this packet? if(isnull(signal)) - return + return RECEIVE_SIGNAL_FINISHED + if(machine_stat & (BROKEN|NOPOWER)) + return RECEIVE_SIGNAL_FINISHED var/sigdat = signal.data //cache for sanic speed this joke is getting old. if(sigdat[PACKET_DESTINATION_ADDRESS] != src.net_id)//This packet doesn't belong to us directly if(sigdat[PACKET_DESTINATION_ADDRESS] == NET_ADDRESS_PING)// But it could be a ping, if so, reply @@ -37,7 +39,16 @@ if(!isnull(tmp_filter) && tmp_filter != net_class) return RECEIVE_SIGNAL_FINISHED //Blame kapu for how stupid this looks :3 - post_signal(create_signal(sigdat[PACKET_SOURCE_ADDRESS], list("command"=NET_COMMAND_PING_REPLY,"netclass"=src.net_class,"netaddr"=src.net_id)+src.ping_addition)) + post_signal( + create_signal( + sigdat[PACKET_SOURCE_ADDRESS], + list( + "command"=NET_COMMAND_PING_REPLY, + "netclass"=src.net_class, + "netaddr"=src.net_id + )+src.ping_addition + ) + ) return RECEIVE_SIGNAL_FINISHED//regardless, return 1 so that machines don't process packets not intended for them. return RECEIVE_SIGNAL_CONTINUE // We are the designated recipient of this packet, we need to handle it. @@ -66,3 +77,8 @@ if(!netjack) return netjack.disconnect_machine(src) + +/// Called just before the network jack is removed. +/// Cannot be used to abort disconnection. +/obj/machinery/proc/netjack_disconnected(var/obj/machinery/power/data_terminal/disconnecting) + return diff --git a/code/modules/modular_computers/hardware/gprs_card.dm b/code/modules/modular_computers/hardware/gprs_card.dm index 4b5ad47f60b7..8291a843f531 100644 --- a/code/modules/modular_computers/hardware/gprs_card.dm +++ b/code/modules/modular_computers/hardware/gprs_card.dm @@ -121,7 +121,7 @@ return //We can't hold volatile signals. if(length(packet_queue) == queue_max) pop_signal() //Discard the first signal in the queue - packet_queue += signal + packet_queue += signal.Copy() return /// Get the length of the packet queue diff --git a/code/modules/power/data_terminal.dm b/code/modules/power/data_terminal.dm index 6ff2efee2c38..7fe7ce6e02da 100644 --- a/code/modules/power/data_terminal.dm +++ b/code/modules/power/data_terminal.dm @@ -13,9 +13,9 @@ AddElement(/datum/element/undertile, TRAIT_T_RAY_VISIBLE) /obj/machinery/power/data_terminal/Destroy() - . = ..() // Disconnect from the seizing machine. disconnect_machine(connected_machine) + return ..() /obj/machinery/power/data_terminal/should_have_node() return TRUE @@ -80,10 +80,11 @@ /// Attempt to disconnect from a data terminal. /obj/machinery/power/data_terminal/proc/disconnect_machine(obj/machinery/leaving_machine) - if(!leaving_machine && !connected_machine) // No connected machine in the first place + if(!leaving_machine || !connected_machine) // One of these is null, beating you with a hammer. return if(leaving_machine != connected_machine)//Let's just be sure. CRASH("[leaving_machine] [REF(leaving_machine)] attempted to disconnect despite not owning the data terminal (owned by [connected_machine] [REF(connected_machine)])!") + leaving_machine.netjack_disconnected(src) UnregisterSignal(leaving_machine, COMSIG_MOVABLE_MOVED) leaving_machine.netjack = null connected_machine = null @@ -126,7 +127,7 @@ "You start screwing [src] into \the [T]", "You hear quiet metal scraping.") tool.play_tool_sound(src, 50) - if(!do_after(user, src, 10 SECONDS, DO_PUBLIC)) + if(!do_after(user, src, 5 SECONDS, DO_PUBLIC)) to_chat(user, span_warning("You need to stand still to install [src]!")) return TOOL_ACT_TOOLTYPE_SUCCESS diff --git a/daedalus.dme b/daedalus.dme index 00701db4e464..2ca987695b6c 100644 --- a/daedalus.dme +++ b/daedalus.dme @@ -4790,4 +4790,12 @@ #include "modular_pariah\modules\indicators\code\typing_indicator.dm" #include "modular_pariah\modules\pixel_shift\code\pixel_shift.dm" #include "modular_pariah\modules\radiosound\code\headset.dm" +#include "WorkInProgress\francinum\cphones_new\_defines.dm" +#include "WorkInProgress\francinum\cphones_new\exchange.dm" +#include "WorkInProgress\francinum\cphones_new\reg_info.dm" +#include "WorkInProgress\francinum\cphones_new\phone\_packet_handler.dm" +#include "WorkInProgress\francinum\cphones_new\phone\phone.dm" +#include "WorkInProgress\francinum\cphones_new\phone\sip_call.dm" +#include "WorkInProgress\francinum\cphones_new\phone\sip_handler.dm" +#include "WorkInProgress\francinum\cphones_new\phone\voice_data_handler.dm" // END_INCLUDE