mirror of
https://github.com/vgstation-coders/vgstation13.git
synced 2025-12-09 07:57:50 +00:00
Byond 516 v2.0 (#37553)
* The TGS thing * Revert the 516 revert * Further segment the world/New() proc * Fixes an issue here
This commit is contained in:
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -20,8 +20,8 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
BYOND_MAJOR: 515
|
||||
BYOND_MINOR: 1603
|
||||
BYOND_MAJOR: 516
|
||||
BYOND_MINOR: 1659
|
||||
|
||||
#This can be declared here because build.py will ignore it if DM_UNIT_TESTS is true.
|
||||
ALL_MAPS: tgstation metaclub defficiency packedstation roidstation test_tiny test_vault snaxi tgstation-sec tgstation-snow LampreyStation xoq synergy bagelstation lowfatbagel Dorfstation waystation line horizon wheelstation
|
||||
|
||||
9
.vscode/tasks.json
vendored
9
.vscode/tasks.json
vendored
@@ -89,6 +89,15 @@
|
||||
],
|
||||
"group": "build",
|
||||
"label": "tgui: sonar"
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "install",
|
||||
"path": "tgui",
|
||||
"group": "clean",
|
||||
"problemMatcher": [],
|
||||
"label": "npm: install - tgui",
|
||||
"detail": "install dependencies from package"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -439,3 +439,6 @@ var/global/list/visible_spaces = list(/turf/simulated/open, /turf/simulated/floo
|
||||
#define OMNI_LINK(A,B) isliving(A) && A:omnitool_connect(B)
|
||||
|
||||
#define is_real_champion(A) ismob(A) && A.is_wearing_item(/obj/item/weapon/storage/belt/champion) && A.is_wearing_item(/obj/item/clothing/mask/luchador)
|
||||
|
||||
// Call by name proc references, checks if the proc exists on either this type () (AND ONLY THIS TYPE) or as a global proc.
|
||||
#define PROC_REF(X) (nameof(.proc/##X))
|
||||
|
||||
6
__DEFINES/cooldowns.dm
Normal file
6
__DEFINES/cooldowns.dm
Normal file
@@ -0,0 +1,6 @@
|
||||
//Returns true if the cooldown has run its course, false otherwise
|
||||
#define COOLDOWN_FINISHED(cd_source, cd_index) (cd_source.cd_index <= world.time)
|
||||
|
||||
#define COOLDOWN_TIMELEFT(cd_source, cd_index) (max(0, cd_source.cd_index - world.time))
|
||||
|
||||
#define COOLDOWN_START(cd_source, cd_index, cd_time) (cd_source.cd_index = world.time + (cd_time))
|
||||
@@ -368,7 +368,7 @@ var/obj/abstract/screen/plane_master/overdark_planemaster/overdark_planemaster =
|
||||
appearance_flags = 0
|
||||
plane = BASE_PLANE
|
||||
mouse_opacity = 0
|
||||
screen_loc = "CENTER,CENTER"
|
||||
screen_loc = "SOUTHWEST,SOUTHWEST"
|
||||
render_source = "*overdark"
|
||||
|
||||
var/obj/abstract/screen/plane_master/overdark_planemaster_target/overdark_planemaster_target = new()
|
||||
|
||||
@@ -6,3 +6,6 @@
|
||||
#define QDEL_LIST_ASSOC_NULL(L) QDEL_LIST_ASSOC(L); L = null
|
||||
#define QDEL_LIST_CUT(L) QDEL_LIST(L); L.Cut()
|
||||
#define QDEL_LIST_ASSOC_CUT(L) QDEL_LIST_ASSOC(L); L.Cut()
|
||||
|
||||
// QDEL macros borrowed from TG
|
||||
#define QDELETED(X) (!X || X.gcDestroyed)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//This file was auto-corrected by findeclaration.exe on 25.5.2012 20:42:31
|
||||
#if DM_VERSION < 513
|
||||
#if DM_VERSION < 516
|
||||
#error Your version of byond is too old, you need version 513 or higher
|
||||
#endif
|
||||
#define RUNWARNING // disable if they re-enable run() in 507 or newer.
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
/// Maximum ping timeout allowed to detect zombie windows
|
||||
#define TGUI_PING_TIMEOUT 4 SECONDS
|
||||
|
||||
/// Used for rate-limiting to prevent DoS by excessively refreshing a TGUI window
|
||||
#define TGUI_REFRESH_FULL_UPDATE_COOLDOWN (1 SECONDS)
|
||||
|
||||
/// Window does not exist
|
||||
#define TGUI_WINDOW_CLOSED 0
|
||||
/// Window was just opened, but is still not ready to be sent data
|
||||
@@ -38,3 +41,12 @@
|
||||
#define TGUI_CREATE_MESSAGE(type, payload) ( \
|
||||
"%7b%22type%22%3a%22[type]%22%2c%22payload%22%3a[url_encode(json_encode(payload))]%7d" \
|
||||
)
|
||||
|
||||
/**
|
||||
* NAMEOF: Compile time checked variable name to string conversion
|
||||
* evaluates to a string equal to "X", but compile errors if X isn't a var on datum.
|
||||
**/
|
||||
#define NAMEOF(datum, X) (#X || ##datum.##X)
|
||||
|
||||
/// Call by name proc reference, checks if the proc exists on either the given type or as a global proc
|
||||
#define TYPE_PROC_REF(TYPE, X) (nameof(##TYPE.proc/##X))
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
var/internal_volume = 10000
|
||||
var/max_pressure = 10000
|
||||
|
||||
var/target_pressure = 4500 //Output pressure.
|
||||
var/target_pressure = 2500 //Output pressure.
|
||||
|
||||
var/datum/gas_mixture/air //Internal tank.
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
))
|
||||
return data
|
||||
|
||||
/obj/machinery/atmospherics/binary/msgs/ui_act(action, list/params)
|
||||
/obj/machinery/atmospherics/binary/msgs/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
@@ -110,9 +110,11 @@
|
||||
if("toggle_power")
|
||||
on = !on
|
||||
update_icon()
|
||||
SStgui.try_update_ui(ui.user, src, ui)
|
||||
return TRUE
|
||||
if("set_pressure")
|
||||
target_pressure = round(clamp(text2num(params["new_pressure"]), 0, 4500))
|
||||
SStgui.try_update_ui(ui.user, src, ui)
|
||||
update_icon()
|
||||
return TRUE
|
||||
|
||||
|
||||
@@ -29,6 +29,9 @@ var/cmp_field = "name"
|
||||
/proc/cmp_records_dsc(datum/data/record/a, datum/data/record/b)
|
||||
return sorttext(a.fields[cmp_field], b.fields[cmp_field])
|
||||
|
||||
/proc/cmp_records_numerically(datum/data/record/a, datum/data/record/b)
|
||||
return b.fields[cmp_field] - a.fields[cmp_field]
|
||||
|
||||
/proc/cmp_ckey_asc(client/a, client/b)
|
||||
return sorttext(b.ckey, a.ckey)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/datum
|
||||
var/list/datum_components
|
||||
var/list/active_timers
|
||||
var/list/open_uis
|
||||
|
||||
/datum/proc/initialize()
|
||||
return TRUE
|
||||
|
||||
@@ -59,3 +59,23 @@
|
||||
fileaccess_timer = world.time + FTPDELAY
|
||||
return 0
|
||||
#undef FTPDELAY
|
||||
|
||||
// Similar to clamp but the bottom rolls around to the top and vice versa. min is inclusive, max is exclusive
|
||||
#define WRAP(val, min, max) clamp(( min == max ? min : (val) - (round(((val) - (min))/((max) - (min))) * ((max) - (min))) ),min,max)
|
||||
|
||||
|
||||
/// Save file as an external file then md5 it.
|
||||
/// Used because md5ing files stored in the rsc sometimes gives incorrect md5 results.
|
||||
/// https://www.byond.com/forum/post/2611357
|
||||
/proc/md5asfile(file)
|
||||
var/static/notch = 0
|
||||
// its importaint this code can handle md5filepath sleeping instead of hard blocking, if it's converted to use rust_g.
|
||||
var/filename = "tmp/md5asfile.[world.realtime].[world.timeofday].[world.time].[world.tick_usage].[notch]"
|
||||
notch = WRAP(notch+1, 0, 2**15)
|
||||
fcopy(file, filename)
|
||||
. = md5filepath(filename)
|
||||
fdel(filename)
|
||||
|
||||
/// Returns the md5 of a file at a given path.
|
||||
/proc/md5filepath(path)
|
||||
. = md5(file(path))
|
||||
|
||||
@@ -210,3 +210,64 @@
|
||||
return HSVtoRGB(hsv(hsv_list[1], hsv_list[2], hsv_list[3], hsv_list[4]))
|
||||
return HSVtoRGB(hsv(hsv_list[1], hsv_list[2], hsv_list[3]))
|
||||
|
||||
/// given an icon object, dmi file path, or atom/image/mutable_appearance, attempts to find and return an associated dmi file path.
|
||||
/// a weird quirk about dm is that /icon objects represent both compile-time or dynamic icons in the rsc,
|
||||
/// but stringifying rsc references returns a dmi file path
|
||||
/// ONLY if that icon represents a completely unchanged dmi file from when the game was compiled.
|
||||
/// so if the given object is associated with an icon that was in the rsc when the game was compiled, this returns a path. otherwise it returns ""
|
||||
/proc/get_icon_dmi_path(icon/icon)
|
||||
/// the dmi file path we attempt to return if the given object argument is associated with a stringifiable icon
|
||||
/// if successful, this looks like "icons/path/to/dmi_file.dmi"
|
||||
var/icon_path = ""
|
||||
|
||||
if(isatom(icon) || istype(icon, /image) || istype(icon, /mutable_appearance))
|
||||
var/atom/atom_icon = icon
|
||||
icon = atom_icon.icon
|
||||
//atom icons compiled in from 'icons/path/to/dmi_file.dmi' are weird and not really icon objects that you generate with icon().
|
||||
//if they're unchanged dmi's then they're stringifiable to "icons/path/to/dmi_file.dmi"
|
||||
|
||||
if(isicon(icon) && isfile(icon))
|
||||
//icons compiled in from 'icons/path/to/dmi_file.dmi' at compile time are weird and aren't really /icon objects,
|
||||
///but they pass both isicon() and isfile() checks. they're the easiest case since stringifying them gives us the path we want
|
||||
var/icon_ref = ref(icon)
|
||||
var/locate_icon_string = "[locate(icon_ref)]"
|
||||
|
||||
icon_path = locate_icon_string
|
||||
|
||||
else if(isicon(icon) && "[icon]" == "/icon")
|
||||
// icon objects generated from icon() at runtime are icons, but they AREN'T files themselves, they represent icon files.
|
||||
// if the files they represent are compile time dmi files in the rsc, then
|
||||
// the rsc reference returned by fcopy_rsc() will be stringifiable to "icons/path/to/dmi_file.dmi"
|
||||
var/rsc_ref = fcopy_rsc(icon)
|
||||
|
||||
var/icon_ref = ref(rsc_ref)
|
||||
|
||||
var/icon_path_string = "[locate(icon_ref)]"
|
||||
|
||||
icon_path = icon_path_string
|
||||
|
||||
else if(istext(icon))
|
||||
var/rsc_ref = fcopy_rsc(icon)
|
||||
//if its the text path of an existing dmi file, the rsc reference returned by fcopy_rsc() will be stringifiable to a dmi path
|
||||
|
||||
var/rsc_ref_ref = ref(rsc_ref)
|
||||
var/rsc_ref_string = "[locate(rsc_ref_ref)]"
|
||||
|
||||
icon_path = rsc_ref_string
|
||||
|
||||
if(is_valid_dmi_file(icon_path))
|
||||
return icon_path
|
||||
|
||||
return FALSE
|
||||
|
||||
///given a text string, returns whether it is a valid dmi icons folder path
|
||||
/proc/is_valid_dmi_file(icon_path)
|
||||
if(!istext(icon_path) || !length(icon_path))
|
||||
return FALSE
|
||||
|
||||
var/is_in_icon_folder = findtextEx(icon_path, "icons/")
|
||||
var/is_dmi_file = findtextEx(icon_path, ".dmi")
|
||||
|
||||
if(is_in_icon_folder && is_dmi_file)
|
||||
return TRUE
|
||||
return FALSE
|
||||
@@ -18,9 +18,9 @@
|
||||
|
||||
/ray
|
||||
var/z //the z-level we are casting our ray in
|
||||
var/vector/origin //the origin of the ray
|
||||
var/vector/origin_floored //the floored origin vector
|
||||
var/vector/direction //direction of the ray
|
||||
var/_vector/origin //the origin of the ray
|
||||
var/_vector/origin_floored //the floored origin vector
|
||||
var/_vector/direction //direction of the ray
|
||||
var/original_damage //original damage of the ray when applicable
|
||||
var/turf/final_turf
|
||||
var/turf/previous_turf
|
||||
@@ -29,7 +29,7 @@
|
||||
return "\[Ray\](\n- origin = " + origin.toString() + "\n- origin_floored = "+ origin_floored.toString() + "\n- direction = " + direction.toString() + "\n- z-level = " + num2text(z) + "\n)"
|
||||
|
||||
//use atom2vector for the origin, atoms2vector for the direction
|
||||
/ray/New(var/vector/p_origin, var/vector/p_direction, var/z)
|
||||
/ray/New(var/_vector/p_origin, var/_vector/p_direction, var/z)
|
||||
origin = p_origin
|
||||
origin_floored = origin.floored() //to save us from calculating it all over again
|
||||
direction = p_direction.chebyshev_normalized()
|
||||
@@ -53,7 +53,7 @@
|
||||
return hitsPoint(other_ray.origin)
|
||||
|
||||
//returns true if point is on our ray (can be called with a max distance)
|
||||
/ray/proc/hitsPoint(var/vector/point, var/max_distance = 0)
|
||||
/ray/proc/hitsPoint(var/_vector/point, var/max_distance = 0)
|
||||
if(origin.equals(point)) //the easy way out
|
||||
return TRUE
|
||||
|
||||
@@ -73,23 +73,23 @@
|
||||
// wrong?
|
||||
/ray/proc/getReboundOnAtom(var/rayCastHit/hit)
|
||||
//calc where we hit the atom
|
||||
var/vector/hit_point = hit.point_raw
|
||||
var/_vector/hit_point = hit.point_raw
|
||||
var/atom/movable/resolved_hit_atom = hit.hit_atom?.get()
|
||||
var/vector/hit_atom_loc = atom2vector(resolved_hit_atom) + new /vector(0.5, 0.5)
|
||||
var/_vector/hit_atom_loc = atom2vector(resolved_hit_atom) + new /_vector(0.5, 0.5)
|
||||
|
||||
var/vector/hit_vector = hit_point - hit_atom_loc
|
||||
var/_vector/hit_vector = hit_point - hit_atom_loc
|
||||
|
||||
//we assume every atom is a octogonal, hence we use all_vectors
|
||||
//here we calculate the "face" of the octagonal atom we want to rebound on
|
||||
var/entry_byond_dir = vector2ClosestDir(hit_vector)
|
||||
var/vector/entry_dir = dir2vector(entry_byond_dir)
|
||||
var/_vector/entry_dir = dir2vector(entry_byond_dir)
|
||||
|
||||
return src.direction.mirrorWithNormal(entry_dir)
|
||||
|
||||
|
||||
//gets a point along the ray
|
||||
/ray/proc/getPoint(var/distance)
|
||||
var/vector/path = direction * distance
|
||||
var/_vector/path = direction * distance
|
||||
return origin + path
|
||||
|
||||
//inherit and override this for costum logic
|
||||
@@ -101,15 +101,15 @@
|
||||
//returns list of raycasthits
|
||||
/ray/proc/cast(var/max_distance = RAY_CAST_DEFAULT_MAX_DISTANCE, var/max_hits = RAY_CAST_UNLIMITED_HITS, var/ignore_origin = TRUE)
|
||||
//calculating a step and its distance to use in the loop
|
||||
var/vector/a_step = direction * RAY_CAST_STEP
|
||||
var/_vector/a_step = direction * RAY_CAST_STEP
|
||||
var/step_distance = a_step.chebyshev_norm()
|
||||
|
||||
//setting up our pointer and distance to track where we are
|
||||
var/vector/pointer = new /vector(0,0)
|
||||
var/_vector/pointer = new /_vector(0,0)
|
||||
var/distance = 0
|
||||
|
||||
//positions list to easier check if we already found this position (since we are moving in tiny steps, not full numbers)
|
||||
var/list/vector/positions = list()
|
||||
var/list/_vector/positions = list()
|
||||
|
||||
//our result
|
||||
var/list/rayCastHit/hits = list()
|
||||
@@ -124,12 +124,12 @@
|
||||
distance += step_distance
|
||||
|
||||
//calculating our current position in world space (its two lines cause byond)
|
||||
var/vector/new_position_unfloored = origin + pointer
|
||||
var/vector/new_position = new_position_unfloored.floored()
|
||||
var/_vector/new_position_unfloored = origin + pointer
|
||||
var/_vector/new_position = new_position_unfloored.floored()
|
||||
|
||||
//check if we already checked this (floored) vector
|
||||
var/exists = FALSE
|
||||
for(var/vector/V in positions)
|
||||
for(var/_vector/V in positions)
|
||||
if(V.equals(new_position))
|
||||
exists = TRUE
|
||||
if(exists)
|
||||
@@ -191,14 +191,14 @@ var/list/ray_draw_icon_cache = list()
|
||||
var/angle = direction.toAngle()
|
||||
var/max_distance = draw_distance - distance_from_endpoint
|
||||
while(distance_pointer < max_distance)
|
||||
var/vector/point
|
||||
var/_vector/point
|
||||
if(distance_pointer > max_distance - step_size) //last loop
|
||||
point = getPoint(max_distance - step_size)
|
||||
else
|
||||
point = getPoint(distance_pointer)
|
||||
var/vector/point_floored = point.floored()
|
||||
var/_vector/point_floored = point.floored()
|
||||
|
||||
var/vector/pixels = (point - point_floored - new /vector(0.5, 0.5)) * WORLD_ICON_SIZE
|
||||
var/_vector/pixels = (point - point_floored - new /_vector(0.5, 0.5)) * WORLD_ICON_SIZE
|
||||
|
||||
var/turf/T = locate(point_floored.x, point_floored.y, z)
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/rayCastHit
|
||||
var/ray/used_ray
|
||||
var/datum/weakref/hit_atom
|
||||
var/vector/point
|
||||
var/vector/point_raw
|
||||
var/_vector/point
|
||||
var/_vector/point_raw
|
||||
var/distance
|
||||
var/hit_type // see defines in ray.dm
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/rayCastHitInfo
|
||||
var/ray/used_ray
|
||||
var/datum/weakref/hit_atom
|
||||
var/vector/point
|
||||
var/vector/point_raw
|
||||
var/_vector/point
|
||||
var/_vector/point_raw
|
||||
var/distance
|
||||
|
||||
/rayCastHitInfo/New(var/ray/used_ray, var/datum/weakref/hit_atom, var/vector/point, var/vector/point_raw, var/distance)
|
||||
/rayCastHitInfo/New(var/ray/used_ray, var/datum/weakref/hit_atom, var/_vector/point, var/_vector/point_raw, var/distance)
|
||||
src.used_ray = used_ray
|
||||
src.hit_atom = hit_atom
|
||||
src.point = point
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
/proc/raycast_test(var/x = 1, var/y = 1, var/dist)
|
||||
var/vector/origin = new /vector(usr.x, usr.y)
|
||||
var/vector/direction = new /vector(x, y)
|
||||
var/_vector/origin = new /_vector(usr.x, usr.y)
|
||||
var/_vector/direction = new /_vector(x, y)
|
||||
|
||||
var/ray/our_ray = new /ray(origin, direction, usr.z)
|
||||
var/list/res = our_ray.cast(dist)
|
||||
|
||||
@@ -1,50 +1,50 @@
|
||||
// Basic geometry things.
|
||||
/vector
|
||||
/_vector
|
||||
var/x = 0
|
||||
var/y = 0
|
||||
|
||||
/vector/New(var/x, var/y)
|
||||
/_vector/New(var/x, var/y)
|
||||
src.x = x
|
||||
src.y = y
|
||||
|
||||
/vector/proc/duplicate()
|
||||
return new /vector(x, y)
|
||||
/_vector/proc/duplicate()
|
||||
return new /_vector(x, y)
|
||||
|
||||
/vector/proc/euclidian_norm()
|
||||
/_vector/proc/euclidian_norm()
|
||||
return sqrt(x*x + y*y)
|
||||
|
||||
/vector/proc/squared_norm()
|
||||
/_vector/proc/squared_norm()
|
||||
return x*x + y*y
|
||||
|
||||
/vector/proc/normalized()
|
||||
/_vector/proc/normalized()
|
||||
var/norm = euclidian_norm()
|
||||
return new /vector(x/norm, y/norm)
|
||||
return new /_vector(x/norm, y/norm)
|
||||
|
||||
/vector/proc/floored()
|
||||
return new /vector(Floor(x), Floor(y))
|
||||
/_vector/proc/floored()
|
||||
return new /_vector(Floor(x), Floor(y))
|
||||
|
||||
//use this one
|
||||
/vector/proc/chebyshev_norm()
|
||||
/_vector/proc/chebyshev_norm()
|
||||
return max(abs(x), abs(y))
|
||||
|
||||
//use this one
|
||||
/vector/proc/chebyshev_normalized()
|
||||
/_vector/proc/chebyshev_normalized()
|
||||
var/norm = chebyshev_norm()
|
||||
return new /vector(x/norm, y/norm)
|
||||
return new /_vector(x/norm, y/norm)
|
||||
|
||||
/vector/proc/is_integer()
|
||||
/_vector/proc/is_integer()
|
||||
return IS_INT(x) && IS_INT(y)
|
||||
|
||||
/vector/proc/is_null()
|
||||
/_vector/proc/is_null()
|
||||
return chebyshev_norm() == 0
|
||||
|
||||
/vector/proc/toString()
|
||||
/_vector/proc/toString()
|
||||
return "\[Vector\]([x],[y])"
|
||||
|
||||
//returns angle from 0 to 360
|
||||
//-1 if vector is (0,0)
|
||||
//angle calculated on north
|
||||
/vector/proc/toAngle()
|
||||
/_vector/proc/toAngle()
|
||||
if(x == 0)
|
||||
if(y == 0)
|
||||
return -1
|
||||
@@ -58,30 +58,30 @@
|
||||
else if(x < 0)
|
||||
return 270
|
||||
|
||||
var/vector/src_norm = src.chebyshev_normalized()
|
||||
var/_vector/src_norm = src.chebyshev_normalized()
|
||||
var/angle = arctan(src_norm.y,src_norm.x) - 360 * -1 //this is broken
|
||||
return (angle >= 360) ? angle - 360 : angle
|
||||
|
||||
/vector/proc/dot(var/vector/B)
|
||||
/_vector/proc/dot(var/_vector/B)
|
||||
return src.x * B.x + src.y * B.y
|
||||
|
||||
/vector/proc/mirrorWithNormal(var/vector/N)
|
||||
var/vector/n_norm = N.normalized()
|
||||
/_vector/proc/mirrorWithNormal(var/_vector/N)
|
||||
var/_vector/n_norm = N.normalized()
|
||||
return src - n_norm * ( 2 * ( src * n_norm ))
|
||||
|
||||
//operator overloading
|
||||
/vector/proc/operator+(var/vector/B)
|
||||
return new /vector(x + B.x, y + B.y)
|
||||
/_vector/proc/operator+(var/_vector/B)
|
||||
return new /_vector(x + B.x, y + B.y)
|
||||
|
||||
/vector/proc/operator-(var/vector/B)
|
||||
return new /vector(x - B.x, y - B.y)
|
||||
/_vector/proc/operator-(var/_vector/B)
|
||||
return new /_vector(x - B.x, y - B.y)
|
||||
|
||||
/vector/proc/operator*(var/mult)
|
||||
if(istype(mult, /vector))
|
||||
/_vector/proc/operator*(var/mult)
|
||||
if(istype(mult, /_vector))
|
||||
return dot(mult)
|
||||
return new /vector(x * mult, y * mult)
|
||||
return new /_vector(x * mult, y * mult)
|
||||
|
||||
/vector/proc/equals(var/vector/vectorB)
|
||||
/_vector/proc/equals(var/_vector/vectorB)
|
||||
return (x == vectorB.x && y == vectorB.y)
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/atom/movable/proc/vector_translate(var/vector/V, var/delay)
|
||||
/atom/movable/proc/vector_translate(var/_vector/V, var/delay)
|
||||
var/turf/T = get_turf(src)
|
||||
var/turf/destination = locate(T.x + V.x, T.y + V.y, z)
|
||||
var/vector/V_norm = V.chebyshev_normalized()
|
||||
var/_vector/V_norm = V.chebyshev_normalized()
|
||||
if (!V_norm.is_integer())
|
||||
return
|
||||
var/turf/destination_temp
|
||||
@@ -11,61 +11,61 @@
|
||||
T = get_turf(src)
|
||||
sleep(delay + world.tick_lag) // Shortest possible time to sleep
|
||||
|
||||
/atom/proc/get_translated_turf(var/vector/V)
|
||||
/atom/proc/get_translated_turf(var/_vector/V)
|
||||
var/turf/T = get_turf(src)
|
||||
return locate(T.x + V.x, T.y + V.y, z)
|
||||
|
||||
//Vector representing world-pos of A
|
||||
/proc/atom2vector(var/atom/A)
|
||||
return new /vector(A.x, A.y)
|
||||
return new /_vector(A.x, A.y)
|
||||
|
||||
//Vector from A -> B
|
||||
/proc/atoms2vector(var/atom/A, var/atom/B)
|
||||
return new /vector((B.x - A.x), (B.y - A.y))
|
||||
return new /_vector((B.x - A.x), (B.y - A.y))
|
||||
|
||||
|
||||
/proc/dir2vector(var/dir)
|
||||
switch(dir)
|
||||
if(NORTH)
|
||||
return new /vector(0,1)
|
||||
return new /_vector(0,1)
|
||||
if(NORTHEAST)
|
||||
return new /vector(1,1)
|
||||
return new /_vector(1,1)
|
||||
if(EAST)
|
||||
return new /vector(1,0)
|
||||
return new /_vector(1,0)
|
||||
if(SOUTHEAST)
|
||||
return new /vector(1,-1)
|
||||
return new /_vector(1,-1)
|
||||
if(SOUTH)
|
||||
return new /vector(0,-1)
|
||||
return new /_vector(0,-1)
|
||||
if(SOUTHWEST)
|
||||
return new /vector(-1,-1)
|
||||
return new /_vector(-1,-1)
|
||||
if(WEST)
|
||||
return new /vector(-1,0)
|
||||
return new /_vector(-1,0)
|
||||
if(NORTHWEST)
|
||||
return new /vector(-1,1)
|
||||
return new /_vector(-1,1)
|
||||
|
||||
//defaults to north
|
||||
/proc/vector2ClosestDir(var/vector/V)
|
||||
var/vector/V_norm = V.chebyshev_normalized()
|
||||
/proc/vector2ClosestDir(var/_vector/V)
|
||||
var/_vector/V_norm = V.chebyshev_normalized()
|
||||
|
||||
var/smallest_dist = 2 //since all vectors are normalized, the biggest possible distance is 2
|
||||
var/closestDir = NORTH
|
||||
for(var/d in alldirs)
|
||||
var/vector/dir = dir2vector(d)
|
||||
var/vector/delta = dir.chebyshev_normalized() - V_norm
|
||||
var/_vector/dir = dir2vector(d)
|
||||
var/_vector/delta = dir.chebyshev_normalized() - V_norm
|
||||
var/dist = delta.chebyshev_norm()
|
||||
if(dist < smallest_dist)
|
||||
smallest_dist = dist
|
||||
closestDir = d
|
||||
return closestDir
|
||||
|
||||
/proc/drawLaser(var/vector/A, var/vector/B, var/icon='icons/obj/projectiles.dmi', var/icon_state = "laser")
|
||||
var/vector/delta = (B - A)
|
||||
/proc/drawLaser(var/_vector/A, var/_vector/B, var/icon='icons/obj/projectiles.dmi', var/icon_state = "laser")
|
||||
var/_vector/delta = (B - A)
|
||||
var/ray/laser_ray = new /ray(A, delta)
|
||||
var/distance = delta.chebyshev_norm()
|
||||
|
||||
laser_ray.draw(distance, icon, icon_state)
|
||||
|
||||
/proc/vector2turf(var/vector/V, var/z)
|
||||
/proc/vector2turf(var/_vector/V, var/z)
|
||||
var/turf/T = locate(V.x, V.y, z)
|
||||
return T
|
||||
|
||||
|
||||
@@ -14,4 +14,4 @@
|
||||
|
||||
sortInstance.timSort(fromIndex, toIndex)
|
||||
|
||||
return L
|
||||
return L
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
#define UTF_LIMIT (1 << 20) + (1 << 16) - 1
|
||||
#define string2charlist(string) (splittext(string, regex("(.)")) - splittext(string, ""))
|
||||
|
||||
#define PREVENT_CHARACTER_TRIM_LOSS(length) (length + 1)
|
||||
|
||||
/*
|
||||
* Holds procs designed to help with filtering text
|
||||
* Contains groups:
|
||||
@@ -128,6 +130,15 @@ var/list/whitelist_name_diacritics_min = list(
|
||||
"à", "á", "â", "ã", "ä", "ä", "æ", "ç", "è", "é", "ê", "ë", "ì", "í", "î", "ï", "ð", "ñ", "ò", "ó", "ô", "ö", "ø", "ù", "ú", "û", "ü", "ý",
|
||||
)
|
||||
|
||||
/proc/stripped_multiline_input(mob/user, message = "", title = "", default = "", max_length=MAX_MESSAGE_LEN, no_trim=FALSE)
|
||||
var/user_input = input(user, message, title, default) as message|null
|
||||
if(isnull(user_input)) // User pressed cancel
|
||||
return
|
||||
if(no_trim)
|
||||
return copytext_char(html_encode(user_input), 1, max_length)
|
||||
else
|
||||
return trim(html_encode(user_input), max_length)
|
||||
|
||||
/proc/reject_bad_name(var/t_in, var/allow_numbers=0, var/max_length=MAX_NAME_LEN)
|
||||
if(!t_in || length(t_in) > max_length)
|
||||
return //Rejects the input if it is null or if it is longer then the max length allowed
|
||||
|
||||
@@ -8,6 +8,11 @@
|
||||
return x!=0?x/abs(x):0
|
||||
#endif
|
||||
|
||||
/// Return html to load a url.
|
||||
/// for use inside of browse() calls to html assets that might be loaded on a cdn.
|
||||
/proc/url2htmlloader(url)
|
||||
return {"<html><head><meta http-equiv="refresh" content="0;URL='[url]'"/></head><body onLoad="parent.location='[url]'"></body></html>"}
|
||||
|
||||
/proc/getline(atom/M,atom/N)//Ultra-Fast Bresenham Line-Drawing Algorithm
|
||||
var/px=M.x //starting x
|
||||
var/py=M.y
|
||||
|
||||
@@ -97,6 +97,9 @@
|
||||
if(modifiers["right"])
|
||||
RightClickOn(A)
|
||||
return
|
||||
// Equivalent to button M4/M5 on gaming mouses.
|
||||
if(modifiers["xbutton1"] || modifiers["xbutton2"])
|
||||
return
|
||||
|
||||
if(attempt_crawling(A))
|
||||
return
|
||||
|
||||
@@ -218,6 +218,20 @@
|
||||
var/rsclist = ""
|
||||
var/rscstring = ""
|
||||
|
||||
// TGUI & tg asset thing
|
||||
var/tgui_max_chunk_count = 32
|
||||
var/tg_asset_transport = "simple" // simple or "webroot". Webroot is via CDN.
|
||||
var/cache_assets = 1 // Disabled during dev, enabled during prod
|
||||
var/smart_cache_assets = 1
|
||||
var/save_spritesheets = 0 // Disabled by default.
|
||||
|
||||
var/asset_simple_preload = 0 // Disabled by default
|
||||
|
||||
// tg asset cdn via webroot systme. Currently unused.
|
||||
var/asset_cdn_webroot = ""
|
||||
var/asset_cdn_url = ""
|
||||
|
||||
|
||||
/datum/configuration/New()
|
||||
. = ..()
|
||||
var/list/L = subtypesof(/datum/gamemode)-/datum/gamemode/cult
|
||||
|
||||
42
code/controllers/subsystem/assets.dm
Normal file
42
code/controllers/subsystem/assets.dm
Normal file
@@ -0,0 +1,42 @@
|
||||
var/datum/subsystem/assets/SSassets
|
||||
|
||||
/datum/subsystem/assets
|
||||
name = "Assets"
|
||||
flags = SS_NO_FIRE
|
||||
var/list/datum/asset_cache_item/cache = list()
|
||||
var/list/preload = list()
|
||||
var/datum/asset_transport/transport = new()
|
||||
|
||||
/datum/subsystem/assets/New()
|
||||
NEW_SS_GLOBAL(SSassets)
|
||||
|
||||
/datum/subsystem/assets/Initialize()
|
||||
|
||||
// -- FIXME ASSETS:.
|
||||
// -- Old, crusty /vg/ style of populating assets (relying on 'global cache.dm')
|
||||
// TOFIX asap!!! Having two concurrent, parralel & independant asset delivery system is BAD
|
||||
populate_asset_cache()
|
||||
|
||||
var/newtransporttype = /datum/asset_transport
|
||||
switch (config.tg_asset_transport)
|
||||
if ("webroot")
|
||||
newtransporttype = /datum/asset_transport/webroot
|
||||
|
||||
if (newtransporttype == transport.type)
|
||||
return
|
||||
|
||||
var/datum/asset_transport/newtransport = new newtransporttype ()
|
||||
if (newtransport.validate_config())
|
||||
transport = newtransport
|
||||
transport.Load()
|
||||
|
||||
for(var/type in typesof(/datum/tg_asset))
|
||||
var/datum/tg_asset/A = type
|
||||
if (type != initial(A._abstract))
|
||||
load_asset_datum(type)
|
||||
|
||||
transport.Initialize(cache)
|
||||
|
||||
/datum/subsystem/assets/Recover()
|
||||
cache = SSassets.cache
|
||||
preload = SSassets.preload
|
||||
@@ -1,16 +0,0 @@
|
||||
var/datum/subsystem/assets/SSassets
|
||||
|
||||
|
||||
/datum/subsystem/assets
|
||||
name = "Asset Cache"
|
||||
init_order = SS_INIT_ASSETS
|
||||
flags = SS_NO_FIRE
|
||||
|
||||
|
||||
/datum/subsystem/assets/New()
|
||||
NEW_SS_GLOBAL(SSassets)
|
||||
|
||||
|
||||
/datum/subsystem/assets/Initialize(timeofday)
|
||||
populate_asset_cache()
|
||||
..()
|
||||
@@ -232,37 +232,37 @@ var/datum/subsystem/persistence_misc/SSpersistence_misc
|
||||
log_debug("[name] task found an empty file on [file_path]")
|
||||
return
|
||||
for(var/list/L in to_read)
|
||||
var/datum/record/money/record = new(L["ckey"], L["role"], L["cash"], L["shift_duration"], L["date"])
|
||||
var/datum/data/record/money/record = new(L["ckey"], L["role"], L["cash"], L["shift_duration"], L["date"])
|
||||
data += record
|
||||
|
||||
/datum/persistence_task/highscores/on_shutdown()
|
||||
var/list/L = list()
|
||||
for(var/datum/record/money/record in data)
|
||||
for(var/datum/data/record/money/record in data)
|
||||
L += list(record.vars)
|
||||
write_file(L)
|
||||
|
||||
/datum/persistence_task/highscores/proc/insert_records(list/records)
|
||||
data += records
|
||||
cmp_field = "cash"
|
||||
sortTim(data, /proc/cmp_list_by_element_desc)
|
||||
global.cmp_field = "cash"
|
||||
sortTim(data, /proc/cmp_records_numerically)
|
||||
if (data.len > 5)
|
||||
data.Cut(6) // we only store the top 5
|
||||
for(var/datum/record/money/record in data)
|
||||
for(var/datum/data/record/money/record in data)
|
||||
if(record in records)
|
||||
if(data[1] == record)
|
||||
announce_new_highest_record(record)
|
||||
else
|
||||
announce_new_record(record)
|
||||
|
||||
/datum/persistence_task/highscores/proc/announce_new_highest_record(var/datum/record/money/record)
|
||||
/datum/persistence_task/highscores/proc/announce_new_highest_record(var/datum/data/record/money/record)
|
||||
var/name = "Richest escape ever"
|
||||
var/desc = "You broke the record of the richest escape! $[record.cash] chips accumulated."
|
||||
give_award(record.ckey, /obj/item/weapon/reagent_containers/food/drinks/golden_cup, name, desc)
|
||||
var/desc = "You broke the record of the richest escape! $[record.fields["cash"]] chips accumulated."
|
||||
give_award(record.fields["ckey"], /obj/item/weapon/reagent_containers/food/drinks/golden_cup, name, desc)
|
||||
|
||||
/datum/persistence_task/highscores/proc/announce_new_record(var/datum/record/money/record)
|
||||
/datum/persistence_task/highscores/proc/announce_new_record(var/datum/data/record/money/record)
|
||||
var/name = "Good rich escape"
|
||||
var/desc = "You made it to the top 5! You accumulated $[record.cash]."
|
||||
give_award(record.ckey, /obj/item/clothing/accessory/medal/gold, name, desc, FALSE)
|
||||
var/desc = "You made it to the top 5! You accumulated $[record.fields["cash"]]."
|
||||
give_award(record.fields["ckey"], /obj/item/clothing/accessory/medal/gold, name, desc, FALSE)
|
||||
|
||||
/datum/persistence_task/highscores/proc/clear_records()
|
||||
data = list()
|
||||
@@ -273,15 +273,15 @@ var/datum/subsystem/persistence_misc/SSpersistence_misc
|
||||
name = "Trader shoal highscores"
|
||||
file_path = "data/persistence/trader_highscores.json"
|
||||
|
||||
/datum/persistence_task/highscores/trader/announce_new_highest_record(var/datum/record/money/record)
|
||||
/datum/persistence_task/highscores/trader/announce_new_highest_record(var/datum/data/record/money/record)
|
||||
var/name = "Richest shoal haul ever"
|
||||
var/desc = "You broke the record of the richest shoal haul! $[record.cash] chips accumulated."
|
||||
give_award(record.ckey, /obj/item/weapon/reagent_containers/food/drinks/golden_cup, name, desc)
|
||||
var/desc = "You broke the record of the richest shoal haul! $[record.fields["cash"]] chips accumulated."
|
||||
give_award(record.fields["ckey"], /obj/item/weapon/reagent_containers/food/drinks/golden_cup, name, desc)
|
||||
|
||||
/datum/persistence_task/highscores/trader/announce_new_record(var/datum/record/money/record)
|
||||
/datum/persistence_task/highscores/trader/announce_new_record(var/datum/data/record/money/record)
|
||||
var/name = "Good rich shoal haul"
|
||||
var/desc = "You made it to the top 5! You accumulated $[record.cash]."
|
||||
give_award(record.ckey, /obj/item/clothing/accessory/medal/gold, name, desc, FALSE)
|
||||
var/desc = "You made it to the top 5! You accumulated $[record.fields["cash"]]."
|
||||
give_award(record.fields["ckey"], /obj/item/clothing/accessory/medal/gold, name, desc, FALSE)
|
||||
|
||||
//stores map votes for code/modules/html_interface/voting/voting.dm
|
||||
/datum/persistence_task/vote
|
||||
|
||||
@@ -13,8 +13,10 @@ var/datum/subsystem/tgui/SStgui
|
||||
|
||||
/// A list of UIs scheduled to process
|
||||
var/list/current_run = list()
|
||||
/// A list of open UIs
|
||||
var/list/open_uis = list()
|
||||
/// A list of all UIs
|
||||
var/list/all_uis = list()
|
||||
/// All curent open UIs
|
||||
open_uis = list()
|
||||
/// A list of open UIs, grouped by src_object.
|
||||
var/list/open_uis_by_src = list()
|
||||
/// The HTML base used for all UIs.
|
||||
@@ -25,17 +27,30 @@ var/datum/subsystem/tgui/SStgui
|
||||
|
||||
/datum/subsystem/tgui/Initialize(timeofday)
|
||||
basehtml = file2text('tgui/public/tgui.html')
|
||||
..()
|
||||
|
||||
// Inject inline helper functions
|
||||
var/helpers = file2text('tgui/public/helpers.min.js')
|
||||
helpers = "<script type='text/javascript'>\n[helpers]\n</script>"
|
||||
basehtml = replacetextEx(basehtml, "<!-- tgui:helpers -->", helpers)
|
||||
|
||||
// Inject inline ntos-error styles
|
||||
var/ntos_error = file2text('tgui/public/ntos-error.min.css')
|
||||
ntos_error = "<style type='text/css'>\n[ntos_error]\n</style>"
|
||||
basehtml = replacetextEx(basehtml, "<!-- tgui:ntos-error -->", ntos_error)
|
||||
|
||||
basehtml = replacetextEx(basehtml, "<!-- tgui:nt-copyright -->", "Nanotrasen (c) 2525-[game_year]")
|
||||
|
||||
|
||||
/datum/subsystem/tgui/Shutdown()
|
||||
close_all_uis()
|
||||
|
||||
/datum/subsystem/tgui/stat_entry()
|
||||
..("P:[length(open_uis)]")
|
||||
/datum/subsystem/tgui/stat_entry(msg)
|
||||
msg = "P:[length(all_uis)]"
|
||||
return ..()
|
||||
|
||||
/datum/subsystem/tgui/fire(resumed = FALSE)
|
||||
if(!resumed)
|
||||
src.current_run = open_uis.Copy()
|
||||
src.current_run = all_uis.Copy()
|
||||
// Cache for sanic speed (lists are references anyways)
|
||||
var/list/current_run = src.current_run
|
||||
while(current_run.len)
|
||||
@@ -45,7 +60,7 @@ var/datum/subsystem/tgui/SStgui
|
||||
if(ui?.user && ui.src_object)
|
||||
ui.process(wait * 0.1)
|
||||
else
|
||||
open_uis.Remove(ui)
|
||||
ui.close(0)
|
||||
if(MC_TICK_CHECK)
|
||||
return
|
||||
|
||||
@@ -56,7 +71,7 @@ var/datum/subsystem/tgui/SStgui
|
||||
* Returns null if pool was exhausted.
|
||||
*
|
||||
* required user mob
|
||||
* return datum/tgui
|
||||
* return datum/tgui_window
|
||||
*/
|
||||
/datum/subsystem/tgui/proc/request_pooled_window(mob/user)
|
||||
if(!user.client)
|
||||
@@ -116,8 +131,6 @@ var/datum/subsystem/tgui/SStgui
|
||||
for(var/datum/tgui/ui in user.tgui_open_uis)
|
||||
if(ui.window && ui.window.id == window_id)
|
||||
ui.close(can_be_suspended = FALSE)
|
||||
// Unset machine just to be sure.
|
||||
user.unset_machine()
|
||||
// Close window directly just to be sure.
|
||||
user << browse(null, "window=[window_id]")
|
||||
|
||||
@@ -164,11 +177,10 @@ var/datum/subsystem/tgui/SStgui
|
||||
* return datum/tgui The found UI.
|
||||
*/
|
||||
/datum/subsystem/tgui/proc/get_open_ui(mob/user, datum/src_object)
|
||||
var/key = "[ref(src_object)]"
|
||||
// No UIs opened for this src_object
|
||||
if(isnull(open_uis_by_src[key]) || !istype(open_uis_by_src[key], /list))
|
||||
if(!(src_object?.open_uis?.len))
|
||||
return null
|
||||
for(var/datum/tgui/ui in open_uis_by_src[key])
|
||||
for(var/datum/tgui/ui in src_object.open_uis)
|
||||
// Make sure we have the right user
|
||||
if(ui.user == user)
|
||||
return ui
|
||||
@@ -184,15 +196,14 @@ var/datum/subsystem/tgui/SStgui
|
||||
* return int The number of UIs updated.
|
||||
*/
|
||||
/datum/subsystem/tgui/proc/update_uis(datum/src_object)
|
||||
var/count = 0
|
||||
var/key = "[ref(src_object)]"
|
||||
// No UIs opened for this src_object
|
||||
if(isnull(open_uis_by_src[key]) || !istype(open_uis_by_src[key], /list))
|
||||
return count
|
||||
for(var/datum/tgui/ui in open_uis_by_src[key])
|
||||
if(!(src_object?.open_uis?.len))
|
||||
return 0
|
||||
var/count = 0
|
||||
for(var/datum/tgui/ui in src_object.open_uis)
|
||||
// Check if UI is valid.
|
||||
if(ui?.src_object && ui.user && ui.src_object.ui_host(ui.user))
|
||||
ui.process(wait * 0.1, force = 1)
|
||||
ui.process(wait * 0.1, TRUE)
|
||||
count++
|
||||
return count
|
||||
|
||||
@@ -206,12 +217,11 @@ var/datum/subsystem/tgui/SStgui
|
||||
* return int The number of UIs closed.
|
||||
*/
|
||||
/datum/subsystem/tgui/proc/close_uis(datum/src_object)
|
||||
var/count = 0
|
||||
var/key = "[ref(src_object)]"
|
||||
// No UIs opened for this src_object
|
||||
if(isnull(open_uis_by_src[key]) || !istype(open_uis_by_src[key], /list))
|
||||
return count
|
||||
for(var/datum/tgui/ui in open_uis_by_src[key])
|
||||
if(!(src_object?.open_uis?.len))
|
||||
return 0
|
||||
var/count = 0
|
||||
for(var/datum/tgui/ui in src_object.open_uis)
|
||||
// Check if UI is valid.
|
||||
if(ui?.src_object && ui.user && ui.src_object.ui_host(ui.user))
|
||||
ui.close()
|
||||
@@ -227,12 +237,11 @@ var/datum/subsystem/tgui/SStgui
|
||||
*/
|
||||
/datum/subsystem/tgui/proc/close_all_uis()
|
||||
var/count = 0
|
||||
for(var/key in open_uis_by_src)
|
||||
for(var/datum/tgui/ui in open_uis_by_src[key])
|
||||
// Check if UI is valid.
|
||||
if(ui?.src_object && ui.user && ui.src_object.ui_host(ui.user))
|
||||
ui.close()
|
||||
count++
|
||||
for(var/datum/tgui/ui in all_uis)
|
||||
// Check if UI is valid.
|
||||
if(ui?.src_object && ui.user && ui.src_object.ui_host(ui.user))
|
||||
ui.close()
|
||||
count++
|
||||
return count
|
||||
|
||||
/**
|
||||
@@ -288,8 +297,16 @@ var/datum/subsystem/tgui/SStgui
|
||||
open_uis_by_src[key] = list()
|
||||
ui.user.tgui_open_uis |= ui
|
||||
var/list/uis = open_uis_by_src[key]
|
||||
|
||||
// this feels a bit silly
|
||||
if (ui.src_object.open_uis)
|
||||
ui.src_object.open_uis += ui
|
||||
else
|
||||
ui.src_object.open_uis = list(ui)
|
||||
|
||||
uis |= ui
|
||||
open_uis |= ui
|
||||
all_uis |= ui
|
||||
|
||||
/**
|
||||
* private
|
||||
@@ -301,18 +318,24 @@ var/datum/subsystem/tgui/SStgui
|
||||
* return bool If the UI was removed or not.
|
||||
*/
|
||||
/datum/subsystem/tgui/proc/on_close(datum/tgui/ui)
|
||||
var/key = "[ref(ui.src_object)]"
|
||||
if(isnull(open_uis_by_src[key]) || !istype(open_uis_by_src[key], /list))
|
||||
return FALSE
|
||||
// Remove it from the list of processing UIs.
|
||||
open_uis.Remove(ui)
|
||||
all_uis -= ui
|
||||
open_uis -= ui
|
||||
var/key = "[ref(ui.src_object)]"
|
||||
open_uis_by_src[key] -= ui
|
||||
if (!length(open_uis_by_src[key]))
|
||||
open_uis_by_src -= key
|
||||
current_run -= ui
|
||||
// If the user exists, remove it from them too.
|
||||
if(ui.user)
|
||||
ui.user.tgui_open_uis.Remove(ui)
|
||||
var/list/uis = open_uis_by_src[key]
|
||||
uis.Remove(ui)
|
||||
if(length(uis) == 0)
|
||||
open_uis_by_src.Remove(key)
|
||||
ui.user.tgui_open_uis -= ui
|
||||
|
||||
// basically a lazy remove
|
||||
if(ui.src_object.open_uis)
|
||||
ui.src_object.open_uis -= ui
|
||||
if (!length(ui.src_object.open_uis))
|
||||
ui.src_object.open_uis = null
|
||||
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
@@ -347,7 +370,7 @@ var/datum/subsystem/tgui/SStgui
|
||||
for(var/datum/tgui/ui in source.tgui_open_uis)
|
||||
// Inform the UIs of their new owner.
|
||||
ui.user = target
|
||||
target.tgui_open_uis.Add(ui)
|
||||
target.tgui_open_uis += ui
|
||||
// Clear the old list.
|
||||
source.tgui_open_uis.Cut()
|
||||
return TRUE
|
||||
|
||||
@@ -60,7 +60,7 @@ var/timer_id = 1
|
||||
// Optimization or waste of time?
|
||||
// If a datum has many timers it may be beneficial to check whether thing_to_call.gcDestroyed has been set,
|
||||
// indicating there's no need to remove this timer from its active_timers list, as the list will be nulled right afterwards.
|
||||
if(thing_to_call != GLOBAL_PROC && !thing_to_call.gcDestroyed)
|
||||
if(thing_to_call != GLOBAL_PROC && (isclient(thing_to_call) || !thing_to_call.gcDestroyed))
|
||||
thing_to_call.active_timers -= src
|
||||
if(!thing_to_call.active_timers.len)
|
||||
thing_to_call.active_timers = null
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
for(var/obj/item/weapon/spacecash/C2 in get_contents_in_object(player, /obj/item/weapon/spacecash))
|
||||
cashscore += (C2.amount * C2.worth)
|
||||
|
||||
var/datum/record/money/record = new(player.key, player.job, cashscore)
|
||||
var/datum/data/record/money/record = new(player.key, player.job, cashscore)
|
||||
rich_escapes += record
|
||||
|
||||
if(cashscore > score.richestcash)
|
||||
@@ -91,7 +91,7 @@
|
||||
if(TR.source_name == player.real_name)
|
||||
shoal_amount += text2num(TR.amount)
|
||||
if(shoal_amount > 0)
|
||||
var/datum/record/money/record = new(player.key, player.job, shoal_amount)
|
||||
var/datum/data/record/money/record = new(player.key, player.job, shoal_amount)
|
||||
rich_shoals += record
|
||||
if(shoal_amount > score.biggestshoalcash)
|
||||
score.biggestshoalcash = shoal_amount
|
||||
|
||||
@@ -312,27 +312,27 @@ var/global/datum/controller/gameticker/scoreboard/score = new()
|
||||
var/datum/persistence_task/highscores/leaderboard = score.money_leaderboard
|
||||
dat += "<b>MONTHLY TOP 5 RICHEST ESCAPEES:</b><br>"
|
||||
var/i = 1
|
||||
for(var/datum/record/money/entry in leaderboard.data)
|
||||
var/cash = num2text(entry.cash, 12)
|
||||
var/list/split_date = splittext(entry.date, "-")
|
||||
for(var/datum/data/record/money/entry in leaderboard.data)
|
||||
var/cash = num2text(entry.fields["cash"], 12)
|
||||
var/list/split_date = splittext(entry.fields["date"], "-")
|
||||
if(text2num(split_date[2]) != text2num(time2text(world.timeofday, "MM")))
|
||||
leaderboard.clear_records()
|
||||
dat += "No rich escapees yet!"
|
||||
break
|
||||
else
|
||||
dat += "[i++]) <b>$[cash]</b> by <b>[entry.ckey]</b> ([entry.role]). That shift lasted [entry.shift_duration]. Date: [entry.date]<br>"
|
||||
dat += "[i++]) <b>$[cash]</b> by <b>[entry.fields["ckey"]]</b> ([entry.fields["role"]]). That shift lasted [entry.fields["shift_duration"]]. Date: [entry.fields["date"]]<br>"
|
||||
var/datum/persistence_task/highscores/trader/leaderboard2 = score.shoal_leaderboard
|
||||
dat += "<br><b>MONTHLY TOP 5 RICHEST TRADERS:</b><br>"
|
||||
i = 1
|
||||
for(var/datum/record/money/entry in leaderboard2.data)
|
||||
var/cash = num2text(entry.cash, 12)
|
||||
var/list/split_date = splittext(entry.date, "-")
|
||||
for(var/datum/data/record/money/entry in leaderboard2.data)
|
||||
var/cash = num2text(entry.fields["cash"], 12)
|
||||
var/list/split_date = splittext(entry.fields["date"], "-")
|
||||
if(text2num(split_date[2]) != text2num(time2text(world.timeofday, "MM")))
|
||||
leaderboard2.clear_records()
|
||||
dat += "No rich traders yet!"
|
||||
break
|
||||
else
|
||||
dat += "[i++]) <b>$[cash]</b> by <b>[entry.ckey]</b>. That shift lasted [entry.shift_duration]. Date: [entry.date]<br>"
|
||||
dat += "[i++]) <b>$[cash]</b> by <b>[entry.fields["ckey"]]</b>. That shift lasted [entry.fields["shift_duration"]]. Date: [entry.fields["date"]]<br>"
|
||||
return dat
|
||||
|
||||
/mob/proc/display_round_end_scoreboard()
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
/datum/record/money
|
||||
var/ckey
|
||||
var/role
|
||||
var/cash
|
||||
var/shift_duration
|
||||
var/date
|
||||
/datum/data/record/money
|
||||
fields = list(
|
||||
"ckey" = "",
|
||||
"role" = "",
|
||||
"cash" = -999999,
|
||||
"shift_duration" = -999999,
|
||||
"date" = "0000-00-00",
|
||||
)
|
||||
|
||||
/datum/record/money/New(ckey, role, cash, shift_duration = worldtime2text(), date = SQLtime())
|
||||
src.ckey = ckey
|
||||
src.role = role
|
||||
src.cash = cash
|
||||
src.shift_duration = shift_duration
|
||||
src.date = date
|
||||
/datum/data/record/money/New(ckey, role, cash, shift_duration = worldtime2text(), date = SQLtime())
|
||||
fields["ckey"] = ckey
|
||||
fields["role"] = role
|
||||
fields["cash"] = cash
|
||||
fields["shift_duration"]= shift_duration
|
||||
fields["date"] = date
|
||||
|
||||
@@ -132,7 +132,6 @@ var/list/obj/machinery/camera/cyborg_cams = list(
|
||||
|
||||
/obj/machinery/computer/security/ui_static_data()
|
||||
var/list/data = list()
|
||||
data["title"] = name
|
||||
data["mapRef"] = map_name
|
||||
var/list/cameras = get_available_cameras()
|
||||
data["cameras"] = list()
|
||||
@@ -140,17 +139,20 @@ var/list/obj/machinery/camera/cyborg_cams = list(
|
||||
var/obj/machinery/camera/C = cameras[i]
|
||||
data["cameras"] += list(list(
|
||||
name = C.c_tag,
|
||||
ref = ref(C),
|
||||
))
|
||||
|
||||
return data
|
||||
|
||||
/obj/machinery/computer/security/ui_act(action, params)
|
||||
. = ..()
|
||||
message_admins("Active camera change")
|
||||
if(.)
|
||||
return
|
||||
|
||||
if(action == "switch_camera")
|
||||
var/c_tag = params["name"]
|
||||
message_admins("Act = switch_camera, [c_tag]")
|
||||
var/list/cameras = get_available_cameras()
|
||||
var/obj/machinery/camera/selected_camera = cameras[c_tag]
|
||||
active_camera = selected_camera
|
||||
|
||||
@@ -44,7 +44,7 @@ var/list/message_monitors = list()
|
||||
..()
|
||||
switchboard_headset = new /obj/item/telephone/switchboard (src)
|
||||
message_monitors += src
|
||||
|
||||
|
||||
/obj/machinery/computer/message_monitor/Destroy()
|
||||
message_monitors -= src
|
||||
if(switchboard_headset)
|
||||
@@ -118,7 +118,7 @@ var/list/message_monitors = list()
|
||||
dat += {"<h4><dd><A href='?src=\ref[src];auth=1'>	<font color='red'>\[Unauthenticated\]</font></a>	/
|
||||
Server Power: <u>[src.linkedServer && src.linkedServer.is_functioning() ? "<font color='green'>\[On\]</font>":"<font color='red'>\[Off\]</font>"]</u>"}
|
||||
dat += {" Landline auto-routing: [src.linkedServer && src.linkedServer.landlines_functioning() ? "<font color='green'>\[On\]</font>":"<font color='red'>\[Off\]</font>"]</h4>"}
|
||||
|
||||
|
||||
if(hacking || emagged)
|
||||
screen = 2
|
||||
if(!linkedServer || (linkedServer.stat & (NOPOWER|BROKEN|FORCEDISABLE)))
|
||||
@@ -151,7 +151,7 @@ var/list/message_monitors = list()
|
||||
dat += "<br>"
|
||||
dat += "<hr><A href='?src=\ref[src];switchboard=1'> Landline Switchboard</a><br>"
|
||||
//TODO add exclamation marks here if someone's calling
|
||||
|
||||
|
||||
//Bottom message
|
||||
if(!auth)
|
||||
dat += "<br><hr><dd><span class='notice'>Please authenticate with the server in order to show additional options.</span>"
|
||||
@@ -176,7 +176,7 @@ var/list/message_monitors = list()
|
||||
var/imgdat = ""
|
||||
if(pda.img_sent)
|
||||
user << browse_rsc(pda.img_sent, "tmp_photo_[index].png")
|
||||
imgdat = "<img src='tmp_photo_[index].png' width = '192' style='-ms-interpolation-mode:nearest-neighbor'>"
|
||||
imgdat = "<img src='tmp_photo_[index].png' width = '192' style='image-rendering: pixelated'>"
|
||||
dat += "<tr><td width = '5%'><center><A href='?src=\ref[src];delete=\ref[pda]' style='color: rgb(255,0,0)'>X</a></center></td><td width='15%'>[pda.sender]</td><td width='15%'>[pda.recipient]</td><td width='300px'>[pda.message]</td><th width='30%'>[imgdat]</th></tr>"
|
||||
dat += "</table>"
|
||||
//Hacking screen.
|
||||
@@ -282,7 +282,7 @@ var/list/message_monitors = list()
|
||||
callconnector = "<A href='?src=\ref[src];manualCall=\ref[RC.landline]'>o</A>"
|
||||
else
|
||||
callconnector = "<A href='?src=\ref[src];setLink=\ref[RC.landline]'> </A>"
|
||||
|
||||
|
||||
var/b = RC.landline.get_status()
|
||||
if(b)
|
||||
shortname = "<A href='?src=\ref[src];setCurrentLine=\ref[RC.landline]'><font color=[b]>[shortname]</font></A>"
|
||||
@@ -558,7 +558,7 @@ var/list/message_monitors = list()
|
||||
playsound(L.calling.linked_phone, switchboard_sound2, 100, 1)
|
||||
L.last_call_log += text("call disconnected by operator<BR>")
|
||||
if(L.linked_phone)
|
||||
playsound(L.linked_phone, switchboard_sound2, 100, 1)
|
||||
playsound(L.linked_phone, switchboard_sound2, 100, 1)
|
||||
L.calling = null
|
||||
updateUsrDialog()
|
||||
playsound(src, switchboard_sound2, 100, 1)
|
||||
|
||||
@@ -71,26 +71,28 @@
|
||||
if(!can_control(R,user))
|
||||
continue
|
||||
var/list/cyborg_data = list(
|
||||
name = R.name,
|
||||
locked_down = R.lockdown,
|
||||
status = R.stat,
|
||||
charge = R.cell ? R.cell.percent() : null,
|
||||
module = R.module ? "[R.modtype] Module" : "No Module Installed",
|
||||
master = R.connected_ai,
|
||||
emagged = R.emagged,
|
||||
borgimage = iconsouth2base64(getFlatIconDeluxe(sort_image_datas(get_content_image_datas(R)), override_dir = SOUTH)),
|
||||
ref = ref(R)
|
||||
"name" = R.name,
|
||||
"locked_down" = R.lockdown,
|
||||
"status" = R.stat,
|
||||
"charge" = R.cell ? R.cell.percent() : null,
|
||||
"module" = R.module ? "[R.modtype] Module" : "No Module Installed",
|
||||
"master" = R.connected_ai,
|
||||
"emagged" = R.emagged,
|
||||
"borgimage" = iconsouth2base64(getFlatIconDeluxe(sort_image_datas(get_content_image_datas(R)), override_dir = SOUTH)),
|
||||
"ref" = ref(R)
|
||||
)
|
||||
data["cyborgs"] += list(cyborg_data)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
/obj/machinery/computer/robotics/ui_act(action, params)
|
||||
/obj/machinery/computer/robotics/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
|
||||
message_admins("ui_act called on robotborg")
|
||||
. = ..()
|
||||
if(.)
|
||||
message_admins("uuuh didn't work :)")
|
||||
return
|
||||
|
||||
message_admins("action: [action], params: [json_encode(params)]")
|
||||
switch(action)
|
||||
if("killbot")
|
||||
if(allowed(usr))
|
||||
@@ -106,7 +108,7 @@
|
||||
to_chat(R.connected_ai, "<span style=\"font-family:Courier\"><b>\[<span class='danger'>ALERT</span>\] Slaved Cyborg [R.name] detonated. Signal traced to [get_area(src).name].</b></span>")
|
||||
R.connected_ai << 'sound/machines/twobeep.ogg'
|
||||
R.self_destruct()
|
||||
|
||||
SStgui.try_update_ui(ui.user, src, ui)
|
||||
else
|
||||
to_chat(usr, "<span class='warning'>Access Denied.</span>")
|
||||
|
||||
@@ -128,6 +130,7 @@
|
||||
else
|
||||
to_chat(R.connected_ai, "<span style=\"font-family:Courier\"><b>\[<span class='notice'>INFO</span>\] The lockdown on cyborg [R.name] has been lifted. Signal traced to [get_area(src).name]</b></span>")
|
||||
R.connected_ai << 'sound/misc/notice2.ogg'
|
||||
SStgui.try_update_ui(ui.user, src, ui)
|
||||
else
|
||||
to_chat(usr, "<span class='warning'>Access Denied.</span>")
|
||||
if("hack")
|
||||
@@ -137,6 +140,7 @@
|
||||
log_game("[key_name(usr)] emagged [key_name(R)] using a robotics console!")
|
||||
message_admins("[key_name(usr)] emagged [key_name(R)] using a robotics console!")
|
||||
R.SetEmagged(TRUE)
|
||||
SStgui.try_update_ui(ui.user, src, ui)
|
||||
if(can_control(R, usr) && ismalf(usr))
|
||||
if (!hacking)
|
||||
hacking = 1
|
||||
@@ -148,6 +152,7 @@
|
||||
to_chat(usr, "<span style=\"font-family:Courier\"><b>\[<span class='danger'>ERROR</span>\] [R.name]: SAFETIES DISABLED.</b></span>")
|
||||
usr << 'sound/misc/notice2.ogg'
|
||||
hacking = 0
|
||||
SStgui.try_update_ui(ui.user, src, ui)
|
||||
else
|
||||
to_chat(usr, "You are already hacking a cyborg.")
|
||||
if("sequence")
|
||||
@@ -159,6 +164,7 @@
|
||||
stop_sequence()
|
||||
message_admins("<span class='notice'>[key_name_admin(usr)] [formatJumpTo(usr)] has halted the global cyborg killswitch!</span>")
|
||||
log_game("<span class='notice'>[key_name(usr)] has halted the global cyborg killswitch!</span>")
|
||||
SStgui.try_update_ui(ui.user, src, ui)
|
||||
return TRUE
|
||||
|
||||
/obj/machinery/computer/robotics/proc/can_control(var/mob/living/silicon/robot/robot, var/mob/controller)
|
||||
|
||||
@@ -432,7 +432,7 @@ var/list/obj/machinery/newscaster/allCasters = list() //Global list that will co
|
||||
if(MESSAGE.img)
|
||||
usr << browse_rsc(MESSAGE.img, "tmp_photo[i].png")
|
||||
|
||||
dat+="<BR><a href='?src=\ref[src];show_photo_info=\ref[MESSAGE]'><img src='tmp_photo[i].png' width = '192' style='-ms-interpolation-mode:nearest-neighbor'></a><BR>"
|
||||
dat+="<BR><a href='?src=\ref[src];show_photo_info=\ref[MESSAGE]'><img src='tmp_photo[i].png' width = '192' style='image-rendering: pixelated'></a><BR>"
|
||||
dat+="<BR><FONT SIZE=1>\[Story by <FONT COLOR='maroon'>[MESSAGE.author]</FONT>\]</FONT><BR><HR>"
|
||||
|
||||
dat += {"<A href='?src=\ref[src];refresh=1'>Refresh</A>
|
||||
|
||||
@@ -33,23 +33,24 @@
|
||||
var/area/A = get_area(M)
|
||||
if(!M)
|
||||
continue
|
||||
var/icon/mech_icon = getFlatIconDeluxe(sort_image_datas(get_content_image_datas(M)), override_dir = SOUTH)
|
||||
var/list/mecha_data = list(
|
||||
name = M.name,
|
||||
health = round((M.health/initial(M.health))*100),
|
||||
charge = M.cell ? round(M.cell.percent()) : null,
|
||||
pilot = M.occupant || "None",
|
||||
location = A.name || "Unknown",
|
||||
active = M.selected ? M.selected.name : "None",
|
||||
status = M.state,
|
||||
mechaimage = iconsouth2base64(getFlatIconDeluxe(sort_image_datas(get_content_image_datas(M)), override_dir = SOUTH)),
|
||||
log = TR.get_mecha_log(),
|
||||
ref = ref(M)
|
||||
"name" = M.name,
|
||||
"health" = round((M.health/initial(M.health))*100),
|
||||
"charge" = M.cell ? round(M.cell.percent()) : null,
|
||||
"pilot" = M.occupant || "None",
|
||||
"location" = A.name || "Unknown",
|
||||
"active" = M.selected ? M.selected.name : "None",
|
||||
"status" = M.state,
|
||||
"mechaimage" = iconsouth2base64(mech_icon),
|
||||
"log" = TR.get_mecha_log(),
|
||||
"ref" = ref(M)
|
||||
)
|
||||
data["mechas"] += list(mecha_data)
|
||||
|
||||
return data
|
||||
|
||||
/obj/machinery/computer/mecha/ui_act(action, params)
|
||||
/obj/machinery/computer/mecha/ui_act(action, list/params)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
|
||||
@@ -557,7 +557,7 @@
|
||||
var/icon/img = app.imglist[note]
|
||||
if(img)
|
||||
usr << browse_rsc(img, "tmp_photo_[note].png")
|
||||
dat += "<img src='tmp_photo_[note].png' width = '192' style='-ms-interpolation-mode:nearest-neighbor'><BR>"
|
||||
dat += "<img src='tmp_photo_[note].png' width = '192' style='image-rendering: pixelated'><BR>"
|
||||
dat += "</body></html>"
|
||||
usr << browse(HTML_SKELETON(dat), "window=log;size=400x444;border=1;can_resize=1;can_close=1;can_minimize=0")
|
||||
|
||||
@@ -576,7 +576,7 @@
|
||||
var/icon/img = app.imglist[note]
|
||||
if(img)
|
||||
usr << browse_rsc(img, "tmp_photo_[note].png")
|
||||
dat += "<img src='tmp_photo_[note].png' width = '192' style='-ms-interpolation-mode:nearest-neighbor'><BR>"
|
||||
dat += "<img src='tmp_photo_[note].png' width = '192' style='image-rendering: pixelated'><BR>"
|
||||
dat += "</body></html>"
|
||||
usr << browse(HTML_SKELETON(dat), "window=log;size=400x444;border=1;can_resize=1;can_close=1;can_minimize=0")
|
||||
else
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
var/icon/img = imglist[note]
|
||||
if(img)
|
||||
user << browse_rsc(ImagePDA(img), "tmp_photo_[note].png")
|
||||
dat += "<img src='tmp_photo_[note].png' width = '192' style='-ms-interpolation-mode:nearest-neighbor'><BR>"
|
||||
dat += "<img src='tmp_photo_[note].png' width = '192' style='image-rendering: pixelated'><BR>"
|
||||
dat += "<br>"
|
||||
return dat
|
||||
|
||||
|
||||
@@ -250,7 +250,7 @@
|
||||
if(7)
|
||||
displaylength = 448
|
||||
|
||||
dat += {"<div style='float: left'> <img src='tmp_photo_gallery_[i].png' width='[displaylength]' style='-ms-interpolation-mode:nearest-neighbor' /> </div>"}
|
||||
dat += {"<div style='float: left'> <img src='tmp_photo_gallery_[i].png' width='[displaylength]' style='image-rendering: pixelated' /> </div>"}
|
||||
i++
|
||||
return dat
|
||||
|
||||
|
||||
@@ -199,6 +199,7 @@
|
||||
transmitting = TRUE
|
||||
set_light(1)
|
||||
update_icon()
|
||||
SStgui.try_update_ui(ui.user, src, ui)
|
||||
return TRUE
|
||||
if("turn_off")
|
||||
if(emped || !transmitting || !Adjacent(usr) || usr.incapacitated())
|
||||
@@ -206,13 +207,16 @@
|
||||
transmitting = FALSE
|
||||
set_light(0)
|
||||
update_icon()
|
||||
SStgui.try_update_ui(ui.user, src, ui)
|
||||
return TRUE
|
||||
if("toggle_refresh")
|
||||
autorefreshing = !autorefreshing
|
||||
SStgui.try_update_ui(ui.user, src, ui)
|
||||
return TRUE
|
||||
if("toggle_injury")
|
||||
if(fullmode)
|
||||
injuryonly = !injuryonly
|
||||
SStgui.try_update_ui(ui.user, src, ui)
|
||||
return TRUE
|
||||
else
|
||||
return FALSE
|
||||
@@ -249,4 +253,4 @@
|
||||
"Suit Sensor-Sensor",
|
||||
"Crew Monitoring Device",
|
||||
"Baby Monitor",
|
||||
"Life-Alert")
|
||||
"Life-Alert")
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
feedback_add_details("admin_verb","DG2") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
|
||||
|
||||
/* 21st sept 2010
|
||||
Updated by Skie -- Still not perfect but better!
|
||||
Stuff you can't do:
|
||||
@@ -37,7 +36,7 @@ But you can call procs that are of type /mob/living/carbon/human/proc/ for that
|
||||
return
|
||||
|
||||
spawn(0)
|
||||
var/target = null
|
||||
var/datum/target = null
|
||||
var/targetselected = 0
|
||||
var/lst[] // List reference
|
||||
lst = new/list() // Make the list
|
||||
|
||||
53
code/modules/asset_cache/asset_cache_client.dm
Normal file
53
code/modules/asset_cache/asset_cache_client.dm
Normal file
@@ -0,0 +1,53 @@
|
||||
|
||||
// Client tg_asset things
|
||||
/client
|
||||
var/last_completed_asset_job
|
||||
var/list/sent_assets = list()
|
||||
|
||||
// Connection time
|
||||
var/connection_time
|
||||
|
||||
/// Process asset cache client topic calls for `"asset_cache_confirm_arrival=[INT]"`
|
||||
/client/proc/asset_cache_confirm_arrival(job_id)
|
||||
var/asset_cache_job = round(text2num(job_id))
|
||||
//because we skip the limiter, we have to make sure this is a valid arrival and not somebody tricking us into letting them append to a list without limit.
|
||||
if (asset_cache_job > 0 && asset_cache_job <= last_asset_job && !(completed_asset_jobs["[asset_cache_job]"]))
|
||||
completed_asset_jobs["[asset_cache_job]"] = TRUE
|
||||
last_completed_asset_job = max(last_completed_asset_job, asset_cache_job)
|
||||
else
|
||||
return asset_cache_job || TRUE
|
||||
|
||||
|
||||
/// Process asset cache client topic calls for `"asset_cache_preload_data=[HTML+JSON_STRING]"`
|
||||
/client/proc/asset_cache_preload_data(data)
|
||||
var/json = data
|
||||
var/list/preloaded_assets = json_decode(json)
|
||||
|
||||
for (var/preloaded_asset in preloaded_assets)
|
||||
if (copytext(preloaded_asset, findlasttext(preloaded_asset, ".")+1) in list("js", "jsm", "htm", "html"))
|
||||
preloaded_assets -= preloaded_asset
|
||||
continue
|
||||
sent_assets |= preloaded_assets
|
||||
|
||||
|
||||
/// Updates the client side stored json file used to keep track of what assets the client has between restarts/reconnects.
|
||||
/client/proc/asset_cache_update_json()
|
||||
if (world.time - connection_time < 10 SECONDS) //don't override the existing data file on a new connection
|
||||
return
|
||||
|
||||
src << browse(json_encode(sent_assets), "file=asset_data.json&display=0")
|
||||
|
||||
/// Blocks until all currently sending browse and browse_rsc assets have been sent.
|
||||
/// Due to byond limitations, this proc will sleep for 1 client round trip even if the client has no pending asset sends.
|
||||
/// This proc will return an untrue value if it had to return before confirming the send, such as timeout or the client going away.
|
||||
/client/proc/tg_browse_queue_flush(timeout = 50)
|
||||
var/job = ++last_asset_job
|
||||
var/t = 0
|
||||
var/timeout_time = timeout
|
||||
src << browse({"<script>window.location.href='byond://?asset_cache_confirm_arrival=[job]'</script>"}, "window=asset_cache_browser&file=asset_cache_send_verify.htm")
|
||||
|
||||
while(!completed_asset_jobs["[job]"] && t < timeout_time) // Reception is handled in Topic()
|
||||
stoplag(1) // Lock up the caller until this is received.
|
||||
t++
|
||||
if (t < timeout_time)
|
||||
return TRUE
|
||||
51
code/modules/asset_cache/asset_cache_item.dm
Normal file
51
code/modules/asset_cache/asset_cache_item.dm
Normal file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* # asset_cache_item
|
||||
*
|
||||
* An internal datum containing info on items in the asset cache. Mainly used to cache md5 info for speed.
|
||||
*/
|
||||
/datum/asset_cache_item
|
||||
/// the name of this asset item, becomes the key in SSassets.cache list
|
||||
var/name
|
||||
/// md5() of the file this asset item represents.
|
||||
var/hash
|
||||
/// the file this asset represents
|
||||
var/resource
|
||||
/// our file extension e.g. .png, .gif, etc
|
||||
var/ext = ""
|
||||
/// Should this file also be sent via the legacy browse_rsc system
|
||||
/// when cdn transports are enabled?
|
||||
var/legacy = FALSE
|
||||
/// Used by the cdn system to keep legacy css assets with their parent
|
||||
/// css file. (css files resolve urls relative to the css file, so the
|
||||
/// legacy system can't be used if the css file itself could go out over
|
||||
/// the cdn)
|
||||
var/namespace = null
|
||||
/// True if this is the parent css or html file for an asset's namespace
|
||||
var/namespace_parent = FALSE
|
||||
/// TRUE for keeping local asset names when browse_rsc backend is used
|
||||
var/keep_local_name = FALSE
|
||||
|
||||
///pass in a valid file_hash if you have one to save it from needing to do it again.
|
||||
///pass in a valid dmi file path string e.g. "icons/path/to/dmi_file.dmi" to make generating the hash less expensive
|
||||
/datum/asset_cache_item/New(name, file, file_hash, dmi_file_path)
|
||||
if (!isfile(file))
|
||||
file = fcopy_rsc(file)
|
||||
|
||||
hash = file_hash
|
||||
|
||||
//the given file is directly from a dmi file and is thus in the rsc already, we know that its file_hash will be correct
|
||||
if(!hash)
|
||||
if(dmi_file_path)
|
||||
hash = md5(file)
|
||||
else
|
||||
hash = md5asfile(file) //icons sent to the rsc md5 incorrectly when theyre given incorrect data
|
||||
if (!hash)
|
||||
CRASH("invalid asset sent to asset cache")
|
||||
src.name = name
|
||||
var/extstart = findlasttext(name, ".")
|
||||
if (extstart)
|
||||
ext = ".[copytext(name, extstart+1)]"
|
||||
resource = file
|
||||
|
||||
/datum/asset_cache_item/variable_edited(variable_name, old_value, new_value)
|
||||
return FALSE
|
||||
225
code/modules/asset_cache/asset_list.dm
Normal file
225
code/modules/asset_cache/asset_list.dm
Normal file
@@ -0,0 +1,225 @@
|
||||
//These datums are used to populate the asset cache, the proc "register()" does this.
|
||||
//Place any asset datums you create in asset_list_items.dm
|
||||
|
||||
//all of our asset datums, used for referring to these later
|
||||
var/list/tg_asset_datums = list()
|
||||
|
||||
//get an assetdatum or make a new one
|
||||
//does NOT ensure it's filled, if you want that use get_tg_asset_datum()
|
||||
/proc/load_asset_datum(type)
|
||||
return global.tg_asset_datums[type] || new type()
|
||||
|
||||
/proc/get_tg_asset_datum(type)
|
||||
var/datum/tg_asset/loaded_asset = global.tg_asset_datums[type] || new type()
|
||||
return loaded_asset.ensure_ready()
|
||||
|
||||
/datum/tg_asset
|
||||
var/_abstract = /datum/tg_asset
|
||||
var/cached_serialized_url_mappings
|
||||
var/cached_serialized_url_mappings_transport_type
|
||||
|
||||
/// Whether or not this asset should be loaded in the "early assets" SS
|
||||
var/early = FALSE
|
||||
|
||||
/// Whether or not this asset can be cached across rounds of the same commit under the `CACHE_ASSETS` config.
|
||||
/// This is not a *guarantee* the asset will be cached. Not all asset subtypes respect this field, and the
|
||||
/// config can, of course, be disabled.
|
||||
/// Disable this if your asset can change between rounds on the same exact version of the code.
|
||||
var/cross_round_cachable = FALSE
|
||||
|
||||
/datum/tg_asset/New()
|
||||
global.tg_asset_datums[type] = src
|
||||
register()
|
||||
|
||||
/// Stub that allows us to react to something trying to get us
|
||||
/// Not useful here, more handy for sprite sheets
|
||||
/datum/tg_asset/proc/ensure_ready()
|
||||
return src
|
||||
|
||||
/// Stub to hook into if your asset is having its generation queued by SSasset_loading
|
||||
/datum/tg_asset/proc/queued_generation()
|
||||
CRASH("[type] inserted into SSasset_loading despite not implementing /proc/queued_generation")
|
||||
|
||||
/datum/tg_asset/proc/get_url_mappings()
|
||||
return list()
|
||||
|
||||
/// Returns a cached tgui message of URL mappings
|
||||
/datum/tg_asset/proc/get_serialized_url_mappings()
|
||||
if (isnull(cached_serialized_url_mappings) || cached_serialized_url_mappings_transport_type != SSassets.transport.type)
|
||||
cached_serialized_url_mappings = TGUI_CREATE_MESSAGE("asset/mappings", get_url_mappings())
|
||||
cached_serialized_url_mappings_transport_type = SSassets.transport.type
|
||||
|
||||
return cached_serialized_url_mappings
|
||||
|
||||
/datum/tg_asset/proc/register()
|
||||
return
|
||||
|
||||
/datum/tg_asset/proc/send(client)
|
||||
return
|
||||
|
||||
/// Returns whether or not the asset should attempt to read from cache
|
||||
/datum/tg_asset/proc/should_refresh()
|
||||
return !cross_round_cachable || !config.cache_assets
|
||||
|
||||
/// Immediately regenerate the asset, overwriting any cache.
|
||||
/datum/tg_asset/proc/regenerate()
|
||||
unregister()
|
||||
cached_serialized_url_mappings = null
|
||||
cached_serialized_url_mappings_transport_type = null
|
||||
register()
|
||||
|
||||
/// Unregisters any assets from the transport.
|
||||
/datum/tg_asset/proc/unregister()
|
||||
CRASH("unregister() not implemented for asset [type]!")
|
||||
|
||||
/// Simply takes any generated file and saves it to the round-specific /logs folder. Useful for debugging potential issues with spritesheet generation/display.
|
||||
/// Only called when the SAVE_SPRITESHEETS config option is uncommented.
|
||||
/datum/tg_asset/proc/save_to_logs(file_name, file_location)
|
||||
var/asset_path = "data/logs/[date_string]/generated_assets/[file_name]"
|
||||
fdel(asset_path) // just in case, sadly we can't use rust_g stuff here.
|
||||
fcopy(file_location, asset_path)
|
||||
|
||||
/// If you don't need anything complicated.
|
||||
/datum/tg_asset/simple
|
||||
_abstract = /datum/tg_asset/simple
|
||||
/// list of assets for this datum in the form of:
|
||||
/// asset_filename = asset_file. At runtime the asset_file will be
|
||||
/// converted into a asset_cache datum.
|
||||
var/assets = list()
|
||||
/// Set to true to have this asset also be sent via the legacy browse_rsc
|
||||
/// system when cdn transports are enabled?
|
||||
var/legacy = FALSE
|
||||
/// TRUE for keeping local asset names when browse_rsc backend is used
|
||||
var/keep_local_name = FALSE
|
||||
|
||||
/datum/tg_asset/simple/register()
|
||||
for(var/asset_name in assets)
|
||||
var/datum/asset_cache_item/ACI = SSassets.transport.register_asset(asset_name, assets[asset_name])
|
||||
if (!ACI)
|
||||
log_debug("ERROR: Invalid asset: [type]:[asset_name]:[ACI]")
|
||||
continue
|
||||
if (legacy)
|
||||
ACI.legacy = legacy
|
||||
if (keep_local_name)
|
||||
ACI.keep_local_name = keep_local_name
|
||||
assets[asset_name] = ACI
|
||||
|
||||
/datum/tg_asset/simple/send(client)
|
||||
. = SSassets.transport.send_assets(client, assets)
|
||||
|
||||
/datum/tg_asset/simple/get_url_mappings()
|
||||
. = list()
|
||||
for (var/asset_name in assets)
|
||||
.[asset_name] = SSassets.transport.get_asset_url(asset_name, assets[asset_name])
|
||||
|
||||
/datum/tg_asset/simple/unregister()
|
||||
for (var/asset_name in assets)
|
||||
SSassets.transport.unregister_asset(asset_name)
|
||||
|
||||
// For registering or sending multiple others at once
|
||||
/datum/tg_asset/group
|
||||
_abstract = /datum/tg_asset/group
|
||||
var/list/children
|
||||
|
||||
/datum/tg_asset/group/register()
|
||||
for(var/type in children)
|
||||
load_asset_datum(type)
|
||||
|
||||
/datum/tg_asset/group/send(client/C)
|
||||
for(var/type in children)
|
||||
var/datum/tg_asset/A = get_tg_asset_datum(type)
|
||||
. = A.send(C) || .
|
||||
|
||||
/datum/tg_asset/group/get_url_mappings()
|
||||
. = list()
|
||||
for(var/type in children)
|
||||
var/datum/tg_asset/A = get_tg_asset_datum(type)
|
||||
. += A.get_url_mappings()
|
||||
|
||||
/datum/tg_asset/group/unregister()
|
||||
for (var/type in children)
|
||||
var/datum/tg_asset/A = get_tg_asset_datum(type)
|
||||
A.unregister()
|
||||
|
||||
// -- IMPLEMENTABLE : tg_asset for changelog items
|
||||
|
||||
//Generates assets based on iconstates of a single icon
|
||||
// -- IMPLEMENTABLE : tg_asset for icon_states
|
||||
|
||||
|
||||
/// Namespace'ed assets (for static css and html files)
|
||||
/// When sent over a cdn transport, all assets in the same asset datum will exist in the same folder, as their plain names.
|
||||
/// Used to ensure css files can reference files by url() without having to generate the css at runtime, both the css file and the files it depends on must exist in the same namespace asset datum. (Also works for html)
|
||||
/// For example `blah.css` with asset `blah.png` will get loaded as `namespaces/a3d..14f/f12..d3c.css` and `namespaces/a3d..14f/blah.png`. allowing the css file to load `blah.png` by a relative url rather then compute the generated url with get_url_mappings().
|
||||
/// The namespace folder's name will change if any of the assets change. (excluding parent assets)
|
||||
/datum/tg_asset/simple/namespaced
|
||||
_abstract = /datum/tg_asset/simple/namespaced
|
||||
/// parents - list of the parent asset or assets (in name = file assoicated format) for this namespace.
|
||||
/// parent assets must be referenced by their generated url, but if an update changes a parent asset, it won't change the namespace's identity.
|
||||
var/list/parents = list()
|
||||
|
||||
/datum/tg_asset/simple/namespaced/register()
|
||||
if (legacy)
|
||||
assets |= parents
|
||||
var/list/hashlist = list()
|
||||
var/list/sorted_assets = sortList(assets)
|
||||
|
||||
for (var/asset_name in sorted_assets)
|
||||
var/datum/asset_cache_item/ACI = new(asset_name, sorted_assets[asset_name])
|
||||
if (!ACI?.hash)
|
||||
log_debug("ERROR: Invalid asset: [type]:[asset_name]:[ACI]")
|
||||
continue
|
||||
hashlist += ACI.hash
|
||||
sorted_assets[asset_name] = ACI
|
||||
var/namespace = md5(hashlist.Join())
|
||||
|
||||
for (var/asset_name in parents)
|
||||
var/datum/asset_cache_item/ACI = new(asset_name, parents[asset_name])
|
||||
if (!ACI?.hash)
|
||||
log_debug("ERROR: Invalid asset: [type]:[asset_name]:[ACI]")
|
||||
continue
|
||||
ACI.namespace_parent = TRUE
|
||||
sorted_assets[asset_name] = ACI
|
||||
|
||||
for (var/asset_name in sorted_assets)
|
||||
var/datum/asset_cache_item/ACI = sorted_assets[asset_name]
|
||||
if (!ACI?.hash)
|
||||
log_debug("ERROR: Invalid asset: [type]:[asset_name]:[ACI]")
|
||||
continue
|
||||
ACI.namespace = namespace
|
||||
|
||||
assets = sorted_assets
|
||||
..()
|
||||
|
||||
/// Get a html string that will load a html asset.
|
||||
/// Needed because byond doesn't allow you to browse() to a url.
|
||||
/datum/tg_asset/simple/namespaced/proc/get_htmlloader(filename)
|
||||
return url2htmlloader(SSassets.transport.get_asset_url(filename, assets[filename]))
|
||||
|
||||
/// A subtype to generate a JSON file from a list
|
||||
/datum/tg_asset/json
|
||||
_abstract = /datum/tg_asset/json
|
||||
/// The filename, will be suffixed with ".json"
|
||||
var/name
|
||||
|
||||
/datum/tg_asset/json/send(client)
|
||||
return SSassets.transport.send_assets(client, "[name].json")
|
||||
|
||||
/datum/tg_asset/json/get_url_mappings()
|
||||
return list(
|
||||
"[name].json" = SSassets.transport.get_asset_url("[name].json"),
|
||||
)
|
||||
|
||||
/datum/tg_asset/json/register()
|
||||
var/filename = "data/[name].json"
|
||||
fdel(filename)
|
||||
rustg_file_write(json_encode(generate()), filename)
|
||||
SSassets.transport.register_asset("[name].json", fcopy_rsc(filename))
|
||||
fdel(filename)
|
||||
|
||||
/// Returns the data that will be JSON encoded
|
||||
/datum/tg_asset/json/proc/generate()
|
||||
CRASH("generate() not implemented for [type]!")
|
||||
|
||||
/datum/tg_asset/json/unregister()
|
||||
SSassets.transport.unregister_asset("[name].json")
|
||||
6
code/modules/asset_cache/assets/tgfont.dm
Normal file
6
code/modules/asset_cache/assets/tgfont.dm
Normal file
@@ -0,0 +1,6 @@
|
||||
/datum/tg_asset/simple/tgfont
|
||||
assets = list(
|
||||
"tgfont.eot" = 'tgui/packages/tgfont/static/tgfont.eot',
|
||||
"tgfont.woff2" = 'tgui/packages/tgfont/static/tgfont.woff2',
|
||||
"tgfont.css" = 'tgui/packages/tgfont/static/tgfont.css',
|
||||
)
|
||||
35
code/modules/asset_cache/assets/tgui.dm
Normal file
35
code/modules/asset_cache/assets/tgui.dm
Normal file
@@ -0,0 +1,35 @@
|
||||
// If you use a file(...) object, instead of caching the asset it will be loaded from disk every time it's requested.
|
||||
// This is useful for development, but not recommended for production.
|
||||
// And if TGS is defined, we're being run in a production environment.
|
||||
|
||||
#ifdef TGS
|
||||
/datum/tg_asset/simple/tgui
|
||||
keep_local_name = FALSE
|
||||
assets = list(
|
||||
"tgui.bundle.js" = "tgui/public/tgui.bundle.js",
|
||||
"tgui.bundle.css" = "tgui/public/tgui.bundle.css",
|
||||
)
|
||||
|
||||
/datum/tg_asset/simple/tgui_panel
|
||||
keep_local_name = FALSE
|
||||
assets = list(
|
||||
"tgui-panel.bundle.js" = "tgui/public/tgui-panel.bundle.js",
|
||||
"tgui-panel.bundle.css" = "tgui/public/tgui-panel.bundle.css",
|
||||
)
|
||||
|
||||
#else
|
||||
/datum/tg_asset/simple/tgui
|
||||
keep_local_name = TRUE
|
||||
assets = list(
|
||||
"tgui.bundle.js" = file("tgui/public/tgui.bundle.js"),
|
||||
"tgui.bundle.css" = file("tgui/public/tgui.bundle.css"),
|
||||
)
|
||||
|
||||
/datum/tg_asset/simple/tgui_panel
|
||||
keep_local_name = TRUE
|
||||
assets = list(
|
||||
"tgui-panel.bundle.js" = file("tgui/public/tgui-panel.bundle.js"),
|
||||
"tgui-panel.bundle.css" = file("tgui/public/tgui-panel.bundle.css"),
|
||||
)
|
||||
|
||||
#endif
|
||||
8
code/modules/asset_cache/font_awesome.dm
Normal file
8
code/modules/asset_cache/font_awesome.dm
Normal file
@@ -0,0 +1,8 @@
|
||||
/datum/tg_asset/simple/fontawesome
|
||||
assets = list(
|
||||
"fa-regular-400.ttf" = 'html/font-awesome/webfonts/fa-regular-400.ttf',
|
||||
"fa-solid-900.ttf" = 'html/font-awesome/webfonts/fa-solid-900.ttf',
|
||||
"fa-v4compatibility.ttf" = 'html/font-awesome/webfonts/fa-v4compatibility.ttf',
|
||||
"v4shim.css" = 'html/font-awesome/css/v4-shims.min.css',
|
||||
"font-awesome.css" = 'html/font-awesome/css/all.min.css',
|
||||
)
|
||||
28
code/modules/asset_cache/icon_ref_map.dm
Normal file
28
code/modules/asset_cache/icon_ref_map.dm
Normal file
@@ -0,0 +1,28 @@
|
||||
/// Maps icon names to ref values
|
||||
/datum/tg_asset/json/icon_ref_map
|
||||
name = "icon_ref_map"
|
||||
early = TRUE
|
||||
|
||||
/datum/tg_asset/json/icon_ref_map/generate()
|
||||
var/list/data = list() //"icons/obj/drinks.dmi" => "[0xc000020]"
|
||||
|
||||
//var/start = "0xc000000"
|
||||
var/value = 0
|
||||
|
||||
while(TRUE)
|
||||
value += 1
|
||||
var/ref = "\[0xc[num2text(value,6,16)]\]"
|
||||
var/mystery_meat = locate(ref)
|
||||
|
||||
if(isicon(mystery_meat))
|
||||
if(!isfile(mystery_meat)) // Ignore the runtime icons for now
|
||||
continue
|
||||
var/path = get_icon_dmi_path(mystery_meat) //Try to get the icon path
|
||||
if(path)
|
||||
data[path] = ref
|
||||
else if(mystery_meat)
|
||||
continue; //Some other non-icon resource, ogg/json/whatever
|
||||
else //Out of resources end this, could also try to end this earlier as soon as runtime generated icons appear but eh
|
||||
break;
|
||||
|
||||
return data
|
||||
37
code/modules/asset_cache/readme.md
Normal file
37
code/modules/asset_cache/readme.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Asset cache system
|
||||
|
||||
## Framework for managing browser assets (javascript,css,images,etc)
|
||||
|
||||
This manages getting the asset to the client without doing unneeded re-sends, as well as utilizing any configured cdns.
|
||||
|
||||
There are two frameworks for using this system:
|
||||
|
||||
### Asset datum:
|
||||
|
||||
Make a datum in asset_list_items.dm with your browser assets for your thing.
|
||||
|
||||
Checkout asset_list.dm for the helper subclasses
|
||||
|
||||
The `simple` subclass will most likely be of use for most cases.
|
||||
|
||||
Call get_tg_asset_datum() with the type of the datum you created to get your asset cache datum
|
||||
|
||||
Call .send(client|usr) on that datum to send the asset to the client. Depending on the asset transport this may or may not block.
|
||||
|
||||
Call .get_url_mappings() to get an associated list with the urls your assets can be found at.
|
||||
|
||||
### Manual backend:
|
||||
|
||||
See the documentation for `/datum/asset_transport` for the backend api the asset datums utilize.
|
||||
|
||||
The global variable `SSassets.transport` contains the currently configured transport.
|
||||
|
||||
|
||||
|
||||
### Notes:
|
||||
|
||||
Because byond browse() calls use non-blocking queues, if your code uses output() (which bypasses all of these queues) to invoke javascript functions you will need to first have the javascript announce to the server it has loaded before trying to invoke js functions.
|
||||
|
||||
To make your code work with any CDNs configured by the server, you must make sure assets are referenced from the url returned by `get_url_mappings()` or by asset_transport's `get_asset_url()`. (TGUI also has helpers for this.) If this can not be easily done, you can bypass the cdn using legacy assets, see the simple asset datum for details.
|
||||
|
||||
CSS files that use url() can be made to use the CDN without needing to rewrite all url() calls in code by using the namespaced helper datum. See the documentation for `/datum/tg_asset/simple/namespaced` for details.
|
||||
171
code/modules/asset_cache/transports/asset_transport.dm
Normal file
171
code/modules/asset_cache/transports/asset_transport.dm
Normal file
@@ -0,0 +1,171 @@
|
||||
/// When sending mutiple assets, how many before we give the client a quaint little sending resources message
|
||||
#define ASSET_CACHE_TELL_CLIENT_AMOUNT 8
|
||||
/// How many assets can be sent at once during legacy asset transport
|
||||
#define SLOW_ASSET_SEND_RATE 6
|
||||
|
||||
/// Base browse_rsc asset transport
|
||||
/datum/asset_transport
|
||||
var/name = "Simple browse_rsc asset transport"
|
||||
var/static/list/preload
|
||||
/// Don't mutate the filename of assets when sending via browse_rsc.
|
||||
/// This is to make it easier to debug issues with assets, and allow server operators to bypass issues that make it to production.
|
||||
/// If turning this on fixes asset issues, something isn't using get_asset_url and the asset isn't marked legacy, fix one of those.
|
||||
var/dont_mutate_filenames = FALSE
|
||||
|
||||
/// Called when the transport is loaded by the config controller, not called on the default transport unless it gets loaded by a config change.
|
||||
/datum/asset_transport/proc/Load()
|
||||
if (config.asset_simple_preload)
|
||||
for(var/client/C in global.clients)
|
||||
add_timer(new /callback(src, PROC_REF(send_assets_slow), C, preload), 1 SECONDS)
|
||||
|
||||
/// Initialize - Called when SSassets initializes.
|
||||
/datum/asset_transport/proc/Initialize(list/assets)
|
||||
preload = assets.Copy()
|
||||
if (!config.asset_simple_preload)
|
||||
return
|
||||
for(var/client/C in global.clients)
|
||||
add_timer(new /callback(src, PROC_REF(send_assets_slow), C, preload), 1 SECONDS)
|
||||
|
||||
|
||||
/**
|
||||
* Register a browser asset with the asset cache system.
|
||||
* returns a /datum/tg_asset_cache_item.
|
||||
* mutiple calls to register the same asset under the same asset_name return the same datum.
|
||||
*
|
||||
* Arguments:
|
||||
* * asset_name - the identifier of the asset.
|
||||
* * asset - the actual asset file (or an asset_cache_item datum).
|
||||
* * file_hash - optional, a hash of the contents of the asset files contents. used so asset_cache_item doesnt have to hash it again
|
||||
* * dmi_file_path - optional, means that the given asset is from the rsc and thus we dont need to do some expensive operations
|
||||
*/
|
||||
/datum/asset_transport/proc/register_asset(asset_name, asset, file_hash, dmi_file_path)
|
||||
var/datum/asset_cache_item/ACI = asset
|
||||
if (!istype(ACI))
|
||||
ACI = new(asset_name, asset, file_hash, dmi_file_path)
|
||||
if (!ACI || !ACI.hash)
|
||||
CRASH("ERROR: Invalid asset: [asset_name]:[asset]:[ACI]")
|
||||
if (SSassets.cache[asset_name])
|
||||
var/datum/asset_cache_item/OACI = SSassets.cache[asset_name]
|
||||
OACI.legacy = ACI.legacy = (ACI.legacy|OACI.legacy)
|
||||
OACI.namespace_parent = ACI.namespace_parent = (ACI.namespace_parent | OACI.namespace_parent)
|
||||
OACI.namespace = OACI.namespace || ACI.namespace
|
||||
if (OACI.hash != ACI.hash)
|
||||
var/error_msg = "ERROR: new asset added to the asset cache with the same name as another asset: [asset_name] existing asset hash: [OACI.hash] new asset hash:[ACI.hash]"
|
||||
stack_trace(error_msg)
|
||||
log_debug(error_msg)
|
||||
else
|
||||
if (length(ACI.namespace))
|
||||
return ACI
|
||||
return OACI
|
||||
|
||||
SSassets.cache[asset_name] = ACI
|
||||
return ACI
|
||||
|
||||
/// Immediately removes an asset from the asset cache.
|
||||
/datum/asset_transport/proc/unregister_asset(asset_name)
|
||||
SSassets.cache[asset_name] = null
|
||||
SSassets.cache.Remove(null)
|
||||
|
||||
/// Returns a url for a given asset.
|
||||
/// asset_name - Name of the asset.
|
||||
/// asset_cache_item - asset cache item datum for the asset, optional, overrides asset_name
|
||||
/datum/asset_transport/proc/get_asset_url(asset_name, datum/asset_cache_item/asset_cache_item)
|
||||
if (!istype(asset_cache_item))
|
||||
asset_cache_item = SSassets.cache[asset_name]
|
||||
// To ensure code that breaks on cdns breaks in local testing, we only
|
||||
// use the normal filename on legacy assets and name space assets.
|
||||
var/keep_local_name = dont_mutate_filenames \
|
||||
|| asset_cache_item.legacy \
|
||||
|| asset_cache_item.keep_local_name \
|
||||
|| (asset_cache_item.namespace && !asset_cache_item.namespace_parent)
|
||||
if (keep_local_name)
|
||||
return url_encode(asset_cache_item.name)
|
||||
return url_encode("asset.[asset_cache_item.hash][asset_cache_item.ext]")
|
||||
|
||||
|
||||
/// Sends a list of browser assets to a client
|
||||
/// client - a client or mob
|
||||
/// asset_list - A list of asset filenames to be sent to the client. Can optionally be assoicated with the asset's asset_cache_item datum.
|
||||
/// Returns TRUE if any assets were sent.
|
||||
/datum/asset_transport/proc/send_assets(client/client, list/asset_list)
|
||||
#if defined(UNIT_TESTS)
|
||||
return
|
||||
#endif
|
||||
|
||||
if (!istype(client))
|
||||
if (ismob(client))
|
||||
var/mob/our_mob = client
|
||||
if (our_mob.client)
|
||||
client = our_mob.client
|
||||
else //no stacktrace because this will mainly happen because the client went away
|
||||
return
|
||||
else
|
||||
CRASH("Invalid argument: client: `[client]`")
|
||||
if (!islist(asset_list))
|
||||
asset_list = list(asset_list)
|
||||
var/list/unreceived = list()
|
||||
|
||||
for (var/asset_name in asset_list)
|
||||
var/datum/asset_cache_item/ACI = asset_list[asset_name]
|
||||
if (!istype(ACI) && !(ACI = SSassets.cache[asset_name]))
|
||||
log_debug("ERROR: can't send asset `[asset_name]`: unregistered or invalid state: `[ACI]`")
|
||||
continue
|
||||
var/asset_file = ACI.resource
|
||||
if (!asset_file)
|
||||
log_debug("ERROR: can't send asset `[asset_name]`: invalid registered resource: `[ACI.resource]`")
|
||||
continue
|
||||
|
||||
var/asset_hash = ACI.hash
|
||||
var/new_asset_name = asset_name
|
||||
var/keep_local_name = dont_mutate_filenames \
|
||||
|| ACI.legacy \
|
||||
|| ACI.keep_local_name \
|
||||
|| (ACI.namespace && !ACI.namespace_parent)
|
||||
if (!keep_local_name)
|
||||
new_asset_name = "asset.[ACI.hash][ACI.ext]"
|
||||
if (client.sent_assets[new_asset_name] == asset_hash)
|
||||
if (global.Debug2)
|
||||
log_debug("DEBUG: Skipping send of `[asset_name]` (as `[new_asset_name]`) for `[client]` because it already exists in the client's sent_assets list")
|
||||
continue
|
||||
unreceived[asset_name] = ACI
|
||||
|
||||
if (unreceived.len)
|
||||
if (unreceived.len >= ASSET_CACHE_TELL_CLIENT_AMOUNT)
|
||||
to_chat(client, ("<span class='info'>Sending Resources...</span>"))
|
||||
|
||||
for (var/asset_name in unreceived)
|
||||
var/new_asset_name = asset_name
|
||||
var/datum/asset_cache_item/ACI = unreceived[asset_name]
|
||||
var/keep_local_name = dont_mutate_filenames \
|
||||
|| ACI.legacy \
|
||||
|| ACI.keep_local_name \
|
||||
|| (ACI.namespace && !ACI.namespace_parent)
|
||||
if (!keep_local_name)
|
||||
new_asset_name = "asset.[ACI.hash][ACI.ext]"
|
||||
client << browse_rsc(ACI.resource, new_asset_name)
|
||||
|
||||
client.sent_assets[new_asset_name] = ACI.hash
|
||||
|
||||
add_timer(new /callback(client, TYPE_PROC_REF(/client, asset_cache_update_json)), 1 SECONDS)
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
|
||||
/// Precache files without clogging up the browse() queue, used for passively sending files on connection start.
|
||||
/datum/asset_transport/proc/send_assets_slow(client/client, list/files, filerate = SLOW_ASSET_SEND_RATE)
|
||||
var/startingfilerate = filerate
|
||||
for (var/file in files)
|
||||
if (!client)
|
||||
break
|
||||
if (send_assets(client, file))
|
||||
if (!(--filerate))
|
||||
filerate = startingfilerate
|
||||
client.tg_browse_queue_flush()
|
||||
stoplag(0) //queuing calls like this too quickly can cause issues in some client versions
|
||||
|
||||
/// Check the config is valid to load this transport
|
||||
/// Returns TRUE or FALSE
|
||||
/datum/asset_transport/proc/validate_config(log = TRUE)
|
||||
return TRUE
|
||||
|
||||
#undef ASSET_CACHE_TELL_CLIENT_AMOUNT
|
||||
87
code/modules/asset_cache/transports/webroot_transport.dm
Normal file
87
code/modules/asset_cache/transports/webroot_transport.dm
Normal file
@@ -0,0 +1,87 @@
|
||||
/// CDN Webroot asset transport.
|
||||
/datum/asset_transport/webroot
|
||||
name = "CDN Webroot asset transport"
|
||||
|
||||
/datum/asset_transport/webroot/Load()
|
||||
if (validate_config(log = FALSE))
|
||||
load_existing_assets()
|
||||
|
||||
/// Processes thru any assets that were registered before we were loaded as a transport.
|
||||
/datum/asset_transport/webroot/proc/load_existing_assets()
|
||||
for (var/asset_name in SSassets.cache)
|
||||
var/datum/asset_cache_item/ACI = SSassets.cache[asset_name]
|
||||
save_asset_to_webroot(ACI)
|
||||
|
||||
/// Register a browser asset with the asset cache system
|
||||
/// We also save it to the CDN webroot at this step instead of waiting for send_assets()
|
||||
/// asset_name - the identifier of the asset
|
||||
/// asset - the actual asset file or an asset_cache_item datum.
|
||||
/datum/asset_transport/webroot/register_asset(asset_name, asset, file_hash, dmi_path)
|
||||
. = ..()
|
||||
var/datum/asset_cache_item/ACI = .
|
||||
|
||||
if (istype(ACI) && ACI.hash)
|
||||
save_asset_to_webroot(ACI)
|
||||
|
||||
/// Saves the asset to the webroot taking into account namespaces and hashes.
|
||||
/datum/asset_transport/webroot/proc/save_asset_to_webroot(datum/asset_cache_item/ACI)
|
||||
var/webroot = config.asset_cdn_webroot
|
||||
var/newpath = "[webroot][get_asset_suffex(ACI)]"
|
||||
if (fexists(newpath))
|
||||
return
|
||||
if (fexists("[newpath].gz")) //its a common pattern in webhosting to save gzip'ed versions of text files and let the webserver serve them up as gzip compressed normal files, sometimes without keeping the original version.
|
||||
return
|
||||
return fcopy(ACI.resource, newpath)
|
||||
|
||||
/// Returns a url for a given asset.
|
||||
/// asset_name - Name of the asset.
|
||||
/// asset_cache_item - asset cache item datum for the asset, optional, overrides asset_name
|
||||
/datum/asset_transport/webroot/get_asset_url(asset_name, datum/asset_cache_item/asset_cache_item)
|
||||
if (!istype(asset_cache_item))
|
||||
asset_cache_item = SSassets.cache[asset_name]
|
||||
var/url = config.asset_cdn_url //config loading will handle making sure this ends in a /
|
||||
return "[url][get_asset_suffex(asset_cache_item)]"
|
||||
|
||||
/datum/asset_transport/webroot/proc/get_asset_suffex(datum/asset_cache_item/asset_cache_item)
|
||||
var/base = "[copytext(asset_cache_item.hash, 1, 3)]/"
|
||||
var/filename = "asset.[asset_cache_item.hash][asset_cache_item.ext]"
|
||||
if (length(asset_cache_item.namespace))
|
||||
base = "namespaces/[copytext(asset_cache_item.namespace, 1, 3)]/[asset_cache_item.namespace]/"
|
||||
if (!asset_cache_item.namespace_parent)
|
||||
filename = "[asset_cache_item.name]"
|
||||
return base + filename
|
||||
|
||||
|
||||
/// webroot asset sending - does nothing unless passed legacy assets
|
||||
/datum/asset_transport/webroot/send_assets(client/client, list/asset_list)
|
||||
. = FALSE
|
||||
var/list/legacy_assets = list()
|
||||
if (!islist(asset_list))
|
||||
asset_list = list(asset_list)
|
||||
for (var/asset_name in asset_list)
|
||||
var/datum/asset_cache_item/ACI = asset_list[asset_name]
|
||||
if (!istype(ACI))
|
||||
ACI = SSassets.cache[asset_name]
|
||||
if (!ACI)
|
||||
legacy_assets += asset_name //pass it on to base send_assets so it can output an error
|
||||
continue
|
||||
if (ACI.legacy)
|
||||
legacy_assets[asset_name] = ACI
|
||||
if (length(legacy_assets))
|
||||
. = ..(client, legacy_assets)
|
||||
|
||||
|
||||
/// webroot slow asset sending - does nothing.
|
||||
/datum/asset_transport/webroot/send_assets_slow(client/client, list/files, filerate)
|
||||
return FALSE
|
||||
|
||||
/datum/asset_transport/webroot/validate_config(log = TRUE)
|
||||
if (!config.asset_cdn_url)
|
||||
if (log)
|
||||
log_debug("ERROR: [type]: Invalid Config: ASSET_CDN_URL")
|
||||
return FALSE
|
||||
if (!config.asset_cdn_url)
|
||||
if (log)
|
||||
log_debug("ERROR: [type]: Invalid Config: ASSET_CDN_WEBROOT")
|
||||
return FALSE
|
||||
return TRUE
|
||||
29
code/modules/asset_cache/validate_assets.html
Normal file
29
code/modules/asset_cache/validate_assets.html
Normal file
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
//this is used over window.location because window.location has a character limit in IE.
|
||||
function sendbyond(text) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', '?'+text, true);
|
||||
xhr.send(null);
|
||||
}
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', 'asset_data.json', true);
|
||||
xhr.responseType = 'text';
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === 4) {
|
||||
var status = xhr.status;
|
||||
if (status >= 200 && status < 400) {
|
||||
sendbyond('asset_cache_preload_data=' + encodeURIComponent(xhr.responseText));
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.send(null);
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -112,5 +112,8 @@
|
||||
// Last Round Scoreboard images have been sent
|
||||
var/received_last_round_images = FALSE
|
||||
|
||||
// Duplicate from /datum
|
||||
var/list/active_timers = list()
|
||||
|
||||
var/list/person_animation_viewers = list()
|
||||
var/list/item_animation_viewers = list()
|
||||
|
||||
@@ -149,7 +149,7 @@ var/updated_stats = 0
|
||||
bunker_setting = 1
|
||||
load_bunker()
|
||||
if(config)
|
||||
winset(src, null, "window1.msay_output.style=[config.world_style_config];")
|
||||
winset_wrapper(null, "window1.msay_output.style=[config.world_style_config];") // The TG people told me to do it. If this fails for reason X or Y, the entire client/New() will runtime and we'll be in huge trouble.
|
||||
else
|
||||
to_chat(src, "<span class='warning'>The stylesheet wasn't properly setup call an administrator to reload the stylesheet or relog.</span>")
|
||||
|
||||
@@ -331,22 +331,22 @@ var/updated_stats = 0
|
||||
prefs.save_preferences_sqlite(src, src.ckey)
|
||||
|
||||
if(prefs.lastchangelog != changelog_hash) //bolds the changelog button on the interface so we know there are updates.
|
||||
winset(src, "rpane.changelog", "background-color=#eaeaea;font-style=bold")
|
||||
winset_wrapper("rpane.changelog", "background-color=#eaeaea;font-style=bold")
|
||||
prefs.SetChangelog(ckey,changelog_hash)
|
||||
to_chat(src, "<span class='info'>Changelog has changed since your last visit.</span>")
|
||||
|
||||
//Set map label to correct map name
|
||||
winset(src, "rpane.mapb", "text=\"[map.nameLong]\"")
|
||||
winset_wrapper(src, "rpane.mapb", "text=\"[map.nameLong]\"")
|
||||
|
||||
if (round_end_info)
|
||||
winset(src, "rpane.round_end", "is-visible=true")
|
||||
winset(src, "rpane.last_round_end", "is-visible=false")
|
||||
winset_wrapper("rpane.round_end", "is-visible=true")
|
||||
winset_wrapper("rpane.last_round_end", "is-visible=false")
|
||||
else if (last_round_end_info)
|
||||
winset(src, "rpane.round_end", "is-visible=false")
|
||||
winset(src, "rpane.last_round_end", "is-visible=true")
|
||||
winset_wrapper("rpane.round_end", "is-visible=false")
|
||||
winset_wrapper("rpane.last_round_end", "is-visible=true")
|
||||
else
|
||||
winset(src, "rpane.round_end", "is-visible=false")
|
||||
winset(src, "rpane.last_round_end", "is-visible=false")
|
||||
winset_wrapper("rpane.round_end", "is-visible=false")
|
||||
winset_wrapper("rpane.last_round_end", "is-visible=false")
|
||||
|
||||
if (runescape_pvp)
|
||||
to_chat(src, "<span class='userdanger'>WARNING: Wilderness mode is enabled; players can only harm one another in maintenance areas!</span>")
|
||||
@@ -359,6 +359,10 @@ var/updated_stats = 0
|
||||
|
||||
fps = (prefs.fps < 0) ? RECOMMENDED_CLIENT_FPS : prefs.fps
|
||||
|
||||
// This is wrapped so that if the winset fucks up somehow, this doesn't crash the entire client login proc.
|
||||
/client/proc/winset_wrapper(control_id, params)
|
||||
winset(src, control_id, params)
|
||||
|
||||
//////////////
|
||||
//DISCONNECT//
|
||||
//////////////
|
||||
@@ -518,6 +522,7 @@ var/updated_stats = 0
|
||||
if(query_connection_log.ErrorMsg())
|
||||
WARNING("FINGERPRINT: [query_connection_log.ErrorMsg()]")
|
||||
qdel(query_connection_log)
|
||||
connection_time = world.time
|
||||
|
||||
#undef TOPIC_SPAM_DELAY
|
||||
#undef UPLOAD_LIMIT
|
||||
|
||||
@@ -13,6 +13,21 @@ var/list/asset_datums = list()
|
||||
var/list/sending = list()
|
||||
var/last_asset_job = 0 // Last job done.
|
||||
|
||||
/// Blocks until all currently sending browse and browse_rsc assets have been sent.
|
||||
/// Due to byond limitations, this proc will sleep for 1 client round trip even if the client has no pending asset sends.
|
||||
/// This proc will return an untrue value if it had to return before confirming the send, such as timeout or the client going away.
|
||||
/client/proc/browse_queue_flush(timeout = 50)
|
||||
var/job = ++last_asset_job
|
||||
var/t = 0
|
||||
var/timeout_time = timeout
|
||||
src << browse({"<script>window.location.href='byond://?asset_cache_confirm_arrival=[job]'</script>"}, "window=asset_cache_browser&file=asset_cache_send_verify.htm")
|
||||
|
||||
while(!completed_asset_jobs["[job]"] && t < timeout_time) // Reception is handled in Topic()
|
||||
stoplag(1) // Lock up the caller until this is received.
|
||||
t++
|
||||
if (t < timeout_time)
|
||||
return TRUE
|
||||
|
||||
//This proc sends the asset to the client, but only if it needs it.
|
||||
/proc/send_asset(var/client/client, var/asset_name, var/verify = TRUE)
|
||||
if(!istype(client))
|
||||
@@ -438,12 +453,11 @@ var/list/asset_datums = list()
|
||||
|
||||
/datum/asset/simple/fontawesome
|
||||
assets = list(
|
||||
"fa-regular-400.eot" = 'html/font-awesome/webfonts/fa-regular-400.eot',
|
||||
"fa-regular-400.woff" = 'html/font-awesome/webfonts/fa-regular-400.woff',
|
||||
"fa-solid-900.eot" = 'html/font-awesome/webfonts/fa-solid-900.eot',
|
||||
"fa-solid-900.woff" = 'html/font-awesome/webfonts/fa-solid-900.woff',
|
||||
"font-awesome.css" = 'html/font-awesome/css/all.min.css',
|
||||
"v4shim.css" = 'html/font-awesome/css/v4-shims.min.css'
|
||||
"fa-regular-400.ttf" = 'html/font-awesome/webfonts/fa-regular-400.ttf',
|
||||
"fa-solid-900.ttf" = 'html/font-awesome/webfonts/fa-solid-900.ttf',
|
||||
"fa-v4compatibility.ttf" = 'html/font-awesome/webfonts/fa-v4compatibility.ttf',
|
||||
"v4shim.css" = 'html/font-awesome/css/v4-shims.min.css',
|
||||
"font-awesome.css" = 'html/font-awesome/css/all.min.css',
|
||||
)
|
||||
|
||||
/datum/asset/simple/tgui
|
||||
|
||||
@@ -241,7 +241,16 @@ var/const/MAX_SAVE_SLOTS = 16
|
||||
var/obj_chat_on_map = FALSE
|
||||
var/no_goonchat_for_obj = FALSE
|
||||
|
||||
// TGUI things
|
||||
var/tgui_fancy = TRUE
|
||||
//// -- Unimplemented for tgui alerts --
|
||||
var/tgui_input = FALSE
|
||||
var/tgui_input_large = FALSE
|
||||
var/tgui_input_swapped = FALSE
|
||||
var/tgui_lock = FALSE
|
||||
var/tgui_scale = TRUE
|
||||
var/layout_prefs_used = FALSE
|
||||
|
||||
var/fps = -1
|
||||
|
||||
var/client/client
|
||||
|
||||
@@ -762,7 +762,7 @@ Target Machine: "}
|
||||
var/icon/img = message_app.imglist[note]
|
||||
if(img)
|
||||
usr << browse_rsc(ImagePDA(img), "tmp_photo_[note].png")
|
||||
dat += "<img src='tmp_photo_[note].png' width = '192' style='-ms-interpolation-mode:nearest-neighbor'><BR>"
|
||||
dat += "<img src='tmp_photo_[note].png' width = '192' style='image-rendering: pixelated'><BR>"
|
||||
return dat
|
||||
|
||||
/mob/living/silicon/pai/proc/softwareHolomap()
|
||||
|
||||
@@ -184,7 +184,7 @@
|
||||
if(dark_plane)
|
||||
if (master_plane)
|
||||
master_plane.blend_mode = BLEND_ADD
|
||||
dark_plane.alphas["spider"] = 15 // with the master_plane at BLEND_ADD, shadows appear well lit while actually well lit places appear blinding.
|
||||
dark_plane.alphas["spider"] = 0 // with the master_plane at BLEND_ADD, shadows appear well lit while actually well lit places appear blinding.
|
||||
client.color = list(
|
||||
1,0,0,0,
|
||||
0,0.2,0,0,
|
||||
|
||||
@@ -430,7 +430,7 @@
|
||||
if(dark_plane)
|
||||
if(master_plane)
|
||||
master_plane.blend_mode = BLEND_ADD
|
||||
dark_plane.alphas["grue"] = 15 // with the master_plane at BLEND_ADD, shadows appear well lit while actually well lit places appear blinding.
|
||||
dark_plane.alphas["grue"] = 0 // with the master_plane at BLEND_ADD, shadows appear well lit while actually well lit places appear blinding.
|
||||
client.color = list(
|
||||
1,0,0,0,
|
||||
-1,0.2,0.2,0,
|
||||
|
||||
@@ -44,5 +44,5 @@
|
||||
return
|
||||
|
||||
// Used by the Nano UI Manager (/datum/nanomanager) to track UIs opened by this mob
|
||||
/client/var/list/open_uis = list()
|
||||
/mob/var/list/open_uis = list()
|
||||
/client/var/list/nano_open_uis = list()
|
||||
/mob/var/list/nano_open_uis = list()
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// There should only ever be one (global) instance of nanomanger
|
||||
/datum/nanomanager
|
||||
// a list of current open /nanoui UIs, grouped by src_object and ui_key
|
||||
var/open_uis[0]
|
||||
var/nano_open_uis[0]
|
||||
// a list of current open /nanoui UIs, not grouped, for use in processing
|
||||
var/list/processing_uis = list()
|
||||
// a list of asset filenames which are to be sent to the client on user logon
|
||||
@@ -107,14 +107,14 @@
|
||||
*/
|
||||
/datum/nanomanager/proc/get_open_ui(var/mob/user, src_object, ui_key)
|
||||
var/src_object_key = "\ref[src_object]"
|
||||
if (isnull(open_uis[src_object_key]) || !istype(open_uis[src_object_key], /list))
|
||||
if (isnull(nano_open_uis[src_object_key]) || !istype(nano_open_uis[src_object_key], /list))
|
||||
//testing("nanomanager/get_open_ui mob [user.name] [src_object:name] [ui_key] - there are no uis open")
|
||||
return null
|
||||
else if (isnull(open_uis[src_object_key][ui_key]) || !istype(open_uis[src_object_key][ui_key], /list))
|
||||
else if (isnull(nano_open_uis[src_object_key][ui_key]) || !istype(nano_open_uis[src_object_key][ui_key], /list))
|
||||
//testing("nanomanager/get_open_ui mob [user.name] [src_object:name] [ui_key] - there are no uis open for this object")
|
||||
return null
|
||||
|
||||
for (var/datum/nanoui/ui in open_uis[src_object_key][ui_key])
|
||||
for (var/datum/nanoui/ui in nano_open_uis[src_object_key][ui_key])
|
||||
if (ui.user == user)
|
||||
return ui
|
||||
|
||||
@@ -130,12 +130,12 @@
|
||||
*/
|
||||
/datum/nanomanager/proc/update_uis(src_object)
|
||||
var/src_object_key = "\ref[src_object]"
|
||||
if (isnull(open_uis[src_object_key]) || !istype(open_uis[src_object_key], /list))
|
||||
if (isnull(nano_open_uis[src_object_key]) || !istype(nano_open_uis[src_object_key], /list))
|
||||
return 0
|
||||
|
||||
var/update_count = 0
|
||||
for (var/ui_key in open_uis[src_object_key])
|
||||
for (var/datum/nanoui/ui in open_uis[src_object_key][ui_key])
|
||||
for (var/ui_key in nano_open_uis[src_object_key])
|
||||
for (var/datum/nanoui/ui in nano_open_uis[src_object_key][ui_key])
|
||||
if(ui && ui.src_object && ui.user)
|
||||
ui.process(1)
|
||||
update_count++
|
||||
@@ -151,11 +151,11 @@
|
||||
* @return int The number of uis updated
|
||||
*/
|
||||
/datum/nanomanager/proc/update_user_uis(var/mob/user, src_object = null, ui_key = null)
|
||||
if (isnull(user.open_uis) || !istype(user.open_uis, /list) || open_uis.len == 0)
|
||||
if (isnull(user.nano_open_uis) || !istype(user.nano_open_uis, /list) || nano_open_uis.len == 0)
|
||||
return 0 // has no open uis
|
||||
|
||||
var/update_count = 0
|
||||
for (var/datum/nanoui/ui in user.open_uis)
|
||||
for (var/datum/nanoui/ui in user.nano_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(1)
|
||||
update_count++
|
||||
@@ -172,17 +172,17 @@
|
||||
* @return int The number of uis closed
|
||||
*/
|
||||
/datum/nanomanager/proc/close_user_uis(var/mob/user, src_object = null, ui_key = null)
|
||||
if (isnull(user.open_uis) || !istype(user.open_uis, /list) || open_uis.len == 0)
|
||||
if (isnull(user.nano_open_uis) || !istype(user.nano_open_uis, /list) || nano_open_uis.len == 0)
|
||||
//testing("nanomanager/close_user_uis mob [user.name] has no open uis")
|
||||
return 0 // has no open uis
|
||||
|
||||
var/close_count = 0
|
||||
for (var/datum/nanoui/ui in user.open_uis)
|
||||
for (var/datum/nanoui/ui in user.nano_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_count++
|
||||
|
||||
//testing("nanomanager/close_user_uis mob [user.name] closed [open_uis.len] of [close_count] uis")
|
||||
//testing("nanomanager/close_user_uis mob [user.name] closed [nano_open_uis.len] of [close_count] uis")
|
||||
|
||||
return close_count
|
||||
|
||||
@@ -195,12 +195,12 @@
|
||||
*/
|
||||
/datum/nanomanager/proc/close_uis(src_object)
|
||||
var/src_object_key = "\ref[src_object]"
|
||||
if(!istype(open_uis[src_object_key], /list))
|
||||
if(!istype(nano_open_uis[src_object_key], /list))
|
||||
return 0
|
||||
|
||||
var/close_count = 0
|
||||
for(var/ui_key in open_uis[src_object_key])
|
||||
for(var/datum/nanoui/ui in open_uis[src_object_key][ui_key])
|
||||
for(var/ui_key in nano_open_uis[src_object_key])
|
||||
for(var/datum/nanoui/ui in nano_open_uis[src_object_key][ui_key])
|
||||
ui.close()
|
||||
close_count++
|
||||
|
||||
@@ -216,16 +216,16 @@
|
||||
*/
|
||||
/datum/nanomanager/proc/ui_opened(var/datum/nanoui/ui)
|
||||
var/src_object_key = "\ref[ui.src_object]"
|
||||
if (isnull(open_uis[src_object_key]) || !istype(open_uis[src_object_key], /list))
|
||||
open_uis[src_object_key] = list(ui.ui_key = list())
|
||||
else if (isnull(open_uis[src_object_key][ui.ui_key]) || !istype(open_uis[src_object_key][ui.ui_key], /list))
|
||||
open_uis[src_object_key][ui.ui_key] = list();
|
||||
if (isnull(nano_open_uis[src_object_key]) || !istype(nano_open_uis[src_object_key], /list))
|
||||
nano_open_uis[src_object_key] = list(ui.ui_key = list())
|
||||
else if (isnull(nano_open_uis[src_object_key][ui.ui_key]) || !istype(nano_open_uis[src_object_key][ui.ui_key], /list))
|
||||
nano_open_uis[src_object_key][ui.ui_key] = list();
|
||||
|
||||
ui.user.open_uis.Add(ui)
|
||||
var/list/uis = open_uis[src_object_key][ui.ui_key]
|
||||
ui.user.nano_open_uis.Add(ui)
|
||||
var/list/uis = nano_open_uis[src_object_key][ui.ui_key]
|
||||
uis.Add(ui)
|
||||
processing_uis.Add(ui)
|
||||
//testing("nanomanager/ui_opened mob [ui.user.name] [ui.src_object:name] [ui.ui_key] - user.open_uis [ui.user.open_uis.len] | uis [uis.len] | processing_uis [processing_uis.len]")
|
||||
//testing("nanomanager/ui_opened mob [ui.user.name] [ui.src_object:name] [ui.ui_key] - user.nano_open_uis [ui.user.nano_open_uis.len] | uis [uis.len] | processing_uis [processing_uis.len]")
|
||||
|
||||
/**
|
||||
* Remove a /nanoui ui from the list of open uis
|
||||
@@ -237,18 +237,18 @@
|
||||
*/
|
||||
/datum/nanomanager/proc/ui_closed(var/datum/nanoui/ui)
|
||||
var/src_object_key = "\ref[ui.src_object]"
|
||||
if (isnull(open_uis[src_object_key]) || !istype(open_uis[src_object_key], /list))
|
||||
if (isnull(nano_open_uis[src_object_key]) || !istype(nano_open_uis[src_object_key], /list))
|
||||
return 0 // wasn't open
|
||||
else if (isnull(open_uis[src_object_key][ui.ui_key]) || !istype(open_uis[src_object_key][ui.ui_key], /list))
|
||||
else if (isnull(nano_open_uis[src_object_key][ui.ui_key]) || !istype(nano_open_uis[src_object_key][ui.ui_key], /list))
|
||||
return 0 // wasn't open
|
||||
|
||||
processing_uis.Remove(ui)
|
||||
if(ui.user)
|
||||
ui.user.open_uis.Remove(ui)
|
||||
var/list/uis = open_uis[src_object_key][ui.ui_key]
|
||||
ui.user.nano_open_uis.Remove(ui)
|
||||
var/list/uis = nano_open_uis[src_object_key][ui.ui_key]
|
||||
uis.Remove(ui)
|
||||
|
||||
//testing("nanomanager/ui_closed mob [ui.user.name] [ui.src_object:name] [ui.ui_key] - user.open_uis [ui.user.open_uis.len] | uis [uis.len] | processing_uis [processing_uis.len]")
|
||||
//testing("nanomanager/ui_closed mob [ui.user.name] [ui.src_object:name] [ui.ui_key] - user.nano_open_uis [ui.user.nano_open_uis.len] | uis [uis.len] | processing_uis [processing_uis.len]")
|
||||
|
||||
return 1
|
||||
|
||||
@@ -279,18 +279,18 @@
|
||||
if(!istype(oldMob))
|
||||
return 0 // no mob, no uis
|
||||
//testing("nanomanager/user_transferred from mob [oldMob.name] to mob [newMob.name]")
|
||||
if (isnull(oldMob.open_uis) || !istype(oldMob.open_uis, /list) || open_uis.len == 0)
|
||||
if (isnull(oldMob.nano_open_uis) || !istype(oldMob.nano_open_uis, /list) || nano_open_uis.len == 0)
|
||||
//testing("nanomanager/user_transferred mob [oldMob.name] has no open uis")
|
||||
return 0 // has no open uis
|
||||
|
||||
if (isnull(newMob.open_uis) || !istype(newMob.open_uis, /list))
|
||||
newMob.open_uis = list()
|
||||
if (isnull(newMob.nano_open_uis) || !istype(newMob.nano_open_uis, /list))
|
||||
newMob.nano_open_uis = list()
|
||||
|
||||
for (var/datum/nanoui/ui in oldMob.open_uis)
|
||||
for (var/datum/nanoui/ui in oldMob.nano_open_uis)
|
||||
ui.user = newMob
|
||||
newMob.open_uis.Add(ui)
|
||||
newMob.nano_open_uis.Add(ui)
|
||||
|
||||
oldMob.open_uis.len = 0
|
||||
oldMob.nano_open_uis.len = 0
|
||||
|
||||
return 1 // success
|
||||
|
||||
@@ -321,13 +321,13 @@
|
||||
*/
|
||||
/datum/nanomanager/proc/send_message(src_object, ui_key, js_function, data, mob/user = null)
|
||||
var/src_object_key = "\ref[src_object]"
|
||||
if (isnull(open_uis[src_object_key]))
|
||||
if (isnull(nano_open_uis[src_object_key]))
|
||||
return 0
|
||||
|
||||
var/paramlist = list2params(data)
|
||||
|
||||
var/update_count = 0
|
||||
for (var/datum/nanoui/ui in open_uis[src_object_key][ui_key])
|
||||
for (var/datum/nanoui/ui in nano_open_uis[src_object_key][ui_key])
|
||||
if (!ui.src_object || (user != null && ui.user != user))
|
||||
continue
|
||||
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
var/list/colourmatrix = list()
|
||||
|
||||
/datum/organ/internal/eyes/proc/update_perception(var/mob/living/carbon/human/M)
|
||||
// Bad hack but in 516 any non-zero value of the dark plane will result in glitch for night vision googles
|
||||
// Fix by reworking dark planes?
|
||||
if (istype(M.glasses, /obj/item/clothing/glasses/scanner/night))
|
||||
return
|
||||
M.dark_plane.alphas["human"] = 5
|
||||
|
||||
/datum/organ/internal/eyes/process() //Eye damage replaces the old eye_stat var.
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
|
||||
if(img)
|
||||
user << browse_rsc(img.img, "tmp_photo.png")
|
||||
info_image = "<img src='tmp_photo.png' width='192' style='-ms-interpolation-mode:nearest-neighbor' /><br><a href='?src=\ref[src];picture=1'>Remove</a><br>"
|
||||
info_image = "<img src='tmp_photo.png' width='192' style='image-rendering: pixelated' /><br><a href='?src=\ref[src];picture=1'>Remove</a><br>"
|
||||
user << browse("<HTML><HEAD><TITLE>[name]</TITLE></HEAD><BODY[color ? " bgcolor=[src.color]":""]>[info_image][info_text][stamps]</BODY></HTML>", "window=[name];size=[display_x]x[display_y]")
|
||||
onclose(user, "[name]")
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
|
||||
user << browse("<html><head><title>[name]</title></head>" \
|
||||
+ "<body style='overflow:hidden;margin:0;text-align:center'>" \
|
||||
+ "<img src='tmp_photo.png' width='[displaylength]' style='-ms-interpolation-mode:nearest-neighbor' />" \
|
||||
+ "<img src='tmp_photo.png' width='[displaylength]' style='image-rendering: pixelated' />" \
|
||||
+ "[scribble ? "<br>Written on the back:<br><i>[scribble]</i>" : ""]"\
|
||||
+ "</body></html>", "window=book;size=[displaylength]x[scribble ? displaylength+108 : displaylength]")
|
||||
if(info) //Would rather not display a blank line of text
|
||||
|
||||
@@ -24,7 +24,7 @@ var/list/beam_master = list()
|
||||
var/obj/item/projectile/beam/fired_beam
|
||||
var/list/rayCastHit/hit_cache
|
||||
|
||||
/ray/beam_ray/New(var/vector/p_origin, var/vector/p_direction, var/obj/item/projectile/beam/fired_beam)
|
||||
/ray/beam_ray/New(var/_vector/p_origin, var/_vector/p_direction, var/obj/item/projectile/beam/fired_beam)
|
||||
..(p_origin, p_direction, fired_beam.starting.z)
|
||||
src.fired_beam = fired_beam
|
||||
original_damage = fired_beam.damage
|
||||
@@ -90,9 +90,9 @@ var/list/beam_master = list()
|
||||
..()
|
||||
|
||||
|
||||
/obj/item/projectile/beam/proc/fireto(var/vector/origin, var/vector/direction)
|
||||
/obj/item/projectile/beam/proc/fireto(var/_vector/origin, var/_vector/direction)
|
||||
// + 0.5 because we want to start in the middle of the tile
|
||||
var/ray/beam_ray/shot_ray = new /ray/beam_ray(origin + new /vector(0.5, 0.5), direction, src)
|
||||
var/ray/beam_ray/shot_ray = new /ray/beam_ray(origin + new /_vector(0.5, 0.5), direction, src)
|
||||
for(var/ray/beam_ray/other_ray in past_rays)
|
||||
if(other_ray.equals(shot_ray))
|
||||
return //we already went here
|
||||
@@ -135,8 +135,8 @@ var/list/beam_master = list()
|
||||
qdel(shot_ray)
|
||||
|
||||
/obj/item/projectile/beam/process()
|
||||
var/vector/origin = atom2vector(starting)
|
||||
var/vector/direction = atoms2vector(starting, original)
|
||||
var/_vector/origin = atom2vector(starting)
|
||||
var/_vector/direction = atoms2vector(starting, original)
|
||||
|
||||
fireto(origin, direction)
|
||||
|
||||
@@ -149,8 +149,8 @@ var/list/beam_master = list()
|
||||
|
||||
//make new ray
|
||||
var/list/rayCastHit/hit_cache = latest_ray.hit_cache
|
||||
var/vector/origin = hit_cache[hit_cache.len].point
|
||||
var/vector/direction = latest_ray.getReboundOnAtom(hit_cache[hit_cache.len])
|
||||
var/_vector/origin = hit_cache[hit_cache.len].point
|
||||
var/_vector/direction = latest_ray.getReboundOnAtom(hit_cache[hit_cache.len])
|
||||
|
||||
//check if raypath was already traveled
|
||||
var/ray/temp_ray = new /ray(origin, direction)
|
||||
@@ -173,8 +173,8 @@ var/list/beam_master = list()
|
||||
var/ray/beam_ray/latest_ray = past_rays[past_rays.len]
|
||||
|
||||
//make new ray
|
||||
var/vector/origin = atom2vector(dest)
|
||||
var/vector/direction = latest_ray.direction
|
||||
var/_vector/origin = atom2vector(dest)
|
||||
var/_vector/direction = latest_ray.direction
|
||||
|
||||
fireto(origin, direction)
|
||||
shot_from = dest
|
||||
@@ -184,8 +184,8 @@ var/list/beam_master = list()
|
||||
src.dir = dir || src.dir
|
||||
src.starting = starting || loc
|
||||
|
||||
var/vector/origin = atom2vector(src.starting)
|
||||
var/vector/direction = dir2vector(src.dir)
|
||||
var/_vector/origin = atom2vector(src.starting)
|
||||
var/_vector/direction = dir2vector(src.dir)
|
||||
fireto(origin, direction)
|
||||
|
||||
// Special laser the captains gun uses
|
||||
@@ -971,7 +971,7 @@ var/list/laser_tag_vests = list(/obj/item/clothing/suit/tag/redtag, /obj/item/cl
|
||||
has_splashed = TRUE
|
||||
..()
|
||||
//I never want to deal wity ray casts ever again
|
||||
/obj/item/projectile/beam/liquid_stream/fireto(var/vector/origin, var/vector/direction)//splashes reagents on the turf if the projectile ran out
|
||||
/obj/item/projectile/beam/liquid_stream/fireto(var/_vector/origin, var/_vector/direction)//splashes reagents on the turf if the projectile ran out
|
||||
..()
|
||||
if (reagents && reagents.total_volume && final_turf && !has_splashed)
|
||||
loc = final_turf
|
||||
|
||||
@@ -2331,8 +2331,8 @@
|
||||
return ..()
|
||||
|
||||
// Geometrically checking if we're on a straight line.
|
||||
var/vector/V = atoms2vector(src, over_location)
|
||||
var/vector/V_norm = V.normalized()
|
||||
var/_vector/V = atoms2vector(src, over_location)
|
||||
var/_vector/V_norm = V.normalized()
|
||||
if (!V_norm.is_integer())
|
||||
return ..() // Only a cardinal vector (north, south, east, west) can pass this test
|
||||
|
||||
@@ -2343,7 +2343,7 @@
|
||||
do
|
||||
temp_turf = temp_turf.get_translated_turf(V_norm)
|
||||
if (!locate(/obj/structure/table) in temp_turf)
|
||||
var/vector/V2 = atoms2vector(src, temp_turf)
|
||||
var/_vector/V2 = atoms2vector(src, temp_turf)
|
||||
vector_translate(V2, 0.1 SECONDS)
|
||||
user.visible_message("<span class='warning'>\The [user] slides \the [src] down the table... and straight into the ground!</span>", "<span class='warning'>You slide \the [src] down the table, and straight into the ground!</span>")
|
||||
create_broken_bottle()
|
||||
|
||||
@@ -132,6 +132,7 @@ var/list/all_GPS_list = list()
|
||||
return FALSE
|
||||
transmitting = TRUE
|
||||
update_icon()
|
||||
SStgui.try_update_ui(ui.user, src, ui)
|
||||
return TRUE
|
||||
if("set_tag")
|
||||
if(isobserver(usr))
|
||||
@@ -148,9 +149,11 @@ var/list/all_GPS_list = list()
|
||||
else
|
||||
gpstag = new_tag
|
||||
update_name()
|
||||
SStgui.try_update_ui(ui.user, src, ui)
|
||||
return TRUE
|
||||
if("toggle_refresh")
|
||||
autorefreshing = !autorefreshing
|
||||
SStgui.try_update_ui(ui.user, src, ui)
|
||||
return TRUE
|
||||
// end tgui
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
*
|
||||
* required user mob The mob interacting with the UI.
|
||||
*
|
||||
* return list Statuic Data to be sent to the UI.
|
||||
* return list Static Data to be sent to the UI.
|
||||
*/
|
||||
/datum/proc/ui_static_data(mob/user)
|
||||
return list()
|
||||
@@ -62,6 +62,17 @@
|
||||
if(ui)
|
||||
ui.send_full_update()
|
||||
|
||||
/**
|
||||
* public
|
||||
*
|
||||
* Will force an update on static data for all viewers.
|
||||
* Should be done manually whenever something happens to
|
||||
* change static data.
|
||||
*/
|
||||
/datum/proc/update_static_data_for_all_viewers()
|
||||
for (var/datum/tgui/window as anything in open_uis)
|
||||
window.send_full_update()
|
||||
|
||||
/**
|
||||
* public
|
||||
*
|
||||
@@ -76,10 +87,20 @@
|
||||
/datum/proc/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
|
||||
SHOULD_CALL_PARENT(TRUE)
|
||||
INVOKE_EVENT(src, /event/ui_act, usr, action)
|
||||
|
||||
// -- FIXME : Should implement the components/signal system to register the ui_act params.
|
||||
//SEND_SIGNAL(src, COMSIG_UI_ACT, usr, action, params)
|
||||
|
||||
// If UI is not interactive or usr calling Topic is not the UI user, bail.
|
||||
if(!ui || ui.status != UI_INTERACTIVE)
|
||||
return TRUE
|
||||
|
||||
// -- FIXME : Should implement this.
|
||||
// if(action == "change_ui_state")
|
||||
// var/mob/living/user = ui.user
|
||||
// //write_preferences will make sure it's valid for href exploits.
|
||||
// user.client.prefs.layout_prefs_used = params["new_state"]
|
||||
|
||||
/**
|
||||
* public
|
||||
*
|
||||
@@ -147,6 +168,8 @@
|
||||
*/
|
||||
/datum/proc/ui_close(mob/user)
|
||||
SHOULD_NOT_SLEEP(TRUE)
|
||||
// -- FIXME: Should use signals when we get to implement them.
|
||||
//SIGNAL_HANDLER
|
||||
|
||||
/**
|
||||
* verb
|
||||
@@ -207,10 +230,18 @@
|
||||
context = window_id)
|
||||
SStgui.force_close_window(usr, window_id)
|
||||
return TRUE
|
||||
|
||||
// Decode payload
|
||||
var/payload
|
||||
if(href_list["payload"])
|
||||
payload = json_decode(href_list["payload"])
|
||||
var/payload_text = href_list["payload"]
|
||||
|
||||
if (!rustg_json_is_valid(payload_text))
|
||||
log_tgui(usr, "Error: Invalid JSON")
|
||||
return TRUE
|
||||
|
||||
payload = json_decode(payload_text)
|
||||
|
||||
// Pass message to window
|
||||
if(window)
|
||||
window.on_message(type, payload, href_list)
|
||||
|
||||
@@ -63,10 +63,10 @@
|
||||
// Close UIs if mindless.
|
||||
if(!client)
|
||||
return UI_CLOSE
|
||||
// Disable UIs if unconcious.
|
||||
// Disable UIs if unconscious.
|
||||
else if(stat)
|
||||
return UI_DISABLED
|
||||
// Update UIs if incapicitated but concious.
|
||||
// Update UIs if incapicitated but conscious.
|
||||
else if(incapacitated())
|
||||
return UI_UPDATE
|
||||
return UI_INTERACTIVE
|
||||
@@ -93,7 +93,14 @@
|
||||
*
|
||||
* return UI_state The state of the UI.
|
||||
*/
|
||||
/mob/living/proc/shared_living_ui_distance(atom/movable/src_object, viewcheck = TRUE)
|
||||
/mob/living/proc/shared_living_ui_distance(atom/movable/src_object, viewcheck = TRUE, allow_tk = TRUE)
|
||||
/* Unimplemented : remote code
|
||||
var/obj/item/item_in_hand = get_active_held_item()
|
||||
if(istype(item_in_hand, /obj/item/machine_remote)) //snowflake, this lets you interact with all.
|
||||
var/obj/item/machine_remote/remote = item_in_hand
|
||||
if(remote.controlling_machine_or_bot == src_object)
|
||||
return UI_INTERACTIVE
|
||||
*/
|
||||
// If the object is obscured, close it.
|
||||
if(viewcheck && !(src_object in view(src)))
|
||||
return UI_CLOSE
|
||||
@@ -110,7 +117,7 @@
|
||||
// Otherwise, we got nothing.
|
||||
return UI_CLOSE
|
||||
|
||||
/mob/living/carbon/human/shared_living_ui_distance(atom/movable/src_object, viewcheck = TRUE)
|
||||
if(M_TK in mutations)
|
||||
/mob/living/carbon/human/shared_living_ui_distance(atom/movable/src_object, viewcheck = TRUE, allow_tk = TRUE)
|
||||
if(allow_tk && (M_TK in mutations))
|
||||
return UI_INTERACTIVE
|
||||
return ..()
|
||||
|
||||
@@ -6,12 +6,25 @@
|
||||
/**
|
||||
* tgui state: admin_state
|
||||
*
|
||||
* Checks that the user is an admin, end-of-story.
|
||||
* Checks if the user has specific admin permissions.
|
||||
*/
|
||||
|
||||
var/datum/ui_state/admin_state/admin_state = new
|
||||
var/list/datum/ui_state/admin_state/admin_states = list()
|
||||
|
||||
/datum/ui_state/admin_state
|
||||
/// The specific admin permissions required for the UI using this state.
|
||||
VAR_FINAL/required_perms = R_ADMIN
|
||||
|
||||
/datum/ui_state/admin_state/New(required_perms = R_ADMIN)
|
||||
. = ..()
|
||||
src.required_perms = required_perms
|
||||
|
||||
/datum/ui_state/admin_state/can_use_topic(src_object, mob/user)
|
||||
if(user.check_rights(R_ADMIN))
|
||||
if(user.check_rights(required_perms))
|
||||
return UI_INTERACTIVE
|
||||
return UI_CLOSE
|
||||
|
||||
/datum/ui_state/admin_state/variable_edited(variable_name, old_value, new_value)
|
||||
if(variable_name == NAMEOF(src, required_perms))
|
||||
return 1 // block var edit
|
||||
return ..()
|
||||
|
||||
@@ -3,4 +3,4 @@ var/datum/ui_state/debug_state/debug_state = new
|
||||
/datum/ui_state/debug_state/can_use_topic(src_object, mob/user)
|
||||
if(user.check_rights(R_DEBUG))
|
||||
return UI_INTERACTIVE
|
||||
return UI_CLOSE
|
||||
return UI_CLOSE
|
||||
@@ -22,7 +22,7 @@ var/datum/ui_state/default/default_state = new
|
||||
. = shared_ui_interaction(src_object)
|
||||
if(. > UI_CLOSE && loc) //must not be in nullspace.
|
||||
. = min(., shared_living_ui_distance(src_object)) // Check the distance...
|
||||
if(. == UI_INTERACTIVE && !ishigherbeing(src)) // unhandy living mobs can only look, not touch.
|
||||
if(. == UI_INTERACTIVE && !dexterity_check()) // unhandy living mobs can only look, not touch.
|
||||
return UI_UPDATE
|
||||
|
||||
/mob/living/silicon/robot/default_can_use_topic(src_object)
|
||||
@@ -42,14 +42,13 @@ var/datum/ui_state/default/default_state = new
|
||||
return
|
||||
|
||||
// The AI can interact with anything it can see nearby, or with cameras while wireless control is enabled.
|
||||
if(!control_disabled && cameranet.checkTurfVis(get_turf(src_object)))
|
||||
if(!control_disabled && can_see(src_object))
|
||||
return UI_INTERACTIVE
|
||||
return UI_CLOSE
|
||||
|
||||
/mob/living/silicon/pai/default_can_use_topic(src_object)
|
||||
// pAIs can only use themselves and itself.
|
||||
var/atom/src_atom = src_object
|
||||
if((src_object == src || istype(src_atom) && src_atom.loc == src) && !stat)
|
||||
// pAIs can only use themselves and the owner's radio.
|
||||
if((src_object == src || src_object == radio) && !stat)
|
||||
return UI_INTERACTIVE
|
||||
else
|
||||
return min(..(), UI_UPDATE)
|
||||
|
||||
14
code/modules/tgui/states/greyscale_menu.dm
Normal file
14
code/modules/tgui/states/greyscale_menu.dm
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* tgui state: greyscale menu
|
||||
*
|
||||
* Checks that the target var of the greyscale menu meets the default can_use_topic criteria
|
||||
*/
|
||||
|
||||
var/datum/ui_state/greyscale_menu_state/greyscale_menu_state = new
|
||||
|
||||
/datum/ui_state/greyscale_menu_state/can_use_topic(src_object, mob/user)
|
||||
var/datum/greyscale_modify_menu/menu = src_object
|
||||
if(!isatom(menu.target))
|
||||
return UI_INTERACTIVE
|
||||
|
||||
return global.default_state.can_use_topic(menu.target, user)
|
||||
@@ -20,7 +20,7 @@ var/datum/ui_state/hands_state/hands_state = new
|
||||
return UI_CLOSE
|
||||
|
||||
/mob/living/hands_can_use_topic(src_object)
|
||||
if(is_holding_item(src_object))
|
||||
if(get_active_hand() == src_object)
|
||||
return UI_INTERACTIVE
|
||||
return UI_CLOSE
|
||||
|
||||
|
||||
12
code/modules/tgui/states/hold_or_view.dm
Normal file
12
code/modules/tgui/states/hold_or_view.dm
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* tgui state: view
|
||||
*
|
||||
* Checks if the object is in view or the mob is holding it, otherwise close the UI
|
||||
*/
|
||||
|
||||
var/datum/ui_state/hold_or_view_state/hold_or_view_state = new
|
||||
|
||||
/datum/ui_state/hold_or_view_state/can_use_topic(src_object, mob/user)
|
||||
if((user in viewers(user.client?.view, src_object)) || user.is_holding(src_object))
|
||||
return UI_INTERACTIVE
|
||||
return UI_CLOSE
|
||||
21
code/modules/tgui/states/language_menu.dm
Normal file
21
code/modules/tgui/states/language_menu.dm
Normal file
@@ -0,0 +1,21 @@
|
||||
/*!
|
||||
* Copyright (c) 2020 Aleksej Komarov
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* tgui state: language_menu_state
|
||||
*/
|
||||
|
||||
/// -- /vg/: unticked, need to be reimplemented
|
||||
|
||||
var/datum/ui_state/language_menu_state/language_menu_state = new
|
||||
|
||||
/datum/ui_state/language_menu/can_use_topic(src_object, mob/user)
|
||||
. = UI_CLOSE
|
||||
if(user.client.holder.check_rights(R_ADMIN))
|
||||
. = UI_INTERACTIVE
|
||||
else if(istype(src_object, /datum/language_menu))
|
||||
var/datum/language_menu/my_languages = src_object
|
||||
if(my_languages.language_holder.owner == user)
|
||||
. = UI_INTERACTIVE
|
||||
@@ -1,13 +1,10 @@
|
||||
/**
|
||||
* tgui state: new_player_state
|
||||
*
|
||||
* Checks that the user is a new_player, or if user is an admin
|
||||
* Checks that the user is a /mob/dead/new_player
|
||||
*/
|
||||
|
||||
var/datum/ui_state/new_player_state/new_player_state = new
|
||||
|
||||
/datum/ui_state/new_player_state/can_use_topic(src_object, mob/user)
|
||||
if(isnewplayer(user) || user.check_rights(R_ADMIN))
|
||||
return UI_INTERACTIVE
|
||||
return UI_CLOSE
|
||||
|
||||
return isnewplayer(user) ? UI_INTERACTIVE : UI_CLOSE
|
||||
|
||||
@@ -10,13 +10,14 @@
|
||||
*/
|
||||
|
||||
var/datum/ui_state/not_incapacitated_state/not_incapacitated_state = new
|
||||
|
||||
/**
|
||||
* tgui state: not_incapacitated_turf_state
|
||||
*
|
||||
* Checks that the user isn't incapacitated and that their loc is a turf
|
||||
*/
|
||||
|
||||
var/datum/ui_state/not_incapacitated_state/not_incapacitated_turf_state = new(no_turfs = TRUE)
|
||||
var/datum/ui_state/not_incapacitated_state/not_incapacitated_turf_state = new
|
||||
|
||||
/datum/ui_state/not_incapacitated_state
|
||||
var/turf_check = FALSE
|
||||
|
||||
@@ -27,3 +27,6 @@ var/datum/ui_state/notcontained_state/notcontained_state = new
|
||||
|
||||
/mob/living/silicon/notcontained_can_use_topic(src_object)
|
||||
return default_can_use_topic(src_object) // Silicons use default bevhavior.
|
||||
|
||||
/mob/living/basic/drone/notcontained_can_use_topic(src_object)
|
||||
return default_can_use_topic(src_object) // Drones use default bevhavior.
|
||||
|
||||
15
code/modules/tgui/states/standing.dm
Normal file
15
code/modules/tgui/states/standing.dm
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* tgui state: standing_state
|
||||
*
|
||||
* Checks that the user isn't incapacitated and is standing upright
|
||||
*/
|
||||
|
||||
/datum/ui_state/not_incapacitated_state/standing = new
|
||||
|
||||
/datum/ui_state/not_incapacitated_state/standing/can_use_topic(src_object, mob/user)
|
||||
if (!isliving(user))
|
||||
return ..()
|
||||
var/mob/living/living_user = user
|
||||
if (living_user.body_position)
|
||||
return UI_DISABLED
|
||||
return ..()
|
||||
@@ -15,10 +15,10 @@
|
||||
/// far away users will be able to see, and anyone farther won't see anything.
|
||||
/// Dead users will receive updates no matter what, though you likely want to add
|
||||
/// a [`ui_status_only_living`] check for finer observer interactions.
|
||||
/proc/ui_status_user_is_adjacent(mob/user, atom/source)
|
||||
/proc/ui_status_user_is_adjacent(mob/user, atom/source, allow_tk = TRUE)
|
||||
if (isliving(user))
|
||||
var/mob/living/living_user = user
|
||||
return living_user.shared_living_ui_distance(source)
|
||||
return living_user.shared_living_ui_distance(source, allow_tk = allow_tk)
|
||||
else
|
||||
return UI_UPDATE
|
||||
|
||||
@@ -33,9 +33,8 @@
|
||||
return UI_INTERACTIVE
|
||||
|
||||
// Regular ghosts can always at least view if in range.
|
||||
var/client/client = user.client
|
||||
if(client)
|
||||
var/clientviewlist = getviewsize(client.view)
|
||||
if(user.client)
|
||||
var/clientviewlist = getviewsize(user.client.view)
|
||||
if(get_dist(source, user) < max(clientviewlist[1], clientviewlist[2]))
|
||||
return UI_UPDATE
|
||||
|
||||
@@ -50,13 +49,14 @@
|
||||
|
||||
/// Returns a UI status such that those without blocked hands will be able to interact,
|
||||
/// but everyone else can only watch.
|
||||
/proc/ui_status_user_has_free_hands(mob/user, atom/source)
|
||||
return user.find_empty_hand_index() ? UI_INTERACTIVE : UI_UPDATE
|
||||
/// TODO: implement (allowed source)
|
||||
/proc/ui_status_user_has_free_hands(mob/user, atom/source, allowed_source)
|
||||
return user.find_empty_hand_index() ? UI_UPDATE : UI_INTERACTIVE
|
||||
|
||||
/// Returns a UI status such that advanced tool users will be able to interact,
|
||||
/// but everyone else can only watch.
|
||||
/proc/ui_status_user_is_advanced_tool_user(mob/user)
|
||||
return ishigherbeing(user) ? UI_INTERACTIVE : UI_UPDATE
|
||||
return user.dexterity_check() ? UI_INTERACTIVE : UI_UPDATE
|
||||
|
||||
/// Returns a UI status such that silicons will be able to interact with whatever
|
||||
/// they would have access to if this was a machine. For example, AIs can
|
||||
@@ -81,13 +81,13 @@
|
||||
|
||||
/mob/living/silicon/ai/get_ui_access(atom/source)
|
||||
// The AI can interact with anything it can see nearby, or with cameras while wireless control is enabled.
|
||||
if(!control_disabled && cameranet.checkTurfVis(get_turf(source)))
|
||||
if(!control_disabled && can_see(source))
|
||||
return UI_INTERACTIVE
|
||||
return UI_CLOSE
|
||||
|
||||
/mob/living/silicon/pai/get_ui_access(atom/source)
|
||||
// pAIs can only use themselves and their contents.
|
||||
if((source == src || source.loc == src) && !stat)
|
||||
// pAIs can only use themselves and the owner's radio.
|
||||
if((source == src || source == radio) && !stat)
|
||||
return UI_INTERACTIVE
|
||||
else
|
||||
return UI_CLOSE
|
||||
@@ -99,7 +99,7 @@
|
||||
return UI_UPDATE
|
||||
|
||||
var/mob/living/living_user = user
|
||||
return (living_user.lying && living_user.stat == CONSCIOUS) \
|
||||
return ((living_user.resting > 0) && living_user.stat == CONSCIOUS) \
|
||||
? UI_INTERACTIVE \
|
||||
: UI_UPDATE
|
||||
|
||||
@@ -110,3 +110,11 @@
|
||||
return UI_CLOSE
|
||||
|
||||
return UI_INTERACTIVE
|
||||
|
||||
/// Return UI_INTERACTIVE if the user is inside the target atom, whether they can see it or not.
|
||||
/// Return UI_CLOSE otherwise.
|
||||
/proc/ui_status_user_inside(mob/user, atom/target)
|
||||
if(target.contains(user))
|
||||
return UI_INTERACTIVE
|
||||
|
||||
return UI_CLOSE
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
var/mob/user
|
||||
/// The object which owns the UI.
|
||||
var/datum/src_object
|
||||
/// The title of te UI.
|
||||
/// The title of the UI.
|
||||
var/title
|
||||
/// The window_id for browse() and onclose().
|
||||
var/datum/tgui_window/window
|
||||
@@ -31,8 +31,12 @@
|
||||
var/closing = FALSE
|
||||
/// The status/visibility of the UI.
|
||||
var/status = UI_INTERACTIVE
|
||||
/// Timed refreshing state
|
||||
var/refreshing = FALSE
|
||||
/// Topic state used to determine status/interactability.
|
||||
var/datum/ui_state/state = null
|
||||
/// Rate limit client refreshes to prevent DoS.
|
||||
var/refresh_cooldown = 0
|
||||
|
||||
/**
|
||||
* public
|
||||
@@ -54,7 +58,7 @@
|
||||
src_object = src_object)
|
||||
src.user = user
|
||||
src.src_object = src_object
|
||||
src.window_key = "[ref(src_object)]-main"
|
||||
src.window_key = "\ref[src_object]-main"
|
||||
src.interface = interface
|
||||
if(title)
|
||||
src.title = title
|
||||
@@ -90,15 +94,14 @@
|
||||
window.acquire_lock(src)
|
||||
if(!window.is_ready())
|
||||
window.initialize(
|
||||
strict_mode = TRUE,
|
||||
fancy = user.client.prefs.tgui_fancy,
|
||||
inline_assets = list(/datum/asset/simple/tgui)
|
||||
)
|
||||
assets = list(
|
||||
get_tg_asset_datum(/datum/tg_asset/simple/tgui),
|
||||
))
|
||||
else
|
||||
window.send_message("ping")
|
||||
window.send_asset(/datum/asset/simple/fontawesome)
|
||||
window.send_asset(/datum/asset/simple/tgfont)
|
||||
for(var/asset in src_object.ui_assets(user))
|
||||
window.send_asset(asset)
|
||||
send_assets()
|
||||
window.send_message("update", get_payload(
|
||||
with_data = TRUE,
|
||||
with_static_data = TRUE))
|
||||
@@ -106,6 +109,30 @@
|
||||
|
||||
return TRUE
|
||||
|
||||
/datum/tgui/proc/send_assets()
|
||||
var/flush_queue = window.send_asset(get_tg_asset_datum(
|
||||
/datum/tg_asset/simple/fontawesome))
|
||||
flush_queue |= window.send_asset(get_tg_asset_datum(
|
||||
/datum/tg_asset/simple/tgfont))
|
||||
flush_queue |= window.send_asset(get_tg_asset_datum(
|
||||
/datum/tg_asset/json/icon_ref_map))
|
||||
for(var/datum/tg_asset/asset in src_object.ui_assets(user))
|
||||
flush_queue |= window.send_asset(asset)
|
||||
|
||||
// -- Legacy code for /vg/ style spritesheet datums --
|
||||
// TOFIX!!! One thing at the time..
|
||||
for(var/asset_type in src_object.ui_assets(user))
|
||||
window.sent_assets |= list(asset_type)
|
||||
var/datum/asset/instance = get_asset_datum(asset_type)
|
||||
instance.send(window.client)
|
||||
if(istype(instance, /datum/asset/spritesheet))
|
||||
var/datum/asset/spritesheet/spritesheet = instance
|
||||
window.send_message("asset/stylesheet", spritesheet.css_filename())
|
||||
window.send_raw_message(TGUI_CREATE_MESSAGE("asset/mappings", instance.get_url_mappings()))
|
||||
// -- END legacy code for /vg/ style spritesheet datums --
|
||||
if (flush_queue)
|
||||
user.client.tg_browse_queue_flush()
|
||||
|
||||
/**
|
||||
* public
|
||||
*
|
||||
@@ -175,11 +202,17 @@
|
||||
/datum/tgui/proc/send_full_update(custom_data, force)
|
||||
if(!user.client || !initialized || closing)
|
||||
return
|
||||
if(!COOLDOWN_FINISHED(src, refresh_cooldown))
|
||||
refreshing = TRUE
|
||||
add_timer(new /callback(src, PROC_REF(send_full_update), custom_data, force), COOLDOWN_TIMELEFT(src, refresh_cooldown), TIMER_UNIQUE)
|
||||
return
|
||||
refreshing = FALSE
|
||||
var/should_update_data = force || status >= UI_UPDATE
|
||||
window.send_message("update", get_payload(
|
||||
custom_data,
|
||||
with_data = should_update_data,
|
||||
with_static_data = TRUE))
|
||||
COOLDOWN_START(src, refresh_cooldown, TGUI_REFRESH_FULL_UPDATE_COOLDOWN)
|
||||
|
||||
/**
|
||||
* public
|
||||
@@ -209,12 +242,17 @@
|
||||
json_data["config"] = list(
|
||||
"title" = title,
|
||||
"status" = status,
|
||||
"interface" = interface,
|
||||
"interface" = list(
|
||||
"name" = interface,
|
||||
"layout" = user.client.prefs.layout_prefs_used,
|
||||
),
|
||||
"refreshing" = refreshing,
|
||||
"window" = list(
|
||||
"key" = window_key,
|
||||
"size" = window_size,
|
||||
"fancy" = user.client.prefs.tgui_fancy,
|
||||
"locked" = TRUE,
|
||||
"locked" = user.client.prefs.tgui_lock,
|
||||
"scale" = user.client.prefs.tgui_scale,
|
||||
),
|
||||
"client" = list(
|
||||
"ckey" = user.client.ckey,
|
||||
@@ -242,12 +280,12 @@
|
||||
* Run an update cycle for this UI. Called internally by SStgui
|
||||
* every second or so.
|
||||
*/
|
||||
/datum/tgui/proc/process(delta_time, force = FALSE)
|
||||
/datum/tgui/proc/process(seconds_per_tick, force = FALSE)
|
||||
if(closing)
|
||||
return
|
||||
var/datum/host = src_object.ui_host(user)
|
||||
// If the object or user died (or something else), abort.
|
||||
if(!src_object || !host || !user || !window)
|
||||
if(QDELETED(src_object) || QDELETED(host) || QDELETED(user) || QDELETED(window))
|
||||
close(can_be_suspended = FALSE)
|
||||
return
|
||||
// Validate ping
|
||||
@@ -297,8 +335,11 @@
|
||||
return FALSE
|
||||
switch(type)
|
||||
if("ready")
|
||||
// Send a full update when the user manually refreshes the UI
|
||||
if(initialized)
|
||||
send_full_update()
|
||||
initialized = TRUE
|
||||
if("pingReply")
|
||||
if("ping/reply")
|
||||
initialized = TRUE
|
||||
if("suspend")
|
||||
close(can_be_suspended = TRUE)
|
||||
@@ -310,7 +351,13 @@
|
||||
if("setSharedState")
|
||||
if(status != UI_INTERACTIVE)
|
||||
return
|
||||
if(!src_object.tgui_shared_states)
|
||||
src_object.tgui_shared_states = list()
|
||||
LAZYINITLIST(src_object.tgui_shared_states)
|
||||
src_object.tgui_shared_states[href_list["key"]] = href_list["value"]
|
||||
SStgui.update_uis(src_object)
|
||||
|
||||
/// Wrapper for behavior to potentially wait until the next tick if the server is overloaded
|
||||
/datum/tgui/proc/on_act_message(act_type, payload, state)
|
||||
if(QDELETED(src) || QDELETED(src_object))
|
||||
return
|
||||
if(src_object.ui_act(act_type, payload, src, state))
|
||||
SStgui.update_uis(src_object)
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
/**
|
||||
* Creates a TGUI alert window and returns the user's response.
|
||||
*
|
||||
* This proc should be used to create alerts that the caller will wait for a response from.
|
||||
* Arguments:
|
||||
* * user - The user to show the alert to.
|
||||
* * message - The content of the alert, shown in the body of the TGUI window.
|
||||
* * title - The of the alert modal, shown on the top of the TGUI window.
|
||||
* * buttons - The options that can be chosen by the user, each string is assigned a button on the UI.
|
||||
* * timeout - The timeout of the alert, after which the modal will close and qdel itself. Set to zero for no timeout.
|
||||
* * autofocus - The bool that controls if this alert should grab window focus.
|
||||
*/
|
||||
/proc/tgui_alert(mob/user, message = null, title = null, list/buttons = list("Ok"), timeout = 0, autofocus = TRUE)
|
||||
if (!user)
|
||||
user = usr
|
||||
if (!istype(user))
|
||||
if (istype(user, /client))
|
||||
var/client/client = user
|
||||
user = client.mob
|
||||
else
|
||||
return
|
||||
var/datum/tgui_modal/alert = new(user, message, title, buttons, timeout, autofocus)
|
||||
alert.tgui_interact(user)
|
||||
alert.wait()
|
||||
if (alert)
|
||||
. = alert.choice
|
||||
qdel(alert)
|
||||
|
||||
/**
|
||||
* Creates an asynchronous TGUI alert window with an associated callback.
|
||||
*
|
||||
* This proc should be used to create alerts that invoke a callback with the user's chosen option.
|
||||
* Arguments:
|
||||
* * user - The user to show the alert to.
|
||||
* * message - The content of the alert, shown in the body of the TGUI window.
|
||||
* * title - The of the alert modal, shown on the top of the TGUI window.
|
||||
* * buttons - The options that can be chosen by the user, each string is assigned a button on the UI.
|
||||
* * callback - The callback to be invoked when a choice is made.
|
||||
* * timeout - The timeout of the alert, after which the modal will close and qdel itself. Disabled by default, can be set to seconds otherwise.
|
||||
* * autofocus - The bool that controls if this alert should grab window focus.
|
||||
*/
|
||||
/proc/tgui_alert_async(mob/user, message = null, title = null, list/buttons = list("Ok"), callback/callback, timeout = 0, autofocus = TRUE)
|
||||
if (!user)
|
||||
user = usr
|
||||
if (!istype(user))
|
||||
if (istype(user, /client))
|
||||
var/client/client = user
|
||||
user = client.mob
|
||||
else
|
||||
return
|
||||
var/datum/tgui_modal/async/alert = new(user, message, title, buttons, callback, timeout, autofocus)
|
||||
alert.tgui_interact(user)
|
||||
|
||||
/**
|
||||
* # tgui_modal
|
||||
*
|
||||
* Datum used for instantiating and using a TGUI-controlled modal that prompts the user with
|
||||
* a message and has buttons for responses.
|
||||
*/
|
||||
/datum/tgui_modal
|
||||
/// The title of the TGUI window
|
||||
var/title
|
||||
/// The textual body of the TGUI window
|
||||
var/message
|
||||
/// The list of buttons (responses) provided on the TGUI window
|
||||
var/list/buttons
|
||||
/// The button that the user has pressed, null if no selection has been made
|
||||
var/choice
|
||||
/// The time at which the tgui_modal was created, for displaying timeout progress.
|
||||
var/start_time
|
||||
/// The lifespan of the tgui_modal, after which the window will close and delete itself.
|
||||
var/timeout
|
||||
/// The bool that controls if this modal should grab window focus
|
||||
var/autofocus
|
||||
/// Boolean field describing if the tgui_modal was closed by the user.
|
||||
var/closed
|
||||
|
||||
/datum/tgui_modal/New(mob/user, message, title, list/buttons, timeout, autofocus)
|
||||
src.title = title
|
||||
src.message = message
|
||||
src.buttons = buttons.Copy()
|
||||
src.autofocus = autofocus
|
||||
if (timeout)
|
||||
src.timeout = timeout
|
||||
start_time = world.time
|
||||
spawn(timeout)
|
||||
qdel(src)
|
||||
|
||||
/datum/tgui_modal/Destroy(force, ...)
|
||||
SStgui.close_uis(src)
|
||||
QDEL_NULL(buttons)
|
||||
. = ..()
|
||||
|
||||
/**
|
||||
* Waits for a user's response to the tgui_modal's prompt before returning. Returns early if
|
||||
* the window was closed by the user.
|
||||
*/
|
||||
/datum/tgui_modal/proc/wait()
|
||||
while (!choice && !closed && !src.gcDestroyed)
|
||||
stoplag()
|
||||
|
||||
/datum/tgui_modal/tgui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "AlertModal")
|
||||
ui.open()
|
||||
|
||||
/datum/tgui_modal/ui_close(mob/user)
|
||||
. = ..()
|
||||
closed = TRUE
|
||||
|
||||
/datum/tgui_modal/ui_state(mob/user)
|
||||
return global.always_state
|
||||
|
||||
/datum/tgui_modal/ui_data(mob/user)
|
||||
. = list(
|
||||
"title" = title,
|
||||
"message" = message,
|
||||
"buttons" = buttons,
|
||||
"autofocus" = autofocus
|
||||
)
|
||||
|
||||
if(timeout)
|
||||
.["timeout"] = CLAMP01((timeout - (world.time - start_time) - 1 SECONDS) / (timeout - 1 SECONDS))
|
||||
|
||||
/datum/tgui_modal/ui_act(action, list/params)
|
||||
. = ..()
|
||||
if (.)
|
||||
return
|
||||
switch(action)
|
||||
if("choose")
|
||||
if (!(params["choice"] in buttons))
|
||||
return
|
||||
set_choice(params["choice"])
|
||||
SStgui.close_uis(src)
|
||||
return TRUE
|
||||
|
||||
/datum/tgui_modal/proc/set_choice(choice)
|
||||
src.choice = choice
|
||||
|
||||
/**
|
||||
* # async tgui_modal
|
||||
*
|
||||
* An asynchronous version of tgui_modal to be used with callbacks instead of waiting on user responses.
|
||||
*/
|
||||
/datum/tgui_modal/async
|
||||
/// The callback to be invoked by the tgui_modal upon having a choice made.
|
||||
var/callback/callback
|
||||
|
||||
/datum/tgui_modal/async/New(mob/user, message, title, list/buttons, callback, timeout, autofocus)
|
||||
..(user, message, title, buttons, timeout, autofocus)
|
||||
src.callback = callback
|
||||
|
||||
/datum/tgui_modal/async/Destroy(force, ...)
|
||||
QDEL_NULL(callback)
|
||||
. = ..()
|
||||
|
||||
/datum/tgui_modal/async/set_choice(choice)
|
||||
. = ..()
|
||||
if(!isnull(src.choice))
|
||||
callback?.invoke_async(src.choice)
|
||||
|
||||
/datum/tgui_modal/async/wait()
|
||||
return
|
||||
@@ -1,186 +0,0 @@
|
||||
/**
|
||||
* Creates a TGUI input list window and returns the user's response.
|
||||
*
|
||||
* This proc should be used to create alerts that the caller will wait for a response from.
|
||||
* Arguments:
|
||||
* * user - The user to show the input box to.
|
||||
* * message - The content of the input box, shown in the body of the TGUI window.
|
||||
* * title - The title of the input box, shown on the top of the TGUI window.
|
||||
* * buttons - The options that can be chosen by the user, each string is assigned a button on the UI.
|
||||
* * timeout - The timeout of the input box, after which the input box will close and qdel itself. Set to zero for no timeout.
|
||||
*/
|
||||
/proc/tgui_input_list(mob/user, message, title, list/buttons, timeout = 0)
|
||||
if (!user)
|
||||
user = usr
|
||||
if(!length(buttons))
|
||||
return
|
||||
if (!istype(user))
|
||||
if (istype(user, /client))
|
||||
var/client/client = user
|
||||
user = client.mob
|
||||
else
|
||||
return
|
||||
var/datum/tgui_list_input/input = new(user, message, title, buttons, timeout)
|
||||
input.tgui_interact(user)
|
||||
input.wait()
|
||||
if (input)
|
||||
. = input.choice
|
||||
qdel(input)
|
||||
|
||||
/**
|
||||
* Creates an asynchronous TGUI input list window with an associated callback.
|
||||
*
|
||||
* This proc should be used to create inputs that invoke a callback with the user's chosen option.
|
||||
* Arguments:
|
||||
* * user - The user to show the input box to.
|
||||
* * message - The content of the input box, shown in the body of the TGUI window.
|
||||
* * title - The title of the input box, shown on the top of the TGUI window.
|
||||
* * buttons - The options that can be chosen by the user, each string is assigned a button on the UI.
|
||||
* * callback - The callback to be invoked when a choice is made.
|
||||
* * timeout - The timeout of the input box, after which the menu will close and qdel itself. Set to zero for no timeout.
|
||||
*/
|
||||
/proc/tgui_input_list_async(mob/user, message, title, list/buttons, callback/callback, timeout = 60 SECONDS)
|
||||
if (!user)
|
||||
user = usr
|
||||
if(!length(buttons))
|
||||
return
|
||||
if (!istype(user))
|
||||
if (istype(user, /client))
|
||||
var/client/client = user
|
||||
user = client.mob
|
||||
else
|
||||
return
|
||||
var/datum/tgui_list_input/async/input = new(user, message, title, buttons, callback, timeout)
|
||||
input.tgui_interact(user)
|
||||
|
||||
/**
|
||||
* # tgui_list_input
|
||||
*
|
||||
* Datum used for instantiating and using a TGUI-controlled list input that prompts the user with
|
||||
* a message and shows a list of selectable options
|
||||
*/
|
||||
/datum/tgui_list_input
|
||||
/// The title of the TGUI window
|
||||
var/title
|
||||
/// The textual body of the TGUI window
|
||||
var/message
|
||||
/// The list of buttons (responses) provided on the TGUI window
|
||||
var/list/buttons
|
||||
/// Buttons (strings specifically) mapped to the actual value (e.g. a mob or a verb)
|
||||
var/list/buttons_map
|
||||
/// The button that the user has pressed, null if no selection has been made
|
||||
var/choice
|
||||
/// The time at which the tgui_list_input was created, for displaying timeout progress.
|
||||
var/start_time
|
||||
/// The lifespan of the tgui_list_input, after which the window will close and delete itself.
|
||||
var/timeout
|
||||
/// Boolean field describing if the tgui_list_input was closed by the user.
|
||||
var/closed
|
||||
|
||||
/datum/tgui_list_input/New(mob/user, message, title, list/buttons, timeout)
|
||||
src.title = title
|
||||
src.message = message
|
||||
src.buttons = list()
|
||||
src.buttons_map = list()
|
||||
var/list/repeat_buttons = list()
|
||||
|
||||
// Gets rid of illegal characters
|
||||
var/static/regex/whitelistedWords = regex(@{"([^\u0020-\u8000]+)"})
|
||||
|
||||
for(var/i in buttons)
|
||||
var/string_key = whitelistedWords.Replace("[i]", "")
|
||||
|
||||
//avoids duplicated keys E.g: when areas have the same name
|
||||
string_key = avoid_assoc_duplicate_keys(string_key, repeat_buttons)
|
||||
|
||||
src.buttons += string_key
|
||||
src.buttons_map[string_key] = i
|
||||
|
||||
|
||||
if (timeout)
|
||||
src.timeout = timeout
|
||||
start_time = world.time
|
||||
spawn(timeout)
|
||||
qdel(src)
|
||||
|
||||
/datum/tgui_list_input/Destroy()
|
||||
SStgui.close_uis(src)
|
||||
QDEL_NULL(buttons)
|
||||
. = ..()
|
||||
|
||||
/**
|
||||
* Waits for a user's response to the tgui_list_input's prompt before returning. Returns early if
|
||||
* the window was closed by the user.
|
||||
*/
|
||||
/datum/tgui_list_input/proc/wait()
|
||||
while (!choice && !closed)
|
||||
stoplag(1)
|
||||
|
||||
/datum/tgui_list_input/tgui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "ListInput")
|
||||
ui.open()
|
||||
|
||||
/datum/tgui_list_input/ui_close(mob/user)
|
||||
. = ..()
|
||||
closed = TRUE
|
||||
|
||||
/datum/tgui_list_input/ui_state(mob/user)
|
||||
return global.always_state
|
||||
|
||||
/datum/tgui_list_input/ui_static_data(mob/user)
|
||||
. = list(
|
||||
"title" = title,
|
||||
"message" = message,
|
||||
"buttons" = buttons
|
||||
)
|
||||
|
||||
/datum/tgui_list_input/ui_data(mob/user)
|
||||
. = list()
|
||||
if(timeout)
|
||||
.["timeout"] = clamp((timeout - (world.time - start_time) - 1 SECONDS) / (timeout - 1 SECONDS), 0, 1)
|
||||
|
||||
/datum/tgui_list_input/ui_act(action, list/params)
|
||||
. = ..()
|
||||
if (.)
|
||||
return
|
||||
switch(action)
|
||||
if("choose")
|
||||
if (!(params["choice"] in buttons))
|
||||
return
|
||||
set_choice(buttons_map[params["choice"]])
|
||||
SStgui.close_uis(src)
|
||||
return TRUE
|
||||
if("cancel")
|
||||
SStgui.close_uis(src)
|
||||
closed = TRUE
|
||||
return TRUE
|
||||
|
||||
/datum/tgui_list_input/proc/set_choice(choice)
|
||||
src.choice = choice
|
||||
|
||||
/**
|
||||
* # async tgui_list_input
|
||||
*
|
||||
* An asynchronous version of tgui_list_input to be used with callbacks instead of waiting on user responses.
|
||||
*/
|
||||
/datum/tgui_list_input/async
|
||||
/// The callback to be invoked by the tgui_list_input upon having a choice made.
|
||||
var/callback/callback
|
||||
|
||||
/datum/tgui_list_input/async/New(mob/user, message, title, list/buttons, callback, timeout)
|
||||
..(user, message, title, buttons, timeout)
|
||||
src.callback = callback
|
||||
|
||||
/datum/tgui_list_input/async/Destroy(force, ...)
|
||||
QDEL_NULL(callback)
|
||||
. = ..()
|
||||
|
||||
/datum/tgui_list_input/async/set_choice(choice)
|
||||
. = ..()
|
||||
if(!isnull(src.choice))
|
||||
callback?.invoke_async(src.choice)
|
||||
|
||||
/datum/tgui_list_input/async/wait()
|
||||
return
|
||||
@@ -11,6 +11,7 @@
|
||||
var/is_browser = FALSE
|
||||
var/status = TGUI_WINDOW_CLOSED
|
||||
var/locked = FALSE
|
||||
var/visible = FALSE
|
||||
var/datum/tgui/locked_by
|
||||
var/datum/subscriber_object
|
||||
var/subscriber_delegate
|
||||
@@ -18,8 +19,14 @@
|
||||
var/message_queue
|
||||
var/sent_assets = list()
|
||||
// Vars passed to initialize proc (and saved for later)
|
||||
var/inline_assets
|
||||
var/fancy
|
||||
var/initial_strict_mode
|
||||
var/initial_fancy
|
||||
var/initial_assets
|
||||
var/initial_inline_html
|
||||
var/initial_inline_js
|
||||
var/initial_inline_css
|
||||
|
||||
var/list/oversized_payloads = list()
|
||||
|
||||
/**
|
||||
* public
|
||||
@@ -44,21 +51,30 @@
|
||||
* state. You can begin sending messages right after initializing. Messages
|
||||
* will be put into the queue until the window finishes loading.
|
||||
*
|
||||
* optional inline_assets list List of assets to inline into the html.
|
||||
* optional inline_html string Custom HTML to inject.
|
||||
* optional fancy bool If TRUE, will hide the window titlebar.
|
||||
* optional strict_mode bool - Enables strict error handling and BSOD.
|
||||
* optional fancy bool - If TRUE and if this is NOT a panel, will hide the window titlebar.
|
||||
* optional assets list - List of assets to load during initialization.
|
||||
* optional inline_html string - Custom HTML to inject.
|
||||
* optional inline_js string - Custom JS to inject.
|
||||
* optional inline_css string - Custom CSS to inject.
|
||||
*/
|
||||
/datum/tgui_window/initialize(
|
||||
inline_assets = list(),
|
||||
strict_mode = FALSE,
|
||||
fancy = FALSE,
|
||||
assets = list(),
|
||||
inline_html = "",
|
||||
fancy = FALSE)
|
||||
inline_js = "",
|
||||
inline_css = "")
|
||||
log_tgui(client,
|
||||
context = "[id]/initialize",
|
||||
window = src)
|
||||
if(!client)
|
||||
return
|
||||
src.inline_assets = inline_assets
|
||||
src.fancy = fancy
|
||||
src.initial_fancy = fancy
|
||||
src.initial_assets = assets
|
||||
src.initial_inline_html = inline_html
|
||||
src.initial_inline_js = inline_js
|
||||
src.initial_inline_css = inline_css
|
||||
status = TGUI_WINDOW_LOADING
|
||||
fatally_errored = FALSE
|
||||
// Build window options
|
||||
@@ -71,11 +87,11 @@
|
||||
// Generate page html
|
||||
var/html = SStgui.basehtml
|
||||
html = replacetextEx(html, "\[tgui:windowId]", id)
|
||||
// Inject inline assets
|
||||
html = replacetextEx(html, "\[tgui:strictMode]", strict_mode)
|
||||
// Inject assets
|
||||
var/inline_assets_str = ""
|
||||
for(var/asset in inline_assets)
|
||||
var/datum/asset/instance = new asset
|
||||
var/mappings = instance.get_url_mappings()
|
||||
for(var/datum/tg_asset/asset in assets)
|
||||
var/mappings = asset.get_url_mappings()
|
||||
for(var/name in mappings)
|
||||
var/url = mappings[name]
|
||||
// Not encoding since asset strings are considered safe
|
||||
@@ -83,12 +99,21 @@
|
||||
inline_assets_str += "Byond.loadCss('[url]', true);\n"
|
||||
else if(copytext(name, -3) == ".js")
|
||||
inline_assets_str += "Byond.loadJs('[url]', true);\n"
|
||||
send_asset(asset)
|
||||
asset.send(client)
|
||||
if(length(inline_assets_str))
|
||||
inline_assets_str = "<script>\n" + inline_assets_str + "</script>\n"
|
||||
html = replacetextEx(html, "<!-- tgui:assets -->\n", inline_assets_str)
|
||||
// Inject custom HTML
|
||||
html = replacetextEx(html, "<!-- tgui:html -->\n", inline_html)
|
||||
// Inject inline HTML
|
||||
if (inline_html)
|
||||
html = replacetextEx(html, "<!-- tgui:inline-html -->", isfile(inline_html) ? file2text(inline_html) : inline_html)
|
||||
// Inject inline JS
|
||||
if (inline_js)
|
||||
inline_js = "<script>\n'use strict';\n[isfile(inline_js) ? file2text(inline_js) : inline_js]\n</script>"
|
||||
html = replacetextEx(html, "<!-- tgui:inline-js -->", inline_js)
|
||||
// Inject inline CSS
|
||||
if (inline_css)
|
||||
inline_css = "<style>\n[isfile(inline_css) ? file2text(inline_css) : inline_css]\n</style>"
|
||||
html = replacetextEx(html, "<!-- tgui:inline-css -->", inline_css)
|
||||
// Open the window
|
||||
client << browse(html, "window=[id];[options]")
|
||||
// Detect whether the control is a browser
|
||||
@@ -97,6 +122,23 @@
|
||||
if(!is_browser)
|
||||
winset(client, id, "on-close=\"uiclose [id]\"")
|
||||
|
||||
/**
|
||||
* public
|
||||
*
|
||||
* Reinitializes the panel with previous data used for initialization.
|
||||
*/
|
||||
/datum/tgui_window/proc/reinitialize()
|
||||
initialize(
|
||||
strict_mode = initial_strict_mode,
|
||||
fancy = initial_fancy,
|
||||
assets = initial_assets,
|
||||
inline_html = initial_inline_html,
|
||||
inline_js = initial_inline_js,
|
||||
inline_css = initial_inline_css)
|
||||
// Resend assets
|
||||
for(var/datum/tg_asset/asset in sent_assets)
|
||||
send_asset(asset)
|
||||
|
||||
/**
|
||||
* public
|
||||
*
|
||||
@@ -184,6 +226,7 @@
|
||||
log_tgui(client,
|
||||
context = "[id]/close (suspending)",
|
||||
window = src)
|
||||
visible = FALSE
|
||||
status = TGUI_WINDOW_READY
|
||||
send_message("suspend")
|
||||
return
|
||||
@@ -191,6 +234,7 @@
|
||||
context = "[id]/close",
|
||||
window = src)
|
||||
release_lock()
|
||||
visible = FALSE
|
||||
status = TGUI_WINDOW_CLOSED
|
||||
message_queue = null
|
||||
// Do not close the window to give user some time
|
||||
@@ -251,16 +295,17 @@
|
||||
*
|
||||
* return bool - TRUE if any assets had to be sent to the client
|
||||
*/
|
||||
/datum/tgui_window/proc/send_asset(datum/asset/asset)
|
||||
/datum/tgui_window/proc/send_asset(datum/tg_asset/asset)
|
||||
if(!client || !asset)
|
||||
return
|
||||
sent_assets |= list(asset)
|
||||
var/datum/asset/instance = get_asset_datum(asset)
|
||||
instance.send(client)
|
||||
if(istype(instance, /datum/asset/spritesheet))
|
||||
var/datum/asset/spritesheet/spritesheet = instance
|
||||
. = asset.send(client)
|
||||
/* FIXME : TG CSS
|
||||
if(istype(asset, /datum/tg_asset/spritesheet))
|
||||
var/datum/tg_asset/spritesheet/spritesheet = asset
|
||||
send_message("asset/stylesheet", spritesheet.css_filename())
|
||||
send_raw_message(TGUI_CREATE_MESSAGE("asset/mappings", instance.get_url_mappings()))
|
||||
*/
|
||||
send_raw_message(asset.get_serialized_url_mappings())
|
||||
|
||||
/**
|
||||
* private
|
||||
@@ -276,6 +321,18 @@
|
||||
: "[id].browser:update")
|
||||
message_queue = null
|
||||
|
||||
/**
|
||||
* public
|
||||
*
|
||||
* Replaces the inline HTML content.
|
||||
*
|
||||
* required inline_html string HTML to inject
|
||||
*/
|
||||
/datum/tgui_window/proc/replace_html(inline_html = "")
|
||||
client << output(url_encode(inline_html), is_browser \
|
||||
? "[id]:replaceHtml" \
|
||||
: "[id].browser:replaceHtml")
|
||||
|
||||
/**
|
||||
* private
|
||||
*
|
||||
@@ -310,7 +367,12 @@
|
||||
// If not locked, handle these message types
|
||||
switch(type)
|
||||
if("ping")
|
||||
send_message("pingReply", payload)
|
||||
send_message("ping/reply", payload)
|
||||
/*
|
||||
if("visible")
|
||||
visible = TRUE
|
||||
SEND_SIGNAL(src, COMSIG_TGUI_WINDOW_VISIBLE, client)
|
||||
*/
|
||||
if("suspend")
|
||||
close(can_be_suspended = TRUE)
|
||||
if("close")
|
||||
@@ -318,8 +380,51 @@
|
||||
if("openLink")
|
||||
client << link(href_list["url"])
|
||||
if("cacheReloaded")
|
||||
// Reinitialize
|
||||
initialize(inline_assets = inline_assets, fancy = fancy)
|
||||
// Resend the assets
|
||||
for(var/asset in sent_assets)
|
||||
send_asset(asset)
|
||||
reinitialize()
|
||||
/* Unimplemented
|
||||
if("chat/resend")
|
||||
SSchat.handle_resend(client, payload)
|
||||
*/
|
||||
if("oversizedPayloadRequest")
|
||||
var/payload_id = payload["id"]
|
||||
var/chunk_count = payload["chunkCount"]
|
||||
var/permit_payload = chunk_count <= config.tgui_max_chunk_count
|
||||
if(permit_payload)
|
||||
create_oversized_payload(payload_id, payload["type"], chunk_count)
|
||||
send_message("oversizePayloadResponse", list("allow" = permit_payload, "id" = payload_id))
|
||||
if("payloadChunk")
|
||||
var/payload_id = payload["id"]
|
||||
append_payload_chunk(payload_id, payload["chunk"])
|
||||
send_message("acknowlegePayloadChunk", list("id" = payload_id))
|
||||
|
||||
/datum/tgui_window/variable_edited(variable_name, old_value, new_value)
|
||||
return (variable_name != NAMEOF(src, id)) && ..()
|
||||
|
||||
/datum/tgui_window/proc/create_oversized_payload(payload_id, message_type, chunk_count)
|
||||
if(oversized_payloads[payload_id])
|
||||
stack_trace("Attempted to create oversized tgui payload with duplicate ID.")
|
||||
return
|
||||
oversized_payloads[payload_id] = list(
|
||||
"type" = message_type,
|
||||
"count" = chunk_count,
|
||||
"chunks" = list(),
|
||||
"timeout" = add_timer(new /callback(src, PROC_REF(remove_oversized_payload), payload_id), 1 SECONDS)
|
||||
)
|
||||
|
||||
/datum/tgui_window/proc/append_payload_chunk(payload_id, chunk)
|
||||
var/list/payload = oversized_payloads[payload_id]
|
||||
if(!payload)
|
||||
return
|
||||
var/list/chunks = payload["chunks"]
|
||||
chunks += chunk
|
||||
if(length(chunks) >= payload["count"])
|
||||
del_timer(payload["timeout"])
|
||||
var/message_type = payload["type"]
|
||||
var/final_payload = chunks.Join()
|
||||
remove_oversized_payload(payload_id)
|
||||
on_message(message_type, json_decode(final_payload), list("type" = message_type, "payload" = final_payload, "tgui" = TRUE, "window_id" = id))
|
||||
else
|
||||
payload["timeout"] = add_timer(new /callback(src, PROC_REF(remove_oversized_payload), payload_id), 1 SECONDS)
|
||||
|
||||
/datum/tgui_window/proc/remove_oversized_payload(payload_id)
|
||||
oversized_payloads -= payload_id
|
||||
|
||||
142
code/modules/tgui_input/alert.dm
Normal file
142
code/modules/tgui_input/alert.dm
Normal file
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* Creates a TGUI alert window and returns the user's response.
|
||||
*
|
||||
* This proc should be used to create alerts that the caller will wait for a response from.
|
||||
* Arguments:
|
||||
* * user - The user to show the alert to.
|
||||
* * message - The content of the alert, shown in the body of the TGUI window.
|
||||
* * title - The of the alert modal, shown on the top of the TGUI window.
|
||||
* * buttons - The options that can be chosen by the user, each string is assigned a button on the UI.
|
||||
* * timeout - The timeout of the alert, after which the modal will close and qdel itself. Set to zero for no timeout.
|
||||
* * autofocus - The bool that controls if this alert should grab window focus.
|
||||
*/
|
||||
/proc/tgui_alert(mob/user, message = "", title, list/buttons = list("Ok"), timeout = 0, autofocus = TRUE, ui_state = global.always_state)
|
||||
if (!user)
|
||||
user = usr
|
||||
if (!istype(user))
|
||||
if (istype(user, /client))
|
||||
var/client/client = user
|
||||
user = client.mob
|
||||
else
|
||||
return null
|
||||
|
||||
if(isnull(user.client))
|
||||
return null
|
||||
|
||||
// A gentle nudge - you should not be using TGUI alert for anything other than a simple message.
|
||||
if(length(buttons) > 3)
|
||||
log_tgui(user, "Error: TGUI Alert initiated with too many buttons. Use a list.", "TguiAlert")
|
||||
return tgui_input_list(user, message, title, buttons, timeout, autofocus)
|
||||
// Client does NOT have tgui_input on: Returns regular input
|
||||
if(!user.client.prefs.tgui_input)
|
||||
if(length(buttons) == 2)
|
||||
return alert(user, message, title, buttons[1], buttons[2])
|
||||
if(length(buttons) == 3)
|
||||
return alert(user, message, title, buttons[1], buttons[2], buttons[3])
|
||||
var/datum/tgui_alert/alert = new(user, message, title, buttons, timeout, autofocus, ui_state)
|
||||
alert.ui_interact(user)
|
||||
alert.wait()
|
||||
if (alert)
|
||||
. = alert.choice
|
||||
qdel(alert)
|
||||
|
||||
/**
|
||||
* # tgui_alert
|
||||
*
|
||||
* Datum used for instantiating and using a TGUI-controlled modal that prompts the user with
|
||||
* a message and has buttons for responses.
|
||||
*/
|
||||
/datum/tgui_alert
|
||||
/// The title of the TGUI window
|
||||
var/title
|
||||
/// The textual body of the TGUI window
|
||||
var/message
|
||||
/// The list of buttons (responses) provided on the TGUI window
|
||||
var/list/buttons
|
||||
/// The button that the user has pressed, null if no selection has been made
|
||||
var/choice
|
||||
/// The time at which the tgui_alert was created, for displaying timeout progress.
|
||||
var/start_time
|
||||
/// The lifespan of the tgui_alert, after which the window will close and delete itself.
|
||||
var/timeout
|
||||
/// The bool that controls if this modal should grab window focus
|
||||
var/autofocus
|
||||
/// Boolean field describing if the tgui_alert was closed by the user.
|
||||
var/closed
|
||||
/// The TGUI UI state that will be returned in ui_state(). Default: always_state
|
||||
var/datum/ui_state/state
|
||||
|
||||
/datum/tgui_alert/New(mob/user, message, title, list/buttons, timeout, autofocus, ui_state)
|
||||
src.autofocus = autofocus
|
||||
src.buttons = buttons.Copy()
|
||||
src.message = message
|
||||
src.title = title
|
||||
src.state = ui_state
|
||||
if (timeout)
|
||||
src.timeout = timeout
|
||||
start_time = world.time
|
||||
spawn(timeout)
|
||||
qdel(src)
|
||||
|
||||
/datum/tgui_alert/Destroy(force)
|
||||
SStgui.close_uis(src)
|
||||
state = null
|
||||
buttons?.Cut()
|
||||
return ..()
|
||||
|
||||
/**
|
||||
* Waits for a user's response to the tgui_alert's prompt before returning. Returns early if
|
||||
* the window was closed by the user.
|
||||
*/
|
||||
/datum/tgui_alert/proc/wait()
|
||||
while (!choice && !closed && !QDELETED(src))
|
||||
stoplag(1)
|
||||
|
||||
/datum/tgui_alert/ui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "AlertModal")
|
||||
ui.open()
|
||||
|
||||
/datum/tgui_alert/ui_close(mob/user)
|
||||
. = ..()
|
||||
closed = TRUE
|
||||
|
||||
/datum/tgui_alert/ui_state(mob/user)
|
||||
return state
|
||||
|
||||
/datum/tgui_alert/ui_static_data(mob/user)
|
||||
var/list/data = list()
|
||||
data["autofocus"] = autofocus
|
||||
data["buttons"] = buttons
|
||||
data["message"] = message
|
||||
data["large_buttons"] = user.client.prefs.tgui_input_large
|
||||
data["swapped_buttons"] = user.client.prefs.tgui_input_swapped
|
||||
data["title"] = title
|
||||
return data
|
||||
|
||||
/datum/tgui_alert/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
if(timeout)
|
||||
data["timeout"] = CLAMP01((timeout - (world.time - start_time) - 1 SECONDS) / (timeout - 1 SECONDS))
|
||||
return data
|
||||
|
||||
/datum/tgui_alert/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
|
||||
. = ..()
|
||||
if (.)
|
||||
return
|
||||
switch(action)
|
||||
if("choose")
|
||||
if (!(params["choice"] in buttons))
|
||||
CRASH("[usr] entered a non-existent button choice: [params["choice"]]")
|
||||
set_choice(params["choice"])
|
||||
closed = TRUE
|
||||
SStgui.close_uis(src)
|
||||
return TRUE
|
||||
if("cancel")
|
||||
closed = TRUE
|
||||
SStgui.close_uis(src)
|
||||
return TRUE
|
||||
|
||||
/datum/tgui_alert/proc/set_choice(choice)
|
||||
src.choice = choice
|
||||
140
code/modules/tgui_input/checkboxes.dm
Normal file
140
code/modules/tgui_input/checkboxes.dm
Normal file
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* ### tgui_input_checkbox
|
||||
* Opens a window with a list of checkboxes and returns a list of selected choices.
|
||||
*
|
||||
* user - The mob to display the window to
|
||||
* message - The message inside the window
|
||||
* title - The title of the window
|
||||
* list/items - The list of items to display
|
||||
* min_checked - The minimum number of checkboxes that must be checked (defaults to 1)
|
||||
* max_checked - The maximum number of checkboxes that can be checked (optional)
|
||||
* timeout - The timeout for the input (optional)
|
||||
*/
|
||||
/proc/tgui_input_checkboxes(mob/user, message, title = "Select", list/items, min_checked = 1, max_checked = 50, timeout = 0, ui_state = global.always_state)
|
||||
if (!user)
|
||||
user = usr
|
||||
if(!length(items))
|
||||
return null
|
||||
if (!istype(user))
|
||||
if (istype(user, /client))
|
||||
var/client/client = user
|
||||
user = client.mob
|
||||
else
|
||||
return null
|
||||
|
||||
if(isnull(user.client))
|
||||
return null
|
||||
|
||||
if(!user.client.prefs.tgui_input)
|
||||
return input(user, message, title) as null|anything in items
|
||||
var/datum/tgui_checkbox_input/input = new(user, message, title, items, min_checked, max_checked, timeout, ui_state)
|
||||
input.ui_interact(user)
|
||||
input.wait()
|
||||
if (input)
|
||||
. = input.choices
|
||||
qdel(input)
|
||||
|
||||
/// Window for tgui_input_checkboxes
|
||||
/datum/tgui_checkbox_input
|
||||
/// Title of the window
|
||||
var/title
|
||||
/// Message to display
|
||||
var/message
|
||||
/// List of items to display
|
||||
var/list/items
|
||||
/// List of selected items
|
||||
var/list/choices
|
||||
/// Time when the input was created
|
||||
var/start_time
|
||||
/// Timeout for the input
|
||||
var/timeout
|
||||
/// Whether the input was closed
|
||||
var/closed
|
||||
/// Minimum number of checkboxes that must be checked
|
||||
var/min_checked
|
||||
/// Maximum number of checkboxes that can be checked
|
||||
var/max_checked
|
||||
/// The TGUI UI state that will be returned in ui_state(). Default: always_state
|
||||
var/datum/ui_state/state
|
||||
|
||||
/datum/tgui_checkbox_input/New(mob/user, message, title, list/items, min_checked, max_checked, timeout, ui_state)
|
||||
src.title = title
|
||||
src.message = message
|
||||
src.items = items.Copy()
|
||||
src.min_checked = min_checked
|
||||
src.max_checked = max_checked
|
||||
src.state = ui_state
|
||||
|
||||
if (timeout)
|
||||
src.timeout = timeout
|
||||
start_time = world.time
|
||||
spawn(timeout)
|
||||
qdel(src)
|
||||
|
||||
/datum/tgui_checkbox_input/Destroy(force)
|
||||
SStgui.close_uis(src)
|
||||
state = null
|
||||
items?.Cut()
|
||||
return ..()
|
||||
|
||||
/datum/tgui_checkbox_input/proc/wait()
|
||||
while (!closed && !QDELETED(src))
|
||||
stoplag(1)
|
||||
|
||||
/datum/tgui_checkbox_input/ui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "CheckboxInput")
|
||||
ui.open()
|
||||
|
||||
/datum/tgui_checkbox_input/ui_close(mob/user)
|
||||
. = ..()
|
||||
closed = TRUE
|
||||
|
||||
/datum/tgui_checkbox_input/ui_state(mob/user)
|
||||
return state
|
||||
|
||||
/datum/tgui_checkbox_input/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
if(timeout)
|
||||
data["timeout"] = CLAMP01((timeout - (world.time - start_time) - 1 SECONDS) / (timeout - 1 SECONDS))
|
||||
|
||||
return data
|
||||
|
||||
/datum/tgui_checkbox_input/ui_static_data(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
data["items"] = items
|
||||
data["min_checked"] = min_checked
|
||||
data["max_checked"] = max_checked
|
||||
data["large_buttons"] = user.client.prefs.tgui_input_large
|
||||
data["message"] = message
|
||||
data["swapped_buttons"] = user.client.prefs.tgui_input_swapped
|
||||
data["title"] = title
|
||||
|
||||
return data
|
||||
|
||||
/datum/tgui_checkbox_input/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
|
||||
. = ..()
|
||||
if (.)
|
||||
return
|
||||
|
||||
switch(action)
|
||||
if("submit")
|
||||
var/list/selections = params["entry"]
|
||||
if(length(selections) >= min_checked && length(selections) <= max_checked)
|
||||
set_choices(selections)
|
||||
closed = TRUE
|
||||
SStgui.close_uis(src)
|
||||
return TRUE
|
||||
|
||||
if("cancel")
|
||||
closed = TRUE
|
||||
SStgui.close_uis(src)
|
||||
return TRUE
|
||||
|
||||
return FALSE
|
||||
|
||||
/datum/tgui_checkbox_input/proc/set_choices(list/selections)
|
||||
src.choices = selections.Copy()
|
||||
127
code/modules/tgui_input/keycombo.dm
Normal file
127
code/modules/tgui_input/keycombo.dm
Normal file
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* Creates a TGUI window with a key input. Returns the user's response as a full key with modifiers, eg ShiftK.
|
||||
*
|
||||
* This proc should be used to create windows for key entry that the caller will wait for a response from.
|
||||
* If tgui fancy chat is turned off: Will return a normal input.
|
||||
*
|
||||
* Arguments:
|
||||
* * user - The user to show the number input to.
|
||||
* * message - The content of the number input, shown in the body of the TGUI window.
|
||||
* * title - The title of the number input modal, shown on the top of the TGUI window.
|
||||
* * default - The default (or current) key, shown as a placeholder.
|
||||
*/
|
||||
/proc/tgui_input_keycombo(mob/user = usr, message, title = "Key Input", default = 0, timeout = 0, ui_state = global.always_state)
|
||||
if (!istype(user))
|
||||
if (istype(user, /client))
|
||||
var/client/client = user
|
||||
user = client.mob
|
||||
else
|
||||
return null
|
||||
|
||||
if (isnull(user.client))
|
||||
return null
|
||||
|
||||
// Client does NOT have tgui_input on: Returns regular input
|
||||
if(!user.client.prefs.tgui_input)
|
||||
var/input_key = input(user, message, title + "(Modifiers are TGUI only, sorry!)", default) as null|text
|
||||
return input_key[1]
|
||||
var/datum/tgui_input_keycombo/key_input = new(user, message, title, default, timeout, ui_state)
|
||||
key_input.ui_interact(user)
|
||||
key_input.wait()
|
||||
if (key_input)
|
||||
. = key_input.entry
|
||||
qdel(key_input)
|
||||
|
||||
/**
|
||||
* # tgui_input_keycombo
|
||||
*
|
||||
* Datum used for instantiating and using a TGUI-controlled key input that prompts the user with
|
||||
* a message and listens for key presses.
|
||||
*/
|
||||
/datum/tgui_input_keycombo
|
||||
/// Boolean field describing if the tgui_input_number was closed by the user.
|
||||
var/closed
|
||||
/// The default (or current) value, shown as a default. Users can press reset with this.
|
||||
var/default
|
||||
/// The entry that the user has return_typed in.
|
||||
var/entry
|
||||
/// The prompt's body, if any, of the TGUI window.
|
||||
var/message
|
||||
/// The time at which the number input was created, for displaying timeout progress.
|
||||
var/start_time
|
||||
/// The lifespan of the number input, after which the window will close and delete itself.
|
||||
var/timeout
|
||||
/// The title of the TGUI window
|
||||
var/title
|
||||
/// The TGUI UI state that will be returned in ui_state(). Default: always_state
|
||||
var/datum/ui_state/state
|
||||
|
||||
/datum/tgui_input_keycombo/New(mob/user, message, title, default, timeout, ui_state)
|
||||
src.default = default
|
||||
src.message = message
|
||||
src.title = title
|
||||
src.state = ui_state
|
||||
if (timeout)
|
||||
src.timeout = timeout
|
||||
start_time = world.time
|
||||
spawn(timeout)
|
||||
qdel(src)
|
||||
|
||||
/datum/tgui_input_keycombo/Destroy(force)
|
||||
SStgui.close_uis(src)
|
||||
state = null
|
||||
return ..()
|
||||
|
||||
/**
|
||||
* Waits for a user's response to the tgui_input_keycombo's prompt before returning. Returns early if
|
||||
* the window was closed by the user.
|
||||
*/
|
||||
/datum/tgui_input_keycombo/proc/wait()
|
||||
while (!entry && !closed && !QDELETED(src))
|
||||
stoplag(1)
|
||||
|
||||
/datum/tgui_input_keycombo/ui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "KeyComboModal")
|
||||
ui.open()
|
||||
|
||||
/datum/tgui_input_keycombo/ui_close(mob/user)
|
||||
. = ..()
|
||||
closed = TRUE
|
||||
|
||||
/datum/tgui_input_keycombo/ui_state(mob/user)
|
||||
return state
|
||||
|
||||
/datum/tgui_input_keycombo/ui_static_data(mob/user)
|
||||
var/list/data = list()
|
||||
data["init_value"] = default // Default is a reserved keyword
|
||||
data["large_buttons"] = user.client.prefs.tgui_input_large
|
||||
data["message"] = message
|
||||
data["swapped_buttons"] = user.client.prefs.tgui_input_swapped
|
||||
data["title"] = title
|
||||
return data
|
||||
|
||||
/datum/tgui_input_keycombo/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
if(timeout)
|
||||
data["timeout"] = CLAMP01((timeout - (world.time - start_time) - 1 SECONDS) / (timeout - 1 SECONDS))
|
||||
return data
|
||||
|
||||
/datum/tgui_input_keycombo/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
|
||||
. = ..()
|
||||
if (.)
|
||||
return
|
||||
switch(action)
|
||||
if("submit")
|
||||
set_entry(params["entry"])
|
||||
closed = TRUE
|
||||
SStgui.close_uis(src)
|
||||
return TRUE
|
||||
if("cancel")
|
||||
closed = TRUE
|
||||
SStgui.close_uis(src)
|
||||
return TRUE
|
||||
|
||||
/datum/tgui_input_keycombo/proc/set_entry(entry)
|
||||
src.entry = entry
|
||||
160
code/modules/tgui_input/list.dm
Normal file
160
code/modules/tgui_input/list.dm
Normal file
@@ -0,0 +1,160 @@
|
||||
/**
|
||||
* Creates a TGUI input list window and returns the user's response.
|
||||
*
|
||||
* This proc should be used to create alerts that the caller will wait for a response from.
|
||||
* Arguments:
|
||||
* * user - The user to show the input box to.
|
||||
* * message - The content of the input box, shown in the body of the TGUI window.
|
||||
* * title - The title of the input box, shown on the top of the TGUI window.
|
||||
* * items - The options that can be chosen by the user, each string is assigned a button on the UI.
|
||||
* * default - If an option is already preselected on the UI. Current values, etc.
|
||||
* * timeout - The timeout of the input box, after which the menu will close and qdel itself. Set to zero for no timeout.
|
||||
*/
|
||||
/proc/tgui_input_list(mob/user, message, title = "Select", list/items, default, timeout = 0, ui_state = global.always_state)
|
||||
if (!user)
|
||||
user = usr
|
||||
if(!length(items))
|
||||
return null
|
||||
if (!istype(user))
|
||||
if (istype(user, /client))
|
||||
var/client/client = user
|
||||
user = client.mob
|
||||
else
|
||||
return null
|
||||
|
||||
if(isnull(user.client))
|
||||
return null
|
||||
|
||||
/// Client does NOT have tgui_input on: Returns regular input
|
||||
if(!user.client.prefs.tgui_input)
|
||||
return input(user, message, title, default) as null|anything in items
|
||||
var/datum/tgui_list_input/input = new(user, message, title, items, default, timeout, ui_state)
|
||||
if(input.invalid)
|
||||
qdel(input)
|
||||
return
|
||||
input.ui_interact(user)
|
||||
input.wait()
|
||||
if (input)
|
||||
. = input.choice
|
||||
qdel(input)
|
||||
|
||||
/**
|
||||
* # tgui_list_input
|
||||
*
|
||||
* Datum used for instantiating and using a TGUI-controlled list input that prompts the user with
|
||||
* a message and shows a list of selectable options
|
||||
*/
|
||||
/datum/tgui_list_input
|
||||
/// The title of the TGUI window
|
||||
var/title
|
||||
/// The textual body of the TGUI window
|
||||
var/message
|
||||
/// The list of items (responses) provided on the TGUI window
|
||||
var/list/items
|
||||
/// Buttons (strings specifically) mapped to the actual value (e.g. a mob or a verb)
|
||||
var/list/items_map
|
||||
/// The button that the user has pressed, null if no selection has been made
|
||||
var/choice
|
||||
/// The default button to be selected
|
||||
var/default
|
||||
/// The time at which the tgui_list_input was created, for displaying timeout progress.
|
||||
var/start_time
|
||||
/// The lifespan of the tgui_list_input, after which the window will close and delete itself.
|
||||
var/timeout
|
||||
/// Boolean field describing if the tgui_list_input was closed by the user.
|
||||
var/closed
|
||||
/// The TGUI UI state that will be returned in ui_state(). Default: always_state
|
||||
var/datum/ui_state/state
|
||||
/// Whether the tgui list input is invalid or not (i.e. due to all list entries being null)
|
||||
var/invalid = FALSE
|
||||
|
||||
/datum/tgui_list_input/New(mob/user, message, title, list/items, default, timeout, ui_state)
|
||||
src.title = title
|
||||
src.message = message
|
||||
src.items = list()
|
||||
src.items_map = list()
|
||||
src.default = default
|
||||
src.state = ui_state
|
||||
var/list/repeat_items = list()
|
||||
// Gets rid of illegal characters
|
||||
var/static/regex/whitelistedWords = regex(@{"([^\u0020-\u8000]+)"})
|
||||
for(var/i in items)
|
||||
if(!i)
|
||||
continue
|
||||
var/string_key = whitelistedWords.Replace("[i]", "")
|
||||
//avoids duplicated keys E.g: when areas have the same name
|
||||
string_key = avoid_assoc_duplicate_keys(string_key, repeat_items)
|
||||
src.items += string_key
|
||||
src.items_map[string_key] = i
|
||||
|
||||
if(length(src.items) == 0)
|
||||
invalid = TRUE
|
||||
if (timeout)
|
||||
src.timeout = timeout
|
||||
start_time = world.time
|
||||
spawn(timeout)
|
||||
qdel(src)
|
||||
|
||||
/datum/tgui_list_input/Destroy(force)
|
||||
SStgui.close_uis(src)
|
||||
state = null
|
||||
items?.Cut()
|
||||
items_map?.Cut()
|
||||
return ..()
|
||||
|
||||
/**
|
||||
* Waits for a user's response to the tgui_list_input's prompt before returning. Returns early if
|
||||
* the window was closed by the user.
|
||||
*/
|
||||
/datum/tgui_list_input/proc/wait()
|
||||
while (!choice && !closed)
|
||||
stoplag(1)
|
||||
|
||||
/datum/tgui_list_input/ui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "ListInputWindow")
|
||||
ui.open()
|
||||
|
||||
/datum/tgui_list_input/ui_close(mob/user)
|
||||
. = ..()
|
||||
closed = TRUE
|
||||
|
||||
/datum/tgui_list_input/ui_state(mob/user)
|
||||
return state
|
||||
|
||||
/datum/tgui_list_input/ui_static_data(mob/user)
|
||||
var/list/data = list()
|
||||
data["init_value"] = default || items[1]
|
||||
data["items"] = items
|
||||
data["large_buttons"] = user.client.prefs.tgui_input_large
|
||||
data["message"] = message
|
||||
data["swapped_buttons"] = user.client.prefs.tgui_input_swapped
|
||||
data["title"] = title
|
||||
return data
|
||||
|
||||
/datum/tgui_list_input/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
if(timeout)
|
||||
data["timeout"] = clamp((timeout - (world.time - start_time) - 1 SECONDS) / (timeout - 1 SECONDS), 0, 1)
|
||||
return data
|
||||
|
||||
/datum/tgui_list_input/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
|
||||
. = ..()
|
||||
if (.)
|
||||
return
|
||||
switch(action)
|
||||
if("submit")
|
||||
if (!(params["entry"] in items))
|
||||
return
|
||||
set_choice(items_map[params["entry"]])
|
||||
closed = TRUE
|
||||
SStgui.close_uis(src)
|
||||
return TRUE
|
||||
if("cancel")
|
||||
closed = TRUE
|
||||
SStgui.close_uis(src)
|
||||
return TRUE
|
||||
|
||||
/datum/tgui_list_input/proc/set_choice(choice)
|
||||
src.choice = choice
|
||||
163
code/modules/tgui_input/number.dm
Normal file
163
code/modules/tgui_input/number.dm
Normal file
@@ -0,0 +1,163 @@
|
||||
/**
|
||||
* Creates a TGUI window with a number input. Returns the user's response as num | null.
|
||||
*
|
||||
* This proc should be used to create windows for number entry that the caller will wait for a response from.
|
||||
* If tgui fancy chat is turned off: Will return a normal input. If a max or min value is specified, will
|
||||
* validate the input inside the UI and ui_act.
|
||||
*
|
||||
* Arguments:
|
||||
* * user - The user to show the number input to.
|
||||
* * message - The content of the number input, shown in the body of the TGUI window.
|
||||
* * title - The title of the number input modal, shown on the top of the TGUI window.
|
||||
* * default - The default (or current) value, shown as a placeholder. Users can press refresh with this.
|
||||
* * max_value - Specifies a maximum value. If none is set, any number can be entered. Pressing "max" defaults to 1000.
|
||||
* * min_value - Specifies a minimum value. Often 0.
|
||||
* * timeout - The timeout of the number input, after which the modal will close and qdel itself. Set to zero for no timeout.
|
||||
* * round_value - whether the inputted number is rounded down into an integer.
|
||||
*/
|
||||
/proc/tgui_input_number(mob/user, message, title = "Number Input", default = 0, max_value = 10000, min_value = 0, timeout = 0, round_value = TRUE, ui_state = global.always_state)
|
||||
if (!user)
|
||||
user = usr
|
||||
if (!istype(user))
|
||||
if (istype(user, /client))
|
||||
var/client/client = user
|
||||
user = client.mob
|
||||
else
|
||||
return null
|
||||
|
||||
if (isnull(user.client))
|
||||
return null
|
||||
|
||||
// Client does NOT have tgui_input on: Returns regular input
|
||||
if(!user.client.prefs.tgui_input)
|
||||
var/input_number = input(user, message, title, default) as null|num
|
||||
return clamp(round_value ? round(input_number) : input_number, min_value, max_value)
|
||||
var/datum/tgui_input_number/number_input = new(user, message, title, default, max_value, min_value, timeout, round_value, ui_state)
|
||||
number_input.ui_interact(user)
|
||||
number_input.wait()
|
||||
if (number_input)
|
||||
. = number_input.entry
|
||||
qdel(number_input)
|
||||
|
||||
/**
|
||||
* # tgui_input_number
|
||||
*
|
||||
* Datum used for instantiating and using a TGUI-controlled number input that prompts the user with
|
||||
* a message and has an input for number entry.
|
||||
*/
|
||||
/datum/tgui_input_number
|
||||
/// Boolean field describing if the tgui_input_number was closed by the user.
|
||||
var/closed
|
||||
/// The default (or current) value, shown as a default. Users can press reset with this.
|
||||
var/default
|
||||
/// The entry that the user has return_typed in.
|
||||
var/entry
|
||||
/// The maximum value that can be entered.
|
||||
var/max_value
|
||||
/// The prompt's body, if any, of the TGUI window.
|
||||
var/message
|
||||
/// The minimum value that can be entered.
|
||||
var/min_value
|
||||
/// Whether the submitted number is rounded down into an integer.
|
||||
var/round_value
|
||||
/// The time at which the number input was created, for displaying timeout progress.
|
||||
var/start_time
|
||||
/// The lifespan of the number input, after which the window will close and delete itself.
|
||||
var/timeout
|
||||
/// The title of the TGUI window
|
||||
var/title
|
||||
/// The TGUI UI state that will be returned in ui_state(). Default: always_state
|
||||
var/datum/ui_state/state
|
||||
|
||||
/datum/tgui_input_number/New(mob/user, message, title, default, max_value, min_value, timeout, round_value, ui_state)
|
||||
src.default = default
|
||||
src.max_value = max_value
|
||||
src.message = message
|
||||
src.min_value = min_value
|
||||
src.title = title
|
||||
src.round_value = round_value
|
||||
src.state = ui_state
|
||||
if (timeout)
|
||||
src.timeout = timeout
|
||||
start_time = world.time
|
||||
spawn(timeout)
|
||||
qdel(src)
|
||||
/// Checks for empty numbers - bank accounts, etc.
|
||||
if(max_value == 0)
|
||||
src.min_value = 0
|
||||
if(default)
|
||||
src.default = 0
|
||||
/// Sanity check
|
||||
if(default < min_value)
|
||||
src.default = min_value
|
||||
if(default > max_value)
|
||||
CRASH("Default value is greater than max value.")
|
||||
|
||||
/datum/tgui_input_number/Destroy(force)
|
||||
SStgui.close_uis(src)
|
||||
state = null
|
||||
return ..()
|
||||
|
||||
/**
|
||||
* Waits for a user's response to the tgui_input_number's prompt before returning. Returns early if
|
||||
* the window was closed by the user.
|
||||
*/
|
||||
/datum/tgui_input_number/proc/wait()
|
||||
while (!entry && !closed && !QDELETED(src))
|
||||
stoplag(1)
|
||||
|
||||
/datum/tgui_input_number/ui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "NumberInputModal")
|
||||
ui.open()
|
||||
|
||||
/datum/tgui_input_number/ui_close(mob/user)
|
||||
. = ..()
|
||||
closed = TRUE
|
||||
|
||||
/datum/tgui_input_number/ui_state(mob/user)
|
||||
return state
|
||||
|
||||
/datum/tgui_input_number/ui_static_data(mob/user)
|
||||
var/list/data = list()
|
||||
data["init_value"] = default // Default is a reserved keyword
|
||||
data["large_buttons"] = user.client.prefs.tgui_input_large
|
||||
data["max_value"] = max_value
|
||||
data["message"] = message
|
||||
data["min_value"] = min_value
|
||||
data["swapped_buttons"] = user.client.prefs.tgui_input_swapped
|
||||
data["title"] = title
|
||||
data["round_value"] = round_value
|
||||
return data
|
||||
|
||||
/datum/tgui_input_number/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
if(timeout)
|
||||
data["timeout"] = CLAMP01((timeout - (world.time - start_time) - 1 SECONDS) / (timeout - 1 SECONDS))
|
||||
return data
|
||||
|
||||
/datum/tgui_input_number/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
|
||||
. = ..()
|
||||
if (.)
|
||||
return
|
||||
switch(action)
|
||||
if("submit")
|
||||
if(!isnum(params["entry"]))
|
||||
CRASH("A non number was input into tgui input number by [usr]")
|
||||
var/choice = round_value ? round(params["entry"]) : params["entry"]
|
||||
if(choice > max_value)
|
||||
CRASH("A number greater than the max value was input into tgui input number by [usr]")
|
||||
if(choice < min_value)
|
||||
CRASH("A number less than the min value was input into tgui input number by [usr]")
|
||||
set_entry(choice)
|
||||
closed = TRUE
|
||||
SStgui.close_uis(src)
|
||||
return TRUE
|
||||
if("cancel")
|
||||
closed = TRUE
|
||||
SStgui.close_uis(src)
|
||||
return TRUE
|
||||
|
||||
/datum/tgui_input_number/proc/set_entry(entry)
|
||||
src.entry = entry
|
||||
166
code/modules/tgui_input/text.dm
Normal file
166
code/modules/tgui_input/text.dm
Normal file
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* Creates a TGUI window with a text input. Returns the user's response.
|
||||
*
|
||||
* This proc should be used to create windows for text entry that the caller will wait for a response from.
|
||||
* If tgui fancy chat is turned off: Will return a normal input. If max_length is specified, will return
|
||||
* stripped_multiline_input.
|
||||
*
|
||||
* Arguments:
|
||||
* * user - The user to show the text input to.
|
||||
* * message - The content of the text input, shown in the body of the TGUI window.
|
||||
* * title - The title of the text input modal, shown on the top of the TGUI window.
|
||||
* * default - The default (or current) value, shown as a placeholder.
|
||||
* * max_length - Specifies a max length for input. By default is infinity.
|
||||
* * multiline - Bool that determines if the input box is much larger. Good for large messages, laws, etc.
|
||||
* * encode - Toggling this determines if input is filtered via html_encode. Setting this to FALSE gives raw input.
|
||||
* * timeout - The timeout of the textbox, after which the modal will close and qdel itself. Set to zero for no timeout.
|
||||
*/
|
||||
/proc/tgui_input_text(mob/user, message = "", title = "Text Input", default, max_length, multiline = FALSE, encode = TRUE, timeout = 0, ui_state = global.always_state)
|
||||
if (!user)
|
||||
user = usr
|
||||
if (!istype(user))
|
||||
if (istype(user, /client))
|
||||
var/client/client = user
|
||||
user = client.mob
|
||||
else
|
||||
return null
|
||||
|
||||
if(isnull(user.client))
|
||||
return null
|
||||
|
||||
// Client does NOT have tgui_input on: Returns regular input
|
||||
if(!user.client.prefs.tgui_input)
|
||||
if(encode)
|
||||
if(multiline)
|
||||
return stripped_multiline_input(user, message, title, default, PREVENT_CHARACTER_TRIM_LOSS(max_length))
|
||||
else
|
||||
return stripped_input(user, message, title, default, PREVENT_CHARACTER_TRIM_LOSS(max_length))
|
||||
else
|
||||
if(multiline)
|
||||
return input(user, message, title, default) as message|null
|
||||
else
|
||||
return input(user, message, title, default) as text|null
|
||||
var/datum/tgui_input_text/text_input = new(user, message, title, default, max_length, multiline, encode, timeout, ui_state)
|
||||
text_input.ui_interact(user)
|
||||
text_input.wait()
|
||||
if (text_input)
|
||||
. = text_input.entry
|
||||
qdel(text_input)
|
||||
|
||||
/**
|
||||
* tgui_input_text
|
||||
*
|
||||
* Datum used for instantiating and using a TGUI-controlled text input that prompts the user with
|
||||
* a message and has an input for text entry.
|
||||
*/
|
||||
/datum/tgui_input_text
|
||||
/// Boolean field describing if the tgui_input_text was closed by the user.
|
||||
var/closed
|
||||
/// The default (or current) value, shown as a default.
|
||||
var/default
|
||||
/// Whether the input should be stripped using html_encode
|
||||
var/encode
|
||||
/// The entry that the user has return_typed in.
|
||||
var/entry
|
||||
/// The maximum length for text entry
|
||||
var/max_length
|
||||
/// The prompt's body, if any, of the TGUI window.
|
||||
var/message
|
||||
/// Multiline input for larger input boxes.
|
||||
var/multiline
|
||||
/// The time at which the text input was created, for displaying timeout progress.
|
||||
var/start_time
|
||||
/// The lifespan of the text input, after which the window will close and delete itself.
|
||||
var/timeout
|
||||
/// The title of the TGUI window
|
||||
var/title
|
||||
/// The TGUI UI state that will be returned in ui_state(). Default: always_state
|
||||
var/datum/ui_state/state
|
||||
|
||||
/datum/tgui_input_text/New(mob/user, message, title, default, max_length, multiline, encode, timeout, ui_state)
|
||||
src.default = default
|
||||
src.encode = encode
|
||||
src.max_length = max_length
|
||||
src.message = message
|
||||
src.multiline = multiline
|
||||
src.title = title
|
||||
src.state = ui_state
|
||||
if (timeout)
|
||||
src.timeout = timeout
|
||||
start_time = world.time
|
||||
spawn(timeout)
|
||||
qdel(src)
|
||||
|
||||
/datum/tgui_input_text/Destroy(force)
|
||||
SStgui.close_uis(src)
|
||||
state = null
|
||||
return ..()
|
||||
|
||||
/**
|
||||
* Waits for a user's response to the tgui_input_text's prompt before returning. Returns early if
|
||||
* the window was closed by the user.
|
||||
*/
|
||||
/datum/tgui_input_text/proc/wait()
|
||||
while (!entry && !closed && !QDELETED(src))
|
||||
stoplag(1)
|
||||
|
||||
/datum/tgui_input_text/ui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "TextInputModal")
|
||||
ui.open()
|
||||
|
||||
/datum/tgui_input_text/ui_close(mob/user)
|
||||
. = ..()
|
||||
closed = TRUE
|
||||
|
||||
/datum/tgui_input_text/ui_state(mob/user)
|
||||
return state
|
||||
|
||||
/datum/tgui_input_text/ui_static_data(mob/user)
|
||||
var/list/data = list()
|
||||
data["large_buttons"] = user.client.prefs.tgui_input_large
|
||||
data["max_length"] = max_length
|
||||
data["message"] = message
|
||||
data["multiline"] = multiline
|
||||
data["placeholder"] = default // Default is a reserved keyword
|
||||
data["swapped_buttons"] = user.client.prefs.tgui_input_swapped
|
||||
data["title"] = title
|
||||
return data
|
||||
|
||||
/datum/tgui_input_text/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
if(timeout)
|
||||
data["timeout"] = CLAMP01((timeout - (world.time - start_time) - 1 SECONDS) / (timeout - 1 SECONDS))
|
||||
return data
|
||||
|
||||
/datum/tgui_input_text/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
|
||||
. = ..()
|
||||
if (.)
|
||||
return
|
||||
switch(action)
|
||||
if("submit")
|
||||
if(max_length)
|
||||
if(length_char(params["entry"]) > max_length)
|
||||
CRASH("[usr] typed a text string longer than the max length")
|
||||
if(encode && (length_char(html_encode(params["entry"])) > max_length))
|
||||
to_chat(usr, span_notice("Your message was clipped due to special character usage."))
|
||||
set_entry(params["entry"])
|
||||
closed = TRUE
|
||||
SStgui.close_uis(src)
|
||||
return TRUE
|
||||
if("cancel")
|
||||
closed = TRUE
|
||||
SStgui.close_uis(src)
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Sets the return value for the tgui text proc.
|
||||
* If html encoding is enabled, the text will be encoded.
|
||||
* This can sometimes result in a string that is longer than the max length.
|
||||
* If the string is longer than the max length, it will be clipped.
|
||||
*/
|
||||
/datum/tgui_input_text/proc/set_entry(entry)
|
||||
if(!isnull(entry))
|
||||
var/converted_entry = encode ? html_encode(entry) : entry
|
||||
src.entry = max_length ? trim(converted_entry, PREVENT_CHARACTER_TRIM_LOSS(max_length)) : converted_entry
|
||||
@@ -3,27 +3,27 @@
|
||||
task.file_path = "data/persistence/money_highscores_test.json"
|
||||
task.clear_records()
|
||||
|
||||
var/datum/record/money/test_record = new("somebody", "Assistant", 40000)
|
||||
var/datum/record/money/test_record2 = new("a condom", "Captain", 35000)
|
||||
var/datum/record/money/test_record3 = new("this should not appear", "Nobody", 10000)
|
||||
var/datum/record/money/test_record4 = new("cuban pete", "Miner", 50000)
|
||||
var/datum/record/money/test_record5 = new("pedro cubano", "Miner", 30000)
|
||||
var/datum/record/money/test_record6 = new("frank sinatra", "Miner", 9999999)
|
||||
var/datum/data/record/money/test_record = new("somebody", "Assistant", 40000)
|
||||
var/datum/data/record/money/test_record2 = new("a condom", "Captain", 35000)
|
||||
var/datum/data/record/money/test_record3 = new("this should not appear", "Nobody", 10000)
|
||||
var/datum/data/record/money/test_record4 = new("cuban pete", "Miner", 50000)
|
||||
var/datum/data/record/money/test_record5 = new("pedro cubano", "Miner", 30000)
|
||||
var/datum/data/record/money/test_record6 = new("frank sinatra", "Miner", 9999999)
|
||||
var/list/records = list(test_record, test_record2, test_record3, test_record4, test_record5, test_record6)
|
||||
|
||||
task.insert_records(records)
|
||||
|
||||
assert_eq(task.data[1].cash, 9999999)
|
||||
assert_eq(task.data[2].cash, 50000)
|
||||
assert_eq(task.data[3].cash, 40000)
|
||||
assert_eq(task.data[4].cash, 35000)
|
||||
assert_eq(task.data[5].cash, 30000)
|
||||
assert_eq(task.data[1].fields["cash"], 9999999)
|
||||
assert_eq(task.data[2].fields["cash"], 50000)
|
||||
assert_eq(task.data[3].fields["cash"], 40000)
|
||||
assert_eq(task.data[4].fields["cash"], 35000)
|
||||
assert_eq(task.data[5].fields["cash"], 30000)
|
||||
|
||||
assert_eq(task.data[1].role, "Miner")
|
||||
assert_eq(task.data[2].role, "Miner")
|
||||
assert_eq(task.data[3].role, "Assistant")
|
||||
assert_eq(task.data[4].role, "Captain")
|
||||
assert_eq(task.data[5].role, "Miner")
|
||||
assert_eq(task.data[1].fields["role"], "Miner")
|
||||
assert_eq(task.data[2].fields["role"], "Miner")
|
||||
assert_eq(task.data[3].fields["role"], "Assistant")
|
||||
assert_eq(task.data[4].fields["role"], "Captain")
|
||||
assert_eq(task.data[5].fields["role"], "Miner")
|
||||
|
||||
if(task.data.len > 5)
|
||||
fail("[task.name] is storing more than five entries. Len: [task.data.len]")
|
||||
@@ -31,8 +31,8 @@
|
||||
task.on_shutdown()
|
||||
task.on_init()
|
||||
|
||||
assert_eq(task.data[1].cash, 9999999)
|
||||
assert_eq(task.data[2].cash, 50000)
|
||||
assert_eq(task.data[3].cash, 40000)
|
||||
assert_eq(task.data[4].cash, 35000)
|
||||
assert_eq(task.data[5].cash, 30000)
|
||||
assert_eq(task.data[1].fields["cash"], 9999999)
|
||||
assert_eq(task.data[2].fields["cash"], 50000)
|
||||
assert_eq(task.data[3].fields["cash"], 40000)
|
||||
assert_eq(task.data[4].fields["cash"], 35000)
|
||||
assert_eq(task.data[5].fields["cash"], 30000)
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
/datum/unit_test/ray_equals/start()
|
||||
var/vector/origin = new /vector(0,3)
|
||||
var/vector/direction = new /vector(4,7)
|
||||
var/_vector/origin = new /_vector(0,3)
|
||||
var/_vector/direction = new /_vector(4,7)
|
||||
var/ray/R1 = new /ray(origin, direction)
|
||||
var/ray/R2 = new /ray(origin, direction)
|
||||
if(!R1.equals(R2))
|
||||
fail("Rays not equal")
|
||||
|
||||
/datum/unit_test/ray_overlaps/start()
|
||||
var/vector/origin = new /vector(0,3)
|
||||
var/vector/direction = new /vector(4,7)
|
||||
var/_vector/origin = new /_vector(0,3)
|
||||
var/_vector/direction = new /_vector(4,7)
|
||||
var/ray/R1 = new /ray(origin, direction)
|
||||
var/ray/R2 = new /ray(origin, direction)
|
||||
if(!R1.overlaps(R2))
|
||||
fail("Rays #1 not overlapping")
|
||||
|
||||
direction = new /vector(-4,-7)
|
||||
direction = new /_vector(-4,-7)
|
||||
R1 = new /ray(origin, direction)
|
||||
R2 = new /ray(origin, direction)
|
||||
if(!R1.equals(R2))
|
||||
fail("Rays #2 not overlapping")
|
||||
|
||||
/datum/unit_test/ray_hitsPoint/start()
|
||||
var/vector/origin = new /vector(0,3)
|
||||
var/vector/direction = new /vector(2,10)
|
||||
var/vector/P = new /vector(1,8)
|
||||
var/_vector/origin = new /_vector(0,3)
|
||||
var/_vector/direction = new /_vector(2,10)
|
||||
var/_vector/P = new /_vector(1,8)
|
||||
var/ray/R = new /ray(origin, direction)
|
||||
if(!R.hitsPoint(P))
|
||||
fail("Ray not hitting point")
|
||||
|
||||
/datum/unit_test/ray_getPoint/start()
|
||||
var/vector/origin = new /vector(0,3)
|
||||
var/vector/direction = new /vector(4,10)
|
||||
var/_vector/origin = new /_vector(0,3)
|
||||
var/_vector/direction = new /_vector(4,10)
|
||||
var/distance = 0.5
|
||||
var/vector/P = new /vector(0.2,3.5)
|
||||
var/_vector/P = new /_vector(0.2,3.5)
|
||||
var/ray/R = new /ray(origin, direction)
|
||||
if(!R.getPoint(distance).equals(P))
|
||||
fail("Ray returned invalid point "+R.getPoint(distance).toString())
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/datum/unit_test/vector_duplicate/start()
|
||||
var/vector/V
|
||||
var/vector/D
|
||||
var/_vector/V
|
||||
var/_vector/D
|
||||
for(var/i in range(100))
|
||||
V = new /vector(i, i*2)
|
||||
V = new /_vector(i, i*2)
|
||||
D = V.duplicate()
|
||||
if(D == V)
|
||||
fail("Reference copied")
|
||||
@@ -11,47 +11,47 @@
|
||||
fail("Value mismatch")
|
||||
|
||||
/datum/unit_test/vector_isnull/start()
|
||||
var/vector/V = new /vector(0,0.0)
|
||||
var/_vector/V = new /_vector(0,0.0)
|
||||
if(!V.is_null())
|
||||
fail("Vector not null")
|
||||
|
||||
/datum/unit_test/vector_isint/start()
|
||||
var/vector/V = new /vector(5416,115)
|
||||
var/_vector/V = new /_vector(5416,115)
|
||||
if(!V.is_integer())
|
||||
fail("Vector not int, should be int")
|
||||
|
||||
V = new /vector(5416.044,115)
|
||||
V = new /_vector(5416.044,115)
|
||||
if(V.is_integer())
|
||||
fail("Vector int, should not be int")
|
||||
|
||||
/datum/unit_test/vector_toangle/start()
|
||||
var/vector/V = new /vector(1,1)
|
||||
var/_vector/V = new /_vector(1,1)
|
||||
var/angle = V.toAngle()
|
||||
if(angle != 45)
|
||||
fail("Angle #1 ("+num2text(angle)+") incorrect")
|
||||
|
||||
V = new /vector(-1,1)
|
||||
V = new /_vector(-1,1)
|
||||
angle = V.toAngle()
|
||||
if(angle != 315)
|
||||
fail("Angle #2 ("+num2text(angle)+") incorrect")
|
||||
|
||||
/datum/unit_test/vector_mirror/start()
|
||||
var/vector/V = new /vector(1,-1)
|
||||
var/vector/N = new /vector(0,1)
|
||||
var/vector/M = new /vector(1,1)
|
||||
var/vector/R = V.mirrorWithNormal(N)
|
||||
var/_vector/V = new /_vector(1,-1)
|
||||
var/_vector/N = new /_vector(0,1)
|
||||
var/_vector/M = new /_vector(1,1)
|
||||
var/_vector/R = V.mirrorWithNormal(N)
|
||||
if(!R.equals(M))
|
||||
fail("Mirror #2 incorrect "+R.toString())
|
||||
|
||||
/datum/unit_test/vector_dot/start()
|
||||
var/vector/V1 = new /vector(4,-1)
|
||||
var/vector/V2 = new /vector(1,1)
|
||||
var/_vector/V1 = new /_vector(4,-1)
|
||||
var/_vector/V2 = new /_vector(1,1)
|
||||
var/d = V1.dot(V2)
|
||||
if(d != 3)
|
||||
fail("Dot product #1 ("+num2text(d)+") incorrect")
|
||||
|
||||
V1 = new /vector(0,2)
|
||||
V2 = new /vector(1,1)
|
||||
V1 = new /_vector(0,2)
|
||||
V2 = new /_vector(1,1)
|
||||
d = V1.dot(V2)
|
||||
if(d != 2)
|
||||
fail("Dot product #2 ("+num2text(d)+") incorrect")
|
||||
|
||||
41
code/tgs_event_handler.dm
Normal file
41
code/tgs_event_handler.dm
Normal file
@@ -0,0 +1,41 @@
|
||||
/datum/tgs_event_handler/impl
|
||||
var/reattach_timer_id
|
||||
|
||||
/datum/tgs_event_handler/impl/HandleEvent(event_code, ...)
|
||||
switch(event_code)
|
||||
if(TGS_EVENT_REBOOT_MODE_CHANGE)
|
||||
var/list/reboot_mode_lookup = list ("[TGS_REBOOT_MODE_NORMAL]" = "be normal", "[TGS_REBOOT_MODE_SHUTDOWN]" = "shutdown the server", "[TGS_REBOOT_MODE_RESTART]" = "hard restart the server")
|
||||
var/old_reboot_mode = args[2]
|
||||
var/new_reboot_mode = args[3]
|
||||
message_admins("TGS: Reboot will no longer [reboot_mode_lookup["[old_reboot_mode]"]], it will instead [reboot_mode_lookup["[new_reboot_mode]"]]")
|
||||
if(TGS_EVENT_PORT_SWAP)
|
||||
message_admins("TGS: Changing port from [world.port] to [args[2]]")
|
||||
if(TGS_EVENT_INSTANCE_RENAMED)
|
||||
message_admins("TGS: Instance renamed to from [world.TgsInstanceName()] to [args[2]]")
|
||||
if(TGS_EVENT_COMPILE_START)
|
||||
message_admins("TGS: Deployment started, new game version incoming...")
|
||||
if(TGS_EVENT_COMPILE_CANCELLED)
|
||||
message_admins("TGS: Deployment cancelled!")
|
||||
if(TGS_EVENT_COMPILE_FAILURE)
|
||||
message_admins("TGS: Deployment failed!")
|
||||
if(TGS_EVENT_DEPLOYMENT_COMPLETE)
|
||||
message_admins("TGS: Deployment complete!")
|
||||
to_chat(world, "Server updated, changes will be applied on the next round...")
|
||||
if(TGS_EVENT_WATCHDOG_DETACH)
|
||||
message_admins("TGS restarting...")
|
||||
reattach_timer_id = add_timer(new /callback(src, nameof(src::LateOnReattach())), 600)
|
||||
if(TGS_EVENT_WATCHDOG_REATTACH)
|
||||
var/datum/tgs_version/old_version = world.TgsVersion()
|
||||
var/datum/tgs_version/new_version = args[2]
|
||||
if(!old_version.Equals(new_version))
|
||||
to_chat(world, "TGS updated to v[new_version.deprefixed_parameter]")
|
||||
else
|
||||
message_admins("TGS: Back online")
|
||||
if(reattach_timer_id)
|
||||
del_timer(reattach_timer_id)
|
||||
reattach_timer_id = null
|
||||
if(TGS_EVENT_WATCHDOG_SHUTDOWN)
|
||||
to_chat(world, "<span class='userdanger'>Server is shutting down!</span>")
|
||||
|
||||
/datum/tgs_event_handler/impl/proc/LateOnReattach()
|
||||
message_admins("Warning: TGS hasn't notified us of it coming back for a full minute! Is there a problem?")
|
||||
@@ -5,8 +5,8 @@ var/world_startup_time
|
||||
var/date_string
|
||||
var/force_restart
|
||||
|
||||
#if DM_VERSION < 515
|
||||
#error You need at least version 515 to compile
|
||||
#if DM_VERSION < 516
|
||||
#error You need at least version 516 to compile.
|
||||
#endif
|
||||
/world
|
||||
mob = /mob/new_player
|
||||
@@ -51,36 +51,15 @@ var/auxtools_path
|
||||
/world/New()
|
||||
world_startup_time = world.timeofday
|
||||
|
||||
TgsNew(null, TGS_SECURITY_TRUSTED)
|
||||
src.InitTgs()
|
||||
|
||||
for(var/i=1, i<=map.zLevels.len, i++)
|
||||
WORLD_X_OFFSET += rand(-50,50)
|
||||
WORLD_Y_OFFSET += rand(-50,50)
|
||||
|
||||
// logs
|
||||
date_string = time2text(world.realtime, "YYYY/MM-Month/DD-Day")
|
||||
|
||||
investigations[I_HREFS] = new /datum/log_controller(I_HREFS, filename="data/logs/[date_string] hrefs.htm", persist=TRUE)
|
||||
investigations[I_ATMOS] = new /datum/log_controller(I_ATMOS, filename="data/logs/[date_string] atmos.htm", persist=TRUE)
|
||||
investigations[I_CHEMS] = new /datum/log_controller(I_CHEMS, filename="data/logs/[date_string] chemistry.htm", persist=TRUE)
|
||||
investigations[I_WIRES] = new /datum/log_controller(I_WIRES, filename="data/logs/[date_string] wires.htm", persist=TRUE)
|
||||
investigations[I_GHOST] = new /datum/log_controller(I_GHOST, filename="data/logs/[date_string] poltergeist.htm", persist=TRUE)
|
||||
investigations[I_ARTIFACT] = new /datum/log_controller(I_ARTIFACT, filename="data/logs/[date_string] artifact.htm", persist=TRUE)
|
||||
investigations[I_RCD] = new /datum/log_controller(I_RCD, filename="data/logs/[date_string] rcd.htm", persist=TRUE)
|
||||
|
||||
diary = file("data/logs/[date_string].log")
|
||||
panicfile = new/savefile("data/logs/profiling/proclogs/[date_string].sav")
|
||||
diaryofmeanpeople = file("data/logs/[date_string] Attack.log")
|
||||
admin_diary = file("data/logs/[date_string] admin only.log")
|
||||
|
||||
var/now = time_stamp()
|
||||
var/log_start = "---------------------\n\[[now]\]WORLD: starting up..."
|
||||
|
||||
diary << log_start
|
||||
diaryofmeanpeople << log_start
|
||||
admin_diary << log_start
|
||||
panicfile.cd = now
|
||||
|
||||
InitializeLogs()
|
||||
changelog_hash = md5('html/changelog.html') //used for telling if the changelog has changed recently
|
||||
|
||||
load_configuration()
|
||||
@@ -114,6 +93,33 @@ var/auxtools_path
|
||||
|
||||
return ..()
|
||||
|
||||
/world/proc/InitTgs()
|
||||
TgsNew(new /datum/tgs_event_handler/impl, TGS_SECURITY_TRUSTED)
|
||||
|
||||
/world/proc/InitializeLogs()
|
||||
investigations[I_HREFS] = new /datum/log_controller(I_HREFS, filename="data/logs/[date_string] hrefs.htm", persist=TRUE)
|
||||
investigations[I_ATMOS] = new /datum/log_controller(I_ATMOS, filename="data/logs/[date_string] atmos.htm", persist=TRUE)
|
||||
investigations[I_CHEMS] = new /datum/log_controller(I_CHEMS, filename="data/logs/[date_string] chemistry.htm", persist=TRUE)
|
||||
investigations[I_WIRES] = new /datum/log_controller(I_WIRES, filename="data/logs/[date_string] wires.htm", persist=TRUE)
|
||||
investigations[I_GHOST] = new /datum/log_controller(I_GHOST, filename="data/logs/[date_string] poltergeist.htm", persist=TRUE)
|
||||
investigations[I_ARTIFACT] = new /datum/log_controller(I_ARTIFACT, filename="data/logs/[date_string] artifact.htm", persist=TRUE)
|
||||
investigations[I_RCD] = new /datum/log_controller(I_RCD, filename="data/logs/[date_string] rcd.htm", persist=TRUE)
|
||||
|
||||
date_string = time2text(world.realtime, "YYYY/MM-Month/DD-Day")
|
||||
diary = file("data/logs/[date_string].log")
|
||||
panicfile = new/savefile("data/logs/profiling/proclogs/[date_string].sav")
|
||||
diaryofmeanpeople = file("data/logs/[date_string] Attack.log")
|
||||
admin_diary = file("data/logs/[date_string] admin only.log")
|
||||
|
||||
var/now = time_stamp()
|
||||
var/log_start = "---------------------\n\[[now]\]WORLD: starting up..."
|
||||
|
||||
diary << log_start
|
||||
diaryofmeanpeople << log_start
|
||||
admin_diary << log_start
|
||||
panicfile.cd = now
|
||||
|
||||
|
||||
/world/Topic(T, addr, master, key)
|
||||
TGS_TOPIC
|
||||
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
#Currently, this is only used by build.cmd and TGS quick setup
|
||||
|
||||
# byond version
|
||||
#export BYOND_MAJOR=514
|
||||
#export BYOND_MINOR=1566
|
||||
#export BYOND_MAJOR=516
|
||||
#export BYOND_MINOR=1659
|
||||
|
||||
#node version
|
||||
export NODE_VERSION=16
|
||||
export NODE_VERSION_PRECISE=16.13.1
|
||||
export NODE_VERSION_PRECISE=22.10
|
||||
|
||||
# SpacemanDMM git tag
|
||||
#export SPACEMAN_DMM_VERSION=suite-1.7.1
|
||||
|
||||
@@ -239,7 +239,8 @@ For the main html chat area
|
||||
iconCache[iconKey] << icon(icon, dir = SOUTH, frame = 1)
|
||||
var/iconData = iconCache.ExportText(iconKey)
|
||||
var/list/partial = splittext(iconData, "{")
|
||||
return replacetext(copytext(partial[2], 3, -5), "\n", "")
|
||||
var/list/partial2 = splittext(partial[2], "}")
|
||||
return replacetext(copytext(partial2[1], 2, -1), "\n", "")
|
||||
|
||||
/proc/bicon(var/obj)
|
||||
if (!obj)
|
||||
|
||||
@@ -39,5 +39,5 @@ table{
|
||||
/* "Center" it, since we can only crop out the top-left */
|
||||
margin: -16px -16px -16px -16px;
|
||||
/* Remove antialias to prevent cataracts */
|
||||
-ms-interpolation-mode: nearest-neighbor;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user