diff --git a/.travis.yml b/.travis.yml index 432d409314..bfdb40e23b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,9 +6,11 @@ env: BYOND_MAJOR="510" BYOND_MINOR="1346" MACRO_COUNT=986 + NODE_VERSION="4" cache: directories: + - tgui/node_modules - $HOME/BYOND-${BYOND_MAJOR}.${BYOND_MINOR} addons: @@ -19,10 +21,13 @@ addons: - libstdc++6:i386 before_script: + - cd tgui && npm install && cd .. - chmod +x ./install-byond.sh - ./install-byond.sh install: + - rm -rf ~/.nvm && git clone https://github.com/creationix/nvm.git ~/.nvm && (cd ~/.nvm && git checkout `git describe --abbrev=0 --tags`) && source ~/.nvm/nvm.sh && nvm install $NODE_VERSION + - npm install -g gulp-cli - pip install --user PyYaml -q - pip install --user beautifulsoup4 -q @@ -34,6 +39,8 @@ script: - awk -f tools/indentation.awk **/*.dm - md5sum -c - <<< "88490b460c26947f5ec1ab1bb9fa9f17 *html/changelogs/example.yml" - (num=`grep -E '\\\\(red|blue|green|black|b|i[^mc])' **/*.dm | wc -l`; echo "$num escapes (expecting ${MACRO_COUNT} or less)"; [ $num -le ${MACRO_COUNT} ]) + - cd tgui && gulp + - cd .. - source $HOME/BYOND-${BYOND_MAJOR}.${BYOND_MINOR}/byond/bin/byondsetup - python tools/TagMatcher/tag-matcher.py ../.. - echo "#define UNIT_TEST 1" > code/_unit_tests.dm diff --git a/code/__defines/tgui.dm b/code/__defines/tgui.dm new file mode 100644 index 0000000000..5ba0096d1b --- /dev/null +++ b/code/__defines/tgui.dm @@ -0,0 +1,4 @@ +#define UI_INTERACTIVE 2 // Green/Interactive +#define UI_UPDATE 1 // Orange/Updates Only +#define UI_DISABLED 0 // Red/Disabled +#define UI_CLOSE -1 // Closed \ No newline at end of file diff --git a/code/__defines/tick.dm b/code/__defines/tick.dm new file mode 100644 index 0000000000..71a63af628 --- /dev/null +++ b/code/__defines/tick.dm @@ -0,0 +1,5 @@ +#define TICK_LIMIT_RUNNING 90 +#define TICK_LIMIT_TO_RUN 85 + +#define TICK_CHECK ( world.tick_usage > TICK_LIMIT_RUNNING ? stoplag() : 0 ) +#define CHECK_TICK if(world.tick_usage > TICK_LIMIT_RUNNING) stoplag() \ No newline at end of file diff --git a/code/_compatibility/509/text.dm b/code/_compatibility/509/text.dm index c22790ccb8..7e17e82795 100644 --- a/code/_compatibility/509/text.dm +++ b/code/_compatibility/509/text.dm @@ -1,6 +1,22 @@ #if DM_VERSION < 510 +//Case Sensitive! +/proc/text2listEx(text, delimiter="\n") + var/delim_len = length(delimiter) + if(delim_len < 1) return list(text) + . = list() + var/last_found = 1 + var/found + do + found = findtextEx(text, delimiter, last_found, 0) + . += copytext(text, last_found, found) + last_found = found + delim_len + while(found) + /proc/replacetext(text, find, replacement) return jointext(splittext(text, find), replacement) +/proc/replacetextEx(text, find, replacement) + return jointext(text2listEx(text, find), replacement) + #endif \ No newline at end of file diff --git a/code/_helpers/atom_movables.dm b/code/_helpers/atom_movables.dm new file mode 100644 index 0000000000..1dc8ba2749 --- /dev/null +++ b/code/_helpers/atom_movables.dm @@ -0,0 +1,28 @@ +/proc/get_turf_pixel(atom/movable/AM) + if(!istype(AM)) + return + + //Find AM's matrix so we can use it's X/Y pixel shifts + var/matrix/M = matrix(AM.transform) + + var/pixel_x_offset = AM.pixel_x + M.get_x_shift() + var/pixel_y_offset = AM.pixel_y + M.get_y_shift() + + //Irregular objects + if(AM.bound_height != world.icon_size || AM.bound_width != world.icon_size) + var/icon/AMicon = icon(AM.icon, AM.icon_state) + pixel_x_offset += ((AMicon.Width()/world.icon_size)-1)*(world.icon_size*0.5) + pixel_y_offset += ((AMicon.Height()/world.icon_size)-1)*(world.icon_size*0.5) + qdel(AMicon) + + //DY and DX + var/rough_x = round(round(pixel_x_offset,world.icon_size)/world.icon_size) + var/rough_y = round(round(pixel_y_offset,world.icon_size)/world.icon_size) + + //Find coordinates + var/turf/T = get_turf(AM) //use AM's turfs, as it's coords are the same as AM's AND AM's coords are lost if it is inside another atom + var/final_x = T.x + rough_x + var/final_y = T.y + rough_y + + if(final_x || final_y) + return locate(final_x, final_y, T.z) \ No newline at end of file diff --git a/code/_helpers/lists.dm b/code/_helpers/lists.dm index d4e6710b35..cd4f1d2f4c 100644 --- a/code/_helpers/lists.dm +++ b/code/_helpers/lists.dm @@ -323,6 +323,30 @@ proc/listclearnulls(list/list) return (result + L.Copy(Li, 0)) return (result + R.Copy(Ri, 0)) +/proc/sortByVar(var/list/L, var/key) + if(L.len < 2) + return L + var/middle = L.len / 2 + 1 + return mergeVaredLists(sortByVar(L.Copy(0, middle), key), sortByVar(L.Copy(middle), key), key) + +/proc/mergeVaredLists(var/list/L, var/list/R, var/key) + var/Li=1 + var/Ri=1 + var/list/result = new() + while(Li <= L.len && Ri <= R.len) + var/datum/LO = L[Li] + var/datum/RO = R[Ri] + if(LO.vars[key] > RO.vars[key]) + // Works around list += list2 merging lists; it's not pretty but it works + result += "temp item" + result[result.len] = R[Ri++] + else + result += "temp item" + result[result.len] = L[Li++] + + if(Li <= L.len) + return (result + L.Copy(Li, 0)) + return (result + R.Copy(Ri, 0)) //Mergesort: any value in a list, preserves key=value structure /proc/sortAssoc(var/list/L) diff --git a/code/_helpers/matrices.dm b/code/_helpers/matrices.dm index abb0366382..52edb17088 100644 --- a/code/_helpers/matrices.dm +++ b/code/_helpers/matrices.dm @@ -15,3 +15,11 @@ animate(src, transform = m120, time = speed, loops) animate(transform = m240, time = speed) animate(transform = m360, time = speed) + +//The X pixel offset of this matrix +/matrix/proc/get_x_shift() + . = c + +//The Y pixel offset of this matrix +/matrix/proc/get_y_shift() + . = f \ No newline at end of file diff --git a/code/_helpers/unsorted.dm b/code/_helpers/unsorted.dm index d8623fa3eb..ff349379b1 100644 --- a/code/_helpers/unsorted.dm +++ b/code/_helpers/unsorted.dm @@ -1315,3 +1315,15 @@ var/mob/dview/dview_mob = new tY = max(1, min(world.maxy, origin.y + (text2num(tY) - (world.view + 1)))) return locate(tX, tY, tZ) +//Key thing that stops lag. Cornerstone of performance in ss13, Just sitting here, in unsorted.dm. +/proc/stoplag() + . = 1 + sleep(world.tick_lag) + if(world.tick_usage > TICK_LIMIT_TO_RUN) //woke up, still not enough tick, sleep for more. + . += 2 + sleep(world.tick_lag*2) + if(world.tick_usage > TICK_LIMIT_TO_RUN) //woke up, STILL not enough tick, sleep for more. + . += 4 + sleep(world.tick_lag*4) + //you might be thinking of adding more steps to this, or making it use a loop and a counter var + // not worth it. \ No newline at end of file diff --git a/code/controllers/Processes/garbage.dm b/code/controllers/Processes/garbage.dm index 2d56dde1a2..b993c46222 100644 --- a/code/controllers/Processes/garbage.dm +++ b/code/controllers/Processes/garbage.dm @@ -66,6 +66,9 @@ world/loop_checks = 0 testing("GC: [refID] old enough to test: GCd_at_time: [GCd_at_time] time_to_kill: [time_to_kill] current: [world.time]") #endif if(A && A.gcDestroyed == GCd_at_time) // So if something else coincidently gets the same ref, it's not deleted by mistake + #ifdef GC_FINDREF + LocateReferences(A) + #endif // Something's still referring to the qdel'd object. Kill it. testing("GC: -- \ref[A] | [A.type] was unable to be GC'd and was deleted --") logging["[A.type]"]++ @@ -88,29 +91,47 @@ world/loop_checks = 0 #undef GC_COLLECTIONS_PER_TICK #ifdef GC_FINDREF -/datum/controller/process/garbage_collector/proc/LookForRefs(var/datum/D, var/list/targ) + +/datum/controller/process/garbage_collector/proc/LocateReferences(var/atom/A) + testing("GC: Attempting to locate references to [A] | [A.type]. This is a potentially long-running operation.") + if(istype(A)) + if(A.loc != null) + testing("GC: [A] | [A.type] is located in [A.loc] instead of null") + if(A.contents.len) + testing("GC: [A] | [A.type] has contents: [jointext(A.contents)]") + var/ref_count = 0 + for(var/atom/atom) + ref_count += LookForRefs(atom, A) + for(var/datum/datum) // This is strictly /datum, not subtypes. + ref_count += LookForRefs(datum, A) + for(var/client/client) + ref_count += LookForRefs(client, A) + var/message = "GC: References found to [A] | [A.type]: [ref_count]." + if(!ref_count) + message += " Has likely been supplied as an 'in list' argment to a proc." + testing(message) + +/datum/controller/process/garbage_collector/proc/LookForRefs(var/datum/D, var/datum/A) . = 0 for(var/V in D.vars) if(V == "contents") continue - if(istype(D.vars[V], /atom)) - var/atom/A = D.vars[V] - if(A in targ) + if(!islist(D.vars[V])) + if(D.vars[V] == A) testing("GC: [A] | [A.type] referenced by [D] | [D.type], var [V]") . += 1 - else if(islist(D.vars[V])) - . += LookForListRefs(D.vars[V], targ, D, V) + else + . += LookForListRefs(D.vars[V], A, D, V) -/datum/controller/process/garbage_collector/proc/LookForListRefs(var/list/L, var/list/targ, var/datum/D, var/V) +/datum/controller/process/garbage_collector/proc/LookForListRefs(var/list/L, var/datum/A, var/datum/D, var/V) . = 0 for(var/F in L) - if(istype(F, /atom)) - var/atom/A = F - if(A in targ) + if(!islist(F)) + if(F == A || L[F] == A) testing("GC: [A] | [A.type] referenced by [D] | [D.type], list [V]") . += 1 - if(islist(F)) - . += LookForListRefs(F, targ, D, "[F] in list [V]") + else + . += LookForListRefs(F, A, D, "[F] in list [V]") #endif /datum/controller/process/garbage_collector/proc/AddTrash(datum/A) diff --git a/code/controllers/Processes/tgui.dm b/code/controllers/Processes/tgui.dm new file mode 100644 index 0000000000..4eef090f2e --- /dev/null +++ b/code/controllers/Processes/tgui.dm @@ -0,0 +1,28 @@ +var/global/datum/controller/process/tgui/tgui_process + +/datum/controller/process/tgui + var/list/tg_open_uis = list() // A list of open UIs, grouped by src_object and ui_key. + var/list/processing_uis = list() // A list of processing UIs, ungrouped. + var/basehtml // The HTML base used for all UIs. + +/datum/controller/process/tgui/setup() + name = "tgui" + schedule_interval = 10 // every 2 seconds + start_delay = 23 + + basehtml = file2text('tgui/tgui.html') // Read the HTML from disk. + tgui_process = src + +/datum/controller/process/tgui/doWork() + for(var/gui in processing_uis) + var/datum/tgui/ui = gui + if(ui && ui.user && ui.src_object) + ui.process() + SCHECK + continue + processing_uis.Remove(ui) + SCHECK + +/datum/controller/process/tgui/statProcess() + ..() + stat(null, "[tgui_process.processing_uis.len] UI\s") \ No newline at end of file diff --git a/code/datums/weakref.dm b/code/datums/weakref.dm new file mode 100644 index 0000000000..3c04580c40 --- /dev/null +++ b/code/datums/weakref.dm @@ -0,0 +1,31 @@ +/datum + var/weakref + +/datum/Destroy() + weakref = null // Clear this reference to ensure it's kept for as brief duration as possible. + . = ..() + +//obtain a weak reference to a datum +/proc/weakref(datum/D) + if(D.gcDestroyed) + return + if(!D.weakref) + D.weakref = new /datum/weakref(D) + return D.weakref + +/datum/weakref + var/ref + +/datum/weakref/New(datum/D) + ref = "\ref[D]" + +/datum/weakref/Destroy() + // A weakref datum should not be manually destroyed as it is a shared resource, + // rather it should be automatically collected by the BYOND GC when all references are gone. + return 0 + +/datum/weakref/proc/resolve() + var/datum/D = locate(ref) + if(D && D.weakref == src) + return D + return null \ No newline at end of file diff --git a/code/game/machinery/atmoalter/canister.dm b/code/game/machinery/atmoalter/canister.dm index 80b33cfaa0..f24e2fa388 100644 --- a/code/game/machinery/atmoalter/canister.dm +++ b/code/game/machinery/atmoalter/canister.dm @@ -265,20 +265,17 @@ update_flag return src.attack_hand(user) /obj/machinery/portable_atmospherics/canister/attack_hand(var/mob/user as mob) - return src.ui_interact(user) + return src.tg_ui_interact(user) -/obj/machinery/portable_atmospherics/canister/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - if (src.destroyed) - return - - // this is the data which will be sent to the ui - var/data[0] +/obj/machinery/portable_atmospherics/canister/ui_data(mob/user) + var/list/data = list() data["name"] = name data["canLabel"] = can_label ? 1 : 0 data["portConnected"] = connected_port ? 1 : 0 data["tankPressure"] = round(air_contents.return_pressure() ? air_contents.return_pressure() : 0) data["releasePressure"] = round(release_pressure ? release_pressure : 0) data["minReleasePressure"] = round(ONE_ATMOSPHERE/10) + data["defaultReleasePressure"] = ONE_ATMOSPHERE data["maxReleasePressure"] = round(10*ONE_ATMOSPHERE) data["valveOpen"] = valve_open ? 1 : 0 @@ -286,83 +283,70 @@ update_flag if (holding) data["holdingTank"] = list("name" = holding.name, "tankPressure" = round(holding.air_contents.return_pressure())) - // update the ui if it exists, returns null if no ui is passed/found - ui = nanomanager.try_update_ui(user, src, ui_key, ui, data, force_open) - if (!ui) - // the ui does not exist, so we'll create a new() one - // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm - ui = new(user, src, ui_key, "canister.tmpl", "Canister", 480, 400) - // when the ui is first opened this is the data it will use - ui.set_initial_data(data) - // open the new ui window - ui.open() - // auto update every Master Controller tick - ui.set_auto_update(1) + return data -/obj/machinery/portable_atmospherics/canister/Topic(href, href_list) - - //Do not use "if(..()) return" here, canisters will stop working in unpowered areas like space or on the derelict. // yeah but without SOME sort of Topic check any dick can mess with them via exploits as he pleases -walter0o - //First comment might be outdated. - if (!istype(src.loc, /turf)) - return 0 - - if(!usr.canmove || usr.stat || usr.restrained() || !in_range(loc, usr)) // exploit protection -walter0o - usr << browse(null, "window=canister") - onclose(usr, "canister") +/obj/machinery/portable_atmospherics/canister/tg_ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = tg_physical_state) + if (src.destroyed) return + ui = tgui_process.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "canister", name, 400, 400, master_ui, state) + ui.open() - if(href_list["toggle"]) - if (valve_open) - if (holding) - release_log += "Valve was closed by [usr] ([usr.ckey]), stopping the transfer into the [holding]
" - else - release_log += "Valve was closed by [usr] ([usr.ckey]), stopping the transfer into the air
" - else - if (holding) - release_log += "Valve was opened by [usr] ([usr.ckey]), starting the transfer into the [holding]
" - else - release_log += "Valve was opened by [usr] ([usr.ckey]), starting the transfer into the air
" - log_open() - valve_open = !valve_open +/obj/machinery/portable_atmospherics/canister/ui_status(mob/user, datum/ui_state/state) + if(!istype(src.loc, /turf)) + return UI_CLOSE + return ..() - if (href_list["remove_tank"]) - if(holding) +/obj/machinery/portable_atmospherics/canister/ui_act(action, params) + switch(action) + if("relabel") + if (can_label) + var/list/colors = list(\ + "\[N2O\]" = "redws", \ + "\[N2\]" = "red", \ + "\[O2\]" = "blue", \ + "\[Phoron\]" = "orange", \ + "\[CO2\]" = "black", \ + "\[Air\]" = "grey", \ + "\[CAUTION\]" = "yellow", \ + ) + var/label = input("Choose canister label", "Gas canister") as null|anything in colors + if (label) + src.canister_color = colors[label] + src.icon_state = colors[label] + src.name = "\improper Canister: [label]" + if("pressure") + var/diff = text2num(params["adjust"]) + if(diff > 0) + release_pressure = min(10*ONE_ATMOSPHERE, release_pressure+diff) + else + release_pressure = max(ONE_ATMOSPHERE/10, release_pressure+diff) + + if("valve") if (valve_open) - valve_open = 0 - release_log += "Valve was closed by [usr] ([usr.ckey]), stopping the transfer into the [holding]
" - if(istype(holding, /obj/item/weapon/tank)) - holding.manipulated_by = usr.real_name - holding.loc = loc - holding = null + if (holding) + release_log += "Valve was closed by [usr] ([usr.ckey]), stopping the transfer into the [holding]
" + else + release_log += "Valve was closed by [usr] ([usr.ckey]), stopping the transfer into the air
" + else + if (holding) + release_log += "Valve was opened by [usr] ([usr.ckey]), starting the transfer into the [holding]
" + else + release_log += "Valve was opened by [usr] ([usr.ckey]), starting the transfer into the air
" + log_open() + valve_open = !valve_open - if (href_list["pressure_adj"]) - var/diff = text2num(href_list["pressure_adj"]) - if(diff > 0) - release_pressure = min(10*ONE_ATMOSPHERE, release_pressure+diff) - else - release_pressure = max(ONE_ATMOSPHERE/10, release_pressure+diff) - - if (href_list["relabel"]) - if (can_label) - var/list/colors = list(\ - "\[N2O\]" = "redws", \ - "\[N2\]" = "red", \ - "\[O2\]" = "blue", \ - "\[Phoron\]" = "orange", \ - "\[CO2\]" = "black", \ - "\[Air\]" = "grey", \ - "\[CAUTION\]" = "yellow", \ - ) - var/label = input("Choose canister label", "Gas canister") as null|anything in colors - if (label) - src.canister_color = colors[label] - src.icon_state = colors[label] - src.name = "Canister: [label]" - - src.add_fingerprint(usr) - update_icon() - - return 1 + if("eject") + if(holding) + if (valve_open) + valve_open = 0 + release_log += "Valve was closed by [usr] ([usr.ckey]), stopping the transfer into the [holding]
" + if(istype(holding, /obj/item/weapon/tank)) + holding.manipulated_by = usr.real_name + holding.loc = loc + holding = null + return TRUE /obj/machinery/portable_atmospherics/canister/phoron/New() ..() diff --git a/code/game/machinery/doors/airlock_electronics.dm b/code/game/machinery/doors/airlock_electronics.dm index 3ae0891b89..b09997c2e0 100644 --- a/code/game/machinery/doors/airlock_electronics.dm +++ b/code/game/machinery/doors/airlock_electronics.dm @@ -11,103 +11,95 @@ req_access = list(access_engine) var/secure = 0 //if set, then wires will be randomized and bolts will drop if the door is broken - var/list/conf_access = null + var/list/conf_access = list() var/one_access = 0 //if set to 1, door would receive req_one_access instead of req_access var/last_configurator = null var/locked = 1 - attack_self(mob/user as mob) - if (!ishuman(user) && !istype(user,/mob/living/silicon/robot)) - return ..(user) - var/t1 = text("Access control
\n") - if (last_configurator) - t1 += "Operator: [last_configurator]
" +/obj/item/weapon/airlock_electronics/attack_self(mob/user as mob) + if (!ishuman(user) && !istype(user,/mob/living/silicon/robot)) + return ..(user) - if (locked) - t1 += "Swipe ID
" - else - t1 += "Block
" + tg_ui_interact(user) - t1 += "Access requirement is set to " - t1 += one_access ? "ONE
" : "ALL
" - t1 += conf_access == null ? "All
" : "All
" - t1 += "
" +//tgui interact code generously lifted from tgstation. +/obj/item/weapon/airlock_electronics/tg_ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, \ + datum/tgui/master_ui = null, datum/ui_state/state = hands_state) - var/list/accesses = get_all_station_access() - for (var/acc in accesses) - var/aname = get_access_desc(acc) + tgui_process.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "airlock_electronics", src.name, 1000, 500, master_ui, state) + ui.open() - if (!conf_access || !conf_access.len || !(acc in conf_access)) - t1 += "[aname]
" - else if(one_access) - t1 += "[aname]
" - else - t1 += "[aname]
" +/obj/item/weapon/airlock_electronics/ui_data(mob/user) + var/list/data = list() + var/list/regions = list() - t1 += text("

Close

\n", src) + for(var/i in ACCESS_REGION_SECURITY to ACCESS_REGION_SUPPLY) //code/game/jobs/_access_defs.dm + var/list/region = list() + var/list/accesses = list() + for(var/j in get_region_accesses(i)) + var/list/access = list() + access["name"] = get_access_desc(j) + access["id"] = j + access["req"] = (j in src.conf_access) + accesses[++accesses.len] = access + region["name"] = get_region_accesses_name(i) + region["accesses"] = accesses + regions[++regions.len] = region + data["regions"] = regions + data["oneAccess"] = one_access + data["locked"] = locked - user << browse(t1, "window=airlock_electronics") - onclose(user, "airlock") + return data - Topic(href, href_list) - ..() - if (usr.stat || usr.restrained() || (!ishuman(usr) && !istype(usr,/mob/living/silicon))) - return - if (href_list["close"]) - usr << browse(null, "window=airlock") - return - - if (href_list["login"]) +/obj/item/weapon/airlock_electronics/ui_act(action, params) + if(..()) + return TRUE + switch(action) + if("clear") + conf_access = list() + one_access = 0 + return TRUE + if("one_access") + one_access = !one_access + return TRUE + if("set") + var/access = text2num(params["access"]) + if (!(access in conf_access)) + conf_access += access + else + conf_access -= access + return TRUE + if("unlock") if(istype(usr,/mob/living/silicon)) - src.locked = 0 - src.last_configurator = usr.name + locked = 0 + last_configurator = usr.name + return TRUE else var/obj/item/I = usr.get_active_hand() if (istype(I, /obj/item/device/pda)) var/obj/item/device/pda/pda = I I = pda.id + if(!istype(I, /obj/item/weapon/card/id)) + usr << "[\src] flashes a yellow LED near the ID scanner. Did you remember to scan your ID or PDA?" + return TRUE if (I && src.check_access(I)) - src.locked = 0 - src.last_configurator = I:registered_name - - if (locked) - return - - if (href_list["logout"]) - locked = 1 - - if (href_list["one_access"]) - one_access = !one_access - - if (href_list["access"]) - toggle_access(href_list["access"]) - - attack_self(usr) - - proc - toggle_access(var/acc) - if (acc == "all") - conf_access = null - else - var/req = text2num(acc) - - if (conf_access == null) - conf_access = list() - - if (!(req in conf_access)) - conf_access += req + locked = 0 + last_configurator = I:registered_name else - conf_access -= req - if (!conf_access.len) - conf_access = null - + usr << "[\src] flashes a red LED near the ID scanner, indicating your access has been denied." + return TRUE + if("lock") + locked = 1 + . = TRUE /obj/item/weapon/airlock_electronics/secure name = "secure airlock electronics" desc = "designed to be somewhat more resistant to hacking than standard electronics." origin_tech = list(TECH_DATA = 2) - secure = 1 + secure = 1 \ No newline at end of file diff --git a/code/game/machinery/doors/brigdoors.dm b/code/game/machinery/doors/brigdoors.dm index 27ce595a77..44a8ea20e6 100644 --- a/code/game/machinery/doors/brigdoors.dm +++ b/code/game/machinery/doors/brigdoors.dm @@ -53,7 +53,6 @@ return return - //Main door timer loop, if it's timing and time is >0 reduce time by 1. // if it's less than 0, open door, reset timer // update the door_timer window and the icon @@ -68,12 +67,10 @@ if(timeleft > 1e5) src.releasetime = 0 - if(world.timeofday > src.releasetime) src.timer_end() // open doors, reset timer, clear status screen src.timing = 0 - src.updateUsrDialog() src.update_icon() else @@ -81,14 +78,12 @@ return - // has the door power situation changed, if so update icon. /obj/machinery/door_timer/power_change() ..() update_icon() return - // open/closedoor checks if door_timer has power, if so it checks if the // linked door is open/closed (by density) then opens it/closes it. @@ -99,6 +94,9 @@ // Set releasetime releasetime = world.timeofday + timetoset + //set timing + timing = 1 + for(var/obj/machinery/door/window/brigdoor/door in targets) if(door.density) continue spawn(0) @@ -111,7 +109,6 @@ C.icon_state = C.icon_locked return 1 - // Opens and unlocks doors, power check /obj/machinery/door_timer/proc/timer_end() if(stat & (NOPOWER|BROKEN)) return 0 @@ -119,6 +116,9 @@ // Reset releasetime releasetime = 0 + //reset timing + timing = 0 + for(var/obj/machinery/door/window/brigdoor/door in targets) if(!door.density) continue spawn(0) @@ -132,7 +132,6 @@ return 1 - // Check for releasetime timeleft /obj/machinery/door_timer/proc/timeleft() . = (releasetime - world.timeofday)/10 @@ -152,116 +151,62 @@ /obj/machinery/door_timer/attack_ai(var/mob/user as mob) return src.attack_hand(user) - -//Allows humans to use door_timer -//Opens dialog window when someone clicks on door timer -// Allows altering timer and the timing boolean. -// Flasher activation limited to 150 seconds /obj/machinery/door_timer/attack_hand(var/mob/user as mob) - if(..()) - return + tg_ui_interact(user) - // Used for the 'time left' display - var/second = round(timeleft() % 60) - var/minute = round((timeleft() - second) / 60) +/obj/machinery/door_timer/ui_data(mob/user) + var/list/data = list() - // Used for 'set timer' - var/setsecond = round((timetoset / 10) % 60) - var/setminute = round(((timetoset / 10) - setsecond) / 60) + data["timing"] = timing + data["releasetime"] = releasetime + data["timetoset"] = timetoset + data["timeleft"] = timeleft() - user.set_machine(src) + var/list/flashes = list() - // dat - var/dat = "" - - dat += "
Timer System:" - dat += " Door [src.id] controls
" - - // Start/Stop timer - if (src.timing) - dat += "Stop Timer and open door
" - else - dat += "Activate Timer and close door
" - - // Time Left display (uses releasetime) - dat += "Time Left: [(minute ? text("[minute]:") : null)][second]
" - dat += "
" - - // Set Timer display (uses timetoset) - if(src.timing) - dat += "Set Timer: [(setminute ? text("[setminute]:") : null)][setsecond] Set
" - else - dat += "Set Timer: [(setminute ? text("[setminute]:") : null)][setsecond]
" - - // Controls - dat += "- - + +
" - - // Mounted flash controls - for(var/obj/machinery/flasher/F in targets) - if(F.last_flash && (F.last_flash + 150) > world.time) - dat += "
Flash Charging" + for(var/obj/machinery/flasher/flash in targets) + var/list/flashdata = list() + if(flash.last_flash && (flash.last_flash + 150) > world.time) + flashdata["status"] = 0 else - dat += "
Activate Flash" + flashdata["status"] = 1 + flashes[++flashes.len] = flashdata - dat += "

Close" - dat += "
" + data["flashes"] = flashes + return data - user << browse(dat, "window=computer;size=400x500") - onclose(user, "computer") - return - - -//Function for using door_timer dialog input, checks if user has permission -// href_list to -// "timing" turns on timer -// "tp" value to modify timer -// "fc" activates flasher -// "change" resets the timer to the timetoset amount while the timer is counting down -// Also updates dialog window and timer icon -/obj/machinery/door_timer/Topic(href, href_list) +/obj/machinery/door_timer/ui_act(action, params) if(..()) - return - if(!src.allowed(usr)) - return - - usr.set_machine(src) - - if(href_list["timing"]) - src.timing = text2num(href_list["timing"]) - - if(src.timing) - src.timer_start() - else - src.timer_end() - - else - if(href_list["tp"]) //adjust timer, close door if not already closed - var/tp = text2num(href_list["tp"]) - var/addtime = (timetoset / 10) - addtime += tp - addtime = min(max(round(addtime), 0), 3600) - - timeset(addtime) - - if(href_list["fc"]) - for(var/obj/machinery/flasher/F in targets) - F.flash() - - if(href_list["change"]) - src.timer_start() + return TRUE src.add_fingerprint(usr) - src.updateUsrDialog() + + if(!src.allowed(usr)) + return TRUE + + switch (action) + if("start") + if(timetoset > 18000) + log_admin("[key_name(usr)] has started a brig timer over 30 minutes in length!") + message_admins("[key_name_admin(usr)] has started a brig timer over 30 minutes in length!") + timer_start() + if("stop") + timer_end() + if("flash") + for(var/obj/machinery/flasher/F in targets) + F.flash() + if("time") + timetoset += text2num(params["adjust"]) + timetoset = Clamp(timetoset, 0, 36000) + src.update_icon() + return TRUE - /* if(src.timing) - src.timer_start() - - else - src.timer_end() */ - - return - +/obj/machinery/door_timer/tg_ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = default_state) + ui = tgui_process.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "brig_timer", name , 300, 150, master_ui, state) + ui.open() //icon update function // if NOPOWER, display blank @@ -282,7 +227,9 @@ disp2 = "Error" update_display(disp1, disp2) else - if(maptext) maptext = "" + if(maptext) + maptext = "" + update_display("Set","Time") // would be nice to have some default printed text return diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm index ac2c156fa4..3f0d9e4613 100644 --- a/code/game/machinery/doors/door.dm +++ b/code/game/machinery/doors/door.dm @@ -31,7 +31,6 @@ var/close_door_at = 0 //When to automatically close the door, if possible //Multi-tile doors - dir = EAST var/width = 1 // turf animation diff --git a/code/game/machinery/doors/multi_tile.dm b/code/game/machinery/doors/multi_tile.dm index 4fc4b2fad7..a87939bf25 100644 --- a/code/game/machinery/doors/multi_tile.dm +++ b/code/game/machinery/doors/multi_tile.dm @@ -1,6 +1,7 @@ //Terribly sorry for the code doubling, but things go derpy otherwise. /obj/machinery/door/airlock/multi_tile width = 2 + dir = EAST /obj/machinery/door/airlock/multi_tile/New() ..() diff --git a/code/game/machinery/jukebox.dm b/code/game/machinery/jukebox.dm index 3cc537be49..4a27cafa90 100644 --- a/code/game/machinery/jukebox.dm +++ b/code/game/machinery/jukebox.dm @@ -8,7 +8,7 @@ datum/track/New(var/title_name, var/audio) title = title_name sound = audio -/obj/machinery/media/jukebox/ +/obj/machinery/media/jukebox name = "space jukebox" icon = 'icons/obj/jukebox.dmi' icon_state = "jukebox2-nopower" @@ -44,10 +44,11 @@ datum/track/New(var/title_name, var/audio) component_parts += new /obj/item/weapon/stock_parts/console_screen(src) component_parts += new /obj/item/stack/cable_coil(src, 5) RefreshParts() + update_icon() /obj/machinery/media/jukebox/Destroy() StopPlaying() - ..() + . = ..() /obj/machinery/media/jukebox/power_change() if(!powered(power_channel) || !anchored) @@ -74,10 +75,7 @@ datum/track/New(var/title_name, var/audio) else overlays += "[state_base]-running" -/obj/machinery/media/jukebox/Topic(href, href_list) - if(..() || !(Adjacent(usr) || istype(usr, /mob/living/silicon))) - return - +/obj/machinery/media/jukebox/interact(mob/user) if(!anchored) usr << "You must secure \the [src] first." return @@ -86,70 +84,74 @@ datum/track/New(var/title_name, var/audio) usr << "\The [src] doesn't appear to function." return - if(href_list["change_track"]) - for(var/datum/track/T in tracks) - if(T.title == href_list["title"]) - current_track = T - StartPlaying() - break - else if(href_list["stop"]) - StopPlaying() - else if(href_list["play"]) - if(emagged) - playsound(src.loc, 'sound/items/AirHorn.ogg', 100, 1) - for(var/mob/living/carbon/M in ohearers(6, src)) - if(M.get_ear_protection() >= 2) - continue - M.sleeping = 0 - M.stuttering += 20 - M.ear_deaf += 30 - M.Weaken(3) - if(prob(30)) - M.Stun(10) - M.Paralyse(4) - else - M.make_jittery(500) - spawn(15) - explode() - else if(current_track == null) - usr << "No track selected." - else - StartPlaying() + tg_ui_interact(user) - return 1 +/obj/machinery/media/jukebox/ui_status(mob/user, datum/ui_state/state) + if(!anchored || inoperable()) + return UI_CLOSE + return ..() -/obj/machinery/media/jukebox/interact(mob/user) - if(stat & (NOPOWER|BROKEN)) - usr << "\The [src] doesn't appear to function." - return - - ui_interact(user) - -/obj/machinery/media/jukebox/ui_interact(mob/user, ui_key = "jukebox", var/datum/nanoui/ui = null, var/force_open = 1) - var/title = "RetroBox - Space Style" - var/data[0] - - if(!(stat & (NOPOWER|BROKEN))) - data["current_track"] = current_track != null ? current_track.title : "" - data["playing"] = playing - - var/list/nano_tracks = new - for(var/datum/track/T in tracks) - nano_tracks[++nano_tracks.len] = list("track" = T.title) - - data["tracks"] = nano_tracks - - // update the ui if it exists, returns null if no ui is passed/found - ui = nanomanager.try_update_ui(user, src, ui_key, ui, data, force_open) - if (!ui) - // the ui does not exist, so we'll create a new() one - // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm - ui = new(user, src, ui_key, "jukebox.tmpl", title, 450, 600) - // when the ui is first opened this is the data it will use - ui.set_initial_data(data) - // open the new ui window +/obj/machinery/media/jukebox/tg_ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = tg_default_state) + ui = tgui_process.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "jukebox", "RetroBox - Space Style", 340, 440, master_ui, state) ui.open() +/obj/machinery/media/jukebox/ui_data() + var/list/juke_tracks = new + for(var/datum/track/T in tracks) + juke_tracks.Add(T.title) + + var/list/data = list( + "current_track" = current_track != null ? current_track.title : "No track selected", + "playing" = playing, + "tracks" = juke_tracks + ) + + return data + +/obj/machinery/media/jukebox/ui_act(action, params) + if(..()) + return TRUE + switch(action) + if("change_track") + for(var/datum/track/T in tracks) + if(T.title == params["title"]) + current_track = T + StartPlaying() + break + . = TRUE + if("stop") + StopPlaying() + . = TRUE + if("play") + if(emagged) + emag_play() + else if(!current_track) + usr << "No track selected." + else + StartPlaying() + . = TRUE + +/obj/machinery/media/jukebox/proc/emag_play() + playsound(loc, 'sound/items/AirHorn.ogg', 100, 1) + for(var/mob/living/carbon/M in ohearers(6, src)) + if(istype(M, /mob/living/carbon/human)) + var/mob/living/carbon/human/H = M + if(istype(H.l_ear, /obj/item/clothing/ears/earmuffs) || istype(H.r_ear, /obj/item/clothing/ears/earmuffs)) + continue + M.sleeping = 0 + M.stuttering += 20 + M.ear_deaf += 30 + M.Weaken(3) + if(prob(30)) + M.Stun(10) + M.Paralyse(4) + else + M.make_jittery(500) + spawn(15) + explode() + /obj/machinery/media/jukebox/attack_ai(mob/user as mob) return src.attack_hand(user) @@ -172,10 +174,6 @@ datum/track/New(var/title_name, var/audio) /obj/machinery/media/jukebox/attackby(obj/item/W as obj, mob/user as mob) src.add_fingerprint(user) - if(default_deconstruction_screwdriver(user, W)) - return - if(default_deconstruction_crowbar(user, W)) - return if(istype(W, /obj/item/weapon/wrench)) if(playing) StopPlaying() @@ -200,7 +198,6 @@ datum/track/New(var/title_name, var/audio) // Always kill the current sound for(var/mob/living/M in mobs_in_area(main_area)) M << sound(null, channel = 1) - main_area.forced_ambience = null playing = 0 update_use_power(1) @@ -220,4 +217,4 @@ datum/track/New(var/title_name, var/audio) playing = 1 update_use_power(2) - update_icon() + update_icon() \ No newline at end of file diff --git a/code/game/machinery/newscaster.dm b/code/game/machinery/newscaster.dm index 7bfe32d719..6d77713ce6 100644 --- a/code/game/machinery/newscaster.dm +++ b/code/game/machinery/newscaster.dm @@ -90,6 +90,8 @@ /datum/feed_network/proc/insert_message_in_channel(var/datum/feed_channel/FC, var/datum/feed_message/newMsg) FC.messages += newMsg + if(newMsg.img) + register_asset("newscaster_photo_[sanitize(FC.channel_name)]_[FC.messages.len].png", newMsg.img) newMsg.parent_channel = FC FC.update() alert_readers(FC.announcement) @@ -377,11 +379,12 @@ var/list/obj/machinery/newscaster/allCasters = list() //Global list that will co else var/i = 0 for(var/datum/feed_message/MESSAGE in src.viewing_channel.messages) - i++ + ++i dat+="-[MESSAGE.body]
" if(MESSAGE.img) - usr << browse_rsc(MESSAGE.img, "tmp_photo[i].png") - dat+="
" + var/resourc_name = "newscaster_photo_[sanitize(viewing_channel.channel_name)]_[i].png" + send_asset(usr.client, resourc_name) + dat+="
" if(MESSAGE.caption) dat+="[MESSAGE.caption]
" dat+="
" @@ -874,11 +877,12 @@ obj/item/weapon/newspaper/attack_self(mob/user as mob) dat+="" if(scribble_page==curr_page) diff --git a/code/game/objects/items/devices/PDA/PDA.dm b/code/game/objects/items/devices/PDA/PDA.dm index f0bb82efd0..2c208ac62b 100644 --- a/code/game/objects/items/devices/PDA/PDA.dm +++ b/code/game/objects/items/devices/PDA/PDA.dm @@ -620,9 +620,9 @@ var/global/list/obj/item/device/pda/PDAs = list() if(!FC.censored) var/index = 0 for(var/datum/feed_message/FM in FC.messages) - index++ + ++index if(FM.img) - usr << browse_rsc(FM.img, "pda_news_tmp_photo_[feed["channel"]]_[index].png") + send_asset(usr.client, "newscaster_photo_[sanitize(FC.channel_name)]_[index].png") // News stories are HTML-stripped but require newline replacement to be properly displayed in NanoUI var/body = replacetext(FM.body, "\n", "
") messages[++messages.len] = list("author" = FM.author, "body" = body, "message_type" = FM.message_type, "time_stamp" = FM.time_stamp, "has_image" = (FM.img != null), "caption" = FM.caption, "index" = index) @@ -651,6 +651,8 @@ var/global/list/obj/item/device/pda/PDAs = list() //NOTE: graphic resources are loaded on client login /obj/item/device/pda/attack_self(mob/user as mob) + var/datum/asset/assets = get_asset_datum(/datum/asset/simple/pda) + assets.send(user) user.set_machine(src) diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index a3f77757f2..51e2d86e8a 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -115,6 +115,7 @@ /obj/attack_ghost(mob/user) ui_interact(user) + tg_ui_interact(user) ..() /obj/proc/interact(mob/user) diff --git a/code/modules/admin/callproc/callproc.dm b/code/modules/admin/callproc/callproc.dm index 9b3da2b92f..cb05d1fe5b 100644 --- a/code/modules/admin/callproc/callproc.dm +++ b/code/modules/admin/callproc/callproc.dm @@ -125,7 +125,7 @@ return if("marked datum") - current = holder.marked_datum + current = holder.marked_datum() if(!current) switch(alert("You do not currently have a marked datum; do you want to pass null instead?",, "Yes", "Cancel")) if("Yes") diff --git a/code/modules/admin/holder2.dm b/code/modules/admin/holder2.dm index 9c73dd9e86..779207861c 100644 --- a/code/modules/admin/holder2.dm +++ b/code/modules/admin/holder2.dm @@ -6,13 +6,17 @@ var/list/admin_datums = list() var/rights = 0 var/fakekey = null - var/datum/marked_datum + var/datum/weakref/marked_datum_weak var/admincaster_screen = 0 //See newscaster.dm under machinery for a full description var/datum/feed_message/admincaster_feed_message = new /datum/feed_message //These two will act as holders. var/datum/feed_channel/admincaster_feed_channel = new /datum/feed_channel var/admincaster_signature //What you'll sign the newsfeeds as +/datum/admins/proc/marked_datum() + if(marked_datum_weak) + return marked_datum_weak.resolve() + /datum/admins/New(initial_rank = "Temporary Admin", initial_rights = 0, ckey) if(!ckey) error("Admin datum created without a ckey argument. Datum has been deleted") diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index cf98ede72f..a59a88f12a 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -1603,6 +1603,7 @@ where = "onfloor" if ( where == "inmarked" ) + var/marked_datum = marked_datum() if ( !marked_datum ) usr << "You don't have any object marked. Abandoning spawn." return @@ -1620,7 +1621,7 @@ if ("relative") target = locate(loc.x + X,loc.y + Y,loc.z + Z) if ( "inmarked" ) - target = marked_datum + target = marked_datum() if(target) for (var/path in paths) diff --git a/code/modules/admin/verbs/modifyvariables.dm b/code/modules/admin/verbs/modifyvariables.dm index 51f1fbceb3..90a040c4c3 100644 --- a/code/modules/admin/verbs/modifyvariables.dm +++ b/code/modules/admin/verbs/modifyvariables.dm @@ -27,20 +27,20 @@ var/list/VVckey_edit = list("key", "ckey") src.modify_variables(ticker) feedback_add_details("admin_verb","ETV") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! -/client/proc/mod_list_add_ass() //haha - +/client/proc/mod_list_add_ass() var/class = "text" - if(src.holder && src.holder.marked_datum) - class = input("What kind of variable?","Variable Type") as null|anything in list("text", - "num","type","reference","mob reference", "icon","file","list","edit referenced object","restore to default","marked datum ([holder.marked_datum.type])") - else - class = input("What kind of variable?","Variable Type") as null|anything in list("text", - "num","type","reference","mob reference", "icon","file","list","edit referenced object","restore to default") + var/list/class_input = list("text","num","type","reference","mob reference", "icon","file","list","edit referenced object","restore to default") + if(src.holder) + var/datum/marked_datum = holder.marked_datum() + if(marked_datum) + class_input += "marked datum ([marked_datum.type])" + class = input("What kind of variable?","Variable Type") as null|anything in class_input if(!class) return - if(holder.marked_datum && class == "marked datum ([holder.marked_datum.type])") + var/datum/marked_datum = holder.marked_datum() + if(marked_datum && class == "marked datum ([marked_datum.type])") class = "marked datum" var/var_value = null @@ -69,7 +69,7 @@ var/list/VVckey_edit = list("key", "ckey") var_value = input("Pick icon:","Icon") as null|icon if("marked datum") - var_value = holder.marked_datum + var_value = holder.marked_datum() if(!var_value) return @@ -79,17 +79,18 @@ var/list/VVckey_edit = list("key", "ckey") /client/proc/mod_list_add(var/list/L, atom/O, original_name, objectvar) var/class = "text" - if(src.holder && src.holder.marked_datum) - class = input("What kind of variable?","Variable Type") as null|anything in list("text", - "num","type","reference","mob reference", "icon","file","list","edit referenced object","restore to default","marked datum ([holder.marked_datum.type])") - else - class = input("What kind of variable?","Variable Type") as null|anything in list("text", - "num","type","reference","mob reference", "icon","file","list","edit referenced object","restore to default") + var/list/class_input = list("text","num","type","reference","mob reference", "icon","file","list","edit referenced object","restore to default") + if(src.holder) + var/datum/marked_datum = holder.marked_datum() + if(marked_datum) + class_input += "marked datum ([marked_datum.type])" + class = input("What kind of variable?","Variable Type") as null|anything in class_input if(!class) return - if(holder.marked_datum && class == "marked datum ([holder.marked_datum.type])") + var/datum/marked_datum = holder.marked_datum() + if(marked_datum && class == "marked datum ([marked_datum.type])") class = "marked datum" var/var_value = null @@ -118,7 +119,7 @@ var/list/VVckey_edit = list("key", "ckey") var_value = input("Pick icon:","Icon") as icon if("marked datum") - var_value = holder.marked_datum + var_value = holder.marked_datum() if(!var_value) return @@ -244,17 +245,21 @@ var/list/VVckey_edit = list("key", "ckey") usr << "If a direction, direction is: [dir]" var/class = "text" - if(src.holder && src.holder.marked_datum) - class = input("What kind of variable?","Variable Type",default) as null|anything in list("text", - "num","type","reference","mob reference", "icon","file","list","edit referenced object","restore to default","marked datum ([holder.marked_datum.type])", "DELETE FROM LIST") - else - class = input("What kind of variable?","Variable Type",default) as null|anything in list("text", - "num","type","reference","mob reference", "icon","file","list","edit referenced object","restore to default", "DELETE FROM LIST") + var/list/class_input = list("text","num","type","reference","mob reference", "icon","file","list","edit referenced object","restore to default") + + if(src.holder) + var/datum/marked_datum = holder.marked_datum() + if(marked_datum) + class_input += "marked datum ([marked_datum.type])" + + class_input += "DELETE FROM LIST" + class = input("What kind of variable?","Variable Type",default) as null|anything in class_input if(!class) return - if(holder.marked_datum && class == "marked datum ([holder.marked_datum.type])") + var/datum/marked_datum = holder.marked_datum() + if(marked_datum && class == "marked datum ([marked_datum.type])") class = "marked datum" var/original_var @@ -336,7 +341,9 @@ var/list/VVckey_edit = list("key", "ckey") L[L.Find(variable)] = new_var if("marked datum") - new_var = holder.marked_datum + new_var = holder.marked_datum() + if(!new_var) + return if(assoc) L[assoc_key] = new_var else @@ -501,12 +508,12 @@ var/list/VVckey_edit = list("key", "ckey") if(dir) usr << "If a direction, direction is: [dir]" - if(src.holder && src.holder.marked_datum) - class = input("What kind of variable?","Variable Type",default) as null|anything in list("text", - "num","type","reference","mob reference", "icon","file","list","edit referenced object","restore to default","marked datum ([holder.marked_datum.type])") - else - class = input("What kind of variable?","Variable Type",default) as null|anything in list("text", - "num","type","reference","mob reference", "icon","file","list","edit referenced object","restore to default") + var/list/class_input = list("text","num","type","reference","mob reference", "icon","file","list","edit referenced object","restore to default") + if(src.holder) + var/datum/marked_datum = holder.marked_datum() + if(marked_datum) + class_input += "marked datum ([marked_datum.type])" + class = input("What kind of variable?","Variable Type",default) as null|anything in class_input if(!class) return @@ -518,7 +525,8 @@ var/list/VVckey_edit = list("key", "ckey") else original_name = O:name - if(holder.marked_datum && class == "marked datum ([holder.marked_datum.type])") + var/datum/marked_datum = holder.marked_datum() + if(marked_datum && class == "marked datum ([marked_datum.type])") class = "marked datum" switch(class) @@ -584,7 +592,7 @@ var/list/VVckey_edit = list("key", "ckey") O.vars[variable] = var_new if("marked datum") - O.vars[variable] = holder.marked_datum + O.vars[variable] = holder.marked_datum() world.log << "### VarEdit by [src]: [O.type] [variable]=[html_encode("[O.vars[variable]]")]" log_admin("[key_name(src)] modified [original_name]'s [variable] to [O.vars[variable]]") diff --git a/code/modules/admin/view_variables/topic.dm b/code/modules/admin/view_variables/topic.dm index 71da9f81ed..9acc180fc2 100644 --- a/code/modules/admin/view_variables/topic.dm +++ b/code/modules/admin/view_variables/topic.dm @@ -223,7 +223,7 @@ usr << "This can only be done to instances of type /datum" return - src.holder.marked_datum = D + src.holder.marked_datum_weak = weakref(D) href_list["datumrefresh"] = href_list["mark_object"] else if(href_list["rotatedatum"]) @@ -476,7 +476,10 @@ usr << "This can only be done on mobs with clients" return - H.client.reload_nanoui_resources() + nanomanager.close_uis(H) + H.client.cache.Cut() + var/datum/asset/assets = get_asset_datum(/datum/asset/nanoui) + assets.send(H) usr << "Resource files sent" H << "Your NanoUI Resource files have been refreshed" diff --git a/code/modules/admin/view_variables/view_variables.dm b/code/modules/admin/view_variables/view_variables.dm index e624d2df05..571dfff560 100644 --- a/code/modules/admin/view_variables/view_variables.dm +++ b/code/modules/admin/view_variables/view_variables.dm @@ -45,7 +45,7 @@
[replacetext("[D.type]", "/", "/")] - [holder.marked_datum == D ? "
Marked Object" : ""] + [holder.marked_datum() == D ? "
Marked Object" : ""]
diff --git a/code/modules/client/asset_cache.dm b/code/modules/client/asset_cache.dm index b4a42b0a96..3cfb75606d 100644 --- a/code/modules/client/asset_cache.dm +++ b/code/modules/client/asset_cache.dm @@ -200,10 +200,45 @@ proc/getFilesSlow(var/client/client, var/list/files, var/register_asset = TRUE) "large_stamp-law.png" = 'icons/paper_icons/large_stamp-law.png', "large_stamp-cent.png" = 'icons/paper_icons/large_stamp-cent.png', "talisman.png" = 'icons/paper_icons/talisman.png', - "ntlogo.png" = 'icons/paper_icons/ntlogo.png' + "ntlogo.png" = 'icons/paper_icons/ntlogo.png', "sglogo.png" = 'icons/paper_icons/sglogo.png' ) +/datum/asset/simple/tgui + assets = list( + "tgui.css" = 'tgui/assets/tgui.css', + "tgui.js" = 'tgui/assets/tgui.js' + ) + +/datum/asset/simple/pda + assets = list( + "pda_atmos.png" = 'icons/pda_icons/pda_atmos.png', + "pda_back.png" = 'icons/pda_icons/pda_back.png', + "pda_bell.png" = 'icons/pda_icons/pda_bell.png', + "pda_blank.png" = 'icons/pda_icons/pda_blank.png', + "pda_boom.png" = 'icons/pda_icons/pda_boom.png', + "pda_bucket.png" = 'icons/pda_icons/pda_bucket.png', + "pda_chatroom.png" = 'icons/pda_icons/pda_chatroom.png', + "pda_crate.png" = 'icons/pda_icons/pda_crate.png', + "pda_cuffs.png" = 'icons/pda_icons/pda_cuffs.png', + "pda_eject.png" = 'icons/pda_icons/pda_eject.png', + "pda_exit.png" = 'icons/pda_icons/pda_exit.png', + "pda_honk.png" = 'icons/pda_icons/pda_honk.png', + "pda_locked.png" = 'icons/pda_icons/pda_locked.png', + "pda_mail.png" = 'icons/pda_icons/pda_mail.png', + "pda_medical.png" = 'icons/pda_icons/pda_medical.png', + "pda_menu.png" = 'icons/pda_icons/pda_menu.png', + "pda_mule.png" = 'icons/pda_icons/pda_mule.png', + "pda_notes.png" = 'icons/pda_icons/pda_notes.png', + "pda_power.png" = 'icons/pda_icons/pda_power.png', + "pda_rdoor.png" = 'icons/pda_icons/pda_rdoor.png', + "pda_reagent.png" = 'icons/pda_icons/pda_reagent.png', + "pda_refresh.png" = 'icons/pda_icons/pda_refresh.png', + "pda_scanner.png" = 'icons/pda_icons/pda_scanner.png', + "pda_signaler.png" = 'icons/pda_icons/pda_signaler.png', + "pda_status.png" = 'icons/pda_icons/pda_status.png' + ) + /datum/asset/nanoui var/list/common = list() @@ -239,4 +274,4 @@ proc/getFilesSlow(var/client/client, var/list/files, var/register_asset = TRUE) uncommon = list(uncommon) send_asset_list(client, uncommon) - send_asset_list(client, common) + send_asset_list(client, common) \ No newline at end of file diff --git a/code/modules/client/preference_setup/global/setting_datums.dm b/code/modules/client/preference_setup/global/setting_datums.dm index b584341fd5..61e20c986d 100644 --- a/code/modules/client/preference_setup/global/setting_datums.dm +++ b/code/modules/client/preference_setup/global/setting_datums.dm @@ -150,6 +150,18 @@ var/list/_client_preferences_by_type enabled_description = "Fancy" disabled_description = "Plain" +/datum/client_preference/tgui_style + description ="tgui Style" + key = "TGUI_FANCY" + enabled_description = "Fancy" + disabled_description = "Plain" + +/datum/client_preference/tgui_monitor + description ="tgui Monitor" + key = "TGUI_MONITOR" + enabled_description = "Primary" + disabled_description = "All" + /******************** * Staff Preferences * ********************/ diff --git a/code/modules/clothing/spacesuits/rig/rig.dm b/code/modules/clothing/spacesuits/rig/rig.dm index ae064dd2a8..a2b5fca3a7 100644 --- a/code/modules/clothing/spacesuits/rig/rig.dm +++ b/code/modules/clothing/spacesuits/rig/rig.dm @@ -798,6 +798,9 @@ return 0 return 1 +/obj/item/weapon/rig/check_access(obj/item/I) + return TRUE + /obj/item/weapon/rig/proc/force_rest(var/mob/user) if(!ai_can_move_suit(user, check_user_module = 1)) return diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index d4a967dc7b..67c167ef13 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -720,7 +720,10 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp /mob/observer/dead/canface() return 1 -/mob/observer/dead/proc/can_admin_interact() +/mob/proc/can_admin_interact() + return 0 + +/mob/observer/dead/can_admin_interact() return check_rights(R_ADMIN, 0, src) /mob/observer/dead/verb/toggle_ghostsee() diff --git a/code/modules/mob/logout.dm b/code/modules/mob/logout.dm index bb01c846ca..f6f7087ebb 100644 --- a/code/modules/mob/logout.dm +++ b/code/modules/mob/logout.dm @@ -1,5 +1,6 @@ /mob/Logout() nanomanager.user_logout(src) // this is used to clean up (remove) this user's Nano UIs + tgui_process.on_logout(src) player_list -= src log_access("Logout: [key_name(src)]") if(admin_datums[src.ckey]) diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index 2f788862f8..fe5988a2f0 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -576,4 +576,10 @@ var/list/global/organ_rel_size = list( ) /mob/proc/flash_eyes(intensity = FLASH_PROTECTION_MODERATE, override_blindness_check = FALSE, affect_silicon = FALSE, visual = FALSE, type = /obj/screen/fullscreen/flash) - return \ No newline at end of file + return + +/proc/get_both_hands(mob/living/carbon/M) + if(!istype(M)) + return + var/list/hands = list(M.l_hand, M.r_hand) + return hands \ No newline at end of file diff --git a/code/modules/nano/interaction/hands.dm b/code/modules/nano/interaction/hands.dm new file mode 100644 index 0000000000..34251ad416 --- /dev/null +++ b/code/modules/nano/interaction/hands.dm @@ -0,0 +1,9 @@ +/* + This state only checks if user is conscious. +*/ +/var/global/datum/topic_state/hands/hands_state = new() + +/datum/topic_state/hands/can_use_topic(var/src_object, var/mob/user) + . = user.shared_ui_interaction(src_object) + if(. > STATUS_CLOSE) + . = min(., user.hands_can_use_topic(src_object)) \ No newline at end of file diff --git a/code/modules/nano/modules/nano_module.dm b/code/modules/nano/modules/nano_module.dm index 0cdf451d6f..40530db0fe 100644 --- a/code/modules/nano/modules/nano_module.dm +++ b/code/modules/nano/modules/nano_module.dm @@ -1,6 +1,6 @@ /datum/nano_module var/name - var/host + var/datum/host /datum/nano_module/New(var/host) src.host = host diff --git a/code/modules/nano/nanomapgen.dm b/code/modules/nano/nanomapgen.dm index 3b2b4158e8..7b9bf6050f 100644 --- a/code/modules/nano/nanomapgen.dm +++ b/code/modules/nano/nanomapgen.dm @@ -57,22 +57,21 @@ world.log << "NanoMapGen: GENERATE MAP ([startX],[startY],[currentZ]) to ([endX],[endY],[currentZ])" usr << "NanoMapGen: GENERATE MAP ([startX],[startY],[currentZ]) to ([endX],[endY],[currentZ])" - var/count = 0; for(var/WorldX = startX, WorldX <= endX, WorldX++) for(var/WorldY = startY, WorldY <= endY, WorldY++) + var/turf/T = locate(WorldX, WorldY, currentZ) + var/list/atoms = T.contents + T + atoms = sortByVar(atoms, "layer") + for(var/atom/A in atoms) + if(A.type == /turf/space|| istype(A, /mob) || A.invisibility > 0) + continue - var/atom/Turf = locate(WorldX, WorldY, currentZ) + var/icon/TurfIcon = new(A.icon, A.icon_state, A.dir, 1, 0) + TurfIcon.Scale(NANOMAP_ICON_SIZE, NANOMAP_ICON_SIZE) - var/icon/TurfIcon = new(Turf.icon, Turf.icon_state) - TurfIcon.Scale(NANOMAP_ICON_SIZE, NANOMAP_ICON_SIZE) + Tile.Blend(TurfIcon, ICON_OVERLAY, ((WorldX - 1) * NANOMAP_ICON_SIZE) + (A.pixel_x * NANOMAP_ICON_SIZE / 32), ((WorldY - 1) * NANOMAP_ICON_SIZE) + (A.pixel_y * NANOMAP_ICON_SIZE / 32)) - Tile.Blend(TurfIcon, ICON_OVERLAY, ((WorldX - 1) * NANOMAP_ICON_SIZE), ((WorldY - 1) * NANOMAP_ICON_SIZE)) - - count++ - - if (count % 8000 == 0) - world.log << "NanoMapGen: [count] tiles done" - sleep(1) + CHECK_TICK var/mapFilename = "nanomap_z[currentZ]-new.png" diff --git a/code/modules/tgui/external.dm b/code/modules/tgui/external.dm new file mode 100644 index 0000000000..74ba1c114e --- /dev/null +++ b/code/modules/tgui/external.dm @@ -0,0 +1,98 @@ + /** + * tgui external + * + * Contains all external tgui declarations. + **/ + + /** + * public + * + * Used to open and update UIs. + * If this proc is not implemented properly, the UI will not update correctly. + * + * required user mob The mob who opened/is using the UI. + * optional ui_key string The ui_key of the UI. + * optional ui datum/tgui The UI to be updated, if it exists. + * optional force_open bool If the UI should be re-opened instead of updated. + * optional master_ui datum/tgui The parent UI. + * optional state datum/ui_state The state used to determine status. + **/ +/datum/proc/tg_ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = tg_default_state) + return -1 // Not implemented. + + /** + * public + * + * Data to be sent to the UI. + * This must be implemented for a UI to work. + * + * required user mob The mob interacting with the UI. + * + * return list Data to be sent to the UI. + **/ +/datum/proc/ui_data(mob/user, ui_key = "main") + return list() // Not implemented. + + + /** + * public + * + * Called on a UI when the UI receieves a href. + * Think of this as Topic(). + * + * required action string The action/button that has been invoked by the user. + * required params list A list of parameters attached to the button. + * + * return bool If the UI should be updated or not. + **/ +/datum/proc/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + if(!ui || ui.status != UI_INTERACTIVE) + return 1 // If UI is not interactive or usr calling Topic is not the UI user, bail. + + + /** + * private + * + * The UI's host object (usually src_object). + * This allows modules/datums to have the UI attached to them, + * and be a part of another object. + **/ +/datum/proc/ui_host() + return src // Default src. + + /** + * global + * + * Used to track the current screen. + **/ +/datum/var/ui_screen = "home" + + /** + * global + * + * Used to track UIs for a mob. + **/ +/mob/var/list/tg_open_uis = list() + + /** + * verb + * + * Called by UIs when they are closed. + * Must be a verb so winset() can call it. + * + * required uiref ref The UI that was closed. + **/ +/client/verb/uiclose(ref as text) + // Name the verb, and hide it from the user panel. + set name = "uiclose" + set hidden = 1 + + // Get the UI based on the ref. + var/datum/tgui/ui = locate(ref) + + // If we found the UI, close it. + if(istype(ui)) + ui.close() + // Unset machine just to be sure. + if(src && src.mob) + src.mob.unset_machine() diff --git a/code/modules/tgui/process.dm b/code/modules/tgui/process.dm new file mode 100644 index 0000000000..1afaae6df6 --- /dev/null +++ b/code/modules/tgui/process.dm @@ -0,0 +1,223 @@ + /** + * tgui process + * + * Contains all tgui state and process code. + **/ + + /** + * public + * + * Get a open UI given a user, src_object, and ui_key and try to update it with data. + * + * required user mob The mob who opened/is using the UI. + * required src_object datum The object/datum which owns the UI. + * required ui_key string The ui_key of the UI. + * optional ui datum/tgui The UI to be updated, if it exists. + * optional force_open bool If the UI should be re-opened instead of updated. + * + * return datum/tgui The found UI. + **/ +/datum/controller/process/tgui/proc/try_update_ui(mob/user, datum/src_object, ui_key, datum/tgui/ui, force_open = 0) + if(isnull(ui)) // No UI was passed, so look for one. + ui = get_open_ui(user, src_object, ui_key) + + if(!isnull(ui)) + var/data = src_object.ui_data(user) // Get data from the src_object. + if(!force_open) // UI is already open; update it. + ui.push_data(data) + else // Re-open it anyways. + ui.reinitialize(null, data) + return ui // We found the UI, return it. + else + return null // We couldn't find a UI. + + /** + * private + * + * Get a open UI given a user, src_object, and ui_key. + * + * required user mob The mob who opened/is using the UI. + * required src_object datum The object/datum which owns the UI. + * required ui_key string The ui_key of the UI. + * + * return datum/tgui The found UI. + **/ +/datum/controller/process/tgui/proc/get_open_ui(mob/user, datum/src_object, ui_key) + var/src_object_key = "\ref[src_object]" + if(isnull(tg_open_uis[src_object_key]) || !istype(tg_open_uis[src_object_key], /list)) + return null // No UIs open. + else if(isnull(tg_open_uis[src_object_key][ui_key]) || !istype(tg_open_uis[src_object_key][ui_key], /list)) + return null // No UIs open for this object. + + for(var/datum/tgui/ui in tg_open_uis[src_object_key][ui_key]) // Find UIs for this object. + if(ui.user == user) // Make sure we have the right user + return ui + + return null // Couldn't find a UI! + + /** + * private + * + * Update all UIs attached to src_object. + * + * required src_object datum The object/datum which owns the UIs. + * + * return int The number of UIs updated. + **/ +/datum/controller/process/tgui/proc/update_uis(datum/src_object) + var/src_object_key = "\ref[src_object]" + if(isnull(tg_open_uis[src_object_key]) || !istype(tg_open_uis[src_object_key], /list)) + return 0 // Couldn't find any UIs for this object. + + var/update_count = 0 + for(var/ui_key in tg_open_uis[src_object_key]) + for(var/datum/tgui/ui in tg_open_uis[src_object_key][ui_key]) + if(ui && ui.src_object && ui.user && ui.src_object.ui_host()) // Check the UI is valid. + ui.process(force = 1) // Update the UI. + update_count++ // Count each UI we update. + return update_count + + /** + * private + * + * Close all UIs attached to src_object. + * + * required src_object datum The object/datum which owns the UIs. + * + * return int The number of UIs closed. + **/ +/datum/controller/process/tgui/proc/close_uis(datum/src_object) + var/src_object_key = "\ref[src_object]" + if(isnull(tg_open_uis[src_object_key]) || !istype(tg_open_uis[src_object_key], /list)) + return 0 // Couldn't find any UIs for this object. + + var/close_count = 0 + for(var/ui_key in tg_open_uis[src_object_key]) + for(var/datum/tgui/ui in tg_open_uis[src_object_key][ui_key]) + if(ui && ui.src_object && ui.user && ui.src_object.ui_host()) // Check the UI is valid. + ui.close() // Close the UI. + close_count++ // Count each UI we close. + return close_count + + /** + * private + * + * Update all UIs belonging to a user. + * + * required user mob The mob who opened/is using the UI. + * optional src_object datum If provided, only update UIs belonging this src_object. + * optional ui_key string If provided, only update UIs with this UI key. + * + * return int The number of UIs updated. + **/ +/datum/controller/process/tgui/proc/update_user_uis(mob/user, datum/src_object = null, ui_key = null) + if(isnull(user.tg_open_uis) || !istype(user.tg_open_uis, /list) || tg_open_uis.len == 0) + return 0 // Couldn't find any UIs for this user. + + var/update_count = 0 + for(var/datum/tgui/ui in user.tg_open_uis) + if((isnull(src_object) || !isnull(src_object) && ui.src_object == src_object) && (isnull(ui_key) || !isnull(ui_key) && ui.ui_key == ui_key)) + ui.process(force = 1) // Update the UI. + update_count++ // Count each UI we upadte. + return update_count + + /** + * private + * + * Close all UIs belonging to a user. + * + * required user mob The mob who opened/is using the UI. + * optional src_object datum If provided, only close UIs belonging this src_object. + * optional ui_key string If provided, only close UIs with this UI key. + * + * return int The number of UIs closed. + **/ +/datum/controller/process/tgui/proc/close_user_uis(mob/user, datum/src_object = null, ui_key = null) + if(isnull(user.tg_open_uis) || !istype(user.tg_open_uis, /list) || tg_open_uis.len == 0) + return 0 // Couldn't find any UIs for this user. + + var/close_count = 0 + for(var/datum/tgui/ui in user.tg_open_uis) + if((isnull(src_object) || !isnull(src_object) && ui.src_object == src_object) && (isnull(ui_key) || !isnull(ui_key) && ui.ui_key == ui_key)) + ui.close() // Close the UI. + close_count++ // Count each UI we close. + return close_count + + /** + * private + * + * Add a UI to the list of open UIs. + * + * required ui datum/tgui The UI to be added. + **/ +/datum/controller/process/tgui/proc/on_open(datum/tgui/ui) + var/src_object_key = "\ref[ui.src_object]" + if(isnull(tg_open_uis[src_object_key]) || !istype(tg_open_uis[src_object_key], /list)) + tg_open_uis[src_object_key] = list(ui.ui_key = list()) // Make a list for the ui_key and src_object. + else if(isnull(tg_open_uis[src_object_key][ui.ui_key]) || !istype(tg_open_uis[src_object_key][ui.ui_key], /list)) + tg_open_uis[src_object_key][ui.ui_key] = list() // Make a list for the ui_key. + + // Append the UI to all the lists. + ui.user.tg_open_uis |= ui + var/list/uis = tg_open_uis[src_object_key][ui.ui_key] + uis |= ui + processing_uis |= ui + + /** + * private + * + * Remove a UI from the list of open UIs. + * + * required ui datum/tgui The UI to be removed. + * + * return bool If the UI was removed or not. + **/ +/datum/controller/process/tgui/proc/on_close(datum/tgui/ui) + var/src_object_key = "\ref[ui.src_object]" + if(isnull(tg_open_uis[src_object_key]) || !istype(tg_open_uis[src_object_key], /list)) + return 0 // It wasn't open. + else if(isnull(tg_open_uis[src_object_key][ui.ui_key]) || !istype(tg_open_uis[src_object_key][ui.ui_key], /list)) + return 0 // It wasn't open. + + processing_uis.Remove(ui) // Remove it from the list of processing UIs. + if(ui.user) // If the user exists, remove it from them too. + ui.user.tg_open_uis.Remove(ui) + var/list/uis = tg_open_uis[src_object_key][ui.ui_key] // Remove it from the list of open UIs. + uis.Remove(ui) + return 1 // Let the caller know we did it. + + /** + * private + * + * Handle client logout, by closing all their UIs. + * + * required user mob The mob which logged out. + * + * return int The number of UIs closed. + **/ +/datum/controller/process/tgui/proc/on_logout(mob/user) + return close_user_uis(user) + + /** + * private + * + * Handle clients switching mobs, by transfering their UIs. + * + * required user source The client's original mob. + * required user target The client's new mob. + * + * return bool If the UIs were transferred. + **/ +/datum/controller/process/tgui/proc/on_transfer(mob/source, mob/target) + if(!source || isnull(source.tg_open_uis) || !istype(source.tg_open_uis, /list) || tg_open_uis.len == 0) + return 0 // The old mob had no open UIs. + + if(isnull(target.tg_open_uis) || !istype(target.tg_open_uis, /list)) + target.tg_open_uis = list() // Create a list for the new mob if needed. + + for(var/datum/tgui/ui in source.tg_open_uis) + ui.user = target // Inform the UIs of their new owner. + target.tg_open_uis.Add(ui) // Transfer all the UIs. + + source.tg_open_uis.Cut() // Clear the old list. + return 1 // Let the caller know we did it. diff --git a/code/modules/tgui/states.dm b/code/modules/tgui/states.dm new file mode 100644 index 0000000000..186ca954a4 --- /dev/null +++ b/code/modules/tgui/states.dm @@ -0,0 +1,110 @@ + /** + * tgui states + * + * Base state and helpers for states. Just does some sanity checks, implement a state for in-depth checks. + **/ + + /** + * public + * + * Checks the UI state for a mob. + * + * required user mob The mob who opened/is using the UI. + * required state datum/ui_state The state to check. + * + * return UI_state The state of the UI. + **/ +/datum/proc/ui_status(mob/user, datum/ui_state/state) + var/datum/src_object = ui_host() + if(src_object != src) + return src_object.ui_status(user, state) + + if(isobserver(user)) // Special-case ghosts. + if(user.can_admin_interact()) + return UI_INTERACTIVE // If they turn it on, admins can interact. + if(get_dist(src_object, src) < user.client.view) + return UI_UPDATE // Regular ghosts can only view. + return UI_CLOSE // To keep too many UIs from being opened. + return state.can_use_topic(src_object, user) // Check if the state allows interaction. + + /** + * private + * + * Checks if a user can use src_object's UI, and returns the state. + * Can call a mob proc, which allows overrides for each mob. + * + * required src_object datum The object/datum which owns the UI. + * required user mob The mob who opened/is using the UI. + * + * return UI_state The state of the UI. + **/ +/datum/ui_state/proc/can_use_topic(src_object, mob/user) + return UI_CLOSE // Don't allow interaction by default. + + /** + * public + * + * Standard interaction/sanity checks. Different mob types may have overrides. + * + * return UI_state The state of the UI. + **/ +/mob/proc/shared_ui_interaction(src_object) + if(!client) // Close UIs if mindless. + return UI_CLOSE + else if(stat) // Disable UIs if unconcious. + return UI_DISABLED + else if(incapacitated() || lying) // Update UIs if incapicitated but concious. + return UI_UPDATE + return UI_INTERACTIVE + +/mob/living/silicon/ai/shared_ui_interaction(src_object) + if(lacks_power()) // Disable UIs if the AI is unpowered. + return UI_DISABLED + return ..() + +/mob/living/silicon/robot/shared_ui_interaction(src_object) + if(cell.charge <= 0 || lockcharge) // Disable UIs if the Borg is unpowered or locked. + return UI_DISABLED + return ..() + +/** + * public + * + * Check the distance for a living mob. + * Really only used for checks outside the context of a mob. + * Otherwise, use shared_living_ui_distance(). + * + * required src_object The object which owns the UI. + * required user mob The mob who opened/is using the UI. + * + * return UI_state The state of the UI. + **/ +/atom/proc/contents_ui_distance(src_object, mob/living/user) + return user.shared_living_ui_distance(src_object) // Just call this mob's check. + + /** + * public + * + * Distance versus interaction check. + * + * required src_object atom/movable The object which owns the UI. + * + * return UI_state The state of the UI. + **/ +/mob/living/proc/shared_living_ui_distance(atom/movable/src_object) + if(!(src_object in view(src))) // If the object is obscured, close it. + return UI_CLOSE + + var/dist = get_dist(src_object, src) + if(dist <= 1) // Open and interact if 1-0 tiles away. + return UI_INTERACTIVE + else if(dist <= 2) // View only if 2-3 tiles away. + return UI_UPDATE + else if(dist <= 5) // Disable if 5 tiles away. + return UI_DISABLED + return UI_CLOSE // Otherwise, we got nothing. + +/mob/living/carbon/human/shared_living_ui_distance(atom/movable/src_object) + if((TK in mutations)) + return UI_INTERACTIVE + return ..() diff --git a/code/modules/tgui/states/admin.dm b/code/modules/tgui/states/admin.dm new file mode 100644 index 0000000000..f68c6cb48e --- /dev/null +++ b/code/modules/tgui/states/admin.dm @@ -0,0 +1,12 @@ + /** + * tgui state: admin_state + * + * Checks that the user is an admin, end-of-story. + **/ + +/var/global/datum/ui_state/admin_state/tg_admin_state = new() + +/datum/ui_state/admin_state/can_use_topic(src_object, mob/user) + if(check_rights(R_ADMIN, 0, user)) + return UI_INTERACTIVE + return UI_CLOSE diff --git a/code/modules/tgui/states/conscious.dm b/code/modules/tgui/states/conscious.dm new file mode 100644 index 0000000000..39da5471ea --- /dev/null +++ b/code/modules/tgui/states/conscious.dm @@ -0,0 +1,12 @@ + /** + * tgui state: conscious_state + * + * Only checks if the user is conscious. + **/ + +/var/global/datum/ui_state/conscious_state/tg_conscious_state = new() + +/datum/ui_state/conscious_state/can_use_topic(src_object, mob/user) + if(user.stat == CONSCIOUS) + return UI_INTERACTIVE + return UI_CLOSE diff --git a/code/modules/tgui/states/contained.dm b/code/modules/tgui/states/contained.dm new file mode 100644 index 0000000000..a8f8ef7ff3 --- /dev/null +++ b/code/modules/tgui/states/contained.dm @@ -0,0 +1,12 @@ + /** + * tgui state: contained_state + * + * Checks that the user is inside the src_object. + **/ + +/var/global/datum/ui_state/contained_state/tg_contained_state = new() + +/datum/ui_state/contained_state/can_use_topic(atom/src_object, mob/user) + if(!src_object.contains(user)) + return UI_CLOSE + return user.shared_ui_interaction(src_object) diff --git a/code/modules/tgui/states/deep_inventory.dm b/code/modules/tgui/states/deep_inventory.dm new file mode 100644 index 0000000000..cfd74c1826 --- /dev/null +++ b/code/modules/tgui/states/deep_inventory.dm @@ -0,0 +1,12 @@ + /** + * tgui state: deep_inventory_state + * + * Checks that the src_object is in the user's deep (backpack, box, toolbox, etc) inventory. + **/ + +/var/global/datum/ui_state/deep_inventory_state/tg_deep_inventory_state = new() + +/datum/ui_state/deep_inventory_state/can_use_topic(src_object, mob/user) + if(!user.contains(src_object)) + return UI_CLOSE + return user.shared_ui_interaction(src_object) diff --git a/code/modules/tgui/states/default.dm b/code/modules/tgui/states/default.dm new file mode 100644 index 0000000000..8f13176f2d --- /dev/null +++ b/code/modules/tgui/states/default.dm @@ -0,0 +1,55 @@ + /** + * tgui state: default_state + * + * Checks a number of things -- mostly physical distance for humans and view for robots. + **/ + +/var/global/datum/ui_state/default/tg_default_state = new() + +/datum/ui_state/default/can_use_topic(src_object, mob/user) + return user.tg_default_can_use_topic(src_object) // Call the individual mob-overriden procs. + +/mob/proc/tg_default_can_use_topic(src_object) + return UI_CLOSE // Don't allow interaction by default. + +/mob/living/tg_default_can_use_topic(src_object) + . = shared_ui_interaction(src_object) + if(. > UI_CLOSE && loc) + . = min(., loc.contents_ui_distance(src_object, src)) // Check the distance... + if(. == UI_INTERACTIVE) // Non-human living mobs can only look, not touch. + return UI_UPDATE + +/mob/living/carbon/human/tg_default_can_use_topic(src_object) + . = shared_ui_interaction(src_object) + if(. > UI_CLOSE) + . = min(., shared_living_ui_distance(src_object)) // Check the distance... + // Derp a bit if we have brain loss. + if(prob(getBrainLoss())) + return UI_UPDATE + +/mob/living/silicon/robot/tg_default_can_use_topic(src_object) + . = shared_ui_interaction(src_object) + if(. <= UI_DISABLED) + return + + // Robots can interact with anything they can see. + if(get_dist(src, src_object) <= client.view) + return UI_INTERACTIVE + return UI_DISABLED // Otherwise they can keep the UI open. + +/mob/living/silicon/ai/tg_default_can_use_topic(src_object) + . = shared_ui_interaction(src_object) + if(. < UI_INTERACTIVE) + return + + // The AI can interact with anything it can see nearby, or with cameras. + if((get_dist(src, src_object) <= client.view) || cameranet.checkTurfVis(get_turf_pixel(src_object))) + return UI_INTERACTIVE + return UI_CLOSE + +/mob/living/silicon/pai/tg_default_can_use_topic(src_object) + // pAIs can only use themselves and the owner's radio. + if((src_object == src || src_object == radio) && !stat) + return UI_INTERACTIVE + else + return ..() diff --git a/code/modules/tgui/states/hands.dm b/code/modules/tgui/states/hands.dm new file mode 100644 index 0000000000..b2af57bb44 --- /dev/null +++ b/code/modules/tgui/states/hands.dm @@ -0,0 +1,20 @@ + /** + * tgui state: hands_state + * + * Checks that the src_object is in the user's hands. + **/ + +/var/global/datum/ui_state/hands_state/tg_hands_state = new() + +/datum/ui_state/hands_state/can_use_topic(src_object, mob/user) + . = user.shared_ui_interaction(src_object) + if(. > UI_CLOSE) + return min(., user.hands_can_use_topic(src_object)) + +/mob/proc/hands_can_use_topic(src_object) + return UI_CLOSE + +/mob/living/hands_can_use_topic(src_object) + if(src_object in get_both_hands(src)) + return UI_INTERACTIVE + return UI_CLOSE \ No newline at end of file diff --git a/code/modules/tgui/states/inventory.dm b/code/modules/tgui/states/inventory.dm new file mode 100644 index 0000000000..101fd478de --- /dev/null +++ b/code/modules/tgui/states/inventory.dm @@ -0,0 +1,12 @@ + /** + * tgui state: inventory_state + * + * Checks that the src_object is in the user's top-level (hand, ear, pocket, belt, etc) inventory. + **/ + +/var/global/datum/ui_state/inventory_state/tg_inventory_state = new() + +/datum/ui_state/inventory_state/can_use_topic(src_object, mob/user) + if(!(src_object in user)) + return UI_CLOSE + return user.shared_ui_interaction(src_object) diff --git a/code/modules/tgui/states/notcontained.dm b/code/modules/tgui/states/notcontained.dm new file mode 100644 index 0000000000..13612b9ff6 --- /dev/null +++ b/code/modules/tgui/states/notcontained.dm @@ -0,0 +1,26 @@ + /** + * tgui state: notcontained_state + * + * Checks that the user is not inside src_object, and then makes the default checks. + **/ + +/var/global/datum/ui_state/notcontained_state/tg_notcontained_state = new() + +/datum/ui_state/notcontained_state/can_use_topic(atom/src_object, mob/user) + . = user.shared_ui_interaction(src_object) + if(. > UI_CLOSE) + return min(., user.notcontained_can_use_topic(src_object)) + +/mob/proc/notcontained_can_use_topic(src_object) + return UI_CLOSE + +/mob/living/notcontained_can_use_topic(atom/src_object) + if(src_object.contains(src)) + return UI_CLOSE // Close if we're inside it. + return tg_default_can_use_topic(src_object) + +/mob/living/silicon/notcontained_can_use_topic(src_object) + return tg_default_can_use_topic(src_object) // Silicons use default bevhavior. + +/mob/living/simple_animal/drone/notcontained_can_use_topic(src_object) + return tg_default_can_use_topic(src_object) // Drones use default bevhavior. diff --git a/code/modules/tgui/states/physical.dm b/code/modules/tgui/states/physical.dm new file mode 100644 index 0000000000..f0c5d163a0 --- /dev/null +++ b/code/modules/tgui/states/physical.dm @@ -0,0 +1,24 @@ + /** + * tgui state: physical_state + * + * Short-circuits the default state to only check physical distance. + **/ + +/var/global/datum/ui_state/physical/tg_physical_state = new() + +/datum/ui_state/physical/can_use_topic(src_object, mob/user) + . = user.shared_ui_interaction(src_object) + if(. > UI_CLOSE) + return min(., user.physical_can_use_topic(src_object)) + +/mob/proc/physical_can_use_topic(src_object) + return UI_CLOSE + +/mob/living/physical_can_use_topic(src_object) + return shared_living_ui_distance(src_object) + +/mob/living/silicon/physical_can_use_topic(src_object) + return max(UI_UPDATE, shared_living_ui_distance(src_object)) // Silicons can always see. + +/mob/living/silicon/ai/physical_can_use_topic(src_object) + return UI_UPDATE // AIs are not physical. diff --git a/code/modules/tgui/states/self.dm b/code/modules/tgui/states/self.dm new file mode 100644 index 0000000000..30068291e5 --- /dev/null +++ b/code/modules/tgui/states/self.dm @@ -0,0 +1,12 @@ + /** + * tgui state: self_state + * + * Only checks that the user and src_object are the same. + **/ + +/var/global/datum/ui_state/self_state/tg_self_state = new() + +/datum/ui_state/self_state/can_use_topic(src_object, mob/user) + if(src_object != user) + return UI_CLOSE + return user.shared_ui_interaction(src_object) diff --git a/code/modules/tgui/states/zlevel.dm b/code/modules/tgui/states/zlevel.dm new file mode 100644 index 0000000000..c1f5578bf6 --- /dev/null +++ b/code/modules/tgui/states/zlevel.dm @@ -0,0 +1,14 @@ + /** + * tgui state: z_state + * + * Only checks that the Z-level of the user and src_object are the same. + **/ + +/var/global/datum/ui_state/z_state/tg_z_state = new() + +/datum/ui_state/z_state/can_use_topic(src_object, mob/user) + var/turf/turf_obj = get_turf(src_object) + var/turf/turf_usr = get_turf(user) + if(turf_obj && turf_usr && turf_obj.z == turf_usr.z) + return UI_INTERACTIVE + return UI_CLOSE diff --git a/code/modules/tgui/tgui.dm b/code/modules/tgui/tgui.dm new file mode 100644 index 0000000000..3c3046b0a4 --- /dev/null +++ b/code/modules/tgui/tgui.dm @@ -0,0 +1,369 @@ + /** + * tgui + * + * /tg/station user interface library + **/ + + /** + * tgui datum (represents a UI). + **/ +/datum/tgui + var/mob/user // The mob who opened/is using the UI. + var/datum/src_object // The object which owns the UI. + var/title // The title of te UI. + var/ui_key // The ui_key of the UI. This allows multiple UIs for one src_object. + var/window_id // The window_id for browse() and onclose(). + var/width = 0 // The window width. + var/height = 0 // The window height + var/window_options = list( // Extra options to winset(). + "focus" = FALSE, + "titlebar" = TRUE, + "can_resize" = TRUE, + "can_minimize" = TRUE, + "can_maximize" = FALSE, + "can_close" = TRUE, + "auto_format" = FALSE + ) + var/style = "nanotrasen" // The style to be used for this UI. + var/interface // The interface (template) to be used for this UI. + var/autoupdate = TRUE // Update the UI every MC tick. + var/initialized = FALSE // If the UI has been initialized yet. + var/list/initial_data // The data (and datastructure) used to initialize the UI. + var/status = UI_INTERACTIVE // The status/visibility of the UI. + var/datum/ui_state/state = null // Topic state used to determine status/interactability. + var/datum/tgui/master_ui // The parent UI. + var/list/datum/tgui/children = list() // Children of this UI. + + /** + * public + * + * Create a new UI. + * + * required user mob The mob who opened/is using the UI. + * required src_object datum The object or datum which owns the UI. + * required ui_key string The ui_key of the UI. + * required interface string The interface used to render the UI. + * optional title string The title of the UI. + * optional width int The window width. + * optional height int The window height. + * optional master_ui datum/tgui The parent UI. + * optional state datum/ui_state The state used to determine status. + * + * return datum/tgui The requested UI. + **/ +/datum/tgui/New(mob/user, datum/src_object, ui_key, interface, title, width = 0, height = 0, datum/tgui/master_ui = null, datum/ui_state/state = tg_default_state) + src.user = user + src.src_object = src_object + src.ui_key = ui_key + src.window_id = "\ref[src_object]-[ui_key]" + + set_interface(interface) + + if(title) + src.title = sanitize(title) + if(width) + src.width = width + if(height) + src.height = height + + src.master_ui = master_ui + if(master_ui) + master_ui.children += src + src.state = state + + var/datum/asset/assets = get_asset_datum(/datum/asset/simple/tgui) + assets.send(user) + + /** + * public + * + * Open this UI (and initialize it with data). + **/ +/datum/tgui/proc/open() + if(!user.client) + return // Bail if there is no client. + + update_status(push = 0) // Update the window status. + if(status < UI_UPDATE) + return // Bail if we're not supposed to open. + + if(!initial_data) + set_initial_data(src_object.ui_data(user)) // Get the UI data. + + var/window_size = "" + if(width && height) // If we have a width and height, use them. + window_size = "size=[width]x[height];" + + var/debugable = check_rights(R_DEBUG, 0, user) + user << browse(get_html(debugable), "window=[window_id];[window_size][list2params(window_options)]") // Open the window. + winset(user, window_id, "on-close=\"uiclose \ref[src]\"") // Instruct the client to signal UI when the window is closed. + tgui_process.on_open(src) + + /** + * public + * + * Reinitialize the UI. + * (Possibly with a new interface and/or data). + * + * optional template string The name of the new interface. + * optional data list The new initial data. + **/ +/datum/tgui/proc/reinitialize(interface, list/data) + if(interface) + set_interface(interface) // Set a new interface. + if(data) + set_initial_data(data) // Replace the initial_data. + open() + + /** + * public + * + * Close the UI, and all its children. + **/ +/datum/tgui/proc/close() + user << browse(null, "window=[window_id]") // Close the window. + tgui_process.on_close(src) + for(var/datum/tgui/child in children) // Loop through and close all children. + child.close() + children.Cut() + state = null + master_ui = null + qdel(src) + + /** + * public + * + * Sets the browse() window options for this UI. + * + * required window_options list The window options to set. + **/ +/datum/tgui/proc/set_window_options(list/window_options) + src.window_options = window_options + + /** + * public + * + * Set the style for this UI. + * + * required style string The new UI style. + **/ +/datum/tgui/proc/set_style(style) + src.style = lowertext(style) + + /** + * public + * + * Set the interface (template) for this UI. + * + * required interface string The new UI interface. + **/ +/datum/tgui/proc/set_interface(interface) + src.interface = lowertext(interface) + + /** + * public + * + * Enable/disable auto-updating of the UI. + * + * required state bool Enable/disable auto-updating. + **/ +/datum/tgui/proc/set_autoupdate(state = 1) + autoupdate = state + + /** + * private + * + * Set the data to initialize the UI with. + * The datastructure cannot be changed by subsequent updates. + * + * optional data list The data/datastructure to initialize the UI with. + **/ +/datum/tgui/proc/set_initial_data(list/data) + initial_data = data + + /** + * private + * + * Generate HTML for this UI. + * + * optional bool inline If the JSON should be inlined into the HTML (for debugging). + * + * return string UI HTML output. + **/ +/datum/tgui/proc/get_html(var/inline) + var/html + // Poplate HTML with JSON if we're supposed to inline. + if(inline) + html = replacetextEx(tgui_process.basehtml, "{}", get_json(initial_data)) + else + html = tgui_process.basehtml + html = replacetextEx(html, "\[ref]", "\ref[src]") + html = replacetextEx(html, "\[style]", style) + return html + + /** + * private + * + * Get the config data/datastructure to initialize the UI with. + * + * return list The config data. + **/ +/datum/tgui/proc/get_config_data() + var/list/config_data = list( + "title" = title, + "status" = status, + "screen" = src_object.ui_screen, + "style" = style, + "interface" = interface, + "fancy" = user.is_preference_enabled(/datum/client_preference/tgui_style), + "locked" = user.is_preference_enabled(/datum/client_preference/tgui_monitor), + "window" = window_id, + "ref" = "\ref[src]", + "user" = list( + "name" = user.name, + "ref" = "\ref[user]" + ), + "srcObject" = list( + "name" = "[src_object]", + "ref" = "\ref[src_object]" + ) + ) + return config_data + + /** + * private + * + * Package the data to send to the UI, as JSON. + * This includes the UI data and config_data. + * + * return string The packaged JSON. + **/ +/datum/tgui/proc/get_json(list/data) + var/list/json_data = list() + + json_data["config"] = get_config_data() + if(!isnull(data)) + json_data["data"] = data + + // Generate the JSON. + var/json = json_encode(json_data) + // Strip #255/improper. + json = replacetext(json, "\proper", "") + json = replacetext(json, "\improper", "") + return json + + /** + * private + * + * Handle clicks from the UI. + * Call the src_object's ui_act() if status is UI_INTERACTIVE. + * If the src_object's ui_act() returns 1, update all UIs attacked to it. + **/ +/datum/tgui/Topic(href, href_list) + if(user != usr) + return // Something is not right here. + + var/action = href_list["action"] + var/params = href_list; params -= "action" + + switch(action) + if("tgui:initialize") + user << output(url_encode(get_json(initial_data)), "[window_id].browser:initialize") + initialized = TRUE + if("tgui:view") + if(params["screen"]) + src_object.ui_screen = params["screen"] + tgui_process.update_uis(src_object) + if("tgui:link") + user << link(params["url"]) + if("tgui:fancy") + user.set_preference(/datum/client_preference/tgui_style, TRUE) + if("tgui:nofrills") + user.set_preference(/datum/client_preference/tgui_style, FALSE) + else + update_status(push = 0) // Update the window state. + if(src_object.ui_act(action, params, src, state)) // Call ui_act() on the src_object. + tgui_process.update_uis(src_object) // Update if the object requested it. + + /** + * private + * + * Update the UI. + * Only updates the data if update is true, otherwise only updates the status. + * + * optional force bool If the UI should be forced to update. + **/ +/datum/tgui/proc/process(force = 0) + var/datum/host = src_object.ui_host() + if(!src_object || !host || !user) // If the object or user died (or something else), abort. + close() + return + + if(status && (force || autoupdate)) + update() // Update the UI if the status and update settings allow it. + else + update_status(push = 1) // Otherwise only update status. + + /** + * private + * + * Push data to an already open UI. + * + * required data list The data to send. + * optional force bool If the update should be sent regardless of state. + **/ +/datum/tgui/proc/push_data(data, force = 0) + update_status(push = 0) // Update the window state. + if(!initialized) + return // Cannot update UI if it is not set up yet. + if(status <= UI_DISABLED && !force) + return // Cannot update UI, we have no visibility. + + // Send the new JSON to the update() Javascript function. + user << output(url_encode(get_json(data)), "[window_id].browser:update") + + /** + * private + * + * Updates the UI by interacting with the src_object again, which will hopefully + * call try_ui_update on it. + * + * optional force_open bool If force_open should be passed to ui_interact. + **/ +/datum/tgui/proc/update(force_open = 0) + src_object.tg_ui_interact(user, ui_key, src, force_open, master_ui, state) + + /** + * private + * + * Update the status/visibility of the UI for its user. + * + * optional push bool Push an update to the UI (an update is always sent for UI_DISABLED). + **/ +/datum/tgui/proc/update_status(push = 0) + var/status = src_object.ui_status(user, state) + if(master_ui) + status = min(status, master_ui.status) + + set_status(status, push) + if(status == UI_CLOSE) + close() + + /** + * private + * + * Set the status/visibility of the UI. + * + * required status int The status to set (UI_CLOSE/UI_DISABLED/UI_UPDATE/UI_INTERACTIVE). + * optional push bool Push an update to the UI (an update is always sent for UI_DISABLED). + **/ +/datum/tgui/proc/set_status(status, push = 0) + if(src.status != status) // Only update if status has changed. + if(src.status == UI_DISABLED) + src.status = status + if(push) + update() + else + src.status = status + if(status == UI_DISABLED || push) // Update if the UI just because disabled, or a push is requested. + push_data(null, force = 1) diff --git a/nano/assets/nanoui.css b/nano/assets/nanoui.css index 929902ead6..9c46a7e592 100644 --- a/nano/assets/nanoui.css +++ b/nano/assets/nanoui.css @@ -3504,6 +3504,120 @@ div.resize { background-position: -112px -128px; } +/*pill and bottle icons used in chem master*/ +.pillIcon32 +{ + float: left; + + width: 32px; + height: 32px; +} +.pillIcon +{ + float: left; + + width: 32px; + height: 32px; + margin: 2px 2px 0 2px; + + background-image: url(pills32.png); +} +.pillIcon.pill1 +{ + background-position: 0 0; +} +.pillIcon.pill2 +{ + background-position: -32px 0; +} +.pillIcon.pill3 +{ + background-position: -64px 0; +} +.pillIcon.pill4 +{ + background-position: -96px 0; +} +.pillIcon.pill5 +{ + background-position: -128px 0; +} +.pillIcon.pill6 +{ + background-position: 0 -32px; +} +.pillIcon.pill7 +{ + background-position: -32px -32px; +} +.pillIcon.pill8 +{ + background-position: -64px -32px; +} +.pillIcon.pill9 +{ + background-position: -96px -32px; +} +.pillIcon.pill10 +{ + background-position: -128px -32px; +} +.pillIcon.pill11 +{ + background-position: 0 -64px; +} +.pillIcon.pill12 +{ + background-position: -32px -64px; +} +.pillIcon.pill13 +{ + background-position: -64px -64px; +} +.pillIcon.pill14 +{ + background-position: -96px -64px; +} +.pillIcon.pill15 +{ + background-position: -128px -64px; +} +.pillIcon.pill16 +{ + background-position: 0 -96px; +} +.pillIcon.pill17 +{ + background-position: -32px -96px; +} +.pillIcon.pill18 +{ + background-position: -64px -96px; +} +.pillIcon.pill19 +{ + background-position: -96px -96px; +} +.pillIcon.pill20 +{ + background-position: -128px -96px; +} +.pillIcon.bottle1 +{ + background-position: 0 -128px; +} +.pillIcon.bottle2 +{ + background-position: -32px -128px; +} +.pillIcon.bottle3 +{ + background-position: -64px -128px; +} +.pillIcon.bottle4 +{ + background-position: -96px -128px; +} ul { margin: 0; diff --git a/nano/images/nanomap_z1.png b/nano/images/nanomap_z1.png index e4a045bf82..474abc2206 100644 Binary files a/nano/images/nanomap_z1.png and b/nano/images/nanomap_z1.png differ diff --git a/nano/images/nanomap_z3.png b/nano/images/nanomap_z3.png new file mode 100644 index 0000000000..5d7db2a33c Binary files /dev/null and b/nano/images/nanomap_z3.png differ diff --git a/nano/images/nanomap_z4.png b/nano/images/nanomap_z4.png new file mode 100644 index 0000000000..59e310132d Binary files /dev/null and b/nano/images/nanomap_z4.png differ diff --git a/nano/images/nanomap_z5.png b/nano/images/nanomap_z5.png new file mode 100644 index 0000000000..3b709cca8c Binary files /dev/null and b/nano/images/nanomap_z5.png differ diff --git a/nano/styles/_icon.less b/nano/styles/_icon.less index 7de6544e0e..5cd401dc8d 100644 --- a/nano/styles/_icon.less +++ b/nano/styles/_icon.less @@ -59,4 +59,44 @@ /* Security Positions */ .mapIcon16.rank-warden { background-position: -112px -128px; } .mapIcon16.rank-detective { background-position: -112px -128px; } -.mapIcon16.rank-securityofficer { background-position: -112px -128px; } \ No newline at end of file +.mapIcon16.rank-securityofficer { background-position: -112px -128px; } + +/*pill and bottle icons used in chem master*/ +.pillIcon32 { + float: left; + width: 32px; + height: 32px; +} + +.pillIcon { + float: left; + width: 32px; + height: 32px; + margin: 2px 2px 0 2px; + background-image: url(pills32.png); +} + +.pillIcon.pill1 { background-position: 0 0; } +.pillIcon.pill2 { background-position: -32px 0; } +.pillIcon.pill3 { background-position: -64px 0; } +.pillIcon.pill4 { background-position: -96px 0; } +.pillIcon.pill5 { background-position: -128px 0; } +.pillIcon.pill6 { background-position: 0 -32px; } +.pillIcon.pill7 { background-position: -32px -32px; } +.pillIcon.pill8 { background-position: -64px -32px; } +.pillIcon.pill9 { background-position: -96px -32px; } +.pillIcon.pill10 { background-position: -128px -32px; } +.pillIcon.pill11 { background-position: 0 -64px; } +.pillIcon.pill12 { background-position: -32px -64px; } +.pillIcon.pill13 { background-position: -64px -64px; } +.pillIcon.pill14 { background-position: -96px -64px; } +.pillIcon.pill15 { background-position: -128px -64px; } +.pillIcon.pill16 { background-position: 0 -96px; } +.pillIcon.pill17 { background-position: -32px -96px; } +.pillIcon.pill18 { background-position: -64px -96px; } +.pillIcon.pill19 { background-position: -96px -96px; } +.pillIcon.pill20 { background-position: -128px -96px; } +.pillIcon.bottle1 { background-position: 0 -128px; } +.pillIcon.bottle2 { background-position: -32px -128px; } +.pillIcon.bottle3 { background-position: -64px -128px; } +.pillIcon.bottle4 { background-position: -96px -128px; } \ No newline at end of file diff --git a/nano/templates/chem_master.tmpl b/nano/templates/chem_master.tmpl index 1b8315154a..7fb093efb9 100644 --- a/nano/templates/chem_master.tmpl +++ b/nano/templates/chem_master.tmpl @@ -69,8 +69,8 @@ {{:helper.link('Create bottle (60 units max)', null, {'createbottle' : 1})}}
- {{:helper.link('', 'pill pill' + data.pillSprite, {'tab_select' : 'pill'}, null, 'link32')}} - {{:helper.link('', 'pill bottle' + data.bottleSprite, {'tab_select' : 'bottle'}, null, 'link32')}} + {{:helper.link("
", null, {'tab_select' : 'pill'}, null, 'link pillIcon32')}} + {{:helper.link("
", null, {'tab_select' : 'bottle'}, null, 'link pillIcon32')}}
{{/if}} {{/if}} @@ -104,13 +104,13 @@ {{else data.tab == 'pill'}} {{for data.pillSpritesAmount}} - {{:helper.link('', 'pill pill' + value, {'pill_sprite' : value}, null, data.pillSprite == value ? 'linkOn link32' : 'link32')}} + {{:helper.link("
", null, {'pill_sprite' : value}, null, data.pillSprite == value ? 'linkOn pillIcon32' : 'link pillIcon32')}} {{/for}}

{{:helper.link('Return', 'arrowreturn-1-w', {'tab_select' : 'home'})}}
{{else data.tab == 'bottle'}} {{for data.bottleSpritesAmount}} - {{:helper.link('', 'pill bottle' + value, {'bottle_sprite' : value}, null, data.bottleSprite == value ? 'linkOn link32' : 'link32')}} + {{:helper.link("
", null, {'bottle_sprite' : value}, null, data.bottleSprite == value ? 'linkOn pillIcon32' : 'link pillIcon32')}} {{/for}}

{{:helper.link('Return', 'arrowreturn-1-w', {'tab_select' : 'home'})}}
{{/if}} \ No newline at end of file diff --git a/nano/templates/crew_monitor.tmpl b/nano/templates/crew_monitor.tmpl index 5c790ee0a3..eaffe24431 100644 --- a/nano/templates/crew_monitor.tmpl +++ b/nano/templates/crew_monitor.tmpl @@ -2,7 +2,7 @@ Title: Crew Monitoring Console (Main content) Used In File(s): \code\game\machinery\computer\crew.dm --> - ",r.insertBefore(n.lastChild,r.firstChild)}function i(){var t=w.elements;return"string"==typeof t?t.split(" "):t}function o(t,e){var n=w.elements;"string"!=typeof n&&(n=n.join(" ")),"string"!=typeof t&&(t=t.join(" ")),w.elements=n+" "+t,l(e)}function a(t){var e=b[t[g]];return e||(e={},y++,t[g]=y,b[y]=e),e}function s(t,e,r){if(e||(e=n),h)return e.createElement(t);r||(r=a(e));var i;return i=r.cache[t]?r.cache[t].cloneNode():m.test(t)?(r.cache[t]=r.createElem(t)).cloneNode():r.createElem(t),!i.canHaveChildren||v.test(t)||i.tagUrn?i:r.frag.appendChild(i)}function u(t,e){if(t||(t=n),h)return t.createDocumentFragment();e=e||a(t);for(var r=e.frag.cloneNode(),o=0,s=i(),u=s.length;u>o;o++)r.createElement(s[o]);return r}function c(t,e){e.cache||(e.cache={},e.createElem=t.createElement,e.createFrag=t.createDocumentFragment,e.frag=e.createFrag()),t.createElement=function(n){return w.shivMethods?s(n,t,e):e.createElem(n)},t.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+i().join().replace(/[\w\-:]+/g,function(t){return e.createElem(t),e.frag.createElement(t),'c("'+t+'")'})+");return n}")(w,e.frag)}function l(t){t||(t=n);var e=a(t);return!w.shivCSS||f||e.hasCSS||(e.hasCSS=!!r(t,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),h||c(t,e),t}var f,h,p="3.7.3-pre",d=t.html5||{},v=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,m=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,g="_html5shiv",y=0,b={};!function(){try{var t=n.createElement("a");t.innerHTML="",f="hidden"in t,h=1==t.childNodes.length||function(){n.createElement("a");var t=n.createDocumentFragment();return void 0===t.cloneNode||void 0===t.createDocumentFragment||void 0===t.createElement}()}catch(e){f=!0,h=!0}}();var w={elements:d.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:p,shivCSS:d.shivCSS!==!1,supportsUnknownElements:h,shivMethods:d.shivMethods!==!1,type:"default",shivDocument:l,createElement:s,createDocumentFragment:u,addElements:o};t.html5=w,l(n),"object"==typeof e&&e.exports&&(e.exports=w)}("undefined"!=typeof window?window:this,document)},{}],194:[function(t,e,n){(function(t){(function(t){!function(t){function e(t,e,n,r){for(var o,a=n.slice(),s=i(e,t),u=0,c=a.length;c>u&&(handler=a[u],"object"==typeof handler?"function"==typeof handler.handleEvent&&handler.handleEvent(s):handler.call(t,s),!s.stoppedImmediatePropagation);u++);return o=!s.stoppedPropagation,r&&o&&t.parentNode?t.parentNode.dispatchEvent(s):!s.defaultPrevented}function n(t,e){return{configurable:!0,get:t,set:e}}function r(t,e,r){var i=y(e||t,r);m(t,"textContent",n(function(){return i.get.call(this)},function(t){i.set.call(this,t)}))}function i(t,e){return t.currentTarget=e,t.eventPhase=t.target===t.currentTarget?2:3,t}function o(t,e){for(var n=t.length;n--&&t[n]!==e;);return n}function a(){if("BR"===this.tagName)return"\n";for(var t=this.firstChild,e=[];t;)8!==t.nodeType&&7!==t.nodeType&&e.push(t.textContent),t=t.nextSibling;return e.join("")}function s(t){var e=document.createEvent("Event");e.initEvent("input",!0,!0),(t.srcElement||t.fromElement||document).dispatchEvent(e)}function u(t){!h&&_.test(document.readyState)&&(h=!h,document.detachEvent(p,u),t=document.createEvent("Event"),t.initEvent(d,!0,!0),document.dispatchEvent(t))}function c(t){for(var e;e=this.lastChild;)this.removeChild(e);null!=t&&this.appendChild(document.createTextNode(t))}function l(e,n){return n||(n=t.event),n.target||(n.target=n.srcElement||n.fromElement||document),n.timeStamp||(n.timeStamp=(new Date).getTime()),n}if(!document.createEvent){var f=!0,h=!1,p="onreadystatechange",d="DOMContentLoaded",v="__IE8__"+Math.random(),m=Object.defineProperty||function(t,e,n){t[e]=n.value},g=Object.defineProperties||function(e,n){for(var r in n)if(b.call(n,r))try{m(e,r,n[r])}catch(i){t.console&&console.log(r+" failed on object:",e,i.message)}},y=Object.getOwnPropertyDescriptor,b=Object.prototype.hasOwnProperty,w=t.Element.prototype,x=t.Text.prototype,E=/^[a-z]+$/,_=/loaded|complete/,k={},S=document.createElement("div"),A=document.documentElement,O=A.removeAttribute,C=A.setAttribute;r(t.HTMLCommentElement.prototype,w,"nodeValue"),r(t.HTMLScriptElement.prototype,null,"text"),r(x,null,"nodeValue"),r(t.HTMLTitleElement.prototype,null,"text"),m(t.HTMLStyleElement.prototype,"textContent",function(t){return n(function(){return t.get.call(this.styleSheet)},function(e){t.set.call(this.styleSheet,e)})}(y(t.CSSStyleSheet.prototype,"cssText"))),g(w,{textContent:{get:a,set:c},firstElementChild:{get:function(){for(var t=this.childNodes||[],e=0,n=t.length;n>e;e++)if(1==t[e].nodeType)return t[e]}},lastElementChild:{get:function(){for(var t=this.childNodes||[],e=t.length;e--;)if(1==t[e].nodeType)return t[e]}},oninput:{get:function(){return this._oninput||null},set:function(t){this._oninput&&(this.removeEventListener("input",this._oninput),this._oninput=t,t&&this.addEventListener("input",t))}},previousElementSibling:{get:function(){for(var t=this.previousSibling;t&&1!=t.nodeType;)t=t.previousSibling;return t}},nextElementSibling:{get:function(){for(var t=this.nextSibling;t&&1!=t.nodeType;)t=t.nextSibling;return t}},childElementCount:{get:function(){for(var t=0,e=this.childNodes||[],n=e.length;n--;t+=1==e[n].nodeType);return t}},addEventListener:{value:function(t,n,r){if("function"==typeof n||"object"==typeof n){var i,a,u=this,c="on"+t,f=u[v]||m(u,v,{value:{}})[v],h=f[c]||(f[c]={}),p=h.h||(h.h=[]);if(!b.call(h,"w")){if(h.w=function(t){return t[v]||e(u,l(u,t),p,!1)},!b.call(k,c))if(E.test(t)){try{i=document.createEventObject(),i[v]=!0,9!=u.nodeType&&(null==u.parentNode&&S.appendChild(u),(a=u.getAttribute(c))&&O.call(u,c)),u.fireEvent(c,i),k[c]=!0}catch(i){for(k[c]=!1;S.hasChildNodes();)S.removeChild(S.firstChild)}null!=a&&C.call(u,c,a)}else k[c]=!1;(h.n=k[c])&&u.attachEvent(c,h.w)}o(p,n)<0&&p[r?"unshift":"push"](n),"input"===t&&u.attachEvent("onkeyup",s)}}},dispatchEvent:{value:function(t){var n,r=this,i="on"+t.type,o=r[v],a=o&&o[i],s=!!a;return t.target||(t.target=r),s?a.n?r.fireEvent(i,t):e(r,t,a.h,!0):(n=r.parentNode)?n.dispatchEvent(t):!0,!t.defaultPrevented}},removeEventListener:{value:function(t,e,n){if("function"==typeof e||"object"==typeof e){var r=this,i="on"+t,a=r[v],s=a&&a[i],u=s&&s.h,c=u?o(u,e):-1;c>-1&&u.splice(c,1)}}}}),g(x,{addEventListener:{value:w.addEventListener},dispatchEvent:{value:w.dispatchEvent},removeEventListener:{value:w.removeEventListener}}),g(t.XMLHttpRequest.prototype,{addEventListener:{value:function(t,e,n){var r=this,i="on"+t,a=r[v]||m(r,v,{value:{}})[v],s=a[i]||(a[i]={}),u=s.h||(s.h=[]);o(u,e)<0&&(r[i]||(r[i]=function(){var e=document.createEvent("Event");e.initEvent(t,!0,!0),r.dispatchEvent(e)}),u[n?"unshift":"push"](e))}},dispatchEvent:{value:function(t){var n=this,r="on"+t.type,i=n[v],o=i&&i[r],a=!!o;return a&&(o.n?n.fireEvent(r,t):e(n,t,o.h,!0))}},removeEventListener:{value:w.removeEventListener}}),g(t.Event.prototype,{bubbles:{value:!0,writable:!0},cancelable:{value:!0,writable:!0},preventDefault:{value:function(){this.cancelable&&(this.defaultPrevented=!0,this.returnValue=!1)}},stopPropagation:{value:function(){this.stoppedPropagation=!0,this.cancelBubble=!0}},stopImmediatePropagation:{value:function(){this.stoppedImmediatePropagation=!0,this.stopPropagation()}},initEvent:{value:function(t,e,n){this.type=t,this.bubbles=!!e,this.cancelable=!!n,this.bubbles||this.stopPropagation()}}}),g(t.HTMLDocument.prototype,{defaultView:{get:function(){return this.parentWindow}},textContent:{get:function(){return 11===this.nodeType?a.call(this):null},set:function(t){11===this.nodeType&&c.call(this,t)}},addEventListener:{value:function(e,n,r){var i=this;w.addEventListener.call(i,e,n,r),f&&e===d&&!_.test(i.readyState)&&(f=!1,i.attachEvent(p,u),t==top&&!function o(t){try{i.documentElement.doScroll("left"),u()}catch(e){setTimeout(o,50)}}())}},dispatchEvent:{value:w.dispatchEvent},removeEventListener:{value:w.removeEventListener},createEvent:{value:function(t){var e;if("Event"!==t)throw Error("unsupported "+t);return e=document.createEventObject(),e.timeStamp=(new Date).getTime(),e}}}),g(t.Window.prototype,{getComputedStyle:{value:function(){function t(t){this._=t}function e(){}var n=/^(?:[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|))(?!px)[a-z%]+$/,r=/^(top|right|bottom|left)$/,i=/\-([a-z])/g,o=function(t,e){return e.toUpperCase()};return t.prototype.getPropertyValue=function(t){var e,a,s,u=this._,c=u.style,l=u.currentStyle,f=u.runtimeStyle;return t=("float"===t?"style-float":t).replace(i,o),e=l?l[t]:c[t],n.test(e)&&!r.test(t)&&(a=c.left,s=f&&f.left,s&&(f.left=l.left),c.left="fontSize"===t?"1em":e,e=c.pixelLeft+"px",c.left=a,s&&(f.left=s)),null==e?e:e+""||"auto"},e.prototype.getPropertyValue=function(){return null},function(n,r){return r?new e(n):new t(n)}}()},addEventListener:{value:function(n,r,i){var a,s=t,u="on"+n;s[u]||(s[u]=function(t){return e(s,l(s,t),a,!1)}),a=s[u][v]||(s[u][v]=[]),o(a,r)<0&&a[i?"unshift":"push"](r)}},dispatchEvent:{value:function(e){var n=t["on"+e.type];return n?n.call(t,e)!==!1&&!e.defaultPrevented:!0}},removeEventListener:{value:function(e,n,r){var i="on"+e,a=(t[i]||Object)[v],s=a?o(a,n):-1;s>-1&&a.splice(s,1)}}}),function(t,e,n){for(n=0;n=s)return(0,u["default"])({points:n});for(var f=1;s-1>=f;f++)o.push((0,c.times)(r,(0,c.minus)(n[f],n[f-1])));for(var h=[(0,c.plus)(n[0],l(o[0],o[1]))],f=1;s-2>=f;f++)h.push((0,c.minus)(n[f],(0,c.average)([o[f],o[f-1]])));h.push((0,c.minus)(n[s-1],l(o[s-2],o[s-3])));var p=h[0],d=h[1],v=n[0],m=n[1],g=(e=(0,a["default"])()).moveto.apply(e,i(v)).curveto(p[0],p[1],d[0],d[1],m[0],m[1]);return{path:(0,c.range)(2,s).reduce(function(t,e){var r=h[e],i=n[e];return t.smoothcurveto(r[0],r[1],i[0],i[1])},g),centroid:(0,c.average)(n)}},e.exports=n["default"]},{198:198,199:199,200:200}],196:[function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(n,"__esModule",{value:!0});var i=function(){function t(t,e){var n=[],r=!0,i=!1,o=void 0;try{for(var a,s=t[Symbol.iterator]();!(r=(a=s.next()).done)&&(n.push(a.value),!e||n.length!==e);r=!0);}catch(u){i=!0,o=u}finally{try{!r&&s["return"]&&s["return"]()}finally{if(i)throw o}}return n}return function(e,n){if(Array.isArray(e))return e;if(Symbol.iterator in Object(e))return t(e,n);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}(),o=t(197),a=r(o),s=t(198),u=1e-5,c=function(t,e){var n=t.map(e),r=n.sort(function(t,e){var n=i(t,2),r=n[0],o=(n[1],i(e,2)),a=o[0];o[1];return r-a}),o=r.length,a=r[0][0],c=r[o-1][0],l=(0,s.minBy)(r,function(t){return t[1]}),f=(0,s.maxBy)(r,function(t){return t[1]});return a==c&&(c+=u),l==f&&(f+=u),{points:r,xmin:a,xmax:c,ymin:l,ymax:f}};n["default"]=function(t){var e=t.data,n=t.xaccessor,r=t.yaccessor,o=t.width,u=t.height,l=t.closed,f=t.min,h=t.max;n||(n=function(t){var e=i(t,2),n=e[0];e[1];return n}),r||(r=function(t){var e=i(t,2),n=(e[0],e[1]);return n});var p=function(t){return[n(t),r(t)]},d=e.map(function(t){return c(t,p)}),v=(0,s.minBy)(d,function(t){return t.xmin}),m=(0,s.maxBy)(d,function(t){return t.xmax}),g=null==f?(0,s.minBy)(d,function(t){return t.ymin}):f,y=null==h?(0,s.maxBy)(d,function(t){return t.ymax}):h;l&&(g=Math.min(g,0),y=Math.max(y,0));var b=l?0:g,w=(0,a["default"])([v,m],[0,o]),x=(0,a["default"])([g,y],[u,0]),E=function(t){var e=i(t,2),n=e[0],r=e[1];return[w(n),x(r)]};return{arranged:d,scale:E,xscale:w,yscale:x,base:b}},e.exports=n["default"]},{197:197,198:198}],197:[function(t,e,n){"use strict";Object.defineProperty(n,"__esModule",{value:!0});var r=function(){function t(t,e){var n=[],r=!0,i=!1,o=void 0;try{for(var a,s=t[Symbol.iterator]();!(r=(a=s.next()).done)&&(n.push(a.value),!e||n.length!==e);r=!0);}catch(u){i=!0,o=u}finally{try{!r&&s["return"]&&s["return"]()}finally{if(i)throw o}}return n}return function(e,n){if(Array.isArray(e))return e;if(Symbol.iterator in Object(e))return t(e,n);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}(),i=function o(t,e){var n=r(t,2),i=n[0],a=n[1],s=r(e,2),u=s[0],c=s[1],l=function(t){return u+(c-u)*(t-i)/(a-i)};return l.inverse=function(){return o([u,c],[i,a])},l};n["default"]=i,e.exports=n["default"]},{}],198:[function(t,e,n){"use strict";Object.defineProperty(n,"__esModule",{value:!0});var r=function(){function t(t,e){var n=[],r=!0,i=!1,o=void 0;try{for(var a,s=t[Symbol.iterator]();!(r=(a=s.next()).done)&&(n.push(a.value),!e||n.length!==e);r=!0);}catch(u){i=!0,o=u}finally{try{!r&&s["return"]&&s["return"]()}finally{if(i)throw o}}return n}return function(e,n){if(Array.isArray(e))return e;if(Symbol.iterator in Object(e))return t(e,n);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}(),i=function(t){return t.reduce(function(t,e){return t+e},0)},o=function(t){return t.reduce(function(t,e){return Math.min(t,e)})},a=function(t){return t.reduce(function(t,e){return Math.max(t,e)})},s=function(t,e){return t.reduce(function(t,n){return t+e(n)},0)},u=function(t,e){return t.reduce(function(t,n){return Math.min(t,e(n))},1/0)},c=function(t,e){return t.reduce(function(t,n){return Math.max(t,e(n))},-(1/0))},l=function(t,e){var n=r(t,2),i=n[0],o=n[1],a=r(e,2),s=a[0],u=a[1];return[i+s,o+u]},f=function(t,e){var n=r(t,2),i=n[0],o=n[1],a=r(e,2),s=a[0],u=a[1];return[i-s,o-u]},h=function(t,e){var n=r(e,2),i=n[0],o=n[1];return[t*i,t*o]},p=function(t){var e=r(t,2),n=e[0],i=e[1];return Math.sqrt(n*n+i*i)},d=function(t){return t.reduce(l,[0,0])},v=function(t){return h(1/t.length,t.reduce(l))},m=function(t,e){return h(t,[Math.sin(e),-Math.cos(e)])},g=function(t,e){var n=t||{};for(var r in n){var i=n[r];e[r]=i(e.index,e.item,e.group)}return e},y=function(t,e,n){for(var r=[],i=t;e>i;i++)r.push(i);return n&&r.push(e),r},b=function(t,e){var n=[],r=!0,i=!1,o=void 0;try{for(var a,s=Object.keys(t)[Symbol.iterator]();!(r=(a=s.next()).done);r=!0){var u=a.value,c=t[u];n.push(e(u,c))}}catch(l){i=!0,o=l}finally{try{!r&&s["return"]&&s["return"]()}finally{if(i)throw o}}return n},w=function(t){return b(t,function(t,e){return[t,e]})},x=function(t){return t};n.sum=i,n.min=o,n.max=a,n.sumBy=s,n.minBy=u,n.maxBy=c,n.plus=l,n.minus=f,n.times=h,n.id=x,n.length=p,n.sumVectors=d,n.average=v,n.onCircle=m,n.enhance=g,n.range=y,n.mapObject=b,n.pairs=w,n["default"]={sum:i,min:o,max:a,sumBy:s,minBy:u,maxBy:c,plus:l,minus:f,times:h,id:x,length:p,sumVectors:d,average:v,onCircle:m,enhance:g,range:y,mapObject:b,pairs:w}},{}],199:[function(t,e,n){"use strict";Object.defineProperty(n,"__esModule",{value:!0});var r=function(){function t(t,e){var n=[],r=!0,i=!1,o=void 0;try{for(var a,s=t[Symbol.iterator]();!(r=(a=s.next()).done)&&(n.push(a.value),!e||n.length!==e);r=!0);}catch(u){i=!0,o=u}finally{try{!r&&s["return"]&&s["return"]()}finally{if(i)throw o}}return n}return function(e,n){if(Array.isArray(e))return e;if(Symbol.iterator in Object(e))return t(e,n);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}(),i=function o(t){var e=t||[],n=function(t,e){var n=t.slice(0,t.length);return n.push(e),n},i=function(t,e){var n=r(t,2),i=n[0],o=n[1],a=r(e,2),s=a[0],u=a[1];return i===s&&o===u},a=function(t,e){for(var n=t.length;"0"===t.charAt(n-1);)n-=1;return"."===t.charAt(n-1)&&(n-=1),t.substr(0,n)},s=function(t,e){var n=t.toFixed(e);return a(n)},u=function(t){var e=t.command,n=t.params,r=n.map(function(t){return s(t,6)});return e+" "+r.join(" ")},c=function(t,e){var n=t.command,i=t.params,o=r(e,2),a=o[0],s=o[1];switch(n){case"M":return[i[0],i[1]];case"L":return[i[0],i[1]];case"H":return[i[0],s];case"V":return[a,i[0]];case"Z":return null;case"C":return[i[4],i[5]];case"S":return[i[2],i[3]];case"Q":return[i[2],i[3]];case"T":return[i[0],i[1]];case"A":return[i[5],i[6]]}},l=function(t,e){return function(n){var r="object"==typeof n?t.map(function(t){return n[t]}):arguments;return e.apply(null,r)}},f=function(t){return o(n(e,t))};return{moveto:l(["x","y"],function(t,e){return f({command:"M",params:[t,e]})}),lineto:l(["x","y"],function(t,e){return f({command:"L",params:[t,e]})}),hlineto:l(["x"],function(t){return f({command:"H",params:[t]})}),vlineto:l(["y"],function(t){return f({command:"V",params:[t]})}),closepath:function(){return f({command:"Z",params:[]})},curveto:l(["x1","y1","x2","y2","x","y"],function(t,e,n,r,i,o){return f({command:"C",params:[t,e,n,r,i,o]})}),smoothcurveto:l(["x2","y2","x","y"],function(t,e,n,r){return f({command:"S",params:[t,e,n,r]})}),qcurveto:l(["x1","y1","x","y"],function(t,e,n,r){return f({command:"Q",params:[t,e,n,r]})}),smoothqcurveto:l(["x","y"],function(t,e){return f({command:"T",params:[t,e]})}),arc:l(["rx","ry","xrot","largeArcFlag","sweepFlag","x","y"],function(t,e,n,r,i,o,a){return f({command:"A",params:[t,e,n,r,i,o,a]})}),print:function(){return e.map(u).join(" ")},points:function(){var t=[],n=[0,0],r=!0,i=!1,o=void 0;try{for(var a,s=e[Symbol.iterator]();!(r=(a=s.next()).done);r=!0){var u=a.value,l=c(u,n);n=l,l&&t.push(l)}}catch(f){i=!0,o=f}finally{try{!r&&s["return"]&&s["return"]()}finally{if(i)throw o}}return t},instructions:function(){return e.slice(0,e.length)},connect:function(t){var e=this.points(),n=e[e.length-1],r=t.points()[0],a=t.instructions().slice(1);return i(n,r)||a.unshift({command:"L",params:r}),o(this.instructions().concat(a))}}};n["default"]=function(){return i()},e.exports=n["default"]},{}],200:[function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){if(Array.isArray(t)){for(var e=0,n=Array(t.length);e1)for(var n=1;n1?e-1:0),r=1;e>r;r++)n[r-1]=arguments[r];for(var i,o;o=n.shift();)for(i in o)Fa.call(o,i)&&(t[i]=o[i]);return t}function i(t){for(var e=arguments.length,n=Array(e>1?e-1:0),r=1;e>r;r++)n[r-1]=arguments[r];return n.forEach(function(e){for(var n in e)!e.hasOwnProperty(n)||n in t||(t[n]=e[n])}),t}function o(t){return"[object Array]"===Ma.call(t)}function a(t){return Na.test(Ma.call(t))}function s(t,e){return null===t&&null===e?!0:"object"==typeof t||"object"==typeof e?!1:t===e}function u(t){return!isNaN(parseFloat(t))&&isFinite(t)}function c(t){return t&&"[object Object]"===Ma.call(t)}function l(t,e){return t.replace(/%s/g,function(){return e.shift()})}function f(t){for(var e=arguments.length,n=Array(e>1?e-1:0),r=1;e>r;r++)n[r-1]=arguments[r];throw t=l(t,n),Error(t)}function h(){Fm.DEBUG&&Pa.apply(null,arguments)}function p(t){for(var e=arguments.length,n=Array(e>1?e-1:0),r=1;e>r;r++)n[r-1]=arguments[r];t=l(t,n),ja(t,n)}function d(t){for(var e=arguments.length,n=Array(e>1?e-1:0),r=1;e>r;r++)n[r-1]=arguments[r];t=l(t,n),La[t]||(La[t]=!0,ja(t,n))}function v(){Fm.DEBUG&&p.apply(null,arguments)}function m(){Fm.DEBUG&&d.apply(null,arguments)}function g(t,e,n){var r=y(t,e,n);return r?r[t][n]:null}function y(t,e,n){for(;e;){if(n in e[t])return e;if(e.isolated)return null;e=e.parent}}function b(t){return function(){return t}}function w(t){var e,n,r,i,o,a;for(e=t.split("."),(n=Wa[e.length])||(n=x(e.length)),o=[],r=function(t,n){return t?"*":e[n]},i=n.length;i--;)a=n[i].map(r).join("."),o.hasOwnProperty(a)||(o.push(a),o[a]=!0);return o}function x(t){var e,n,r,i,o,a,s,u,c="";if(!Wa[t]){for(r=[];c.length=o;o+=1){for(n=o.toString(2);n.lengtha;a++)u.push(i(n[a]));r[o]=u}Wa[t]=r}return Wa[t]}function E(t,e,n,r){var i=t[e];if(!i||!i.equalsOrStartsWith(r)&&i.equalsOrStartsWith(n))return t[e]=i?i.replace(n,r):r,!0}function _(t){var e=t.slice(2);return"i"===t[1]&&u(e)?+e:e}function k(t){return null==t?t:(Ka.hasOwnProperty(t)||(Ka[t]=new $a(t)),Ka[t])}function S(t,e){function n(e,n){var r,i,a;return n.isRoot?a=[].concat(Object.keys(t.viewmodel.data),Object.keys(t.viewmodel.mappings),Object.keys(t.viewmodel.computations)):(r=t.viewmodel.wrapped[n.str],i=r?r.get():t.viewmodel.get(n),a=i?Object.keys(i):null),a&&a.forEach(function(t){"_ractive"===t&&o(i)||e.push(n.join(t))}),e}var r,i,a;for(r=e.str.split("."),a=[Ya];i=r.shift();)"*"===i?a=a.reduce(n,[]):a[0]===Ya?a[0]=k(i):a=a.map(A(i));return a}function A(t){return function(e){return e.join(t)}}function O(t){return t?t.replace(Ha,".$1"):""}function C(t,e,n){if("string"!=typeof e||!u(n))throw Error("Bad arguments");var r=void 0,i=void 0;if(/\*/.test(e))return i={},S(t,k(O(e))).forEach(function(e){var r=t.viewmodel.get(e);if(!u(r))throw Error(Xa);i[e.str]=r+n}),t.set(i);if(r=t.get(e),!u(r))throw Error(Xa);return t.set(e,+r+n)}function P(t,e){return Ja(this,t,void 0===e?1:+e)}function j(t){this.event=t,this.method="on"+t,this.deprecate=rs[t]}function T(t,e){var n=t.indexOf(e);-1===n&&t.push(e)}function F(t,e){for(var n=0,r=t.length;r>n;n++)if(t[n]==e)return!0;return!1}function M(t,e){var n;if(!o(t)||!o(e))return!1;if(t.length!==e.length)return!1;for(n=t.length;n--;)if(t[n]!==e[n])return!1;return!0}function N(t){return"string"==typeof t?[t]:void 0===t?[]:t}function L(t){return t[t.length-1]}function R(t,e){var n=t.indexOf(e);-1!==n&&t.splice(n,1)}function I(t){for(var e=[],n=t.length;n--;)e[n]=t[n];return e}function D(t){setTimeout(t,0); +}function V(t,e){return function(){for(var n;n=t.shift();)n(e)}}function U(t,e,n,r){var i;if(e===t)throw new TypeError("A promise's fulfillment handler cannot return the same promise");if(e instanceof is)e.then(n,r);else if(!e||"object"!=typeof e&&"function"!=typeof e)n(e);else{try{i=e.then}catch(o){return void r(o)}if("function"==typeof i){var a,s,u;s=function(e){a||(a=!0,U(t,e,n,r))},u=function(t){a||(a=!0,r(t))};try{i.call(e,s,u)}catch(o){if(!a)return r(o),void(a=!0)}}else n(e)}}function B(t,e,n){var r;return e=O(e),"~/"===e.substr(0,2)?(r=k(e.substring(2)),W(t,r.firstKey,n)):"."===e[0]?(r=q(ls(n),e),r&&W(t,r.firstKey,n)):r=z(t,k(e),n),r}function q(t,e){var n;if(void 0!=t&&"string"!=typeof t&&(t=t.str),"."===e)return k(t);if(n=t?t.split("."):[],"../"===e.substr(0,3)){for(;"../"===e.substr(0,3);){if(!n.length)throw Error('Could not resolve reference - too many "../" prefixes');n.pop(),e=e.substring(3)}return n.push(e),k(n.join("."))}return k(t?t+e.replace(/^\.\//,"."):e.replace(/^\.\/?/,""))}function z(t,e,n,r){var i,o,a,s,u;if(e.isRoot)return e;for(o=e.firstKey;n;)if(i=n.context,n=n.parent,i&&(s=!0,a=t.viewmodel.get(i),a&&("object"==typeof a||"function"==typeof a)&&o in a))return i.join(e.str);return H(t.viewmodel,o)?e:t.parent&&!t.isolated&&(s=!0,n=t.component.parentFragment,o=k(o),u=z(t.parent,o,n,!0))?(t.viewmodel.map(o,{origin:t.parent.viewmodel,keypath:u}),e):r||s?void 0:(t.viewmodel.set(e,void 0),e)}function W(t,e){var n;!t.parent||t.isolated||H(t.viewmodel,e)||(e=k(e),(n=z(t.parent,e,t.component.parentFragment,!0))&&t.viewmodel.map(e,{origin:t.parent.viewmodel,keypath:n}))}function H(t,e){return""===e||e in t.data||e in t.computations||e in t.mappings}function G(t){t.teardown()}function K(t){t.unbind()}function $(t){t.unrender()}function Q(t){t.cancel()}function Y(t){t.detach()}function J(t){t.detachNodes()}function X(t){!t.ready||t.outros.length||t.outroChildren||(t.outrosComplete||(t.parent?t.parent.decrementOutros(t):t.detachNodes(),t.outrosComplete=!0),t.intros.length||t.totalChildren||("function"==typeof t.callback&&t.callback(),t.parent&&t.parent.decrementTotal()))}function Z(){for(var t,e,n;ps.ractives.length;)e=ps.ractives.pop(),n=e.viewmodel.applyChanges(),n&&gs.fire(e,n);for(tt(),t=0;t=0;o--)i=t._subs[e[o]],i&&(s=gt(t,i,n,r)&&s);if(zs.dequeue(t),t.parent&&s){if(a&&t.component){var u=t.component.name+"."+e[e.length-1];e=k(u).wildcardMatches(),n&&(n.component=t)}mt(t.parent,e,n,r)}}function gt(t,e,n,r){var i=null,o=!1;n&&!n._noArg&&(r=[n].concat(r)),e=e.slice();for(var a=0,s=e.length;s>a;a+=1)e[a].apply(t,r)===!1&&(o=!0);return n&&!n._noArg&&o&&(i=n.original)&&(i.preventDefault&&i.preventDefault(),i.stopPropagation&&i.stopPropagation()),!o}function yt(t){var e={args:Array.prototype.slice.call(arguments,1)};Ws(this,t,e)}function bt(t){var e;return t=k(O(t)),e=this.viewmodel.get(t,Ks),void 0===e&&this.parent&&!this.isolated&&fs(this,t.str,this.component.parentFragment)&&(e=this.viewmodel.get(t)),e}function wt(e,n){if(!this.fragment.rendered)throw Error("The API has changed - you must call `ractive.render(target[, anchor])` to render your Ractive instance. Once rendered you can use `ractive.insert()`.");if(e=t(e),n=t(n)||null,!e)throw Error("You must specify a valid target to insert into");e.insertBefore(this.detach(),n),this.el=e,(e.__ractive_instances__||(e.__ractive_instances__=[])).push(this),this.detached=null,xt(this)}function xt(t){Qs.fire(t),t.findAllComponents("*").forEach(function(t){xt(t.instance)})}function Et(t,e,n){var r,i;return t=k(O(t)),r=this.viewmodel.get(t),o(r)&&o(e)?(i=ys.start(this,!0),this.viewmodel.merge(t,r,e,n),ys.end(),i):this.set(t,e,n&&n.complete)}function _t(t,e){var n,r;return n=S(t,e),r={},n.forEach(function(e){r[e.str]=t.get(e.str)}),r}function kt(t,e,n,r){var i,o,a;e=k(O(e)),r=r||lu,e.isPattern?(i=new uu(t,e,n,r),t.viewmodel.patternObservers.push(i),o=!0):i=new Zs(t,e,n,r),i.init(r.init),t.viewmodel.register(e,i,o?"patternObservers":"observers"),i.ready=!0;var s={cancel:function(){var n;a||(o?(n=t.viewmodel.patternObservers.indexOf(i),t.viewmodel.patternObservers.splice(n,1),t.viewmodel.unregister(e,i,"patternObservers")):t.viewmodel.unregister(e,i,"observers"),a=!0)}};return t._observers.push(s),s}function St(t,e,n){var r,i,o,a;if(c(t)){n=e,i=t,r=[];for(t in i)i.hasOwnProperty(t)&&(e=i[t],r.push(this.observe(t,e,n)));return{cancel:function(){for(;r.length;)r.pop().cancel()}}}if("function"==typeof t)return n=e,e=t,t="",cu(this,t,e,n);if(o=t.split(" "),1===o.length)return cu(this,t,e,n);for(r=[],a=o.length;a--;)t=o[a],t&&r.push(cu(this,t,e,n));return{cancel:function(){for(;r.length;)r.pop().cancel()}}}function At(t,e,n){var r=this.observe(t,function(){e.apply(this,arguments),r.cancel()},{init:!1,defer:n&&n.defer});return r}function Ot(t,e){var n,r=this;if(t)n=t.split(" ").map(pu).filter(du),n.forEach(function(t){var n,i;(n=r._subs[t])&&(e?(i=n.indexOf(e),-1!==i&&n.splice(i,1)):r._subs[t]=[])});else for(t in this._subs)delete this._subs[t];return this}function Ct(t,e){var n,r,i,o=this;if("object"==typeof t){n=[];for(r in t)t.hasOwnProperty(r)&&n.push(this.on(r,t[r]));return{cancel:function(){for(var t;t=n.pop();)t.cancel()}}}return i=t.split(" ").map(pu).filter(du),i.forEach(function(t){(o._subs[t]||(o._subs[t]=[])).push(e)}),{cancel:function(){return o.off(t,e)}}}function Pt(t,e){var n=this.on(t,function(){e.apply(this,arguments),n.cancel()});return n}function jt(t,e,n){var r,i,o,a,s,u,c=[];if(r=Tt(t,e,n),!r)return null;for(i=t.length,s=r.length-2-r[1],o=Math.min(i,r[0]),a=o+r[1],u=0;o>u;u+=1)c.push(u);for(;a>u;u+=1)c.push(-1);for(;i>u;u+=1)c.push(u+s);return 0!==s?c.touchedFrom=r[0]:c.touchedFrom=t.length,c}function Tt(t,e,n){switch(e){case"splice":for(void 0!==n[0]&&n[0]<0&&(n[0]=t.length+Math.max(n[0],-t.length));n.length<2;)n.push(0);return n[1]=Math.min(n[1],t.length-n[0]),n;case"sort":case"reverse":return null;case"pop":return t.length?[t.length-1,1]:[0,0];case"push":return[t.length,0].concat(n);case"shift":return[0,t.length?1:0];case"unshift":return[0,0].concat(n)}}function Ft(e,n){var r,i,o,a=this;if(o=this.transitionsEnabled,this.noIntro&&(this.transitionsEnabled=!1),r=ys.start(this,!0),ys.scheduleTask(function(){return Tu.fire(a)},!0),this.fragment.rendered)throw Error("You cannot call ractive.render() on an already rendered instance! Call ractive.unrender() first");if(e=t(e)||this.el,n=t(n)||this.anchor,this.el=e,this.anchor=n,!this.append&&e){var s=e.__ractive_instances__;s&&s.length&&Mt(s),e.innerHTML=""}return this.cssId&&Pu.apply(),e&&((i=e.__ractive_instances__)?i.push(this):e.__ractive_instances__=[this],n?e.insertBefore(this.fragment.render(),n):e.appendChild(this.fragment.render())),ys.end(),this.transitionsEnabled=o,r.then(function(){return Fu.fire(a)})}function Mt(t){t.splice(0,t.length).forEach(G)}function Nt(t,e){for(var n=t.slice(),r=e.length;r--;)~n.indexOf(e[r])||n.push(e[r]);return n}function Lt(t,e){var n,r,i;return r='[data-ractive-css~="{'+e+'}"]',i=function(t){var e,n,i,o,a,s,u,c=[];for(e=[];n=Du.exec(t);)e.push({str:n[0],base:n[1],modifiers:n[2]});for(o=e.map(It),u=e.length;u--;)s=o.slice(),i=e[u],s[u]=i.base+r+i.modifiers||"",a=o.slice(),a[u]=r+" "+a[u],c.push(s.join(" "),a.join(" "));return c.join(", ")},n=Uu.test(t)?t.replace(Uu,r):t.replace(Iu,"").replace(Ru,function(t,e){var n,r;return Vu.test(e)?t:(n=e.split(",").map(Rt),r=n.map(i).join(", ")+" ",t.replace(e,r))})}function Rt(t){return t.trim?t.trim():t.replace(/^\s+/,"").replace(/\s+$/,"")}function It(t){return t.str}function Dt(t){t&&t.constructor!==Object&&("function"==typeof t||("object"!=typeof t?f("data option must be an object or a function, `"+t+"` is not valid"):v("If supplied, options.data should be a plain JavaScript object - using a non-POJO as the root object may work, but is discouraged")))}function Vt(t,e){Dt(e);var n="function"==typeof t,r="function"==typeof e;return e||n||(e={}),n||r?function(){var i=r?Ut(e,this):e,o=n?Ut(t,this):t;return Bt(i,o)}:Bt(e,t)}function Ut(t,e){var n=t.call(e);if(n)return"object"!=typeof n&&f("Data function must return an object"),n.constructor!==Object&&m("Data function returned something other than a plain JavaScript object. This might work, but is strongly discouraged"),n}function Bt(t,e){if(t&&e){for(var n in e)n in t||(t[n]=e[n]);return t}return t||e}function qt(t){var e=ka($u);return e.parse=function(e,n){return zt(e,n||t)},e}function zt(t,e){if(!Gu)throw Error("Missing Ractive.parse - cannot parse template. Either preparse or use the version that includes the parser");return Gu(t,e||this.options)}function Wt(t,e){var n;if(!Zo){if(e&&e.noThrow)return;throw Error("Cannot retrieve template #"+t+" as Ractive is not running in a browser.")}if(Ht(t)&&(t=t.substring(1)),!(n=document.getElementById(t))){if(e&&e.noThrow)return;throw Error("Could not find template element with id #"+t)}if("SCRIPT"!==n.tagName.toUpperCase()){if(e&&e.noThrow)return;throw Error("Template element with id #"+t+", must be a + + + {{#if data.siliconUser}} + + {{data.locked ? "Engaged" : "Disengaged"}} + + {{else}} + Swipe an ID card to {{data.locked ? "unlock" : "lock"}} this interface. + {{/if}} + + + + {{#if data.locked && !data.siliconUser}} + {{data.isOperating ? "On" : "Off"}} + {{else}} + {{data.isOperating ? "On" : "Off"}} + {{/if}} + + + {{data.externalPower == 2 ? "Good" : data.externalPower == 1 ? "Low" : "None"}} + + + {{#if data.powerCellStatus != null}} + {{Math.fixed(adata.powerCellStatus)}}% + {{else}} + Removed + {{/if}} + + {{#if data.powerCellStatus != null}} + + {{#if data.locked && !data.siliconUser}} + {{data.chargeMode ? "Auto" : "Off"}} + {{else}} + {{data.chargeMode ? "Auto" : "Off"}} + {{/if}} +   + [{{data.chargingStatus == 2 ? "Fully Charged" : data.chargingStatus == 1 ? "Charging" : "Not Charging"}}] + + {{/if}} + + + {{#each data.powerChannels}} + +
{{Math.round(adata.powerChannels[@index].powerLoad)}} W
+
{{status >= 2 ? "On" : "Off"}}
+
[{{status == 1 || status == 3 ? "Auto" : "Manual"}}]
+
+ {{#if !data.locked || data.siliconUser}} + Auto + On + Off + {{/if}} +
+
+ {{/each}} + + {{Math.round(adata.totalLoad)}} W + +
+{{#if data.siliconUser}} + + Overload + {{#if data.malfStatus}} + {{malfButton}} + {{/if}} + +{{/if}} + + + {{#if data.locked && !data.siliconUser}} + {{data.coverLocked ? "Engaged" : "Disengaged"}} + {{else}} + {{data.coverLocked ? "Engaged" : "Disengaged"}} + {{/if}} + + diff --git a/tgui/src/Unimplemented/atmos_alert.ract b/tgui/src/Unimplemented/atmos_alert.ract new file mode 100644 index 0000000000..05bf9b797d --- /dev/null +++ b/tgui/src/Unimplemented/atmos_alert.ract @@ -0,0 +1,14 @@ + +
    + {{#each data.priority}} +
  • {{.}}
  • + {{else}} +
  • No Priority Alerts
  • + {{/each}} + {{#each data.minor}} +
  • {{.}}
  • + {{else}} +
  • No Minor Alerts
  • + {{/each}} +
+
diff --git a/tgui/src/Unimplemented/atmos_control.ract b/tgui/src/Unimplemented/atmos_control.ract new file mode 100644 index 0000000000..36035285c8 --- /dev/null +++ b/tgui/src/Unimplemented/atmos_control.ract @@ -0,0 +1,40 @@ + + {{#each adata.sensors}} + + + {{Math.fixed(pressure, 2)}} kPa + + {{#if temperature}} + + {{Math.fixed(temperature, 2)}} K + + {{/if}} + {{#each gases:id}} + + {{Math.fixed(., 2)}}% + + {{/each}} + + {{/each}} + +{{#if data.tank}} + + {{#partial button}} + Reconnect + {{/partial}} + + + {{data.inputting ? "Injecting": "Off"}} + + + {{Math.fixed(adata.inputRate)}} L/s + + + + {{data.outputting ? "Open": "Closed"}} + + + {{Math.round(adata.outputPressure)}} kPa + + +{{/if}} diff --git a/tgui/src/Unimplemented/atmos_filter.ract b/tgui/src/Unimplemented/atmos_filter.ract new file mode 100644 index 0000000000..59a00efc3c --- /dev/null +++ b/tgui/src/Unimplemented/atmos_filter.ract @@ -0,0 +1,25 @@ + + + {{data.on ? "On" : "Off"}} + + + Set + Max + {{Math.round(adata.pressure)}} kPa + + + Nothing + Plasma + O2 + N2 + CO2 + N2O + + diff --git a/tgui/src/Unimplemented/atmos_mixer.ract b/tgui/src/Unimplemented/atmos_mixer.ract new file mode 100644 index 0000000000..4f9285dae6 --- /dev/null +++ b/tgui/src/Unimplemented/atmos_mixer.ract @@ -0,0 +1,33 @@ + + + {{data.on ? "On" : "Off"}} + + + Set + Max + {{Math.round(adata.set_pressure)}} kPa + + + + + + + {{Math.round(adata.node1_concentration)}}% + + + + + + + {{Math.round(adata.node2_concentration)}}% + + diff --git a/tgui/src/Unimplemented/atmos_pump.ract b/tgui/src/Unimplemented/atmos_pump.ract new file mode 100644 index 0000000000..2f1f47aa4f --- /dev/null +++ b/tgui/src/Unimplemented/atmos_pump.ract @@ -0,0 +1,19 @@ + + + {{data.on ? "On" : "Off"}} + + {{#if data.max_rate}} + + Set + Max + {{Math.round(adata.rate)}} L/s + + {{else}} + + Set + Max + {{Math.round(adata.pressure)}} kPa + + {{/if}} + diff --git a/tgui/src/Unimplemented/chem_dispenser.ract b/tgui/src/Unimplemented/chem_dispenser.ract new file mode 100644 index 0000000000..1faddb2ff9 --- /dev/null +++ b/tgui/src/Unimplemented/chem_dispenser.ract @@ -0,0 +1,38 @@ + + + {{Math.fixed(adata.energy)}} Units + + + + {{#partial button}} + {{#each data.beakerTransferAmounts}} + {{.}} + {{/each}} + {{/partial}} + + {{#each data.chemicals}} + {{title}} + {{/each}} + + + + {{#partial button}} + {{#each data.beakerTransferAmounts}} + {{.}} + {{/each}} + Eject + {{/partial}} + + {{#if data.isBeakerLoaded}} + {{Math.round(adata.beakerCurrentVolume)}}/{{data.beakerMaxVolume}} Units +
+ {{#each adata.beakerContents}} + {{Math.fixed(volume, 2)}} units of {{name}}
+ {{else}} + Beaker Empty + {{/each}} + {{else}} + No Beaker + {{/if}} +
+
diff --git a/tgui/src/Unimplemented/chem_heater.ract b/tgui/src/Unimplemented/chem_heater.ract new file mode 100644 index 0000000000..9093804cb7 --- /dev/null +++ b/tgui/src/Unimplemented/chem_heater.ract @@ -0,0 +1,29 @@ + + + {{data.isActive ? "On" : "Off"}} + + + {{Math.round(adata.targetTemp)}} K + + + + {{#partial button}} + Eject + {{/partial}} + + {{#if data.isBeakerLoaded}} + Temperature: {{Math.round(adata.currentTemp)}} K +
+ {{#each adata.beakerContents}} + {{Math.fixed(volume, 2)}} units of {{name}}
+ {{else}} + Beaker Empty + {{/each}} + {{else}} + No Beaker + {{/if}} +
+
diff --git a/tgui/src/Unimplemented/cryo.ract b/tgui/src/Unimplemented/cryo.ract new file mode 100644 index 0000000000..f7ba23b5e2 --- /dev/null +++ b/tgui/src/Unimplemented/cryo.ract @@ -0,0 +1,74 @@ + + + + + {{data.occupant.name ? data.occupant.name : "No Occupant"}} + + {{#if data.hasOccupant}} + + {{data.occupant.stat == 0 ? "Conscious" : data.occupant.stat == 1 ? "Unconcious" : "Dead"}} + + + {{Math.round(adata.occupant.bodyTemperature)}} K + + + {{Math.round(adata.occupant.health)}} + + {{#each [{label: "Brute", type: "bruteLoss"}, {label: "Respiratory", type: "oxyLoss"}, {label: "Toxin", type: "toxLoss"}, {label: "Burn", type: "fireLoss"}]}} + + {{Math.round(adata.occupant[type])}} + + {{/each}} + {{/if}} + + + + {{data.isOperating ? "On" : "Off"}} + + + {{Math.round(adata.cellTemperature)}} K + + + {{data.isOpen ? "Open" : "Closed"}} + {{data.autoEject ? "Auto" : "Manual"}} + + + + {{#partial button}} + Eject + {{/partial}} + + {{#if data.isBeakerLoaded}} + {{#each adata.beakerContents}} + {{Math.fixed(volume, 2)}} units of {{name}}
+ {{else}} + Beaker Empty + {{/each}} + {{else}} + No Beaker + {{/if}} +
+
diff --git a/tgui/src/Unimplemented/firealarm.ract b/tgui/src/Unimplemented/firealarm.ract new file mode 100644 index 0000000000..52f43d5b36 --- /dev/null +++ b/tgui/src/Unimplemented/firealarm.ract @@ -0,0 +1,29 @@ + + + + + {{text.titleCase(data.seclevel)}} + + + + {{data.alarm ? "Reset" : "Activate"}} + + {{#if data.emagged}} + + Safety measures offline. Device may exhibit abnormal behavior. + + {{/if}} + diff --git a/tgui/src/Unimplemented/intellicard.ract b/tgui/src/Unimplemented/intellicard.ract new file mode 100644 index 0000000000..a1941e48f8 --- /dev/null +++ b/tgui/src/Unimplemented/intellicard.ract @@ -0,0 +1,42 @@ + + +{{#if data.wiping}} + + Wipe in progress! + +{{/if}} + + {{#partial button}} + {{#if data.name}} + Wipe AI + {{/if}} + {{/partial}} + {{#if data.name}} + + {{data.isDead || data.isBraindead ? "Offline" : "Operational"}} + + + {{Math.round(adata.health)}}% + + + {{#each data.laws}} + {{.}}
+ {{/each}} +
+ + Wireless Activity + Subspace Radio + + {{/if}} +
diff --git a/tgui/src/Unimplemented/keycard_auth.ract b/tgui/src/Unimplemented/keycard_auth.ract new file mode 100644 index 0000000000..3a7479f578 --- /dev/null +++ b/tgui/src/Unimplemented/keycard_auth.ract @@ -0,0 +1,16 @@ +{{#if data.waiting}} + + Waiting for another device to confirm your request... + +{{else}} + + + {{#if data.auth_required}} + Authorize {{data.auth_required}} + {{else}} + Red Alert + Emergency Maintenance Access + {{/if}} + + +{{/if}} diff --git a/tgui/src/Unimplemented/mulebot.ract b/tgui/src/Unimplemented/mulebot.ract new file mode 100644 index 0000000000..7a98a5489b --- /dev/null +++ b/tgui/src/Unimplemented/mulebot.ract @@ -0,0 +1,60 @@ + + {{#if data.siliconUser}} + + {{data.locked ? "Engaged" : "Disengaged"}} + + {{else}} + Swipe an ID card to {{data.locked ? "unlock" : "lock"}} this interface. + {{/if}} + + + + {{#if !data.locked || data.siliconUser }} + {{data.on ? "On" : "Off"}} + {{else}} + {{data.on ? "On" : "Off"}} + {{/if}} + + + {{data.cell ? data.cellPercent + "%" : "No Cell"}} + + + {{data.mode}} + + + {{data.load ? data.load : "None"}} + + + {{data.destination ? data.destination : "None"}} + + +{{#if !data.locked || data.siliconUser}} + + {{#partial button}} + {{#if data.load}} + Unload + {{/if}} + {{#if data.haspai}} + Eject PAI + {{/if}} + Set ID + {{/partial}} + + Set Destination + Stop + Go + + + Go Home + Set Home + + + + Auto-Return Home + + Auto-Pickup Crate + + Report Deliveries + + +{{/if}} diff --git a/tgui/src/Unimplemented/portable_pump.ract b/tgui/src/Unimplemented/portable_pump.ract new file mode 100644 index 0000000000..c9b780f231 --- /dev/null +++ b/tgui/src/Unimplemented/portable_pump.ract @@ -0,0 +1,54 @@ + + The regulator {{data.holding ? "is" : "is not"}} connected to a tank. + + + + {{Math.round(adata.pressure)}} kPa + + + {{data.connected ? "Connected" : "Not Connected"}} + + + + + {{data.on ? "On" : "Off"}} + + + {{data.direction == "out" ? "Out" : "In"}} + + + {{Math.round(adata.target_pressure)}} kPa + + + Reset + Min + Set + Max + + + + {{#partial button}} + {{#if data.holding}} + Eject + {{/if}} + {{/partial}} + {{#if data.holding}} + + {{data.holding.name}} + + + {{Math.round(adata.holding.pressure)}} kPa + + {{else}} + + No Holding Tank + + {{/if}} + diff --git a/tgui/src/Unimplemented/portable_scrubber.ract b/tgui/src/Unimplemented/portable_scrubber.ract new file mode 100644 index 0000000000..ff8edff71c --- /dev/null +++ b/tgui/src/Unimplemented/portable_scrubber.ract @@ -0,0 +1,37 @@ + + The regulator {{data.holding ? "is" : "is not"}} connected to a tank. + + + + {{Math.round(adata.pressure)}} kPa + + + {{data.connected ? "Connected" : "Not Connected"}} + + + + + {{data.on ? "On" : "Off"}} + + + + {{#partial button}} + {{#if data.holding}} + Eject + {{/if}} + {{/partial}} + {{#if data.holding}} + + {{data.holding.name}} + + + {{Math.round(adata.holding.pressure)}} kPa + + {{else}} + + No Holding Tank + + {{/if}} + diff --git a/tgui/src/Unimplemented/power_monitor.ract b/tgui/src/Unimplemented/power_monitor.ract new file mode 100644 index 0000000000..2ebadeccf5 --- /dev/null +++ b/tgui/src/Unimplemented/power_monitor.ract @@ -0,0 +1,77 @@ + + + + {{#if config.fancy}} + + {{else}} + + {{data.supply}} W + + + {{data.demand}} W + + {{/if}} + + + +
Area
+
Charge
+
Load
+
Status
+
Equipment
+
Lighting
+
Environment
+
+ {{#each data.areas}} + +
{{Math.round(adata.areas[@index].charge)}} %
+
{{Math.round(adata.areas[@index].load)}} W
+
{{chargingMode(charging)}}
+
{{channelPower(eqp)}} [{{channelMode(eqp)}}]
+
{{channelPower(lgt)}} [{{channelMode(lgt)}}]
+
{{channelPower(env)}} [{{channelMode(env)}}]
+
+ {{/each}} +
diff --git a/tgui/src/Unimplemented/radio.ract b/tgui/src/Unimplemented/radio.ract new file mode 100644 index 0000000000..a07347b940 --- /dev/null +++ b/tgui/src/Unimplemented/radio.ract @@ -0,0 +1,66 @@ + + + + {{#if data.headset}} + + + {{data.listening ? "On": "Off"}} + + {{else}} + + + {{data.broadcasting ? "Engaged": "Disengaged"}} + + + + {{data.listening ? "Engaged": "Disengaged"}} + + {{/if}} + {{#if data.command}} + + + {{data.useCommand ? "On": "Off"}} + + {{/if}} + + + + {{#if data.freqlock}} + {{readableFrequency}} + {{else}} + + + {{readableFrequency}} + + + {{/if}} + + {{#if data.subspaceSwitchable}} + + {{data.subspace ? "Active" : "Inactive"}} + + {{/if}} + {{#if data.subspace && data.channels}} + + {{#each data.channels:channel}} + + {{channel}}
+ {{/each}} +
+ {{/if}} +
diff --git a/tgui/src/Unimplemented/sleeper.ract b/tgui/src/Unimplemented/sleeper.ract new file mode 100644 index 0000000000..1cda6f0daa --- /dev/null +++ b/tgui/src/Unimplemented/sleeper.ract @@ -0,0 +1,56 @@ + + + + + {{data.occupant.name ? data.occupant.name : "No Occupant"}} + + {{#if data.occupied}} + + {{data.occupant.stat == 0 ? "Conscious" : data.occupant.stat == 1 ? "Unconcious" : "Dead"}} + + + {{Math.round(adata.occupant.health)}} + + {{#each [{label: "Brute", type: "bruteLoss"}, {label: "Respiratory", type: "oxyLoss"}, {label: "Toxin", type: "toxLoss"}, {label: "Burn", type: "fireLoss"}]}} + + {{Math.round(adata.occupant[type])}} + + {{/each}} + + {{data.occupant.cloneLoss ? "Damaged" : "Healthy"}} + + + {{data.occupant.brainLoss ? "Abnormal" : "Healthy"}} + + + {{#each adata.occupant.reagents}} + {{Math.fixed(volume, 1)}} units of {{name}}
+ {{else}} + Pure + {{/each}} +
+ {{/if}} +
+ + + {{data.open ? "Open" : "Closed"}} + + + {{#each data.chems}} + {{name}}
+ {{/each}} +
+
diff --git a/tgui/src/Unimplemented/smes.ract b/tgui/src/Unimplemented/smes.ract new file mode 100644 index 0000000000..45982141f1 --- /dev/null +++ b/tgui/src/Unimplemented/smes.ract @@ -0,0 +1,70 @@ + + + + + {{Math.fixed(adata.capacityPercent)}}% + + + + + {{data.inputAttempt ? "Auto" : "Off"}} +   + [{{data.capacityPercent >= 100 ? "Fully Charged" : data.inputting ? "Charging" : "Not Charging"}}] + + + {{Math.round(adata.inputLevel)}}W + + + + + Set + + + + + {{Math.round(adata.inputAvailable)}}W + + + + + {{data.outputAttempt ? "On" : "Off"}} +   + [{{data.outputting ? "Sending" : data.charge > 0 ? "Not Sending" : "No Charge"}}] + + + {{Math.round(adata.outputLevel)}}W + + + + + Set + + + + + {{Math.round(adata.outputUsed)}}W + + diff --git a/tgui/src/Unimplemented/solar_control.ract b/tgui/src/Unimplemented/solar_control.ract new file mode 100644 index 0000000000..45af5ba7b0 --- /dev/null +++ b/tgui/src/Unimplemented/solar_control.ract @@ -0,0 +1,46 @@ + + + {{Math.round(adata.generated)}}W + + + {{Math.round(adata.angle)}}° ({{data.direction}}) + + + 15° + + + 15° + + + + + Off + Timed + Auto + + + {{Math.round(adata.tracking_rate)}}°/h ({{data.rotating_way}}) + + + 180° + 30° + + + 30° + 180° + + + + {{#partial button}} + Refresh + {{/partial}} + + {{data.connected_tracker ? "" : "Not "}}Found + + + {{Math.round(adata.connected_panels)}} Panels Connected + + diff --git a/tgui/src/Unimplemented/space_heater.ract b/tgui/src/Unimplemented/space_heater.ract new file mode 100644 index 0000000000..9f143d9562 --- /dev/null +++ b/tgui/src/Unimplemented/space_heater.ract @@ -0,0 +1,45 @@ + + {{#partial button}} + {{#if data.open}} + Eject + {{/if}} + {{/partial}} + + {{data.on ? "On" : "Off"}} + + + {{#if data.hasPowercell}} + {{Math.fixed(adata.powerLevel)}}% + {{else}} + No Cell + {{/if}} + + + + + {{Math.round(adata.currentTemp)}}°C + + + {{Math.round(adata.targetTemp)}}°C + + {{#if data.open}} + + + + Set + + + + {{/if}} + + {{#if data.open}} + Heat + Cool + Auto + {{else}} + {{text.titleCase(data.mode)}} + {{/if}} + + diff --git a/tgui/src/Unimplemented/station_alert.ract b/tgui/src/Unimplemented/station_alert.ract new file mode 100644 index 0000000000..56c20e302f --- /dev/null +++ b/tgui/src/Unimplemented/station_alert.ract @@ -0,0 +1,11 @@ +{{#each data.alarms:class}} + +
    + {{#each .}} +
  • {{.}}
  • + {{else}} +
  • System Nominal
  • + {{/each}} +
+
+{{/each}} diff --git a/tgui/src/Unimplemented/suit_storage_unit.ract b/tgui/src/Unimplemented/suit_storage_unit.ract new file mode 100644 index 0000000000..d37a7d4b20 --- /dev/null +++ b/tgui/src/Unimplemented/suit_storage_unit.ract @@ -0,0 +1,41 @@ +{{#if data.occupied && data.safeties}} + + Biological entity detected in contents. Please remove. + +{{/if}} +{{#if data.uv_active}} + + Contents are being disinfected. Please wait. + +{{else}} + + {{#partial button}} + {{#if !data.open}}{{data.locked ? 'Unlock' : 'Lock'}}{{/if}} + {{#if !data.locked}}{{data.open ? 'Close' : 'Open'}}{{/if}} + {{/partial}} + {{#if data.locked}} + + Unit Locked + + {{elseif data.open}} + + {{data.helmet || "Empty"}} + + + {{data.suit || "Empty"}} + + + {{data.mask || "Empty"}} + + + {{data.storage || "Empty"}} + + {{else}} + Disinfect + {{/if}} + +{{/if}} diff --git a/tgui/src/Unimplemented/tank_dispenser.ract b/tgui/src/Unimplemented/tank_dispenser.ract new file mode 100644 index 0000000000..220bf40e2d --- /dev/null +++ b/tgui/src/Unimplemented/tank_dispenser.ract @@ -0,0 +1,8 @@ + + + Plasma ({{Math.round(adata.plasma)}}) + Oxygen ({{Math.round(adata.oxygen)}}) + + diff --git a/tgui/src/Unimplemented/tanks.ract b/tgui/src/Unimplemented/tanks.ract new file mode 100644 index 0000000000..e4d08c8441 --- /dev/null +++ b/tgui/src/Unimplemented/tanks.ract @@ -0,0 +1,35 @@ + + + + The regulator {{data.connected? "is" : "is not"}} connected to a mask. + + + + {{Math.round(adata.tankPressure)}} kPa + + + {{Math.round(adata.releasePressure)}} kPa + + + Reset + Min + Set + Max + + diff --git a/tgui/src/Unimplemented/thermomachine.ract b/tgui/src/Unimplemented/thermomachine.ract new file mode 100644 index 0000000000..136284b145 --- /dev/null +++ b/tgui/src/Unimplemented/thermomachine.ract @@ -0,0 +1,25 @@ + + + {{Math.fixed(adata.temperature, 2)}} K + + + {{Math.fixed(adata.pressure, 2)}} kPa + + + + + {{data.on ? "On": "Off"}} + + + + + {{Math.fixed(adata.target, 2)}} + + + + diff --git a/tgui/src/Unimplemented/uplink.ract b/tgui/src/Unimplemented/uplink.ract new file mode 100644 index 0000000000..6c38989574 --- /dev/null +++ b/tgui/src/Unimplemented/uplink.ract @@ -0,0 +1,50 @@ + + + + {{#partial button}} + {{#if config.fancy}} + + {{/if}} + {{#if data.lockable}} + Lock + {{/if}} + {{/partial}} + + {{data.telecrystals}} TC + + +{{#each data.categories}} + + {{#each items}} + + {{cost}} TC + + {{/each}} + +{{/each}} diff --git a/tgui/src/Unimplemented/wires.ract b/tgui/src/Unimplemented/wires.ract new file mode 100644 index 0000000000..a0ee953ebd --- /dev/null +++ b/tgui/src/Unimplemented/wires.ract @@ -0,0 +1,16 @@ + + {{#each data.wires}} + + {{cut ? "Mend" : "Cut"}} + Pulse + {{attached ? "Detach" : "Attach"}} + + {{/each}} + +{{#if data.status}} + + {{#each data.status}} + {{.}} + {{/each}} + +{{/if}} diff --git a/tgui/src/components/bar.ract b/tgui/src/components/bar.ract new file mode 100644 index 0000000000..f4d18125ef --- /dev/null +++ b/tgui/src/components/bar.ract @@ -0,0 +1,16 @@ + + +
+
+ {{yield}} +
diff --git a/tgui/src/components/bar.styl b/tgui/src/components/bar.styl new file mode 100644 index 0000000000..0e2b52cda8 --- /dev/null +++ b/tgui/src/components/bar.styl @@ -0,0 +1,32 @@ +context = selector() + +.bar + display: inline-block + position: relative + vertical-align: middle + width: 100% + height: 20px + line-height: @height - 3px + padding: 1px + + border: 1px solid bar-color-border + background: bar-color-background + + .barText + @extend {context} $fontReset + position: absolute + top: 0 + right: 3px + + .barFill + display: block + height: 100% + + transition: background-color 1s + background-color: bar-color-normal + &.good + background-color: bar-color-good + &.average + background-color: bar-color-average + &.bad + background-color: bar-color-bad diff --git a/tgui/src/components/button.ract b/tgui/src/components/button.ract new file mode 100644 index 0000000000..e61164160a --- /dev/null +++ b/tgui/src/components/button.ract @@ -0,0 +1,59 @@ + + + + {{#if icon}} + + {{/if}} + {{yield}} + diff --git a/tgui/src/components/button.styl b/tgui/src/components/button.styl new file mode 100644 index 0000000000..f02c779a81 --- /dev/null +++ b/tgui/src/components/button.styl @@ -0,0 +1,41 @@ +context = selector() + +buttoncolor(selector, color) + &.{selector} + transition: background-color 0.5s + background-color: color + &.{selector}.active:hover, + &.{selector}.active:focus + transition: background-color 0.25s + background-color: lighten(color, button-lighten-hover) + outline: 0 + +span.button + @extend {context} $fontReset + display: inline-block + vertical-align: middle + height: 20px + line-height: @height - 3px + padding: 0 5px + white-space: nowrap + + border: 1px solid button-color-border + + .fa + padding-right: 2px + + buttoncolor(normal, button-color-normal) + buttoncolor(disabled, button-color-disabled) + buttoncolor(selected, button-color-selected) + buttoncolor(caution, button-color-caution) + buttoncolor(danger, button-color-danger) + + &.gridable + width: 125px + margin: 2px 0 + +span:not(.button) + span.button + margin-left: 5px + +span.button + span:not(.button) + margin-left: 5px diff --git a/tgui/src/components/display.ract b/tgui/src/components/display.ract new file mode 100644 index 0000000000..2b37a0235d --- /dev/null +++ b/tgui/src/components/display.ract @@ -0,0 +1,13 @@ +
+ {{#if title}} +
+

{{title}}

+ {{#if button}} +
{{yield button}}
+ {{/if}} +
+ {{/if}} +
+ {{yield}} +
+
diff --git a/tgui/src/components/display.styl b/tgui/src/components/display.styl new file mode 100644 index 0000000000..02b4d5d2a7 --- /dev/null +++ b/tgui/src/components/display.styl @@ -0,0 +1,29 @@ +div.display + width: 100% + padding: 4px + margin: 6px 0 + + background-color: display-color-background // Transparent background. + box-shadow: inset 0 0 5px display-color-shadow + + header + display: block + position: relative + width: 100% + padding: 0 4px + margin-bottom: 6px + + color: display-color-title + + border-bottom: rule-size solid rule-color-normal + + .buttonRight + position: absolute + bottom: 6px + right: 4px + + article + display: table + width: 100% + + border-collapse: collapse; diff --git a/tgui/src/components/input.ract b/tgui/src/components/input.ract new file mode 100644 index 0000000000..8114cead81 --- /dev/null +++ b/tgui/src/components/input.ract @@ -0,0 +1,13 @@ + + + + diff --git a/tgui/src/components/input.styl b/tgui/src/components/input.styl new file mode 100644 index 0000000000..4669519939 --- /dev/null +++ b/tgui/src/components/input.styl @@ -0,0 +1,16 @@ +input + display: inline-block + vertical-align: middle + height: 20px + line-height: @height - 3px + padding: 0 5px + white-space: nowrap + + color: input-color-text + background-color: input-color-background + border: 1px solid input-color-border + + &::placeholder + color: input-color-placeholder + &::-ms-clear + display: none diff --git a/tgui/src/components/linegraph.ract b/tgui/src/components/linegraph.ract new file mode 100644 index 0000000000..a2c20a0c3b --- /dev/null +++ b/tgui/src/components/linegraph.ract @@ -0,0 +1,88 @@ + + + + + {{#graph({data: points, xaccessor: xaccessor, yaccessor: yaccessor, width: width, height: height})}} + {{#each xaxis}} + + {{#if @index % 2 == 0}} + {{(size - .) * xfactor}} {{xunit}} + {{/if}} + {{/each}} + {{#each yaxis}} + + {{. * yfactor}} {{yunit}} + {{/each}} + {{#each curves:curve}} + + {{/each}} + {{#each curves:curve}} + + {{/each}} + {{#each curves:curve}} + {{#each line.path.points():count}} + + {{/each}} + {{/each}} + {{#each curves:curve}} + {{#each line.path.points():count}} + {{#if selected == count }} + + {{item[count].y * yfactor}} {{yunit}} @ {{(size - item[count].x) * xfactor}} {{xunit}} + + {{/if}} + {{/each}} + {{/each}} + {{#each curves:curve}} + + + {{legend[curve]}} + + {{/each}} + {{/graph}} + + diff --git a/tgui/src/components/linegraph.styl b/tgui/src/components/linegraph.styl new file mode 100644 index 0000000000..a3226b28b2 --- /dev/null +++ b/tgui/src/components/linegraph.styl @@ -0,0 +1,2 @@ +svg.linegraph + overflow: hidden diff --git a/tgui/src/components/notice.ract b/tgui/src/components/notice.ract new file mode 100644 index 0000000000..c23c27b122 --- /dev/null +++ b/tgui/src/components/notice.ract @@ -0,0 +1,3 @@ +
+ {{yield}} +
diff --git a/tgui/src/components/notice.styl b/tgui/src/components/notice.styl new file mode 100644 index 0000000000..10ea60a953 --- /dev/null +++ b/tgui/src/components/notice.styl @@ -0,0 +1,27 @@ +div.notice + margin: 8px 0 + padding: 4px + + box-shadow: none + + color: text-color-inverse + font-weight: bold + font-style: italic + + background-color: notice-color-first + background-image: repeating-linear-gradient( + -45deg, + notice-color-first, + notice-color-first 10px, + notice-color-second 10px, + notice-color-second 20px + ) + + .label + color: text-color-inverse + + .content:only-of-type + padding: 0 + + hr + background-color: rule-color-dark diff --git a/tgui/src/components/resize.ract b/tgui/src/components/resize.ract new file mode 100644 index 0000000000..7065ee4572 --- /dev/null +++ b/tgui/src/components/resize.ract @@ -0,0 +1,29 @@ + + +{{#if config.fancy}} +
+{{/if}} diff --git a/tgui/src/components/resize.styl b/tgui/src/components/resize.styl new file mode 100644 index 0000000000..f5f5fe37aa --- /dev/null +++ b/tgui/src/components/resize.styl @@ -0,0 +1,12 @@ +div.resize + position: fixed + bottom: 0 + right: 0 + width: 0 + height: 0 + + border-style: solid; + border-width: 0 0 45px 45px; + border-color: transparent transparent resize-color transparent + + transform: rotate(360deg) diff --git a/tgui/src/components/section.ract b/tgui/src/components/section.ract new file mode 100644 index 0000000000..d229d2c454 --- /dev/null +++ b/tgui/src/components/section.ract @@ -0,0 +1,12 @@ +
+ {{#if label}} + {{label}}: + {{/if}} + {{#if nowrap}} + {{yield}} + {{else}} +
+ {{yield}} +
+ {{/if}} +
diff --git a/tgui/src/components/section.styl b/tgui/src/components/section.styl new file mode 100644 index 0000000000..d208433355 --- /dev/null +++ b/tgui/src/components/section.styl @@ -0,0 +1,33 @@ +/$cell + display: table-cell + margin: 0 + text-align: left + vertical-align: middle + padding: 3px 2px + +section + display: table-row + width: 100% + + &:not(:first-child) + padding-top: 4px + + &.candystripe:nth-child(even) + background-color: section-color-candystripe + + .label + @extend $cell + width: 1% + padding-right: 32px + white-space: nowrap + + color: section-color-label + + .content + @extend $cell + &:not(:last-child) + padding-right: 16px + + .line + @extend $cell + width: 100% diff --git a/tgui/src/components/subdisplay.ract b/tgui/src/components/subdisplay.ract new file mode 100644 index 0000000000..8d6cd641f9 --- /dev/null +++ b/tgui/src/components/subdisplay.ract @@ -0,0 +1,11 @@ +
+ {{#if title}} +
+

{{title}}

+ {{#if button}}{{yield button}}{{/if}} +
+ {{/if}} +
+ {{yield}} +
+
diff --git a/tgui/src/components/subdisplay.styl b/tgui/src/components/subdisplay.styl new file mode 100644 index 0000000000..11420ecba7 --- /dev/null +++ b/tgui/src/components/subdisplay.styl @@ -0,0 +1,11 @@ +context = selector() + +div.subdisplay + width: 100% + margin: 0 + + header + @extend {context} div.display header + + article + @extend {context} div.display article diff --git a/tgui/src/components/tabs.ract b/tgui/src/components/tabs.ract new file mode 100644 index 0000000000..2a7db40a51 --- /dev/null +++ b/tgui/src/components/tabs.ract @@ -0,0 +1,27 @@ + + + + +
+ {{#each tabs}} + {{.}} + {{/each}} +
+ + {{>content}} + diff --git a/tgui/src/components/tabs/tab.ract b/tgui/src/components/tabs/tab.ract new file mode 100644 index 0000000000..b02bba5c20 --- /dev/null +++ b/tgui/src/components/tabs/tab.ract @@ -0,0 +1,3 @@ +{{#if shown}} + {{yield}} +{{/if}} diff --git a/tgui/src/components/titlebar.ract b/tgui/src/components/titlebar.ract new file mode 100644 index 0000000000..a67832cef8 --- /dev/null +++ b/tgui/src/components/titlebar.ract @@ -0,0 +1,56 @@ + + +
+ + {{yield}} + {{#if config.fancy}} + + + {{/if}} +
diff --git a/tgui/src/components/titlebar.styl b/tgui/src/components/titlebar.styl new file mode 100644 index 0000000000..628fe75745 --- /dev/null +++ b/tgui/src/components/titlebar.styl @@ -0,0 +1,65 @@ +context = selector() + +$titleButton + display: inline-block + position: relative + padding: 7px // To make a bigger clickable area. + margin: -7px + + color: titlebar-color-button; + + &:hover + color: lighten(titlebar-color-button, button-lighten-hover) + +header.titlebar + position: fixed; + z-index: 1 + top: 0; + left: 0; + width: 100%; + height: 32px; + + background-color: titlebar-color-background + border-bottom: 1px solid titlebar-color-coreshadow + box-shadow: 0 3px 3px titlebar-color-shadow + + .statusicon + position: absolute + top: 4px + left: 12px + transition: color 0.5s + + .title + position: absolute + top: 6px + left: 46px + + color: titlebar-color-text + font-size: 16px + white-space: nowrap + + .minimize + @extend {context} $titleButton + position: absolute + top: 6px + right: 46px + .close + @extend {context} $titleButton + position: absolute + top: 4px + right: 12px + +/.no-icons header.titlebar + .statusicon + font-size: 20px + &::after + content: "O" + .minimize + top: -2px + font-size: 20px + &::after + content: "—" + .close + font-size: 20px + &::after + content: "X" diff --git a/tgui/src/components/warnings.ract b/tgui/src/components/warnings.ract new file mode 100644 index 0000000000..74e4819f4a --- /dev/null +++ b/tgui/src/components/warnings.ract @@ -0,0 +1,47 @@ + + + +{{#if config.fancy && ie && ie < 11}} + + You have an old (IE{{ie}}), end-of-life (click 'EOL Info' for more information) version of Internet Explorer installed.
+ To upgrade, click 'Upgrade IE' to download IE11 from Microsoft.
+ If you are unable to upgrade directly, click 'IE VMs' to download a VM with IE11 or Edge from Microsoft.
+ Otherwise, click 'No Frills' below to disable potentially incompatible features (and this message). +
+ No Frills + + Upgrade IE + + IE VMs + + EOL Info + Debug Info + {{#if debug}} +
+ Detected: IE{{ie}}
+ User Agent: {{userAgent}} + {{/if}} +
+{{/if}} diff --git a/tgui/src/images/nanotrasen.svg b/tgui/src/images/nanotrasen.svg new file mode 100644 index 0000000000..d21b9f0a2a --- /dev/null +++ b/tgui/src/images/nanotrasen.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/tgui/src/images/syndicate.svg b/tgui/src/images/syndicate.svg new file mode 100644 index 0000000000..c2863b790d --- /dev/null +++ b/tgui/src/images/syndicate.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/tgui/src/interfaces/brig_timer.ract b/tgui/src/interfaces/brig_timer.ract new file mode 100644 index 0000000000..28637da6b4 --- /dev/null +++ b/tgui/src/interfaces/brig_timer.ract @@ -0,0 +1,38 @@ + + + + {{#partial button}} + {{data.timing ? "Stop" : "Start"}} + {{#each data.flashes}} + {{status ? "Flash" : "Recharging"}} + {{/each}} + {{/partial}} + + + + {{#if data.timing}} + {{text.zeroPad(getminute, 2)}}:{{text.zeroPad(getsecond, 2)}} + {{else}} + {{text.zeroPad(setminute, 2)}}:{{text.zeroPad(setsecond, 2)}} + {{/if}} + + + + diff --git a/tgui/src/interfaces/canister.ract b/tgui/src/interfaces/canister.ract new file mode 100644 index 0000000000..48ef646694 --- /dev/null +++ b/tgui/src/interfaces/canister.ract @@ -0,0 +1,62 @@ + + The regulator {{data.hasHoldingTank ? "is" : "is not"}} connected to a tank. + + + {{#partial button}} + Relabel + {{/partial}} + + {{Math.round(adata.tankPressure)}} kPa + + + {{data.portConnected ? "Connected" : "Not Connected"}} + + + + + {{Math.round(adata.releasePressure)}} kPa + + + + + + + + + + + + + {{data.valveOpen ? "Open" : "Closed"}} + + + + {{#partial button}} + {{#if data.hasHoldingTank}} + Eject + {{/if}} + {{/partial}} + {{#if data.hasHoldingTank}} + + {{data.holdingTank.name}} + + + {{Math.round(adata.holdingTank.tankPressure)}} kPa + + {{else}} + + No Holding Tank + + {{/if}} + diff --git a/tgui/src/interfaces/cargo.ract b/tgui/src/interfaces/cargo.ract new file mode 100644 index 0000000000..5c7918c180 --- /dev/null +++ b/tgui/src/interfaces/cargo.ract @@ -0,0 +1,101 @@ + + +{{#if data.active}} + + + Cargo station logged in as register. + Log Out + + +{{/if}} + + + Location: {{data.location}} + {{#if data.active}} + Undock Shuttle + {{/if}} + + + {{Math.floor(adata.points)}} + + +{{#if data.active}} + + {{#partial button}} + Clear + {{/partial}} + {{#each data.cart}} + +
#{{id}}
+
{{object}}
+
{{cost}} Points
+
By: {{orderer}}
+
Reason: {{reason}}
+
+ Remove + +
+
+ {{else}} + Nothing in Cart + {{/each}} +
+{{/if}} + + {{#partial button}} + {{#if data.active}} + Clear + {{/if}} + {{/partial}} + {{#each data.requests}} + +
#{{id}}
+
{{object}}
+
{{cost}} Points
+
By {{orderer}}
+
Reason: {{reason}}
+
+ {{#if data.active}} + Approve + Deny + {{/if}} + {{data.active ? "" :"Print Receipt"}} +
+
+ {{else}} + No Requests + {{/each}} +
+{{#if data.canorder}} + + {{#each adata.supplies}} + + {{#each pack}} + + {{cost}} Credits + + {{/each}} + + {{/each}} + +{{else}} + + Cargo Request services are disabled until a register is activated. + +{{/if}} +{{#if !data.active}} + + + Log in to register mode (Will log out current register): + Log In + + +{{/if}} diff --git a/tgui/src/interfaces/error.ract b/tgui/src/interfaces/error.ract new file mode 100644 index 0000000000..23d76c2248 --- /dev/null +++ b/tgui/src/interfaces/error.ract @@ -0,0 +1,3 @@ + + The requested interface ({{config.interface}}) was not found. Does it exist? + diff --git a/tgui/src/interfaces/jukebox.ract b/tgui/src/interfaces/jukebox.ract new file mode 100644 index 0000000000..b02105fca9 --- /dev/null +++ b/tgui/src/interfaces/jukebox.ract @@ -0,0 +1,11 @@ + + + Play + Stop + + + + {{#each data.tracks}} + {{.}}
+ {{/each}} +
diff --git a/tgui/src/interfaces/resleever.ract b/tgui/src/interfaces/resleever.ract new file mode 100644 index 0000000000..29c3a21fc4 --- /dev/null +++ b/tgui/src/interfaces/resleever.ract @@ -0,0 +1,18 @@ + + + {{data.name}} + + + {{data.lace}} + + + + + + Start Procedure + + + Eject Occupant + Eject Neural Lace + diff --git a/tgui/src/interfaces/suit_sensor_jammer.ract b/tgui/src/interfaces/suit_sensor_jammer.ract new file mode 100644 index 0000000000..13a0e47154 --- /dev/null +++ b/tgui/src/interfaces/suit_sensor_jammer.ract @@ -0,0 +1,22 @@ + + + Enable + Disable + + + + + Decrease + Increase + + + + {{#each data.methods}} + {{name}} - {{cost}}
+ {{/each}} +
+ + + {{Math.fixed(data.current_charge*100/data.max_charge)}}% + + diff --git a/tgui/src/styles/nanotrasen.styl b/tgui/src/styles/nanotrasen.styl new file mode 100644 index 0000000000..14f6c7d574 --- /dev/null +++ b/tgui/src/styles/nanotrasen.styl @@ -0,0 +1,7 @@ +body.nanotrasen + background: data-url('images/nanotrasen.svg') no-repeat fixed center/70% 70%, + linear-gradient(to bottom, + background-color-start 0%, + background-color-end 100%) + @import "util/*" + @import "components/*" diff --git a/tgui/src/styles/syndicate.styl b/tgui/src/styles/syndicate.styl new file mode 100644 index 0000000000..e9d11c2e8a --- /dev/null +++ b/tgui/src/styles/syndicate.styl @@ -0,0 +1,30 @@ +body.syndicate + color-good = pale-green + color-highlight = black + background-color-start = #750000 + background-color-end = #340404 + rule-color-normal = dark-gray + titlebar-color-text = pale-red + titlebar-color-button = pale-red + display-color-background = alpha(#000, 0.5) + display-color-shadow = alpha(#000, 0.75) + notice-color-first = #750000 + notice-color-second = #910101 + section-color-label = white + bar-color-normal = black + bar-color-good = pale-green + bar-color-border = black + button-color-normal = dark-green + button-color-disabled = gray + button-color-selected = dark-red + button-color-caution = yellow-orange + button-color-danger = yellow + input-color-text = white + input-color-background = dark-red + + background: data-url('images/syndicate.svg') no-repeat fixed center/70% 70%, + linear-gradient(to bottom, + background-color-start 0%, + background-color-end 100%) + @import "util/*" + @import "components/*" diff --git a/tgui/src/tgui.js b/tgui/src/tgui.js new file mode 100644 index 0000000000..21adfb4859 --- /dev/null +++ b/tgui/src/tgui.js @@ -0,0 +1,53 @@ +// Temporarily import Ractive first to keep it from detecting ie8's object.defineProperty shim, which it misuses (ractivejs/ractive#2343). +import Ractive from 'ractive' +Ractive.DEBUG = /minified/.test(() => {/* minified */}) + +import 'ie8' +import 'babel-polyfill' +import 'dom4' +import 'html5shiv' + +// Extend the Math builtin with our own utilities. +Object.assign(Math, require('util/math')) + +// Set up the initialize function. This is either called below if JSON is provided +// inline, or called by the server if it was not. +import TGUI from 'tgui.ract' +window.initialize = (dataString) => { + if (window.tgui) return // Don't run twice. + window.tgui = new TGUI({ + el: '#container', + data () { + const initial = JSON.parse(dataString) + return { + constants: require('util/constants'), + text: require('util/text'), + config: initial.config, + data: initial.data, + adata: initial.data + } + } + }) +} + +// Try to find data in the page. If the JSON was inlined, load it. +const holder = document.getElementById('data') +const data = holder.textContent +const ref = holder.getAttribute('data-ref') +if (data !== '{}') { + window.initialize(data) + holder.remove() +} +// Let the server know we're set up. This also sends data if it was not inlined. +import { act } from 'util/byond' +act(ref, 'tgui:initialize') + +// Load fonts. +import { loadCSS } from 'fg-loadcss' +loadCSS('https://cdn.jsdelivr.net/fontawesome/4.5.0/css/font-awesome.min.css') +// Handle font loads. +import FontFaceObserver from 'fontfaceobserver' +const fontawesome = new FontFaceObserver('FontAwesome') +fontawesome.check('\uf240') + .then(() => document.body.classList.add('icons')) + .catch(() => document.body.classList.add('no-icons')) diff --git a/tgui/src/tgui.ract b/tgui/src/tgui.ract new file mode 100644 index 0000000000..e5d93aebf8 --- /dev/null +++ b/tgui/src/tgui.ract @@ -0,0 +1,61 @@ + + + + + + +{{{config.title}}} +
+ + +
+ diff --git a/tgui/src/tgui.styl b/tgui/src/tgui.styl new file mode 100644 index 0000000000..3fc64c9045 --- /dev/null +++ b/tgui/src/tgui.styl @@ -0,0 +1,134 @@ +@charset "utf-8" + +white = white +pale-red = #e74242 +red = #b00e0e +dark-red = #9d0808 +yellow-orange = #be6209 +yellow = #9a9d00 +pale-green = #73E573 +green = #2f943c +grass-green = #537d29 +dark-green = #397439 +royal-blue = #40628a +pale-blue = #8ba5c4 +black = black +black-gray = #161616 +dark-gray = #272727 +gray = #363636 +light-gray = #999999 + +// Branding Colors +color-normal = royal-blue +color-good = grass-green +color-average = yellow-orange +color-bad = red +color-highlight = pale-blue + +// Text +text-color-normal = white +text-color-inverse = black + +// Background +background-color-start = #2a2a2a +background-color-end = #202020 + +// Rules (
etc) +rule-color-normal = royal-blue +rule-color-dark = dark-gray +rule-size = 2px + +// Titlebar +titlebar-color-text = pale-blue +titlebar-color-button = pale-blue +titlebar-color-background = gray +titlebar-color-coreshadow = black-gray +titlebar-color-shadow = alpha(#000, 0.1) + +// Resize +resize-color = gray + +// Display +display-color-title = white +display-color-background = alpha(#000, 0.33) +display-color-shadow = alpha(#000, 0.5) + +// Notice +notice-color-first = #bb9b68 +notice-color-second = #b1905d +notice-color-border = dark-gray + +// Section +section-color-label = pale-blue +section-color-candystripe = alpha(#000, 0.2) + +// Bar +bar-color-normal = color-normal +bar-color-good = color-good +bar-color-average = color-average +bar-color-bad = color-bad +bar-color-border = royal-blue +bar-color-background = dark-gray + +// Buttons +button-color-normal = royal-blue +button-color-disabled = light-gray +button-color-selected = green +button-color-caution = yellow +button-color-danger = dark-red +button-color-border = dark-gray +button-lighten-hover = 15% +button-alpha-disabled = .75 + +// Input +input-color-text = black +input-color-placeholder = light-gray +input-color-border = dark-gray +input-color-background = white + +// Tooltips +tooltip-color-border = dark-gray +tooltip-color-background = gray + +html, body + box-sizing: border-box + height: 100% + margin: 0 + +html + overflow: hidden + cursor: default // Reset the cursor. + +body + overflow: auto + + font-family: Verdana, Geneva, sans-serif + font-size: 12px + color: text-color-normal + + background-color: background-color-start + background-image: linear-gradient(to bottom, + background-color-start 0%, + background-color-end 100%) + +*, *:before, *:after + box-sizing: inherit + +$h + display: inline-block + margin: 0 + padding: 6px 0 +h1 + @extend $h + font-size: 18px +h2 + @extend $h + font-size: 16px +h3 + @extend $h + font-size: 14px +h4 + @extend $h + font-size: 12px + +@require "styles/*" diff --git a/tgui/src/util/byond.js b/tgui/src/util/byond.js new file mode 100644 index 0000000000..beb3157809 --- /dev/null +++ b/tgui/src/util/byond.js @@ -0,0 +1,16 @@ +const encode = encodeURIComponent + +// Helper to generate a BYOND href given 'params' as an object (with an optional 'url' for eg winset). +export function href (params = {}, url = '') { + return `byond://${url}?` + Object.keys(params).map(key => `${encode(key)}=${encode(params[key])}`).join('&') +} + +// Helper to make a BYOND ui_act() call on the UI 'src' given an 'action' and optional 'params'. +export function act (src, action, params = {}) { + window.location.href = href(Object.assign({ src, action }, params)) +} + +// Helper to make a BYOND winset() call on 'window', setting 'key' to 'value' +export function winset (win, key, value) { + window.location.href = href({[`${win}.${key}`]: value}, 'winset') +} diff --git a/tgui/src/util/colors.styl b/tgui/src/util/colors.styl new file mode 100644 index 0000000000..d1f17a7768 --- /dev/null +++ b/tgui/src/util/colors.styl @@ -0,0 +1,10 @@ +.normal + color: color-normal +.good + color: color-good +.average + color: color-average +.bad + color: color-bad +.highlight + color: color-highlight diff --git a/tgui/src/util/constants.js b/tgui/src/util/constants.js new file mode 100644 index 0000000000..b7dac6eb67 --- /dev/null +++ b/tgui/src/util/constants.js @@ -0,0 +1,5 @@ +// Constants used in tgui; these are mirrored from the BYOND code. +export const UI_INTERACTIVE = 2 +export const UI_UPDATE = 1 +export const UI_DISABLED = 0 +export const UI_CLOSE = -1 diff --git a/tgui/src/util/dragresize.js b/tgui/src/util/dragresize.js new file mode 100644 index 0000000000..d31e32d641 --- /dev/null +++ b/tgui/src/util/dragresize.js @@ -0,0 +1,51 @@ +import {winset} from './byond' + +export function lock (x, y) { + if (x < 0) { // Left + x = 0 + } else if (x + window.innerWidth > window.screen.availWidth) { // Right + x = window.screen.availWidth - window.innerWidth + } + + if (y < 0) { // Top + y = 0 + } else if (y + window.innerHeight > window.screen.availHeight) { // Bottom + y = window.screen.availHeight - window.innerHeight + } + + return {x, y} +} + +export function drag (event) { + event.preventDefault() + + if (!this.get('drag')) return + + if (this.get('x')) { + let x = (event.screenX - this.get('x')) + window.screenLeft + let y = (event.screenY - this.get('y')) + window.screenTop + if (this.get('config.locked')) ({x, y} = lock(x, y)) // Lock to primary monitor. + winset(this.get('config.window'), 'pos', `${x},${y}`) + } + this.set({ x: event.screenX, y: event.screenY }) +} + +export function sane (x, y) { + x = Math.clamp(100, window.screen.width, x) + y = Math.clamp(100, window.screen.height, y) + return {x, y} +} + +export function resize (event) { + event.preventDefault() + + if (!this.get('resize')) return + + if (this.get('x')) { + let x = (event.screenX - this.get('x')) + window.innerWidth + let y = (event.screenY - this.get('y')) + window.innerHeight + ;({x, y} = sane(x, y)) + winset(this.get('config.window'), 'size', `${x},${y}`) + } + this.set({ x: event.screenX, y: event.screenY }) +} diff --git a/tgui/src/util/filter.js b/tgui/src/util/filter.js new file mode 100644 index 0000000000..66e9360052 --- /dev/null +++ b/tgui/src/util/filter.js @@ -0,0 +1,22 @@ +export function filterMulti (displays, string) { + for (let display of displays) { // First check if the display includes the search term in the first place. + if (display.textContent.toLowerCase().includes(string)) { + display.style.display = '' + filter(display, string) + } else { + display.style.display = 'none' + } + } +} + +export function filter (display, string) { + const items = display.queryAll('section') + const titleMatch = display.query('header').textContent.toLowerCase().includes(string) + for (let item of items) { // Check if the item or its displays title contains the search term. + if (titleMatch || item.textContent.toLowerCase().includes(string)) { + item.style.display = '' + } else { + item.style.display = 'none' + } + } +} diff --git a/tgui/src/util/math.js b/tgui/src/util/math.js new file mode 100644 index 0000000000..39a4b0c424 --- /dev/null +++ b/tgui/src/util/math.js @@ -0,0 +1,9 @@ +// Helper to limit a number to be inside 'min' and 'max'. +export function clamp (min, max, number) { + return Math.max(min, Math.min(number, max)) +} + +// Helper to round a number to 'decimals' decimals. +export function fixed (number, decimals = 1) { + return Number(Math.round(number + 'e' + decimals) + 'e-' + decimals) +} diff --git a/tgui/src/util/misc.styl b/tgui/src/util/misc.styl new file mode 100644 index 0000000000..08dd093547 --- /dev/null +++ b/tgui/src/util/misc.styl @@ -0,0 +1,12 @@ +main + display: block + margin-top: 32px + padding: 2px 6px 0 + +hr + height: rule-size + background-color: rule-color-normal + border: none + +.hidden + display: none diff --git a/tgui/src/util/text.js b/tgui/src/util/text.js new file mode 100644 index 0000000000..657160b673 --- /dev/null +++ b/tgui/src/util/text.js @@ -0,0 +1,14 @@ +export function upperCaseFirst (str) { + return str[0].toUpperCase() + str.slice(1).toLowerCase() +} + +export function titleCase (str) { + return str.replace(/\w\S*/g, upperCaseFirst) +} + +export function zeroPad (str, pad_size) { + str = str.toString() + while(str.length < pad_size) + str = '0' + str + return str +} diff --git a/tgui/src/util/text.styl b/tgui/src/util/text.styl new file mode 100644 index 0000000000..752bb120cc --- /dev/null +++ b/tgui/src/util/text.styl @@ -0,0 +1,17 @@ +// Helper to reset font options to default. +$fontReset + color: text-color-normal + font-size: 12px + font-weight: normal + font-style: normal + text-decoration: none + +// Utility classes to set font styles. +.bold + font-weight: bold +.italic + font-style: italic + +// Make 'unselectable' text unselectable on all browsers. +[unselectable=on] + user-select: none diff --git a/tgui/src/util/tooltip.styl b/tgui/src/util/tooltip.styl new file mode 100644 index 0000000000..a951b82876 --- /dev/null +++ b/tgui/src/util/tooltip.styl @@ -0,0 +1,53 @@ +div[data-tooltip], span[data-tooltip] + position: relative + + &::after + position: absolute + display: block + z-index: 2 + width: 250px + padding: 10px + transform: translateX(-50%) + + visibility: hidden + opacity: 0 + + white-space: normal + text-align: left + content: attr(data-tooltip) + + transition: all .5s + border: 1px solid tooltip-color-border + background-color: tooltip-color-background + + &:hover::after + visibility: visible + opacity: 1 + + &.tooltip-top::after + bottom: 100% + left: 50% + transform: translateX(-50%) translateY(8px) + &.tooltip-top:hover::after + transform: translateX(-50%) translateY(-8px) + + &.tooltip-bottom::after + top: 100% + left: 50% + transform: translateX(-50%) translateY(-8px) + &.tooltip-bottom:hover::after + transform: translateX(-50%) translateY(8px) + + &.tooltip-left::after + top: 50% + right: 100% + transform: translateX(8px) translateY(-50%) + &.tooltip-left:hover::after + transform: translateX(-8px) translateY(-50%) + + &.tooltip-right::after + top: 50% + left: 100% + transform: translateX(-8px) translateY(-50%) + &.tooltip-right:hover::after + transform: translateX(8px) translateY(-50%) diff --git a/tgui/tgui.html b/tgui/tgui.html new file mode 100644 index 0000000000..adcb337b94 --- /dev/null +++ b/tgui/tgui.html @@ -0,0 +1,35 @@ + + + + + + + + + + + +
+
+ Loading...
+
+
+ + +