diff --git a/code/game/objects/items/devices/communicator/UI.dm b/code/game/objects/items/devices/communicator/UI.dm
new file mode 100644
index 0000000000..77ce5b03c1
--- /dev/null
+++ b/code/game/objects/items/devices/communicator/UI.dm
@@ -0,0 +1,238 @@
+// Proc: ui_interact()
+// Parameters: 4 (standard NanoUI arguments)
+// Description: Uses a bunch of for loops to turn lists into lists of lists, so they can be displayed in nanoUI, then displays various buttons to the user.
+/obj/item/device/communicator/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/key_state = null)
+ // this is the data which will be sent to the ui
+ var/data[0] //General nanoUI information
+ var/communicators[0] //List of communicators
+ var/invites[0] //Communicators and ghosts we've invited to our communicator.
+ var/requests[0] //Communicators and ghosts wanting to go in our communicator.
+ var/voices[0] //Current /mob/living/voice s inside the device.
+ var/connected_communicators[0] //Current communicators connected to the device.
+
+ var/im_contacts_ui[0] //List of communicators that have been messaged.
+ var/im_list_ui[0] //List of messages.
+
+ var/weather[0]
+ var/modules_ui[0] //Home screen info.
+
+ //First we add other 'local' communicators.
+ for(var/obj/item/device/communicator/comm in known_devices)
+ if(comm.network_visibility && comm.exonet)
+ communicators[++communicators.len] = list("name" = sanitize(comm.name), "address" = comm.exonet.address)
+
+ //Now for ghosts who we pretend have communicators.
+ for(var/mob/observer/dead/O in known_devices)
+ if(O.client && O.client.prefs.communicator_visibility == 1 && O.exonet)
+ communicators[++communicators.len] = list("name" = sanitize("[O.client.prefs.real_name]'s communicator"), "address" = O.exonet.address, "ref" = "\ref[O]")
+
+ //Lists all the other communicators that we invited.
+ for(var/obj/item/device/communicator/comm in voice_invites)
+ if(comm.exonet)
+ invites[++invites.len] = list("name" = sanitize(comm.name), "address" = comm.exonet.address, "ref" = "\ref[comm]")
+
+ //Ghosts we invited.
+ for(var/mob/observer/dead/O in voice_invites)
+ if(O.exonet && O.client)
+ invites[++invites.len] = list("name" = sanitize("[O.client.prefs.real_name]'s communicator"), "address" = O.exonet.address, "ref" = "\ref[O]")
+
+ //Communicators that want to talk to us.
+ for(var/obj/item/device/communicator/comm in voice_requests)
+ if(comm.exonet)
+ requests[++requests.len] = list("name" = sanitize(comm.name), "address" = comm.exonet.address, "ref" = "\ref[comm]")
+
+ //Ghosts that want to talk to us.
+ for(var/mob/observer/dead/O in voice_requests)
+ if(O.exonet && O.client)
+ requests[++requests.len] = list("name" = sanitize("[O.client.prefs.real_name]'s communicator"), "address" = O.exonet.address, "ref" = "\ref[O]")
+
+ //Now for all the voice mobs inside the communicator.
+ for(var/mob/living/voice/voice in contents)
+ voices[++voices.len] = list("name" = sanitize("[voice.name]'s communicator"), "true_name" = sanitize(voice.name))
+
+ //Finally, all the communicators linked to this one.
+ for(var/obj/item/device/communicator/comm in communicating)
+ connected_communicators[++connected_communicators.len] = list("name" = sanitize(comm.name), "true_name" = sanitize(comm.name), "ref" = "\ref[comm]")
+
+ //Devices that have been messaged or recieved messages from.
+ for(var/obj/item/device/communicator/comm in im_contacts)
+ if(comm.exonet)
+ im_contacts_ui[++im_contacts_ui.len] = list("name" = sanitize(comm.name), "address" = comm.exonet.address, "ref" = "\ref[comm]")
+
+ for(var/mob/observer/dead/ghost in im_contacts)
+ if(ghost.exonet)
+ im_contacts_ui[++im_contacts_ui.len] = list("name" = sanitize(ghost.name), "address" = ghost.exonet.address, "ref" = "\ref[ghost]")
+
+ //Actual messages.
+ for(var/I in im_list)
+ im_list_ui[++im_list_ui.len] = list("address" = I["address"], "to_address" = I["to_address"], "im" = I["im"])
+
+ //Weather reports.
+ for(var/datum/planet/planet in planet_controller.planets)
+ if(planet.weather_holder && planet.weather_holder.current_weather)
+ weather[++weather.len] = list("Planet" = planet.name, "Weather" = planet.weather_holder.current_weather.name, "Temperature" = (round(planet.weather_holder.temperature*1000)/1000),\
+ "High" = planet.weather_holder.current_weather.temp_high, "Low" = planet.weather_holder.current_weather.temp_low, "Wind" = "[planet.weather_holder.wind_speed]|[dir2text(planet.weather_holder.wind_dir)]")
+
+ //Modules for homescreen.
+ for(var/list/R in modules)
+ modules_ui[++modules_ui.len] = R
+
+ data["owner"] = owner ? owner : "Unset"
+ data["occupation"] = occupation ? occupation : "Swipe ID to set."
+ data["connectionStatus"] = get_connection_to_tcomms()
+ data["visible"] = network_visibility
+ data["address"] = exonet.address ? exonet.address : "Unallocated"
+ data["targetAddress"] = target_address
+ data["targetAddressName"] = target_address_name
+ data["currentTab"] = selected_tab
+ data["knownDevices"] = communicators
+ data["invitesSent"] = invites
+ data["requestsReceived"] = requests
+ data["voice_mobs"] = voices
+ data["communicating"] = connected_communicators
+ data["video_comm"] = video_source ? "\ref[video_source.loc]" : null
+ data["imContacts"] = im_contacts_ui
+ data["imList"] = im_list_ui
+ data["time"] = stationtime2text()
+ data["ring"] = ringer
+ data["homeScreen"] = modules_ui
+ data["note"] = note // current notes
+ data["weather"] = weather
+
+ // update the ui if it exists, returns null if no ui is passed/found
+ ui = nanomanager.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, "communicator.tmpl", "Communicator", 475, 700, state = key_state)
+ // 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 five Master Controller tick
+ ui.set_auto_update(5)
+
+// Proc: Topic()
+// Parameters: 2 (standard Topic arguments)
+// Description: Responds to NanoUI button presses.
+/obj/item/device/communicator/Topic(href, href_list)
+ if(..())
+ return 1
+ if(href_list["rename"])
+ var/new_name = sanitizeSafe(input(usr,"Please enter your name.","Communicator",usr.name) )
+ if(new_name)
+ owner = new_name
+ name = "[owner]'s [initial(name)]"
+ if(camera)
+ camera.name = name
+ camera.c_tag = name
+
+ if(href_list["toggle_visibility"])
+ switch(network_visibility)
+ if(1) //Visible, becoming invisbile
+ network_visibility = 0
+ if(camera)
+ camera.remove_network(NETWORK_COMMUNICATORS)
+ if(0) //Invisible, becoming visible
+ network_visibility = 1
+ if(camera)
+ camera.add_network(NETWORK_COMMUNICATORS)
+
+ if(href_list["toggle_ringer"])
+ ringer = !ringer
+
+ if(href_list["add_hex"])
+ var/hex = href_list["add_hex"]
+ add_to_EPv2(hex)
+
+ if(href_list["write_target_address"])
+ var/new_address = sanitizeSafe(input(usr,"Please enter the desired target EPv2 address. Note that you must write the colons \
+ yourself.","Communicator",src.target_address) )
+ if(new_address)
+ target_address = new_address
+
+ if(href_list["clear_target_address"])
+ target_address = ""
+
+ if(href_list["dial"])
+ if(!get_connection_to_tcomms())
+ usr << "Error: Cannot connect to Exonet node."
+ return
+ var/their_address = href_list["dial"]
+ exonet.send_message(their_address, "voice")
+
+ if(href_list["decline"])
+ var/ref_to_remove = href_list["decline"]
+ var/atom/decline = locate(ref_to_remove)
+ if(decline)
+ del_request(decline)
+
+ if(href_list["message"])
+ if(!get_connection_to_tcomms())
+ usr << "Error: Cannot connect to Exonet node."
+ return
+ var/their_address = href_list["message"]
+ var/text = sanitizeSafe(input(usr,"Enter your message.","Text Message"))
+ if(text)
+ exonet.send_message(their_address, "text", text)
+ im_list += list(list("address" = exonet.address, "to_address" = their_address, "im" = text))
+ log_pda("[usr] (COMM: [src]) sent \"[text]\" to [exonet.get_atom_from_address(their_address)]")
+ for(var/mob/M in player_list)
+ if(M.stat == DEAD && M.is_preference_enabled(/datum/client_preference/ghost_ears))
+ if(istype(M, /mob/new_player) || M.forbid_seeing_deadchat)
+ continue
+ if(exonet.get_atom_from_address(their_address) == M)
+ continue
+ M.show_message("Comm IM - [src] -> [exonet.get_atom_from_address(their_address)]: [text]")
+
+ if(href_list["disconnect"])
+ var/name_to_disconnect = href_list["disconnect"]
+ for(var/mob/living/voice/V in contents)
+ if(name_to_disconnect == V.name)
+ close_connection(usr, V, "[usr] hung up")
+ for(var/obj/item/device/communicator/comm in communicating)
+ if(name_to_disconnect == comm.name)
+ close_connection(usr, comm, "[usr] hung up")
+
+ if(href_list["startvideo"])
+ var/ref_to_video = href_list["startvideo"]
+ var/obj/item/device/communicator/comm = locate(ref_to_video)
+ if(comm)
+ connect_video(usr, comm)
+
+ if(href_list["endvideo"])
+ if(video_source)
+ end_video()
+
+ if(href_list["watchvideo"])
+ if(video_source)
+ watch_video(usr,video_source.loc)
+
+ if(href_list["copy"])
+ target_address = href_list["copy"]
+
+ if(href_list["copy_name"])
+ target_address_name = href_list["copy_name"]
+
+ if(href_list["hang_up"])
+ for(var/mob/living/voice/V in contents)
+ close_connection(usr, V, "[usr] hung up")
+ for(var/obj/item/device/communicator/comm in communicating)
+ close_connection(usr, comm, "[usr] hung up")
+
+ if(href_list["switch_tab"])
+ selected_tab = href_list["switch_tab"]
+
+ if(href_list["edit"])
+ var/n = input(usr, "Please enter message", name, notehtml)
+ n = sanitizeSafe(n, extra = 0)
+ if(n)
+ note = html_decode(n)
+ notehtml = note
+ note = replacetext(note, "\n", "
")
+ else
+ note = ""
+ notehtml = note
+
+ nanomanager.update_uis(src)
+ add_fingerprint(usr)
\ No newline at end of file
diff --git a/code/game/objects/items/devices/communicator/cartridge.dm b/code/game/objects/items/devices/communicator/cartridge.dm
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/code/game/objects/items/devices/communicator/communicator.dm b/code/game/objects/items/devices/communicator/communicator.dm
index 9e31cb38bb..d5a4114aff 100644
--- a/code/game/objects/items/devices/communicator/communicator.dm
+++ b/code/game/objects/items/devices/communicator/communicator.dm
@@ -37,7 +37,8 @@ var/global/list/obj/item/device/communicator/all_communicators = list()
list("module" = "Contacts", "icon" = "person64", "number" = 3),
list("module" = "Messaging", "icon" = "comment64", "number" = 4),
list("module" = "Note", "icon" = "note64", "number" = 5),
- list("module" = "Settings", "icon" = "gear64", "number" = 6)
+ list("module" = "Weather", "icon" = "sun64", "number" = 6),
+ list("module" = "Settings", "icon" = "gear64", "number" = 7)
) //list("module" = "Name of Module", "icon" = "icon name64", "number" = "what tab is the module")
var/selected_tab = 1
@@ -191,16 +192,19 @@ var/global/list/obj/item/device/communicator/all_communicators = list()
if(istype(C, /obj/item/weapon/card/id))
var/obj/item/weapon/card/id/idcard = C
if(!idcard.registered_name || !idcard.assignment)
- user << "\The [src] rejects the ID."
- return
- if(!owner)
- user << "\The [src] rejects the ID."
- return
- if(owner == idcard.registered_name)
+ to_chat(user, "\The [src] rejects the ID.")
+ else if(!owner)
+ to_chat(user, "\The [src] rejects the ID.")
+ else if(owner == idcard.registered_name)
occupation = idcard.assignment
- user << "Occupation updated."
- return
- else return
+ to_chat(user, "Occupation updated.")
+// else if(istype(C, /obj/item/weapon/cartridge))
+// if(cartridge)
+// to_chat(user, "\The [src] already has an external device attached!")
+// else
+// modules.Add(list("module" = "External Device", "icon = external64", "number" = 8))
+// cartridge = C
+ return
// Proc: attack_self()
// Parameters: 1 (user - the mob that clicked the device in their hand)
@@ -258,288 +262,6 @@ var/global/list/obj/item/device/communicator/all_communicators = list()
exonet = null
return ..()
-// Proc: ui_interact()
-// Parameters: 4 (standard NanoUI arguments)
-// Description: Uses a bunch of for loops to turn lists into lists of lists, so they can be displayed in nanoUI, then displays various buttons to the user.
-/obj/item/device/communicator/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/key_state = null)
- // this is the data which will be sent to the ui
- var/data[0] //General nanoUI information
- var/communicators[0] //List of communicators
- var/invites[0] //Communicators and ghosts we've invited to our communicator.
- var/requests[0] //Communicators and ghosts wanting to go in our communicator.
- var/voices[0] //Current /mob/living/voice s inside the device.
- var/connected_communicators[0] //Current communicators connected to the device.
-
- var/im_contacts_ui[0] //List of communicators that have been messaged.
- var/im_list_ui[0] //List of messages.
-
- var/modules_ui[0] //Home screen info.
-
- //First we add other 'local' communicators.
- for(var/obj/item/device/communicator/comm in known_devices)
- if(comm.network_visibility && comm.exonet)
- communicators[++communicators.len] = list("name" = sanitize(comm.name), "address" = comm.exonet.address)
-
- //Now for ghosts who we pretend have communicators.
- for(var/mob/observer/dead/O in known_devices)
- if(O.client && O.client.prefs.communicator_visibility == 1 && O.exonet)
- communicators[++communicators.len] = list("name" = sanitize("[O.client.prefs.real_name]'s communicator"), "address" = O.exonet.address, "ref" = "\ref[O]")
-
- //Lists all the other communicators that we invited.
- for(var/obj/item/device/communicator/comm in voice_invites)
- if(comm.exonet)
- invites[++invites.len] = list("name" = sanitize(comm.name), "address" = comm.exonet.address, "ref" = "\ref[comm]")
-
- //Ghosts we invited.
- for(var/mob/observer/dead/O in voice_invites)
- if(O.exonet && O.client)
- invites[++invites.len] = list("name" = sanitize("[O.client.prefs.real_name]'s communicator"), "address" = O.exonet.address, "ref" = "\ref[O]")
-
- //Communicators that want to talk to us.
- for(var/obj/item/device/communicator/comm in voice_requests)
- if(comm.exonet)
- requests[++requests.len] = list("name" = sanitize(comm.name), "address" = comm.exonet.address, "ref" = "\ref[comm]")
-
- //Ghosts that want to talk to us.
- for(var/mob/observer/dead/O in voice_requests)
- if(O.exonet && O.client)
- requests[++requests.len] = list("name" = sanitize("[O.client.prefs.real_name]'s communicator"), "address" = O.exonet.address, "ref" = "\ref[O]")
-
- //Now for all the voice mobs inside the communicator.
- for(var/mob/living/voice/voice in contents)
- voices[++voices.len] = list("name" = sanitize("[voice.name]'s communicator"), "true_name" = sanitize(voice.name))
-
- //Finally, all the communicators linked to this one.
- for(var/obj/item/device/communicator/comm in communicating)
- connected_communicators[++connected_communicators.len] = list("name" = sanitize(comm.name), "true_name" = sanitize(comm.name), "ref" = "\ref[comm]")
-
- //Devices that have been messaged or recieved messages from.
- for(var/obj/item/device/communicator/comm in im_contacts)
- if(comm.exonet)
- im_contacts_ui[++im_contacts_ui.len] = list("name" = sanitize(comm.name), "address" = comm.exonet.address, "ref" = "\ref[comm]")
-
- for(var/mob/observer/dead/ghost in im_contacts)
- if(ghost.exonet)
- im_contacts_ui[++im_contacts_ui.len] = list("name" = sanitize(ghost.name), "address" = ghost.exonet.address, "ref" = "\ref[ghost]")
-
- //Actual messages.
- for(var/I in im_list)
- im_list_ui[++im_list_ui.len] = list("address" = I["address"], "to_address" = I["to_address"], "im" = I["im"])
-
- //Modules for homescreen.
- for(var/list/R in modules)
- modules_ui[++modules_ui.len] = R
-
- data["owner"] = owner ? owner : "Unset"
- data["occupation"] = occupation ? occupation : "Swipe ID to set."
- data["connectionStatus"] = get_connection_to_tcomms()
- data["visible"] = network_visibility
- data["address"] = exonet.address ? exonet.address : "Unallocated"
- data["targetAddress"] = target_address
- data["targetAddressName"] = target_address_name
- data["currentTab"] = selected_tab
- data["knownDevices"] = communicators
- data["invitesSent"] = invites
- data["requestsReceived"] = requests
- data["voice_mobs"] = voices
- data["communicating"] = connected_communicators
- data["video_comm"] = video_source ? "\ref[video_source.loc]" : null
- data["imContacts"] = im_contacts_ui
- data["imList"] = im_list_ui
- data["time"] = stationtime2text()
- data["ring"] = ringer
- data["homeScreen"] = modules_ui
- data["note"] = note // current notes
-
- // update the ui if it exists, returns null if no ui is passed/found
- ui = nanomanager.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, "communicator.tmpl", "Communicator", 475, 700, state = key_state)
- // 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 five Master Controller tick
- ui.set_auto_update(5)
-
-// Proc: Topic()
-// Parameters: 2 (standard Topic arguments)
-// Description: Responds to NanoUI button presses.
-/obj/item/device/communicator/Topic(href, href_list)
- if(..())
- return 1
- if(href_list["rename"])
- var/new_name = sanitizeSafe(input(usr,"Please enter your name.","Communicator",usr.name) )
- if(new_name)
- owner = new_name
- name = "[owner]'s [initial(name)]"
- if(camera)
- camera.name = name
- camera.c_tag = name
-
- if(href_list["toggle_visibility"])
- switch(network_visibility)
- if(1) //Visible, becoming invisbile
- network_visibility = 0
- if(camera)
- camera.remove_network(NETWORK_COMMUNICATORS)
- if(0) //Invisible, becoming visible
- network_visibility = 1
- if(camera)
- camera.add_network(NETWORK_COMMUNICATORS)
-
- if(href_list["toggle_ringer"])
- ringer = !ringer
-
- if(href_list["add_hex"])
- var/hex = href_list["add_hex"]
- add_to_EPv2(hex)
-
- if(href_list["write_target_address"])
- var/new_address = sanitizeSafe(input(usr,"Please enter the desired target EPv2 address. Note that you must write the colons \
- yourself.","Communicator",src.target_address) )
- if(new_address)
- target_address = new_address
-
- if(href_list["clear_target_address"])
- target_address = ""
-
- if(href_list["dial"])
- if(!get_connection_to_tcomms())
- usr << "Error: Cannot connect to Exonet node."
- return
- var/their_address = href_list["dial"]
- exonet.send_message(their_address, "voice")
-
- if(href_list["decline"])
- var/ref_to_remove = href_list["decline"]
- var/atom/decline = locate(ref_to_remove)
- if(decline)
- del_request(decline)
-
- if(href_list["message"])
- if(!get_connection_to_tcomms())
- usr << "Error: Cannot connect to Exonet node."
- return
- var/their_address = href_list["message"]
- var/text = sanitizeSafe(input(usr,"Enter your message.","Text Message"))
- if(text)
- exonet.send_message(their_address, "text", text)
- im_list += list(list("address" = exonet.address, "to_address" = their_address, "im" = text))
- log_pda("[usr] (COMM: [src]) sent \"[text]\" to [exonet.get_atom_from_address(their_address)]")
- for(var/mob/M in player_list)
- if(M.stat == DEAD && M.is_preference_enabled(/datum/client_preference/ghost_ears))
- if(istype(M, /mob/new_player) || M.forbid_seeing_deadchat)
- continue
- if(exonet.get_atom_from_address(their_address) == M)
- continue
- M.show_message("Comm IM - [src] -> [exonet.get_atom_from_address(their_address)]: [text]")
-
- if(href_list["disconnect"])
- var/name_to_disconnect = href_list["disconnect"]
- for(var/mob/living/voice/V in contents)
- if(name_to_disconnect == V.name)
- close_connection(usr, V, "[usr] hung up")
- for(var/obj/item/device/communicator/comm in communicating)
- if(name_to_disconnect == comm.name)
- close_connection(usr, comm, "[usr] hung up")
-
- if(href_list["startvideo"])
- var/ref_to_video = href_list["startvideo"]
- var/obj/item/device/communicator/comm = locate(ref_to_video)
- if(comm)
- connect_video(usr, comm)
-
- if(href_list["endvideo"])
- if(video_source)
- end_video()
-
- if(href_list["watchvideo"])
- if(video_source)
- watch_video(usr,video_source.loc)
-
- if(href_list["copy"])
- target_address = href_list["copy"]
-
- if(href_list["copy_name"])
- target_address_name = href_list["copy_name"]
-
- if(href_list["hang_up"])
- for(var/mob/living/voice/V in contents)
- close_connection(usr, V, "[usr] hung up")
- for(var/obj/item/device/communicator/comm in communicating)
- close_connection(usr, comm, "[usr] hung up")
-
- if(href_list["switch_tab"])
- selected_tab = href_list["switch_tab"]
-
- if(href_list["edit"])
- var/n = input(usr, "Please enter message", name, notehtml)
- n = sanitizeSafe(n, extra = 0)
- if(n)
- note = html_decode(n)
- notehtml = note
- note = replacetext(note, "\n", "
")
- else
- note = ""
- notehtml = note
-
- nanomanager.update_uis(src)
- add_fingerprint(usr)
-
-// Proc: receive_exonet_message()
-// Parameters: 4 (origin atom - the source of the message's holder, origin_address - where the message came from, message - the message received,
-// text - message text to send if message is of type "text")
-// Description: Handles voice requests and invite messages originating from both real communicators and ghosts. Also includes a ping response and IM function.
-/obj/item/device/communicator/receive_exonet_message(var/atom/origin_atom, origin_address, message, text)
- if(message == "voice")
- if(isobserver(origin_atom) || istype(origin_atom, /obj/item/device/communicator))
- if(origin_atom in voice_invites)
- var/user = null
- if(ismob(origin_atom.loc))
- user = origin_atom.loc
- open_connection(user, origin_atom)
- return
- else if(origin_atom in voice_requests)
- return //Spam prevention
- else
- request(origin_atom)
- if(message == "ping")
- if(network_visibility)
- var/random = rand(200,350)
- random = random / 10
- exonet.send_message(origin_address, "64 bytes received from [exonet.address] ecmp_seq=1 ttl=51 time=[random] ms")
- if(message == "text")
- request_im(origin_atom, origin_address, text)
- return
-
-// Proc: receive_exonet_message()
-// Parameters: 3 (origin atom - the source of the message's holder, origin_address - where the message came from, message - the message received)
-// Description: Handles voice requests and invite messages originating from both real communicators and ghosts. Also includes a ping response.
-/mob/observer/dead/receive_exonet_message(origin_atom, origin_address, message, text)
- if(message == "voice")
- if(istype(origin_atom, /obj/item/device/communicator))
- var/obj/item/device/communicator/comm = origin_atom
- if(src in comm.voice_invites)
- comm.open_connection(src)
- return
- src << "\icon[origin_atom] Receiving communicator request from [origin_atom]. To answer, use the Call Communicator \
- verb, and select that name to answer the call."
- src << 'sound/machines/defib_SafetyOn.ogg'
- comm.voice_invites |= src
- if(message == "ping")
- if(client && client.prefs.communicator_visibility)
- var/random = rand(450,700)
- random = random / 10
- exonet.send_message(origin_address, "64 bytes received from [exonet.address] ecmp_seq=1 ttl=51 time=[random] ms")
- if(message == "text")
- src << "\icon[origin_atom] Received text message from [origin_atom]: \"[text]\""
- src << 'sound/machines/defib_safetyOff.ogg'
- exonet_messages.Add("From [origin_atom]:
[text]")
- return
-
// Proc: register_device()
// Parameters: 1 (user - the person to use their name for)
// Description: Updates the owner's name and the device's name.
@@ -553,252 +275,13 @@ var/global/list/obj/item/device/communicator/all_communicators = list()
camera.name = name
camera.c_tag = name
-// Proc: add_communicating()
-// Parameters: 1 (comm - the communicator to add to communicating)
-// Description: Used when this communicator gets a new communicator to relay say/me messages to
-/obj/item/device/communicator/proc/add_communicating(obj/item/device/communicator/comm)
- if(!comm || !istype(comm)) return
-
- communicating |= comm
- listening_objects |= src
- update_icon()
-
-// Proc: del_communicating()
-// Parameters: 1 (comm - the communicator to remove from communicating)
-// Description: Used when this communicator is being asked to stop relaying say/me messages to another
-/obj/item/device/communicator/proc/del_communicating(obj/item/device/communicator/comm)
- if(!comm || !istype(comm)) return
-
- communicating.Remove(comm)
- update_icon()
-
-// Proc: open_connection()
-// Parameters: 2 (user - the person who initiated the connecting being opened, candidate - the communicator or observer that will connect to the device)
-// Description: Typechecks the candidate, then calls the correct proc for further connecting.
-/obj/item/device/communicator/proc/open_connection(mob/user, var/atom/candidate)
- if(isobserver(candidate))
- voice_invites.Remove(candidate)
- open_connection_to_ghost(user, candidate)
- else
- if(istype(candidate, /obj/item/device/communicator))
- open_connection_to_communicator(user, candidate)
-
-// Proc: open_connection_to_communicator()
-// Parameters: 2 (user - the person who initiated this and will be receiving feedback information, candidate - someone else's communicator)
-// Description: Adds the candidate and src to each other's communicating lists, allowing messages seen by the devices to be relayed.
-/obj/item/device/communicator/proc/open_connection_to_communicator(mob/user, var/atom/candidate)
- if(!istype(candidate, /obj/item/device/communicator))
- return
- var/obj/item/device/communicator/comm = candidate
- voice_invites.Remove(candidate)
- comm.voice_requests.Remove(src)
-
- if(user)
- comm.visible_message("\icon[src] Connecting to [src].")
- user << "\icon[src] Attempting to call [comm]."
- sleep(10)
- user << "\icon[src] Dialing internally from [station_name()], Kara Subsystem, [system_name()]."
- sleep(20) //If they don't have an exonet something is very wrong and we want a runtime.
- user << "\icon[src] Connection re-routed to [comm] at [comm.exonet.address]."
- sleep(40)
- user << "\icon[src] Connection to [comm] at [comm.exonet.address] established."
- comm.visible_message("\icon[src] Connection to [src] at [exonet.address] established.")
- sleep(20)
-
- src.add_communicating(comm)
- comm.add_communicating(src)
-
-// Proc: open_connection_to_ghost()
-// Parameters: 2 (user - the person who initiated this, candidate - the ghost that will be turned into a voice mob)
-// Description: Pulls the candidate ghost from deadchat, makes a new voice mob, transfers their identity, then their client.
-/obj/item/device/communicator/proc/open_connection_to_ghost(mob/user, var/mob/candidate)
- if(!isobserver(candidate))
- return
- //Handle moving the ghost into the new shell.
- announce_ghost_joinleave(candidate, 0, "They are occupying a personal communications device now.")
- voice_requests.Remove(candidate)
- voice_invites.Remove(candidate)
- var/mob/living/voice/new_voice = new /mob/living/voice(src) //Make the voice mob the ghost is going to be.
- new_voice.transfer_identity(candidate) //Now make the voice mob load from the ghost's active character in preferences.
- //Do some simple logging since this is a tad risky as a concept.
- var/msg = "[candidate && candidate.client ? "[candidate.client.key]" : "*no key*"] ([candidate]) has entered [src], triggered by \
- [user && user.client ? "[user.client.key]" : "*no key*"] ([user ? "[user]" : "*null*"]) at [x],[y],[z]. They have joined as [new_voice.name]."
- message_admins(msg)
- log_game(msg)
- new_voice.mind = candidate.mind //Transfer the mind, if any.
- new_voice.ckey = candidate.ckey //Finally, bring the client over.
- voice_mobs.Add(new_voice)
- listening_objects |= src
-
- var/obj/screen/blackness = new() //Makes a black screen, so the candidate can't see what's going on before actually 'connecting' to the communicator.
- blackness.screen_loc = ui_entire_screen
- blackness.icon = 'icons/effects/effects.dmi'
- blackness.icon_state = "1"
- blackness.mouse_opacity = 2 //Can't see anything!
- new_voice.client.screen.Add(blackness)
-
- update_icon()
-
- //Now for some connection fluff.
- if(user)
- user << "\icon[src] Connecting to [candidate]."
- new_voice << "\icon[src] Attempting to call [src]."
- sleep(10)
- new_voice << "\icon[src] Dialing to [station_name()], Kara Subsystem, [system_name()]."
- sleep(20)
- new_voice << "\icon[src] Connecting to [station_name()] telecommunications array."
- sleep(40)
- new_voice << "\icon[src] Connection to [station_name()] telecommunications array established. Redirecting signal to [src]."
- sleep(20)
-
- //We're connected, no need to hide everything.
- new_voice.client.screen.Remove(blackness)
- qdel(blackness)
-
- new_voice << "\icon[src] Connection to [src] established."
- new_voice << "To talk to the person on the other end of the call, just talk normally."
- new_voice << "If you want to end the call, use the 'Hang Up' verb. The other person can also hang up at any time."
- new_voice << "Remember, your character does not know anything you've learned from observing!"
- if(new_voice.mind)
- new_voice.mind.assigned_role = "Disembodied Voice"
- if(user)
- user << "\icon[src] Your communicator is now connected to [candidate]'s communicator."
-
-// Proc: close_connection()
-// Parameters: 3 (user - the user who initiated the disconnect, target - the mob or device being disconnected, reason - string shown when disconnected)
-// Description: Deletes specific voice_mobs or disconnects communicators, and shows a message to everyone when doing so. If target is null, all communicators
-// and voice mobs are removed.
-/obj/item/device/communicator/proc/close_connection(mob/user, var/atom/target, var/reason)
- if(voice_mobs.len == 0 && communicating.len == 0)
- return
-
- for(var/mob/living/voice/voice in voice_mobs) //Handle ghost-callers
- if(target && voice != target) //If no target is inputted, it deletes all of them.
- continue
- voice << "\icon[src] [reason]."
- visible_message("\icon[src] [reason].")
- voice_mobs.Remove(voice)
- qdel(voice)
- update_icon()
-
- for(var/obj/item/device/communicator/comm in communicating) //Now we handle real communicators.
- if(target && comm != target)
- continue
- src.del_communicating(comm)
- comm.del_communicating(src)
- comm.visible_message("\icon[src] [reason].")
- visible_message("\icon[src] [reason].")
- if(comm.camera && video_source == comm.camera) //We hung up on the person on video
- end_video()
- if(camera && comm.video_source == camera) //We hung up on them while they were watching us
- comm.end_video()
-
- if(voice_mobs.len == 0 && communicating.len == 0)
- listening_objects.Remove(src)
-
-// Proc: request()
-// Parameters: 1 (candidate - the ghost or communicator wanting to call the device)
-// Description: Response to a communicator or observer trying to call the device. Adds them to the list of requesters
-/obj/item/device/communicator/proc/request(var/atom/candidate)
- if(candidate in voice_requests)
- return
- var/who = null
- if(isobserver(candidate))
- who = candidate.name
- else if(istype(candidate, /obj/item/device/communicator))
- var/obj/item/device/communicator/comm = candidate
- who = comm.owner
- comm.voice_invites |= src
-
- if(!who)
- return
-
- voice_requests |= candidate
-
- if(ringer)
- playsound(loc, 'sound/machines/twobeep.ogg', 50, 1)
- for (var/mob/O in hearers(2, loc))
- O.show_message(text("\icon[src] *beep*"))
-
- alert_called = 1
- update_icon()
-
- //Search for holder of the device.
- var/mob/living/L = null
- if(loc && isliving(loc))
- L = loc
-
- if(L)
- L << "\icon[src] Communications request from [who]."
-
-// Proc: del_request()
-// Parameters: 1 (candidate - the ghost or communicator to be declined)
-// Description: Declines a request and cleans up both ends
-/obj/item/device/communicator/proc/del_request(var/atom/candidate)
- if(!(candidate in voice_requests))
- return
-
- if(isobserver(candidate))
- candidate << "Your communicator call request was declined."
- else if(istype(candidate, /obj/item/device/communicator))
- var/obj/item/device/communicator/comm = candidate
- comm.voice_invites -= src
-
- voice_requests -= candidate
-
- //Search for holder of our device.
- var/mob/living/us = null
- if(loc && isliving(loc))
- us = loc
-
- if(us)
- us << "\icon[src] Declined request."
-
-// Proc: request_im()
-// Parameters: 3 (candidate - the communicator wanting to message the device, origin_address - the address of the sender, text - the message)
-// Description: Response to a communicator trying to message the device.
-// Adds them to the list of people that have messaged this device and adds the message to the message list.
-/obj/item/device/communicator/proc/request_im(var/atom/candidate, var/origin_address, var/text)
- var/who = null
- if(isobserver(candidate))
- var/mob/observer/dead/ghost = candidate
- who = ghost
- im_list += list(list("address" = origin_address, "to_address" = exonet.address, "im" = text))
- else if(istype(candidate, /obj/item/device/communicator))
- var/obj/item/device/communicator/comm = candidate
- who = comm.owner
- comm.im_contacts |= src
- im_list += list(list("address" = origin_address, "to_address" = exonet.address, "im" = text))
- else return
-
- im_contacts |= candidate
-
- if(!who)
- return
-
- if(ringer)
- playsound(loc, 'sound/machines/twobeep.ogg', 50, 1)
- for (var/mob/O in hearers(2, loc))
- O.show_message(text("\icon[src] *beep*"))
-
- alert_called = 1
- update_icon()
-
- //Search for holder of the device.
- var/mob/living/L = null
- if(loc && isliving(loc))
- L = loc
-
- if(L)
- L << "\icon[src] Message from [who]."
-
// Proc: Destroy()
// Parameters: None
// Description: Deletes all the voice mobs, disconnects all linked communicators, and cuts lists to allow successful qdel()
/obj/item/device/communicator/Destroy()
for(var/mob/living/voice/voice in contents)
voice_mobs.Remove(voice)
- voice << "\icon[src] Connection timed out with remote host."
+ to_chat(voice, "\icon[src] Connection timed out with remote host.")
qdel(voice)
close_connection(reason = "Connection timed out")
communicating.Cut()
@@ -832,290 +315,6 @@ var/global/list/obj/item/device/communicator/all_communicators = list()
icon_state = initial(icon_state)
-// Proc: see_emote()
-// Parameters: 2 (M - the mob the emote originated from, text - the emote's contents)
-// Description: Relays the emote to all linked communicators.
-/obj/item/device/communicator/see_emote(mob/living/M, text)
- var/rendered = "\icon[src] [text]"
- for(var/obj/item/device/communicator/comm in communicating)
- var/turf/T = get_turf(comm)
- if(!T) return
- var/list/in_range = get_mobs_and_objs_in_view_fast(T,world.view,0) //Range of 3 since it's a tiny video display
- var/list/mobs_to_relay = in_range["mobs"]
-
- for(var/mob/mob in mobs_to_relay) //We can't use visible_message(), or else we will get an infinite loop if two communicators hear each other.
- var/dst = get_dist(get_turf(mob),get_turf(comm))
- if(dst <= video_range)
- mob.show_message(rendered)
- else
- mob << "You can barely see some movement on \the [src]'s display."
-
- ..()
-
-// Proc: hear_talk()
-// Parameters: 4 (M - the mob the speech originated from, text - what is being said, verb - the word used to describe how text is being said, speaking - language
-// being used)
-// Description: Relays the speech to all linked communicators.
-/obj/item/device/communicator/hear_talk(mob/living/M, text, verb, datum/language/speaking)
- for(var/obj/item/device/communicator/comm in communicating)
-
- var/turf/T = get_turf(comm)
- if(!T) return
- var/list/in_range = get_mobs_and_objs_in_view_fast(T,world.view,0)
- var/list/mobs_to_relay = in_range["mobs"]
-
- for(var/mob/mob in mobs_to_relay)
- //Can whoever is hearing us understand?
- if(!mob.say_understands(M, speaking))
- if(speaking)
- text = speaking.scramble(text)
- else
- text = stars(text)
- var/name_used = M.GetVoice()
- var/rendered = null
- if(speaking) //Language being used
- rendered = "\icon[src] [name_used] [speaking.format_message(text, verb)]"
- else
- rendered = "\icon[src] [name_used] [verb], \"[text]\""
- mob.show_message(rendered, 2)
-
-// Proc: show_message()
-// Parameters: 4 (msg - the message, type - number to determine if message is visible or audible, alt - unknown, alt_type - unknown)
-// Description: Relays the message to all linked communicators.
-/obj/item/device/communicator/show_message(msg, type, alt, alt_type)
- var/rendered = "\icon[src] [msg]"
- for(var/obj/item/device/communicator/comm in communicating)
- var/turf/T = get_turf(comm)
- if(!T) return
- var/list/in_range = get_mobs_and_objs_in_view_fast(T,world.view,0)
- var/list/mobs_to_relay = in_range["mobs"]
-
- for(var/mob/mob in mobs_to_relay)
- mob.show_message(rendered)
- ..()
-
-// Verb: join_as_voice()
-// Parameters: None
-// Description: Allows ghosts to call communicators, if they meet all the requirements.
-/mob/observer/dead/verb/join_as_voice()
- set category = "Ghost"
- set name = "Call Communicator"
- set desc = "If there is a communicator available, send a request to speak through it. This will reset your respawn timer, if someone picks up."
-
- if(ticker.current_state < GAME_STATE_PLAYING)
- src << "The game hasn't started yet!"
- return
-
- if (!src.stat)
- return
-
- if (usr != src)
- return //something is terribly wrong
-
- var/confirm = alert(src, "Would you like to talk as [src.client.prefs.real_name], over a communicator? \
- This will reset your respawn timer, if someone answers.", "Join as Voice?", "Yes","No")
- if(confirm == "No")
- return
-
- for(var/mob/living/L in mob_list) //Simple check so you don't have dead people calling.
- if(src.client.prefs.real_name == L.real_name)
- src << "Your identity is already present in the game world. Please load in a different character first."
- return
-
- var/obj/machinery/exonet_node/E = get_exonet_node()
- if(!E || !E.on || !E.allow_external_communicators)
- src << "The Exonet node at telecommunications is down at the moment, or is actively blocking you, so your call can't go through."
- return
-
- var/list/choices = list()
- for(var/obj/item/device/communicator/comm in all_communicators)
- if(!comm.network_visibility || !comm.exonet || !comm.exonet.address)
- continue
- choices.Add(comm)
-
- if(!choices.len)
- src << "There are no available communicators, sorry."
- return
-
- var/choice = input(src,"Send a voice request to whom?") as null|anything in choices
- if(choice)
- var/obj/item/device/communicator/chosen_communicator = choice
- var/mob/observer/dead/O = src
- if(O.exonet)
- O.exonet.send_message(chosen_communicator.exonet.address, "voice")
-
- src << "A communications request has been sent to [chosen_communicator]. Now you need to wait until someone answers."
-
-// Verb: text_communicator()
-// Parameters: None
-// Description: Allows a ghost to send a text message to a communicator.
-/mob/observer/dead/verb/text_communicator()
- set category = "Ghost"
- set name = "Text Communicator"
- set desc = "If there is a communicator available, send a text message to it."
-
- if(ticker.current_state < GAME_STATE_PLAYING)
- src << "The game hasn't started yet!"
- return
-
- if (!src.stat)
- return
-
- if (usr != src)
- return //something is terribly wrong
-
- for(var/mob/living/L in mob_list) //Simple check so you don't have dead people calling.
- if(src.client.prefs.real_name == L.real_name)
- src << "Your identity is already present in the game world. Please load in a different character first."
- return
-
- var/obj/machinery/exonet_node/E = get_exonet_node()
- if(!E || !E.on || !E.allow_external_communicators)
- src << "The Exonet node at telecommunications is down at the moment, or is actively blocking you, so your call can't go through."
- return
-
- var/list/choices = list()
- for(var/obj/item/device/communicator/comm in all_communicators)
- if(!comm.network_visibility || !comm.exonet || !comm.exonet.address)
- continue
- choices.Add(comm)
-
- if(!choices.len)
- src << "There are no available communicators, sorry."
- return
-
- var/choice = input(src,"Send a text message to whom?") as null|anything in choices
- if(choice)
- var/obj/item/device/communicator/chosen_communicator = choice
- var/mob/observer/dead/O = src
- var/text_message = sanitize(input(src, "What do you want the message to say?")) as message
- if(text_message && O.exonet)
- O.exonet.send_message(chosen_communicator.exonet.address, "text", text_message)
-
- src << "You have sent '[text_message]' to [chosen_communicator]."
- exonet_messages.Add("To [chosen_communicator]:
[text_message]")
- log_pda("[usr] (COMM: [src]) sent \"[text_message]\" to [chosen_communicator]")
- for(var/mob/M in player_list)
- if(M.stat == DEAD && M.is_preference_enabled(/datum/client_preference/ghost_ears))
- if(istype(M, /mob/new_player) || M.forbid_seeing_deadchat)
- continue
- if(M == src)
- continue
- M.show_message("Comm IM - [src] -> [chosen_communicator]: [text_message]")
-
-
-
-// Verb: show_text_messages()
-// Parameters: None
-// Description: Lets ghosts review messages they've sent or received.
-/mob/observer/dead/verb/show_text_messages()
- set category = "Ghost"
- set name = "Show Text Messages"
- set desc = "Allows you to see exonet text messages you've sent and received."
-
- var/HTML = "
Exonet Message Log"
- for(var/line in exonet_messages)
- HTML += line + "
"
- HTML +=""
- usr << browse(HTML, "window=log;size=400x444;border=1;can_resize=1;can_close=1;can_minimize=0")
-
-// Proc: connect_video()
-// Parameters: user - the mob doing the viewing of video, comm - the communicator at the far end
-// Description: Sets up a videocall and puts the first view into it using watch_video, and updates the icon
-/obj/item/device/communicator/proc/connect_video(mob/user,obj/item/device/communicator/comm)
- if((!user) || (!comm) || user.stat) return //KO or dead, or already in a video
-
- if(video_source) //Already in a video
- user << "You are already connected to a video call!"
-
- if(user.blinded) //User is blinded
- user << "You cannot see well enough to do that!"
-
- if(!(src in comm.communicating) || !comm.camera) //You called someone with a broken communicator or one that's fake or yourself or something
- user << "\icon[src]ERROR: Video failed. Either bandwidth is too low, or the other communicator is malfunctioning."
-
- user << "\icon[src] Attempting to start video over existing call."
- sleep(30)
- user << "\icon[src] Please wait..."
-
- video_source = comm.camera
- comm.visible_message("\icon[src] New video connection from [comm].")
- watch_video(user)
- update_icon()
-
-// Proc: watch_video()
-// Parameters: user - the mob doing the viewing of video
-// Description: Moves a mob's eye to the far end for the duration of viewing the far end
-/obj/item/device/communicator/proc/watch_video(mob/user)
- if(!Adjacent(user) || !video_source) return
- user.set_machine(video_source)
- user.reset_view(video_source)
- to_chat(user,"Now viewing video session. To leave camera view, close the communicator window OR: OOC -> Cancel Camera View")
- to_chat(user,"To return to an active video session, use the communicator in your hand.")
- spawn(0)
- while(user.machine == video_source && Adjacent(user))
- var/turf/T = get_turf(video_source)
- if(!T || !is_on_same_plane_or_station(T.z, user.z) || !video_source.can_use())
- user << "The screen bursts into static, then goes black."
- video_cleanup(user)
- return
- sleep(10)
-
- video_cleanup(user)
-
-// Proc: video_cleanup()
-// Parameters: user - the mob who doesn't want to see video anymore
-// Description: Cleans up mob's client when they stop watching a video
-/obj/item/device/communicator/proc/video_cleanup(mob/user)
- if(!user) return
-
- user.reset_view(null)
- user.unset_machine()
-
-// Proc: end_video()
-// Parameters: reason - the text reason to print for why it ended
-// Description: Ends the video call by clearing video_source
-/obj/item/device/communicator/proc/end_video(var/reason)
- video_source = null
-
- . = "\icon[src] [reason ? reason : "Video session ended"]."
-
- visible_message(.)
- update_icon()
-
-//For synths who have no hands.
-/obj/item/device/communicator/integrated
- name = "integrated communicator"
- desc = "A circuit used for long-range communications, able to be integrated into a system."
-
-//A stupid hack because synths don't use languages properly or something.
-//I don't want to go digging in saycode for a week, so BS it as translation software or something.
-
-// Proc: open_connection_to_ghost()
-// Parameters: 2 (refer to base definition for arguments)
-// Description: Synths don't use languages properly, so this is a bandaid fix until that can be resolved..
-/obj/item/device/communicator/integrated/open_connection_to_ghost(user, candidate)
- ..(user, candidate)
- spawn(1)
- for(var/mob/living/voice/V in contents)
- V.universal_speak = 1
- V.universal_understand = 1
-
-// Verb: activate()
-// Parameters: None
-// Description: Lets synths use their communicators without hands.
-/obj/item/device/communicator/integrated/verb/activate()
- set category = "AI IM"
- set name = "Use Communicator"
- set desc = "Utilizes your built-in communicator."
- set src in usr
-
- if(usr.stat == 2)
- usr << "You can't do that because you are dead!"
- return
-
- src.attack_self(usr)
-
// A camera preset for spawning in the communicator
/obj/machinery/camera/communicator
network = list(NETWORK_COMMUNICATORS)
diff --git a/code/game/objects/items/devices/communicator/integrated.dm b/code/game/objects/items/devices/communicator/integrated.dm
new file mode 100644
index 0000000000..f464e15e0b
--- /dev/null
+++ b/code/game/objects/items/devices/communicator/integrated.dm
@@ -0,0 +1,32 @@
+//For synths who have no hands.
+/obj/item/device/communicator/integrated
+ name = "integrated communicator"
+ desc = "A circuit used for long-range communications, able to be integrated into a system."
+
+//A stupid hack because synths don't use languages properly or something.
+//I don't want to go digging in saycode for a week, so BS it as translation software or something.
+
+// Proc: open_connection_to_ghost()
+// Parameters: 2 (refer to base definition for arguments)
+// Description: Synths don't use languages properly, so this is a bandaid fix until that can be resolved..
+/obj/item/device/communicator/integrated/open_connection_to_ghost(user, candidate)
+ ..(user, candidate)
+ spawn(1)
+ for(var/mob/living/voice/V in contents)
+ V.universal_speak = 1
+ V.universal_understand = 1
+
+// Verb: activate()
+// Parameters: None
+// Description: Lets synths use their communicators without hands.
+/obj/item/device/communicator/integrated/verb/activate()
+ set category = "AI IM"
+ set name = "Use Communicator"
+ set desc = "Utilizes your built-in communicator."
+ set src in usr
+
+ if(usr.stat == 2)
+ to_chat(usr, "You can't do that because you are dead!")
+ return
+
+ src.attack_self(usr)
\ No newline at end of file
diff --git a/code/game/objects/items/devices/communicator/messaging.dm b/code/game/objects/items/devices/communicator/messaging.dm
new file mode 100644
index 0000000000..3419985a71
--- /dev/null
+++ b/code/game/objects/items/devices/communicator/messaging.dm
@@ -0,0 +1,162 @@
+// Proc: receive_exonet_message()
+// Parameters: 4 (origin atom - the source of the message's holder, origin_address - where the message came from, message - the message received,
+// text - message text to send if message is of type "text")
+// Description: Handles voice requests and invite messages originating from both real communicators and ghosts. Also includes a ping response and IM function.
+/obj/item/device/communicator/receive_exonet_message(var/atom/origin_atom, origin_address, message, text)
+ if(message == "voice")
+ if(isobserver(origin_atom) || istype(origin_atom, /obj/item/device/communicator))
+ if(origin_atom in voice_invites)
+ var/user = null
+ if(ismob(origin_atom.loc))
+ user = origin_atom.loc
+ open_connection(user, origin_atom)
+ return
+ else if(origin_atom in voice_requests)
+ return //Spam prevention
+ else
+ request(origin_atom)
+ if(message == "ping")
+ if(network_visibility)
+ var/random = rand(200,350)
+ random = random / 10
+ exonet.send_message(origin_address, "64 bytes received from [exonet.address] ecmp_seq=1 ttl=51 time=[random] ms")
+ if(message == "text")
+ request_im(origin_atom, origin_address, text)
+ return
+
+// Proc: receive_exonet_message()
+// Parameters: 3 (origin atom - the source of the message's holder, origin_address - where the message came from, message - the message received)
+// Description: Handles voice requests and invite messages originating from both real communicators and ghosts. Also includes a ping response.
+/mob/observer/dead/receive_exonet_message(origin_atom, origin_address, message, text)
+ if(message == "voice")
+ if(istype(origin_atom, /obj/item/device/communicator))
+ var/obj/item/device/communicator/comm = origin_atom
+ if(src in comm.voice_invites)
+ comm.open_connection(src)
+ return
+ to_chat(src, "\icon[origin_atom] Receiving communicator request from [origin_atom]. To answer, use the Call Communicator \
+ verb, and select that name to answer the call.")
+ src << 'sound/machines/defib_SafetyOn.ogg'
+ comm.voice_invites |= src
+ if(message == "ping")
+ if(client && client.prefs.communicator_visibility)
+ var/random = rand(450,700)
+ random = random / 10
+ exonet.send_message(origin_address, "64 bytes received from [exonet.address] ecmp_seq=1 ttl=51 time=[random] ms")
+ if(message == "text")
+ to_chat(src, "\icon[origin_atom] Received text message from [origin_atom]: \"[text]\"")
+ src << 'sound/machines/defib_safetyOff.ogg'
+ exonet_messages.Add("From [origin_atom]:
[text]")
+ return
+
+// Proc: request_im()
+// Parameters: 3 (candidate - the communicator wanting to message the device, origin_address - the address of the sender, text - the message)
+// Description: Response to a communicator trying to message the device.
+// Adds them to the list of people that have messaged this device and adds the message to the message list.
+/obj/item/device/communicator/proc/request_im(var/atom/candidate, var/origin_address, var/text)
+ var/who = null
+ if(isobserver(candidate))
+ var/mob/observer/dead/ghost = candidate
+ who = ghost
+ im_list += list(list("address" = origin_address, "to_address" = exonet.address, "im" = text))
+ else if(istype(candidate, /obj/item/device/communicator))
+ var/obj/item/device/communicator/comm = candidate
+ who = comm.owner
+ comm.im_contacts |= src
+ im_list += list(list("address" = origin_address, "to_address" = exonet.address, "im" = text))
+ else return
+
+ im_contacts |= candidate
+
+ if(!who)
+ return
+
+ if(ringer)
+ playsound(loc, 'sound/machines/twobeep.ogg', 50, 1)
+ for (var/mob/O in hearers(2, loc))
+ O.show_message(text("\icon[src] *beep*"))
+
+ alert_called = 1
+ update_icon()
+
+ //Search for holder of the device.
+ var/mob/living/L = null
+ if(loc && isliving(loc))
+ L = loc
+
+ if(L)
+ to_chat(L, "\icon[src] Message from [who].")
+
+// Verb: text_communicator()
+// Parameters: None
+// Description: Allows a ghost to send a text message to a communicator.
+/mob/observer/dead/verb/text_communicator()
+ set category = "Ghost"
+ set name = "Text Communicator"
+ set desc = "If there is a communicator available, send a text message to it."
+
+ if(ticker.current_state < GAME_STATE_PLAYING)
+ to_chat(src, "The game hasn't started yet!")
+ return
+
+ if (!src.stat)
+ return
+
+ if (usr != src)
+ return //something is terribly wrong
+
+ for(var/mob/living/L in mob_list) //Simple check so you don't have dead people calling.
+ if(src.client.prefs.real_name == L.real_name)
+ to_chat(src, "Your identity is already present in the game world. Please load in a different character first.")
+ return
+
+ var/obj/machinery/exonet_node/E = get_exonet_node()
+ if(!E || !E.on || !E.allow_external_communicators)
+ to_chat(src, "The Exonet node at telecommunications is down at the moment, or is actively blocking you, \
+ so your call can't go through.")
+ return
+
+ var/list/choices = list()
+ for(var/obj/item/device/communicator/comm in all_communicators)
+ if(!comm.network_visibility || !comm.exonet || !comm.exonet.address)
+ continue
+ choices.Add(comm)
+
+ if(!choices.len)
+ to_chat(src, "There are no available communicators, sorry.")
+ return
+
+ var/choice = input(src,"Send a text message to whom?") as null|anything in choices
+ if(choice)
+ var/obj/item/device/communicator/chosen_communicator = choice
+ var/mob/observer/dead/O = src
+ var/text_message = sanitize(input(src, "What do you want the message to say?")) as message
+ if(text_message && O.exonet)
+ O.exonet.send_message(chosen_communicator.exonet.address, "text", text_message)
+
+ to_chat(src, "You have sent '[text_message]' to [chosen_communicator].")
+ exonet_messages.Add("To [chosen_communicator]:
[text_message]")
+ log_pda("[usr] (COMM: [src]) sent \"[text_message]\" to [chosen_communicator]")
+ for(var/mob/M in player_list)
+ if(M.stat == DEAD && M.is_preference_enabled(/datum/client_preference/ghost_ears))
+ if(istype(M, /mob/new_player) || M.forbid_seeing_deadchat)
+ continue
+ if(M == src)
+ continue
+ M.show_message("Comm IM - [src] -> [chosen_communicator]: [text_message]")
+
+
+
+// Verb: show_text_messages()
+// Parameters: None
+// Description: Lets ghosts review messages they've sent or received.
+/mob/observer/dead/verb/show_text_messages()
+ set category = "Ghost"
+ set name = "Show Text Messages"
+ set desc = "Allows you to see exonet text messages you've sent and received."
+
+ var/HTML = "Exonet Message Log"
+ for(var/line in exonet_messages)
+ HTML += line + "
"
+ HTML +=""
+ usr << browse(HTML, "window=log;size=400x444;border=1;can_resize=1;can_close=1;can_minimize=0")
\ No newline at end of file
diff --git a/code/game/objects/items/devices/communicator/phone.dm b/code/game/objects/items/devices/communicator/phone.dm
new file mode 100644
index 0000000000..d120bac21a
--- /dev/null
+++ b/code/game/objects/items/devices/communicator/phone.dm
@@ -0,0 +1,380 @@
+// Proc: add_communicating()
+// Parameters: 1 (comm - the communicator to add to communicating)
+// Description: Used when this communicator gets a new communicator to relay say/me messages to
+/obj/item/device/communicator/proc/add_communicating(obj/item/device/communicator/comm)
+ if(!comm || !istype(comm)) return
+
+ communicating |= comm
+ listening_objects |= src
+ update_icon()
+
+// Proc: del_communicating()
+// Parameters: 1 (comm - the communicator to remove from communicating)
+// Description: Used when this communicator is being asked to stop relaying say/me messages to another
+/obj/item/device/communicator/proc/del_communicating(obj/item/device/communicator/comm)
+ if(!comm || !istype(comm)) return
+
+ communicating.Remove(comm)
+ update_icon()
+
+// Proc: open_connection()
+// Parameters: 2 (user - the person who initiated the connecting being opened, candidate - the communicator or observer that will connect to the device)
+// Description: Typechecks the candidate, then calls the correct proc for further connecting.
+/obj/item/device/communicator/proc/open_connection(mob/user, var/atom/candidate)
+ if(isobserver(candidate))
+ voice_invites.Remove(candidate)
+ open_connection_to_ghost(user, candidate)
+ else
+ if(istype(candidate, /obj/item/device/communicator))
+ open_connection_to_communicator(user, candidate)
+
+// Proc: open_connection_to_communicator()
+// Parameters: 2 (user - the person who initiated this and will be receiving feedback information, candidate - someone else's communicator)
+// Description: Adds the candidate and src to each other's communicating lists, allowing messages seen by the devices to be relayed.
+/obj/item/device/communicator/proc/open_connection_to_communicator(mob/user, var/atom/candidate)
+ if(!istype(candidate, /obj/item/device/communicator))
+ return
+ var/obj/item/device/communicator/comm = candidate
+ voice_invites.Remove(candidate)
+ comm.voice_requests.Remove(src)
+
+ if(user)
+ comm.visible_message("\icon[src] Connecting to [src].")
+ to_chat(user, "\icon[src] Attempting to call [comm].")
+ sleep(10)
+ to_chat(user, "\icon[src] Dialing internally from [station_name()], Kara Subsystem, [system_name()].")
+ sleep(20) //If they don't have an exonet something is very wrong and we want a runtime.
+ to_chat(user, "\icon[src] Connection re-routed to [comm] at [comm.exonet.address].")
+ sleep(40)
+ to_chat(user, "\icon[src] Connection to [comm] at [comm.exonet.address] established.")
+ comm.visible_message("\icon[src] Connection to [src] at [exonet.address] established.")
+ sleep(20)
+
+ src.add_communicating(comm)
+ comm.add_communicating(src)
+
+// Proc: open_connection_to_ghost()
+// Parameters: 2 (user - the person who initiated this, candidate - the ghost that will be turned into a voice mob)
+// Description: Pulls the candidate ghost from deadchat, makes a new voice mob, transfers their identity, then their client.
+/obj/item/device/communicator/proc/open_connection_to_ghost(mob/user, var/mob/candidate)
+ if(!isobserver(candidate))
+ return
+ //Handle moving the ghost into the new shell.
+ announce_ghost_joinleave(candidate, 0, "They are occupying a personal communications device now.")
+ voice_requests.Remove(candidate)
+ voice_invites.Remove(candidate)
+ var/mob/living/voice/new_voice = new /mob/living/voice(src) //Make the voice mob the ghost is going to be.
+ new_voice.transfer_identity(candidate) //Now make the voice mob load from the ghost's active character in preferences.
+ //Do some simple logging since this is a tad risky as a concept.
+ var/msg = "[candidate && candidate.client ? "[candidate.client.key]" : "*no key*"] ([candidate]) has entered [src], triggered by \
+ [user && user.client ? "[user.client.key]" : "*no key*"] ([user ? "[user]" : "*null*"]) at [x],[y],[z]. They have joined as [new_voice.name]."
+ message_admins(msg)
+ log_game(msg)
+ new_voice.mind = candidate.mind //Transfer the mind, if any.
+ new_voice.ckey = candidate.ckey //Finally, bring the client over.
+ voice_mobs.Add(new_voice)
+ listening_objects |= src
+
+ var/obj/screen/blackness = new() //Makes a black screen, so the candidate can't see what's going on before actually 'connecting' to the communicator.
+ blackness.screen_loc = ui_entire_screen
+ blackness.icon = 'icons/effects/effects.dmi'
+ blackness.icon_state = "1"
+ blackness.mouse_opacity = 2 //Can't see anything!
+ new_voice.client.screen.Add(blackness)
+
+ update_icon()
+
+ //Now for some connection fluff.
+ if(user)
+ to_chat(user, "\icon[src] Connecting to [candidate].")
+ to_chat(new_voice, "\icon[src] Attempting to call [src].")
+ sleep(10)
+ to_chat(new_voice, "\icon[src] Dialing to [station_name()], Kara Subsystem, [system_name()].")
+ sleep(20)
+ to_chat(new_voice, "\icon[src] Connecting to [station_name()] telecommunications array.")
+ sleep(40)
+ to_chat(new_voice, "\icon[src] Connection to [station_name()] telecommunications array established. Redirecting signal to [src].")
+ sleep(20)
+
+ //We're connected, no need to hide everything.
+ new_voice.client.screen.Remove(blackness)
+ qdel(blackness)
+
+ to_chat(new_voice, "\icon[src] Connection to [src] established.")
+ to_chat(new_voice, "To talk to the person on the other end of the call, just talk normally.")
+ to_chat(new_voice, "If you want to end the call, use the 'Hang Up' verb. The other person can also hang up at any time.")
+ to_chat(new_voice, "Remember, your character does not know anything you've learned from observing!")
+ if(new_voice.mind)
+ new_voice.mind.assigned_role = "Disembodied Voice"
+ if(user)
+ to_chat(user, "\icon[src] Your communicator is now connected to [candidate]'s communicator.")
+
+// Proc: close_connection()
+// Parameters: 3 (user - the user who initiated the disconnect, target - the mob or device being disconnected, reason - string shown when disconnected)
+// Description: Deletes specific voice_mobs or disconnects communicators, and shows a message to everyone when doing so. If target is null, all communicators
+// and voice mobs are removed.
+/obj/item/device/communicator/proc/close_connection(mob/user, var/atom/target, var/reason)
+ if(voice_mobs.len == 0 && communicating.len == 0)
+ return
+
+ for(var/mob/living/voice/voice in voice_mobs) //Handle ghost-callers
+ if(target && voice != target) //If no target is inputted, it deletes all of them.
+ continue
+ to_chat(voice, "\icon[src] [reason].")
+ visible_message("\icon[src] [reason].")
+ voice_mobs.Remove(voice)
+ qdel(voice)
+ update_icon()
+
+ for(var/obj/item/device/communicator/comm in communicating) //Now we handle real communicators.
+ if(target && comm != target)
+ continue
+ src.del_communicating(comm)
+ comm.del_communicating(src)
+ comm.visible_message("\icon[src] [reason].")
+ visible_message("\icon[src] [reason].")
+ if(comm.camera && video_source == comm.camera) //We hung up on the person on video
+ end_video()
+ if(camera && comm.video_source == camera) //We hung up on them while they were watching us
+ comm.end_video()
+
+ if(voice_mobs.len == 0 && communicating.len == 0)
+ listening_objects.Remove(src)
+
+// Proc: request()
+// Parameters: 1 (candidate - the ghost or communicator wanting to call the device)
+// Description: Response to a communicator or observer trying to call the device. Adds them to the list of requesters
+/obj/item/device/communicator/proc/request(var/atom/candidate)
+ if(candidate in voice_requests)
+ return
+ var/who = null
+ if(isobserver(candidate))
+ who = candidate.name
+ else if(istype(candidate, /obj/item/device/communicator))
+ var/obj/item/device/communicator/comm = candidate
+ who = comm.owner
+ comm.voice_invites |= src
+
+ if(!who)
+ return
+
+ voice_requests |= candidate
+
+ if(ringer)
+ playsound(loc, 'sound/machines/twobeep.ogg', 50, 1)
+ for (var/mob/O in hearers(2, loc))
+ O.show_message(text("\icon[src] *beep*"))
+
+ alert_called = 1
+ update_icon()
+
+ //Search for holder of the device.
+ var/mob/living/L = null
+ if(loc && isliving(loc))
+ L = loc
+
+ if(L)
+ to_chat(L, "\icon[src] Communications request from [who].")
+
+// Proc: del_request()
+// Parameters: 1 (candidate - the ghost or communicator to be declined)
+// Description: Declines a request and cleans up both ends
+/obj/item/device/communicator/proc/del_request(var/atom/candidate)
+ if(!(candidate in voice_requests))
+ return
+
+ if(isobserver(candidate))
+ to_chat(candidate, "Your communicator call request was declined.")
+ else if(istype(candidate, /obj/item/device/communicator))
+ var/obj/item/device/communicator/comm = candidate
+ comm.voice_invites -= src
+
+ voice_requests -= candidate
+
+ //Search for holder of our device.
+ var/mob/living/us = null
+ if(loc && isliving(loc))
+ us = loc
+
+ if(us)
+ to_chat(us, "\icon[src] Declined request.")
+
+// Proc: see_emote()
+// Parameters: 2 (M - the mob the emote originated from, text - the emote's contents)
+// Description: Relays the emote to all linked communicators.
+/obj/item/device/communicator/see_emote(mob/living/M, text)
+ var/rendered = "\icon[src] [text]"
+ for(var/obj/item/device/communicator/comm in communicating)
+ var/turf/T = get_turf(comm)
+ if(!T) return
+ var/list/in_range = get_mobs_and_objs_in_view_fast(T,world.view,0) //Range of 3 since it's a tiny video display
+ var/list/mobs_to_relay = in_range["mobs"]
+
+ for(var/mob/mob in mobs_to_relay) //We can't use visible_message(), or else we will get an infinite loop if two communicators hear each other.
+ var/dst = get_dist(get_turf(mob),get_turf(comm))
+ if(dst <= video_range)
+ mob.show_message(rendered)
+ else
+ to_chat(mob, "You can barely see some movement on \the [src]'s display.")
+
+ ..()
+
+// Proc: hear_talk()
+// Parameters: 4 (M - the mob the speech originated from, text - what is being said, verb - the word used to describe how text is being said, speaking - language
+// being used)
+// Description: Relays the speech to all linked communicators.
+/obj/item/device/communicator/hear_talk(mob/living/M, text, verb, datum/language/speaking)
+ for(var/obj/item/device/communicator/comm in communicating)
+
+ var/turf/T = get_turf(comm)
+ if(!T) return
+ var/list/in_range = get_mobs_and_objs_in_view_fast(T,world.view,0)
+ var/list/mobs_to_relay = in_range["mobs"]
+
+ for(var/mob/mob in mobs_to_relay)
+ //Can whoever is hearing us understand?
+ if(!mob.say_understands(M, speaking))
+ if(speaking)
+ text = speaking.scramble(text)
+ else
+ text = stars(text)
+ var/name_used = M.GetVoice()
+ var/rendered = null
+ if(speaking) //Language being used
+ rendered = "\icon[src] [name_used] [speaking.format_message(text, verb)]"
+ else
+ rendered = "\icon[src] [name_used] [verb], \"[text]\""
+ mob.show_message(rendered, 2)
+
+// Proc: show_message()
+// Parameters: 4 (msg - the message, type - number to determine if message is visible or audible, alt - unknown, alt_type - unknown)
+// Description: Relays the message to all linked communicators.
+/obj/item/device/communicator/show_message(msg, type, alt, alt_type)
+ var/rendered = "\icon[src] [msg]"
+ for(var/obj/item/device/communicator/comm in communicating)
+ var/turf/T = get_turf(comm)
+ if(!T) return
+ var/list/in_range = get_mobs_and_objs_in_view_fast(T,world.view,0)
+ var/list/mobs_to_relay = in_range["mobs"]
+
+ for(var/mob/mob in mobs_to_relay)
+ mob.show_message(rendered)
+ ..()
+
+// Verb: join_as_voice()
+// Parameters: None
+// Description: Allows ghosts to call communicators, if they meet all the requirements.
+/mob/observer/dead/verb/join_as_voice()
+ set category = "Ghost"
+ set name = "Call Communicator"
+ set desc = "If there is a communicator available, send a request to speak through it. This will reset your respawn timer, if someone picks up."
+
+ if(ticker.current_state < GAME_STATE_PLAYING)
+ to_chat(src, "The game hasn't started yet!")
+ return
+
+ if (!src.stat)
+ return
+
+ if (usr != src)
+ return //something is terribly wrong
+
+ var/confirm = alert(src, "Would you like to talk as [src.client.prefs.real_name], over a communicator? \
+ This will reset your respawn timer, if someone answers.", "Join as Voice?", "Yes","No")
+ if(confirm == "No")
+ return
+
+ for(var/mob/living/L in mob_list) //Simple check so you don't have dead people calling.
+ if(src.client.prefs.real_name == L.real_name)
+ to_chat(src, "Your identity is already present in the game world. Please load in a different character first.")
+ return
+
+ var/obj/machinery/exonet_node/E = get_exonet_node()
+ if(!E || !E.on || !E.allow_external_communicators)
+ to_chat(src, "The Exonet node at telecommunications is down at the moment, or is actively blocking you, \
+ so your call can't go through.")
+ return
+
+ var/list/choices = list()
+ for(var/obj/item/device/communicator/comm in all_communicators)
+ if(!comm.network_visibility || !comm.exonet || !comm.exonet.address)
+ continue
+ choices.Add(comm)
+
+ if(!choices.len)
+ to_chat(src , "There are no available communicators, sorry.")
+ return
+
+ var/choice = input(src,"Send a voice request to whom?") as null|anything in choices
+ if(choice)
+ var/obj/item/device/communicator/chosen_communicator = choice
+ var/mob/observer/dead/O = src
+ if(O.exonet)
+ O.exonet.send_message(chosen_communicator.exonet.address, "voice")
+
+ to_chat(src, "A communications request has been sent to [chosen_communicator]. Now you need to wait until someone answers.")
+
+// Proc: connect_video()
+// Parameters: user - the mob doing the viewing of video, comm - the communicator at the far end
+// Description: Sets up a videocall and puts the first view into it using watch_video, and updates the icon
+/obj/item/device/communicator/proc/connect_video(mob/user,obj/item/device/communicator/comm)
+ if((!user) || (!comm) || user.stat) return //KO or dead, or already in a video
+
+ if(video_source) //Already in a video
+ to_chat(user, "You are already connected to a video call!")
+
+ if(user.blinded) //User is blinded
+ to_chat(user, "You cannot see well enough to do that!")
+
+ if(!(src in comm.communicating) || !comm.camera) //You called someone with a broken communicator or one that's fake or yourself or something
+ to_chat(user, "\icon[src]ERROR: Video failed. Either bandwidth is too low, or the other communicator is malfunctioning.")
+
+ to_chat(user, "\icon[src] Attempting to start video over existing call.")
+ sleep(30)
+ to_chat(user, "\icon[src] Please wait...")
+
+ video_source = comm.camera
+ comm.visible_message("\icon[src] New video connection from [comm].")
+ watch_video(user)
+ update_icon()
+
+// Proc: watch_video()
+// Parameters: user - the mob doing the viewing of video
+// Description: Moves a mob's eye to the far end for the duration of viewing the far end
+/obj/item/device/communicator/proc/watch_video(mob/user)
+ if(!Adjacent(user) || !video_source) return
+ user.set_machine(video_source)
+ user.reset_view(video_source)
+ to_chat(user,"Now viewing video session. To leave camera view, close the communicator window OR: OOC -> Cancel Camera View")
+ to_chat(user,"To return to an active video session, use the communicator in your hand.")
+ spawn(0)
+ while(user.machine == video_source && Adjacent(user))
+ var/turf/T = get_turf(video_source)
+ if(!T || !is_on_same_plane_or_station(T.z, user.z) || !video_source.can_use())
+ user << "The screen bursts into static, then goes black."
+ video_cleanup(user)
+ return
+ sleep(10)
+
+ video_cleanup(user)
+
+// Proc: video_cleanup()
+// Parameters: user - the mob who doesn't want to see video anymore
+// Description: Cleans up mob's client when they stop watching a video
+/obj/item/device/communicator/proc/video_cleanup(mob/user)
+ if(!user) return
+
+ user.reset_view(null)
+ user.unset_machine()
+
+// Proc: end_video()
+// Parameters: reason - the text reason to print for why it ended
+// Description: Ends the video call by clearing video_source
+/obj/item/device/communicator/proc/end_video(var/reason)
+ video_source = null
+
+ . = "\icon[src] [reason ? reason : "Video session ended"]."
+
+ visible_message(.)
+ update_icon()
+
diff --git a/html/changelogs/Atermonera - Weatherapp.yml b/html/changelogs/Atermonera - Weatherapp.yml
new file mode 100644
index 0000000000..2a513366f1
--- /dev/null
+++ b/html/changelogs/Atermonera - Weatherapp.yml
@@ -0,0 +1,5 @@
+author: Atermonera
+
+delete-after: True
+
+ - rscadd: "Communicators now have a weather app."
diff --git a/nano/css/icons.css b/nano/css/icons.css
index b2eacc0ab8..68ac412aba 100644
--- a/nano/css/icons.css
+++ b/nano/css/icons.css
@@ -50,222 +50,241 @@
/* positioning */
.uiIcon16.icon-blank { background-position: 16px 16px; }
-.uiIcon16.icon-carat-1-n { background-position: 0 0; }
-.uiIcon16.icon-carat-1-ne { background-position: -16px 0; }
-.uiIcon16.icon-carat-1-e { background-position: -32px 0; }
-.uiIcon16.icon-carat-1-se { background-position: -48px 0; }
-.uiIcon16.icon-carat-1-s { background-position: -64px 0; }
-.uiIcon16.icon-carat-1-sw { background-position: -80px 0; }
-.uiIcon16.icon-carat-1-w { background-position: -96px 0; }
-.uiIcon16.icon-carat-1-nw { background-position: -112px 0; }
+
+.uiIcon16.icon-carat-1-n { background-position: 0 0; }
+.uiIcon16.icon-carat-1-ne { background-position: -16px 0; }
+.uiIcon16.icon-carat-1-e { background-position: -32px 0; }
+.uiIcon16.icon-carat-1-se { background-position: -48px 0; }
+.uiIcon16.icon-carat-1-s { background-position: -64px 0; }
+.uiIcon16.icon-carat-1-sw { background-position: -80px 0; }
+.uiIcon16.icon-carat-1-w { background-position: -96px 0; }
+.uiIcon16.icon-carat-1-nw { background-position: -112px 0; }
.uiIcon16.icon-carat-2-n-s { background-position: -128px 0; }
.uiIcon16.icon-carat-2-e-w { background-position: -144px 0; }
-.uiIcon16.icon-triangle-1-n { background-position: 0 -16px; }
-.uiIcon16.icon-triangle-1-ne { background-position: -16px -16px; }
-.uiIcon16.icon-triangle-1-e { background-position: -32px -16px; }
-.uiIcon16.icon-triangle-1-se { background-position: -48px -16px; }
-.uiIcon16.icon-triangle-1-s { background-position: -64px -16px; }
-.uiIcon16.icon-triangle-1-sw { background-position: -80px -16px; }
-.uiIcon16.icon-triangle-1-w { background-position: -96px -16px; }
-.uiIcon16.icon-triangle-1-nw { background-position: -112px -16px; }
+
+.uiIcon16.icon-triangle-1-n { background-position: 0 -16px; }
+.uiIcon16.icon-triangle-1-ne { background-position: -16px -16px; }
+.uiIcon16.icon-triangle-1-e { background-position: -32px -16px; }
+.uiIcon16.icon-triangle-1-se { background-position: -48px -16px; }
+.uiIcon16.icon-triangle-1-s { background-position: -64px -16px; }
+.uiIcon16.icon-triangle-1-sw { background-position: -80px -16px; }
+.uiIcon16.icon-triangle-1-w { background-position: -96px -16px; }
+.uiIcon16.icon-triangle-1-nw { background-position: -112px -16px; }
.uiIcon16.icon-triangle-2-n-s { background-position: -128px -16px; }
.uiIcon16.icon-triangle-2-e-w { background-position: -144px -16px; }
-.uiIcon16.icon-arrow-1-n { background-position: 0 -32px; }
-.uiIcon16.icon-arrow-1-ne { background-position: -16px -32px; }
-.uiIcon16.icon-arrow-1-e { background-position: -32px -32px; }
-.uiIcon16.icon-arrow-1-se { background-position: -48px -32px; }
-.uiIcon16.icon-arrow-1-s { background-position: -64px -32px; }
-.uiIcon16.icon-arrow-1-sw { background-position: -80px -32px; }
-.uiIcon16.icon-arrow-1-w { background-position: -96px -32px; }
-.uiIcon16.icon-arrow-1-nw { background-position: -112px -32px; }
-.uiIcon16.icon-arrow-2-n-s { background-position: -128px -32px; }
+
+.uiIcon16.icon-arrow-1-n { background-position: 0 -32px; }
+.uiIcon16.icon-arrow-1-ne { background-position: -16px -32px; }
+.uiIcon16.icon-arrow-1-e { background-position: -32px -32px; }
+.uiIcon16.icon-arrow-1-se { background-position: -48px -32px; }
+.uiIcon16.icon-arrow-1-s { background-position: -64px -32px; }
+.uiIcon16.icon-arrow-1-sw { background-position: -80px -32px; }
+.uiIcon16.icon-arrow-1-w { background-position: -96px -32px; }
+.uiIcon16.icon-arrow-1-nw { background-position: -112px -32px; }
+.uiIcon16.icon-arrow-2-n-s { background-position: -128px -32px; }
.uiIcon16.icon-arrow-2-ne-sw { background-position: -144px -32px; }
-.uiIcon16.icon-arrow-2-e-w { background-position: -160px -32px; }
+.uiIcon16.icon-arrow-2-e-w { background-position: -160px -32px; }
.uiIcon16.icon-arrow-2-se-nw { background-position: -176px -32px; }
+
.uiIcon16.icon-arrowstop-1-n { background-position: -192px -32px; }
.uiIcon16.icon-arrowstop-1-e { background-position: -208px -32px; }
.uiIcon16.icon-arrowstop-1-s { background-position: -224px -32px; }
.uiIcon16.icon-arrowstop-1-w { background-position: -240px -32px; }
-.uiIcon16.icon-arrowthick-1-n { background-position: 0 -48px; }
-.uiIcon16.icon-arrowthick-1-ne { background-position: -16px -48px; }
-.uiIcon16.icon-arrowthick-1-e { background-position: -32px -48px; }
-.uiIcon16.icon-arrowthick-1-se { background-position: -48px -48px; }
-.uiIcon16.icon-arrowthick-1-s { background-position: -64px -48px; }
-.uiIcon16.icon-arrowthick-1-sw { background-position: -80px -48px; }
-.uiIcon16.icon-arrowthick-1-w { background-position: -96px -48px; }
-.uiIcon16.icon-arrowthick-1-nw { background-position: -112px -48px; }
-.uiIcon16.icon-arrowthick-2-n-s { background-position: -128px -48px; }
+
+.uiIcon16.icon-arrowthick-1-n { background-position: 0 -48px; }
+.uiIcon16.icon-arrowthick-1-ne { background-position: -16px -48px; }
+.uiIcon16.icon-arrowthick-1-e { background-position: -32px -48px; }
+.uiIcon16.icon-arrowthick-1-se { background-position: -48px -48px; }
+.uiIcon16.icon-arrowthick-1-s { background-position: -64px -48px; }
+.uiIcon16.icon-arrowthick-1-sw { background-position: -80px -48px; }
+.uiIcon16.icon-arrowthick-1-w { background-position: -96px -48px; }
+.uiIcon16.icon-arrowthick-1-nw { background-position: -112px -48px; }
+.uiIcon16.icon-arrowthick-2-n-s { background-position: -128px -48px; }
.uiIcon16.icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
-.uiIcon16.icon-arrowthick-2-e-w { background-position: -160px -48px; }
+.uiIcon16.icon-arrowthick-2-e-w { background-position: -160px -48px; }
.uiIcon16.icon-arrowthick-2-se-nw { background-position: -176px -48px; }
+
.uiIcon16.icon-arrowthickstop-1-n { background-position: -192px -48px; }
.uiIcon16.icon-arrowthickstop-1-e { background-position: -208px -48px; }
.uiIcon16.icon-arrowthickstop-1-s { background-position: -224px -48px; }
.uiIcon16.icon-arrowthickstop-1-w { background-position: -240px -48px; }
+
.uiIcon16.icon-arrowreturnthick-1-w { background-position: 0 -64px; }
.uiIcon16.icon-arrowreturnthick-1-n { background-position: -16px -64px; }
.uiIcon16.icon-arrowreturnthick-1-e { background-position: -32px -64px; }
.uiIcon16.icon-arrowreturnthick-1-s { background-position: -48px -64px; }
-.uiIcon16.icon-arrowreturn-1-w { background-position: -64px -64px; }
-.uiIcon16.icon-arrowreturn-1-n { background-position: -80px -64px; }
-.uiIcon16.icon-arrowreturn-1-e { background-position: -96px -64px; }
-.uiIcon16.icon-arrowreturn-1-s { background-position: -112px -64px; }
+
+.uiIcon16.icon-arrowreturn-1-w { background-position: -64px -64px; }
+.uiIcon16.icon-arrowreturn-1-n { background-position: -80px -64px; }
+.uiIcon16.icon-arrowreturn-1-e { background-position: -96px -64px; }
+.uiIcon16.icon-arrowreturn-1-s { background-position: -112px -64px; }
.uiIcon16.icon-arrowrefresh-1-w { background-position: -128px -64px; }
.uiIcon16.icon-arrowrefresh-1-n { background-position: -144px -64px; }
.uiIcon16.icon-arrowrefresh-1-e { background-position: -160px -64px; }
.uiIcon16.icon-arrowrefresh-1-s { background-position: -176px -64px; }
-.uiIcon16.icon-arrow-4 { background-position: 0 -80px; }
-.uiIcon16.icon-arrow-4-diag { background-position: -16px -80px; }
-.uiIcon16.icon-extlink { background-position: -32px -80px; }
-.uiIcon16.icon-newwin { background-position: -48px -80px; }
-.uiIcon16.icon-refresh { background-position: -64px -80px; }
-.uiIcon16.icon-shuffle { background-position: -80px -80px; }
-.uiIcon16.icon-transfer-e-w { background-position: -96px -80px; }
+
+.uiIcon16.icon-arrow-4 { background-position: 0 -80px; }
+.uiIcon16.icon-arrow-4-diag { background-position: -16px -80px; }
+.uiIcon16.icon-extlink { background-position: -32px -80px; }
+.uiIcon16.icon-newwin { background-position: -48px -80px; }
+.uiIcon16.icon-refresh { background-position: -64px -80px; }
+.uiIcon16.icon-shuffle { background-position: -80px -80px; }
+.uiIcon16.icon-transfer-e-w { background-position: -96px -80px; }
.uiIcon16.icon-transferthick-e-w { background-position: -112px -80px; }
-.uiIcon16.icon-radiation { background-position: -128px -80px; }
-.uiIcon16.icon-folder-collapsed { background-position: 0 -96px; }
-.uiIcon16.icon-folder-open { background-position: -16px -96px; }
-.uiIcon16.icon-document { background-position: -32px -96px; }
-.uiIcon16.icon-document-b { background-position: -48px -96px; }
-.uiIcon16.icon-note { background-position: -64px -96px; }
-.uiIcon16.icon-mail-closed { background-position: -80px -96px; }
-.uiIcon16.icon-mail-open { background-position: -96px -96px; }
-.uiIcon16.icon-suitcase { background-position: -112px -96px; }
-.uiIcon16.icon-comment { background-position: -128px -96px; }
-.uiIcon16.icon-person { background-position: -144px -96px; }
-.uiIcon16.icon-print { background-position: -160px -96px; }
-.uiIcon16.icon-trash { background-position: -176px -96px; }
-.uiIcon16.icon-locked { background-position: -192px -96px; }
-.uiIcon16.icon-unlocked { background-position: -208px -96px; }
-.uiIcon16.icon-bookmark { background-position: -224px -96px; }
-.uiIcon16.icon-tag { background-position: -240px -96px; }
-.uiIcon16.icon-home { background-position: 0 -112px; }
-.uiIcon16.icon-flag { background-position: -16px -112px; }
-.uiIcon16.icon-calendar { background-position: -32px -112px; }
-.uiIcon16.icon-cart { background-position: -48px -112px; }
-.uiIcon16.icon-pencil { background-position: -64px -112px; }
-.uiIcon16.icon-clock { background-position: -80px -112px; }
-.uiIcon16.icon-disk { background-position: -96px -112px; }
-.uiIcon16.icon-calculator { background-position: -112px -112px; }
-.uiIcon16.icon-zoomin { background-position: -128px -112px; }
-.uiIcon16.icon-zoomout { background-position: -144px -112px; }
-.uiIcon16.icon-search { background-position: -160px -112px; }
-.uiIcon16.icon-wrench { background-position: -176px -112px; }
-.uiIcon16.icon-gear { background-position: -192px -112px; }
-.uiIcon16.icon-heart { background-position: -208px -112px; }
-.uiIcon16.icon-star { background-position: -224px -112px; }
-.uiIcon16.icon-link { background-position: -240px -112px; }
-.uiIcon16.icon-cancel { background-position: 0 -128px; }
-.uiIcon16.icon-plus { background-position: -16px -128px; }
-.uiIcon16.icon-plusthick { background-position: -32px -128px; }
-.uiIcon16.icon-minus { background-position: -48px -128px; }
-.uiIcon16.icon-minusthick { background-position: -64px -128px; }
-.uiIcon16.icon-close { background-position: -80px -128px; }
-.uiIcon16.icon-closethick { background-position: -96px -128px; }
-.uiIcon16.icon-key { background-position: -112px -128px; }
-.uiIcon16.icon-lightbulb { background-position: -128px -128px; }
-.uiIcon16.icon-scissors { background-position: -144px -128px; }
-.uiIcon16.icon-clipboard { background-position: -160px -128px; }
-.uiIcon16.icon-copy { background-position: -176px -128px; }
-.uiIcon16.icon-contact { background-position: -192px -128px; }
-.uiIcon16.icon-image { background-position: -208px -128px; }
-.uiIcon16.icon-video { background-position: -224px -128px; }
-.uiIcon16.icon-script { background-position: -240px -128px; }
-.uiIcon16.icon-alert { background-position: 0 -144px; }
-.uiIcon16.icon-alert-red { background-image: url(uiIcons16Red.png); background-position: 0 -144px; }
-.uiIcon16.icon-info { background-position: -16px -144px; }
-.uiIcon16.icon-notice { background-position: -32px -144px; }
-.uiIcon16.icon-help { background-position: -48px -144px; }
-.uiIcon16.icon-check { background-position: -64px -144px; }
-.uiIcon16.icon-bullet { background-position: -80px -144px; }
-.uiIcon16.icon-radio-on { background-position: -96px -144px; }
-.uiIcon16.icon-radio-off { background-position: -112px -144px; }
-.uiIcon16.icon-pin-w { background-position: -128px -144px; }
-.uiIcon16.icon-pin-s { background-position: -144px -144px; }
-.uiIcon16.icon-phone { background-position: -160px -144px; }
-.uiIcon16.icon-list { background-position: -176px -144px; }
-.uiIcon16.icon-syringe { background-position: -192px -144px; }
-.uiIcon16.icon-play { background-position: 0 -160px; }
-.uiIcon16.icon-pause { background-position: -16px -160px; }
-.uiIcon16.icon-seek-next { background-position: -32px -160px; }
-.uiIcon16.icon-seek-prev { background-position: -48px -160px; }
-.uiIcon16.icon-seek-end { background-position: -64px -160px; }
-.uiIcon16.icon-seek-start { background-position: -80px -160px; }
+.uiIcon16.icon-radiation { background-position: -128px -80px; }
+.uiIcon16.icon-folder-collapsed { background-position: 0 -96px; }
+.uiIcon16.icon-folder-open { background-position: -16px -96px; }
+.uiIcon16.icon-document { background-position: -32px -96px; }
+.uiIcon16.icon-document-b { background-position: -48px -96px; }
+.uiIcon16.icon-note { background-position: -64px -96px; }
+.uiIcon16.icon-mail-closed { background-position: -80px -96px; }
+.uiIcon16.icon-mail-open { background-position: -96px -96px; }
+.uiIcon16.icon-suitcase { background-position: -112px -96px; }
+.uiIcon16.icon-comment { background-position: -128px -96px; }
+.uiIcon16.icon-person { background-position: -144px -96px; }
+.uiIcon16.icon-print { background-position: -160px -96px; }
+.uiIcon16.icon-trash { background-position: -176px -96px; }
+.uiIcon16.icon-locked { background-position: -192px -96px; }
+.uiIcon16.icon-unlocked { background-position: -208px -96px; }
+.uiIcon16.icon-bookmark { background-position: -224px -96px; }
+.uiIcon16.icon-tag { background-position: -240px -96px; }
+.uiIcon16.icon-home { background-position: 0 -112px; }
+.uiIcon16.icon-flag { background-position: -16px -112px; }
+.uiIcon16.icon-calendar { background-position: -32px -112px; }
+.uiIcon16.icon-cart { background-position: -48px -112px; }
+.uiIcon16.icon-pencil { background-position: -64px -112px; }
+.uiIcon16.icon-clock { background-position: -80px -112px; }
+.uiIcon16.icon-disk { background-position: -96px -112px; }
+.uiIcon16.icon-calculator { background-position: -112px -112px; }
+.uiIcon16.icon-zoomin { background-position: -128px -112px; }
+.uiIcon16.icon-zoomout { background-position: -144px -112px; }
+.uiIcon16.icon-search { background-position: -160px -112px; }
+.uiIcon16.icon-wrench { background-position: -176px -112px; }
+.uiIcon16.icon-gear { background-position: -192px -112px; }
+.uiIcon16.icon-heart { background-position: -208px -112px; }
+.uiIcon16.icon-star { background-position: -224px -112px; }
+.uiIcon16.icon-link { background-position: -240px -112px; }
+.uiIcon16.icon-cancel { background-position: 0 -128px; }
+.uiIcon16.icon-plus { background-position: -16px -128px; }
+.uiIcon16.icon-plusthick { background-position: -32px -128px; }
+.uiIcon16.icon-minus { background-position: -48px -128px; }
+.uiIcon16.icon-minusthick { background-position: -64px -128px; }
+.uiIcon16.icon-close { background-position: -80px -128px; }
+.uiIcon16.icon-closethick { background-position: -96px -128px; }
+.uiIcon16.icon-key { background-position: -112px -128px; }
+.uiIcon16.icon-lightbulb { background-position: -128px -128px; }
+.uiIcon16.icon-scissors { background-position: -144px -128px; }
+.uiIcon16.icon-clipboard { background-position: -160px -128px; }
+.uiIcon16.icon-copy { background-position: -176px -128px; }
+.uiIcon16.icon-contact { background-position: -192px -128px; }
+.uiIcon16.icon-image { background-position: -208px -128px; }
+.uiIcon16.icon-video { background-position: -224px -128px; }
+.uiIcon16.icon-script { background-position: -240px -128px; }
+.uiIcon16.icon-alert { background-position: 0 -144px; }
+.uiIcon16.icon-alert-red { background-image: url(uiIcons16Red.png); background-position: 0 -144px; }
+.uiIcon16.icon-info { background-position: -16px -144px; }
+.uiIcon16.icon-notice { background-position: -32px -144px; }
+.uiIcon16.icon-help { background-position: -48px -144px; }
+.uiIcon16.icon-check { background-position: -64px -144px; }
+.uiIcon16.icon-bullet { background-position: -80px -144px; }
+.uiIcon16.icon-radio-on { background-position: -96px -144px; }
+.uiIcon16.icon-radio-off { background-position: -112px -144px; }
+.uiIcon16.icon-pin-w { background-position: -128px -144px; }
+.uiIcon16.icon-pin-s { background-position: -144px -144px; }
+.uiIcon16.icon-phone { background-position: -160px -144px; }
+.uiIcon16.icon-list { background-position: -176px -144px; }
+.uiIcon16.icon-syringe { background-position: -192px -144px; }
+.uiIcon16.icon-play { background-position: 0 -160px; }
+.uiIcon16.icon-pause { background-position: -16px -160px; }
+.uiIcon16.icon-seek-next { background-position: -32px -160px; }
+.uiIcon16.icon-seek-prev { background-position: -48px -160px; }
+.uiIcon16.icon-seek-end { background-position: -64px -160px; }
+.uiIcon16.icon-seek-start { background-position: -80px -160px; }
+
/* uiIcon-seek-first is deprecated, use uiIcon-seek-start instead */
-.uiIcon16.icon-seek-first { background-position: -80px -160px; }
-.uiIcon16.icon-stop { background-position: -96px -160px; }
-.uiIcon16.icon-eject { background-position: -112px -160px; }
-.uiIcon16.icon-volume-off { background-position: -128px -160px; }
-.uiIcon16.icon-volume-on { background-position: -144px -160px; }
-.uiIcon16.icon-power { background-position: 0 -176px; }
-.uiIcon16.icon-signal-diag { background-position: -16px -176px; }
-.uiIcon16.icon-signal { background-position: -32px -176px; }
+.uiIcon16.icon-seek-first { background-position: -80px -160px; }
+.uiIcon16.icon-stop { background-position: -96px -160px; }
+.uiIcon16.icon-eject { background-position: -112px -160px; }
+.uiIcon16.icon-volume-off { background-position: -128px -160px; }
+.uiIcon16.icon-volume-on { background-position: -144px -160px; }
+.uiIcon16.icon-power { background-position: 0 -176px; }
+.uiIcon16.icon-signal-diag { background-position: -16px -176px; }
+.uiIcon16.icon-signal { background-position: -32px -176px; }
.uiIcon16.icon-signal-green { background-image: url(uiIcons16Green.png); background-position: -32px -176px; }
+
.uiIcon16.icon-battery-0 { background-position: -48px -176px; }
.uiIcon16.icon-battery-1 { background-position: -64px -176px; }
.uiIcon16.icon-battery-2 { background-position: -80px -176px; }
.uiIcon16.icon-battery-3 { background-position: -96px -176px; }
-.uiIcon16.icon-circle-plus { background-position: 0 -192px; }
-.uiIcon16.icon-circle-minus { background-position: -16px -192px; }
-.uiIcon16.icon-circle-close { background-position: -32px -192px; }
+
+.uiIcon16.icon-circle-plus { background-position: 0 -192px; }
+.uiIcon16.icon-circle-minus { background-position: -16px -192px; }
+.uiIcon16.icon-circle-close { background-position: -32px -192px; }
.uiIcon16.icon-circle-triangle-e { background-position: -48px -192px; }
.uiIcon16.icon-circle-triangle-s { background-position: -64px -192px; }
.uiIcon16.icon-circle-triangle-w { background-position: -80px -192px; }
.uiIcon16.icon-circle-triangle-n { background-position: -96px -192px; }
-.uiIcon16.icon-circle-arrow-e { background-position: -112px -192px; }
-.uiIcon16.icon-circle-arrow-s { background-position: -128px -192px; }
-.uiIcon16.icon-circle-arrow-w { background-position: -144px -192px; }
-.uiIcon16.icon-circle-arrow-n { background-position: -160px -192px; }
-.uiIcon16.icon-circle-zoomin { background-position: -176px -192px; }
-.uiIcon16.icon-circle-zoomout { background-position: -192px -192px; }
-.uiIcon16.icon-circle-check { background-position: -208px -192px; }
-.uiIcon16.icon-circlesmall-plus { background-position: 0 -208px; }
+.uiIcon16.icon-circle-arrow-e { background-position: -112px -192px; }
+.uiIcon16.icon-circle-arrow-s { background-position: -128px -192px; }
+.uiIcon16.icon-circle-arrow-w { background-position: -144px -192px; }
+.uiIcon16.icon-circle-arrow-n { background-position: -160px -192px; }
+.uiIcon16.icon-circle-zoomin { background-position: -176px -192px; }
+.uiIcon16.icon-circle-zoomout { background-position: -192px -192px; }
+.uiIcon16.icon-circle-check { background-position: -208px -192px; }
+
+.uiIcon16.icon-circlesmall-plus { background-position: 0 -208px; }
.uiIcon16.icon-circlesmall-minus { background-position: -16px -208px; }
.uiIcon16.icon-circlesmall-close { background-position: -32px -208px; }
-.uiIcon16.icon-squaresmall-plus { background-position: -48px -208px; }
+
+.uiIcon16.icon-squaresmall-plus { background-position: -48px -208px; }
.uiIcon16.icon-squaresmall-minus { background-position: -64px -208px; }
.uiIcon16.icon-squaresmall-close { background-position: -80px -208px; }
-.uiIcon16.icon-grip-dotted-vertical { background-position: 0 -224px; }
+
+.uiIcon16.icon-grip-dotted-vertical { background-position: 0 -224px; }
.uiIcon16.icon-grip-dotted-horizontal { background-position: -16px -224px; }
-.uiIcon16.icon-grip-solid-vertical { background-position: -32px -224px; }
-.uiIcon16.icon-grip-solid-horizontal { background-position: -48px -224px; }
-.uiIcon16.icon-gripsmall-diagonal-se { background-position: -64px -224px; }
-.uiIcon16.icon-grip-diagonal-se { background-position: -80px -224px; }
+.uiIcon16.icon-grip-solid-vertical { background-position: -32px -224px; }
+.uiIcon16.icon-grip-solid-horizontal { background-position: -48px -224px; }
+.uiIcon16.icon-gripsmall-diagonal-se { background-position: -64px -224px; }
+.uiIcon16.icon-grip-diagonal-se { background-position: -80px -224px; }
+
.uiIcon16.icon-batt_full { background-image: url(c_max.gif); background-position: 0px 0px }
.uiIcon16.icon-batt_disc { background-image: url(c_discharging.gif); background-position: 0px 0px }
.uiIcon16.icon-batt_chrg { background-image: url(c_charging.gif); background-position: 0px 0px }
-/*yes, defining these all like this is awful, but unless the javascript for icon handling is sorted out this will have to do*/
-.uiIcon16.icon-empty64 { background-image: url(uiIcons64.png); background-position: 0 0; width: 64px; height: 64px; }
-.uiIcon16.icon-phone64 { background-image: url(uiIcons64.png); background-position: -64px 0; width: 64px; height: 64px; }
-.uiIcon16.icon-comment64 { background-image: url(uiIcons64.png); background-position: -128px 0; width: 64px; height: 64px; }
-.uiIcon16.icon-list64 { background-image: url(uiIcons64.png); background-position: -192px 0; width: 64px; height: 64px; }
-.uiIcon16.icon-gear64 { background-image: url(uiIcons64.png); background-position: -256px 0; width: 64px; height: 64px; }
-.uiIcon16.icon-person64 { background-image: url(uiIcons64.png); background-position: -320px 0; width: 64px; height: 64px; }
-.uiIcon16.icon-newspaper64 { background-image: url(uiIcons64.png); background-position: 0 -64px; width: 64px; height: 64px; }
-.uiIcon16.icon-note64 { background-image: url(uiIcons64.png); background-position: -64px -64px; width: 64px; height: 64px; }
-.uiIcon16.icon-close64 { background-image: url(uiIcons64.png); background-position: -128px -64px; width: 64px; height: 64px; }
-.uiIcon16.icon-pill { background-image: url(pills32.png); width: 32px; height: 32px;}
-.uiIcon16.icon-pill.pill1 { background-position: 0 0; }
-.uiIcon16.icon-pill.pill2 { background-position: -32px 0; }
-.uiIcon16.icon-pill.pill3 { background-position: -64px 0; }
-.uiIcon16.icon-pill.pill4 { background-position: -96px 0; }
-.uiIcon16.icon-pill.pill5 { background-position: -128px 0; }
-.uiIcon16.icon-pill.pill6 { background-position: 0 -32px; }
-.uiIcon16.icon-pill.pill7 { background-position: -32px -32px; }
-.uiIcon16.icon-pill.pill8 { background-position: -64px -32px; }
-.uiIcon16.icon-pill.pill9 { background-position: -96px -32px; }
-.uiIcon16.icon-pill.pill10 { background-position: -128px -32px; }
-.uiIcon16.icon-pill.pill11 { background-position: 0 -64px; }
-.uiIcon16.icon-pill.pill12 { background-position: -32px -64px; }
-.uiIcon16.icon-pill.pill13 { background-position: -64px -64px; }
-.uiIcon16.icon-pill.pill14 { background-position: -96px -64px; }
-.uiIcon16.icon-pill.pill15 { background-position: -128px -64px; }
-.uiIcon16.icon-pill.pill16 { background-position: 0 -96px; }
-.uiIcon16.icon-pill.pill17 { background-position: -32px -96px; }
-.uiIcon16.icon-pill.pill18 { background-position: -64px -96px; }
-.uiIcon16.icon-pill.pill19 { background-position: -96px -96px; }
-.uiIcon16.icon-pill.pill20 { background-position: -128px -96px; }
+/*yes, defining these all like this is awful, but unless the javascript for icon handling is sorted out this will have to do*/
+.uiIcon16.icon-empty64 { background-image: url(uiIcons64.png); background-position: 0 0; width: 64px; height: 64px; }
+.uiIcon16.icon-phone64 { background-image: url(uiIcons64.png); background-position: -64px 0; width: 64px; height: 64px; }
+.uiIcon16.icon-comment64 { background-image: url(uiIcons64.png); background-position: -128px 0; width: 64px; height: 64px; }
+.uiIcon16.icon-list64 { background-image: url(uiIcons64.png); background-position: -192px 0; width: 64px; height: 64px; }
+.uiIcon16.icon-gear64 { background-image: url(uiIcons64.png); background-position: -256px 0; width: 64px; height: 64px; }
+.uiIcon16.icon-person64 { background-image: url(uiIcons64.png); background-position: -320px 0; width: 64px; height: 64px; }
+.uiIcon16.icon-newspaper64 { background-image: url(uiIcons64.png); background-position: 0 -64px; width: 64px; height: 64px; }
+.uiIcon16.icon-note64 { background-image: url(uiIcons64.png); background-position: -64px -64px; width: 64px; height: 64px; }
+.uiIcon16.icon-close64 { background-image: url(uiIcons64.png); background-position: -128px -64px; width: 64px; height: 64px; }
+.uiIcon16.icon-sun64 { background-image: url(uiIcons64.png); background-position: -192px -64px; width: 64px; height: 64px; }
+.uiIcon16.icon-external64 { background-image: url(uiIcons64.png); background-position: -256px -64px; width: 64px; height: 64px; }
+
+.uiIcon16.icon-pill { background-image: url(pills32.png); width: 32px; height: 32px;}
+.uiIcon16.icon-pill.pill1 { background-position: 0 0; }
+.uiIcon16.icon-pill.pill2 { background-position: -32px 0; }
+.uiIcon16.icon-pill.pill3 { background-position: -64px 0; }
+.uiIcon16.icon-pill.pill4 { background-position: -96px 0; }
+.uiIcon16.icon-pill.pill5 { background-position: -128px 0; }
+.uiIcon16.icon-pill.pill6 { background-position: 0 -32px; }
+.uiIcon16.icon-pill.pill7 { background-position: -32px -32px; }
+.uiIcon16.icon-pill.pill8 { background-position: -64px -32px; }
+.uiIcon16.icon-pill.pill9 { background-position: -96px -32px; }
+.uiIcon16.icon-pill.pill10 { background-position: -128px -32px; }
+.uiIcon16.icon-pill.pill11 { background-position: 0 -64px; }
+.uiIcon16.icon-pill.pill12 { background-position: -32px -64px; }
+.uiIcon16.icon-pill.pill13 { background-position: -64px -64px; }
+.uiIcon16.icon-pill.pill14 { background-position: -96px -64px; }
+.uiIcon16.icon-pill.pill15 { background-position: -128px -64px; }
+.uiIcon16.icon-pill.pill16 { background-position: 0 -96px; }
+.uiIcon16.icon-pill.pill17 { background-position: -32px -96px; }
+.uiIcon16.icon-pill.pill18 { background-position: -64px -96px; }
+.uiIcon16.icon-pill.pill19 { background-position: -96px -96px; }
+.uiIcon16.icon-pill.pill20 { background-position: -128px -96px; }
.uiIcon16.icon-pill.bottle1 { background-position: 0 -128px; }
.uiIcon16.icon-pill.bottle2 { background-position: -32px -128px; }
.uiIcon16.icon-pill.bottle3 { background-position: -64px -128px; }
diff --git a/nano/images/source/uiIcons64.xcf b/nano/images/source/uiIcons64.xcf
index c167e3b686..76c06ea1b6 100644
Binary files a/nano/images/source/uiIcons64.xcf and b/nano/images/source/uiIcons64.xcf differ
diff --git a/nano/images/uiIcons64.png b/nano/images/uiIcons64.png
index b58eb78f62..d849b3e869 100644
Binary files a/nano/images/uiIcons64.png and b/nano/images/uiIcons64.png differ
diff --git a/nano/templates/communicator.tmpl b/nano/templates/communicator.tmpl
index 6157f45d61..00f58378be 100644
--- a/nano/templates/communicator.tmpl
+++ b/nano/templates/communicator.tmpl
@@ -234,9 +234,39 @@ Used In File(s): code\game\objects\items\devices\communicator\communicator.dm
-
+
{{else data.currentTab == 6}}
+ Weather
+
+
+ {{:helper.link('Home', 'home', {'switch_tab' : 1})}}
+
+ {{for data.weather}}
+
+
+ {{:value.Planet}}:
+
+
+ Weather: {{:value.Weather}}, {{:value.Temperature - 273.15}}℃
+ High: {{:value.High - 273.15}}℃ | Low: {{:value.Low - 273.15}}℃
+ Wind: {{:value.Wind}}
+
+
+ {{empty}}
+
+
+ Error
+
+
+ No weather reports available. Please try again later.
+
+
+ {{/for}}
+
+
+
+{{else data.currentTab == 7}}
Settings
@@ -295,4 +325,12 @@ Used In File(s): code\game\objects\items\devices\communicator\communicator.dm
-{{/if}}
+
+
+{{else data.currentTab == 8}}
+
+
+
+
+
+{{/if}}
\ No newline at end of file
diff --git a/polaris.dme b/polaris.dme
index 5843688dca..e7a9b0c3f5 100644
--- a/polaris.dme
+++ b/polaris.dme
@@ -849,7 +849,12 @@
#include "code\game\objects\items\devices\uplink_random_lists.dm"
#include "code\game\objects\items\devices\violin.dm"
#include "code\game\objects\items\devices\whistle.dm"
+#include "code\game\objects\items\devices\communicator\cartridge.dm"
#include "code\game\objects\items\devices\communicator\communicator.dm"
+#include "code\game\objects\items\devices\communicator\integrated.dm"
+#include "code\game\objects\items\devices\communicator\messaging.dm"
+#include "code\game\objects\items\devices\communicator\phone.dm"
+#include "code\game\objects\items\devices\communicator\UI.dm"
#include "code\game\objects\items\devices\PDA\cart.dm"
#include "code\game\objects\items\devices\PDA\chatroom.dm"
#include "code\game\objects\items\devices\PDA\PDA.dm"