"
+
+ if(sendcooldown)
+ dat += "Transmitter arrays realigning. Please stand by. "
+
+ else
+ dat += "Send "
+ dat += "Currently sending: [tofax.name]"
+
+ else
+ if(sendcooldown)
+ dat += "Please insert paper to send to Central Command via secure connection.
"
+ dat += "Transmitter arrays realigning. Please stand by. "
+ else
+ dat += "Please insert paper to send to Central Command via secure connection.
"
+
+ else
+ dat += "Proper authentication is required to use this device.
"
+
+ if(tofax)
+ dat += "Remove Paper "
+
+ user << browse(dat, "window=copier")
+ onclose(user, "copier")
+ return
+
+ Topic(href, href_list)
+
+ if(href_list["send"])
+ if(tofax)
+ Centcomm_fax(tofax.info, tofax.name, usr)
+ usr << "Message transmitted."
+ sendcooldown = 1
+ spawn(3000) // three minute cooldown. might mess with this number a bit as time goes on
+ sendcooldown = 0
+
+ if(href_list["remove"])
+ if(tofax)
+ tofax.loc = usr.loc
+ usr.put_in_hands(tofax)
+ usr << "You take the paper out of \the [src]."
+ tofax = null
+
+ if(href_list["scan"])
+ if (scan)
+ if(ishuman(usr))
+ scan.loc = usr.loc
+ if(!usr.get_active_hand())
+ usr.put_in_hands(scan)
+ scan = null
+ else
+ scan.loc = src.loc
+ scan = null
+ else
+ var/obj/item/I = usr.get_active_hand()
+ if (istype(I, /obj/item/weapon/card/id))
+ usr.drop_item()
+ I.loc = src
+ scan = I
+ authenticated = 0
+
+ if(href_list["auth"])
+ if ( (!( authenticated ) && (scan)) )
+ if (check_access(scan))
+ authenticated = 1
+
+ if(href_list["logout"])
+ authenticated = 0
+
+ updateUsrDialog()
+
+ attackby(obj/item/O as obj, mob/user as mob)
+
+ if(istype(O, /obj/item/weapon/paper))
+ if(!tofax)
+ user.drop_item()
+ tofax = O
+ O.loc = src
+ user << "You insert the paper into \the [src]."
+ flick("bigscanner1", src)
+ updateUsrDialog()
+ else
+ user << "There is already something in \the [src]."
+
+ else if(istype(O, /obj/item/weapon/card/id))
+ var/obj/item/weapon/card/id/idcard = O
+
+ if(!scan)
+ usr.drop_item()
+ idcard.loc = src
+ scan = idcard
+
+ else if(istype(O, /obj/item/weapon/wrench))
+ playsound(loc, 'sound/items/Ratchet.ogg', 50, 1)
+ anchored = !anchored
+ user << "You [anchored ? "wrench" : "unwrench"] \the [src]."
+ return
+
+/proc/Centcomm_fax(var/sent, var/sentname, var/mob/Sender)
+
+ var/msg = "\blue CENTCOMM FAX: [key_name(Sender, 1)] (PP) (VV) (SM) (JMP) (CA) (RPLY): Receiving '[sentname]' via secure connection ... view message"
+ admins << msg
diff --git a/code/controllers/master_controller.dm b/code/controllers/master_controller.dm
index be684e3201..f880ad66d8 100644
--- a/code/controllers/master_controller.dm
+++ b/code/controllers/master_controller.dm
@@ -24,6 +24,7 @@ datum/controller/game_controller
var/objects_cost = 0
var/networks_cost = 0
var/powernets_cost = 0
+ var/nano_cost = 0
var/events_cost = 0
var/ticker_cost = 0
var/total_cost = 0
@@ -191,6 +192,13 @@ datum/controller/game_controller/proc/process()
sleep(breather_ticks)
+ //NANO UIS
+ timer = world.timeofday
+ process_nano()
+ nano_cost = (world.timeofday - timer) / 10
+
+ sleep(breather_ticks)
+
//EVENTS
timer = world.timeofday
process_events()
@@ -203,7 +211,7 @@ datum/controller/game_controller/proc/process()
ticker_cost = (world.timeofday - timer) / 10
//TIMING
- total_cost = air_cost + sun_cost + mobs_cost + diseases_cost + machines_cost + objects_cost + networks_cost + powernets_cost + events_cost + ticker_cost
+ total_cost = air_cost + sun_cost + mobs_cost + diseases_cost + machines_cost + objects_cost + networks_cost + powernets_cost + nano_cost + events_cost + ticker_cost
var/end_time = world.timeofday
if(end_time < start_time)
@@ -281,6 +289,16 @@ datum/controller/game_controller/proc/process_powernets()
continue
powernets.Cut(i,i+1)
+datum/controller/game_controller/proc/process_nano()
+ var/i = 1
+ while(i<=nanomanager.processing_uis.len)
+ var/datum/nanoui/ui = nanomanager.processing_uis[i]
+ if(ui && ui.src_object && ui.user)
+ ui.process()
+ i++
+ continue
+ nanomanager.processing_uis.Cut(i,i+1)
+
datum/controller/game_controller/proc/process_events()
last_thing_processed = /datum/event
var/i = 1
diff --git a/code/controllers/voting.dm b/code/controllers/voting.dm
index 7fc7f20b8f..9bb2197e6e 100644
--- a/code/controllers/voting.dm
+++ b/code/controllers/voting.dm
@@ -117,7 +117,8 @@ datum/controller/vote
var/text
if(winners.len > 0)
if(winners.len > 1)
- text = "Vote Tied Between:\n"
+ if(mode != "gamemode" || ticker.hide_mode == 0) // Here we are making sure we don't announce potential game modes
+ text = "Vote Tied Between:\n"
for(var/option in winners)
text += "\t[option]\n"
. = pick(winners)
@@ -125,8 +126,14 @@ datum/controller/vote
for(var/key in current_votes)
if(choices[current_votes[key]] == .)
round_voters += key // Keep track of who voted for the winning round.
+ if((mode == "gamemode" && . == "extended") || ticker.hide_mode == 0) // Announce Extended gamemode, but not other gamemodes
+ text += "Vote Result: [.]"
+ else
+ if(mode != "gamemode")
+ text += "Vote Result: [.]"
+ else
+ text += "The vote has ended." // What will be shown if it is a gamemode vote that isn't extended
- text += "Vote Result: [.]"
else
text += "Vote Result: Inconclusive - No Votes!"
log_vote(text)
diff --git a/code/datums/datumvars.dm b/code/datums/datumvars.dm
index 517c6d0931..dc3a3b36db 100644
--- a/code/datums/datumvars.dm
+++ b/code/datums/datumvars.dm
@@ -762,6 +762,7 @@ client
if(H.set_species(new_species))
usr << "Set species of [H] to [H.species]."
+ H.regenerate_icons()
else
usr << "Failed! Something went wrong."
diff --git a/code/game/area/Space Station 13 areas.dm b/code/game/area/Space Station 13 areas.dm
index 2ff200ae88..9133753e4e 100755
--- a/code/game/area/Space Station 13 areas.dm
+++ b/code/game/area/Space Station 13 areas.dm
@@ -497,26 +497,32 @@ proc/process_ghost_teleport_locs()
/area/vox_station/transit
name = "\improper hyperspace"
icon_state = "shuttle"
+ requires_power = 0
/area/vox_station/southwest_solars
name = "\improper aft port solars"
icon_state = "southwest"
+ requires_power = 0
/area/vox_station/northwest_solars
name = "\improper fore port solars"
icon_state = "northwest"
+ requires_power = 0
/area/vox_station/northeast_solars
name = "\improper fore starboard solars"
icon_state = "northeast"
+ requires_power = 0
/area/vox_station/southeast_solars
name = "\improper aft starboard solars"
icon_state = "southeast"
+ requires_power = 0
/area/vox_station/mining
name = "\improper nearby mining asteroid"
icon_state = "north"
+ requires_power = 0
//PRISON
/area/prison
diff --git a/code/game/gamemodes/gameticker.dm b/code/game/gamemodes/gameticker.dm
index 78a8fe6938..4d83aa5eef 100644
--- a/code/game/gamemodes/gameticker.dm
+++ b/code/game/gamemodes/gameticker.dm
@@ -10,7 +10,7 @@ var/global/datum/controller/gameticker/ticker
var/const/restart_timeout = 600
var/current_state = GAME_STATE_PREGAME
- var/hide_mode = 0
+ var/hide_mode = 1
var/datum/game_mode/mode = null
var/event_time = null
var/event = 0
diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm
index 3d15448513..12ac544784 100644
--- a/code/game/gamemodes/objective.dm
+++ b/code/game/gamemodes/objective.dm
@@ -834,6 +834,8 @@ datum/objective/heist/loot
for(var/obj/O in locate(/area/shuttle/vox/station))
if(istype(O,target)) total_amount++
+ for(var/obj/I in O.contents)
+ if(istype(I,target)) total_amount++
if(total_amount >= target_amount) return 1
var/datum/game_mode/heist/H = ticker.mode
@@ -881,10 +883,17 @@ datum/objective/heist/salvage
var/total_amount = 0
for(var/obj/item/O in locate(/area/shuttle/vox/station))
+
+ var/obj/item/stack/sheet/S
if(istype(O,/obj/item/stack/sheet))
if(O.name == target)
- var/obj/item/stack/sheet/S = O
+ S = O
total_amount += S.amount
+ for(var/obj/I in O.contents)
+ if(istype(I,/obj/item/stack/sheet))
+ if(I.name == target)
+ S = I
+ total_amount += S.amount
var/datum/game_mode/heist/H = ticker.mode
for(var/datum/mind/raider in H.raiders)
diff --git a/code/game/jobs/job/assistant.dm b/code/game/jobs/job/assistant.dm
index 2706d44aa4..270a4e3867 100644
--- a/code/game/jobs/job/assistant.dm
+++ b/code/game/jobs/job/assistant.dm
@@ -15,6 +15,10 @@
if(!H) return 0
H.equip_to_slot_or_del(new /obj/item/clothing/under/color/grey(H), slot_w_uniform)
H.equip_to_slot_or_del(new /obj/item/clothing/shoes/black(H), slot_shoes)
+ if(H.backbag == 1)
+ H.equip_to_slot_or_del(new /obj/item/weapon/storage/box/survival(H), slot_r_hand)
+ else
+ H.equip_to_slot_or_del(new /obj/item/weapon/storage/box/survival(H.back), slot_in_backpack)
return 1
/datum/job/assistant/get_access()
diff --git a/code/game/jobs/job/civilian.dm b/code/game/jobs/job/civilian.dm
index a7e24bf1e2..8d9e0875dc 100644
--- a/code/game/jobs/job/civilian.dm
+++ b/code/game/jobs/job/civilian.dm
@@ -342,6 +342,7 @@
equip(var/mob/living/carbon/human/H)
if(!H) return 0
+ H.equip_to_slot_or_del(new /obj/item/device/radio/headset/headset_sec(H), slot_ears)
switch(H.backbag)
if(2) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack(H), slot_back)
if(3) H.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/satchel_norm(H), slot_back)
diff --git a/code/game/machinery/OpTable.dm b/code/game/machinery/OpTable.dm
index 7078d9df90..e1aa0ebfa7 100644
--- a/code/game/machinery/OpTable.dm
+++ b/code/game/machinery/OpTable.dm
@@ -91,10 +91,10 @@
/obj/machinery/optable/proc/check_victim()
if(locate(/mob/living/carbon/human, src.loc))
- var/mob/M = locate(/mob/living/carbon/human, src.loc)
+ var/mob/living/carbon/human/M = locate(/mob/living/carbon/human, src.loc)
if(M.resting)
src.victim = M
- icon_state = "table2-active"
+ icon_state = M.pulse ? "table2-active" : "table2-idle"
return 1
src.victim = null
icon_state = "table2-idle"
@@ -103,22 +103,40 @@
/obj/machinery/optable/process()
check_victim()
-/obj/machinery/optable/attackby(obj/item/weapon/W as obj, mob/living/carbon/user as mob)
+/obj/machinery/optable/proc/take_victim(mob/living/carbon/human/H, mob/living/carbon/user as mob)
+ if (H == user)
+ user.visible_message("[user] climbs on the operating table.","You climb on the operating table.")
+ else
+ visible_message("\red [H] has been laid on the operating table by [user].", 3)
+ if (H.client)
+ H.client.perspective = EYE_PERSPECTIVE
+ H.client.eye = src
+ H.resting = 1
+ H.loc = src.loc
+ for(var/obj/O in src)
+ O.loc = src.loc
+ src.add_fingerprint(user)
+ icon_state = H.pulse ? "table2-active" : "table2-idle"
+ src.victim = H
+/obj/machinery/optable/verb/climb_on()
+ set name = "Climb On Table"
+ set category = "Object"
+ set src in oview(1)
+
+ if(usr.stat || !ishuman(usr) || usr.buckled || usr.restrained())
+ return
+
+ if(src.victim)
+ usr << "\blue The table is already occupied!"
+ return
+
+ take_victim(usr,usr)
+
+/obj/machinery/optable/attackby(obj/item/weapon/W as obj, mob/living/carbon/user as mob)
if (istype(W, /obj/item/weapon/grab))
- if(ismob(W:affecting))
- var/mob/M = W:affecting
- if (M.client)
- M.client.perspective = EYE_PERSPECTIVE
- M.client.eye = src
- M.resting = 1
- M.loc = src.loc
- visible_message("\red [M] has been laid on the operating table by [user].", 3)
- for(var/obj/O in src)
- O.loc = src.loc
- src.add_fingerprint(user)
- icon_state = "table2-active"
- src.victim = M
+ if(ishuman(W:affecting))
+ take_victim(W:affecting,usr)
del(W)
return
user.drop_item()
diff --git a/code/game/machinery/computer/camera.dm b/code/game/machinery/computer/camera.dm
index 5352d3d833..1b21ecac95 100644
--- a/code/game/machinery/computer/camera.dm
+++ b/code/game/machinery/computer/camera.dm
@@ -200,4 +200,4 @@
name = "Engineering Cameras"
desc = "Used to monitor fires and breaches."
icon_state = "engineeringcameras"
- network = list("Power Alarms","Atmosphere Alarms","Fire Alarms")
\ No newline at end of file
+ network = list("Engineering","Power Alarms","Atmosphere Alarms","Fire Alarms")
diff --git a/code/game/machinery/computer/vox_shuttle.dm b/code/game/machinery/computer/vox_shuttle.dm
index fa9d05d7b9..6854949cd2 100644
--- a/code/game/machinery/computer/vox_shuttle.dm
+++ b/code/game/machinery/computer/vox_shuttle.dm
@@ -1,8 +1,37 @@
-#define VOX_SHUTTLE_MOVE_TIME 260
-#define VOX_SHUTTLE_COOLDOWN 460
+#define VOX_SHUTTLE_MOVE_TIME 400
+#define VOX_SHUTTLE_COOLDOWN 1200
//Copied from Syndicate shuttle.
var/global/vox_shuttle_location
+var/global/announce_vox_departure = 1 //Stealth systems - give an announcement or not.
+
+/obj/machinery/computer/vox_stealth
+ name = "skipjack cloaking field terminal"
+ icon = 'icons/obj/computer.dmi'
+ icon_state = "syndishuttle"
+ req_access = list(access_syndicate)
+
+/obj/machinery/computer/vox_stealth/attackby(obj/item/I as obj, mob/user as mob)
+ return attack_hand(user)
+
+/obj/machinery/computer/vox_stealth/attack_ai(mob/user as mob)
+ return attack_hand(user)
+
+/obj/machinery/computer/vox_stealth/attack_paw(mob/user as mob)
+ return attack_hand(user)
+
+/obj/machinery/computer/vox_stealth/attack_hand(mob/user as mob)
+ if(!allowed(user))
+ user << "\red Access Denied"
+ return
+
+ if(announce_vox_departure)
+ user << "\red Shuttle stealth systems have been activated. The Exodus will not be warned of our arrival."
+ announce_vox_departure = 0
+ else
+ user << "\red Shuttle stealth systems have been deactivated. The Exodus will be warned of our arrival."
+ announce_vox_departure = 1
+
/obj/machinery/computer/vox_station
name = "vox skipjack terminal"
@@ -24,6 +53,12 @@ var/global/vox_shuttle_location
var/area/dest_location = locate(destination)
if(curr_location == dest_location) return
+ if(announce_vox_departure)
+ if(curr_location == locate(/area/shuttle/vox/station))
+ command_alert("Attention, Exodus, we just tracked a small target bypassing our defensive perimeter. Can't fire on it without hitting the station - you've got incoming visitors, like it or not.", "NSV Icarus")
+ else if(dest_location == locate(/area/shuttle/vox/station))
+ command_alert("Your guests are pulling away, Exodus - moving too fast for us to draw a bead on them. Looks like they're heading out of Tau Ceti at a rapid clip.", "NSV Icarus")
+
moving = 1
lastMove = world.time
diff --git a/code/game/machinery/cryo.dm b/code/game/machinery/cryo.dm
index abe6341230..163c5f6bae 100644
--- a/code/game/machinery/cryo.dm
+++ b/code/game/machinery/cryo.dm
@@ -1,11 +1,11 @@
/obj/machinery/atmospherics/unary/cryo_cell
- name = "cryo cell"
+ name = "Cryo Cell"
icon = 'icons/obj/cryogenics.dmi'
icon_state = "cell-off"
density = 1
anchored = 1.0
layer = 2.8
-
+
var/on = 0
var/temperature_archived
var/mob/living/carbon/occupant = null
@@ -61,37 +61,66 @@
go_out()
return
-/obj/machinery/atmospherics/unary/cryo_cell/attack_hand(mob/user as mob)
- user.set_machine(src)
- var/beaker_text = ""
- var/health_text = ""
- var/temp_text = ""
- if(occupant)
- if(occupant.health <= -100)
- health_text = "Dead"
- else if(occupant.health < 0)
- health_text = "[round(occupant.health,0.1)]"
- else
- health_text = "[round(occupant.health,0.1)]"
- if(air_contents.temperature > T0C)
- temp_text = "[air_contents.temperature]"
+/obj/machinery/atmospherics/unary/cryo_cell/attack_hand(mob/user)
+ ui_interact(user)
+
+/obj/machinery/atmospherics/unary/cryo_cell/ui_interact(mob/user, ui_key = "main")
+
+ var/data[0]
+ data["isOperating"] = on
+
+ data["hasOccupant"] = occupant ? 1 : 0
+
+ var/occupantData[0]
+ if (!occupant)
+ occupantData["name"] = null
+ occupantData["stat"] = null
+ occupantData["health"] = null
+ occupantData["bruteLoss"] = null
+ occupantData["oxyLoss"] = null
+ occupantData["toxLoss"] = null
+ occupantData["fireLoss"] = null
+ occupantData["bodyTemperature"] = null
+ else
+ occupantData["name"] = occupant.name
+ occupantData["stat"] = occupant.stat
+ occupantData["health"] = round(occupant.health)
+ occupantData["bruteLoss"] = round(occupant.getBruteLoss())
+ occupantData["oxyLoss"] = round(occupant.getOxyLoss())
+ occupantData["toxLoss"] = round(occupant.getToxLoss())
+ occupantData["fireLoss"] = round(occupant.getFireLoss())
+ occupantData["bodyTemperature"] = round(occupant.bodytemperature)
+ data["occupant"] = occupantData;
+
+ data["cellTemperature"] = round(air_contents.temperature)
+ data["cellTemperatureStatus"] = "good"
+ if(air_contents.temperature > T0C) // if greater than 273.15 kelvin (0 celcius)
+ data["cellTemperatureStatus"] = "bad"
else if(air_contents.temperature > 225)
- temp_text = "[air_contents.temperature]"
+ data["cellTemperatureStatus"] = "average"
+
+ data["isBeakerLoaded"] = beaker ? 1 : 0
+ var beakerContents[0]
+ if(beaker && beaker:reagents && beaker:reagents.reagent_list.len)
+ for(var/datum/reagent/R in beaker:reagents.reagent_list)
+ beakerContents.Add(list(list("name" = R.name, "volume" = R.volume))) // list in a list because Byond merges the first list...
+ data["beakerContents"] = beakerContents
+
+ //user << list2json(data)
+
+ var/datum/nanoui/ui = nanomanager.get_open_ui(user, src, ui_key)
+ if (!ui)
+ ui = new(user, src, ui_key, "cryo.tmpl", "Cryo Cell Control System", 520, 410)
+ // When the UI is first opened this is the data it will use
+ ui.set_initial_data(data)
+ ui.open()
+ // Auto update every Master Controller tick
+ ui.set_auto_update(1)
else
- temp_text = "[air_contents.temperature]"
- if(beaker)
- beaker_text = "Beaker:Eject"
- else
- beaker_text = "Beaker: No beaker loaded"
- var/dat = {"Cryo cell control system
- Current cell temperature: [temp_text]K
- Cryo status: [ on ? "OffOn" : "OffOn"]
- [beaker_text]
- Current occupant: [occupant ? " Name: [occupant] Health: [health_text] Oxygen deprivation: [round(occupant.getOxyLoss(),0.1)] Brute damage: [round(occupant.getBruteLoss(),0.1)] Fire damage: [round(occupant.getFireLoss(),0.1)] Toxin damage: [round(occupant.getToxLoss(),0.1)] Body temperature: [occupant.bodytemperature] Heartbeat rate: [occupant.get_pulse(GETPULSE_TOOL)]" : "None"]
- "}
- user.set_machine(src)
- user << browse(dat, "window=cryo")
- onclose(user, "cryo")
+ // The UI is already open so push the new data to it
+ ui.push_data(data)
+ return
+ //user.set_machine(src)
/obj/machinery/atmospherics/unary/cryo_cell/Topic(href, href_list)
if ((get_dist(src, usr) <= 1) || istype(usr, /mob/living/silicon/ai))
@@ -102,9 +131,9 @@
if (beaker)
var/obj/item/weapon/reagent_containers/glass/B = beaker
B.loc = get_step(loc, SOUTH)
- beaker = null
+ beaker = null
- updateUsrDialog()
+ nanomanager.update_uis(src) // update all UIs attached to this object
add_fingerprint(usr)
return
diff --git a/code/game/machinery/podmen.dm b/code/game/machinery/podmen.dm
index da3fbb7234..7654bda964 100644
--- a/code/game/machinery/podmen.dm
+++ b/code/game/machinery/podmen.dm
@@ -113,7 +113,7 @@ Growing it to term with nothing injected will grab a ghost from the observers. *
found_player = 1
- var/mob/living/carbon/human/podman = new /mob/living/carbon/human(parent.loc)
+ var/mob/living/carbon/monkey/diona/podman = new(parent.loc)
podman.ckey = player.ckey
if(player.mob && player.mob.mind)
@@ -122,14 +122,9 @@ Growing it to term with nothing injected will grab a ghost from the observers. *
if(realName)
podman.real_name = realName
else
- podman.real_name = "Diona [rand(0,999)]"
+ podman.real_name = "diona nymph ([rand(100,999)])"
- podman.gender = NEUTER
- podman.dna = new /datum/dna()
podman.dna.real_name = podman.real_name
- podman.set_species("Diona")
- podman.dna.mutantrace = "plant"
- podman.update_mutantrace()
// Update mode specific HUD icons.
switch(ticker.mode.name)
@@ -158,5 +153,5 @@ Growing it to term with nothing injected will grab a ghost from the observers. *
if (newname != "")
podman.real_name = newname
- parent.visible_message("\blue The pod disgorges a fully-formed plant person!")
+ parent.visible_message("\blue The pod disgorges a fully-formed plant creature!")
parent.update_tray()
diff --git a/code/game/machinery/telecomms/broadcaster.dm b/code/game/machinery/telecomms/broadcaster.dm
index 7302ed81b6..d90986a5dd 100644
--- a/code/game/machinery/telecomms/broadcaster.dm
+++ b/code/game/machinery/telecomms/broadcaster.dm
@@ -473,7 +473,6 @@ var/message_delay = 0 // To make sure restarting the recentmessages list is kept
R.show_message(rendered, 2)
/* --- Process all the mobs that heard the voice normally (did not understand) --- */
- // Does not display message; displayes the mob's voice_message (ie "chimpers")
if (length(heard_voice))
var/rendered = "[part_a][vname][part_b][vmessage][part_c]"
diff --git a/code/game/objects/items/devices/PDA/PDA.dm b/code/game/objects/items/devices/PDA/PDA.dm
index 378d08f880..e494baf024 100755
--- a/code/game/objects/items/devices/PDA/PDA.dm
+++ b/code/game/objects/items/devices/PDA/PDA.dm
@@ -1021,6 +1021,8 @@ var/global/list/obj/item/device/pda/PDAs = list()
switch(scanmode)
if(3)
+ if(!isobj(A))
+ return
if(!isnull(A.reagents))
if(A.reagents.reagent_list.len > 0)
var/reagents_length = A.reagents.reagent_list.len
diff --git a/code/game/objects/items/devices/radio/radio.dm b/code/game/objects/items/devices/radio/radio.dm
index 422a27897c..45420280f6 100644
--- a/code/game/objects/items/devices/radio/radio.dm
+++ b/code/game/objects/items/devices/radio/radio.dm
@@ -324,7 +324,7 @@ var/GLOBAL_RADIO_TYPE = 1 // radio type to use
"name" = displayname, // the mob's display name
"job" = jobname, // the mob's job
"key" = mobkey, // the mob's key
- "vmessage" = M.voice_message, // the message to display if the voice wasn't understood
+ "vmessage" = pick(M.speak_emote), // the message to display if the voice wasn't understood
"vname" = M.voice_name, // the name to display if the voice wasn't understood
"vmask" = voicemask, // 1 if the mob is using a voice gas mask
@@ -381,7 +381,7 @@ var/GLOBAL_RADIO_TYPE = 1 // radio type to use
"name" = displayname, // the mob's display name
"job" = jobname, // the mob's job
"key" = mobkey, // the mob's key
- "vmessage" = M.voice_message, // the message to display if the voice wasn't understood
+ "vmessage" = pick(M.speak_emote), // the message to display if the voice wasn't understood
"vname" = M.voice_name, // the name to display if the voice wasn't understood
"vmask" = voicemask, // 1 if the mob is using a voice gas mas
@@ -414,7 +414,7 @@ var/GLOBAL_RADIO_TYPE = 1 // radio type to use
//THIS IS TEMPORARY.
if(!connection) return //~Carn
- Broadcast_Message(connection, M, voicemask, M.voice_message,
+ Broadcast_Message(connection, M, voicemask, pick(M.speak_emote),
src, message, displayname, jobname, real_name, M.voice_name,
filter_type, signal.data["compression"], list(position.z), connection.frequency)
@@ -477,10 +477,7 @@ var/GLOBAL_RADIO_TYPE = 1 // radio type to use
else
heard_normal += R
else
- if (M.voice_message)
- heard_voice += R
- else
- heard_garbled += R
+ heard_voice += R
if (length(heard_masked) || length(heard_normal) || length(heard_voice) || length(heard_garbled))
var/part_a = ""
@@ -574,11 +571,11 @@ var/GLOBAL_RADIO_TYPE = 1 // radio type to use
R.show_message(rendered, 2)
if (length(heard_voice))
- var/rendered = "[part_a][M.voice_name][part_b][M.voice_message][part_c]"
+ var/rendered = "[part_a][M.voice_name][part_b][pick(M.speak_emote)][part_c]"
for (var/mob/R in heard_voice)
if(istype(R, /mob/living/silicon/ai))
- R.show_message("[part_a][M.voice_name] ([eqjobname]) [part_b][M.voice_message][part_c]", 2)
+ R.show_message("[part_a][M.voice_name] ([eqjobname]) [part_b][pick(M.speak_emote)][part_c]", 2)
else
R.show_message(rendered, 2)
diff --git a/code/game/objects/items/devices/scanners.dm b/code/game/objects/items/devices/scanners.dm
index bef800f0fa..a5a4093028 100644
--- a/code/game/objects/items/devices/scanners.dm
+++ b/code/game/objects/items/devices/scanners.dm
@@ -6,7 +6,7 @@ HEALTH ANALYZER
GAS ANALYZER
PLANT ANALYZER
MASS SPECTROMETER
-
+REAGENT SCANNER
*/
/obj/item/device/t_scanner
name = "T-ray scanner"
@@ -337,3 +337,61 @@ MASS SPECTROMETER
icon_state = "adv_spectrometer"
details = 1
origin_tech = "magnets=4;biotech=2"
+
+/obj/item/device/reagent_scanner
+ name = "reagent scanner"
+ desc = "A hand-held reagent scanner which identifies chemical agents."
+ icon_state = "spectrometer"
+ item_state = "analyzer"
+ w_class = 2.0
+ flags = FPRINT | TABLEPASS | CONDUCT
+ slot_flags = SLOT_BELT
+ throwforce = 5
+ throw_speed = 4
+ throw_range = 20
+ m_amt = 30
+ g_amt = 20
+ origin_tech = "magnets=2;biotech=2"
+ var/details = 0
+ var/recent_fail = 0
+
+/obj/item/device/reagent_scanner/afterattack(obj/O, mob/user as mob)
+ if (user.stat)
+ return
+ if (!(istype(user, /mob/living/carbon/human) || ticker) && ticker.mode.name != "monkey")
+ user << "\red You don't have the dexterity to do this!"
+ return
+ if(!istype(O))
+ return
+ if (crit_fail)
+ user << "\red This device has critically failed and is no longer functional!"
+ return
+
+ if(!isnull(O.reagents))
+ var/dat = ""
+ if(O.reagents.reagent_list.len > 0)
+ var/one_percent = O.reagents.total_volume / 100
+ for (var/datum/reagent/R in O.reagents.reagent_list)
+ if(prob(reliability))
+ dat += "\n \t \blue [R][details ? ": [R.volume / one_percent]%" : ""]"
+ recent_fail = 0
+ else if(recent_fail)
+ crit_fail = 1
+ dat = null
+ break
+ else
+ recent_fail = 1
+ if(dat)
+ user << "\blue Chemicals found: [dat]"
+ else
+ user << "\blue No active chemical agents found in [O]."
+ else
+ user << "\blue No significant chemical agents found in [O]."
+
+ return
+
+/obj/item/device/reagent_scanner/adv
+ name = "advanced reagent scanner"
+ icon_state = "adv_spectrometer"
+ details = 1
+ origin_tech = "magnets=4;biotech=2"
\ No newline at end of file
diff --git a/code/game/objects/items/robot/robot_upgrades.dm b/code/game/objects/items/robot/robot_upgrades.dm
index 5011f5346e..315d9103dd 100644
--- a/code/game/objects/items/robot/robot_upgrades.dm
+++ b/code/game/objects/items/robot/robot_upgrades.dm
@@ -30,10 +30,9 @@
R.uneq_all()
R.hands.icon_state = "nomod"
R.icon_state = "robot"
- R.base_icon = "robot"
del(R.module)
R.module = null
- R.camera.network.Remove(list("Medical","MINE"))
+ R.camera.network.Remove(list("Engineering","Medical","MINE"))
R.updatename("Default")
R.status_flags |= CANPUSH
R.updateicon()
diff --git a/code/game/objects/items/weapons/cards_ids.dm b/code/game/objects/items/weapons/cards_ids.dm
index 2b932dc969..094888c92c 100644
--- a/code/game/objects/items/weapons/cards_ids.dm
+++ b/code/game/objects/items/weapons/cards_ids.dm
@@ -54,6 +54,14 @@
/*
* ID CARDS
*/
+
+/obj/item/weapon/card/emag_broken
+ desc = "It's a card with a magnetic strip attached to some circuitry. It looks too busted to be used for anything but salvage."
+ name = "broken cryptographic sequencer"
+ icon_state = "emag"
+ item_state = "card-id"
+ origin_tech = "magnets=2;syndicate=2"
+
/obj/item/weapon/card/emag
desc = "It's a card with a magnetic strip attached to some circuitry."
name = "cryptographic sequencer"
@@ -61,6 +69,56 @@
item_state = "card-id"
origin_tech = "magnets=2;syndicate=2"
var/uses = 10
+ // List of devices that cost a use to emag.
+ var/list/devices = list(
+ /obj/item/robot_parts,
+ /obj/item/weapon/storage/lockbox,
+ /obj/item/weapon/storage/secure,
+ /obj/item/weapon/circuitboard,
+ /obj/item/device/eftpos,
+ /obj/item/device/lightreplacer,
+ /obj/item/device/taperecorder,
+ /obj/item/device/hailer,
+ /obj/item/clothing/tie/holobadge,
+ /obj/structure/closet/crate/secure,
+ /obj/structure/closet/secure_closet,
+ /obj/machinery/librarycomp,
+ /obj/machinery/computer,
+ /obj/machinery/power,
+ /obj/machinery/suspension_gen,
+ /obj/machinery/shield_capacitor,
+ /obj/machinery/shield_gen,
+ /obj/machinery/zero_point_emitter,
+ /obj/machinery/clonepod,
+ /obj/machinery/deployable,
+ /obj/machinery/door_control,
+ /obj/machinery/porta_turret,
+ /obj/machinery/shieldgen,
+ /obj/machinery/turretid,
+ /obj/machinery/vending,
+ /obj/machinery/bot,
+ /obj/machinery/door,
+ /obj/machinery/telecomms,
+ /obj/machinery/mecha_part_fabricator
+ )
+
+
+/obj/item/weapon/card/emag/afterattack(var/obj/item/weapon/O as obj, mob/user as mob)
+
+ for(var/type in devices)
+ if(istype(O,type))
+ uses--
+ break
+
+ if(uses<1)
+ user.visible_message("[src] fizzles and sparks - it seems it's been used once too often, and is now broken.")
+ user.drop_item()
+ var/obj/item/weapon/card/emag_broken/junk = new(user.loc)
+ junk.add_fingerprint(user)
+ del(src)
+ return
+
+ ..()
/obj/item/weapon/card/id
name = "identification card"
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm b/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm
index 1574244d52..95e50cf6fe 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm
@@ -69,7 +69,7 @@
/obj/structure/closet/secure_closet/engineering_welding
name = "Welding Supplies"
- req_access = list(access_engine_equip)
+ req_access = list(access_construction)
icon_state = "secureengweld1"
icon_closed = "secureengweld"
icon_locked = "secureengweld1"
@@ -113,14 +113,42 @@
new /obj/item/clothing/tie/storage/brown_vest(src)
else
new /obj/item/clothing/tie/storage/webbing(src)
- new /obj/item/clothing/under/rank/engineer(src)
- new /obj/item/clothing/shoes/orange(src)
new /obj/item/weapon/storage/toolbox/mechanical(src)
-// new /obj/item/weapon/cartridge/engineering(src)
new /obj/item/device/radio/headset/headset_eng(src)
new /obj/item/clothing/suit/storage/hazardvest(src)
new /obj/item/clothing/mask/gas(src)
new /obj/item/clothing/glasses/meson(src)
- new /obj/item/taperoll/engineering(src)
+ new /obj/item/weapon/cartridge/engineering(src)
new /obj/item/taperoll/engineering(src)
return
+/obj/structure/closet/secure_closet/atmos_personal
+ name = "Technician's Locker"
+ req_access = list(access_atmospherics)
+ icon_state = "secureeng1"
+ icon_closed = "secureeng"
+ icon_locked = "secureeng1"
+ icon_opened = "secureengopen"
+ icon_broken = "secureengbroken"
+ icon_off = "secureengoff"
+
+
+ New()
+ ..()
+ sleep(2)
+ if(prob(50))
+ new /obj/item/weapon/storage/backpack/industrial(src)
+ else
+ new /obj/item/weapon/storage/backpack/satchel_eng(src)
+ if (prob(70))
+ new /obj/item/clothing/tie/storage/brown_vest(src)
+ else
+ new /obj/item/clothing/tie/storage/webbing(src)
+ new /obj/item/clothing/suit/fire/firefighter(src)
+ new /obj/item/device/flashlight(src)
+ new /obj/item/weapon/extinguisher(src)
+ new /obj/item/device/radio/headset/headset_eng(src)
+ new /obj/item/clothing/suit/storage/hazardvest(src)
+ new /obj/item/clothing/mask/gas(src)
+ new /obj/item/weapon/cartridge/atmos(src)
+ new /obj/item/taperoll/engineering(src)
+ return
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/security.dm b/code/game/objects/structures/crates_lockers/closets/secure/security.dm
index 50d98f0c60..011f19f198 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/security.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/security.dm
@@ -106,7 +106,9 @@
new /obj/item/clothing/head/helmet/HoS(src)
new /obj/item/clothing/suit/armor/vest(src)
new /obj/item/clothing/under/rank/head_of_security/jensen(src)
+ new /obj/item/clothing/under/rank/head_of_security/corp(src)
new /obj/item/clothing/suit/armor/hos/jensen(src)
+ new /obj/item/clothing/suit/armor/hos(src)
new /obj/item/clothing/head/helmet/HoS/dermal(src)
new /obj/item/weapon/cartridge/hos(src)
new /obj/item/device/radio/headset/heads/hos(src)
@@ -145,6 +147,7 @@
new /obj/item/weapon/storage/backpack/satchel_sec(src)
new /obj/item/clothing/suit/armor/vest/security(src)
new /obj/item/clothing/under/rank/warden(src)
+ new /obj/item/clothing/under/rank/warden/corp(src)
new /obj/item/clothing/suit/armor/vest/warden(src)
new /obj/item/clothing/head/helmet/warden(src)
// new /obj/item/weapon/cartridge/security(src)
@@ -192,6 +195,8 @@
new /obj/item/taperoll/police(src)
new /obj/item/device/hailer(src)
new /obj/item/clothing/tie/storage/black_vest(src)
+ new /obj/item/clothing/head/soft/sec/corp(src)
+ new /obj/item/clothing/under/rank/security/corp(src)
return
diff --git a/code/game/objects/structures/crates_lockers/closets/utility_closets.dm b/code/game/objects/structures/crates_lockers/closets/utility_closets.dm
index c4058c54a6..76458dbf08 100644
--- a/code/game/objects/structures/crates_lockers/closets/utility_closets.dm
+++ b/code/game/objects/structures/crates_lockers/closets/utility_closets.dm
@@ -202,6 +202,16 @@
density = 0
wall_mounted = 1
+/obj/structure/closet/hydrant/New()
+ ..()
+ sleep(2)
+ new /obj/item/clothing/suit/fire/firefighter(src)
+ new /obj/item/clothing/mask/gas(src)
+ new /obj/item/device/flashlight(src)
+ new /obj/item/weapon/tank/oxygen/red(src)
+ new /obj/item/weapon/extinguisher(src)
+ new /obj/item/clothing/head/hardhat/red(src)
+
/*
* First Aid
*/
diff --git a/code/game/objects/structures/crates_lockers/closets/wardrobe.dm b/code/game/objects/structures/crates_lockers/closets/wardrobe.dm
index 1875cb7628..d115c40ff4 100644
--- a/code/game/objects/structures/crates_lockers/closets/wardrobe.dm
+++ b/code/game/objects/structures/crates_lockers/closets/wardrobe.dm
@@ -162,6 +162,12 @@
new /obj/item/clothing/shoes/black(src)
new /obj/item/clothing/shoes/black(src)
new /obj/item/clothing/shoes/black(src)
+ new /obj/item/clothing/head/hardhat/red(src)
+ new /obj/item/clothing/head/hardhat/red(src)
+ new /obj/item/clothing/head/hardhat/red(src)
+ new /obj/item/clothing/head/beret/eng(src)
+ new /obj/item/clothing/head/beret/eng(src)
+ new /obj/item/clothing/head/beret/eng(src)
return
@@ -178,6 +184,12 @@
new /obj/item/clothing/shoes/orange(src)
new /obj/item/clothing/shoes/orange(src)
new /obj/item/clothing/shoes/orange(src)
+ new /obj/item/clothing/head/hardhat(src)
+ new /obj/item/clothing/head/hardhat(src)
+ new /obj/item/clothing/head/hardhat(src)
+ new /obj/item/clothing/head/beret/eng(src)
+ new /obj/item/clothing/head/beret/eng(src)
+ new /obj/item/clothing/head/beret/eng(src)
return
diff --git a/code/global.dm b/code/global.dm
index 348cc8702f..d8e900dbc4 100644
--- a/code/global.dm
+++ b/code/global.dm
@@ -165,6 +165,9 @@ var/shuttlecoming = 0
var/join_motd = null
var/forceblob = 0
+// nanomanager, the manager for Nano UIs
+var/datum/nanomanager/nanomanager = new()
+
//airlockWireColorToIndex takes a number representing the wire color, e.g. the orange wire is always 1, the dark red wire is always 2, etc. It returns the index for whatever that wire does.
//airlockIndexToWireColor does the opposite thing - it takes the index for what the wire does, for example AIRLOCK_WIRE_IDSCAN is 1, AIRLOCK_WIRE_POWER1 is 2, etc. It returns the wire color number.
//airlockWireColorToFlag takes the wire color number and returns the flag for it (1, 2, 4, 8, 16, etc)
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index 803f2b1254..e5a6574ccc 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -1433,6 +1433,41 @@
log_admin("[src.owner] replied to [key_name(H)]'s Syndicate message with the message [input].")
H << "You hear something crackle in your headset for a moment before a voice speaks. \"Please stand by for a message from your benefactor. Message as follows, agent. [input]. Message ends.\""
+ else if(href_list["CentcommFaxView"])
+ var/info = locate(href_list["CentcommFaxView"])
+
+ usr << browse("Centcomm Fax Message[info]", "window=Centcomm Fax Message")
+
+ else if(href_list["CentcommFaxReply"])
+ var/mob/living/carbon/human/H = locate(href_list["CentcommFaxReply"])
+
+ var/input = input(src.owner, "Please enter a message to reply to [key_name(H)] via secure connection. NOTE: BBCode does not work, but HTML tags do! Use for line breaks.", "Outgoing message from Centcomm", "") as message|null
+ if(!input) return
+
+ var/customname = input(src.owner, "Pick a title for the report", "Title") as text|null
+
+ for(var/obj/machinery/faxmachine/F in world)
+ if(! (F.stat & (BROKEN|NOPOWER) ) )
+ var/obj/item/weapon/paper/P = new /obj/item/weapon/paper( F.loc )
+ P.name = "[command_name()]- [customname]"
+ P.info = input
+ P.update_icon()
+ playsound(F.loc, "sound/items/polaroid1.ogg", 50, 1)
+
+ // Stamps
+ var/image/stampoverlay = image('icons/obj/bureaucracy.dmi')
+ stampoverlay.icon_state = "paper_stamp-cent"
+ if(!P.stamped)
+ P.stamped = new
+ P.stamped += /obj/item/weapon/stamp
+ P.overlays += stampoverlay
+ P.stamps += "This paper has been stamped by the Central Command Quantum Relay."
+
+ src.owner << "Message reply to transmitted successfully."
+ log_admin("[key_name(src.owner)] replied to a fax message from [key_name(H)]: [input]")
+ message_admins("[key_name_admin(src.owner)] replied to a fax message from [key_name_admin(H)]", 1)
+
+
else if(href_list["jumpto"])
if(!check_rights(R_ADMIN)) return
diff --git a/code/modules/admin/verbs/one_click_antag.dm b/code/modules/admin/verbs/one_click_antag.dm
index 80408f022c..6f4ee9340c 100644
--- a/code/modules/admin/verbs/one_click_antag.dm
+++ b/code/modules/admin/verbs/one_click_antag.dm
@@ -517,6 +517,7 @@ client/proc/one_click_antag()
new_vox.mind_initialize()
new_vox.mind.assigned_role = "MODE"
new_vox.mind.special_role = "Vox Raider"
+ new_vox.mutations |= NOCLONE //Stops the station crew from messing around with their DNA.
ticker.mode.traitors += new_vox.mind
new_vox.equip_vox_raider()
diff --git a/code/modules/admin/verbs/vox_raiders.dm b/code/modules/admin/verbs/vox_raiders.dm
index 4b6e227159..be4b04f42f 100644
--- a/code/modules/admin/verbs/vox_raiders.dm
+++ b/code/modules/admin/verbs/vox_raiders.dm
@@ -37,8 +37,8 @@ var/global/vox_tick = 1
if(3) // Vox saboteur!
- equip_to_slot_or_del(new /obj/item/clothing/suit/space/vox/carapace(src), slot_wear_suit)
- equip_to_slot_or_del(new /obj/item/clothing/head/helmet/space/vox/carapace(src), slot_head)
+ equip_to_slot_or_del(new /obj/item/clothing/suit/space/vox/stealth(src), slot_wear_suit)
+ equip_to_slot_or_del(new /obj/item/clothing/head/helmet/space/vox/stealth(src), slot_head)
equip_to_slot_or_del(new /obj/item/weapon/storage/belt/utility/full(src), slot_belt)
equip_to_slot_or_del(new /obj/item/clothing/glasses/thermal/monocle(src), slot_glasses) // REPLACE WITH CODED VOX ALTERNATIVE.
equip_to_slot_or_del(new /obj/item/weapon/card/emag(src), slot_l_store)
@@ -46,8 +46,8 @@ var/global/vox_tick = 1
equip_to_slot_or_del(new /obj/item/device/multitool(src), slot_l_hand)
if(4) // Vox medic!
- equip_to_slot_or_del(new /obj/item/clothing/suit/space/vox/pressure(src), slot_wear_suit)
- equip_to_slot_or_del(new /obj/item/clothing/head/helmet/space/vox/pressure(src), slot_head)
+ equip_to_slot_or_del(new /obj/item/clothing/suit/space/vox/medic(src), slot_wear_suit)
+ equip_to_slot_or_del(new /obj/item/clothing/head/helmet/space/vox/medic(src), slot_head)
equip_to_slot_or_del(new /obj/item/weapon/storage/belt/utility/full(src), slot_belt) // Who needs actual surgical tools?
equip_to_slot_or_del(new /obj/item/clothing/glasses/hud/health(src), slot_glasses) // REPLACE WITH CODED VOX ALTERNATIVE.
equip_to_slot_or_del(new /obj/item/weapon/circular_saw(src), slot_l_store)
diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm
index b74355c271..f372f27e54 100644
--- a/code/modules/client/client procs.dm
+++ b/code/modules/client/client procs.dm
@@ -1,7 +1,7 @@
////////////
//SECURITY//
////////////
-#define TOPIC_SPAM_DELAY 4 //4 ticks is about 3/10ths of a second
+#define TOPIC_SPAM_DELAY 2 //2 ticks is about 2/10ths of a second; it was 4 ticks, but that caused too many clicks to be lost due to lag
#define UPLOAD_LIMIT 10485760 //Restricts client uploads to the server to 10MB //Boosted this thing. What's the worst that can happen?
#define MIN_CLIENT_VERSION 0 //Just an ambiguously low version for now, I don't want to suddenly stop people playing.
//I would just like the code ready should it ever need to be used.
@@ -242,6 +242,19 @@
//send resources to the client. It's here in its own proc so we can move it around easiliy if need be
/client/proc/send_resources()
getFiles(
+ 'nano/js/libraries.min.js',
+ 'nano/js/nano_update.js',
+ 'nano/js/nano_config.js',
+ 'nano/js/nano_base_helpers.js',
+ 'nano/css/shared.css',
+ 'nano/css/icons.css',
+ 'nano/templates/cryo.tmpl',
+ 'nano/images/uiBackground.png',
+ 'nano/images/uiIcons16.png',
+ 'nano/images/uiIcons24.png',
+ 'nano/images/uiLinkPendingIcon.gif',
+ 'nano/images/uiNoticeBackground.jpg',
+ 'nano/images/uiTitleFluff.png',
'html/search.js',
'html/panels.css',
'icons/pda_icons/pda_atmos.png',
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index cdf4e8e20f..33807cee23 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -879,7 +879,7 @@ datum/preferences
for(var/L in all_languages)
var/datum/language/lang = all_languages[L]
if(!(lang.flags & RESTRICTED))
- new_languages += lang
+ new_languages += lang.name
language = input("Please select a secondary language", "Character Generation", null) in new_languages
diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm
index 2d13a0ce92..16beaacac9 100644
--- a/code/modules/clothing/clothing.dm
+++ b/code/modules/clothing/clothing.dm
@@ -1,6 +1,32 @@
/obj/item/clothing
name = "clothing"
+ var/list/species_restricted = null //Only these species can wear this kit.
+//BS12: Species-restricted clothing check.
+/obj/item/clothing/mob_can_equip(M as mob, slot)
+
+ if(species_restricted && istype(M,/mob/living/carbon/human))
+
+ var/wearable = null
+ var/exclusive = null
+ var/mob/living/carbon/human/H = M
+
+ if("exclude" in species_restricted)
+ exclusive = 1
+
+ if(H.species)
+ if(exclusive)
+ if(!(H.species.name in species_restricted))
+ wearable = 1
+ else
+ if(H.species.name in species_restricted)
+ wearable = 1
+
+ if(!wearable)
+ M << "\red Your species cannot wear [src]."
+ return 0
+
+ return ..()
//Ears: currently only used for headsets and earmuffs
/obj/item/clothing/ears
@@ -50,6 +76,7 @@ BLIND // can't see anything
body_parts_covered = HANDS
slot_flags = SLOT_GLOVES
attack_verb = list("challenged")
+ species_restricted = list("exclude","Unathi","Tajaran")
/obj/item/clothing/gloves/examine()
set src in usr
@@ -94,6 +121,7 @@ BLIND // can't see anything
permeability_coefficient = 0.50
slowdown = SHOES_SLOWDOWN
+ species_restricted = list("exclude","Unathi","Tajaran")
//Suit
/obj/item/clothing/suit
@@ -122,6 +150,7 @@ BLIND // can't see anything
cold_protection = HEAD
min_cold_protection_temperature = SPACE_HELMET_MIN_COLD_PROTECITON_TEMPERATURE
siemens_coefficient = 0.9
+ species_restricted = list("exclude","Diona","Vox")
/obj/item/clothing/suit/space
name = "Space suit"
@@ -140,7 +169,7 @@ BLIND // can't see anything
cold_protection = UPPER_TORSO | LOWER_TORSO | LEGS | FEET | ARMS | HANDS
min_cold_protection_temperature = SPACE_SUIT_MIN_COLD_PROTECITON_TEMPERATURE
siemens_coefficient = 0.9
-
+ species_restricted = list("exclude","Diona","Vox")
//Under clothing
/obj/item/clothing/under
diff --git a/code/modules/clothing/gloves/stungloves.dm b/code/modules/clothing/gloves/stungloves.dm
index dd53138968..ea3dd1dc3f 100644
--- a/code/modules/clothing/gloves/stungloves.dm
+++ b/code/modules/clothing/gloves/stungloves.dm
@@ -4,18 +4,21 @@
..()
return
if(istype(W, /obj/item/weapon/cable_coil))
- var/obj/item/weapon/cable_coil/C = W
- if(!wired)
- if(C.amount >= 2)
- C.use(2)
- wired = 1
- siemens_coefficient = 3.0
- user << "You wrap some wires around [src]."
- update_icon()
+ if(!("stunglove" in species_restricted))
+ var/obj/item/weapon/cable_coil/C = W
+ if(!wired)
+ if(C.amount >= 2)
+ C.use(2)
+ wired = 1
+ siemens_coefficient = 3.0
+ user << "You wrap some wires around [src]."
+ update_icon()
+ else
+ user << "There is not enough wire to cover [src]."
else
- user << "There is not enough wire to cover [src]."
+ user << "[src] are already wired."
else
- user << "[src] are already wired."
+ user << ""
else if(istype(W, /obj/item/weapon/cell))
if(!wired)
@@ -30,6 +33,26 @@
user << "[src] already have a cell."
else if(istype(W, /obj/item/weapon/wirecutters))
+ if("exclude" in species_restricted)
+ name = "mangled [name]"
+ species_restricted -= "Unathi"
+ species_restricted -= "Tajaran"
+ species_restricted += "stunglove"
+
+ wired = null
+
+ if(cell)
+ cell.updateicon()
+ cell.loc = get_turf(src.loc)
+ cell = null
+
+ playsound(src.loc, 'sound/items/Wirecutter.ogg', 100, 1)
+ user.visible_message("\red [user] snips the fingertips off [src].","\red You snip the fingertips off [src].")
+
+ update_icon()
+
+ return
+
if(cell)
cell.updateicon()
cell.loc = get_turf(src.loc)
diff --git a/code/modules/clothing/head/jobs.dm b/code/modules/clothing/head/jobs.dm
index 14f250ebaf..e7d15b27f5 100644
--- a/code/modules/clothing/head/jobs.dm
+++ b/code/modules/clothing/head/jobs.dm
@@ -59,6 +59,11 @@
desc = "A beret with the security insignia emblazoned on it. For officers that are more inclined towards style than safety."
icon_state = "beret_badge"
flags = FPRINT | TABLEPASS
+/obj/item/clothing/head/beret/eng
+ name = "engineering beret"
+ desc = "A beret with the engineering insignia emblazoned on it. For engineers that are more inclined towards style than safety."
+ icon_state = "e_beret_badge"
+ flags = FPRINT | TABLEPASS
//Medical
/obj/item/clothing/head/surgery
diff --git a/code/modules/clothing/head/soft_caps.dm b/code/modules/clothing/head/soft_caps.dm
index af286a0e9b..75857f3345 100644
--- a/code/modules/clothing/head/soft_caps.dm
+++ b/code/modules/clothing/head/soft_caps.dm
@@ -85,4 +85,10 @@
name = "security cap"
desc = "It's baseball hat in tasteful red colour."
icon_state = "secsoft"
- color = "sec"
\ No newline at end of file
+ color = "sec"
+
+/obj/item/clothing/head/soft/sec/corp
+ name = "corporate security cap"
+ desc = "It's baseball hat in corpotate colours."
+ icon_state = "corpsoft"
+ color = "corp"
\ No newline at end of file
diff --git a/code/modules/clothing/shoes/miscellaneous.dm b/code/modules/clothing/shoes/miscellaneous.dm
index d7c6177893..9ef2467825 100644
--- a/code/modules/clothing/shoes/miscellaneous.dm
+++ b/code/modules/clothing/shoes/miscellaneous.dm
@@ -8,6 +8,7 @@
origin_tech = "syndicate=3"
var/list/clothing_choices = list()
siemens_coefficient = 0.8
+ species_restricted = null
/obj/item/clothing/shoes/mime
name = "mime shoes"
@@ -48,11 +49,13 @@
min_cold_protection_temperature = SHOE_MIN_COLD_PROTECITON_TEMPERATURE
heat_protection = FEET
max_heat_protection_temperature = SHOE_MAX_HEAT_PROTECITON_TEMPERATURE
+ species_restricted = null
/obj/item/clothing/shoes/sandal
desc = "A pair of rather plain, wooden sandals."
name = "sandals"
icon_state = "wizard"
+ species_restricted = null
/obj/item/clothing/shoes/sandal/marisa
desc = "A pair of magic, black shoes."
@@ -66,6 +69,7 @@
permeability_coefficient = 0.05
flags = NOSLIP
slowdown = SHOES_SLOWDOWN+1
+ species_restricted = null
/obj/item/clothing/shoes/clown_shoes
desc = "The prankster's standard-issue clowning shoes. Damn they're huge!"
@@ -75,6 +79,7 @@
slowdown = SHOES_SLOWDOWN+1
color = "clown"
var/footstep = 1 //used for squeeks whilst walking
+ species_restricted = null
/obj/item/clothing/shoes/jackboots
name = "jackboots"
@@ -96,6 +101,7 @@
min_cold_protection_temperature = SHOE_MIN_COLD_PROTECITON_TEMPERATURE
heat_protection = FEET
max_heat_protection_temperature = SHOE_MAX_HEAT_PROTECITON_TEMPERATURE
+ species_restricted = null
/obj/item/clothing/shoes/cyborg
name = "cyborg boots"
@@ -107,6 +113,7 @@
desc = "Fluffy!"
icon_state = "slippers"
item_state = "slippers"
+ species_restricted = null
/obj/item/clothing/shoes/slippers_worn
name = "worn bunny slippers"
diff --git a/code/modules/clothing/spacesuits/alien.dm b/code/modules/clothing/spacesuits/alien.dm
index 250935d61a..a124745793 100644
--- a/code/modules/clothing/spacesuits/alien.dm
+++ b/code/modules/clothing/spacesuits/alien.dm
@@ -1,20 +1,64 @@
+// Tajaran rigs.
+/obj/item/clothing/head/helmet/space/rig/tajara
+ desc = "A special helmet designed for work in a hazardous, low-pressure environment. Has radiation shielding. This one doesn't look like it was made for humans."
+ icon_state = "rig0-taj-helmet"
+ item_state = "rig0-taj-helmet"
+ color = "taj-helmet"
+ species_restricted = list("Tajaran")
+
+/obj/item/clothing/suit/space/rig/tajara
+ desc = "A special suit that protects against hazardous, low pressure environments. Has radiation shielding. This one doesn't look like it was made for humans."
+ icon_state = "rig-taj"
+ item_state = "rig-taj"
+ color = "rig-taj"
+ species_restricted = list("Tajaran")
+
+//Skrell space gear. Sleek like a wetsuit.
+
+/obj/item/clothing/head/helmet/space/skrell
+ name = "Skrellian helmet"
+ desc = "Smoothly contoured and polished to a shine. Still looks like a fishbowl."
+ armor = list(melee = 20, bullet = 20, laser = 50,energy = 50, bomb = 50, bio = 100, rad = 100)
+ max_heat_protection_temperature = SPACE_SUIT_MAX_HEAT_PROTECITON_TEMPERATURE
+ species_restricted = list("Skrell","Human")
+
+/obj/item/clothing/head/helmet/space/skrell/white
+ icon_state = "skrell_helmet_white"
+ item_state = "skrell_helmet_white"
+ color = "skrell_helmet_white"
+
+/obj/item/clothing/head/helmet/space/skrell/black
+ icon_state = "skrell_helmet_black"
+ item_state = "skrell_helmet_black"
+ color = "skrell_helmet_black"
+
+/obj/item/clothing/suit/space/skrell
+ name = "Skrellian hardsuit"
+ desc = "Seems like a wetsuit with reinforced plating seamlessly attached to it. Very chic."
+ armor = list(melee = 20, bullet = 20, laser = 50,energy = 50, bomb = 50, bio = 100, rad = 100)
+ allowed = list(/obj/item/device/flashlight,/obj/item/weapon/tank,/obj/item/weapon/storage/bag/ore,/obj/item/device/t_scanner,/obj/item/weapon/pickaxe, /obj/item/weapon/rcd)
+ heat_protection = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS
+ max_heat_protection_temperature = SPACE_SUIT_MAX_HEAT_PROTECITON_TEMPERATURE
+ species_restricted = list("Skrell","Human")
+
+/obj/item/clothing/suit/space/skrell/white
+ icon_state = "skrell_suit_white"
+ item_state = "skrell_suit_white"
+ color = "skrell_suit_white"
+
+/obj/item/clothing/suit/space/skrell/black
+ icon_state = "skrell_suit_black"
+ item_state = "skrell_suit_black"
+ color = "skrell_suit_black"
+
+//Unathi space gear. Huge and restrictive.
+
/obj/item/clothing/head/helmet/space/unathi
-
+ armor = list(melee = 40, bullet = 30, laser = 30,energy = 15, bomb = 35, bio = 100, rad = 50)
+ heat_protection = HEAD
+ max_heat_protection_temperature = SPACE_SUIT_MAX_HEAT_PROTECITON_TEMPERATURE
var/up = 0 //So Unathi helmets play nicely with the weldervision check.
- mob_can_equip(M as mob, slot)
- var/mob/living/carbon/human/U = M
- if(U.species.name != "Unathi")
- U << "This clearly isn't designed for your species!"
- return 0
- return ..()
-
-/obj/item/clothing/suit/space/unathi/mob_can_equip(M as mob, slot)
- var/mob/living/carbon/human/U = M
- if(U.species.name != "Unathi")
- U << "This clearly isn't designed for your species!"
- return 0
-
- return ..()
+ species_restricted = list("Unathi")
/obj/item/clothing/head/helmet/space/unathi/helmet_cheap
name = "NT breacher helmet"
@@ -22,9 +66,13 @@
icon_state = "unathi_helm_cheap"
item_state = "unathi_helm_cheap"
color = "unathi_helm_cheap"
+
+/obj/item/clothing/suit/space/unathi
armor = list(melee = 40, bullet = 30, laser = 30,energy = 15, bomb = 35, bio = 100, rad = 50)
- heat_protection = HEAD
+ allowed = list(/obj/item/device/flashlight,/obj/item/weapon/tank,/obj/item/weapon/storage/bag/ore,/obj/item/device/t_scanner,/obj/item/weapon/pickaxe, /obj/item/weapon/rcd)
+ heat_protection = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS
max_heat_protection_temperature = SPACE_SUIT_MAX_HEAT_PROTECITON_TEMPERATURE
+ species_restricted = list("Unathi")
/obj/item/clothing/suit/space/unathi/rig_cheap
name = "NT breacher chassis"
@@ -32,68 +80,90 @@
icon_state = "rig-unathi-cheap"
item_state = "rig-unathi-cheap"
slowdown = 3
- armor = list(melee = 40, bullet = 30, laser = 30,energy = 15, bomb = 35, bio = 100, rad = 50)
- allowed = list(/obj/item/device/flashlight,/obj/item/weapon/tank,/obj/item/weapon/storage/bag/ore,/obj/item/device/t_scanner,/obj/item/weapon/pickaxe, /obj/item/weapon/rcd)
- heat_protection = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS
- max_heat_protection_temperature = SPACE_SUIT_MAX_HEAT_PROTECITON_TEMPERATURE
+
+/obj/item/clothing/head/helmet/space/unathi/breacher
+ name = "breacher helm"
+ desc = "Weathered, ancient and battle-scarred. The helmet is too."
+ icon_state = "unathi_breacher"
+ item_state = "unathi_breacher"
+ color = "unathi_breacher"
+
+/obj/item/clothing/suit/space/unathi/breacher
+ name = "breacher chassis"
+ desc = "Huge, bulky and absurdly heavy. It must be like wearing a tank."
+ icon_state = "unathi_breacher"
+ item_state = "unathi_breacher"
+ color = "unathi_breacher"
+ slowdown = 1
// Vox space gear (vaccuum suit, low pressure armour)
// Can't be equipped by any other species due to bone structure and vox cybernetics.
-/obj/item/clothing/head/helmet/space/vox/pressure
- name = "alien helmet"
- icon_state = "vox-pressure"
- item_state = "vox-pressure"
- desc = "Hey, wasn't this a prop in \'The Abyss\'?"
- armor = list(melee = 60, bullet = 50, laser = 30, energy = 15, bomb = 30, bio = 30, rad = 30)
-
-/obj/item/clothing/suit/space/vox/pressure
- name = "alien pressure suit"
- icon_state = "vox-pressure"
- item_state = "vox-pressure"
- desc = "A huge, armoured, pressurized suit, designed for distinctly nonhuman proportions."
+/obj/item/clothing/suit/space/vox
w_class = 3
allowed = list(/obj/item/weapon/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/weapon/melee/baton,/obj/item/weapon/melee/energy/sword,/obj/item/weapon/handcuffs,/obj/item/weapon/tank)
slowdown = 2
armor = list(melee = 60, bullet = 50, laser = 30,energy = 15, bomb = 30, bio = 30, rad = 30)
heat_protection = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS
max_heat_protection_temperature = SPACE_SUIT_MAX_HEAT_PROTECITON_TEMPERATURE
+ species_restricted = list("Vox")
+
+/obj/item/clothing/head/helmet/space/vox
+ armor = list(melee = 60, bullet = 50, laser = 30, energy = 15, bomb = 30, bio = 30, rad = 30)
+ flags = HEADCOVERSEYES|STOPSPRESSUREDMAGE
+ species_restricted = list("Vox")
+
+/obj/item/clothing/head/helmet/space/vox/pressure
+ name = "alien helmet"
+ icon_state = "vox-pressure"
+ item_state = "vox-pressure"
+ desc = "Hey, wasn't this a prop in \'The Abyss\'?"
+
+/obj/item/clothing/suit/space/vox/pressure
+ name = "alien pressure suit"
+ icon_state = "vox-pressure"
+ item_state = "vox-pressure"
+ desc = "A huge, armoured, pressurized suit, designed for distinctly nonhuman proportions."
/obj/item/clothing/head/helmet/space/vox/carapace
name = "alien visor"
icon_state = "vox-carapace"
item_state = "vox-carapace"
desc = "A glowing visor, perhaps stolen from a depressed Cylon."
- armor = list(melee = 60, bullet = 50, laser = 30, energy = 15, bomb = 30, bio = 30, rad = 30)
- flags = HEADCOVERSEYES|STOPSPRESSUREDMAGE
/obj/item/clothing/suit/space/vox/carapace
name = "alien carapace armour"
icon_state = "vox-carapace"
item_state = "vox-carapace"
desc = "An armoured, segmented carapace with glowing purple lights. It looks pretty run-down."
- w_class = 3
- allowed = list(/obj/item/weapon/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/weapon/melee/baton,/obj/item/weapon/melee/energy/sword,/obj/item/weapon/handcuffs,/obj/item/weapon/tank)
- slowdown = 1
- armor = list(melee = 60, bullet = 50, laser = 30,energy = 15, bomb = 30, bio = 30, rad = 30)
-/obj/item/clothing/head/helmet/space/vox/mob_can_equip(M as mob, slot)
- var/mob/living/carbon/human/V = M
- if(V.species.name != "Vox")
- V << "This clearly isn't designed for your species!"
- return 0
- return ..()
+/obj/item/clothing/head/helmet/space/vox/stealth
+ name = "alien stealth helmet"
+ icon_state = "vox-stealth"
+ item_state = "vox-stealth"
+ desc = "A smoothly contoured, matte-black alien helmet."
-/obj/item/clothing/suit/space/vox/mob_can_equip(M as mob, slot)
- var/mob/living/carbon/human/V = M
- if(V.species.name != "Vox")
- V << "This clearly isn't designed for your species!"
- return 0
+/obj/item/clothing/suit/space/vox/stealth
+ name = "alien stealth suit"
+ icon_state = "vox-stealth"
+ item_state = "vox-stealth"
+ desc = "A sleek black suit. It seems to have a tail, and is very heavy."
- return ..()
+/obj/item/clothing/head/helmet/space/vox/medic
+ name = "alien goggled helmet"
+ icon_state = "vox-medic"
+ item_state = "vox-medic"
+ desc = "An alien helmet with enormous goggled lenses."
+
+/obj/item/clothing/suit/space/vox/medic
+ name = "alien armour"
+ icon_state = "vox-medic"
+ item_state = "vox-medic"
+ desc = "An almost organic looking nonhuman pressure suit."
/obj/item/clothing/under/vox
has_sensor = 0
+ species_restricted = list("Vox")
/obj/item/clothing/under/vox/vox_casual
name = "alien clothing"
@@ -117,13 +187,7 @@
siemens_coefficient = 0
permeability_coefficient = 0.05
color="gloves-vox"
-
-/obj/item/clothing/gloves/yellow/vox/mob_can_equip(M as mob, slot)
- var/mob/living/carbon/human/U = M
- if(U.species.name != "Vox")
- U << "This clearly isn't designed for your species!"
- return 0
- return ..()
+ species_restricted = list("Vox")
/obj/item/clothing/shoes/magboots/vox
@@ -131,6 +195,7 @@
name = "vox boots"
item_state = "boots-vox"
icon_state = "boots-vox"
+ species_restricted = list("Vox")
toggle()
//set name = "Toggle Floor Grip"
@@ -148,11 +213,4 @@
examine()
set src in view()
- ..()
-
-/obj/item/clothing/shoes/magboots/vox/mob_can_equip(M as mob, slot)
- var/mob/living/carbon/human/U = M
- if(U.species.name != "Vox")
- U << "This clearly isn't designed for your species!"
- return 0
- return ..()
\ No newline at end of file
+ ..()
\ No newline at end of file
diff --git a/code/modules/clothing/spacesuits/rig.dm b/code/modules/clothing/spacesuits/rig.dm
index 0eccaaf96a..b075d92916 100644
--- a/code/modules/clothing/spacesuits/rig.dm
+++ b/code/modules/clothing/spacesuits/rig.dm
@@ -1,3 +1,33 @@
+//Species modification item.
+
+/obj/item/weapon/modkit/tajaran
+ name = "hardsuit modification kit"
+ desc = "A kit containing all the needed tools and parts to modify a hardsuit for another species. This one looks like it's meant for Tajara."
+ icon = 'icons/obj/custom_items.dmi'
+ icon_state = "royce_kit"
+
+/obj/item/clothing/head/helmet/space/rig/attackby(obj/item/I as obj, mob/user as mob)
+ if(istype(I,/obj/item/weapon/modkit/tajaran))
+ user.drop_item()
+ playsound(src.loc, 'sound/items/Screwdriver.ogg', 100, 1)
+ user << "\red You painstakingly modify [src] to make it more suitable for a Tajaran user."
+ new /obj/item/clothing/head/helmet/space/rig/tajara(user.loc)
+ del(I)
+ del(src)
+ return
+ ..()
+
+/obj/item/clothing/suit/space/rig/attackby(obj/item/I as obj, mob/user as mob)
+ if(istype(I,/obj/item/weapon/modkit/tajaran))
+ user.drop_item()
+ playsound(src.loc, 'sound/items/Screwdriver.ogg', 100, 1)
+ user << "\red You painstakingly modify [src] to make it more suitable for a Tajaran user."
+ new /obj/item/clothing/suit/space/rig/tajara(user.loc)
+ del(I)
+ del(src)
+ return
+ ..()
+
//Regular rig suits
/obj/item/clothing/head/helmet/space/rig
name = "engineering hardsuit helmet"
@@ -12,6 +42,7 @@
icon_action_button = "action_hardhat"
heat_protection = HEAD
max_heat_protection_temperature = SPACE_SUIT_MAX_HEAT_PROTECITON_TEMPERATURE
+ species_restricted = list("exclude","Unathi","Tajaran","Skrell","Diona","Vox")
attack_self(mob/user)
if(!isturf(user.loc))
@@ -46,6 +77,7 @@
allowed = list(/obj/item/device/flashlight,/obj/item/weapon/tank,/obj/item/weapon/storage/bag/ore,/obj/item/device/t_scanner,/obj/item/weapon/pickaxe, /obj/item/weapon/rcd)
heat_protection = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS
max_heat_protection_temperature = SPACE_SUIT_MAX_HEAT_PROTECITON_TEMPERATURE
+ species_restricted = list("exclude","Unathi","Tajaran","Diona","Vox")
//Chief Engineer's rig
/obj/item/clothing/head/helmet/space/rig/elite
diff --git a/code/modules/clothing/under/jobs/security.dm b/code/modules/clothing/under/jobs/security.dm
index ecec4174ec..9de05b4a87 100644
--- a/code/modules/clothing/under/jobs/security.dm
+++ b/code/modules/clothing/under/jobs/security.dm
@@ -48,6 +48,16 @@
flags = FPRINT | TABLEPASS
siemens_coefficient = 0.9
+/obj/item/clothing/under/rank/security/corp
+ icon_state = "sec_corporate"
+ item_state = "sec_corporate"
+ color = "sec_corporate"
+
+/obj/item/clothing/under/rank/warden/corp
+ icon_state = "warden_corporate"
+ item_state = "warden_corporate"
+ color = "warden_corporate"
+
/*
* Detective
*/
@@ -83,6 +93,10 @@
flags = FPRINT | TABLEPASS
siemens_coefficient = 0.8
+/obj/item/clothing/under/rank/head_of_security/corp
+ icon_state = "hos_corporate"
+ item_state = "hos_corporate"
+ color = "hos_corporate"
/obj/item/clothing/head/helmet/HoS
name = "Head of Security Hat"
@@ -94,10 +108,9 @@
flags_inv = HIDEEARS
siemens_coefficient = 0.8
-
/obj/item/clothing/suit/armor/hos
name = "armored coat"
- desc = "A greatcoat enchanced with a special alloy for some protection and style."
+ desc = "A greatcoat enhanced with a special alloy for some protection and style."
icon_state = "hos"
item_state = "hos"
body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS|LEGS
diff --git a/code/modules/mob/dead/observer/say.dm b/code/modules/mob/dead/observer/say.dm
index d85257efdd..db9cd2129c 100644
--- a/code/modules/mob/dead/observer/say.dm
+++ b/code/modules/mob/dead/observer/say.dm
@@ -1,6 +1,3 @@
-/mob/dead/observer/say_understands(var/other)
- return 1
-
/mob/dead/observer/say(var/message)
message = trim(copytext(sanitize(message), 1, MAX_MESSAGE_LEN))
diff --git a/code/modules/mob/emote.dm b/code/modules/mob/emote.dm
index eaa345be1b..416528d3fc 100644
--- a/code/modules/mob/emote.dm
+++ b/code/modules/mob/emote.dm
@@ -1,7 +1,7 @@
// All mobs should have custom emote, really..
/mob/proc/custom_emote(var/m_type=1,var/message = null)
- if(!use_me && usr == src)
+ if(stat || !use_me && usr == src)
usr << "You are unable to emote."
return
diff --git a/code/modules/mob/language.dm b/code/modules/mob/language.dm
index ba868a2bd4..ea82e57925 100644
--- a/code/modules/mob/language.dm
+++ b/code/modules/mob/language.dm
@@ -43,11 +43,18 @@
key = "v"
flags = RESTRICTED
-/*
+/datum/language/diona
+ name = "Rootspeak"
+ desc = "A creaking, subvocal language spoken instinctively by the Dionaea. Due to the unique makeup of the average Diona, a phrase of Rootspeak can be a combination of anywhere from one to twelve individual voices and notes."
+ speech_verb = "creaks and rustles"
+ colour = "soghun"
+ key = "q"
+ flags = RESTRICTED
+
/datum/language/human
name = "Sol Common"
desc = "A bastardized hybrid of informal English and elements of Mandarin Chinese; the common language of the Sol system."
- key = "hum"
+ key = "1"
flags = RESTRICTED
// Galactic common languages (systemwide accepted standards).
@@ -55,14 +62,13 @@
name = "Tradeband"
desc = "Maintained by the various trading cartels in major systems, this elegant, structured language is used for bartering and bargaining."
speech_verb = "enunciates"
- key = "tra"
+ key = "2"
/datum/language/gutter
name = "Gutter"
desc = "Much like Standard, this crude pidgin tongue descended from numerous languages and serves as Tradeband for criminal elements."
speech_verb = "growls"
- key = "gut"
-*/
+ key = "3"
// Language handling.
/mob/proc/add_language(var/language)
@@ -86,4 +92,18 @@
languages -= L
return 1
- return 0
\ No newline at end of file
+ return 0
+
+//TBD
+/mob/verb/check_languages()
+ set name = "Check Known Languages"
+ set category = "IC"
+ set src = usr
+
+ var/dat = "Known Languages
"
+
+ for(var/datum/language/L in languages)
+ dat += "[L.name] (:[L.key]) [L.desc]
"
+
+ src << browse(dat, "window=checklanguage")
+ return
\ No newline at end of file
diff --git a/code/modules/mob/living/carbon/alien/alien.dm b/code/modules/mob/living/carbon/alien/alien.dm
index 2b94f9d706..8f888806b8 100644
--- a/code/modules/mob/living/carbon/alien/alien.dm
+++ b/code/modules/mob/living/carbon/alien/alien.dm
@@ -5,8 +5,7 @@
/mob/living/carbon/alien
name = "alien"
voice_name = "alien"
- voice_message = "hisses"
- say_message = "hisses"
+ speak_emote = list("hisses")
icon = 'icons/mob/alien.dmi'
gender = NEUTER
dna = null
diff --git a/code/modules/mob/living/carbon/alien/say.dm b/code/modules/mob/living/carbon/alien/say.dm
index a791d06652..28947ac55e 100644
--- a/code/modules/mob/living/carbon/alien/say.dm
+++ b/code/modules/mob/living/carbon/alien/say.dm
@@ -1,8 +1,3 @@
-/mob/living/carbon/alien/say_understands(var/other)
- if (istype(other, /mob/living/carbon/alien))
- return 1
- return ..()
-
/mob/living/carbon/alien/say(var/message)
if (silent)
@@ -22,12 +17,6 @@
return ..(message)
else
-// ~lol~
-/mob/living/carbon/alien/say_quote(var/text)
-// var/ending = copytext(text, length(text))
-
- return "[say_message], \"[text]\"";
-
/mob/living/proc/alien_talk(var/message)
log_say("[key_name(src)] : [message]")
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index d9d6fdc070..c7469e678c 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -255,7 +255,16 @@
var/turf/startloc = loc
var/obj/selection = input("Select a destination.", "Duct System") as null|anything in sortList(vents)
- if(!selection) return
+
+ if(!selection)
+ return
+
+ if(!do_after(src, 45))
+ return
+
+ if(!src||!selection)
+ return
+
if(loc==startloc)
if(contents.len)
for(var/obj/item/carried_item in contents)//If the monkey got on objects.
@@ -476,4 +485,79 @@
return num2text(method ? temp : temp + rand(-10, 10))
if(PULSE_THREADY)
return method ? ">250" : "extremely weak and fast, patient's artery feels like a thread"
-// output for machines^ ^^^^^^^output for people^^^^^^^^^
\ No newline at end of file
+// output for machines^ ^^^^^^^output for people^^^^^^^^^
+
+
+//Brain slug proc for voluntary removal of control.
+/mob/living/carbon/proc/release_control()
+
+ set category = "Alien"
+ set name = "Release Control"
+ set desc = "Release control of your host's body."
+
+ var/mob/living/simple_animal/borer/B = has_brain_worms()
+
+ if(!B)
+ return
+
+ if(B.controlling)
+ src << "\red You withdraw your probosci, releasing control of [B.host_brain]"
+ B.host_brain << "\red Your vision swims as the alien parasite releases control of your body."
+ B.ckey = ckey
+ B.controlling = 0
+ if(B.host_brain.ckey)
+ ckey = B.host_brain.ckey
+ B.host_brain.ckey = null
+ B.host_brain.name = "host brain"
+ B.host_brain.real_name = "host brain"
+
+ verbs -= /mob/living/carbon/proc/release_control
+ verbs -= /mob/living/carbon/proc/punish_host
+ verbs -= /mob/living/carbon/proc/spawn_larvae
+
+//Brain slug proc for tormenting the host.
+/mob/living/carbon/proc/punish_host()
+ set category = "Alien"
+ set name = "Torment host"
+ set desc = "Punish your host with agony."
+
+ var/mob/living/simple_animal/borer/B = has_brain_worms()
+
+ if(!B)
+ return
+
+ if(B.host_brain.ckey)
+ src << "\red You send a punishing spike of psychic agony lancing into your host's brain."
+ B.host_brain << "\red Horrific, burning agony lances through you, ripping a soundless scream from your trapped mind!"
+
+//Check for brain worms in head.
+/mob/living/carbon/proc/has_brain_worms()
+
+ for(var/I in contents)
+ if(istype(I,/mob/living/simple_animal/borer))
+ return I
+
+ return 0
+
+/mob/living/carbon/proc/spawn_larvae()
+ set category = "Alien"
+ set name = "Reproduce"
+ set desc = "Spawn several young."
+
+ var/mob/living/simple_animal/borer/B = has_brain_worms()
+
+ if(!B)
+ return
+
+ if(B.chemicals >= 100)
+ src << "\red Your host twitches and quivers as you rapdly excrete several larvae from your sluglike body."
+ visible_message("\red [src] heaves violently, expelling a rush of vomit and a wriggling, sluglike creature!")
+ B.chemicals -= 100
+
+ new /obj/effect/decal/cleanable/vomit(get_turf(src))
+ playsound(loc, 'sound/effects/splat.ogg', 50, 1)
+ new /mob/living/simple_animal/borer(get_turf(src))
+
+ else
+ src << "You do not have enough chemicals stored to reproduce."
+ return
diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm
index a2cc5d21a9..0dff048d00 100644
--- a/code/modules/mob/living/carbon/carbon_defines.dm
+++ b/code/modules/mob/living/carbon/carbon_defines.dm
@@ -4,8 +4,6 @@
var/brain_op_stage = 0.0
var/list/datum/disease2/disease/virus2 = list()
var/antibodies = 0
-
- var/silent = null //Can't talk. Value goes down every life proc.
var/last_eating = 0 //Not sure what this does... I found it hidden in food.dm
var/life_tick = 0 // The amount of life ticks that have processed on this mob.
diff --git a/code/modules/mob/living/carbon/human/death.dm b/code/modules/mob/living/carbon/human/death.dm
index 095695e23d..95ebdf2011 100644
--- a/code/modules/mob/living/carbon/human/death.dm
+++ b/code/modules/mob/living/carbon/human/death.dm
@@ -71,7 +71,7 @@
B.host_brain.name = "host brain"
B.host_brain.real_name = "host brain"
- verbs -= /mob/living/carbon/human/proc/release_control
+ verbs -= /mob/living/carbon/proc/release_control
//Check for heist mode kill count.
if(ticker.mode && ( istype( ticker.mode,/datum/game_mode/heist) ) )
@@ -134,5 +134,5 @@
/mob/living/carbon/human/proc/Drain()
ChangeToHusk()
- mutations |= NOCLONE
+ //mutations |= NOCLONE
return
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 13fb91770b..c7543d398a 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -1278,84 +1278,4 @@ mob/living/carbon/human/yank_out_object()
if(species)
return 1
else
- return 0
-
-//Brain slug proc for voluntary removal of control.
-/mob/living/carbon/human/proc/release_control()
-
- set category = "Alien"
- set name = "Release Control"
- set desc = "Release control of your host's body."
-
- var/datum/organ/external/head = get_organ("head")
- var/mob/living/simple_animal/borer/B
-
- for(var/I in head.implants)
- if(istype(I,/mob/living/simple_animal/borer))
- B = I
- if(!B)
- return
-
- if(B.controlling)
- src << "\red You withdraw your probosci, releasing control of [B.host_brain]"
- B.host_brain << "\red Your vision swims as the alien parasite releases control of your body."
- B.ckey = ckey
- B.controlling = 0
- if(B.host_brain.ckey)
- ckey = B.host_brain.ckey
- B.host_brain.ckey = null
- B.host_brain.name = "host brain"
- B.host_brain.real_name = "host brain"
-
- verbs -= /mob/living/carbon/human/proc/release_control
- verbs -= /mob/living/carbon/human/proc/punish_host
- verbs -= /mob/living/carbon/human/proc/spawn_larvae
-
-//Brain slug proc for tormenting the host.
-/mob/living/carbon/human/proc/punish_host()
- set category = "Alien"
- set name = "Torment host"
- set desc = "Punish your host with agony."
-
- var/mob/living/simple_animal/borer/B = has_brain_worms()
-
- if(!B)
- return
-
- if(B.host_brain.ckey)
- src << "\red You send a punishing spike of psychic agony lancing into your host's brain."
- B.host_brain << "\red Horrific, burning agony lances through you, ripping a soundless scream from your trapped mind!"
-
-//Check for brain worms in head.
-/mob/living/carbon/human/proc/has_brain_worms()
-
- var/datum/organ/external/head = get_organ("head")
-
- for(var/I in head.implants)
- if(istype(I,/mob/living/simple_animal/borer))
- return I
-
- return 0
-
-/mob/living/carbon/human/proc/spawn_larvae()
- set category = "Alien"
- set name = "Reproduce"
- set desc = "Spawn several young."
-
- var/mob/living/simple_animal/borer/B = has_brain_worms()
-
- if(!B)
- return
-
- if(B.chemicals >= 100)
- src << "\red Your host twitches and quivers as you rapdly excrete several larvae from your sluglike body."
- visible_message("\red [src] heaves violently, expelling a rush of vomit and a wriggling, sluglike creature!")
- B.chemicals -= 100
-
- new /obj/effect/decal/cleanable/vomit(get_turf(src))
- playsound(loc, 'sound/effects/splat.ogg', 50, 1)
- new /mob/living/simple_animal/borer(get_turf(src))
-
- else
- src << "You do not have enough chemicals stored to reproduce."
- return
+ return 0
\ No newline at end of file
diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm
index f8a184f3c1..4044c0aae1 100644
--- a/code/modules/mob/living/carbon/human/human_defense.dm
+++ b/code/modules/mob/living/carbon/human/human_defense.dm
@@ -191,6 +191,8 @@ emp_act
user << "\red [src]'s [affecting.display_name] is already sabotaged!"
else
user << "\red You sneakily slide [I] into the dataport on [src]'s [affecting.display_name] and short out the safeties."
+ var/obj/item/weapon/card/emag/emag = I
+ emag.uses--
affecting.sabotaged = 1
return
diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm
index f531495406..b92bd7e706 100644
--- a/code/modules/mob/living/carbon/human/life.dm
+++ b/code/modules/mob/living/carbon/human/life.dm
@@ -215,6 +215,7 @@
adjustCloneLoss(0.1)
proc/handle_mutations_and_radiation()
+
if(getFireLoss())
if((COLD_RESISTANCE in mutations) || (prob(1)))
heal_organ_damage(0,1)
@@ -237,6 +238,16 @@
radiation = 0
else
+ if(species.flags & RAD_ABSORB)
+ var/rads = radiation/25
+ radiation -= rads
+ nutrition += rads
+ heal_overall_damage(rads,rads)
+ adjustOxyLoss(-(rads))
+ adjustToxLoss(-(rads))
+ updatehealth()
+ return
+
var/damage = 0
switch(radiation)
if(1 to 49)
@@ -275,6 +286,7 @@
proc/breathe()
if(reagents.has_reagent("lexorin")) return
if(istype(loc, /obj/machinery/atmospherics/unary/cryo_cell)) return
+ if(species && species.flags & NO_BREATHE) return
var/datum/organ/internal/lungs/L = internal_organs["lungs"]
L.process()
@@ -312,7 +324,6 @@
breath = loc.remove_air(breath_moles)
-
if(!is_lung_ruptured())
if(!breath || breath.total_moles < BREATH_MOLES / 5 || breath.total_moles > BREATH_MOLES * 5)
if(prob(5))
@@ -849,6 +860,8 @@
if(A.lighting_use_dynamic) light_amount = min(10,T.lighting_lumcount) - 5 //hardcapped so it's not abused by having a ton of flashlights
else light_amount = 5
nutrition += light_amount
+ traumatic_shock -= light_amount
+
if(nutrition > 500)
nutrition = 500
if(light_amount > 2) //if there's enough light, heal
@@ -901,6 +914,7 @@
if(species.flags & REQUIRE_LIGHT)
if(nutrition < 200)
take_overall_damage(2,0)
+ traumatic_shock++
if (drowsyness)
drowsyness--
diff --git a/code/modules/mob/living/carbon/human/say.dm b/code/modules/mob/living/carbon/human/say.dm
index b79b974d53..ba6733010b 100644
--- a/code/modules/mob/living/carbon/human/say.dm
+++ b/code/modules/mob/living/carbon/human/say.dm
@@ -1,53 +1,5 @@
/mob/living/carbon/human/say(var/message)
- if(silent)
- return
-
- //Mimes dont speak! Changeling hivemind and emotes are allowed.
- if(miming)
- if(length(message) >= 2)
- if(mind && mind.changeling)
- if(copytext(message, 1, 2) != "*" && department_radio_keys[copytext(message, 1, 3)] != "changeling")
- return
- else
- return ..(message)
- if(stat == DEAD)
- return ..(message)
-
- if(length(message) >= 1) //In case people forget the '*help' command, this will slow them the message and prevent people from saying one letter at a time
- if (copytext(message, 1, 2) != "*")
- return
-
- /*if(dna)
- if(dna.mutantrace == "lizard")
- if(copytext(message, 1, 2) != "*")
- message = replacetext(message, "s", stutter("ss"))
-
- if(dna.mutantrace == "slime" && prob(5))
- if(copytext(message, 1, 2) != "*")
- if(copytext(message, 1, 2) == ";")
- message = ";"
- else
- message = ""
- message += "SKR"
- var/imax = rand(5,20)
- for(var/i = 0,i 0)
+ trays += tray
+
+ var/obj/machinery/hydroponics/target = input("Select a tray:") as null|anything in trays
+
+ if(!src || !target || target.weedlevel == 0) return //Sanity check.
+
+ src.reagents.add_reagent("nutriment", target.weedlevel)
+ target.weedlevel = 0
+ src.visible_message("\red [src] begins rooting through [target], ripping out weeds and eating them noisily.","\red You begin rooting through [target], ripping out weeds and eating them noisily.")
+
+/mob/living/carbon/monkey/diona/verb/evolve()
+
+ set category = "Diona"
+ set name = "Evolve"
+ set desc = "Grow to a more complex form."
+
+ if(donors.len < 5)
+ src << "You are not yet ready for your growth..."
+ return
+
+ if(reagents.get_reagent_amount("nutriment") < 5)
+ src << "You have not yet consumed enough to grow..."
+ return
+
+ src.visible_message("\red [src] begins to shift and quiver, and erupts in a shower of shed bark and twigs!","\red You begin to shift and quiver, then erupt in a shower of shed bark and twigs, attaining your adult form!")
+ var/mob/living/carbon/human/adult = new(loc)
+ adult.set_species("Diona")
+ for(var/datum/language/L in languages)
+ adult.add_language(L.name)
+ adult.regenerate_icons()
+
+ adult.name = src.name
+ adult.real_name = src.real_name
+ adult.ckey = src.ckey
+ del(src)
+
+/mob/living/carbon/monkey/diona/verb/steal_blood()
+ set category = "Diona"
+ set name = "Steal Blood"
+ set desc = "Take a blood sample from a suitable donor."
+
+ var/list/choices = list()
+ for(var/mob/living/C in view(1,src))
+ if(C.real_name != real_name)
+ choices += C
+
+ var/mob/living/M = input(src,"Who do you wish to take a sample from?") in null|choices
+
+ if(!M || !src) return
+
+ if(donors.Find(M.real_name))
+ src << "\red That donor offers you nothing new."
+ return
+
+ src.visible_message("\red [src] flicks out a feeler and neatly steals a sample of [M]'s blood.","\red You flick out a feeler and neatly steal a sample of [M]'s blood.")
+ donors += M.real_name
+ spawn(25)
+ update_progression()
+
+/mob/living/carbon/monkey/diona/proc/update_progression()
+
+ if(!donors.len)
+ return
+
+ if(donors.len == 5)
+ ready_evolve = 1
+ src << "\green You feel ready to move on to your next stage of growth."
+ else if(donors.len == 3)
+ universal_speak = 1
+ src << "\green You feel your awareness expand, and realize you know how to speak to the meat-creatures around you."
+ else
+ src << "\green The blood seeps into your small form, and you draw out the echoes of memories and personality from it, working them into your budding mind."
\ No newline at end of file
diff --git a/code/modules/mob/living/carbon/monkey/life.dm b/code/modules/mob/living/carbon/monkey/life.dm
index ce68c8710d..675f2c76a5 100644
--- a/code/modules/mob/living/carbon/monkey/life.dm
+++ b/code/modules/mob/living/carbon/monkey/life.dm
@@ -117,6 +117,17 @@
emote("collapse")
if (radiation)
+
+ if(istype(src,/mob/living/carbon/monkey/diona)) //Filthy check. Dionaea don't take rad damage.
+ var/rads = radiation/25
+ radiation -= rads
+ nutrition += rads
+ heal_overall_damage(rads,rads)
+ adjustOxyLoss(-(rads))
+ adjustToxLoss(-(rads))
+ updatehealth()
+ return
+
if (radiation > 100)
radiation = 100
Weaken(10)
@@ -408,6 +419,25 @@
proc/handle_chemicals_in_body()
+ if(istype(src,/mob/living/carbon/monkey/diona)) //Filthy check. Dionaea nymphs need light or they get sad.
+ var/light_amount = 0 //how much light there is in the place, affects receiving nutrition and healing
+ if(isturf(loc)) //else, there's considered to be no light
+ var/turf/T = loc
+ var/area/A = T.loc
+ if(A)
+ if(A.lighting_use_dynamic) light_amount = min(10,T.lighting_lumcount) - 5 //hardcapped so it's not abused by having a ton of flashlights
+ else light_amount = 5
+
+ nutrition += light_amount
+ traumatic_shock -= light_amount
+
+ if(nutrition > 500)
+ nutrition = 500
+ if(light_amount > 2) //if there's enough light, heal
+ heal_overall_damage(1,1)
+ adjustToxLoss(-1)
+ adjustOxyLoss(-1)
+
if(reagents) reagents.metabolize(src)
if (drowsyness)
diff --git a/code/modules/mob/living/carbon/monkey/monkey.dm b/code/modules/mob/living/carbon/monkey/monkey.dm
index a073dfa10d..7f109dd7d7 100644
--- a/code/modules/mob/living/carbon/monkey/monkey.dm
+++ b/code/modules/mob/living/carbon/monkey/monkey.dm
@@ -1,8 +1,7 @@
/mob/living/carbon/monkey
name = "monkey"
voice_name = "monkey"
- voice_message = "chimpers"
- say_message = "chimpers"
+ speak_emote = list("chimpers")
icon_state = "monkey1"
icon = 'icons/mob/monkey.dmi'
gender = NEUTER
@@ -17,24 +16,21 @@
/mob/living/carbon/monkey/tajara
name = "farwa"
voice_name = "farwa"
- voice_message = "mews"
- say_message = "mews"
+ speak_emote = list("mews")
ico = "tajkey"
uni_append = "0A0E00"
/mob/living/carbon/monkey/skrell
name = "neaera"
voice_name = "neaera"
- voice_message = "squicks"
- say_message = "squicks"
+ speak_emote = list("squicks")
ico = "skrellkey"
uni_append = "01CC92"
/mob/living/carbon/monkey/unathi
name = "stok"
voice_name = "stok"
- voice_message = "hisses"
- say_message = "hisses"
+ speak_emote = list("hisses")
ico = "stokkey"
uni_append = "044C5D"
@@ -43,7 +39,7 @@
reagents = R
R.my_atom = src
- if(name == "monkey" || name == "farwa" || name == "stok" || name == "neara") //Hideous but necessary to stop Pun-Pun becoming generic.
+ if(name == "monkey" || name == "farwa" || name == "stok" || name == "neara" || name == "diona nymph") //Hideous but necessary to stop Pun-Pun becoming generic.
name = "[name] ([rand(1, 1000)])"
real_name = name
@@ -71,18 +67,29 @@
..()
dna.mutantrace = "lizard"
greaterform = "Unathi"
+ add_language("Sinta'unathi")
/mob/living/carbon/monkey/skrell/New()
..()
dna.mutantrace = "skrell"
greaterform = "Skrell"
+ add_language("Skrellian")
/mob/living/carbon/monkey/tajara/New()
..()
dna.mutantrace = "tajaran"
greaterform = "Tajaran"
+ add_language("Siik'tajr")
+
+/mob/living/carbon/monkey/diona/New()
+
+ ..()
+ gender = NEUTER
+ dna.mutantrace = "plant"
+ greaterform = "Diona"
+ add_language("Rootspeak")
/mob/living/carbon/monkey/movement_delay()
var/tally = 0
diff --git a/code/modules/mob/living/carbon/monkey/say.dm b/code/modules/mob/living/carbon/monkey/say.dm
deleted file mode 100644
index b213659368..0000000000
--- a/code/modules/mob/living/carbon/monkey/say.dm
+++ /dev/null
@@ -1,8 +0,0 @@
-/mob/living/carbon/monkey/say(var/message)
- if (silent)
- return
- else
- return ..()
-
-/mob/living/carbon/monkey/say_quote(var/text)
- return "[src.say_message], \"[text]\"";
diff --git a/code/modules/mob/living/carbon/species.dm b/code/modules/mob/living/carbon/species.dm
index ec5e3cd403..788f3d2c0e 100644
--- a/code/modules/mob/living/carbon/species.dm
+++ b/code/modules/mob/living/carbon/species.dm
@@ -15,7 +15,7 @@
var/attack_verb = "punch" // Empty hand hurt intent verb.
var/mutantrace // Safeguard due to old code.
- var/breath_type // Non-oxygen gas breathed, if any.
+ var/breath_type = "oxygen" // Non-oxygen gas breathed, if any.
var/cold_level_1 = 260 // Cold damage level 1 below this point.
var/cold_level_2 = 200 // Cold damage level 2 below this point.
@@ -63,6 +63,14 @@
attack_verb = "scratch"
darksight = 8
+ cold_level_1 = 200
+ cold_level_2 = 140
+ cold_level_3 = 80
+
+ heat_level_1 = 330
+ heat_level_2 = 380
+ heat_level_3 = 800
+
primitive = /mob/living/carbon/monkey/tajara
flags = WHITELISTED | HAS_LIPS | HAS_UNDERWEAR | HAS_TAIL
@@ -82,6 +90,13 @@
deform = 'icons/mob/human_races/r_def_vox.dmi'
language = "Vox-pidgin"
+ warning_low_pressure = 50
+ hazard_low_pressure = 0
+
+ cold_level_1 = 80
+ cold_level_2 = 50
+ cold_level_3 = 0
+
eyes = "vox_eyes_s"
breath_type = "nitrogen"
@@ -91,6 +106,19 @@
name = "Diona"
icobase = 'icons/mob/human_races/r_plant.dmi'
deform = 'icons/mob/human_races/r_def_plant.dmi'
+ language = "Rootspeak"
attack_verb = "slash"
+ primitive = /mob/living/carbon/monkey/diona
- flags = WHITELISTED | NO_EAT | NO_BREATHE | REQUIRE_LIGHT | NON_GENDERED | NO_SCAN | IS_PLANT
\ No newline at end of file
+ warning_low_pressure = 50
+ hazard_low_pressure = -1
+
+ cold_level_1 = 50
+ cold_level_2 = -1
+ cold_level_3 = -1
+
+ heat_level_1 = 2000
+ heat_level_2 = 3000
+ heat_level_3 = 4000
+
+ flags = WHITELISTED | NO_BREATHE | REQUIRE_LIGHT | NON_GENDERED | NO_SCAN | IS_PLANT | RAD_ABSORB
diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm
index 8d76b47513..2b3d9f861a 100644
--- a/code/modules/mob/living/living_defines.dm
+++ b/code/modules/mob/living/living_defines.dm
@@ -29,10 +29,10 @@
var/t_sl_gas = null
var/t_n2 = null
-
var/now_pushing = null
var/cameraFollow = null
var/tod = null // Time of death
- var/update_slimes = 1
\ No newline at end of file
+ var/update_slimes = 1
+ var/silent = null //Can't talk. Value goes down every life proc.
\ No newline at end of file
diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm
index 69d62106f4..d7021a22b4 100644
--- a/code/modules/mob/living/say.dm
+++ b/code/modules/mob/living/say.dm
@@ -15,10 +15,6 @@ var/list/department_radio_keys = list(
":t" = "Syndicate", "#t" = "Syndicate", ".t" = "Syndicate",
":u" = "Supply", "#u" = "Supply", ".u" = "Supply",
":g" = "changeling", "#g" = "changeling", ".g" = "changeling",
- ":k" = "skrell", "#k" = "skrell", ".k" = "skrell",
- ":j" = "tajaran", "#j" = "tajaran", ".j" = "tajaran",
- ":o" = "soghun", "#o" = "soghun", ".o" = "soghun",
- ":v" = "vox", "#v" = "vox", ".v" = "vox",
":R" = "right hand", "#R" = "right hand", ".R" = "right hand",
":L" = "left hand", "#L" = "left hand", ".L" = "left hand",
@@ -35,10 +31,6 @@ var/list/department_radio_keys = list(
":T" = "Syndicate", "#T" = "Syndicate", ".T" = "Syndicate",
":U" = "Supply", "#U" = "Supply", ".U" = "Supply",
":G" = "changeling", "#G" = "changeling", ".G" = "changeling",
- ":K" = "skrell", "#K" = "skrell", ".K" = "skrell",
- ":J" = "tajaran", "#J" = "tajaran", ".J" = "tajaran",
- ":O" = "soghun", "#O" = "soghun", ".O" = "soghun",
- ":V" = "vox", "#V" = "vox", ".V" = "vox",
//kinda localization -- rastaf0
//same keys as above, but on russian keyboard layout. This file uses cp1251 as encoding.
@@ -56,10 +48,7 @@ var/list/department_radio_keys = list(
":ô" = "alientalk", "#ô" = "alientalk", ".ô" = "alientalk",
":å" = "Syndicate", "#å" = "Syndicate", ".å" = "Syndicate",
":é" = "Supply", "#é" = "Supply", ".é" = "Supply",
- ":ï" = "changeling", "#ï" = "changeling", ".ï" = "changeling",
- ":ë" = "skrell", "#ë" = "skrell", ".ë" = "skrell",
- ":î" = "tajaran", "#î" = "tajaran", ".î" = "tajaran",
- ":ù" = "soghun", "#ù" = "soghun", ".ù" = "soghun"
+ ":ï" = "changeling", "#ï" = "changeling", ".ï" = "changeling"
)
/mob/living/proc/binarycheck()
@@ -86,14 +75,27 @@ var/list/department_radio_keys = list(
/mob/living/say(var/message)
+ /*
+ Formatting and sanitizing.
+ */
+
message = trim(copytext(sanitize(message), 1, MAX_MESSAGE_LEN))
message = capitalize(message)
+ /*
+ Sanity checking and speech failure.
+ */
+
if (!message)
return
- if (stat == 2)
+ if(silent)
+ return
+
+ if (stat == 2) // Dead.
return say_dead(message)
+ else if (stat) // Unconcious.
+ return
if (src.client)
if(client.prefs.muted & MUTE_IC)
@@ -102,94 +104,74 @@ var/list/department_radio_keys = list(
if (src.client.handle_spam_prevention(message,MUTE_IC))
return
- // stat == 2 is handled above, so this stops transmission of uncontious messages
- if (stat)
- return
-
// Mute disability
if (sdisabilities & MUTE)
return
+ // Muzzled.
if (istype(wear_mask, /obj/item/clothing/mask/muzzle))
return
- // emotes
+ // Emotes.
if (copytext(message, 1, 2) == "*" && !stat)
return emote(copytext(message, 2))
+ /*
+ Identity hiding.
+ */
var/alt_name = ""
if (istype(src, /mob/living/carbon/human) && name != GetVoice())
var/mob/living/carbon/human/H = src
alt_name = " (as [H.get_id_name("Unknown")])"
+
+ /*
+ Now we get into the real meat of the say processing. Determining the message mode.
+ */
+
var/italics = 0
var/message_range = null
var/message_mode = null
var/datum/language/speaking = null //For use if a specific language is being spoken.
- // If brain damaged, talk on headset at random.
- if (getBrainLoss() >= 60 && prob(50))
- if (ishuman(src))
- message_mode = "headset"
+ var/braindam = getBrainLoss()
+ if (braindam >= 60)
+ if(prob(braindam/4))
+ message = stutter(message)
+ if(prob(braindam))
+ message = uppertext(message)
// General public key. Special message handling
- else if (copytext(message, 1, 2) == ";")
+ else if (copytext(message, 1, 2) == ";" || prob(braindam/2))
if (ishuman(src))
message_mode = "headset"
else if(ispAI(src) || isrobot(src))
message_mode = "pAI"
message = copytext(message, 2)
-
+ // Begin checking for either a message mode or a language to speak.
else if (length(message) >= 2)
var/channel_prefix = copytext(message, 1, 3)
//Check if the person is speaking a language that they know.
- for(var/datum/language/L in languages)
- if(lowertext(channel_prefix) == ":[L.key]")
- speaking = L
- break
+ if(languages.len)
+ for(var/datum/language/L in languages)
+ if(lowertext(channel_prefix) == ":[L.key]")
+ speaking = L
+ break
message_mode = department_radio_keys[channel_prefix]
- if (message_mode)
+ if (message_mode || speaking || copytext(message,1,2) == ":")
message = trim(copytext(message, 3))
- if (!(ishuman(src) || istype(src, /mob/living/simple_animal/parrot) || isrobot(src) && (message_mode=="department" || (message_mode in radiochannels))))
+ if (!(istype(src,/mob/living/carbon/human) || istype(src,/mob/living/carbon/monkey) || istype(src, /mob/living/simple_animal/parrot) || isrobot(src) && (message_mode=="department" || (message_mode in radiochannels))))
message_mode = null //only humans can use headsets
- // Check changed so that parrots can use headsets. Other simple animals do not have ears and will cause runtimes.
- // And borgs -Sieve
if(src.stunned > 2 || (traumatic_shock > 61 && prob(50)))
- message_mode = "" //Stunned people shouldn't be able to physically turn on their radio/hold down the button to speak into it
+ message_mode = null //Stunned people shouldn't be able to physically turn on their radio/hold down the button to speak into it
if (!message)
return
- // :downs:
- if (getBrainLoss() >= 60)
- message = replacetext(message, " am ", " ")
- message = replacetext(message, " is ", " ")
- message = replacetext(message, " are ", " ")
- message = replacetext(message, "you", "u")
- message = replacetext(message, "help", "halp")
- message = replacetext(message, "grief", "grife")
- message = replacetext(message, "space", "spess")
- message = replacetext(message, "carp", "crap")
- message = replacetext(message, "reason", "raisin")
- if(prob(50))
- message = uppertext(message)
- message += "[stutter(pick("!", "!!", "!!!"))]"
- if(!stuttering && prob(15))
- message = stutter(message)
-
if (stuttering)
message = stutter(message)
-/* //qw do not have beesease atm.
- if(virus)
- if(virus.name=="beesease" && virus.stage>=2)
- if(prob(virus.stage*10))
- var/bzz = length(message)
- message = "B"
- for(var/i=0,iCommunication circuits remain unitialized."
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index ab6739d744..621b375aee 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -9,7 +9,6 @@
var/sight_mode = 0
var/custom_name = ""
- var/base_icon
var/custom_sprite = 0 //Due to all the sprites involved, a var for our custom borgs may be best
var/crisis //Admin-settable for combat module use.
@@ -204,6 +203,8 @@
if("Engineering")
module = new /obj/item/weapon/robot_module/engineering(src)
channels = list("Engineering" = 1)
+ if(camera && "Robots" in camera.network)
+ camera.network.Add("Engineering")
module_sprites["Basic"] = "Engineering"
module_sprites["Antique"] = "engineerrobot"
module_sprites["Landmate"] = "landmate"
@@ -232,7 +233,6 @@
choose_icon(6,module_sprites)
radio.config(channels)
- base_icon = icon_state
/mob/living/silicon/robot/proc/updatename(var/prefix as text)
if(prefix)
@@ -697,6 +697,8 @@
if(!opened)//Cover is closed
if(locked)
if(prob(90))
+ var/obj/item/weapon/card/emag/emag = W
+ emag.uses--
user << "You emag the cover lock."
locked = 0
else
@@ -1007,9 +1009,11 @@
if(module_active && istype(module_active,/obj/item/borg/combat/shield))
overlays += "[icon_state]-shield"
- if(base_icon)
+ if(modtype == "Combat")
+ var/base_icon = ""
+ base_icon = icon_state
if(module_active && istype(module_active,/obj/item/borg/combat/mobility))
- icon_state = "[base_icon]-roll"
+ icon_state = "[icon_state]-roll"
else
icon_state = base_icon
return
@@ -1229,8 +1233,8 @@
var/icontype
- if (src.name == "Lucy" && src.ckey == "rowtree")
- icontype = "Lucy"
+ if (custom_sprite == 1)
+ icontype = "Custom"
triesleft = 0
else
icontype = input("Select an icon! [triesleft ? "You have [triesleft] more chances." : "This is your last try."]", "Robot", null, null) in module_sprites
@@ -1240,11 +1244,9 @@
else
src << "Something is badly wrong with the sprite selection. Harass a coder."
icon_state = module_sprites[1]
- base_icon = icon_state
return
overlays -= "eyes"
- base_icon = icon_state
updateicon()
if (triesleft >= 1)
diff --git a/code/modules/mob/living/silicon/robot/robot_modules.dm b/code/modules/mob/living/silicon/robot/robot_modules.dm
index fc3a815266..ab4080ed50 100644
--- a/code/modules/mob/living/silicon/robot/robot_modules.dm
+++ b/code/modules/mob/living/silicon/robot/robot_modules.dm
@@ -65,6 +65,7 @@
..()
src.modules += new /obj/item/borg/sight/hud/med(src)
src.modules += new /obj/item/device/healthanalyzer(src)
+ src.modules += new /obj/item/device/reagent_scanner/adv(src)
src.modules += new /obj/item/weapon/reagent_containers/borghypo(src)
src.modules += new /obj/item/weapon/reagent_containers/glass/beaker/large(src)
src.modules += new /obj/item/weapon/reagent_containers/robodropper(src)
diff --git a/code/modules/mob/living/silicon/robot/say.dm b/code/modules/mob/living/silicon/robot/say.dm
deleted file mode 100644
index f71a96a9d9..0000000000
--- a/code/modules/mob/living/silicon/robot/say.dm
+++ /dev/null
@@ -1,24 +0,0 @@
-/mob/living/silicon/robot/say_understands(var/other)
- if (istype(other, /mob/living/silicon/ai))
- return 1
- if (istype(other, /mob/living/silicon/decoy))
- return 1
- if (istype(other, /mob/living/carbon/human))
- return 1
- if (istype(other, /mob/living/carbon/brain))
- return 1
- if (istype(other, /mob/living/silicon/pai))
- return 1
-// if (istype(other, /mob/living/silicon/hivebot))
-// return 1
- return ..()
-
-/mob/living/silicon/robot/say_quote(var/text)
- var/ending = copytext(text, length(text))
-
- if (ending == "?")
- return "queries, \"[text]\"";
- else if (ending == "!")
- return "declares, \"[text]\"";
-
- return "states, \"[text]\"";
diff --git a/code/modules/mob/living/silicon/say.dm b/code/modules/mob/living/silicon/say.dm
index 6ef24637b9..4dac67e885 100644
--- a/code/modules/mob/living/silicon/say.dm
+++ b/code/modules/mob/living/silicon/say.dm
@@ -1,3 +1,13 @@
+/mob/living/silicon/say_quote(var/text)
+ var/ending = copytext(text, length(text))
+
+ if (ending == "?")
+ return "queries, \"[text]\"";
+ else if (ending == "!")
+ return "declares, \"[copytext(text, 1, length(text))]\"";
+
+ return "states, \"[text]\"";
+
/mob/living/silicon/say(var/message)
if (!message)
return
diff --git a/code/modules/mob/living/simple_animal/borer.dm b/code/modules/mob/living/simple_animal/borer.dm
index b457f4b15b..d3166dd974 100644
--- a/code/modules/mob/living/simple_animal/borer.dm
+++ b/code/modules/mob/living/simple_animal/borer.dm
@@ -154,9 +154,9 @@
host.ckey = src.ckey
controlling = 1
- host.verbs += /mob/living/carbon/human/proc/release_control
- host.verbs += /mob/living/carbon/human/proc/punish_host
- host.verbs += /mob/living/carbon/human/proc/spawn_larvae
+ host.verbs += /mob/living/carbon/proc/release_control
+ host.verbs += /mob/living/carbon/proc/punish_host
+ host.verbs += /mob/living/carbon/proc/spawn_larvae
/mob/living/simple_animal/borer/verb/secrete_chemicals()
set category = "Alien"
@@ -220,8 +220,11 @@ mob/living/simple_animal/borer/proc/detatch()
if(!host) return
- var/datum/organ/external/head = host.get_organ("head")
- head.implants -= src
+ if(istype(host,/mob/living/carbon/human))
+ var/mob/living/carbon/human/H = host
+ var/datum/organ/external/head = H.get_organ("head")
+ head.implants -= src
+
src.loc = get_turf(host)
controlling = 0
@@ -231,9 +234,9 @@ mob/living/simple_animal/borer/proc/detatch()
host.reset_view(null)
host.machine = null
- host.verbs -= /mob/living/carbon/human/proc/release_control
- host.verbs -= /mob/living/carbon/human/proc/punish_host
- host.verbs -= /mob/living/carbon/human/proc/spawn_larvae
+ host.verbs -= /mob/living/carbon/proc/release_control
+ host.verbs -= /mob/living/carbon/proc/punish_host
+ host.verbs -= /mob/living/carbon/proc/spawn_larvae
if(host_brain.ckey)
src.ckey = host.ckey
@@ -244,7 +247,7 @@ mob/living/simple_animal/borer/proc/detatch()
host = null
-/mob/living/simple_animal/borer/verb/infest(var/mob/living/carbon/human/H)
+/mob/living/simple_animal/borer/verb/infest()
set category = "Alien"
set name = "Infest"
set desc = "Infest a suitable humanoid host."
@@ -258,11 +261,11 @@ mob/living/simple_animal/borer/proc/detatch()
return
var/list/choices = list()
- for(var/mob/living/carbon/human/C in view(1,src))
- if(istype(C,/mob/living/carbon/human) && C.stat != 2)
+ for(var/mob/living/carbon/C in view(1,src))
+ if(C.stat != 2)
choices += C
- var/mob/living/carbon/human/M = input(src,"Who do you wish to infest?") in null|choices
+ var/mob/living/carbon/M = input(src,"Who do you wish to infest?") in null|choices
if(!M || !src) return
@@ -293,10 +296,13 @@ mob/living/simple_animal/borer/proc/detatch()
M << "Something disgusting and slimy wiggles into your ear!"
src.host = M
- var/datum/organ/external/head = M.get_organ("head")
- head.implants += src
src.loc = M
+ if(istype(M,/mob/living/carbon/human))
+ var/mob/living/carbon/human/H = M
+ var/datum/organ/external/head = H.get_organ("head")
+ head.implants += src
+
host_brain.name = M.name
host_brain.real_name = M.real_name
diff --git a/code/modules/mob/living/simple_animal/friendly/spiderbot.dm b/code/modules/mob/living/simple_animal/friendly/spiderbot.dm
index 30bf04c7ad..71e38d0ba5 100644
--- a/code/modules/mob/living/simple_animal/friendly/spiderbot.dm
+++ b/code/modules/mob/living/simple_animal/friendly/spiderbot.dm
@@ -125,6 +125,8 @@
user << "\red [src] is already overloaded - better run."
return 0
else
+ var/obj/item/weapon/card/emag/emag = O
+ emag.uses--
emagged = 1
user << "\blue You short out the security protocols and overload [src]'s cell, priming it to explode in a short time."
spawn(100) src << "\red Your cell seems to be outputting a lot of power..."
diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm
index 74bd81ca93..73eb427c36 100644
--- a/code/modules/mob/living/simple_animal/simple_animal.dm
+++ b/code/modules/mob/living/simple_animal/simple_animal.dm
@@ -9,7 +9,6 @@
var/icon_gib = null //We only try to show a gibbing animation if this exists.
var/list/speak = list()
- var/list/speak_emote = list()// Emotes while speaking IE: Ian [emote], [text] -- Ian barks, "WOOF!". Spoken text is generated from the speak variable.
var/speak_chance = 0
var/list/emote_hear = list() //Hearable emotes
var/list/emote_see = list() //Unlike speak_emote, the list of things in this variable only show by themselves with no spoken text. IE: Ian barks, Ian yaps
@@ -217,13 +216,6 @@
new meat_type(src.loc)
..()
-/mob/living/simple_animal/say_quote(var/text)
- if(speak_emote && speak_emote.len)
- var/emote = pick(speak_emote)
- if(emote)
- return "[emote], \"[text]\""
- return "says, \"[text]\"";
-
/mob/living/simple_animal/emote(var/act, var/type, var/desc)
if(act)
if(act == "scream") act = "whimper" //ugly hack to stop animals screaming when crushed :P
diff --git a/code/modules/mob/logout.dm b/code/modules/mob/logout.dm
index 5627dbd43b..bb01c846ca 100644
--- a/code/modules/mob/logout.dm
+++ b/code/modules/mob/logout.dm
@@ -1,4 +1,5 @@
/mob/Logout()
+ nanomanager.user_logout(src) // this is used to clean up (remove) this user's Nano UIs
player_list -= src
log_access("Logout: [key_name(src)]")
if(admin_datums[src.ckey])
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 5c833ff8aa..450516c576 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -719,6 +719,7 @@ note dizziness decrements automatically in the mob's Life() proc.
stat(null,"Mch-[master_controller.machines_cost]\t#[machines.len]")
stat(null,"Obj-[master_controller.objects_cost]\t#[processing_objects.len]")
stat(null,"Net-[master_controller.networks_cost]\tPnet-[master_controller.powernets_cost]")
+ stat(null,"NanoUI-[master_controller.nano_cost]\t#[nanomanager.processing_uis.len]")
stat(null,"Tick-[master_controller.ticker_cost]\tALL-[master_controller.total_cost]")
else
stat(null,"MasterController-ERROR")
diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm
index b82f6736bb..f876e5a1ce 100644
--- a/code/modules/mob/mob_defines.dm
+++ b/code/modules/mob/mob_defines.dm
@@ -86,9 +86,11 @@
var/lastpuke = 0
var/unacidable = 0
var/small = 0
- var/list/pinned = list() //List of things pinning this creature to walls (see living_defense.dm)
- var/list/embedded = list() //Embedded items, since simple mobs don't have organs.
- var/list/languages = list() // For speaking/listening.
+ var/list/pinned = list() //List of things pinning this creature to walls (see living_defense.dm)
+ var/list/embedded = list() //Embedded items, since simple mobs don't have organs.
+ var/list/languages = list() // For speaking/listening.
+ var/list/speak_emote = list("says") //Verbs used when speaking. Defaults to 'say' if speak_emote is null.
+
var/name_archive //For admin things like possession
var/timeofdeath = 0.0//Living
@@ -155,8 +157,6 @@
//see: setup.dm for list of mutations
var/voice_name = "unidentifiable voice"
- var/voice_message = null // When you are not understood by others (replaced with just screeches, hisses, chimpers etc.)
- var/say_message = null // When you are understood by others. Currently only used by aliens and monkeys in their say_quote procs
var/faction = "neutral" //Used for checking whether hostile simple animals will attack you, possibly more stuff later
diff --git a/code/modules/mob/new_player/new_player.dm b/code/modules/mob/new_player/new_player.dm
index c1e808a6bd..5c354cc54a 100644
--- a/code/modules/mob/new_player/new_player.dm
+++ b/code/modules/mob/new_player/new_player.dm
@@ -74,7 +74,10 @@
if(ticker.hide_mode)
stat("Game Mode:", "Secret")
else
- stat("Game Mode:", "[master_mode]")
+ if(ticker.hide_mode == 0)
+ stat("Game Mode:", "[master_mode]") // Old setting for showing the game mode
+ else
+ stat("Game Mode: ", "Secret")
if((ticker.current_state == GAME_STATE_PREGAME) && going)
stat("Time To Start:", ticker.pregame_timeleft)
@@ -354,10 +357,10 @@
var/datum/language/chosen_language
if(client.prefs.language)
- chosen_language = all_languages[client.prefs.language]
+ chosen_language = all_languages["[client.prefs.language]"]
if(chosen_language)
if(is_alien_whitelisted(src, client.prefs.language) || !config.usealienwhitelist || !(chosen_language.flags & WHITELISTED))
- new_character.add_language(client.prefs.language)
+ new_character.add_language("[client.prefs.language]")
if(ticker.random_players)
new_character.gender = pick(MALE, FEMALE)
diff --git a/code/modules/mob/say.dm b/code/modules/mob/say.dm
index 5a5d1d4aa2..f4a99e097f 100644
--- a/code/modules/mob/say.dm
+++ b/code/modules/mob/say.dm
@@ -62,8 +62,12 @@
return
/mob/proc/say_understands(var/mob/other,var/datum/language/speaking = null)
+
if(!other)
return 1
+ //Universal speak makes everything understandable, for obvious reasons.
+ else if(other.universal_speak || src.universal_speak)
+ return 1
else if (src.stat == 2)
return 1
else if (speaking) //Language check.
@@ -83,11 +87,12 @@
return 1
else if(isAI(src) && ispAI(other))
return 1
- else if (istype(other, src.type) || istype(src, other.type))
+ else if (istype(other, src.type) || istype(src, other.type))
return 1
return 0
/mob/proc/say_quote(var/text,var/datum/language/speaking)
+
if(!text)
return "says, \"...\""; //not the best solution, but it will stop a large number of runtimes. The cause is somewhere in the Tcomms code
//tcomms code is still runtiming somewhere here
@@ -97,6 +102,8 @@
if (speaking)
speechverb = "[speaking.speech_verb], \""
+ else if(speak_emote && speak_emote.len)
+ speechverb = "[pick(speak_emote)], \""
else if (src.stuttering)
speechverb = "stammers, \""
else if (src.slurring)
diff --git a/code/modules/mob/screen.dm b/code/modules/mob/screen.dm
index b36e3e7516..7e65b4048e 100644
--- a/code/modules/mob/screen.dm
+++ b/code/modules/mob/screen.dm
@@ -631,9 +631,9 @@
H.name = "host brain"
H.real_name = "host brain"
- verbs -= /mob/living/carbon/human/proc/release_control
- verbs -= /mob/living/carbon/human/proc/punish_host
- verbs -= /mob/living/carbon/human/proc/spawn_larvae
+ verbs -= /mob/living/carbon/proc/release_control
+ verbs -= /mob/living/carbon/proc/punish_host
+ verbs -= /mob/living/carbon/proc/spawn_larvae
return
diff --git a/code/modules/nano/JSON Reader.dm b/code/modules/nano/JSON Reader.dm
new file mode 100644
index 0000000000..12edf1dd5c
--- /dev/null
+++ b/code/modules/nano/JSON Reader.dm
@@ -0,0 +1,205 @@
+json_token
+ var
+ value
+ New(v)
+ src.value = v
+ text
+ number
+ word
+ symbol
+ eof
+
+json_reader
+ var
+ list
+ string = list("'", "\"")
+ symbols = list("{", "}", "\[", "]", ":", "\"", "'", ",")
+ sequences = list("b" = 8, "t" = 9, "n" = 10, "f" = 12, "r" = 13)
+ tokens
+ json
+ i = 1
+
+
+ proc
+ // scanner
+ ScanJson(json)
+ src.json = json
+ . = new/list()
+ src.i = 1
+ while(src.i <= lentext(json))
+ var/char = get_char()
+ if(is_whitespace(char))
+ i++
+ continue
+ if(string.Find(char))
+ . += read_string(char)
+ else if(symbols.Find(char))
+ . += new/json_token/symbol(char)
+ else if(is_digit(char))
+ . += read_number()
+ else
+ . += read_word()
+ i++
+ . += new/json_token/eof()
+
+ read_word()
+ var/val = ""
+ while(i <= lentext(json))
+ var/char = get_char()
+ if(is_whitespace(char) || symbols.Find(char))
+ i-- // let scanner handle this character
+ return new/json_token/word(val)
+ val += char
+ i++
+
+ read_string(delim)
+ var
+ escape = FALSE
+ val = ""
+ while(++i <= lentext(json))
+ var/char = get_char()
+ if(escape)
+ switch(char)
+ if("\\", "'", "\"", "/", "u")
+ val += char
+ else
+ // TODO: support octal, hex, unicode sequences
+ ASSERT(sequences.Find(char))
+ val += ascii2text(sequences[char])
+ else
+ if(char == delim)
+ return new/json_token/text(val)
+ else if(char == "\\")
+ escape = TRUE
+ else
+ val += char
+ CRASH("Unterminated string.")
+
+ read_number()
+ var/val = ""
+ var/char = get_char()
+ while(is_digit(char) || char == "." || lowertext(char) == "e")
+ val += char
+ i++
+ char = get_char()
+ i-- // allow scanner to read the first non-number character
+ return new/json_token/number(text2num(val))
+
+ check_char()
+ ASSERT(args.Find(get_char()))
+
+ get_char()
+ return copytext(json, i, i+1)
+
+ is_whitespace(char)
+ return char == " " || char == "\t" || char == "\n" || text2ascii(char) == 13
+
+ is_digit(char)
+ var/c = text2ascii(char)
+ return 48 <= c && c <= 57 || char == "+" || char == "-"
+
+
+ // parser
+ ReadObject(list/tokens)
+ src.tokens = tokens
+ . = new/list()
+ i = 1
+ read_token("{", /json_token/symbol)
+ while(i <= tokens.len)
+ var/json_token/K = get_token()
+ check_type(/json_token/word, /json_token/text)
+ next_token()
+ read_token(":", /json_token/symbol)
+
+ .[K.value] = read_value()
+
+ var/json_token/S = get_token()
+ check_type(/json_token/symbol)
+ switch(S.value)
+ if(",")
+ next_token()
+ continue
+ if("}")
+ next_token()
+ return
+ else
+ die()
+
+ get_token()
+ return tokens[i]
+
+ next_token()
+ return tokens[++i]
+
+ read_token(val, type)
+ var/json_token/T = get_token()
+ if(!(T.value == val && istype(T, type)))
+ CRASH("Expected '[val]', found '[T.value]'.")
+ next_token()
+ return T
+
+ check_type(...)
+ var/json_token/T = get_token()
+ for(var/type in args)
+ if(istype(T, type))
+ return
+ CRASH("Bad token type: [T.type].")
+
+ check_value(...)
+ var/json_token/T = get_token()
+ ASSERT(args.Find(T.value))
+
+ read_key()
+ var/char = get_char()
+ if(char == "\"" || char == "'")
+ return read_string(char)
+
+ read_value()
+ var/json_token/T = get_token()
+ switch(T.type)
+ if(/json_token/text, /json_token/number)
+ next_token()
+ return T.value
+ if(/json_token/word)
+ next_token()
+ switch(T.value)
+ if("true")
+ return TRUE
+ if("false")
+ return FALSE
+ if("null")
+ return null
+ if(/json_token/symbol)
+ switch(T.value)
+ if("\[")
+ return read_array()
+ if("{")
+ return ReadObject(tokens.Copy(i))
+ die()
+
+ read_array()
+ read_token("\[", /json_token/symbol)
+ . = new/list()
+ var/list/L = .
+ while(i <= tokens.len)
+ // Avoid using Add() or += in case a list is returned.
+ L.len++
+ L[L.len] = read_value()
+ var/json_token/T = get_token()
+ check_type(/json_token/symbol)
+ switch(T.value)
+ if(",")
+ next_token()
+ continue
+ if("]")
+ next_token()
+ return
+ else
+ die()
+ next_token()
+ CRASH("Unterminated array.")
+
+
+ die(json_token/T)
+ if(!T) T = get_token()
+ CRASH("Unexpected token: [T.value].")
\ No newline at end of file
diff --git a/code/modules/nano/JSON Writer.dm b/code/modules/nano/JSON Writer.dm
new file mode 100644
index 0000000000..95c0cb7578
--- /dev/null
+++ b/code/modules/nano/JSON Writer.dm
@@ -0,0 +1,54 @@
+
+json_writer
+ proc
+ WriteObject(list/L)
+ . = "{"
+ var/i = 1
+ for(var/k in L)
+ var/val = L[k]
+ . += {"\"[k]\":[write(val)]"}
+ if(i++ < L.len)
+ . += ","
+ .+= "}"
+
+ write(val)
+ if(isnum(val))
+ return num2text(val, 100)
+ else if(isnull(val))
+ return "null"
+ else if(istype(val, /list))
+ if(is_associative(val))
+ return WriteObject(val)
+ else
+ return write_array(val)
+ else
+ . += write_string("[val]")
+
+ write_array(list/L)
+ . = "\["
+ for(var/i = 1 to L.len)
+ . += write(L[i])
+ if(i < L.len)
+ . += ","
+ . += "]"
+
+ write_string(txt)
+ var/static/list/json_escape = list("\\", "\"", "'", "\n")
+ for(var/targ in json_escape)
+ var/start = 1
+ while(start <= lentext(txt))
+ var/i = findtext(txt, targ, start)
+ if(!i)
+ break
+ if(targ == "\n")
+ txt = copytext(txt, 1, i) + "\\n" + copytext(txt, i+1)
+ else
+ txt = copytext(txt, 1, i) + "\\" + copytext(txt, i)
+ start = i + 2
+ return {""[txt]""}
+
+ is_associative(list/L)
+ for(var/key in L)
+ // if the key is a list that means it's actually an array of lists (stupid Byond...)
+ if(!isnum(key) && !istype(key, /list))
+ return TRUE
diff --git a/code/modules/nano/_JSON.dm b/code/modules/nano/_JSON.dm
new file mode 100644
index 0000000000..5692e643ba
--- /dev/null
+++ b/code/modules/nano/_JSON.dm
@@ -0,0 +1,12 @@
+/*
+n_Json v11.3.21
+*/
+
+proc
+ json2list(json)
+ var/static/json_reader/_jsonr = new()
+ return _jsonr.ReadObject(_jsonr.ScanJson(json))
+
+ list2json(list/L)
+ var/static/json_writer/_jsonw = new()
+ return _jsonw.WriteObject(L)
diff --git a/code/modules/nano/nanoexternal.dm b/code/modules/nano/nanoexternal.dm
new file mode 100644
index 0000000000..7efe4dd477
--- /dev/null
+++ b/code/modules/nano/nanoexternal.dm
@@ -0,0 +1,6 @@
+// All movable things can have a Nano UI, always use ui_interact to open/interact with a Nano UI
+/atom/movable/proc/ui_interact(mob/user, ui_key = "main")
+ return
+
+// Used by the Nano UI Manager (/datum/nanomanager) to track UIs opened by this mob
+/mob/var/list/open_uis = list()
diff --git a/code/modules/nano/nanomanager.dm b/code/modules/nano/nanomanager.dm
new file mode 100644
index 0000000000..c3edfa04ea
--- /dev/null
+++ b/code/modules/nano/nanomanager.dm
@@ -0,0 +1,69 @@
+// This is the window/UI manager for Nano UI
+// There should only ever be one (global) instance of nanomanger
+/datum/nanomanager
+ var/open_uis[0]
+ var/list/processing_uis = list()
+
+/datum/nanomanager/New()
+ return
+
+/datum/nanomanager/proc/get_open_ui(var/mob/user, src_object, ui_key)
+ var/src_object_key = "\ref[src_object]"
+ if (isnull(open_uis[src_object_key]) || !istype(open_uis[src_object_key], /list))
+ return null
+ else if (isnull(open_uis[src_object_key][ui_key]) || !istype(open_uis[src_object_key][ui_key], /list))
+ return null
+
+ for (var/datum/nanoui/ui in open_uis[src_object_key][ui_key])
+ if (ui.user == user)
+ return ui
+
+ return null
+
+/datum/nanomanager/proc/update_uis(src_object)
+ var/src_object_key = "\ref[src_object]"
+ if (isnull(open_uis[src_object_key]) || !istype(open_uis[src_object_key], /list))
+ return 0
+
+ var/update_count = 0
+ for (var/ui_key in open_uis[src_object_key])
+ for (var/datum/nanoui/ui in open_uis[src_object_key][ui_key])
+ if(ui && ui.src_object && ui.user)
+ ui.process()
+ update_count++
+ return update_count
+
+/datum/nanomanager/proc/ui_opened(var/datum/nanoui/ui)
+ var/src_object_key = "\ref[ui.src_object]"
+ if (isnull(open_uis[src_object_key]) || !istype(open_uis[src_object_key], /list))
+ open_uis[src_object_key] = list(ui.ui_key = list())
+ else if (isnull(open_uis[src_object_key][ui.ui_key]) || !istype(open_uis[src_object_key][ui.ui_key], /list))
+ open_uis[src_object_key][ui.ui_key] = list();
+
+ ui.user.open_uis.Add(ui)
+ var/list/uis = open_uis[src_object_key][ui.ui_key]
+ uis.Add(ui)
+ processing_uis.Add(ui)
+
+/datum/nanomanager/proc/ui_closed(var/datum/nanoui/ui)
+ var/src_object_key = "\ref[ui.src_object]"
+ if (isnull(open_uis[src_object_key]) || !istype(open_uis[src_object_key], /list))
+ return 0 // wasn't open
+ else if (isnull(open_uis[src_object_key][ui.ui_key]) || !istype(open_uis[src_object_key][ui.ui_key], /list))
+ return 0 // wasn't open
+
+ processing_uis.Remove(ui)
+ ui.user.open_uis.Remove(ui)
+ var/list/uis = open_uis[src_object_key][ui.ui_key]
+ return uis.Remove(ui)
+
+// user has logged out (or is switching mob) so close/clear all uis
+/datum/nanomanager/proc/user_logout(var/mob/user)
+ if (isnull(user.open_uis) || !istype(user.open_uis, /list) || open_uis.len == 0)
+ return 0 // has no open uis
+
+ for (var/datum/nanoui/ui in user.open_uis)
+ ui.close();
+
+
+
diff --git a/code/modules/nano/nanoui.dm b/code/modules/nano/nanoui.dm
new file mode 100644
index 0000000000..add6437c3b
--- /dev/null
+++ b/code/modules/nano/nanoui.dm
@@ -0,0 +1,251 @@
+/datum/nanoui
+ var/mob/user
+ var/atom/movable/src_object
+ var/title
+ var/ui_key
+ var/window_id // window_id is used as the window name for browse and onclose
+ var/width = 0
+ var/height = 0
+ var/atom/ref = null
+ var/on_close_logic = 1
+ var/window_options = "focus=0;can_close=1;can_minimize=1;can_maximize=0;can_resize=1;titlebar=1;" // window option is set using window_id
+ var/list/stylesheets = list()
+ var/list/scripts = list()
+ var/templates[0]
+ var/title_image
+ var/head_elements
+ var/body_elements
+ var/head_content = ""
+ var/content = "" // the #mainTemplate div will contain the compiled "main" template html
+ var/list/initial_data[0]
+ var/is_auto_updating = 0
+ var/status = 2
+
+
+/datum/nanoui/New(nuser, nsrc_object, nui_key, ntemplate, ntitle = 0, nwidth = 0, nheight = 0, var/atom/nref = null)
+ user = nuser
+ src_object = nsrc_object
+ ui_key = nui_key
+ window_id = "[ui_key]\ref[src_object]"
+
+ add_template("main", ntemplate)
+
+ if (ntitle)
+ title = ntitle
+ if (nwidth)
+ width = nwidth
+ if (nheight)
+ height = nheight
+ if (nref)
+ ref = nref
+
+ add_common_assets()
+
+/datum/nanoui/proc/add_common_assets()
+ add_script("libraries.min.js") // The jQuery library
+ add_script("nano_update.js") // The NanoUpdate JS, this is used to receive updates and apply them.
+ add_script("nano_config.js") // The NanoUpdate JS, this is used to receive updates and apply them.
+ add_script("nano_base_helpers.js") // The NanoBaseHelpers JS, this is used to set up template helpers which are common to all templates
+ add_stylesheet("shared.css") // this CSS sheet is common to all UIs
+ add_stylesheet("icons.css") // this CSS sheet is common to all UIs
+
+/datum/nanoui/proc/set_status(state)
+ if (state != status)
+ status = state
+ push_data(list(), 1) // Update the UI
+ else
+ status = state
+
+/datum/nanoui/proc/set_auto_update(state = 1)
+ is_auto_updating = state
+
+/datum/nanoui/proc/set_initial_data(data)
+ initial_data = modify_data(data)
+
+/datum/nanoui/proc/add_head_content(nhead_content)
+ head_content = nhead_content
+
+/datum/nanoui/proc/set_window_options(nwindow_options)
+ window_options = nwindow_options
+
+/datum/nanoui/proc/set_title_image(ntitle_image)
+ //title_image = ntitle_image
+
+/datum/nanoui/proc/add_stylesheet(file)
+ stylesheets.Add(file)
+
+/datum/nanoui/proc/add_script(file)
+ scripts.Add(file)
+
+/datum/nanoui/proc/add_template(name, file)
+ templates[name] = file
+
+/datum/nanoui/proc/set_content(ncontent)
+ content = ncontent
+
+/datum/nanoui/proc/add_content(ncontent)
+ content += ncontent
+
+/datum/nanoui/proc/use_on_close_logic(nsetting)
+ on_close_logic = nsetting
+
+/datum/nanoui/proc/get_header()
+ for (var/filename in stylesheets)
+ head_content += ""
+
+ var/title_attributes = "id='uiTitle'"
+ if (title_image)
+ title_attributes = "id='uiTitle icon' style='background-image: url([title_image]);'"
+
+ var/templatel_data[0]
+ for (var/key in templates)
+ templatel_data[key] = templates[key];
+
+ var/template_data_json = "{}" // An empty JSON object
+ if (templatel_data.len > 0)
+ template_data_json = list2json(templatel_data)
+
+ var/initial_data_json = "{}" // An empty JSON object
+ if (initial_data.len > 0)
+ initial_data_json = list2json(initial_data)
+
+ var/url_parameters_json = list2json(list("src" = "\ref[src_object]"))
+
+ return {"
+
+
+
+ [head_content]
+
+
+
+
a";cd=b3.getElementsByTagName("*")||[];cb=b3.getElementsByTagName("a")[0];if(!cb||!cb.style||!cd.length){return ce}cc=l.createElement("select");b5=cc.appendChild(l.createElement("option"));ca=b3.getElementsByTagName("input")[0];cb.style.cssText="top:1px;float:left;opacity:.5";ce.getSetAttribute=b3.className!=="t";ce.leadingWhitespace=b3.firstChild.nodeType===3;ce.tbody=!b3.getElementsByTagName("tbody").length;ce.htmlSerialize=!!b3.getElementsByTagName("link").length;ce.style=/top/.test(cb.getAttribute("style"));ce.hrefNormalized=cb.getAttribute("href")==="/a";ce.opacity=/^0.5/.test(cb.style.opacity);ce.cssFloat=!!cb.style.cssFloat;ce.checkOn=!!ca.value;ce.optSelected=b5.selected;ce.enctype=!!l.createElement("form").enctype;ce.html5Clone=l.createElement("nav").cloneNode(true).outerHTML!=="<:nav>";ce.inlineBlockNeedsLayout=false;ce.shrinkWrapBlocks=false;ce.pixelPosition=false;ce.deleteExpando=true;ce.noCloneEvent=true;ce.reliableMarginRight=true;ce.boxSizingReliable=true;ca.checked=true;ce.noCloneChecked=ca.cloneNode(true).checked;cc.disabled=true;ce.optDisabled=!b5.disabled;try{delete b3.test}catch(b8){ce.deleteExpando=false}ca=l.createElement("input");ca.setAttribute("value","");ce.input=ca.getAttribute("value")==="";ca.value="t";ca.setAttribute("type","radio");ce.radioValue=ca.value==="t";ca.setAttribute("checked","t");ca.setAttribute("name","t");b9=l.createDocumentFragment();b9.appendChild(ca);ce.appendChecked=ca.checked;ce.checkClone=b9.cloneNode(true).cloneNode(true).lastChild.checked;if(b3.attachEvent){b3.attachEvent("onclick",function(){ce.noCloneEvent=false});b3.cloneNode(true).click()}for(b6 in {submit:true,change:true,focusin:true}){b3.setAttribute(b7="on"+b6,"t");ce[b6+"Bubbles"]=b7 in a1||b3.attributes[b7].expando===false}b3.style.backgroundClip="content-box";b3.cloneNode(true).style.backgroundClip="";ce.clearCloneStyle=b3.style.backgroundClip==="content-box";for(b6 in bI(ce)){break}ce.ownLast=b6!=="0";bI(function(){var cf,ci,ch,cg="padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",e=l.getElementsByTagName("body")[0];if(!e){return}cf=l.createElement("div");cf.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px";e.appendChild(cf).appendChild(b3);b3.innerHTML="
").append(bI.parseHTML(ca)).find(e):ca)}).complete(b9&&function(cb,ca){b3.each(b9,b4||[cb.responseText,ca,cb])})}return this};bI.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,b3){bI.fn[b3]=function(b4){return this.on(b3,b4)}});bI.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:W,type:"GET",isLocal:A.test(b1[1]),global:true,processData:true,async:true,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":aU,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":true,"text json":bI.parseJSON,"text xml":bI.parseXML},flatOptions:{url:true,context:true}},ajaxSetup:function(b3,e){return e?r(r(b3,bI.ajaxSettings),e):r(bI.ajaxSettings,b3)},ajaxPrefilter:bK(v),ajaxTransport:bK(a6),ajax:function(b7,b4){if(typeof b7==="object"){b4=b7;b7=aE}b4=b4||{};var cg,ci,b8,cn,cc,b3,cj,b5,cb=bI.ajaxSetup({},b4),cp=cb.context||cb,ce=cb.context&&(cp.nodeType||cp.jquery)?bI(cp):bI.event,co=bI.Deferred(),cl=bI.Callbacks("once memory"),b9=cb.statusCode||{},cf={},cm={},b6=0,ca="canceled",ch={readyState:0,getResponseHeader:function(cq){var e;if(b6===2){if(!b5){b5={};while((e=ae.exec(cn))){b5[e[1].toLowerCase()]=e[2]}}e=b5[cq.toLowerCase()]}return e==null?null:e},getAllResponseHeaders:function(){return b6===2?cn:null},setRequestHeader:function(cq,cr){var e=cq.toLowerCase();if(!b6){cq=cm[e]=cm[e]||cq;cf[cq]=cr}return this},overrideMimeType:function(e){if(!b6){cb.mimeType=e}return this},statusCode:function(cq){var e;if(cq){if(b6<2){for(e in cq){b9[e]=[b9[e],cq[e]]}}else{ch.always(cq[ch.status])}}return this},abort:function(cq){var e=cq||ca;if(cj){cj.abort(e)}cd(0,e);return this}};co.promise(ch).complete=cl.add;ch.success=ch.done;ch.error=ch.fail;cb.url=((b7||cb.url||W)+"").replace(an,"").replace(aF,b1[1]+"//");cb.type=b4.method||b4.type||cb.method||cb.type;cb.dataTypes=bI.trim(cb.dataType||"*").toLowerCase().match(aa)||[""];if(cb.crossDomain==null){cg=aS.exec(cb.url.toLowerCase());cb.crossDomain=!!(cg&&(cg[1]!==b1[1]||cg[2]!==b1[2]||(cg[3]||(cg[1]==="http:"?"80":"443"))!==(b1[3]||(b1[1]==="http:"?"80":"443"))))}if(cb.data&&cb.processData&&typeof cb.data!=="string"){cb.data=bI.param(cb.data,cb.traditional)}n(v,cb,b4,ch);if(b6===2){return ch}b3=cb.global;if(b3&&bI.active++===0){bI.event.trigger("ajaxStart")}cb.type=cb.type.toUpperCase();cb.hasContent=!o.test(cb.type);b8=cb.url;if(!cb.hasContent){if(cb.data){b8=(cb.url+=(ax.test(b8)?"&":"?")+cb.data);delete cb.data}if(cb.cache===false){cb.url=M.test(b8)?b8.replace(M,"$1_="+bN++):b8+(ax.test(b8)?"&":"?")+"_="+bN++}}if(cb.ifModified){if(bI.lastModified[b8]){ch.setRequestHeader("If-Modified-Since",bI.lastModified[b8])}if(bI.etag[b8]){ch.setRequestHeader("If-None-Match",bI.etag[b8])}}if(cb.data&&cb.hasContent&&cb.contentType!==false||b4.contentType){ch.setRequestHeader("Content-Type",cb.contentType)}ch.setRequestHeader("Accept",cb.dataTypes[0]&&cb.accepts[cb.dataTypes[0]]?cb.accepts[cb.dataTypes[0]]+(cb.dataTypes[0]!=="*"?", "+aU+"; q=0.01":""):cb.accepts["*"]);for(ci in cb.headers){ch.setRequestHeader(ci,cb.headers[ci])}if(cb.beforeSend&&(cb.beforeSend.call(cp,ch,cb)===false||b6===2)){return ch.abort()}ca="abort";for(ci in {success:1,error:1,complete:1}){ch[ci](cb[ci])}cj=n(a6,cb,b4,ch);if(!cj){cd(-1,"No Transport")}else{ch.readyState=1;if(b3){ce.trigger("ajaxSend",[ch,cb])}if(cb.async&&cb.timeout>0){cc=setTimeout(function(){ch.abort("timeout")},cb.timeout)}try{b6=1;cj.send(cf,cd)}catch(ck){if(b6<2){cd(-1,ck)}else{throw ck}}}function cd(cu,cq,cv,cs){var e,cy,cw,ct,cx,cr=cq;if(b6===2){return}b6=2;if(cc){clearTimeout(cc)}cj=aE;cn=cs||"";ch.readyState=cu>0?4:0;e=cu>=200&&cu<300||cu===304;if(cv){ct=g(cb,ch,cv)}ct=ad(cb,ct,ch,e);if(e){if(cb.ifModified){cx=ch.getResponseHeader("Last-Modified");if(cx){bI.lastModified[b8]=cx}cx=ch.getResponseHeader("etag");if(cx){bI.etag[b8]=cx}}if(cu===204||cb.type==="HEAD"){cr="nocontent"}else{if(cu===304){cr="notmodified"}else{cr=ct.state;cy=ct.data;cw=ct.error;e=!cw}}}else{cw=cr;if(cu||!cr){cr="error";if(cu<0){cu=0}}}ch.status=cu;ch.statusText=(cq||cr)+"";if(e){co.resolveWith(cp,[cy,cr,ch])}else{co.rejectWith(cp,[ch,cr,cw])}ch.statusCode(b9);b9=aE;if(b3){ce.trigger(e?"ajaxSuccess":"ajaxError",[ch,cb,e?cy:cw])}cl.fireWith(cp,[ch,cr]);if(b3){ce.trigger("ajaxComplete",[ch,cb]);if(!(--bI.active)){bI.event.trigger("ajaxStop")}}}return ch},getJSON:function(e,b3,b4){return bI.get(e,b3,b4,"json")},getScript:function(e,b3){return bI.get(e,aE,b3,"script")}});bI.each(["get","post"],function(e,b3){bI[b3]=function(b4,b6,b7,b5){if(bI.isFunction(b6)){b5=b5||b7;b7=b6;b6=aE}return bI.ajax({url:b4,type:b3,dataType:b5,data:b6,success:b7})}});function g(ca,b9,b6){var e,b5,b4,b7,b3=ca.contents,b8=ca.dataTypes;while(b8[0]==="*"){b8.shift();if(b5===aE){b5=ca.mimeType||b9.getResponseHeader("Content-Type")}}if(b5){for(b7 in b3){if(b3[b7]&&b3[b7].test(b5)){b8.unshift(b7);break}}}if(b8[0] in b6){b4=b8[0]}else{for(b7 in b6){if(!b8[0]||ca.converters[b7+" "+b8[0]]){b4=b7;break}if(!e){e=b7}}b4=b4||e}if(b4){if(b4!==b8[0]){b8.unshift(b4)}return b6[b4]}}function ad(ce,b6,cb,b4){var b3,b9,cc,b7,b5,cd={},ca=ce.dataTypes.slice();if(ca[1]){for(cc in ce.converters){cd[cc.toLowerCase()]=ce.converters[cc]}}b9=ca.shift();while(b9){if(ce.responseFields[b9]){cb[ce.responseFields[b9]]=b6}if(!b5&&b4&&ce.dataFilter){b6=ce.dataFilter(b6,ce.dataType)}b5=b9;b9=ca.shift();if(b9){if(b9==="*"){b9=b5}else{if(b5!=="*"&&b5!==b9){cc=cd[b5+" "+b9]||cd["* "+b9];if(!cc){for(b3 in cd){b7=b3.split(" ");if(b7[1]===b9){cc=cd[b5+" "+b7[0]]||cd["* "+b7[0]];if(cc){if(cc===true){cc=cd[b3]}else{if(cd[b3]!==true){b9=b7[0];ca.unshift(b7[1])}}break}}}}if(cc!==true){if(cc&&ce["throws"]){b6=cc(b6)}else{try{b6=cc(b6)}catch(b8){return{state:"parsererror",error:cc?b8:"No conversion from "+b5+" to "+b9}}}}}}}}return{state:"success",data:b6}}bI.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(e){bI.globalEval(e);return e}}});bI.ajaxPrefilter("script",function(e){if(e.cache===aE){e.cache=false}if(e.crossDomain){e.type="GET";e.global=false}});bI.ajaxTransport("script",function(b4){if(b4.crossDomain){var e,b3=l.head||bI("head")[0]||l.documentElement;return{send:function(b5,b6){e=l.createElement("script");e.async=true;if(b4.scriptCharset){e.charset=b4.scriptCharset}e.src=b4.url;e.onload=e.onreadystatechange=function(b8,b7){if(b7||!e.readyState||/loaded|complete/.test(e.readyState)){e.onload=e.onreadystatechange=null;if(e.parentNode){e.parentNode.removeChild(e)}e=null;if(!b7){b6(200,"success")}}};b3.insertBefore(e,b3.firstChild)},abort:function(){if(e){e.onload(aE,true)}}}}});var bp=[],a4=/(=)\?(?=&|$)|\?\?/;bI.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=bp.pop()||(bI.expando+"_"+(bN++));this[e]=true;return e}});bI.ajaxPrefilter("json jsonp",function(b5,e,b6){var b8,b3,b4,b7=b5.jsonp!==false&&(a4.test(b5.url)?"url":typeof b5.data==="string"&&!(b5.contentType||"").indexOf("application/x-www-form-urlencoded")&&a4.test(b5.data)&&"data");if(b7||b5.dataTypes[0]==="jsonp"){b8=b5.jsonpCallback=bI.isFunction(b5.jsonpCallback)?b5.jsonpCallback():b5.jsonpCallback;if(b7){b5[b7]=b5[b7].replace(a4,"$1"+b8)}else{if(b5.jsonp!==false){b5.url+=(ax.test(b5.url)?"&":"?")+b5.jsonp+"="+b8}}b5.converters["script json"]=function(){if(!b4){bI.error(b8+" was not called")}return b4[0]};b5.dataTypes[0]="json";b3=a1[b8];a1[b8]=function(){b4=arguments};b6.always(function(){a1[b8]=b3;if(b5[b8]){b5.jsonpCallback=e.jsonpCallback;bp.push(b8)}if(b4&&bI.isFunction(b3)){b3(b4[0])}b4=b3=aE});return"script"}});var af,av,aw=0,aN=a1.ActiveXObject&&function(){var e;for(e in af){af[e](aE,true)}};function bC(){try{return new a1.XMLHttpRequest()}catch(b3){}}function bd(){try{return new a1.ActiveXObject("Microsoft.XMLHTTP")}catch(b3){}}bI.ajaxSettings.xhr=a1.ActiveXObject?function(){return !this.isLocal&&bC()||bd()}:bC;av=bI.ajaxSettings.xhr();bI.support.cors=!!av&&("withCredentials" in av);av=bI.support.ajax=!!av;if(av){bI.ajaxTransport(function(e){if(!e.crossDomain||bI.support.cors){var b3;return{send:function(b9,b4){var b7,b5,b8=e.xhr();if(e.username){b8.open(e.type,e.url,e.async,e.username,e.password)}else{b8.open(e.type,e.url,e.async)}if(e.xhrFields){for(b5 in e.xhrFields){b8[b5]=e.xhrFields[b5]}}if(e.mimeType&&b8.overrideMimeType){b8.overrideMimeType(e.mimeType)}if(!e.crossDomain&&!b9["X-Requested-With"]){b9["X-Requested-With"]="XMLHttpRequest"}try{for(b5 in b9){b8.setRequestHeader(b5,b9[b5])}}catch(b6){}b8.send((e.hasContent&&e.data)||null);b3=function(cc,cb){var ca,cd,cg,ce;try{if(b3&&(cb||b8.readyState===4)){b3=aE;if(b7){b8.onreadystatechange=bI.noop;if(aN){delete af[b7]}}if(cb){if(b8.readyState!==4){b8.abort()}}else{ce={};ca=b8.status;cd=b8.getAllResponseHeaders();if(typeof b8.responseText==="string"){ce.text=b8.responseText}try{cg=b8.statusText}catch(cf){cg=""}if(!ca&&e.isLocal&&!e.crossDomain){ca=ce.text?200:404}else{if(ca===1223){ca=204}}}}}catch(ch){if(!cb){b4(-1,ch)}}if(ce){b4(ca,cg,ce,cd)}};if(!e.async){b3()}else{if(b8.readyState===4){setTimeout(b3)}else{b7=++aw;if(aN){if(!af){af={};bI(a1).unload(aN)}af[b7]=b3}b8.onreadystatechange=b3}}},abort:function(){if(b3){b3(aE,true)}}}}})}var J,ab,bQ=/^(?:toggle|show|hide)$/,bJ=new RegExp("^(?:([+-])=|)("+bz+")([a-z%]*)$","i"),bP=/queueHooks$/,az=[h],aY={"*":[function(e,b8){var ca=this.createTween(e,b8),b6=ca.cur(),b5=bJ.exec(b8),b9=b5&&b5[3]||(bI.cssNumber[e]?"":"px"),b3=(bI.cssNumber[e]||b9!=="px"&&+b6)&&bJ.exec(bI.css(ca.elem,e)),b4=1,b7=20;if(b3&&b3[3]!==b9){b9=b9||b3[3];b5=b5||[];b3=+b6||1;do{b4=b4||".5";b3=b3/b4;bI.style(ca.elem,e,b3+b9)}while(b4!==(b4=ca.cur()/b6)&&b4!==1&&--b7)}if(b5){b3=ca.start=+b3||+b6||0;ca.unit=b9;ca.end=b5[1]?b3+(b5[1]+1)*b5[2]:+b5[2]}return ca}]};function bl(){setTimeout(function(){J=aE});return(J=bI.now())}function ba(b6,b8,b5){var b3,b7=(aY[b8]||[]).concat(aY["*"]),e=0,b4=b7.length;for(;e-1,cb={},ca={},b4,b6;if(cd){ca=b7.position();b4=ca.top;b6=ca.left}else{b4=parseFloat(e)||0;b6=parseFloat(cc)||0}if(bI.isFunction(ce)){ce=ce.call(b5,b8,b3)}if(ce.top!=null){cb.top=(ce.top-b3.top)+b4}if(ce.left!=null){cb.left=(ce.left-b3.left)+b6}if("using" in ce){ce.using.call(b5,cb)}else{b7.css(cb)}}};bI.fn.extend({position:function(){if(!this[0]){return}var b4,b5,e={top:0,left:0},b3=this[0];if(bI.css(b3,"position")==="fixed"){b5=b3.getBoundingClientRect()}else{b4=this.offsetParent();b5=this.offset();if(!bI.nodeName(b4[0],"html")){e=b4.offset()}e.top+=bI.css(b4[0],"borderTopWidth",true);e.left+=bI.css(b4[0],"borderLeftWidth",true)}return{top:b5.top-e.top-bI.css(b3,"marginTop",true),left:b5.left-e.left-bI.css(b3,"marginLeft",true)}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||bV;while(e&&(!bI.nodeName(e,"html")&&bI.css(e,"position")==="static")){e=e.offsetParent}return e||bV})}});bI.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(b4,b3){var e=/Y/.test(b3);bI.fn[b4]=function(b5){return bI.access(this,function(b6,b9,b8){var b7=bo(b6);if(b8===aE){return b7?(b3 in b7)?b7[b3]:b7.document.documentElement[b9]:b6[b9]}if(b7){b7.scrollTo(!e?b8:bI(b7).scrollLeft(),e?b8:bI(b7).scrollTop())}else{b6[b9]=b8}},b4,b5,arguments.length,null)}});function bo(e){return bI.isWindow(e)?e:e.nodeType===9?e.defaultView||e.parentWindow:false}bI.each({Height:"height",Width:"width"},function(e,b3){bI.each({padding:"inner"+e,content:b3,"":"outer"+e},function(b4,b5){bI.fn[b5]=function(b9,b8){var b7=arguments.length&&(b4||typeof b9!=="boolean"),b6=b4||(b9===true||b8===true?"margin":"border");return bI.access(this,function(cb,ca,cc){var cd;if(bI.isWindow(cb)){return cb.document.documentElement["client"+e]}if(cb.nodeType===9){cd=cb.documentElement;return Math.max(cb.body["scroll"+e],cd["scroll"+e],cb.body["offset"+e],cd["offset"+e],cd["client"+e])}return cc===aE?bI.css(cb,ca,b6):bI.style(cb,ca,cc,b6)},b3,b7?b9:aE,b7,null)}})});bI.fn.size=function(){return this.length};bI.fn.andSelf=bI.fn.addBack;if(typeof module==="object"&&module&&typeof module.exports==="object"){module.exports=bI}else{a1.jQuery=a1.$=bI;if(typeof define==="function"&&define.amd){define("jquery",[],function(){return bI})}}})(window);
+/*! JsRender v1.0.0-beta: http://github.com/BorisMoore/jsrender and http://jsviews.com/jsviews */
+(function(a,w,p){if(w&&w.views||a.jsviews){return}var af="v1.0.0-beta",L,ab,s,x,n="{",m="{",g="}",f="}",X="^",y=/^(?:null|true|false|\d[\d.]*|([\w$]+|\.|~([\w$]+)|#(view|([\w$]+))?)([\w$.^]*?)(?:[.[^]([\w$]+)\]?)?)$/g,Y=/(\()(?=\s*\()|(?:([([])\s*)?(?:(\^?)([#~]?[\w$.^]+)?\s*((\+\+|--)|\+|-|&&|\|\||===|!==|==|!=|<=|>=|[<>%*!:?\/]|(=))\s*|([#~]?[\w$.^]+)([([])?)|(,\s*)|(\(?)\\?(?:(')|("))|(?:\s*(([)\]])(?=\s*\.|\s*\^)|[)\]])([([]?))|(\s+)/g,S=/[ \t]*(\r\n|\n|\r)/g,e=/\\(['"])/g,ad=/['"\\]/g,o=/\x08(~)?([^\x08]+)\x08/g,j=/^if\s/,r=/<(\w+)[>\s]/,N=/[\x00`><"'&]/g,I=N,ag=0,ae=0,K={"&":"&","<":"<",">":">","\x00":"","'":"'",'"':""","`":"`"},V="data-jsv-tmpl",O={},R={template:{compile:b},tag:{compile:q},helper:{},converter:{}},M={jsviews:af,render:O,settings:{delimiters:u,debugMode:true,tryCatch:true},sub:{View:ac,Error:D,tmplFn:B,parse:v,extend:t,error:F,syntaxError:Q},_cnvt:G,_tag:c,_err:function(ah){return T.debugMode?("Error: "+(ah.message||ah))+". ":""}};function D(ai,ah){if(ah&&ah.onError){if(ah.onError(ai)===false){return}}this.name="JsRender Error";this.message=ai||"JsRender error"}function t(aj,ai){var ah;aj=aj||{};for(ah in ai){aj[ah]=ai[ah]}return aj}(D.prototype=new Error()).constructor=D;function u(ah,aj,ai){if(!U.rTag||arguments.length){n=ah?ah.charAt(0):n;m=ah?ah.charAt(1):m;g=aj?aj.charAt(0):g;f=aj?aj.charAt(1):f;X=ai||X;ah="\\"+n+"(\\"+X+")?\\"+m;aj="\\"+g+"\\"+f;s="(?:(?:(\\w+(?=[\\/\\s\\"+g+"]))|(?:(\\w+)?(:)|(>)|!--((?:[^-]|-(?!-))*)--|(\\*)))\\s*((?:[^\\"+g+"]|\\"+g+"(?!\\"+f+"))*?)";U.rTag=s+")";s=new RegExp(ah+s+"(\\/)?|(?:\\/(\\w+)))"+aj,"g");x=new RegExp("<.*>|([^\\\\]|^)[{}]|"+ah+".*"+aj)}return[n,m,g,f,X]}function J(al,an){if(!an){an=al;al=p}var ak,am,aj,ao,ai=this,ah=!an||an==="root";if(al){ao=ai.type===an?ai:p;if(!ao){ak=ai.views;if(ai._.useKey){for(am in ak){if(ao=ak[am].get(al,an)){break}}}else{for(am=0,aj=ak.length;!ao&&am0){try{aj=aq.nodeType>0?aq:!x.test(aq)&&w&&w(a.document).find(aq)[0]}catch(ar){}if(aj){aq=aj.getAttribute(V);ah=ah||aq;aq=E[aq];if(!aq){ah=ah||"_"+ag++;aj.setAttribute(V,ah);aq=E[ah]=b(ah,aj.innerHTML,ao,am,ai,ap)}}return aq}}var al,aj;an=an||"";al=ak(an);ap=ap||(an.markup?an:{});ap.tmplName=ah;if(ao){ap._parentTmpl=ao}if(!al&&an.markup&&(al=ak(an.markup))){if(al.fn&&(al.debug!==an.debug||al.allowCode!==an.allowCode)){al=al.markup}}if(al!==p){if(ah&&!ao){O[ah]=function(){return an.render.apply(an,arguments)}}if(al.fn||an.fn){if(al.fn){if(ah&&ah!==al.tmplName){an=aa(ap,al)}else{an=al}}}else{an=h(al,ap);B(al,an)}k(ap);return an}}function h(ak,al){var ah,ai=T.wrapMap||{},aj=t({markup:ak,tmpls:[],links:{},tags:{},bnds:[],_is:"template",render:l},al);if(!al.htmlTag){ah=r.exec(ak);aj.htmlTag=ah?ah[1].toLowerCase():""}ah=ai[aj.htmlTag];if(ah&&ah!==ai.div){aj.markup=L.trim(aj.markup);aj._elCnt=true}return aj}function d(aj,ai){function ak(al,an,ar){var am,aq,ap,ao;if(al&&""+al!==al&&!al.nodeType&&!al.markup){for(ap in al){ak(ap,al[ap],an)}return M}if(an===p){an=al;al=p}if(al&&""+al!==al){ar=an;an=al;al=p}ao=ar?ar[ah]=ar[ah]||{}:ak;aq=ai.compile;if(am=U.onBeforeStoreItem){aq=am(ao,al,an,aq)||aq}if(!al){an=aq(p,an)}else{if(an===null){delete ao[al]}else{ao[al]=aq?(an=aq(al,an,ar,aj,ai)):an}}if(aq&&an){an._is=aj}if(am=U.onStoreItem){am(ao,al,an,aq)}return an}var ah=aj+"s";M[ah]=ak;R[aj]=ai}function l(aC,aj,ak,aD,am,al){var ax,aw,ao,az,aB,au,at,ap,ai,aA,ay,ah,an,av=this,ar=!av.attr||av.attr==="html",aq="";if(aD===true){at=true;aD=0}if(av.tag){ap=av;av=av.tag;aA=av._;ah=av.tagName;an=ap.tmpl;aj=aa(aj,av.ctx);ai=ap.content;if(ap.props.link===false){aj=aj||{};aj.link=false}ak=ak||ap.view;aC=aC===p?ak:aC}else{an=av.jquery&&(av[0]||F('Unknown template: "'+av.selector+'"'))||av}if(an){if(!ak&&aC&&aC._is==="view"){ak=aC}if(ak){ai=ai||ak.content;al=al||ak._.onRender;if(aC===ak){aC=ak.data;am=true}aj=aa(aj,ak.ctx)}if(!ak||ak.data===p){(aj=aj||{}).root=aC}if(!an.fn){an=E[an]||E(an)}if(an){al=(aj&&aj.link)!==false&&ar&&al;ay=al;if(al===true){ay=p;al=ak._.onRender}if(L.isArray(aC)&&!am){az=at?ak:(aD!==p&&ak)||ac(aj,"array",ak,aC,an,aD,ai,al);for(ax=0,aw=aC.length;ax":aM+ap}if(ah){ay="prm"+aG;ah="try{var "+ay+"=["+aL+"][0];}catch(e){"+ay+'="";}\n';aL=ay}}else{if(aC){ar=h(aI,aw);ar.tmplName=ai+"/"+ap;A(aC,ar);aq.push(ar)}if(!aH){aE=ap;al=ak;ak=""}at=av[aG+1];at=at&&at[0]==="else"}an+=",args:["+aL+"]}";if(aO&&aN||aM&&ap!==">"){aF=new Function("data,view,j,u"," // "+ai+" "+aK+" "+ap+"\n"+ah+"return {"+an+";");aF.paths=aN;aF._ctxs=ap;if(au){return aF}az=1}ak+=(aO?"\n"+(aN?"":ah)+(au?"return ":"ret+=")+(az?(az=0,ao=true,'c("'+aM+'",view,'+(aN?((aj[aK-1]=aF),aK):"{"+an)+");"):ap===">"?(am=true,"h("+aL+");"):(aD=true,"(v="+aL+")!="+(au?"=":"")+'u?v:"";')):(aP=true,"{tmpl:"+(aC?aq.length:"0")+","+an+","));if(aE&&!at){ak="["+ak.slice(0,-1)+"]";if(au||aN){ak=new Function("data,view,j,u"," // "+ai+" "+aK+" "+aE+"\nreturn "+ak+";");if(aN){(aj[aK-1]=ak).paths=aN}ak._ctxs=ap;if(au){return ak}}ak=al+'\nret+=t("'+aE+'",view,this,'+(aK||ak)+");";aN=0;aE=0}}}}ak="// "+ai+"\nvar j=j||"+(w?"jQuery.":"js")+"views"+(aD?",v":"")+(aP?",t=j._tag":"")+(ao?",c=j._cnvt":"")+(am?",h=j.converters.html":"")+(au?";\n":',ret="";\n')+(T.tryCatch?"try{\n":"")+(aw.debug?"debugger;":"")+ak+(au?"\n":"\nreturn ret;\n")+(T.tryCatch?"\n}catch(e){return j._err(e);}":"");try{ak=new Function("data,view,j,u",ak)}catch(aJ){Q("Compiled template code:\n\n"+ak,aJ)}if(ax){ax.fn=ak}return ak}function v(am,ai,ar){function at(ay,aP,aA,av,aH,aF,aB,aQ,aw,aG,aO,aN,aJ,aM,aD,aK,az,aL,aC,aE){var ax;aF=aF||"";aA=aA||aP||aN;aH=aH||aw;aG=aG||az||"";function aI(aU,aT,aW,aR,aY,aX,aV){if(aT){if(ai){if(an==="linkTo"){au=ai.to=ai.to||[];au.push(aH)}if(!an||ao){ai.push(aH)}}if(aT!=="."){var aS=(aW?'view.hlp("'+aW+'")':aR?"view":"data")+(aV?(aY?"."+aY:aW?"":(aR?"":"."+aT))+(aX||""):(aV=aW?"":aR?aY||"":aT,""));aS=aS+(aV?"."+aV:"");return aS.slice(0,9)==="view.data"?aS.slice(5):aS}}return aU}if(aB){Q(am)}else{if(ai&&aK&&!ap&&!al){if(!an||ao||au){ax=aq[aj];if(aE.length-2>aC-ax){ax=aE.slice(ax,aC+1);aK=m+":"+ax+g;aK=ak[aK]=ak[aK]||B(n+aK+f,ar,true);if(!aK.paths){v(ax,aK.paths=[],ar)}(au||ai).push({_jsvOb:aK})}}}return(ap?(ap=!aJ,(ap?ay:'"')):al?(al=!aM,(al?ay:'"')):((aA?(aj++,aq[aj]=aC++,aA):"")+(aL?(aj?"":an?(an=ao=au=false,"\b"):","):aQ?(aj&&Q(am),an=aH,ao=av,"\b"+aH+":"):aH?(aH.split("^").join(".").replace(y,aI)+(aG?(ah[++aj]=true,aH.charAt(0)!=="."&&(aq[aj]=aC),aG):aF)):aF?aF:aD?((ah[aj--]=false,aD)+(aG?(ah[++aj]=true,aG):"")):aO?(ah[aj]||Q(am),","):aP?"":(ap=aJ,al=aM,'"'))))}}var an,au,ao,ak=ar.links,ah={},aq={0:-1},aj=0,al=false,ap=false;return(am+" ").replace(Y,at)}function aa(ai,ah){return ai&&ai!==ah?(ah?t(t({},ah),ai):ai):ah&&t({},ah)}function i(ah){return K[ah]||(K[ah]=""+ah.charCodeAt(0)+";")}for(ab in R){d(ab,R[ab])}var E=M.templates,Z=M.converters,C=M.helpers,P=M.tags,U=M.sub,T=M.settings;if(w){L=w;L.fn.render=l}else{L=a.jsviews={};L.isArray=Array&&Array.isArray||function(ah){return Object.prototype.toString.call(ah)==="[object Array]"}}L.render=O;L.views=M;L.templates=E=M.templates;P({"else":function(){},"if":{render:function(aj){var ah=this,ai=(ah.rendering.done||!aj&&(arguments.length||!ah.tagCtx.index))?"":(ah.rendering.done=true,ah.selected=ah.tagCtx.index,ah.tagCtx.render());return ai},onUpdate:function(al,ak,aj){var ai,ah,am;for(ai=0;(ah=this.tagCtxs[ai])&&ah.args.length;ai++){ah=ah.args[0];am=!ah!==!aj[ai].args[0];if(!!ah||am){return am}}return false},flow:true},"for":{render:function(am){var ak=this,aj=ak.tagCtx,al=!arguments.length,ah="",ai=al||0;if(!ak.rendering.done){if(al){ah=p}else{if(am!==p){ah+=aj.render(am);ai+=L.isArray(am)?am.length:1}}if(ak.rendering.done=ai){ak.selected=aj.index}}return ah},onArrayChange:function(ak,ai){var aj,ah=this,al=ai.change;if(this.tagCtxs[1]&&(al==="insert"&&ak.target.length===ai.items.length||al==="remove"&&!ak.target.length||al==="refresh"&&!ai.oldItems.length!==!ak.target.length)){this.refresh()}else{for(aj in ah._.arrVws){aj=ah._.arrVws[aj];if(aj.data===ak.target){aj._.onArrayChange.apply(aj,arguments)}}}ak.done=true},flow:true},include:{flow:true},"*":{render:function(ah){return ah},flow:true}});Z({html:function(ah){return ah!=p?String(ah).replace(I,i):""},attr:function(ah){return ah!=p?String(ah).replace(N,i):ah===null?null:""},url:function(ah){return ah!=p?encodeURI(String(ah)):ah===null?null:""}});u()})(this,this.jQuery);
+/*! JsObservable v1.0.0-alpha: http://github.com/BorisMoore/jsviews and http://jsviews.com/jsviews */
+(function(y,f,k){if(!f){throw"requires jQuery or JsRender"}if(f.observable){return}var c="v1.0.0-alpha",w,n,H,t,A=f.event.special,s=f.views?f.views.sub:{},l=1,G=[].splice,j=[].concat,p=f.isArray,C=f.expando,q="object",E=s.propChng=s.propChng||"propertyChange",e=s.arrChng=s.arrChng||"arrayChange",x=s._cbBnds=s._cbBnds||{},g=E+".observe",u=f.isFunction,i=1,r=1,h=f.hasData;function v(I){return p(I)?new a(I):new b(I)}function b(I){this._data=I;return this}function a(I){this._data=I;return this}function B(I){return p(I)?[I]:I}function m(I){if(typeof I!=="number"){throw"Invalid index."}}function d(P,J){P=p(P)?P:[P];var N,O,L=J,M=L,I=P.length,K=[];for(N=0;N-1){ah=ah[L];continue}}if(L==="*"){if(u(ah)){if(O=ah.depends){o(O,M,I||P)}}else{S(ai,L)}break}else{if(L&&!(u(O=ah[L])&&O.depends)){S(ai+"."+L,T.join("."))}}}L=L?ah[L]:ah}if(u(L)){if(O=L.depends){o(ah,d(O,ah),M,N,I||B(P))}break}ah=L}}aa(ah,I);if(V){F(ae,V)}return{cbId:V,bnd:ae,leaf:ah}}function D(){[].push.call(arguments,true);return o.apply(this,arguments)}f.observable=v;v.Object=b;v.Array=a;v.observe=o;v.unobserve=D;b.prototype={_data:null,data:function(){return this._data},observe:function(I,J){return o(this._data,I,J)},unobserve:function(I,J){return D(this._data,I,J)},setProperty:function(Q,N,L){var M,O,J,K,P=this,I=P._data;Q=Q||"";if(I){if(p(Q)){O=Q.length;while(O--){J=Q[O];P.setProperty(J.name,J.value,L===k||L)}}else{if(""+Q!==Q){for(O in Q){P.setProperty(O,Q[O],N)}}else{if(Q.indexOf(C)<0){K=Q.split(".");while(I&&K.length>1){I=I[K.shift()]}P._setProperty(I,K.join("."),N,L)}}}}return P},_setProperty:function(J,N,M,L){var O,I,K=N?J[N]:J;if(u(K)){if(K.set){I=K;O=K.set===true?K:K.set;K=K.call(J)}}if(K!==M||L&&K!=M){if(!(K instanceof Date)||K>M||K1){J=p(J)?J:[J];if(J.length){this._insert(I,J)}}return this},_insert:function(I,J){t=this._data;H=t.length;G.apply(t,[I,0].concat(J));this._trigger({change:"insert",index:I,items:J})},remove:function(J,K){m(J);K=(K===k||K===null)?1:K;if(K&&J>-1){var I=this._data.slice(J,J+K);K=I.length;if(K){this._remove(J,K,I)}}return this},_remove:function(J,K,I){t=this._data;H=t.length;t.splice(J,K);this._trigger({change:"remove",index:J,items:I})},move:function(L,K,I){m(L);m(K);I=(I===k||I===null)?1:I;if(I){var J=this._data.slice(L,L+I);this._move(L,K,I,J)}return this},_move:function(L,K,I,J){t=this._data;H=t.length;t.splice(L,I);t.splice.apply(t,[K,0].concat(J));this._trigger({change:"move",oldIndex:L,index:K,items:J})},refresh:function(I){var J=this._data.slice(0);this._refresh(J,I);return this},_refresh:function(J,I){t=this._data;H=t.length;G.apply(t,[0,t.length].concat(I));this._trigger({change:"refresh",oldItems:J})},_trigger:function(I){var K=t.length,J=f([t]);J.triggerHandler(e,I);if(K!==H){J.triggerHandler(E,{path:"length",value:K,oldValue:H})}}};A[E]=A[e]={remove:function(I){if((I=I.data)&&(I.off=1,I=I.cb)){w=x[n=I._bnd]}},teardown:function(I){if(w){delete w[f.data(this,"obId")];F(w,n)}}}})(this,this.jQuery||this.jsviews);
+/*! JsViews v1.0.0-alpha: http://github.com/BorisMoore/jsviews and http://jsviews.com/jsviews */
+(function(r,az,u){if(!az){throw"requires jQuery"}if(!az.views){throw"requires JsRender"}if(!az.observable){throw"requires jquery.observable"}if(az.link){return}var g="v1.0.0-alpha",V,aG,aP,e,au,at,M,L,ax,b,Q=r.document,h=az.views,K=h.sub,m=h.settings,n=K.extend,O=K.View(u,"top"),d=az.isFunction,l=h.templates,S=az.observable,aO=S.observe,af="data-jsv",ab=m.linkAttr||"data-link",q=m.propChng=m.propChng||"propertyChange",Z=K.arrChng=K.arrChng||"arrayChange",aH=K._cbBnds=K._cbBnds||{},P="change.jsv",y="onBeforeChange",x="onAfterChange",aB="onAfterCreate",c='"><\/script>',aE='',
+ openScript = ' - data-linked tag, close marker
+ preceding = id
+ ? (preceding + endOfElCnt + spaceBefore + openScript + id + closeScript + spaceAfter + tag)
+ : endOfElCnt || all;
+ }
+ if (tag) {
+ // If there are ids (markers since the last tag), move them to the defer string
+ tagStack.unshift(parentTag);
+ parentTag = tag.slice(1);
+ if (tagStack[0] === badParent[parentTag]) {
+ // TODO: move this to design-time validation check
+ error('"' + parentTag + '" has incorrect parent tag');
+ }
+ if ((elCnt = elContent[parentTag]) && !prevElCnt) {
+ deferStack.unshift(defer);
+ defer = "";
+ }
+ prevElCnt = elCnt;
+//TODO Consider providing validation which throws if you place as child of
, etc. - since if not caught,
+//this can cause errors subsequently which are difficult to debug.
+// if (elContent[tagStack[0]]>2 && !elCnt) {
+// error(parentTag + " in " + tagStack[0]);
+// }
+ if (defer && elCnt) {
+ defer += "+"; // Will be used for stepping back through deferred tokens
+ }
+ }
+ return preceding;
+ }
+
+ function processViewInfos(vwInfos, targetParent) {
+ // If targetParent, we are processing viewInfos (which may include navigation through '+-' paths) and hooking up to the right parentElem etc.
+ // (and elem may also be defined - the next node)
+ // If no targetParent, then we are processing viewInfos on newly inserted content
+ var deferPath, deferChar, bindChar, parentElem, id, onAftCr, deep,
+ addedBindEls = [];
+
+ // In elCnt context (element-only content model), prevNode is the first node after the open, nextNode is the first node after the close.
+ // If both are null/undefined, then open and close are at end of parent content, so the view is empty, and its placeholder is the
+ // 'lastChild' of the parentNode. If there is a prevNode, then it is either the first node in the view, or the view is empty and
+ // its placeholder is the 'previousSibling' of the prevNode, which is also the nextNode.
+ if (vwInfos) {
+ //targetParent = targetParent || targetElem && targetElem.previousSibling;
+ //targetParent = targetElem ? targetElem.previousSibling : targetParent;
+ len = vwInfos.length;
+ if (vwInfos.tokens.charAt(0) === "@") {
+ // This is a special script element that was created in convertMarkers() to process deferred bindings, and inserted following the
+ // target parent element - because no element tags were encountered to carry those binding tokens.
+ targetParent = elem.previousSibling;
+ elem.parentNode.removeChild(elem);
+ elem = null;
+ }
+ len = vwInfos.length;
+ while (len--) {
+ vwInfo = vwInfos[len];
+ //if (prevIds.indexOf(vwInfo.token) < 0) { // This token is a newly created view or tag binding
+ bindChar = vwInfo.ch;
+ if (deferPath = vwInfo.path) {
+ // We have a 'deferred path'
+ j = deferPath.length - 1;
+ while (deferChar = deferPath.charAt(j--)) {
+ // Use the "+" and"-" characters to navigate the path back to the original parent node where the deferred bindings ocurred
+ if (deferChar === "+") {
+ if (deferPath.charAt(j) === "-") {
+ j--;
+ targetParent = targetParent.previousSibling;
+ } else {
+ targetParent = targetParent.parentNode;
+ }
+ } else {
+ targetParent = targetParent.lastChild;
+ }
+ // Note: Can use previousSibling and lastChild, not previousElementSibling and lastElementChild,
+ // since we have removed white space within elCnt. Hence support IE < 9
+ }
+ }
+ if (bindChar === "^") {
+ if (tag = bindingStore[id = vwInfo.id]) {
+ // The binding may have been deleted, for example in a different handler to an array collectionChange event
+ // This is a tag binding
+ deep = targetParent && (!elem || elem.parentNode !== targetParent);
+ if (!elem || deep) {
+ tag.parentElem = targetParent;
+ }
+ if (vwInfo.elCnt) {
+ if (vwInfo.open) {
+ if (targetParent) {
+ // This is an 'open view' node (preceding script marker node,
+ // or if elCnt, the first element in the view, with a data-jsv annotation) for binding
+ targetParent._dfr = "#" + id + bindChar + (targetParent._dfr || "");
+ }
+ } else if (deep) {
+ // There is no ._nxt so add token to _dfr. It is deferred.
+ targetParent._dfr = "/" + id + bindChar + (targetParent._dfr || "");
+ }
+ }
+ // This is an open or close marker for a data-linked tag {^{...}}. Add it to bindEls.
+ addedBindEls.push([deep ? null : elem, vwInfo]);
+ }
+ } else if (view = viewStore[id = vwInfo.id]) {
+ // The view may have been deleted, for example in a different handler to an array collectionChange event
+ if (!view.link) {
+ // If view is not already extended for JsViews, extend and initialize the view object created in JsRender, as a JsViews view
+ view.parentElem = targetParent || elem && elem.parentNode || parentNode;
+ $extend(view, LinkedView);
+ view._.onRender = addBindingMarkers;
+ view._.onArrayChange = arrayChangeHandler;
+ setArrayChangeLink(view);
+ }
+ parentElem = view.parentElem;
+ if (vwInfo.open) {
+ // This is an 'open view' node (preceding script marker node,
+ // or if elCnt, the first element in the view, with a data-jsv annotation) for binding
+ view._elCnt = vwInfo.elCnt;
+ if (targetParent) {
+ targetParent._dfr = "#" + id + bindChar + (targetParent._dfr || "");
+ } else {
+ // No targetParent, so there is a ._nxt elem (and this is processing tokens on the elem)
+ if (!view._prv) {
+ parentElem._dfr = removeSubStr(parentElem._dfr, "#" + id + bindChar);
+ }
+ view._prv = elem;
+ }
+ } else {
+ // This is a 'close view' marker node for binding
+ if (targetParent && (!elem || elem.parentNode !== targetParent)) {
+ // There is no ._nxt so add token to _dfr. It is deferred.
+ targetParent._dfr = "/" + id + bindChar + (targetParent._dfr || "");
+ view._nxt = undefined;
+ } else if (elem) {
+ // This view did not have a ._nxt, but has one now, so token may be in _dfr, and must be removed. (No longer deferred)
+ if (!view._nxt) {
+ parentElem._dfr = removeSubStr(parentElem._dfr, "/" + id + bindChar);
+ }
+ view._nxt = elem;
+ }
+ linkCtx = view.linkCtx;
+ if (onAftCr = onAfterCreate || (view.ctx && view.ctx.onAfterCreate)) {
+ onAftCr.call(linkCtx, view);
+ }
+ }
+ //}
+ }
+ }
+ len = addedBindEls.length;
+ while (len--) {
+ // These were added in reverse order to addedBindEls. We push them in BindEls in the correct order.
+ bindEls.push(addedBindEls[len]);
+ }
+ }
+ return !vwInfos || vwInfos.elCnt;
+ }
+
+ function getViewInfos(vwInfos) {
+ // Used by view.childTags() and tag.childTags()
+ // Similar to processViewInfos in how it steps through bindings to find tags. Only finds data-linked tags.
+ var level, parentTag;
+
+ if (len = vwInfos && vwInfos.length) {
+ for (j = 0; j < len; j++) {
+ vwInfo = vwInfos[j];
+ if (get.id) {
+ get.id = get.id !== vwInfo.id && get.id;
+ } else {
+ // This is an open marker for a data-linked tag {^{...}}, within the content of the tag whose id is get.id. Add it to bindEls.
+ parentTag = tag = bindingStore[vwInfo.id].linkCtx.tag;
+ if (!tag.flow) {
+ if (!deep) {
+ level = 1;
+ while (parentTag = parentTag.parent) {
+ level++;
+ }
+ tagDepth = tagDepth || level; // The level of the first tag encountered.
+ }
+ if ((deep || level === tagDepth) && (!tagName || tag.tagName === tagName)) {
+ // Filter on top-level or tagName as appropriate
+ tags.push(tag);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ function dataLink() {
+ //================ Data-link and fixup of data-jsv annotations ================
+ elems = qsa ? parentNode.querySelectorAll(linkViewsSel) : $(linkViewsSel, parentNode).get();
+ l = elems.length;
+
+ // The prevNode will be in the returned query, since we called markPrevOrNextNode() on it.
+ // But it may have contained nodes that satisfy the selector also.
+ if (prevNode) {
+ // Find the last contained node of prevNode, to use as the prevNode - so we only link subsequent elems in the query
+ prevNodes = qsa ? prevNode.querySelectorAll(linkViewsSel) : $(linkViewsSel, prevNode).get();
+ prevNode = prevNodes.length ? prevNodes[prevNodes.length - 1] : prevNode;
+ }
+ tagDepth = 0;
+ for (i = 0; i < l; i++) {
+ elem = elems[i];
+ if (prevNode && !found) {
+ // If prevNode is set, not false, skip linking. If this element is the prevNode, set to false so subsequent elements will link.
+ found = (elem === prevNode);
+ } else if (nextNode && elem === nextNode) {
+ // If nextNode is set then break when we get to nextNode
+ break;
+ } else if (elem.parentNode
+ // elem has not been removed from DOM
+ && processInfos(viewInfos(elem, undefined, tags && rOpenTagMarkers))
+ // If a link() call, processViewInfos() adds bindings to bindEls, and returns true for non-script nodes, for adding data-link bindings
+ // If a childTags() call getViewInfos adds tag bindings to tags array.
+ && elem.getAttribute($viewsLinkAttr)) {
+ bindEls.push([elem]);
+ }
+ }
+
+ // Remove temporary marker script nodes they were added by markPrevOrNextNode
+ unmarkPrevOrNextNode(prevNode, elCnt);
+ unmarkPrevOrNextNode(nextNode, elCnt);
+
+ if (get) {
+ lazyLink && lazyLink.resolve();
+ return;
+ }
+
+ if (elCnt && defer + ids) {
+ // There are some views with elCnt, for which the open or close did not precede any HTML tag - so they have not been processed yet
+ elem = nextNode;
+ if (defer) {
+ if (nextNode) {
+ processViewInfos(viewInfos(defer + "+", true), nextNode);
+ } else {
+ processViewInfos(viewInfos(defer, true), parentNode);
+ }
+ }
+ processViewInfos(viewInfos(ids, true), parentNode);
+ // If there were any tokens on nextNode which have now been associated with inserted HTML tags, remove them from nextNode
+ if (nextNode) {
+ tokens = nextNode.getAttribute(jsvAttrStr);
+ if (l = tokens.indexOf(prevIds) + 1) {
+ tokens = tokens.slice(l + prevIds.length - 1);
+ }
+ nextNode.setAttribute(jsvAttrStr, ids + tokens);
+ }
+ }
+
+ //================ Bind the data-linked elements and tags ================
+ l = bindEls.length;
+ for (i = 0; i < l; i++) {
+ elem = bindEls[i];
+ linkInfo = elem[1];
+ elem = elem[0];
+ if (linkInfo) {
+ tag = bindingStore[linkInfo.id];
+ linkCtx = tag.linkCtx;
+ tag = linkCtx ? linkCtx.tag : tag;
+ // The tag may have been stored temporarily on the bindingStore - or may have already been replaced by the actual binding
+ if (linkInfo.open) {
+ // This is an 'open linked tag' binding annotation for a data-linked tag {^{...}}
+ if (elem) {
+ tag.parentElem = elem.parentNode;
+ tag._prv = elem;
+ }
+ tag._elCnt = linkInfo.elCnt;
+ if (tag && (!tag.onBeforeLink || tag.onBeforeLink() !== false) && !tag._.bound) {
+ // By default we data-link depth-first ("on the way in"), which is better for perf. But if a tag needs nested tags to be linked (refreshed)
+ // first, before linking its content, then make onBeforeLink() return false. In that case we data-link depth-first, so nested tags will have already refreshed.
+ tag._.bound = true;
+ view = tag.tagCtx.view;
+ addDataBinding(undefined, tag._prv, view, view.data||outerData, linkInfo.id);
+ }
+
+ tag._.linking = true;
+ } else {
+ tag._nxt = elem;
+ if (tag._.linking) {
+ // This is a 'close linked tag' binding annotation
+ // Add data binding
+ tagCtx = tag.tagCtx;
+ view = tagCtx.view;
+ tag.contents = getContents;
+ tag.nodes = getNodes;
+ tag.childTags = getChildTags;
+
+ delete tag._.linking;
+ callAfterLink(tag, tagCtx);
+ if (!tag._.bound) {
+ tag._.bound = true;
+ addDataBinding(undefined, tag._prv, view, view.data||outerData, linkInfo.id);
+ }
+ }
+ }
+ } else {
+ view = $view(elem);
+ // Add data binding for a data-linked element (with data-link attribute)
+ addDataBinding(elem.getAttribute($viewsLinkAttr), elem, view, view.data||outerData);
+ }
+ }
+ lazyLink && lazyLink.resolve();
+ }
+ //==== /end of nested functions ====
+
+ var linkCtx, tag, i, l, j, len, elems, elem, view, vwInfos, vwInfo, linkInfo, prevNodes, token, prevView, nextView, node, tags, deep, tagName, tagCtx, cvt,
+ tagDepth, get, depth, fragment, copiedNode, firstTag, parentTag, wrapper, div, tokens, elCnt, prevElCnt, htmlTag, ids, prevIds, found, lazyLink, linkedElem,
+ noDomLevel0 = $viewsSettings.noDomLevel0,
+ self = this,
+ thisId = self._.id + "_",
+ defer = "",
+ // The marker ids for which no tag was encountered (empty views or final closing markers) which we carry over to container tag
+ bindEls = [],
+ tagStack = [],
+ deferStack = [],
+ onAfterCreate = self.hlp(onAfterCreateStr),
+ processInfos = processViewInfos;
+
+ if (refresh) {
+ lazyLink = refresh.lazyLink && $.Deferred();
+ if (refresh.tmpl) {
+ // refresh is the prevView, passed in from addViews()
+ prevView = "/" + refresh._.id + "_";
+ } else {
+ get = refresh.get;
+ if (refresh.tag) {
+ thisId = refresh.tag + "^";
+ refresh = true;
+ }
+ }
+ refresh = refresh === true;
+ }
+
+ if (get) {
+ processInfos = getViewInfos;
+ tags = get.tags;
+ deep = get.deep;
+ tagName = get.name;
+ }
+
+ parentNode = parentNode
+ ? ("" + parentNode === parentNode
+ ? $(parentNode)[0] // It is a string, so treat as selector
+ : parentNode.jquery
+ ? parentNode[0] // A jQuery object - take first element.
+ : parentNode)
+ : (self.parentElem // view.link()
+ || document.body); // link(null, data) to link the whole document
+
+ parentTag = parentNode.tagName.toLowerCase();
+ elCnt = !!elContent[parentTag];
+
+ prevNode = prevNode && markPrevOrNextNode(prevNode, elCnt);
+ nextNode = nextNode && markPrevOrNextNode(nextNode, elCnt) || null;
+
+ if (html !== undefined) {
+ //================ Insert html into DOM using documentFragments (and wrapping HTML appropriately). ================
+ // Also convert markers to DOM annotations, based on content model.
+ // Corresponds to nextNode ? $(nextNode).before(html) : $(parentNode).html(html);
+ // but allows insertion to wrap correctly even with inserted script nodes. jQuery version will fail e.g. under tbody or select.
+ // This version should also be slightly faster
+ div = document.createElement("div");
+ wrapper = div;
+ prevIds = ids = "";
+ htmlTag = parentNode.namespaceURI === "http://www.w3.org/2000/svg" ? "svg" : (firstTag = rFirstElem.exec(html)) && firstTag[1] || "";
+ if (noDomLevel0 && firstTag && firstTag[2]) {
+ error("Unsupported: " + firstTag[2]); // For security reasons, don't allow insertion of elements with onFoo attributes.
+ }
+ if (elCnt) {
+ // Now look for following view, and find its tokens, or if not found, get the parentNode._dfr tokens
+ node = nextNode;
+ while (node && !(nextView = viewInfos(node))) {
+ node = node.nextSibling;
+ }
+ if (tokens = nextView ? nextView.tokens : parentNode._dfr) {
+ token = prevView || "";
+ if (refresh || !prevView) {
+ token += "#" + thisId;
+ }
+ j = tokens.indexOf(token);
+ if (j + 1) {
+ j += token.length;
+ // Transfer the initial tokens to inserted nodes, by setting them as the ids variable, picked up in convertMarkers
+ prevIds = ids = tokens.slice(0, j);
+ tokens = tokens.slice(j);
+ if (nextView) {
+ node.setAttribute(jsvAttrStr, tokens);
+ } else {
+ parentNode._dfr = tokens;
+ }
+ }
+ }
+ }
+
+ //================ Convert the markers to DOM annotations, based on content model. ================
+// oldElCnt = elCnt;
+ html = ("" + html).replace(rConvertMarkers, convertMarkers);
+// if (!!oldElCnt !== !!elCnt) {
+// error("Parse: " + html); // Parse error. Content not well-formed?
+// }
+ // Append wrapper element to doc fragment
+ safeFragment.appendChild(div);
+
+ // Go to html and back, then peel off extra wrappers
+ // Corresponds to jQuery $(nextNode).before(html) or $(parentNode).html(html);
+ // but supports svg elements, and other features missing from jQuery version (and this version should also be slightly faster)
+ htmlTag = wrapMap[htmlTag] || wrapMap.div;
+ depth = htmlTag[0];
+ wrapper.innerHTML = htmlTag[1] + html + htmlTag[2];
+ while (depth--) {
+ wrapper = wrapper.lastChild;
+ }
+ safeFragment.removeChild(div);
+ fragment = document.createDocumentFragment();
+ while (copiedNode = wrapper.firstChild) {
+ fragment.appendChild(copiedNode);
+ }
+ // Insert into the DOM
+ parentNode.insertBefore(fragment, nextNode);
+ }
+
+ if (lazyLink) {
+ setTimeout(dataLink, 0);
+ } else {
+ dataLink();
+ }
+
+ return lazyLink && lazyLink.promise();
+ }
+
+ function addDataBinding(linkMarkup, node, currentView, data, boundTagId) {
+ // Add data binding for data-linked elements or {^{...}} data-linked tags
+ var tmpl, tokens, attr, convertBack, params, trimLen, tagExpr, linkFn, linkCtx, tag, rTagIndex;
+
+ if (boundTagId) {
+ // {^{...}} data-linked tag. So only one linkTag in linkMarkup
+ tag = bindingStore[boundTagId];
+ tag = tag.linkCtx ? tag.linkCtx.tag : tag;
+
+ linkCtx = tag.linkCtx || {
+ data: data, // source
+ elem: tag._elCnt ? tag.parentElem : node, // target
+ view: currentView,
+ ctx: currentView.ctx,
+ attr: "html", // Script marker nodes are associated with {^{ and always target HTML.
+ fn: tag._.bnd,
+ tag: tag,
+ // Pass the boundTagId in the linkCtx, so that it can be picked up in observeAndBind
+ _bndId: boundTagId
+ };
+ bindDataLinkTarget(linkCtx, linkCtx.fn);
+ } else if (linkMarkup && node) {
+ // Compiled linkFn expressions could be stored in the tmpl.links array of the template
+ // TODO - consider also caching globally so that if {{:foo}} or data-link="foo" occurs in different places,
+ // the compiled template for this is cached and only compiled once...
+ //links = currentView.links || currentView.tmpl.links;
+
+ tmpl = currentView.tmpl;
+
+// if (!(linkTags = links[linkMarkup])) {
+ // This is the first time this view template has been linked, so we compile the data-link expressions, and store them on the template.
+
+ linkMarkup = normalizeLinkTag(linkMarkup, node);
+ rTag.lastIndex = 0;
+ while (tokens = rTag.exec(linkMarkup)) { // TODO require } to be followed by whitespace or $, and remove the \}(!\}) option.
+ // Iterate over the data-link expressions, for different target attrs,
+ // (only one if there is a boundTagId - the case of data-linked tag {^{...}})
+ // e.g. )|!--((?:[^-]|-(?!-))*)--|(\*)))\s*((?:[^}]|}(?!}))*?))})
+ return this;
+ })();
+
+ //===============
+ // Public helpers
+ //===============
+
+ $viewsSub.viewInfos = viewInfos;
+ // Expose viewInfos() as public helper method
+
+ //====================================
+ // Additional members for linked views
+ //====================================
+
+ function transferViewTokens(prevNode, nextNode, parentElem, id, viewOrTagChar, refresh) {
+ // Transfer tokens on prevNode of viewToRemove/viewToRefresh to nextNode or parentElem._dfr
+ var i, l, vwInfos, vwInfo, viewOrTag, viewId, tokens,
+ precedingLength = 0,
+ emptyView = prevNode === nextNode;
+
+ if (prevNode) {
+ // prevNode is either the first node in the viewOrTag, or has been replaced by the vwInfos tokens string
+ vwInfos = viewInfos(prevNode) || [];
+ for (i = 0, l = vwInfos.length; i < l; i++) {
+ // Step through views or tags on the prevNode
+ vwInfo = vwInfos[i];
+ viewId = vwInfo.id;
+ if (viewId === id && vwInfo.ch === viewOrTagChar) {
+ if (refresh) {
+ // This is viewOrTagToRefresh, this is the last viewOrTag to process...
+ l = 0;
+ } else {
+ // This is viewOrTagToRemove, so we are done...
+ break;
+ }
+ }
+ if (!emptyView) {
+ viewOrTag = vwInfo.ch === "_"
+ ? viewStore[viewId]
+ : bindingStore[viewId].linkCtx.tag;
+ if (vwInfo.open) {
+ // A "#m" token
+ viewOrTag._prv = nextNode;
+ } else if (vwInfo.close) {
+ // A "/m" token
+ viewOrTag._nxt = nextNode;
+ }
+ }
+ precedingLength += viewId.length + 2;
+ }
+
+ if (precedingLength) {
+ prevNode.setAttribute(jsvAttrStr, prevNode.getAttribute(jsvAttrStr).slice(precedingLength));
+ }
+ tokens = nextNode ? nextNode.getAttribute(jsvAttrStr) : parentElem._dfr;
+ if (l = tokens.indexOf("/" + id + viewOrTagChar) + 1) {
+ tokens = vwInfos.tokens.slice(0, precedingLength) + tokens.slice(l + (refresh ? -1 : id.length + 1));
+ }
+ if (tokens) {
+ if (nextNode) {
+ // If viewOrTagToRemove was an empty viewOrTag, we will remove both #n and /n
+ // (and any intervening tokens) from the nextNode (=== prevNode)
+ // If viewOrTagToRemove was not empty, we will take tokens preceding #n from prevNode,
+ // and concatenate with tokens following /n on nextNode
+ nextNode.setAttribute(jsvAttrStr, tokens);
+ } else {
+ parentElem._dfr = tokens;
+ }
+ }
+ } else {
+ // !prevNode, so there may be a deferred nodes token on the parentElem. Remove it.
+ parentElem._dfr = removeSubStr(parentElem._dfr, "#" + id + viewOrTagChar);
+ if (!refresh && !nextNode) {
+ // If this viewOrTag is being removed, and there was no .nxt, remove closing token from deferred tokens
+ parentElem._dfr = removeSubStr(parentElem._dfr, "/" + id + viewOrTagChar);
+ }
+ }
+ }
+
+ LinkedView = {
+ // Note: a linked view will also, after linking have nodes[], _prv (prevNode), _nxt (nextNode) ...
+ addViews: function(index, dataItems, tmpl) {
+ // if view is not an array view, do nothing
+ var i, viewsCount,
+ self = this,
+ itemsCount = dataItems.length,
+ views = self.views;
+
+ if (!self._.useKey && itemsCount && (tmpl = self.tmpl)) {
+ // view is of type "array"
+ // Use passed-in template if provided, since self added view may use a different template than the original one used to render the array.
+ viewsCount = views.length + itemsCount;
+
+ if (renderAndLink(self, index, tmpl, views, dataItems, self.ctx) !== false) {
+ for (i = index + itemsCount; i < viewsCount; i++) {
+ $observable(views[i]).setProperty("index", i);
+ //This is fixing up index, but not key, and not index on child views. From child views, use view.get("item").index.
+ }
+ }
+ }
+ return self;
+ },
+
+ removeViews: function(index, itemsCount, keepNodes) {
+ // view.removeViews() removes all the child views
+ // view.removeViews( index ) removes the child view with specified index or key
+ // view.removeViews( index, count ) removes the specified nummber of child views, starting with the specified index
+ function removeView(index) {
+ var id, bindId, parentElem, prevNode, nextNode, nodesToRemove,
+ viewToRemove = views[index];
+
+ if (viewToRemove && viewToRemove.link) {
+ id = viewToRemove._.id;
+ if (!keepNodes) {
+ // Remove the HTML nodes from the DOM, unless they have already been removed, including nodes of child views
+ nodesToRemove = viewToRemove.nodes();
+ }
+
+ // Remove child views, without removing nodes
+ viewToRemove.removeViews(undefined, undefined, true);
+
+ viewToRemove.data = undefined; // Set data to undefined: used as a flag that this view is being removed
+ prevNode = viewToRemove._prv;
+ nextNode = viewToRemove._nxt;
+ parentElem = viewToRemove.parentElem;
+ // If prevNode and nextNode are the same, the view is empty
+ if (!keepNodes) {
+ // Remove the HTML nodes from the DOM, unless they have already been removed, including nodes of child views
+ if (viewToRemove._elCnt) {
+ // if keepNodes is false (and transferring of tokens has not already been done at a higher level)
+ // then transfer tokens from prevNode which is being removed, to nextNode.
+ transferViewTokens(prevNode, nextNode, parentElem, id, "_");
+ }
+ $(nodesToRemove).remove();
+ }
+ if (!viewToRemove._elCnt) {
+ prevNode.parentNode && parentElem.removeChild(prevNode);
+ nextNode.parentNode && parentElem.removeChild(nextNode);
+ }
+ setArrayChangeLink(viewToRemove);
+ for (bindId in viewToRemove._.bnds) {
+ removeViewBinding(bindId);
+ }
+ delete viewStore[id];
+ }
+ }
+
+ var current, view, viewsCount,
+ self = this,
+ isArray = !self._.useKey,
+ views = self.views;
+
+ if (isArray) {
+ viewsCount = views.length;
+ }
+ if (index === undefined) {
+ // Remove all child views
+ if (isArray) {
+ // views and data are arrays
+ current = viewsCount;
+ while (current--) {
+ removeView(current);
+ }
+ self.views = [];
+ } else {
+ // views and data are objects
+ for (view in views) {
+ // Remove by key
+ removeView(view);
+ }
+ self.views = {};
+ }
+ } else {
+ if (itemsCount === undefined) {
+ if (isArray) {
+ // The parentView is data array view.
+ // Set itemsCount to 1, to remove this item
+ itemsCount = 1;
+ } else {
+ // Remove child view with key 'index'
+ removeView(index);
+ delete views[index];
+ }
+ }
+ if (isArray && itemsCount) {
+ current = index + itemsCount;
+ // Remove indexed items (parentView is data array view);
+ while (current-- > index) {
+ removeView(current);
+ }
+ views.splice(index, itemsCount);
+ if (viewsCount = views.length) {
+ // Fixup index on following view items...
+ while (index < viewsCount) {
+ $observable(views[index]).setProperty("index", index++);
+ }
+ }
+ }
+ }
+ return this;
+ },
+
+ refresh: function(context) {
+ var self = this,
+ parent = self.parent;
+
+ if (parent) {
+ renderAndLink(self, self.index, self.tmpl, parent.views, self.data, context, true);
+ setArrayChangeLink(self);
+ }
+ return self;
+ },
+
+ nodes: getNodes,
+ contents: getContents,
+ childTags: getChildTags,
+ link: viewLink
+ };
+
+ //=========================
+ // Extend $.views.settings
+ //=========================
+
+ $viewsSettings.merge = {
+ input: {
+ from: { fromAttr: inputAttrib }, to: { toAttr: "value" }
+ },
+ textarea: valueBinding,
+ select: valueBinding,
+ optgroup: {
+ from: { fromAttr: "label" }, to: { toAttr: "label" }
+ }
+ };
+
+ if ($viewsSettings.debugMode) {
+ // In debug mode create global for accessing views, etc
+ validate = !$viewsSettings.noValidate;
+ global._jsv = {
+ views: viewStore,
+ bindings: bindingStore
+ };
+ }
+
+ //========================
+ // Extend jQuery namespace
+ //========================
+
+ $extend($, {
+
+ //=======================
+ // jQuery $.view() plugin
+ //=======================
+
+ view: $views.view = $view = function(node, inner, type) {
+ // $.view() returns top view
+ // $.view(node) returns view that contains node
+ // $.view(selector) returns view that contains first selected element
+ // $.view(nodeOrSelector, type) returns nearest containing view of given type
+ // $.view(nodeOrSelector, "root") returns root containing view (child of top view)
+ // $.view(nodeOrSelector, true, type) returns nearest inner (contained) view of given type
+
+ if (inner !== !!inner) {
+ // inner not boolean, so this is view(nodeOrSelector, type)
+ type = inner;
+ inner = undefined;
+ }
+ var view, vwInfos, i, j, k, l, elem, elems,
+ level = 0,
+ body = document.body;
+
+ if (node && node !== body && topView._.useKey > 1) {
+ // Perf optimization for common cases
+
+ node = "" + node === node
+ ? $(node)[0]
+ : node.jquery
+ ? node[0]
+ : node;
+
+ if (node) {
+ if (inner) {
+ // Treat supplied node as a container element and return the first view encountered.
+ elems = qsa ? node.querySelectorAll(bindElsSel) : $(bindElsSel, node).get();
+ l = elems.length;
+ for (i = 0; i < l; i++) {
+ elem = elems[i];
+ vwInfos = viewInfos(elem, undefined, rOpenViewMarkers);
+
+ for (j = 0, k = vwInfos.length; j < k; j++) {
+ view = vwInfos[j];
+ if (view = viewStore[view.id]) {
+ view = view && type ? view.get(true, type) : view;
+ if (view) {
+ return view;
+ }
+ }
+ }
+ }
+ } else {
+ while (node) {
+ // Move back through siblings and up through parents to find preceding node which is a _prv (prevNode)
+ // script marker node for a non-element-content view, or a _prv (first node) for an elCnt view
+ if (vwInfos = viewInfos(node, undefined, rViewMarkers)) {
+ l = vwInfos.length;
+ while (l--) {
+ view = vwInfos[l];
+ if (view.open) {
+ if (level < 1) {
+ view = viewStore[view.id];
+ return view && type ? view.get(type) : view || topView;
+ }
+ level--;
+ } else {
+ // level starts at zero. If we hit a view.close, then we move level to 1, and we don't return a view until
+ // we are back at level zero (or a parent view with level < 0)
+ level++;
+ }
+ }
+ }
+ node = node.previousSibling || node.parentNode;
+ }
+ }
+ }
+ }
+ return inner ? undefined : topView;
+ },
+
+ link: $views.link = $link,
+ unlink: $views.unlink = $unlink,
+
+ //=====================
+ // override $.cleanData
+ //=====================
+ cleanData: function(elems) {
+ if (elems.length) {
+ // Remove JsViews bindings. Also, remove from the DOM any corresponding script marker nodes
+ clean(elems);
+ // (elems HTMLCollection no longer includes removed script marker nodes)
+ oldCleanData.call($, elems);
+ }
+ }
+ });
+
+ //===============================
+ // Extend jQuery instance plugins
+ //===============================
+
+ $extend($.fn, {
+ link: function(expr, from, context, parentView, prevNode, nextNode) {
+ return $link(expr, this, from, context, parentView, prevNode, nextNode);
+ },
+ unlink: function(expr) {
+ return $unlink(expr, this);
+ },
+ view: function(type) {
+ return $view(this[0], type);
+ }
+ });
+
+ //===============
+ // Extend topView
+ //===============
+
+ $extend(topView, { tmpl: { links: {}, tags: {} }});
+ $extend(topView, LinkedView);
+ topView._.onRender = addBindingMarkers;
+
+})(this, this.jQuery);
diff --git a/nano/js/libraries/3-jquery.timers.js b/nano/js/libraries/3-jquery.timers.js
new file mode 100644
index 0000000000..7001e50310
--- /dev/null
+++ b/nano/js/libraries/3-jquery.timers.js
@@ -0,0 +1,4 @@
+jQuery.fn.extend({everyTime:function(c,a,d,b){return this.each(function(){jQuery.timer.add(this,c,a,d,b)})},oneTime:function(c,a,d){return this.each(function(){jQuery.timer.add(this,c,a,d,1)})},stopTime:function(c,a){return this.each(function(){jQuery.timer.remove(this,c,a)})}});
+jQuery.extend({timer:{global:[],guid:1,dataKey:"jQuery.timer",regex:/^([0-9]+(?:\.[0-9]*)?)\s*(.*s)?$/,powers:{ms:1,cs:10,ds:100,s:1E3,das:1E4,hs:1E5,ks:1E6},timeParse:function(c){if(c==undefined||c==null)return null;var a=this.regex.exec(jQuery.trim(c.toString()));return a[2]?parseFloat(a[1])*(this.powers[a[2]]||1):c},add:function(c,a,d,b,e){var g=0;if(jQuery.isFunction(d)){e||(e=b);b=d;d=a}a=jQuery.timer.timeParse(a);if(!(typeof a!="number"||isNaN(a)||a<0)){if(typeof e!="number"||isNaN(e)||e<0)e=
+0;e=e||0;var f=jQuery.data(c,this.dataKey)||jQuery.data(c,this.dataKey,{});f[d]||(f[d]={});b.timerID=b.timerID||this.guid++;var h=function(){if(++g>e&&e!==0||b.call(c,g)===false)jQuery.timer.remove(c,d,b)};h.timerID=b.timerID;f[d][b.timerID]||(f[d][b.timerID]=window.setInterval(h,a));this.global.push(c)}},remove:function(c,a,d){var b=jQuery.data(c,this.dataKey),e;if(b){if(a){if(b[a]){if(d){if(d.timerID){window.clearInterval(b[a][d.timerID]);delete b[a][d.timerID]}}else for(d in b[a]){window.clearInterval(b[a][d]);
+delete b[a][d]}for(e in b[a])break;if(!e){e=null;delete b[a]}}}else for(a in b)this.remove(c,a,d);for(e in b)break;e||jQuery.removeData(c,this.dataKey)}}}});jQuery(window).bind("unload",function(){jQuery.each(jQuery.timer.global,function(c,a){jQuery.timer.remove(a)})});
\ No newline at end of file
diff --git a/nano/js/nano_base_helpers.js b/nano/js/nano_base_helpers.js
new file mode 100644
index 0000000000..61ca5267b9
--- /dev/null
+++ b/nano/js/nano_base_helpers.js
@@ -0,0 +1,116 @@
+NanoBaseHelpers = function ()
+{
+ var _urlParameters = {}; // This is populated with the base url parameters, which is probaby just the "src" parameter
+
+ var init = function ()
+ {
+ var body = $('body'); // We store data in the body tag, it's as good a place as any
+
+ _urlParameters = body.data('urlParameters');
+
+ initHelpers();
+ };
+
+ var initHelpers = function ()
+ {
+ $.views.helpers({
+ // Generate a Byond link
+ link: function( text, icon, parameters, status ) {
+
+ var iconHtml = '';
+ if (typeof icon != 'undefined' && icon != null)
+ {
+ iconHtml = '';
+ }
+
+ if (typeof status != 'undefined' && status != null)
+ {
+ return '
' + iconHtml + text + '
';
+ }
+
+ return '
' + iconHtml + text + '
';
+ },
+ // Round a number to the nearest integer
+ round: function(number) {
+ return Math.round(number);
+ },
+ // Round a number down to integer
+ floor: function(number) {
+ return Math.floor(number);
+ },
+ // Round a number up to integer
+ ceil: function(number) {
+ return Math.ceil(number);
+ },
+ // Format a string (~string("Hello {0}, how are {1}?", 'Martin', 'you') becomes "Hello Martin, how are you?")
+ string: function() {
+ if (arguments.length == 0)
+ {
+ return "";
+ }
+ else if (arguments.length == 1)
+ {
+ return arguments[0];
+ }
+ else if (arguments.length > 1)
+ {
+ stringArgs = [];
+ for (var i = 1; i < arguments.length; i++)
+ {
+ stringArgs.push(arguments[i]);
+ }
+ return arguments[0].format(stringArgs);
+ }
+ return
+ }
+ });
+ }
+
+ var generateHref = function (parameters)
+ {
+ var queryString = '?';
+
+ for (var key in _urlParameters)
+ {
+ if (_urlParameters.hasOwnProperty(key))
+ {
+ if (queryString !== '?')
+ {
+ queryString += ';';
+ }
+ queryString += key + '=' + _urlParameters[key];
+ }
+ }
+
+ for (var key in parameters)
+ {
+ if (parameters.hasOwnProperty(key))
+ {
+ if (queryString !== '?')
+ {
+ queryString += ';';
+ }
+ queryString += key + '=' + parameters[key];
+ }
+ }
+ return queryString;
+ }
+
+ return {
+ init: function ()
+ {
+ init();
+ }
+ };
+} ();
+
+$(document).ready(function()
+{
+ NanoBaseHelpers.init();
+});
+
+
+
+
+
+
diff --git a/nano/js/nano_config.js b/nano/js/nano_config.js
new file mode 100644
index 0000000000..2fe30a9d72
--- /dev/null
+++ b/nano/js/nano_config.js
@@ -0,0 +1,94 @@
+var NanoConfig = function ()
+{
+ return {
+ init: function ()
+ {
+
+ }
+ }
+} ();
+
+NanoConfig.init();
+
+if (!Array.prototype.indexOf)
+{
+ Array.prototype.indexOf = function(elt /*, from*/)
+ {
+ var len = this.length;
+
+ var from = Number(arguments[1]) || 0;
+ from = (from < 0)
+ ? Math.ceil(from)
+ : Math.floor(from);
+ if (from < 0)
+ from += len;
+
+ for (; from < len; from++)
+ {
+ if (from in this &&
+ this[from] === elt)
+ return from;
+ }
+ return -1;
+ };
+}
+
+if (!String.prototype.format)
+{
+ String.prototype.format = function (args) {
+ var str = this;
+ return str.replace(String.prototype.format.regex, function(item) {
+ var intVal = parseInt(item.substring(1, item.length - 1));
+ var replace;
+ if (intVal >= 0) {
+ replace = args[intVal];
+ } else if (intVal === -1) {
+ replace = "{";
+ } else if (intVal === -2) {
+ replace = "}";
+ } else {
+ replace = "";
+ }
+ return replace;
+ });
+ };
+ String.prototype.format.regex = new RegExp("{-?[0-9]+}", "g");
+}
+
+Object.size = function(obj) {
+ var size = 0, key;
+ for (var key in obj) {
+ if (obj.hasOwnProperty(key)) size++;
+ }
+ return size;
+};
+
+if(!window.console) {
+ window.console = {
+ log : function(str) {
+ return false;
+ }
+ };
+}
+
+String.prototype.toTitleCase = function () {
+ var smallWords = /^(a|an|and|as|at|but|by|en|for|if|in|of|on|or|the|to|vs?\.?|via)$/i;
+
+ return this.replace(/([^\W_]+[^\s-]*) */g, function (match, p1, index, title) {
+ if (index > 0 && index + p1.length !== title.length &&
+ p1.search(smallWords) > -1 && title.charAt(index - 2) !== ":" &&
+ title.charAt(index - 1).search(/[^\s-]/) < 0) {
+ return match.toLowerCase();
+ }
+
+ if (p1.substr(1).search(/[A-Z]|\../) > -1) {
+ return match;
+ }
+
+ return match.charAt(0).toUpperCase() + match.substr(1);
+ });
+};
+
+$.ajaxSetup({
+ cache: false
+});
\ No newline at end of file
diff --git a/nano/js/nano_update.js b/nano/js/nano_update.js
new file mode 100644
index 0000000000..1034ac8979
--- /dev/null
+++ b/nano/js/nano_update.js
@@ -0,0 +1,196 @@
+NanoUpdate = function ()
+{
+ var _isInitialised = false;
+
+ var _templates = null;
+ var _data = null;
+ var _earlyUpdateData = null; // This is for newer data which has arrived before the template has been rendered
+
+ var _beforeUpdateCallbacks = [];
+ var _afterUpdateCallbacks = [];
+
+ var _canClick = true;
+
+ var init = function ()
+ {
+ var body = $('body'); // We store data in the body tag, it's as good a place as any
+
+ _data = body.data('initialData');
+
+ var templateData = body.data('templateData');
+
+ var templateCount = 0;
+ for (var key in templateData)
+ {
+ if (templateData.hasOwnProperty(key))
+ {
+ templateCount++;
+ }
+ }
+
+ for (var key in templateData)
+ {
+ if (templateData.hasOwnProperty(key))
+ {
+ $.when($.get(templateData[key]))
+ .done(function(templateData) {
+ if (_templates == null)
+ {
+ _templates = {};
+ }
+
+ templateData += ''
+
+ try
+ {
+ _templates[key] = $.templates(templateData);
+ _templates[key].link( "#mainTemplate", _data ); // initial data gets applied first, before any updates
+
+ templateCount--;
+
+ if (templateCount <= 0)
+ {
+ _isInitialised = true;
+ }
+
+ if (_earlyUpdateData !== null) // Newer data has already arrived, so update
+ {
+ observedDataUpdateRecursive(_earlyUpdateData, _data);
+ }
+
+ //alert($("#mainTemplate").html());
+ }
+ catch(error)
+ {
+ alert('An error occurred while loading the UI: ' + error.message);
+ return;
+ }
+ });
+ }
+ }
+
+ NanoUpdate.addAfterUpdateCallback(function (updateData) {
+ var uiStatusClass;
+ if (updateData['ui']['status'] == 2)
+ {
+ uiStatusClass = 'icon24 uiStatusGood';
+ $('.linkActive').removeClass('inactive');
+ }
+ else if (updateData['ui']['status'] == 1)
+ {
+ uiStatusClass = 'icon24 uiStatusAverage';
+ $('.linkActive').addClass('inactive');
+ }
+ else
+ {
+ uiStatusClass = 'icon24 uiStatusBad'
+ $('.linkActive').addClass('inactive');
+ }
+ $('#uiStatusIcon').attr('class', uiStatusClass);
+
+ $('.linkActive').stopTime('linkPending');
+ $('.linkActive').removeClass('linkPending');
+
+ $('.linkActive').off('click');
+ $('.linkActive').on('click', function (event) {
+ event.preventDefault();
+ var href = $(this).data('href');
+ if (href != null && _canClick)
+ {
+ _canClick = false;
+ $('body').oneTime(300, 'enableClick', function () {
+ _canClick = true;
+ });
+ $(this).oneTime(300, 'linkPending', function () {
+ $(this).addClass('linkPending');
+ });
+ window.location.href = href;
+ }
+ });
+ });
+ };
+
+ // Receive update data from the server
+ var receiveUpdateData = function (jsonString)
+ {
+ var updateData = jQuery.parseJSON(jsonString);
+
+ if (_isInitialised) // templates have been loaded and are observing the data. We need to update it recursively
+ {
+ executeCallbacks(_beforeUpdateCallbacks, updateData);
+
+ observedDataUpdateRecursive(updateData, _data);
+
+ executeCallbacks(_afterUpdateCallbacks, updateData);
+ }
+ else
+ {
+ _earlyUpdateData = updateData; // templates have not been loaded, therefor they are not observing the data. We set _earlyUpdateData which will be applied after the template is loaded with the initial data
+ }
+ }
+
+ // This function updates the observed data recursively
+ // It has to be done recursively as each piece of data is observed individually and needs to be updated individually
+ var observedDataUpdateRecursive = function (updateData, data, path)
+ {
+ if (path === null || typeof path === 'undefined')
+ {
+ path = '';
+ }
+ else
+ {
+ path += '.';
+ }
+ for (var key in updateData)
+ {
+ if (updateData.hasOwnProperty(key))
+ {
+ var currentPath = path + key;
+ if (updateData[key] != null && typeof updateData[key] === 'object' && !$.isArray(updateData[key]))
+ {
+ observedDataUpdateRecursive(updateData[key], data, currentPath)
+ }
+ else
+ {
+ $.observable(data).setProperty(currentPath, updateData[key]);
+ }
+ }
+ }
+ }
+
+ var executeCallbacks = function (callbacks, updateData)
+ {
+ for (var index in callbacks)
+ {
+ callbacks[index].call(this, updateData);
+ }
+ }
+
+ return {
+ init: function ()
+ {
+ init();
+ },
+ isInitialised: function ()
+ {
+ return _isInitialised;
+ },
+ receiveUpdateData: function (jsonString)
+ {
+ receiveUpdateData(jsonString);
+ },
+ addBeforeUpdateCallback: function (callbackFunction)
+ {
+ _beforeUpdateCallbacks.push(callbackFunction);
+ },
+ addAfterUpdateCallback: function (callbackFunction)
+ {
+ _afterUpdateCallbacks.push(callbackFunction);
+ }
+ };
+} ();
+
+$(document).ready(function()
+{
+ NanoUpdate.init();
+});
\ No newline at end of file
diff --git a/nano/templates/apc.tmpl b/nano/templates/apc.tmpl
new file mode 100644
index 0000000000..446ba31a14
--- /dev/null
+++ b/nano/templates/apc.tmpl
@@ -0,0 +1,141 @@
+
+ {^{if locked}}
+ Swipe ID card to unlock interface
+ {{else}}
+ Swipe ID card to lock interface
+ {{/if}}
+
\ No newline at end of file
diff --git a/nano/templates/cryo.tmpl b/nano/templates/cryo.tmpl
new file mode 100644
index 0000000000..3399876ac0
--- /dev/null
+++ b/nano/templates/cryo.tmpl
@@ -0,0 +1,58 @@
+
\ No newline at end of file
diff --git a/nano/uiBackground.fla b/nano/uiBackground.fla
new file mode 100644
index 0000000000..60f9f1ee94
Binary files /dev/null and b/nano/uiBackground.fla differ