diff --git a/WorkInProgress/francinum/cphones_new/_defines.dm b/WorkInProgress/francinum/cphones_new/_defines.dm new file mode 100644 index 000000000000..af62e7b65569 --- /dev/null +++ b/WorkInProgress/francinum/cphones_new/_defines.dm @@ -0,0 +1,15 @@ +#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" + #define PACKET_SIP_VERB_REGISTER "register" + #define PACKET_SIP_VERB_ACKNOWLEDGE "ack" + #define PACKET_PROTOCOL_RTP "rtp" 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..e7ea9141fe36 --- /dev/null +++ b/WorkInProgress/francinum/cphones_new/extensions_info.txt @@ -0,0 +1,40 @@ +//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 + +user send: +{ + "proto":"sip", + "verb":"register", + "user":"$EXTNUM", + "auth":"$SECRET", +} +exchange reply: +{ + "proto":"sip", + "verb":"ack", + "user":"$EXTNUM", + "reg_token":"$token", +} + + +//keepalive, Intentionally doesn't include password. +{ + "proto":"sip", + "verb":"reauth", + "user":"$EXTNUM", +} 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..e4f93df29a02 --- /dev/null +++ b/WorkInProgress/francinum/cphones_new/phone/_packet_handler.dm @@ -0,0 +1,11 @@ +/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. + */ diff --git a/WorkInProgress/francinum/cphones_new/phone/phone.dm b/WorkInProgress/francinum/cphones_new/phone/phone.dm new file mode 100644 index 000000000000..db1339f398f7 --- /dev/null +++ b/WorkInProgress/francinum/cphones_new/phone/phone.dm @@ -0,0 +1,88 @@ +#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 = "" + + /// Set to TRUE once the SIP core has fully booted. + var/tmp/net_booted = FALSE + + /// 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() + +/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 have not 'booted' + if(packet_handlers[PACKET_HANDLER_SIGNALLING]) + packet_handlers[PACKET_HANDLER_SIGNALLING] = new /datum/packet_handler/sip_registration( + init_extension, + init_office_code, + autoconf_secret + ) + 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 + +/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}; 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..9aa8fc5a69a3 --- /dev/null +++ b/WorkInProgress/francinum/cphones_new/phone/sip_handler.dm @@ -0,0 +1,50 @@ +/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) + + VAR_PRIVATE/office_code + VAR_PRIVATE/auth_name + 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 + + + /// Address of our server. + VAR_PRIVATE/server_address + VAR_PRIVATE/master_netid + + /* Master Interface: + * + * output: receive_sip_packet(datum/signal/varname) + * > Receives a data-complete SIP signalling packet. + * d_addr is already set, so it should mostly be sent + * straight out of post_signal. + * + * 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(_ext, _office, _auth) + . = ..() + auth_name = _ext + auth_secret = _auth + office_code = _office + +/datum/packet_handler/sip_registration/process(delta_time) + if(COOLDOWN_FINISHED()) + +/datum/packet_handler/sip_registration/receive_signal(datum/signal/signal) + + + 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..575a4d0ab3cf --- /dev/null +++ b/WorkInProgress/francinum/cphones_new/phone/voice_data_handler.dm @@ -0,0 +1,160 @@ +/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_voice_packet(datum/signal/varname) + * > 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(_speaker, _allow_mute) + . = ..() + allow_mute = _allow_mute + set_speaker(_speaker) + +/datum/packet_handler/voice_data/Destroy(force, ...) + release_speaker() + master = null + 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/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..ed9a2cb4e974 100644 --- a/daedalus.dme +++ b/daedalus.dme @@ -4790,4 +4790,11 @@ #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_handler.dm" +#include "WorkInProgress\francinum\cphones_new\phone\voice_data_handler.dm" // END_INCLUDE