diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3ac1639098e..427aff55d4b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 3a28d0a346f..21a5f2b1c3f 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -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" } ] } diff --git a/__DEFINES/_macros.dm b/__DEFINES/_macros.dm index c492549e0c9..fad3dc9d8b6 100644 --- a/__DEFINES/_macros.dm +++ b/__DEFINES/_macros.dm @@ -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)) diff --git a/__DEFINES/cooldowns.dm b/__DEFINES/cooldowns.dm new file mode 100644 index 00000000000..2d678a60bdc --- /dev/null +++ b/__DEFINES/cooldowns.dm @@ -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)) diff --git a/__DEFINES/planes+layers.dm b/__DEFINES/planes+layers.dm index 4ccc6d6e89f..989a087b326 100644 --- a/__DEFINES/planes+layers.dm +++ b/__DEFINES/planes+layers.dm @@ -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() diff --git a/__DEFINES/qdel.dm b/__DEFINES/qdel.dm index 7c62cb74936..8d8cb49f7f1 100644 --- a/__DEFINES/qdel.dm +++ b/__DEFINES/qdel.dm @@ -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) diff --git a/__DEFINES/setup.dm b/__DEFINES/setup.dm index 6ea59399a38..81b432587be 100644 --- a/__DEFINES/setup.dm +++ b/__DEFINES/setup.dm @@ -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. diff --git a/__DEFINES/tgui.dm b/__DEFINES/tgui.dm index a5a5940243c..8bbe9578da4 100644 --- a/__DEFINES/tgui.dm +++ b/__DEFINES/tgui.dm @@ -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)) diff --git a/code/ATMOSPHERICS/components/binary_devices/MSGS.dm b/code/ATMOSPHERICS/components/binary_devices/MSGS.dm index 6acb34ed9e5..034837fee21 100644 --- a/code/ATMOSPHERICS/components/binary_devices/MSGS.dm +++ b/code/ATMOSPHERICS/components/binary_devices/MSGS.dm @@ -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 diff --git a/code/__HELPERS/cmp.dm b/code/__HELPERS/cmp.dm index 95940d35a9c..703ab5e0d7c 100644 --- a/code/__HELPERS/cmp.dm +++ b/code/__HELPERS/cmp.dm @@ -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) diff --git a/code/__HELPERS/datum.dm b/code/__HELPERS/datum.dm index 51b8f29d72d..27a5980942b 100644 --- a/code/__HELPERS/datum.dm +++ b/code/__HELPERS/datum.dm @@ -1,6 +1,7 @@ /datum var/list/datum_components var/list/active_timers + var/list/open_uis /datum/proc/initialize() return TRUE diff --git a/code/__HELPERS/files.dm b/code/__HELPERS/files.dm index 4e0076a64b5..409a76408f6 100644 --- a/code/__HELPERS/files.dm +++ b/code/__HELPERS/files.dm @@ -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)) diff --git a/code/__HELPERS/icons.dm b/code/__HELPERS/icons.dm index 64b6924fd5a..1e0f451baa8 100644 --- a/code/__HELPERS/icons.dm +++ b/code/__HELPERS/icons.dm @@ -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 \ No newline at end of file diff --git a/code/__HELPERS/math/ray/ray.dm b/code/__HELPERS/math/ray/ray.dm index ec77ee2fa24..7f9e07b689f 100644 --- a/code/__HELPERS/math/ray/ray.dm +++ b/code/__HELPERS/math/ray/ray.dm @@ -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) diff --git a/code/__HELPERS/math/ray/rayCastHit.dm b/code/__HELPERS/math/ray/rayCastHit.dm index cdc5f22a316..5a06d161a13 100644 --- a/code/__HELPERS/math/ray/rayCastHit.dm +++ b/code/__HELPERS/math/ray/rayCastHit.dm @@ -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 diff --git a/code/__HELPERS/math/ray/rayCastHitInfo.dm b/code/__HELPERS/math/ray/rayCastHitInfo.dm index ba44ac8a0c4..9fe0f976eb0 100644 --- a/code/__HELPERS/math/ray/rayCastHitInfo.dm +++ b/code/__HELPERS/math/ray/rayCastHitInfo.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 diff --git a/code/__HELPERS/math/ray/ray_test_procs.dm b/code/__HELPERS/math/ray/ray_test_procs.dm index f1c439afcf1..a4314ca32b9 100644 --- a/code/__HELPERS/math/ray/ray_test_procs.dm +++ b/code/__HELPERS/math/ray/ray_test_procs.dm @@ -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) diff --git a/code/__HELPERS/math/vector/vector.dm b/code/__HELPERS/math/vector/vector.dm index f3e0057eac9..c9eb2a07d49 100644 --- a/code/__HELPERS/math/vector/vector.dm +++ b/code/__HELPERS/math/vector/vector.dm @@ -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) diff --git a/code/__HELPERS/math/vector/vector_procs.dm b/code/__HELPERS/math/vector/vector_procs.dm index 24c3a49b7c3..458c8df9823 100644 --- a/code/__HELPERS/math/vector/vector_procs.dm +++ b/code/__HELPERS/math/vector/vector_procs.dm @@ -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 diff --git a/code/__HELPERS/sorts/TimSort.dm b/code/__HELPERS/sorts/TimSort.dm index 4aa51263656..cfa55f0dfa3 100644 --- a/code/__HELPERS/sorts/TimSort.dm +++ b/code/__HELPERS/sorts/TimSort.dm @@ -14,4 +14,4 @@ sortInstance.timSort(fromIndex, toIndex) - return L \ No newline at end of file + return L diff --git a/code/__HELPERS/text.dm b/code/__HELPERS/text.dm index 8ee9a32513c..e36b5c14634 100644 --- a/code/__HELPERS/text.dm +++ b/code/__HELPERS/text.dm @@ -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 diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index f392548e149..c58dfff03f3 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -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 {"
"} + /proc/getline(atom/M,atom/N)//Ultra-Fast Bresenham Line-Drawing Algorithm var/px=M.x //starting x var/py=M.y diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm index 9405e9849a9..83a0e3c937a 100644 --- a/code/_onclick/click.dm +++ b/code/_onclick/click.dm @@ -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 diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm index 5e19b78cc42..b4e2a654d32 100644 --- a/code/controllers/configuration.dm +++ b/code/controllers/configuration.dm @@ -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 diff --git a/code/controllers/subsystem/assets.dm b/code/controllers/subsystem/assets.dm new file mode 100644 index 00000000000..c196e149b4a --- /dev/null +++ b/code/controllers/subsystem/assets.dm @@ -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 diff --git a/code/controllers/subsystem/init/assets.dm b/code/controllers/subsystem/init/assets.dm deleted file mode 100644 index e1e7174b381..00000000000 --- a/code/controllers/subsystem/init/assets.dm +++ /dev/null @@ -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() - ..() diff --git a/code/controllers/subsystem/init/persistence_misc.dm b/code/controllers/subsystem/init/persistence_misc.dm index 5668770b573..ec90ad7fe5a 100644 --- a/code/controllers/subsystem/init/persistence_misc.dm +++ b/code/controllers/subsystem/init/persistence_misc.dm @@ -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 diff --git a/code/controllers/subsystem/tgui.dm b/code/controllers/subsystem/tgui.dm index 04c8e0c7c57..b7635837b75 100644 --- a/code/controllers/subsystem/tgui.dm +++ b/code/controllers/subsystem/tgui.dm @@ -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 = "" + basehtml = replacetextEx(basehtml, "", helpers) + + // Inject inline ntos-error styles + var/ntos_error = file2text('tgui/public/ntos-error.min.css') + ntos_error = "" + basehtml = replacetextEx(basehtml, "", ntos_error) + + basehtml = replacetextEx(basehtml, "", "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 diff --git a/code/controllers/subsystem/timer.dm b/code/controllers/subsystem/timer.dm index 5f0ba75939c..3fff31be29f 100644 --- a/code/controllers/subsystem/timer.dm +++ b/code/controllers/subsystem/timer.dm @@ -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 diff --git a/code/game/gamemodes/endgame/scoreboard/misc_score.dm b/code/game/gamemodes/endgame/scoreboard/misc_score.dm index b9c52480167..581d9ea6a9a 100644 --- a/code/game/gamemodes/endgame/scoreboard/misc_score.dm +++ b/code/game/gamemodes/endgame/scoreboard/misc_score.dm @@ -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 diff --git a/code/game/gamemodes/endgame/scoreboard/scoreboard.dm b/code/game/gamemodes/endgame/scoreboard/scoreboard.dm index 8acbc3fc2b7..624f2412047 100644 --- a/code/game/gamemodes/endgame/scoreboard/scoreboard.dm +++ b/code/game/gamemodes/endgame/scoreboard/scoreboard.dm @@ -312,27 +312,27 @@ var/global/datum/controller/gameticker/scoreboard/score = new() var/datum/persistence_task/highscores/leaderboard = score.money_leaderboard dat += "MONTHLY TOP 5 RICHEST ESCAPEES:
"
+ imgdat = "
"
dat += "


