final new NanoUI fixes and tgui port

This commit is contained in:
SinTwo
2016-07-27 23:01:26 -04:00
parent 0086d61d56
commit fe46fac5cc
166 changed files with 5075 additions and 412 deletions

View File

@@ -6,9 +6,11 @@ env:
BYOND_MAJOR="510" BYOND_MAJOR="510"
BYOND_MINOR="1346" BYOND_MINOR="1346"
MACRO_COUNT=986 MACRO_COUNT=986
NODE_VERSION="4"
cache: cache:
directories: directories:
- tgui/node_modules
- $HOME/BYOND-${BYOND_MAJOR}.${BYOND_MINOR} - $HOME/BYOND-${BYOND_MAJOR}.${BYOND_MINOR}
addons: addons:
@@ -19,10 +21,13 @@ addons:
- libstdc++6:i386 - libstdc++6:i386
before_script: before_script:
- cd tgui && npm install && cd ..
- chmod +x ./install-byond.sh - chmod +x ./install-byond.sh
- ./install-byond.sh - ./install-byond.sh
install: 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 PyYaml -q
- pip install --user beautifulsoup4 -q - pip install --user beautifulsoup4 -q
@@ -34,6 +39,8 @@ script:
- awk -f tools/indentation.awk **/*.dm - awk -f tools/indentation.awk **/*.dm
- md5sum -c - <<< "88490b460c26947f5ec1ab1bb9fa9f17 *html/changelogs/example.yml" - 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} ]) - (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 - source $HOME/BYOND-${BYOND_MAJOR}.${BYOND_MINOR}/byond/bin/byondsetup
- python tools/TagMatcher/tag-matcher.py ../.. - python tools/TagMatcher/tag-matcher.py ../..
- echo "#define UNIT_TEST 1" > code/_unit_tests.dm - echo "#define UNIT_TEST 1" > code/_unit_tests.dm

4
code/__defines/tgui.dm Normal file
View File

@@ -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

5
code/__defines/tick.dm Normal file
View File

@@ -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()

View File

@@ -1,6 +1,22 @@
#if DM_VERSION < 510 #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) /proc/replacetext(text, find, replacement)
return jointext(splittext(text, find), replacement) return jointext(splittext(text, find), replacement)
/proc/replacetextEx(text, find, replacement)
return jointext(text2listEx(text, find), replacement)
#endif #endif

View File

@@ -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)

View File

@@ -323,6 +323,30 @@ proc/listclearnulls(list/list)
return (result + L.Copy(Li, 0)) return (result + L.Copy(Li, 0))
return (result + R.Copy(Ri, 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 //Mergesort: any value in a list, preserves key=value structure
/proc/sortAssoc(var/list/L) /proc/sortAssoc(var/list/L)

View File

@@ -15,3 +15,11 @@
animate(src, transform = m120, time = speed, loops) animate(src, transform = m120, time = speed, loops)
animate(transform = m240, time = speed) animate(transform = m240, time = speed)
animate(transform = m360, 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

View File

@@ -1315,3 +1315,15 @@ var/mob/dview/dview_mob = new
tY = max(1, min(world.maxy, origin.y + (text2num(tY) - (world.view + 1)))) tY = max(1, min(world.maxy, origin.y + (text2num(tY) - (world.view + 1))))
return locate(tX, tY, tZ) 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.

View File

@@ -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]") testing("GC: [refID] old enough to test: GCd_at_time: [GCd_at_time] time_to_kill: [time_to_kill] current: [world.time]")
#endif #endif
if(A && A.gcDestroyed == GCd_at_time) // So if something else coincidently gets the same ref, it's not deleted by mistake 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. // 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 --") testing("GC: -- \ref[A] | [A.type] was unable to be GC'd and was deleted --")
logging["[A.type]"]++ logging["[A.type]"]++
@@ -88,29 +91,47 @@ world/loop_checks = 0
#undef GC_COLLECTIONS_PER_TICK #undef GC_COLLECTIONS_PER_TICK
#ifdef GC_FINDREF #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 . = 0
for(var/V in D.vars) for(var/V in D.vars)
if(V == "contents") if(V == "contents")
continue continue
if(istype(D.vars[V], /atom)) if(!islist(D.vars[V]))
var/atom/A = D.vars[V] if(D.vars[V] == A)
if(A in targ)
testing("GC: [A] | [A.type] referenced by [D] | [D.type], var [V]") testing("GC: [A] | [A.type] referenced by [D] | [D.type], var [V]")
. += 1 . += 1
else if(islist(D.vars[V])) else
. += LookForListRefs(D.vars[V], targ, D, V) . += 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 . = 0
for(var/F in L) for(var/F in L)
if(istype(F, /atom)) if(!islist(F))
var/atom/A = F if(F == A || L[F] == A)
if(A in targ)
testing("GC: [A] | [A.type] referenced by [D] | [D.type], list [V]") testing("GC: [A] | [A.type] referenced by [D] | [D.type], list [V]")
. += 1 . += 1
if(islist(F)) else
. += LookForListRefs(F, targ, D, "[F] in list [V]") . += LookForListRefs(F, A, D, "[F] in list [V]")
#endif #endif
/datum/controller/process/garbage_collector/proc/AddTrash(datum/A) /datum/controller/process/garbage_collector/proc/AddTrash(datum/A)

View File

@@ -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")

31
code/datums/weakref.dm Normal file
View File

@@ -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

View File

@@ -265,20 +265,17 @@ update_flag
return src.attack_hand(user) return src.attack_hand(user)
/obj/machinery/portable_atmospherics/canister/attack_hand(var/mob/user as mob) /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) /obj/machinery/portable_atmospherics/canister/ui_data(mob/user)
if (src.destroyed) var/list/data = list()
return
// this is the data which will be sent to the ui
var/data[0]
data["name"] = name data["name"] = name
data["canLabel"] = can_label ? 1 : 0 data["canLabel"] = can_label ? 1 : 0
data["portConnected"] = connected_port ? 1 : 0 data["portConnected"] = connected_port ? 1 : 0
data["tankPressure"] = round(air_contents.return_pressure() ? air_contents.return_pressure() : 0) data["tankPressure"] = round(air_contents.return_pressure() ? air_contents.return_pressure() : 0)
data["releasePressure"] = round(release_pressure ? release_pressure : 0) data["releasePressure"] = round(release_pressure ? release_pressure : 0)
data["minReleasePressure"] = round(ONE_ATMOSPHERE/10) data["minReleasePressure"] = round(ONE_ATMOSPHERE/10)
data["defaultReleasePressure"] = ONE_ATMOSPHERE
data["maxReleasePressure"] = round(10*ONE_ATMOSPHERE) data["maxReleasePressure"] = round(10*ONE_ATMOSPHERE)
data["valveOpen"] = valve_open ? 1 : 0 data["valveOpen"] = valve_open ? 1 : 0
@@ -286,83 +283,70 @@ update_flag
if (holding) if (holding)
data["holdingTank"] = list("name" = holding.name, "tankPressure" = round(holding.air_contents.return_pressure())) 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 return data
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)
/obj/machinery/portable_atmospherics/canister/Topic(href, href_list) /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)
//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")
return 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"]) /obj/machinery/portable_atmospherics/canister/ui_status(mob/user, datum/ui_state/state)
if (valve_open) if(!istype(src.loc, /turf))
if (holding) return UI_CLOSE
release_log += "Valve was <b>closed</b> by [usr] ([usr.ckey]), stopping the transfer into the [holding]<br>" return ..()
else
release_log += "Valve was <b>closed</b> by [usr] ([usr.ckey]), stopping the transfer into the <font color='red'><b>air</b></font><br>"
else
if (holding)
release_log += "Valve was <b>opened</b> by [usr] ([usr.ckey]), starting the transfer into the [holding]<br>"
else
release_log += "Valve was <b>opened</b> by [usr] ([usr.ckey]), starting the transfer into the <font color='red'><b>air</b></font><br>"
log_open()
valve_open = !valve_open
if (href_list["remove_tank"]) /obj/machinery/portable_atmospherics/canister/ui_act(action, params)
if(holding) 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) if (valve_open)
valve_open = 0 if (holding)
release_log += "Valve was <b>closed</b> by [usr] ([usr.ckey]), stopping the transfer into the [holding]<br>" release_log += "Valve was <b>closed</b> by [usr] ([usr.ckey]), stopping the transfer into the [holding]<br>"
if(istype(holding, /obj/item/weapon/tank)) else
holding.manipulated_by = usr.real_name release_log += "Valve was <b>closed</b> by [usr] ([usr.ckey]), stopping the transfer into the <font color='red'><b>air</b></font><br>"
holding.loc = loc else
holding = null if (holding)
release_log += "Valve was <b>opened</b> by [usr] ([usr.ckey]), starting the transfer into the [holding]<br>"
else
release_log += "Valve was <b>opened</b> by [usr] ([usr.ckey]), starting the transfer into the <font color='red'><b>air</b></font><br>"
log_open()
valve_open = !valve_open
if (href_list["pressure_adj"]) if("eject")
var/diff = text2num(href_list["pressure_adj"]) if(holding)
if(diff > 0) if (valve_open)
release_pressure = min(10*ONE_ATMOSPHERE, release_pressure+diff) valve_open = 0
else release_log += "Valve was <b>closed</b> by [usr] ([usr.ckey]), stopping the transfer into the [holding]<br>"
release_pressure = max(ONE_ATMOSPHERE/10, release_pressure+diff) if(istype(holding, /obj/item/weapon/tank))
holding.manipulated_by = usr.real_name
if (href_list["relabel"]) holding.loc = loc
if (can_label) holding = null
var/list/colors = list(\ return TRUE
"\[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
/obj/machinery/portable_atmospherics/canister/phoron/New() /obj/machinery/portable_atmospherics/canister/phoron/New()
..() ..()

View File

@@ -11,103 +11,95 @@
req_access = list(access_engine) 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/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/one_access = 0 //if set to 1, door would receive req_one_access instead of req_access
var/last_configurator = null var/last_configurator = null
var/locked = 1 var/locked = 1
attack_self(mob/user as mob)
if (!ishuman(user) && !istype(user,/mob/living/silicon/robot))
return ..(user)
var/t1 = text("<B>Access control</B><br>\n")
if (last_configurator) /obj/item/weapon/airlock_electronics/attack_self(mob/user as mob)
t1 += "Operator: [last_configurator]<br>" if (!ishuman(user) && !istype(user,/mob/living/silicon/robot))
return ..(user)
if (locked) tg_ui_interact(user)
t1 += "<a href='?src=\ref[src];login=1'>Swipe ID</a><hr>"
else
t1 += "<a href='?src=\ref[src];logout=1'>Block</a><hr>"
t1 += "Access requirement is set to "
t1 += one_access ? "<a style='color: green' href='?src=\ref[src];one_access=1'>ONE</a><hr>" : "<a style='color: red' href='?src=\ref[src];one_access=1'>ALL</a><hr>"
t1 += conf_access == null ? "<font color=red>All</font><br>" : "<a href='?src=\ref[src];access=all'>All</a><br>"
t1 += "<br>" //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() tgui_process.try_update_ui(user, src, ui_key, ui, force_open)
for (var/acc in accesses) if(!ui)
var/aname = get_access_desc(acc) 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)) /obj/item/weapon/airlock_electronics/ui_data(mob/user)
t1 += "<a href='?src=\ref[src];access=[acc]'>[aname]</a><br>" var/list/data = list()
else if(one_access) var/list/regions = list()
t1 += "<a style='color: green' href='?src=\ref[src];access=[acc]'>[aname]</a><br>"
else
t1 += "<a style='color: red' href='?src=\ref[src];access=[acc]'>[aname]</a><br>"
t1 += text("<p><a href='?src=\ref[];close=1'>Close</a></p>\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") return data
onclose(user, "airlock")
Topic(href, href_list) /obj/item/weapon/airlock_electronics/ui_act(action, params)
..() if(..())
if (usr.stat || usr.restrained() || (!ishuman(usr) && !istype(usr,/mob/living/silicon))) return TRUE
return switch(action)
if (href_list["close"]) if("clear")
usr << browse(null, "window=airlock") conf_access = list()
return one_access = 0
return TRUE
if (href_list["login"]) 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)) if(istype(usr,/mob/living/silicon))
src.locked = 0 locked = 0
src.last_configurator = usr.name last_configurator = usr.name
return TRUE
else else
var/obj/item/I = usr.get_active_hand() var/obj/item/I = usr.get_active_hand()
if (istype(I, /obj/item/device/pda)) if (istype(I, /obj/item/device/pda))
var/obj/item/device/pda/pda = I var/obj/item/device/pda/pda = I
I = pda.id I = pda.id
if(!istype(I, /obj/item/weapon/card/id))
usr << "<span class='warning'>[\src] flashes a yellow LED near the ID scanner. Did you remember to scan your ID or PDA?</span>"
return TRUE
if (I && src.check_access(I)) if (I && src.check_access(I))
src.locked = 0 locked = 0
src.last_configurator = I:registered_name 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
else else
conf_access -= req usr << "<span class='warning'>[\src] flashes a red LED near the ID scanner, indicating your access has been denied.</span>"
if (!conf_access.len) return TRUE
conf_access = null if("lock")
locked = 1
. = TRUE
/obj/item/weapon/airlock_electronics/secure /obj/item/weapon/airlock_electronics/secure
name = "secure airlock electronics" name = "secure airlock electronics"
desc = "designed to be somewhat more resistant to hacking than standard electronics." desc = "designed to be somewhat more resistant to hacking than standard electronics."
origin_tech = list(TECH_DATA = 2) origin_tech = list(TECH_DATA = 2)
secure = 1 secure = 1

View File

@@ -53,7 +53,6 @@
return return
return return
//Main door timer loop, if it's timing and time is >0 reduce time by 1. //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 // if it's less than 0, open door, reset timer
// update the door_timer window and the icon // update the door_timer window and the icon
@@ -68,12 +67,10 @@
if(timeleft > 1e5) if(timeleft > 1e5)
src.releasetime = 0 src.releasetime = 0
if(world.timeofday > src.releasetime) if(world.timeofday > src.releasetime)
src.timer_end() // open doors, reset timer, clear status screen src.timer_end() // open doors, reset timer, clear status screen
src.timing = 0 src.timing = 0
src.updateUsrDialog()
src.update_icon() src.update_icon()
else else
@@ -81,14 +78,12 @@
return return
// has the door power situation changed, if so update icon. // has the door power situation changed, if so update icon.
/obj/machinery/door_timer/power_change() /obj/machinery/door_timer/power_change()
..() ..()
update_icon() update_icon()
return return
// open/closedoor checks if door_timer has power, if so it checks if the // 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. // linked door is open/closed (by density) then opens it/closes it.
@@ -99,6 +94,9 @@
// Set releasetime // Set releasetime
releasetime = world.timeofday + timetoset releasetime = world.timeofday + timetoset
//set timing
timing = 1
for(var/obj/machinery/door/window/brigdoor/door in targets) for(var/obj/machinery/door/window/brigdoor/door in targets)
if(door.density) continue if(door.density) continue
spawn(0) spawn(0)
@@ -111,7 +109,6 @@
C.icon_state = C.icon_locked C.icon_state = C.icon_locked
return 1 return 1
// Opens and unlocks doors, power check // Opens and unlocks doors, power check
/obj/machinery/door_timer/proc/timer_end() /obj/machinery/door_timer/proc/timer_end()
if(stat & (NOPOWER|BROKEN)) return 0 if(stat & (NOPOWER|BROKEN)) return 0
@@ -119,6 +116,9 @@
// Reset releasetime // Reset releasetime
releasetime = 0 releasetime = 0
//reset timing
timing = 0
for(var/obj/machinery/door/window/brigdoor/door in targets) for(var/obj/machinery/door/window/brigdoor/door in targets)
if(!door.density) continue if(!door.density) continue
spawn(0) spawn(0)
@@ -132,7 +132,6 @@
return 1 return 1
// Check for releasetime timeleft // Check for releasetime timeleft
/obj/machinery/door_timer/proc/timeleft() /obj/machinery/door_timer/proc/timeleft()
. = (releasetime - world.timeofday)/10 . = (releasetime - world.timeofday)/10
@@ -152,116 +151,62 @@
/obj/machinery/door_timer/attack_ai(var/mob/user as mob) /obj/machinery/door_timer/attack_ai(var/mob/user as mob)
return src.attack_hand(user) 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) /obj/machinery/door_timer/attack_hand(var/mob/user as mob)
if(..()) tg_ui_interact(user)
return
// Used for the 'time left' display /obj/machinery/door_timer/ui_data(mob/user)
var/second = round(timeleft() % 60) var/list/data = list()
var/minute = round((timeleft() - second) / 60)
// Used for 'set timer' data["timing"] = timing
var/setsecond = round((timetoset / 10) % 60) data["releasetime"] = releasetime
var/setminute = round(((timetoset / 10) - setsecond) / 60) data["timetoset"] = timetoset
data["timeleft"] = timeleft()
user.set_machine(src) var/list/flashes = list()
// dat for(var/obj/machinery/flasher/flash in targets)
var/dat = "<HTML><BODY><TT>" var/list/flashdata = list()
if(flash.last_flash && (flash.last_flash + 150) > world.time)
dat += "<HR>Timer System:</hr>" flashdata["status"] = 0
dat += " <b>Door [src.id] controls</b><br/>"
// Start/Stop timer
if (src.timing)
dat += "<a href='?src=\ref[src];timing=0'>Stop Timer and open door</a><br/>"
else
dat += "<a href='?src=\ref[src];timing=1'>Activate Timer and close door</a><br/>"
// Time Left display (uses releasetime)
dat += "Time Left: [(minute ? text("[minute]:") : null)][second] <br/>"
dat += "<br/>"
// Set Timer display (uses timetoset)
if(src.timing)
dat += "Set Timer: [(setminute ? text("[setminute]:") : null)][setsecond] <a href='?src=\ref[src];change=1'>Set</a><br/>"
else
dat += "Set Timer: [(setminute ? text("[setminute]:") : null)][setsecond]<br/>"
// Controls
dat += "<a href='?src=\ref[src];tp=-60'>-</a> <a href='?src=\ref[src];tp=-1'>-</a> <a href='?src=\ref[src];tp=1'>+</a> <A href='?src=\ref[src];tp=60'>+</a><br/>"
// Mounted flash controls
for(var/obj/machinery/flasher/F in targets)
if(F.last_flash && (F.last_flash + 150) > world.time)
dat += "<br/><A href='?src=\ref[src];fc=1'>Flash Charging</A>"
else else
dat += "<br/><A href='?src=\ref[src];fc=1'>Activate Flash</A>" flashdata["status"] = 1
flashes[++flashes.len] = flashdata
dat += "<br/><br/><a href='?src=\ref[user];mach_close=computer'>Close</a>" data["flashes"] = flashes
dat += "</TT></BODY></HTML>" return data
user << browse(dat, "window=computer;size=400x500") /obj/machinery/door_timer/ui_act(action, params)
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)
if(..()) if(..())
return return TRUE
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()
src.add_fingerprint(usr) 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() src.update_icon()
return TRUE
/* if(src.timing) /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)
src.timer_start() ui = tgui_process.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
else ui = new(user, src, ui_key, "brig_timer", name , 300, 150, master_ui, state)
src.timer_end() */ ui.open()
return
//icon update function //icon update function
// if NOPOWER, display blank // if NOPOWER, display blank
@@ -282,7 +227,9 @@
disp2 = "Error" disp2 = "Error"
update_display(disp1, disp2) update_display(disp1, disp2)
else else
if(maptext) maptext = "" if(maptext)
maptext = ""
update_display("Set","Time") // would be nice to have some default printed text
return return

View File

@@ -31,7 +31,6 @@
var/close_door_at = 0 //When to automatically close the door, if possible var/close_door_at = 0 //When to automatically close the door, if possible
//Multi-tile doors //Multi-tile doors
dir = EAST
var/width = 1 var/width = 1
// turf animation // turf animation

View File

@@ -1,6 +1,7 @@
//Terribly sorry for the code doubling, but things go derpy otherwise. //Terribly sorry for the code doubling, but things go derpy otherwise.
/obj/machinery/door/airlock/multi_tile /obj/machinery/door/airlock/multi_tile
width = 2 width = 2
dir = EAST
/obj/machinery/door/airlock/multi_tile/New() /obj/machinery/door/airlock/multi_tile/New()
..() ..()

View File

@@ -8,7 +8,7 @@ datum/track/New(var/title_name, var/audio)
title = title_name title = title_name
sound = audio sound = audio
/obj/machinery/media/jukebox/ /obj/machinery/media/jukebox
name = "space jukebox" name = "space jukebox"
icon = 'icons/obj/jukebox.dmi' icon = 'icons/obj/jukebox.dmi'
icon_state = "jukebox2-nopower" 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/weapon/stock_parts/console_screen(src)
component_parts += new /obj/item/stack/cable_coil(src, 5) component_parts += new /obj/item/stack/cable_coil(src, 5)
RefreshParts() RefreshParts()
update_icon()
/obj/machinery/media/jukebox/Destroy() /obj/machinery/media/jukebox/Destroy()
StopPlaying() StopPlaying()
..() . = ..()
/obj/machinery/media/jukebox/power_change() /obj/machinery/media/jukebox/power_change()
if(!powered(power_channel) || !anchored) if(!powered(power_channel) || !anchored)
@@ -74,10 +75,7 @@ datum/track/New(var/title_name, var/audio)
else else
overlays += "[state_base]-running" overlays += "[state_base]-running"
/obj/machinery/media/jukebox/Topic(href, href_list) /obj/machinery/media/jukebox/interact(mob/user)
if(..() || !(Adjacent(usr) || istype(usr, /mob/living/silicon)))
return
if(!anchored) if(!anchored)
usr << "<span class='warning'>You must secure \the [src] first.</span>" usr << "<span class='warning'>You must secure \the [src] first.</span>"
return return
@@ -86,70 +84,74 @@ datum/track/New(var/title_name, var/audio)
usr << "\The [src] doesn't appear to function." usr << "\The [src] doesn't appear to function."
return return
if(href_list["change_track"]) tg_ui_interact(user)
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()
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) /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)
if(stat & (NOPOWER|BROKEN)) ui = tgui_process.try_update_ui(user, src, ui_key, ui, force_open)
usr << "\The [src] doesn't appear to function." if(!ui)
return ui = new(user, src, ui_key, "jukebox", "RetroBox - Space Style", 340, 440, master_ui, state)
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
ui.open() 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) /obj/machinery/media/jukebox/attack_ai(mob/user as mob)
return src.attack_hand(user) 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) /obj/machinery/media/jukebox/attackby(obj/item/W as obj, mob/user as mob)
src.add_fingerprint(user) 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(istype(W, /obj/item/weapon/wrench))
if(playing) if(playing)
StopPlaying() StopPlaying()
@@ -200,7 +198,6 @@ datum/track/New(var/title_name, var/audio)
// Always kill the current sound // Always kill the current sound
for(var/mob/living/M in mobs_in_area(main_area)) for(var/mob/living/M in mobs_in_area(main_area))
M << sound(null, channel = 1) M << sound(null, channel = 1)
main_area.forced_ambience = null main_area.forced_ambience = null
playing = 0 playing = 0
update_use_power(1) update_use_power(1)
@@ -220,4 +217,4 @@ datum/track/New(var/title_name, var/audio)
playing = 1 playing = 1
update_use_power(2) update_use_power(2)
update_icon() update_icon()

View File

@@ -90,6 +90,8 @@
/datum/feed_network/proc/insert_message_in_channel(var/datum/feed_channel/FC, var/datum/feed_message/newMsg) /datum/feed_network/proc/insert_message_in_channel(var/datum/feed_channel/FC, var/datum/feed_message/newMsg)
FC.messages += 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 newMsg.parent_channel = FC
FC.update() FC.update()
alert_readers(FC.announcement) alert_readers(FC.announcement)
@@ -377,11 +379,12 @@ var/list/obj/machinery/newscaster/allCasters = list() //Global list that will co
else else
var/i = 0 var/i = 0
for(var/datum/feed_message/MESSAGE in src.viewing_channel.messages) for(var/datum/feed_message/MESSAGE in src.viewing_channel.messages)
i++ ++i
dat+="-[MESSAGE.body] <BR>" dat+="-[MESSAGE.body] <BR>"
if(MESSAGE.img) if(MESSAGE.img)
usr << browse_rsc(MESSAGE.img, "tmp_photo[i].png") var/resourc_name = "newscaster_photo_[sanitize(viewing_channel.channel_name)]_[i].png"
dat+="<img src='tmp_photo[i].png' width = '180'><BR>" send_asset(usr.client, resourc_name)
dat+="<img src='[resourc_name]' width = '180'><BR>"
if(MESSAGE.caption) if(MESSAGE.caption)
dat+="<FONT SIZE=1><B>[MESSAGE.caption]</B></FONT><BR>" dat+="<FONT SIZE=1><B>[MESSAGE.caption]</B></FONT><BR>"
dat+="<BR>" dat+="<BR>"
@@ -874,11 +877,12 @@ obj/item/weapon/newspaper/attack_self(mob/user as mob)
dat+="<ul>" dat+="<ul>"
var/i = 0 var/i = 0
for(var/datum/feed_message/MESSAGE in C.messages) for(var/datum/feed_message/MESSAGE in C.messages)
i++ ++i
dat+="-[MESSAGE.body] <BR>" dat+="-[MESSAGE.body] <BR>"
if(MESSAGE.img) if(MESSAGE.img)
user << browse_rsc(MESSAGE.img, "tmp_photo[i].png") var/resourc_name = "newscaster_photo_[sanitize(C.channel_name)]_[i].png"
dat+="<img src='tmp_photo[i].png' width = '180'><BR>" send_asset(user.client, resourc_name)
dat+="<img src='[resourc_name]' width = '180'><BR>"
dat+="<FONT SIZE=1>\[[MESSAGE.message_type] by <FONT COLOR='maroon'>[MESSAGE.author]</FONT>\]</FONT><BR><BR>" dat+="<FONT SIZE=1>\[[MESSAGE.message_type] by <FONT COLOR='maroon'>[MESSAGE.author]</FONT>\]</FONT><BR><BR>"
dat+="</ul>" dat+="</ul>"
if(scribble_page==curr_page) if(scribble_page==curr_page)

View File

@@ -620,9 +620,9 @@ var/global/list/obj/item/device/pda/PDAs = list()
if(!FC.censored) if(!FC.censored)
var/index = 0 var/index = 0
for(var/datum/feed_message/FM in FC.messages) for(var/datum/feed_message/FM in FC.messages)
index++ ++index
if(FM.img) 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 // News stories are HTML-stripped but require newline replacement to be properly displayed in NanoUI
var/body = replacetext(FM.body, "\n", "<br>") var/body = replacetext(FM.body, "\n", "<br>")
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) 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 //NOTE: graphic resources are loaded on client login
/obj/item/device/pda/attack_self(mob/user as mob) /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) user.set_machine(src)

View File

@@ -115,6 +115,7 @@
/obj/attack_ghost(mob/user) /obj/attack_ghost(mob/user)
ui_interact(user) ui_interact(user)
tg_ui_interact(user)
..() ..()
/obj/proc/interact(mob/user) /obj/proc/interact(mob/user)

View File

@@ -125,7 +125,7 @@
return return
if("marked datum") if("marked datum")
current = holder.marked_datum current = holder.marked_datum()
if(!current) if(!current)
switch(alert("You do not currently have a marked datum; do you want to pass null instead?",, "Yes", "Cancel")) switch(alert("You do not currently have a marked datum; do you want to pass null instead?",, "Yes", "Cancel"))
if("Yes") if("Yes")

View File

@@ -6,13 +6,17 @@ var/list/admin_datums = list()
var/rights = 0 var/rights = 0
var/fakekey = null 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/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_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/datum/feed_channel/admincaster_feed_channel = new /datum/feed_channel
var/admincaster_signature //What you'll sign the newsfeeds as 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) /datum/admins/New(initial_rank = "Temporary Admin", initial_rights = 0, ckey)
if(!ckey) if(!ckey)
error("Admin datum created without a ckey argument. Datum has been deleted") error("Admin datum created without a ckey argument. Datum has been deleted")

View File

@@ -1603,6 +1603,7 @@
where = "onfloor" where = "onfloor"
if ( where == "inmarked" ) if ( where == "inmarked" )
var/marked_datum = marked_datum()
if ( !marked_datum ) if ( !marked_datum )
usr << "You don't have any object marked. Abandoning spawn." usr << "You don't have any object marked. Abandoning spawn."
return return
@@ -1620,7 +1621,7 @@
if ("relative") if ("relative")
target = locate(loc.x + X,loc.y + Y,loc.z + Z) target = locate(loc.x + X,loc.y + Y,loc.z + Z)
if ( "inmarked" ) if ( "inmarked" )
target = marked_datum target = marked_datum()
if(target) if(target)
for (var/path in paths) for (var/path in paths)

View File

@@ -27,20 +27,20 @@ var/list/VVckey_edit = list("key", "ckey")
src.modify_variables(ticker) 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! 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" var/class = "text"
if(src.holder && src.holder.marked_datum) var/list/class_input = list("text","num","type","reference","mob reference", "icon","file","list","edit referenced object","restore to default")
class = input("What kind of variable?","Variable Type") as null|anything in list("text", if(src.holder)
"num","type","reference","mob reference", "icon","file","list","edit referenced object","restore to default","marked datum ([holder.marked_datum.type])") var/datum/marked_datum = holder.marked_datum()
else if(marked_datum)
class = input("What kind of variable?","Variable Type") as null|anything in list("text", class_input += "marked datum ([marked_datum.type])"
"num","type","reference","mob reference", "icon","file","list","edit referenced object","restore to default")
class = input("What kind of variable?","Variable Type") as null|anything in class_input
if(!class) if(!class)
return 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" class = "marked datum"
var/var_value = null var/var_value = null
@@ -69,7 +69,7 @@ var/list/VVckey_edit = list("key", "ckey")
var_value = input("Pick icon:","Icon") as null|icon var_value = input("Pick icon:","Icon") as null|icon
if("marked datum") if("marked datum")
var_value = holder.marked_datum var_value = holder.marked_datum()
if(!var_value) return 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) /client/proc/mod_list_add(var/list/L, atom/O, original_name, objectvar)
var/class = "text" var/class = "text"
if(src.holder && src.holder.marked_datum) var/list/class_input = list("text","num","type","reference","mob reference", "icon","file","list","edit referenced object","restore to default")
class = input("What kind of variable?","Variable Type") as null|anything in list("text", if(src.holder)
"num","type","reference","mob reference", "icon","file","list","edit referenced object","restore to default","marked datum ([holder.marked_datum.type])") var/datum/marked_datum = holder.marked_datum()
else if(marked_datum)
class = input("What kind of variable?","Variable Type") as null|anything in list("text", class_input += "marked datum ([marked_datum.type])"
"num","type","reference","mob reference", "icon","file","list","edit referenced object","restore to default")
class = input("What kind of variable?","Variable Type") as null|anything in class_input
if(!class) if(!class)
return 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" class = "marked datum"
var/var_value = null var/var_value = null
@@ -118,7 +119,7 @@ var/list/VVckey_edit = list("key", "ckey")
var_value = input("Pick icon:","Icon") as icon var_value = input("Pick icon:","Icon") as icon
if("marked datum") if("marked datum")
var_value = holder.marked_datum var_value = holder.marked_datum()
if(!var_value) return if(!var_value) return
@@ -244,17 +245,21 @@ var/list/VVckey_edit = list("key", "ckey")
usr << "If a direction, direction is: [dir]" usr << "If a direction, direction is: [dir]"
var/class = "text" var/class = "text"
if(src.holder && src.holder.marked_datum) var/list/class_input = list("text","num","type","reference","mob reference", "icon","file","list","edit referenced object","restore to default")
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") if(src.holder)
else var/datum/marked_datum = holder.marked_datum()
class = input("What kind of variable?","Variable Type",default) as null|anything in list("text", if(marked_datum)
"num","type","reference","mob reference", "icon","file","list","edit referenced object","restore to default", "DELETE FROM LIST") 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) if(!class)
return 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" class = "marked datum"
var/original_var var/original_var
@@ -336,7 +341,9 @@ var/list/VVckey_edit = list("key", "ckey")
L[L.Find(variable)] = new_var L[L.Find(variable)] = new_var
if("marked datum") if("marked datum")
new_var = holder.marked_datum new_var = holder.marked_datum()
if(!new_var)
return
if(assoc) if(assoc)
L[assoc_key] = new_var L[assoc_key] = new_var
else else
@@ -501,12 +508,12 @@ var/list/VVckey_edit = list("key", "ckey")
if(dir) if(dir)
usr << "If a direction, direction is: [dir]" usr << "If a direction, direction is: [dir]"
if(src.holder && src.holder.marked_datum) var/list/class_input = list("text","num","type","reference","mob reference", "icon","file","list","edit referenced object","restore to default")
class = input("What kind of variable?","Variable Type",default) as null|anything in list("text", if(src.holder)
"num","type","reference","mob reference", "icon","file","list","edit referenced object","restore to default","marked datum ([holder.marked_datum.type])") var/datum/marked_datum = holder.marked_datum()
else if(marked_datum)
class = input("What kind of variable?","Variable Type",default) as null|anything in list("text", class_input += "marked datum ([marked_datum.type])"
"num","type","reference","mob reference", "icon","file","list","edit referenced object","restore to default") class = input("What kind of variable?","Variable Type",default) as null|anything in class_input
if(!class) if(!class)
return return
@@ -518,7 +525,8 @@ var/list/VVckey_edit = list("key", "ckey")
else else
original_name = O:name 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" class = "marked datum"
switch(class) switch(class)
@@ -584,7 +592,7 @@ var/list/VVckey_edit = list("key", "ckey")
O.vars[variable] = var_new O.vars[variable] = var_new
if("marked datum") 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]]")]" 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]]") log_admin("[key_name(src)] modified [original_name]'s [variable] to [O.vars[variable]]")

View File

@@ -223,7 +223,7 @@
usr << "This can only be done to instances of type /datum" usr << "This can only be done to instances of type /datum"
return return
src.holder.marked_datum = D src.holder.marked_datum_weak = weakref(D)
href_list["datumrefresh"] = href_list["mark_object"] href_list["datumrefresh"] = href_list["mark_object"]
else if(href_list["rotatedatum"]) else if(href_list["rotatedatum"])
@@ -476,7 +476,10 @@
usr << "This can only be done on mobs with clients" usr << "This can only be done on mobs with clients"
return 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" usr << "Resource files sent"
H << "Your NanoUI Resource files have been refreshed" H << "Your NanoUI Resource files have been refreshed"

View File

@@ -45,7 +45,7 @@
</tr></table> </tr></table>
<div align='center'> <div align='center'>
<b><font size='1'>[replacetext("[D.type]", "/", "/<wbr>")]</font></b> <b><font size='1'>[replacetext("[D.type]", "/", "/<wbr>")]</font></b>
[holder.marked_datum == D ? "<br/><font size='1' color='red'><b>Marked Object</b></font>" : ""] [holder.marked_datum() == D ? "<br/><font size='1' color='red'><b>Marked Object</b></font>" : ""]
</div> </div>
</td> </td>
<td width='50%'> <td width='50%'>

View File

@@ -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-law.png" = 'icons/paper_icons/large_stamp-law.png',
"large_stamp-cent.png" = 'icons/paper_icons/large_stamp-cent.png', "large_stamp-cent.png" = 'icons/paper_icons/large_stamp-cent.png',
"talisman.png" = 'icons/paper_icons/talisman.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' "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 /datum/asset/nanoui
var/list/common = list() var/list/common = list()
@@ -239,4 +274,4 @@ proc/getFilesSlow(var/client/client, var/list/files, var/register_asset = TRUE)
uncommon = list(uncommon) uncommon = list(uncommon)
send_asset_list(client, uncommon) send_asset_list(client, uncommon)
send_asset_list(client, common) send_asset_list(client, common)

View File

@@ -150,6 +150,18 @@ var/list/_client_preferences_by_type
enabled_description = "Fancy" enabled_description = "Fancy"
disabled_description = "Plain" 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 * * Staff Preferences *
********************/ ********************/

View File

@@ -798,6 +798,9 @@
return 0 return 0
return 1 return 1
/obj/item/weapon/rig/check_access(obj/item/I)
return TRUE
/obj/item/weapon/rig/proc/force_rest(var/mob/user) /obj/item/weapon/rig/proc/force_rest(var/mob/user)
if(!ai_can_move_suit(user, check_user_module = 1)) if(!ai_can_move_suit(user, check_user_module = 1))
return return

View File

@@ -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() /mob/observer/dead/canface()
return 1 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) return check_rights(R_ADMIN, 0, src)
/mob/observer/dead/verb/toggle_ghostsee() /mob/observer/dead/verb/toggle_ghostsee()

View File

@@ -1,5 +1,6 @@
/mob/Logout() /mob/Logout()
nanomanager.user_logout(src) // this is used to clean up (remove) this user's Nano UIs nanomanager.user_logout(src) // this is used to clean up (remove) this user's Nano UIs
tgui_process.on_logout(src)
player_list -= src player_list -= src
log_access("Logout: [key_name(src)]") log_access("Logout: [key_name(src)]")
if(admin_datums[src.ckey]) if(admin_datums[src.ckey])

View File

@@ -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) /mob/proc/flash_eyes(intensity = FLASH_PROTECTION_MODERATE, override_blindness_check = FALSE, affect_silicon = FALSE, visual = FALSE, type = /obj/screen/fullscreen/flash)
return 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

View File

@@ -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))

View File

@@ -1,6 +1,6 @@
/datum/nano_module /datum/nano_module
var/name var/name
var/host var/datum/host
/datum/nano_module/New(var/host) /datum/nano_module/New(var/host)
src.host = host src.host = host

View File

@@ -57,22 +57,21 @@
world.log << "NanoMapGen: <B>GENERATE MAP ([startX],[startY],[currentZ]) to ([endX],[endY],[currentZ])</B>" world.log << "NanoMapGen: <B>GENERATE MAP ([startX],[startY],[currentZ]) to ([endX],[endY],[currentZ])</B>"
usr << "NanoMapGen: <B>GENERATE MAP ([startX],[startY],[currentZ]) to ([endX],[endY],[currentZ])</B>" usr << "NanoMapGen: <B>GENERATE MAP ([startX],[startY],[currentZ]) to ([endX],[endY],[currentZ])</B>"
var/count = 0;
for(var/WorldX = startX, WorldX <= endX, WorldX++) for(var/WorldX = startX, WorldX <= endX, WorldX++)
for(var/WorldY = startY, WorldY <= endY, WorldY++) 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) 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))
TurfIcon.Scale(NANOMAP_ICON_SIZE, NANOMAP_ICON_SIZE)
Tile.Blend(TurfIcon, ICON_OVERLAY, ((WorldX - 1) * NANOMAP_ICON_SIZE), ((WorldY - 1) * NANOMAP_ICON_SIZE)) CHECK_TICK
count++
if (count % 8000 == 0)
world.log << "NanoMapGen: <B>[count] tiles done</B>"
sleep(1)
var/mapFilename = "nanomap_z[currentZ]-new.png" var/mapFilename = "nanomap_z[currentZ]-new.png"

View File

@@ -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()

View File

@@ -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.

110
code/modules/tgui/states.dm Normal file
View File

@@ -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 ..()

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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 ..()

View File

@@ -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

View File

@@ -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)

View File

@@ -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.

View File

@@ -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.

View File

@@ -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)

View File

@@ -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

369
code/modules/tgui/tgui.dm Normal file
View File

@@ -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)

View File

@@ -3504,6 +3504,120 @@ div.resize
{ {
background-position: -112px -128px; 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 ul
{ {
margin: 0; margin: 0;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 775 KiB

After

Width:  |  Height:  |  Size: 313 KiB

BIN
nano/images/nanomap_z3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
nano/images/nanomap_z4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

BIN
nano/images/nanomap_z5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

View File

@@ -59,4 +59,44 @@
/* Security Positions */ /* Security Positions */
.mapIcon16.rank-warden { background-position: -112px -128px; } .mapIcon16.rank-warden { background-position: -112px -128px; }
.mapIcon16.rank-detective { background-position: -112px -128px; } .mapIcon16.rank-detective { background-position: -112px -128px; }
.mapIcon16.rank-securityofficer { background-position: -112px -128px; } .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; }

View File

@@ -69,8 +69,8 @@
{{:helper.link('Create bottle (60 units max)', null, {'createbottle' : 1})}} {{:helper.link('Create bottle (60 units max)', null, {'createbottle' : 1})}}
</div> </div>
<div class='item'> <div class='item'>
{{:helper.link('', 'pill pill' + data.pillSprite, {'tab_select' : 'pill'}, null, 'link32')}} {{:helper.link("<div class='pillIcon pill" + data.pillSprite + "'></div>", null, {'tab_select' : 'pill'}, null, 'link pillIcon32')}}
{{:helper.link('', 'pill bottle' + data.bottleSprite, {'tab_select' : 'bottle'}, null, 'link32')}} {{:helper.link("<div class='pillIcon bottle" + data.bottleSprite + "'></div>", null, {'tab_select' : 'bottle'}, null, 'link pillIcon32')}}
</div> </div>
{{/if}} {{/if}}
{{/if}} {{/if}}
@@ -104,13 +104,13 @@
{{else data.tab == 'pill'}} {{else data.tab == 'pill'}}
{{for data.pillSpritesAmount}} {{for data.pillSpritesAmount}}
{{:helper.link('', 'pill pill' + value, {'pill_sprite' : value}, null, data.pillSprite == value ? 'linkOn link32' : 'link32')}} {{:helper.link("<div class='pillIcon pill" + value + "'></div>", null, {'pill_sprite' : value}, null, data.pillSprite == value ? 'linkOn pillIcon32' : 'link pillIcon32')}}
{{/for}} {{/for}}
<div class='item'><br>{{:helper.link('Return', 'arrowreturn-1-w', {'tab_select' : 'home'})}}</div> <div class='item'><br>{{:helper.link('Return', 'arrowreturn-1-w', {'tab_select' : 'home'})}}</div>
{{else data.tab == 'bottle'}} {{else data.tab == 'bottle'}}
{{for data.bottleSpritesAmount}} {{for data.bottleSpritesAmount}}
{{:helper.link('', 'pill bottle' + value, {'bottle_sprite' : value}, null, data.bottleSprite == value ? 'linkOn link32' : 'link32')}} {{:helper.link("<div class='pillIcon bottle" + value + "'></div>", null, {'bottle_sprite' : value}, null, data.bottleSprite == value ? 'linkOn pillIcon32' : 'link pillIcon32')}}
{{/for}} {{/for}}
<div class='item'><br>{{:helper.link('Return', 'arrowreturn-1-w', {'tab_select' : 'home'})}}</div> <div class='item'><br>{{:helper.link('Return', 'arrowreturn-1-w', {'tab_select' : 'home'})}}</div>
{{/if}} {{/if}}

View File

@@ -2,7 +2,7 @@
Title: Crew Monitoring Console (Main content) Title: Crew Monitoring Console (Main content)
Used In File(s): \code\game\machinery\computer\crew.dm Used In File(s): \code\game\machinery\computer\crew.dm
--> -->
<style type="text/css"> <style type="text/css">
table.wideTable { table.wideTable {
width:100%; width:100%;
} }

View File

@@ -892,7 +892,7 @@ Used In File(s): \code\game\objects\items\devices\PDA\PDA.dm
{{for data.feed.messages}} {{for data.feed.messages}}
-{{:value.body}}<br> -{{:value.body}}<br>
{{if value.has_image}} {{if value.has_image}}
<img src='pda_news_tmp_photo_{{:data.feed.channel}}_{{:value.index}}.png' width = '180'><br> <img src='newscaster_photo_{{:data.feed.channel}}_{{:value.index}}.png' width = '180'><br>
{{if value.caption}} {{if value.caption}}
<span class="caption">{{:value.caption}}</span><br> <span class="caption">{{:value.caption}}</span><br>
{{/if}} {{/if}}

View File

@@ -4,7 +4,7 @@ Used In File(s): \code\game\machinery\computer\camera.dm
--> -->
{{for data.cameras}} {{for data.cameras}}
{{if value.z == 1}} {{if value.z == 1}}
<div class="mapIcon mapIcon16" style="left: {{:(value.x)}}px; bottom: {{:(value.y - 14.75)}}px;"> <div class="mapIcon mapIcon16" style="left: {{:(value.x - 9.75)}}px; bottom: {{:(value.y - 14.25)}}px;">
{{if data.current && value.name == data.current.name}} {{if data.current && value.name == data.current.name}}
{{:helper.link("#", '', {'switch_camera' : value.camera}, 'selected')}} {{:helper.link("#", '', {'switch_camera' : value.camera}, 'selected')}}
{{else value.deact}} {{else value.deact}}

View File

@@ -36,6 +36,8 @@
#include "code\__defines\research.dm" #include "code\__defines\research.dm"
#include "code\__defines\species_languages.dm" #include "code\__defines\species_languages.dm"
#include "code\__defines\targeting.dm" #include "code\__defines\targeting.dm"
#include "code\__defines\tgui.dm"
#include "code\__defines\tick.dm"
#include "code\__defines\turfs.dm" #include "code\__defines\turfs.dm"
#include "code\__defines\unit_tests.dm" #include "code\__defines\unit_tests.dm"
#include "code\_compatibility\509\_JSON.dm" #include "code\_compatibility\509\_JSON.dm"
@@ -45,6 +47,7 @@
#include "code\_compatibility\509\type2type.dm" #include "code\_compatibility\509\type2type.dm"
#include "code\_helpers\_global_objects.dm" #include "code\_helpers\_global_objects.dm"
#include "code\_helpers\atmospherics.dm" #include "code\_helpers\atmospherics.dm"
#include "code\_helpers\atom_movables.dm"
#include "code\_helpers\datum_pool.dm" #include "code\_helpers\datum_pool.dm"
#include "code\_helpers\files.dm" #include "code\_helpers\files.dm"
#include "code\_helpers\game.dm" #include "code\_helpers\game.dm"
@@ -149,6 +152,7 @@
#include "code\controllers\Processes\Shuttle.dm" #include "code\controllers\Processes\Shuttle.dm"
#include "code\controllers\Processes\sun.dm" #include "code\controllers\Processes\sun.dm"
#include "code\controllers\Processes\supply.dm" #include "code\controllers\Processes\supply.dm"
#include "code\controllers\Processes\tgui.dm"
#include "code\controllers\Processes\ticker.dm" #include "code\controllers\Processes\ticker.dm"
#include "code\controllers\Processes\turf.dm" #include "code\controllers\Processes\turf.dm"
#include "code\controllers\Processes\vote.dm" #include "code\controllers\Processes\vote.dm"
@@ -170,6 +174,7 @@
#include "code\datums\progressbar.dm" #include "code\datums\progressbar.dm"
#include "code\datums\recipe.dm" #include "code\datums\recipe.dm"
#include "code\datums\sun.dm" #include "code\datums\sun.dm"
#include "code\datums\weakref.dm"
#include "code\datums\autolathe\arms.dm" #include "code\datums\autolathe\arms.dm"
#include "code\datums\autolathe\autolathe.dm" #include "code\datums\autolathe\autolathe.dm"
#include "code\datums\autolathe\devices.dm" #include "code\datums\autolathe\devices.dm"
@@ -1659,6 +1664,7 @@
#include "code\modules\nano\interaction\conscious.dm" #include "code\modules\nano\interaction\conscious.dm"
#include "code\modules\nano\interaction\contained.dm" #include "code\modules\nano\interaction\contained.dm"
#include "code\modules\nano\interaction\default.dm" #include "code\modules\nano\interaction\default.dm"
#include "code\modules\nano\interaction\hands.dm"
#include "code\modules\nano\interaction\interactive.dm" #include "code\modules\nano\interaction\interactive.dm"
#include "code\modules\nano\interaction\inventory.dm" #include "code\modules\nano\interaction\inventory.dm"
#include "code\modules\nano\interaction\inventory_deep.dm" #include "code\modules\nano\interaction\inventory_deep.dm"
@@ -2028,6 +2034,21 @@
#include "code\modules\tables\rack.dm" #include "code\modules\tables\rack.dm"
#include "code\modules\tables\tables.dm" #include "code\modules\tables\tables.dm"
#include "code\modules\tables\update_triggers.dm" #include "code\modules\tables\update_triggers.dm"
#include "code\modules\tgui\external.dm"
#include "code\modules\tgui\process.dm"
#include "code\modules\tgui\states.dm"
#include "code\modules\tgui\tgui.dm"
#include "code\modules\tgui\states\admin.dm"
#include "code\modules\tgui\states\conscious.dm"
#include "code\modules\tgui\states\contained.dm"
#include "code\modules\tgui\states\deep_inventory.dm"
#include "code\modules\tgui\states\default.dm"
#include "code\modules\tgui\states\hands.dm"
#include "code\modules\tgui\states\inventory.dm"
#include "code\modules\tgui\states\notcontained.dm"
#include "code\modules\tgui\states\physical.dm"
#include "code\modules\tgui\states\self.dm"
#include "code\modules\tgui\states\zlevel.dm"
#include "code\modules\vehicles\cargo_train.dm" #include "code\modules\vehicles\cargo_train.dm"
#include "code\modules\vehicles\train.dm" #include "code\modules\vehicles\train.dm"
#include "code\modules\vehicles\vehicle.dm" #include "code\modules\vehicles\vehicle.dm"

Binary file not shown.

Binary file not shown.

5
tgui/.babelrc Normal file
View File

@@ -0,0 +1,5 @@
{
"presets": [
"es2015"
]
}

1
tgui/.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
assets/* binary

2
tgui/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
npm-debug.log
node_modules/

18
tgui/LICENSE.md Normal file
View File

@@ -0,0 +1,18 @@
Copyright (c) 2016 Bjorn Neergaard (neersighted), tgui contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

139
tgui/README.md Normal file
View File

@@ -0,0 +1,139 @@
<!-- TOC depthFrom:1 depthTo:6 withLinks:1 updateOnSave:1 orderedList:0 -->
- [tgui](#tgui)
- [Concepts](#concepts)
- [Using It](#using-it)
- [Copypasta](#copypasta)
<!-- /TOC -->
# tgui
tgui is the user interface library of /tg/station. It is rendered clientside, based on JSON data sent from the server. Clicks are processed on the server, in a similar method to native BYOND `Topic()`.
Basic tgui consists of defining a few procs. In these procs you will handle a request to open or update a UI (typically by updating a UI if it exists or setting up and opening it if it does not), a request for data, in which you build a list to be passed as JSON to the UI, and an action handler, which handles any user input. In addition, you will write a HTML template file which renders your data and provides actionable inputs.
tgui is very different from most UIs you will encounter in BYOND programming, and is heavily reliant of Javascript and web technologies as opposed to DM. However, if you are familiar with NanoUI (a library which can be found on almost every other SS13 codebase), tgui should be fairly easy to pick up.
tgui is a fork of NanoUI. The server-side code (DM) is similar and derived from NanoUI, while the clientside is a wholly new project with no code in common.
## Concepts
tgui is loosely based a MVVM architecture. MVVM stands for model, view, view model.
- A model is the object that a UI represents. This is the atom a UI corresponds to in the game world in most cases, and is known as the `src_object` in tgui.
- The view model is how data is represented in terms of the view. In tgui, this is the `ui_data` proc which munges whatever complex data your `src_object` has into a list.
- The view is how the data is rendered. This is the template, a HTML (plus mustaches and other goodies) file which is compiled into the tgui blob that the browser executes.
Not included in the MVVM model are other important concepts:
- The action/topic handler, `ui_act`, is what recieves input from the user and acts on it.
- The request/update proc, `ui_interact` is where you open your UI and set options like title, size, autoupdate, theme, and more.
- Finally, `ui_state`s (set in `ui_interact`) dictate under what conditions a UI may be interacted with. This may be the standard checks that check if you are in range and conscious, or more.
States are easy to write and extend, and what make tgui interactions so powerful. Because states can over overridden from other procs, you can build powerful interactions for embedded objects or remote access.
## Using It
All these examples and abstracts sound great, you might say. But you also might say, "How do I use it?"
Examples can be as simple or as complex as you would like. Let's start with a very basic hello world.
```DM
/obj/machinery/my_machine/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, "my_machine", name, 300, 300, master_ui, state)
ui.open()
```
This is the proc that defines our interface. There's a bit going on here, so let's break it down. First, we override the ui_interact proc on our object. This will be called by `interact` for you, which is in turn called by `attack_hand` (or `attack_self` for items). `ui_interact` is also called to update a UI (hence the `try_update_ui`), so we accept an existing UI to update. The `state` is a default argument so that a caller can overload it with named arguments (`ui_interact(state = overloaded_state)`) if needed.
Inside the `if(!ui)` block (which means we are creating a new UI), we choose our template, title, and size; we can also set various options like `style` (for themes), or autoupdate. These options will be elaborated on later (as will `ui_state`s).
After `ui_interact`, we need to define `ui_data`. This just returns a list of data for our object to use. Let's imagine our object has a few vars:
```DM
/obj/machinery/my_machine/ui_data(mob/user)
var/list/data = list()
data["health"] = health
data["color"] = color
return data
```
The `ui_data` proc is what people often find the hardest about tgui, but its really quite simple! You just need to represent your object as numbers, strings, and lists, instead of atoms and datums.
Finally, the `ui_act` proc is called by the interface whenever the user used an input. The input's `action` and `params` are passed to the proc.
```DM
/obj/machinery/my_machine/ui_act(action, params)
if(..())
return
switch(action)
if("change_color")
var/new_color = params["color"]
if(!(color in allowed_coors))
return
color = new_color
. = TRUE
update_icon()
```
The `..()` (parent call) is very important here, as it is how we check that the user is allowed to use this interface (to avoid so-called href exploits). It is also very important to clamp and sanitize all input here. Always assume the user is attempting to exploit the game.
Also note the use of `. = TRUE` (or `FALSE`), which is used to notify the UI that this input caused an update. This is especially important for UIs that do not auto-update, as otherwise the user will never see their change.
Finally, you have a template. This is also a source of confusion for many new users. Some basic HTML knowledge will get you a long way, however.
A template is regular HTML, with mustache for logic and built-in components to quickly build UIs. Here's how we might show some data (components will be elaborated on later).
In a template there are a few special values. `config` is always the same and is part of core tgui (it will be explained later), `data` is the data returned from `ui_data`, and `adata` is the same, but with certain values (numbers at this time) interpolated in order to allow animation.
```html
<ui-display>
<ui-section label='Health'>
<span>{{data.health}}</span>
</ui-section>
<ui-section label='Color'>
<span>{{data.color}}</span>
</ui-section>
</ui-display>
```
Templates can be very confusing at first, as ternary operators, computed properties, and iterators are used quite a bit in more complex interfaces. Start with the basics, and work your way up. Much of the complexity stems from performance concerns. If in doubt, take the simpler approach and refactor if performance becomes an issue.
## Copypasta
We all do it, even the best of us. If you just want to make a tgui **fast**, here's what you need (note that you'll probably be forced to clean your shit up upon code review):
```DM
/obj/copypasta/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) // Remember to use the appropriate state.
ui = tgui_process.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "copypasta", name, 300, 300, master_ui, state)
ui.open()
/obj/copypasta/ui_data(mob/user)
var/list/data = list()
data["var"] = var
return data
/obj/copypasta/ui_act(action, params)
if(..())
return
switch(action)
if("copypasta")
var/newvar = params["var"]
var = Clamp(newvar, min_val, max_val) // Just a demo of proper input sanitation.
. = TRUE
update_icon() // Not applicable to all objects.
```
And the template:
```html
<ui-display title='My Copypasta Section'>
<ui-section label='Var'>
<span>{{data.var}}</span>
</ui-section>
<ui-section label='Animated Var'>
<span>{{adata.var}}</span>
</ui-section>
</ui-display>
```

1
tgui/assets/tgui.css Normal file

File diff suppressed because one or more lines are too long

9
tgui/assets/tgui.js Normal file

File diff suppressed because one or more lines are too long

6
tgui/build_assets.bat Normal file
View File

@@ -0,0 +1,6 @@
@echo off
echo node.js and all dependencies must be installed for this script to work.
echo If this script fails try installing dependencies again.
REM Build minified assets
cmd /c gulp --min
pause

31
tgui/gulp/css.js Normal file
View File

@@ -0,0 +1,31 @@
import * as f from './flags'
import { gulp as g, postcss as s } from './plugins'
const entry = 'tgui.styl'
import gulp from 'gulp'
export function css () {
return gulp.src(`${f.src}/${entry}`)
.pipe(g.if(f.debug, g.sourcemaps.init({loadMaps: true})))
.pipe(g.stylus({
url: 'data-url',
paths: [ f.src ]
}))
.pipe(g.postcss([
s.autoprefixer({ browsers: ['last 2 versions', 'ie >= 8'] }),
s.gradient,
s.opacity,
s.rgba({oldie: true}),
s.plsfilters({oldIE: true}),
s.fontweights
]))
.pipe(g.bytediff.start())
.pipe(g.if(f.min, g.cssnano({autoprefixer: false})))
.pipe(g.if(f.debug, g.sourcemaps.write()))
.pipe(g.bytediff.stop())
.pipe(gulp.dest(f.dest))
}
export function watch_css () {
gulp.watch(`${f.src}/**/*.styl`, css)
return css()
}

7
tgui/gulp/flags.js Normal file
View File

@@ -0,0 +1,7 @@
const flags = require('minimist')(process.argv.slice(2))
export const src = flags.src || 'src'
export const dest = flags.dest || 'assets'
export const debug = flags.debug || flags.d
export const min = flags.min || flags.m

62
tgui/gulp/js.js Normal file
View File

@@ -0,0 +1,62 @@
import * as f from './flags'
import { browserify as b, gulp as g } from './plugins'
const entry = 'tgui.js'
import { transform as babel } from 'babel-core'
import { readFileSync as read } from 'fs'
b.componentify.compilers['text/javascript'] = function (source, file) {
const config = { sourceMaps: true }
Object.assign(config, JSON.parse(read(`${f.src}/.babelrc`, 'utf8')))
const compiled = babel(source, config)
return { source: compiled.code, map: compiled.map }
}
import { render as stylus } from 'stylus'
b.componentify.compilers['text/stylus'] = function (source, file) {
const config = { filename: file }
const compiled = stylus(source, config)
return { source: compiled }
}
import browserify from 'browserify'
const bundle = browserify(`${f.src}/${entry}`, {
debug: f.debug,
cache: {},
packageCache: {},
extensions: [ '.js', '.ract' ],
paths: [ f.src ]
})
if (f.min) bundle.plugin(b.collapse)
bundle
.transform(b.babelify)
.plugin(b.helpers)
.transform(b.componentify)
.transform(b.globify)
.transform(b.es3ify)
import buffer from 'vinyl-buffer'
import gulp from 'gulp'
import source from 'vinyl-source-stream'
export function js () {
return bundle.bundle()
.pipe(source(entry))
.pipe(buffer())
.pipe(g.if(f.debug, g.sourcemaps.init({loadMaps: true})))
.pipe(g.bytediff.start())
.pipe(g.if(f.min, g.uglify({mangle: true, compress: {unsafe: true}})))
.pipe(g.if(f.debug, g.sourcemaps.write()))
.pipe(g.bytediff.stop())
.pipe(gulp.dest(f.dest))
}
import gulplog from 'gulplog'
export function watch_js () {
bundle.plugin(b.watchify)
bundle.on('update', js)
bundle.on('error', err => {
gulplog.error(err.toString())
this.emit('end')
})
return js()
}

20
tgui/gulp/plugins.js Normal file
View File

@@ -0,0 +1,20 @@
export const browserify = {
babelify: require('babelify'),
collapse: require('bundle-collapser/plugin'),
componentify: require('ractive-componentify'),
es3ify: require('es3ify'),
globify: require('require-globify'),
helpers: require('babelify-external-helpers'),
watchify: require('watchify')
}
export const gulp = require('gulp-load-plugins')({ replaceString: /^gulp(-|\.)|-/g })
export const postcss = {
autoprefixer: require('autoprefixer'),
fontweights: require('postcss-font-weights'),
gradient: require('postcss-filter-gradient'),
opacity: require('postcss-opacity'),
plsfilters: require('pleeease-filters'),
rgba: require('postcss-color-rgba-fallback')
}

10
tgui/gulp/reload.js Normal file
View File

@@ -0,0 +1,10 @@
const out = 'assets'
import { exec } from 'child_process'
export function reload () {
return exec('reload.bat')
}
import gulp from 'gulp'
export function watch_reload () {
gulp.watch(`${out}/**`, reload)
}

9
tgui/gulp/size.js Normal file
View File

@@ -0,0 +1,9 @@
import { gulp as g } from './plugins'
const out = 'assets'
import gulp from 'gulp'
export function size () {
return gulp.src(`${out}/**`)
.pipe(g.size())
}

12
tgui/gulpfile.babel.js Normal file
View File

@@ -0,0 +1,12 @@
import gulp from 'gulp'
import { css, watch_css } from './gulp/css'
import { js, watch_js } from './gulp/js'
import { reload, watch_reload } from './gulp/reload'
import { size } from './gulp/size'
gulp.task(reload)
gulp.task(size)
gulp.task('default', gulp.series(gulp.parallel(css, js), size))
gulp.task('watch', gulp.parallel(watch_css, watch_js, watch_reload))

View File

@@ -0,0 +1,14 @@
@echo off
echo node.js 5.3.0 or newer must be installed for this script to work.
echo If this script fails, try closing editors and running it again first.
echo Any warnings about optional dependencies can be safely ignored.
pause
REM Install Gulp
cmd /c npm install gulp-cli -g
REM Install tgui dependencies
cmd /c npm install
REM Flatten dependency tree
cmd /c npm dedupe
REM Clean dependency tree
cmd /c npm prune
pause

63
tgui/package.json Normal file
View File

@@ -0,0 +1,63 @@
{
"name": "tgui",
"private": true,
"dependencies": {
"autoprefixer": "6.3.3",
"babel-core": "6.5.2",
"babel-plugin-external-helpers": "6.5.0",
"babel-polyfill": "6.5.0",
"babel-preset-es2015": "6.5.0",
"babel-preset-es2015-loose": "7.0.0",
"babel-register": "6.5.2",
"babelify": "7.2.0",
"babelify-external-helpers": "1.1.0",
"browserify": "13.0.0",
"bulkify": "1.1.1",
"bundle-collapser": "1.2.1",
"dom4": "1.7.0",
"es3ify": "0.2.1",
"fg-loadcss": "1.0.0-0",
"fontfaceobserver": "1.6.3",
"gulp": "github:gulpjs/gulp#4.0",
"gulp-bytediff": "1.0.0",
"gulp-cssnano": "2.1.1",
"gulp-if": "2.0.0",
"gulp-load-plugins": "1.2.0",
"gulp-postcss": "6.1.0",
"gulp-size": "2.0.0",
"gulp-sourcemaps": "1.6.0",
"gulp-stylus": "2.3.0",
"gulp-uglify": "1.5.2",
"gulplog": "1.0.0",
"html5shiv": "3.7.3",
"ie8": "0.3.2",
"minimist": "1.2.0",
"paths-js": "0.4.2",
"pleeease-filters": "2.0.0",
"postcss": "5.0.16",
"postcss-color-rgba-fallback": "2.2.0",
"postcss-filter-gradient": "0.2.2",
"postcss-font-weights": "2.0.1",
"postcss-opacity": "3.0.0",
"ractive": "0.7.3",
"ractive-componentify": "0.2.4",
"ractive-events-keys": "0.2.1",
"ractive-transitions-fade": "0.3.1",
"require-globify": "1.3.0",
"stylus": "0.53.0",
"vinyl-buffer": "1.0.0",
"vinyl-source-stream": "1.1.0",
"watchify": "3.7.0"
},
"browser": {
"ractive": "ractive/ractive-legacy.runtime"
},
"require-globify": {
"appliesTo": {
"includeExtensions": [
".js",
".ract"
]
}
}
}

3
tgui/reload.bat Normal file
View File

@@ -0,0 +1,3 @@
@echo off
REM Copy assets to the BYOND cache
cmd /c copy assets\* "%USERPROFILE%\Documents\BYOND\cache" /y

8
tgui/src/.babelrc Normal file
View File

@@ -0,0 +1,8 @@
{
"presets": [
"es2015-loose"
],
"plugins": [
"external-helpers"
]
}

View File

@@ -0,0 +1,51 @@
<link rel='ractive' href='./airalarm/scrubbers.ract'>
<link rel='ractive' href='./airalarm/status.ract'>
<link rel='ractive' href='./airalarm/thresholds.ract'>
<link rel='ractive' href='./airalarm/modes.ract'>
<link rel='ractive' href='./airalarm/vents.ract'>
<ui-notice>
{{#if data.siliconUser}}
<ui-section label='Interface Lock'>
<ui-button icon='{{data.locked ? "lock" : "unlock"}}' action='lock'>{{data.locked ? "Engaged" : "Disengaged"}}</ui-button>
</ui-section>
{{else}}
<span>Swipe an ID card to {{data.locked ? "unlock" : "lock"}} this interface.</span>
{{/if}}
</ui-notice>
<status/>
{{#if !data.locked || data.siliconUser}}
{{#if config.screen == "home"}}
<ui-display title='Air Controls'>
<ui-section>
<ui-button icon='{{data.atmos_alarm ? "exclamation-triangle" : "exclamation"}}' style='{{data.atmos_alarm ? "caution" : null}}'
action='{{data.atmos_alarm ? "reset" : "alarm"}}'>Area Atmosphere Alarm</ui-button>
</ui-section>
<ui-section>
<ui-button icon='{{data.mode == 3 ? "exclamation-triangle" : "exclamation"}}' style='{{data.mode == 3 ? "danger" : null}}'
action='mode' params='{"mode": {{data.mode == 3 ? 1 : 3}}}'>Panic Siphon</ui-button>
</ui-section>
<br/>
<ui-section>
<ui-button icon='sign-out' action='tgui:view' params='{"screen": "vents"}'>Vent Controls</ui-button>
</ui-section>
<ui-section>
<ui-button icon='filter' action='tgui:view' params='{"screen": "scrubbers"}'>Scrubber Controls</ui-button>
</ui-section>
<ui-section>
<ui-button icon='cog' action='tgui:view' params='{"screen": "modes"}'>Operating Mode</ui-button>
</ui-section>
<ui-section>
<ui-button icon='bar-chart' action='tgui:view' params='{"screen": "thresholds"}'>Alarm Thresholds</ui-button>
</ui-section>
</ui-display>
{{elseif config.screen == "vents"}}
<vents/>
{{elseif config.screen == "scrubbers"}}
<scrubbers/>
{{elseif config.screen == "modes"}}
<modes/>
{{elseif config.screen == "thresholds"}}
<thresholds/>
{{/if}}
{{/if}}

View File

@@ -0,0 +1 @@
<ui-button icon='arrow-left' action='tgui:view' params='{"screen": "home"}'>Back</ui-button>

View File

@@ -0,0 +1,14 @@
<link rel='ractive' href='./back.ract'>
<ui-display title='Operating Modes' button>
{{#partial button}}
<back/>
{{/partial}}
{{#each data.modes}}
<ui-section>
<ui-button icon='{{selected ? "check-square-o" : "square-o"}}'
state='{{selected ? danger ? "danger" : "selected" : null}}'
action='mode' params='{"mode": {{mode}}}'>{{name}}</ui-button>
</ui-section>
{{/each}}
</ui-display>

View File

@@ -0,0 +1,33 @@
<link rel='ractive' href='./back.ract'>
<ui-display title='Scrubber Controls' button>
{{#partial button}}
<back/>
{{/partial}}
{{#each data.scrubbers}}
<ui-subdisplay title='{{long_name}}'>
<ui-section label='Power'>
<ui-button icon='{{power ? "power-off" : "close"}}' style='{{power ? "selected" : null}}'
action='power' params='{"id_tag": "{{id_tag}}", "val": {{+!power}}}'>{{power ? "On" : "Off"}}</ui-button>
</ui-section>
<ui-section label='Mode'>
<ui-button icon='{{scrubbing ? "filter" : "sign-in"}}' style='{{scrubbing ? null : "danger"}}'
action='scrubbing' params='{"id_tag": "{{id_tag}}", "val": {{+!scrubbing}}}'>{{scrubbing ? "Scrubbing" : "Siphoning"}}</ui-button>
</ui-section>
<ui-section label='Range'>
<ui-button icon='{{widenet ? "expand" : "compress"}}' style='{{widenet ? "selected" : null}}'
action='widenet' params='{"id_tag": "{{id_tag}}", "val": {{+!widenet}}}'>{{widenet ? "Expanded" : "Normal"}}</ui-button>
</ui-section>
<ui-section label='Filters'>
<ui-button icon='{{filter_co2 ? "check-square-o" : "square-o"}}' style='{{filter_co2 ? "selected" : null}}'
action='co2_scrub' params='{"id_tag": "{{id_tag}}", "val": {{+!filter_co2}}}'>CO2</ui-button>
<ui-button icon='{{filter_n2o ? "check-square-o" : "square-o"}}' style='{{filter_n2o ? "selected" : null}}'
action='n2o_scrub' params='{"id_tag": "{{id_tag}}", "val": {{+!filter_n2o}}}'>N2O</ui-button>
<ui-button icon='{{filter_toxins ? "check-square-o" : "square-o"}}' style='{{filter_toxins ? "selected" : null}}'
action='tox_scrub' params='{"id_tag": "{{id_tag}}", "val": {{+!filter_toxins}}}'>Plasma</ui-button>
</ui-section>
</ui-subdisplay>
{{else}}
<span class='bad'>Error: No scrubbers connected.</span>
{{/each}}
</ui-display>

View File

@@ -0,0 +1,30 @@
<ui-display title='Air Status'>
{{#if data.environment_data}}
{{#each adata.environment_data}}
<ui-section label='{{name}}'>
<span class='{{danger_level == 2 ? "bad" : danger_level == 1 ? "average" : "good"}}'>
{{Math.fixed(value, 2)}}{{unit}}
</span>
</ui-section>
{{/each}}
<ui-section label='Local Status'>
<span class='{{data.danger_level == 2 ? "bad bold" : data.danger_level == 1 ? "average bold" : "good"}}'>
{{data.danger_level == 2 ? "Danger (Internals Required)" : data.danger_level == 1 ? "Caution" : "Optimal"}}
</span>
</ui-section>
<ui-section label='Area Status'>
<span class='{{data.atmos_alarm || data.fire_alarm ? "bad bold" : "good"}}'>
{{data.atmos_alarm ? "Atmosphere Alarm" : fire_alarm ? "Fire Alarm" : "Nominal"}}
</span>
</ui-section>
{{else}}
<ui-section label='Warning'>
<span class='bad bold'>Cannot obtain air sample for analysis.</span>
</ui-section>
{{/if}}
{{#if data.emagged}}
<ui-section label='Warning'>
<span class='bad bold'>Safety measures offline. Device may exhibit abnormal behavior.</span>
</ui-section>
{{/if}}
</ui-display>

View File

@@ -0,0 +1,31 @@
<link rel='ractive' href='./back.ract'>
<ui-display title='Alarm Thresholds' button>
{{#partial button}}
<back/>
{{/partial}}
<table>
<thead><tr>
<th></th>
<th><span class="bad">min2</span></th>
<th><span class="average">min1</span></th>
<th><span class="average">max1</span></th>
<th><span class="bad">max2</span></th>
</tr></thead>
<tbody>
{{#each data.thresholds}}<tr>
<th>{{{name}}}</th>
{{#each settings}}<td>
<ui-button action='threshold' params='{"env": "{{env}}", "var": "{{val}}"}'>{{Math.fixed(selected, 2)}}</ui-button>
</td>{{/each}}
</tr>{{/each}}
</tbody>
<table>
</ui-display>
<style>
th, td {
padding-right: 16px;
text-align: left;
}
</style>

View File

@@ -0,0 +1,32 @@
<link rel='ractive' href='./back.ract'>
<ui-display title='Vent Controls' button>
{{#partial button}}
<back/>
{{/partial}}
{{#each data.vents}}
<ui-subdisplay title='{{long_name}}'>
<ui-section label='Power'>
<ui-button icon='{{power ? "power-off" : "close"}}' style='{{power ? "selected" : null}}'
action='power' params='{"id_tag": "{{id_tag}}", "val": {{+!power}}}'>{{power ? "On" : "Off"}}</ui-button>
</ui-section>
<ui-section label='Mode'>
<span>{{direction == "release" ? "Pressurizing" : "Siphoning"}}</span>
</ui-section>
<ui-section label='Pressure Regulator'>
<ui-button icon='sign-in' style='{{incheck ? "selected" : null}}'
action='incheck' params='{"id_tag": "{{id_tag}}", "val": {{checks}}}'>Internal</ui-button>
<ui-button icon='sign-out' style='{{excheck ? "selected" : null}}'
action='excheck' params='{"id_tag": "{{id_tag}}", "val": {{checks}}}'>External</ui-button>
</ui-section>
<ui-section label='Target Pressure'>
<ui-button icon='pencil' action='set_external_pressure'
params='{"id_tag": "{{id_tag}}"}'>{{Math.fixed(external)}}</ui-button>
<ui-button icon='refresh' state='{{extdefault ? "disabled" : null}}' action='reset_external_pressure'
params='{"id_tag": "{{id_tag}}"}'>Reset</ui-button>
</ui-section>
</ui-subdisplay>
{{else}}
<span class='bad'>Error: No vents connected.</span>
{{/each}}
</ui-display>

View File

@@ -0,0 +1,46 @@
<ui-display>
{{#if data.locked}}
<ui-section label='Card locked!'>
Swipe ID to continue: <ui-button icon='unlock' action='unlock'>Unlock</ui-button>
</ui-section>
{{else}}
<ui-section>
<ui-button icon='{{data.oneAccess ? "unlock" : "lock"}}' action='one_access'>{{data.oneAccess ? "One" : "All"}} Required</ui-button>
<ui-button icon='refresh' action='clear'>Clear</ui-button>
<ui-button icon='lock' action='lock'>Lock</ui-button>
</ui-section>
<hr/>
<table>
<thead>
<tr>{{#each data.regions}}
<th><span class='highlight bold'>{{name}}</span></th>
{{/each}}</tr>
</thead>
<tbody>
<tr>{{#each data.regions}}
<td>{{#each accesses}}
<ui-button icon='{{req ? "check-square-o" : "square-o"}}' style='{{req ? "selected" : null}}'
action='set' params='{"access": "{{id}}"}'>{{name}}</ui-button>
<br/>
{{/each}}</td>
{{/each}}</tr>
</tbody>
</table>
{{/if}}
</ui-display>
<style>
table {
width: 100%;
border-spacing: 2px;
}
th {
text-align: left;
}
td {
vertical-align: top;
}
td .button {
margin-top: 4px
}
</style>

View File

@@ -0,0 +1,125 @@
<script>
component.exports = {
data: {
powerState (status) {
switch (status) {
case 2: return 'good'
case 1: return 'average'
default: return 'bad'
}
}
},
computed: {
malfAction () {
switch (this.get('data.malfStatus')) {
case 1: return 'hack'
case 2: return 'occupy'
case 3: return 'deoccupy'
}
},
malfButton () {
switch (this.get('data.malfStatus')) {
case 1: return 'Override Programming'
case 2:
case 4: return 'Shunt Core Process'
case 3: return 'Return to Main Core'
}
},
malfIcon () {
switch (this.get('data.malfStatus')) {
case 1: return 'terminal'
case 2:
case 4: return 'caret-square-o-down'
case 3: return 'caret-square-o-left'
}
},
powerCellStatusState () {
const status = this.get('data.powerCellStatus')
if (status > 50) return 'good'
else if (status > 25) return 'average'
else return 'bad'
}
}
}
</script>
<ui-notice>
{{#if data.siliconUser}}
<ui-section label='Interface Lock'>
<ui-button icon='{{data.locked ? "lock" : "unlock"}}' action='lock'>{{data.locked ? "Engaged" : "Disengaged"}}</ui-button>
</ui-section>
{{else}}
<span>Swipe an ID card to {{data.locked ? "unlock" : "lock"}} this interface.</span>
{{/if}}
</ui-notice>
<ui-display title='Power Status'>
<ui-section label='Main Breaker'>
{{#if data.locked && !data.siliconUser}}
<span class='{{data.isOperating ? "good" : "bad"}}'>{{data.isOperating ? "On" : "Off"}}</span>
{{else}}
<ui-button icon='{{data.isOperating ? "power-off" : "close"}}' style='{{data.isOperating ? "selected" : null}}'
action='breaker'>{{data.isOperating ? "On" : "Off"}}</ui-button>
{{/if}}
</ui-section>
<ui-section label='External Power'>
<span class='{{powerState(data.externalPower)}}'>{{data.externalPower == 2 ? "Good" : data.externalPower == 1 ? "Low" : "None"}}</span>
</ui-section>
<ui-section label='Power Cell'>
{{#if data.powerCellStatus != null}}
<ui-bar min='0' max='100' value='{{data.powerCellStatus}}' state='{{powerCellStatusState}}'>{{Math.fixed(adata.powerCellStatus)}}%</ui-bar>
{{else}}
<span class='bad'>Removed</span>
{{/if}}
</ui-section>
{{#if data.powerCellStatus != null}}
<ui-section label='Charge Mode'>
{{#if data.locked && !data.siliconUser}}
<span class='{{data.chargeMode ? "good" : "bad"}}'>{{data.chargeMode ? "Auto" : "Off"}}</span>
{{else}}
<ui-button icon='{{data.chargeMode ? "refresh" : "close"}}' style='{{data.chargeMode ? "selected" : null}}'
action='charge'>{{data.chargeMode ? "Auto" : "Off"}}</ui-button>
{{/if}}
&nbsp;
[<span class='{{powerState(data.chargingStatus)}}'>{{data.chargingStatus == 2 ? "Fully Charged" : data.chargingStatus == 1 ? "Charging" : "Not Charging"}}</span>]
</ui-section>
{{/if}}
</ui-display>
<ui-display title='Power Channels'>
{{#each data.powerChannels}}
<ui-section label='{{title}}' nowrap>
<div class='content'>{{Math.round(adata.powerChannels[@index].powerLoad)}} W</div>
<div class='content'><span class='{{status >= 2 ? "good" : "bad"}}'>{{status >= 2 ? "On" : "Off"}}</span></div>
<div class='content'>[<span>{{status == 1 || status == 3 ? "Auto" : "Manual"}}</span>]</div>
<div class='content' style='float:right'>
{{#if !data.locked || data.siliconUser}}
<ui-button icon='refresh' state='{{status == 1 || status == 3 ? "selected" : null}}'
action='channel' params='{{topicParams.auto}}'>Auto</ui-button>
<ui-button icon='power-off' state='{{status == 2 ? "selected" : null}}' action='channel'
params='{{topicParams.on}}'>On</ui-button>
<ui-button icon='close' state='{{status == 0 ? "selected" : null}}' action='channel'
params='{{topicParams.off}}'>Off</ui-button>
{{/if}}
</div>
</ui-section>
{{/each}}
<ui-section label='Total Load'>
<span class='bold'>{{Math.round(adata.totalLoad)}} W</span>
</ui-section>
</ui-display>
{{#if data.siliconUser}}
<ui-display title='System Overrides'>
<ui-button icon='lightbulb-o' action='overload'>Overload</ui-button>
{{#if data.malfStatus}}
<ui-button icon='{{malfIcon}}' state='{{data.malfStatus == 4 ? "disabled" : null}}' action='{{malfAction}}'>{{malfButton}}</ui-button>
{{/if}}
</ui-display>
{{/if}}
<ui-notice>
<ui-section label='Cover Lock'>
{{#if data.locked && !data.siliconUser}}
<span>{{data.coverLocked ? "Engaged" : "Disengaged"}}</span>
{{else}}
<ui-button icon='{{data.coverLocked ? "lock" : "unlock"}}' action='cover'>{{data.coverLocked ? "Engaged" : "Disengaged"}}</ui-button>
{{/if}}
</ui-section>
</ui-notice>

View File

@@ -0,0 +1,14 @@
<ui-display title='Alarms'>
<ul>
{{#each data.priority}}
<li><ui-button icon='close' style='danger' action='clear' params='{"zone": "{{.}}"}'>{{.}}</ui-button></li>
{{else}}
<li><span class='good'>No Priority Alerts</span></li>
{{/each}}
{{#each data.minor}}
<li><ui-button icon='close' style='caution' action='clear' params='{"zone": "{{.}}"}'>{{.}}</ui-button></li>
{{else}}
<li><span class='good'>No Minor Alerts</span></li>
{{/each}}
</ul>
</ui-display>

View File

@@ -0,0 +1,40 @@
<ui-display title='{{data.tank ? data.sensors[0].long_name : null}}'>
{{#each adata.sensors}}
<ui-subdisplay title='{{!data.tank ? long_name : null}}'>
<ui-section label='Pressure'>
<span>{{Math.fixed(pressure, 2)}} kPa</span>
</ui-section>
{{#if temperature}}
<ui-section label='Temperature'>
<span>{{Math.fixed(temperature, 2)}} K</span>
</ui-section>
{{/if}}
{{#each gases:id}}
<ui-section label='{{id}}'>
<span>{{Math.fixed(., 2)}}%</span>
</ui-section>
{{/each}}
</ui-subdisplay>
{{/each}}
</ui-display>
{{#if data.tank}}
<ui-display title='Controls' button>
{{#partial button}}
<ui-button icon='refresh' action='reconnect'>Reconnect</ui-button>
{{/partial}}
<ui-section label='Input Injector'>
<ui-button icon='{{data.inputting ? "power-off" : "close"}}' style='{{data.inputting ? "selected" : null}}' action='input'>
{{data.inputting ? "Injecting": "Off"}}</ui-button>
</ui-section>
<ui-section label='Input Rate'>
<span>{{Math.fixed(adata.inputRate)}} L/s</span>
</ui-section>
<ui-section label='Output Regulator'>
<ui-button icon='{{data.outputting ? "power-off" : "close"}}' style='{{data.outputting ? "selected" : null}}' action='output'>
{{data.outputting ? "Open": "Closed"}}</ui-button>
</ui-section>
<ui-section label='Output Pressure'>
<ui-button icon='pencil' action='pressure'>{{Math.round(adata.outputPressure)}} kPa</ui-button>
</ui-section>
</ui-display>
{{/if}}

View File

@@ -0,0 +1,25 @@
<ui-display>
<ui-section label='Power'>
<ui-button icon='{{data.on ? "power-off" : "close"}}' style='{{data.on ? "selected" : null}}'
action='power'>{{data.on ? "On" : "Off"}}</ui-button>
</ui-section>
<ui-section label='Output Pressure'>
<ui-button icon='pencil' action='pressure' params='{"pressure": "input"}'>Set</ui-button>
<ui-button icon='plus' state='{{data.pressure == data.max_pressure ? "disabled" : null}}' action='pressure' params='{"pressure": "max"}'>Max</ui-button>
<span>{{Math.round(adata.pressure)}} kPa</span>
</ui-section>
<ui-section label='Filter'>
<ui-button state='{{data.filter_type == "" ? "selected" : null}}'
action='filter' params='{"mode": ""}'>Nothing</ui-button>
<ui-button state='{{data.filter_type == "plasma" ? "selected" : null}}'
action='filter' params='{"mode": "plasma"}'>Plasma</ui-button>
<ui-button state='{{data.filter_type == "o2" ? "selected" : null}}'
action='filter' params='{"mode": "o2"}'>O2</ui-button>
<ui-button state='{{data.filter_type == "n2" ? "selected" : null}}'
action='filter' params='{"mode": "n2"}'>N2</ui-button>
<ui-button state='{{data.filter_type == "co2" ? "selected" : null}}'
action='filter' params='{"mode": "co2"}'>CO2</ui-button>
<ui-button state='{{data.filter_type == "n2o" ? "selected" : null}}'
action='filter' params='{"mode": "n2o"}'>N2O</ui-button>
</ui-section>
</ui-display>

View File

@@ -0,0 +1,33 @@
<ui-display>
<ui-section label='Power'>
<ui-button icon='{{data.on ? "power-off" : "close"}}' style='{{data.on ? "selected" : null}}'
action='power'>{{data.on ? "On" : "Off"}}</ui-button>
</ui-section>
<ui-section label='Output Pressure'>
<ui-button icon='pencil' action='pressure' params='{"pressure": "input"}'>Set</ui-button>
<ui-button icon='plus' state='{{data.set_pressure == data.max_pressure ? "disabled" : null}}' action='pressure' params='{"pressure": "max"}'>Max</ui-button>
<span>{{Math.round(adata.set_pressure)}} kPa</span>
</ui-section>
<ui-section label='Node 1'>
<ui-button icon='fast-backward' state='{{data.node1_concentration == 0 ? "disabled" : null}}'
action='node1' params='{"concentration": -0.1}'/>
<ui-button icon='backward' state='{{data.node1_concentration == 0 ? "disabled" : null}}'
action='node1' params='{"concentration": -0.01}'/>
<ui-button icon='forward' state='{{data.node1_concentration == 100 ? "disabled" : null}}'
action='node1' params='{"concentration": 0.01}'/>
<ui-button icon='fast-forward' state='{{data.node1_concentration == 100 ? "disabled" : null}}'
action='node1' params='{"concentration": 0.1}'/>
<span>{{Math.round(adata.node1_concentration)}}%</span>
</ui-section>
<ui-section label='Node 2'>
<ui-button icon='fast-backward' state='{{data.node2_concentration == 0 ? "disabled" : null}}'
action='node2' params='{"concentration": -0.1}'/>
<ui-button icon='backward' state='{{data.node2_concentration == 0 ? "disabled" : null}}'
action='node2' params='{"concentration": -0.01}'/>
<ui-button icon='forward' state='{{data.node2_concentration == 100 ? "disabled" : null}}'
action='node2' params='{"concentration": 0.01}'/>
<ui-button icon='fast-forward' state='{{data.node2_concentration == 100 ? "disabled" : null}}'
action='node2' params='{"concentration": 0.1}'/>
<span>{{Math.round(adata.node2_concentration)}}%</span>
</ui-section>
</ui-display>

View File

@@ -0,0 +1,19 @@
<ui-display>
<ui-section label='Power'>
<ui-button icon='{{data.on ? "power-off" : "close"}}' style='{{data.on ? "selected" : null}}'
action='power'>{{data.on ? "On" : "Off"}}</ui-button>
</ui-section>
{{#if data.max_rate}}
<ui-section label='Transfer Rate'>
<ui-button icon='pencil' action='rate' params='{"rate": "input"}'>Set</ui-button>
<ui-button icon='plus' state='{{data.rate == data.max_rate ? "disabled" : null}}' action='transfer' params='{"rate": "max"}'>Max</ui-button>
<span>{{Math.round(adata.rate)}} L/s</span>
</ui-section>
{{else}}
<ui-section label='Output Pressure'>
<ui-button icon='pencil' action='pressure' params='{"pressure": "input"}'>Set</ui-button>
<ui-button icon='plus' state='{{data.pressure == data.max_pressure ? "disabled" : null}}' action='pressure' params='{"pressure": "max"}'>Max</ui-button>
<span>{{Math.round(adata.pressure)}} kPa</span>
</ui-section>
{{/if}}
</ui-display>

View File

@@ -0,0 +1,38 @@
<ui-display title='Status'>
<ui-section label='Energy'>
<ui-bar min='0' max='{{data.maxEnergy}}' value='{{data.energy}}'>{{Math.fixed(adata.energy)}} Units</ui-bar>
</ui-section>
</ui-display>
<ui-display title='Dispense' button>
{{#partial button}}
{{#each data.beakerTransferAmounts}}
<ui-button icon='plus' state='{{data.amount == . ? "selected" : null}}' action='amount' params='{"target": {{.}}}'>{{.}}</ui-button>
{{/each}}
{{/partial}}
<ui-section>
{{#each data.chemicals}}
<ui-button grid icon='tint' action='dispense' params='{"reagent": "{{id}}"}'>{{title}}</ui-button>
{{/each}}
</ui-section>
</ui-display>
<ui-display title='Beaker' button>
{{#partial button}}
{{#each data.beakerTransferAmounts}}
<ui-button icon='minus' action='remove' params='{"amount": {{.}}}'>{{.}}</ui-button>
{{/each}}
<ui-button icon='eject' state='{{data.isBeakerLoaded ? null : "disabled"}}' action='eject'>Eject</ui-button>
{{/partial}}
<ui-section label='Contents'>
{{#if data.isBeakerLoaded}}
<span>{{Math.round(adata.beakerCurrentVolume)}}/{{data.beakerMaxVolume}} Units</span>
<br/>
{{#each adata.beakerContents}}
<span class='highlight' intro-outro='fade'>{{Math.fixed(volume, 2)}} units of {{name}}</span><br/>
{{else}}
<span class='bad'>Beaker Empty</span>
{{/each}}
{{else}}
<span class='average'>No Beaker</span>
{{/if}}
</ui-section>
</ui-display>

View File

@@ -0,0 +1,29 @@
<ui-display title='Thermostat'>
<ui-section label='Power'>
<ui-button icon='{{data.isActive ? "power-off" : "close"}}'
style='{{data.isActive ? "selected" : null}}'
state='{{data.isBeakerLoaded ? null : "disabled"}}'
action='power'>{{data.isActive ? "On" : "Off"}}</ui-button>
</ui-section>
<ui-section label='Target'>
<ui-button icon='pencil' action='temperature' params='{"target": "input"}'>{{Math.round(adata.targetTemp)}} K</ui-button>
</ui-section>
</ui-display>
<ui-display title='Beaker' button>
{{#partial button}}
<ui-button icon='eject' state='{{data.isBeakerLoaded ? null : "disabled"}}' action='eject'>Eject</ui-button>
{{/partial}}
<ui-section label='Contents'>
{{#if data.isBeakerLoaded}}
<span>Temperature: {{Math.round(adata.currentTemp)}} K</span>
<br />
{{#each adata.beakerContents}}
<span class='highlight' intro-outro='fade'>{{Math.fixed(volume, 2)}} units of {{name}}</span><br/>
{{else}}
<span class='bad'>Beaker Empty</span>
{{/each}}
{{else}}
<span class='average'>No Beaker</span>
{{/if}}
</ui-section>
</ui-display>

View File

@@ -0,0 +1,74 @@
<script>
component.exports = {
data: {
temperatureStatus (temp) {
if (temp < 225) return 'good'
else if (temp < 273.15) return 'average'
else return 'bad'
}
},
computed: {
occupantStatState () {
switch (this.get('data.occupant.stat')) {
case 0: return 'good'
case 1: return 'average'
default: return 'bad'
}
}
}
}
</script>
<ui-display title='Occupant'>
<ui-section label='Occupant'>
<span>{{data.occupant.name ? data.occupant.name : "No Occupant"}}</span>
</ui-section>
{{#if data.hasOccupant}}
<ui-section label='State'>
<span class='{{occupantStatState}}'>{{data.occupant.stat == 0 ? "Conscious" : data.occupant.stat == 1 ? "Unconcious" : "Dead"}}</span>
</ui-section>
<ui-section label='Temperature'>
<span class='{{temperatureStatus(adata.occupant.bodyTemperature)}}'>{{Math.round(adata.occupant.bodyTemperature)}} K</span>
</ui-section>
<ui-section label='Health'>
<ui-bar min='{{data.occupant.minHealth}}' max='{{data.occupant.maxHealth}}' value='{{data.occupant.health}}'
state='{{data.occupant.health >= 0 ? "good" : "average"}}'>{{Math.round(adata.occupant.health)}}</ui-bar>
</ui-section>
{{#each [{label: "Brute", type: "bruteLoss"}, {label: "Respiratory", type: "oxyLoss"}, {label: "Toxin", type: "toxLoss"}, {label: "Burn", type: "fireLoss"}]}}
<ui-section label='{{label}}'>
<ui-bar min='0' max='{{data.occupant.maxHealth}}' value='{{data.occupant[type]}}' state='bad'>{{Math.round(adata.occupant[type])}}</ui-bar>
</ui-section>
{{/each}}
{{/if}}
</ui-display>
<ui-display title='Cell'>
<ui-section label='Power'>
<ui-button icon='{{data.isOperating ? "power-off" : "close"}}'
style='{{data.isOperating ? "selected" : null}}'
state='{{data.isOpen ? "disabled" : null}}'
action='power'>{{data.isOperating ? "On" : "Off"}}</ui-button>
</ui-section>
<ui-section label='Temperature'>
<span class='{{temperatureStatus(adata.cellTemperature)}}'>{{Math.round(adata.cellTemperature)}} K</span>
</ui-section>
<ui-section label='Door'>
<ui-button icon='{{data.isOpen ? "unlock" : "lock"}}' action='door'>{{data.isOpen ? "Open" : "Closed"}}</ui-button>
<ui-button icon='{{data.autoEject ? "sign-out" : "sign-in"}}' action='autoeject'>{{data.autoEject ? "Auto" : "Manual"}}</ui-button>
</ui-section>
</ui-display>
<ui-display title='Beaker' button>
{{#partial button}}
<ui-button icon='eject' state='{{data.isBeakerLoaded ? null : "disabled"}}' action='ejectbeaker'>Eject</ui-button>
{{/partial}}
<ui-section label='Contents'>
{{#if data.isBeakerLoaded}}
{{#each adata.beakerContents}}
<span class='highlight' intro-outro='fade'>{{Math.fixed(volume, 2)}} units of {{name}}</span><br/>
{{else}}
<span class='bad'>Beaker Empty</span>
{{/each}}
{{else}}
<span class='average'>No Beaker</span>
{{/if}}
</ui-section>
</ui-display>

View File

@@ -0,0 +1,29 @@
<script>
component.exports = {
computed: {
seclevelState () {
switch (this.get('data.seclevel')) {
case 'blue': return 'average'
case 'red': return 'bad'
case 'delta': return 'bad bold'
default: return 'good'
}
}
}
}
</script>
<ui-display>
<ui-section label='Alert Level'>
<span class='{{seclevelState}}'>{{text.titleCase(data.seclevel)}}</span>
</ui-section>
<ui-section label='Controls'>
<ui-button icon='{{data.alarm ? "close" : "bell-o"}}' action='{{data.alarm ? "reset" : "alarm"}}'>
{{data.alarm ? "Reset" : "Activate"}}</ui-button>
</ui-section>
{{#if data.emagged}}
<ui-section label='Warning'>
<span class='bad bold'>Safety measures offline. Device may exhibit abnormal behavior.</span>
</ui-section>
{{/if}}
</ui-display>

Some files were not shown because too many files have changed in this diff Show More