diff --git a/.github/workflows/autodoc.yml b/.github/workflows/autodoc.yml index 47b9d25fd0793..81aaa928b7047 100644 --- a/.github/workflows/autodoc.yml +++ b/.github/workflows/autodoc.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: ref: master diff --git a/.github/workflows/compile_changelogs.yml b/.github/workflows/compile_changelogs.yml index 0609d5fbcad16..ae962610368cc 100644 --- a/.github/workflows/compile_changelogs.yml +++ b/.github/workflows/compile_changelogs.yml @@ -22,7 +22,7 @@ jobs: python -m pip install pyyaml sudo apt-get install dos2unix - name: "Checkout" - uses: actions/checkout@v1 + uses: actions/checkout@v4 with: fetch-depth: 25 - name: "Compile" diff --git a/.github/workflows/turdis.yml b/.github/workflows/turdis.yml index c405587c77b89..62b8517eab190 100644 --- a/.github/workflows/turdis.yml +++ b/.github/workflows/turdis.yml @@ -16,10 +16,10 @@ jobs: name: Lints runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Cache SpacemanDMM - uses: actions/cache@v1 + uses: actions/cache@v4 with: path: ~/SpacemanDMM key: ${{ runner.os }}-dreamchecker-${{ hashFiles('dependencies.sh')}} @@ -84,7 +84,7 @@ jobs: name: Compile All Maps runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Install Dependencies run: | @@ -93,7 +93,7 @@ jobs: sudo apt install libstdc++6:i386 - name: Restore Cache BYOND - uses: actions/cache@v1 + uses: actions/cache@v4 with: path: ~/BYOND key: ${{ runner.os }}-byond-${{ hashFiles('Dockerfile')}} @@ -118,7 +118,7 @@ jobs: outputs: maps: ${{ steps.map_finder.outputs.maps }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Find Maps id: map_finder run: | @@ -145,7 +145,7 @@ jobs: group: ci-${{ github.ref }}-${{ matrix.map }} cancel-in-progress: true steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Install Dependencies run: | @@ -154,20 +154,20 @@ jobs: sudo apt install libstdc++6:i386 gcc-multilib g++-7 g++-7-multilib zlib1g:i386 libssl1.1 libssl1.1:i386 - name: Restore Cache BYOND - uses: actions/cache@v1 + uses: actions/cache@v4 with: path: ~/BYOND key: ${{ runner.os }}-byond-${{ hashFiles('Dockerfile')}} restore-keys: ${{ runner.os }}-byond - name: Restore Cache Auxmos - uses: actions/cache@v1 + uses: actions/cache@v4 with: path: ~/.byond key: auxmos-${{ hashFiles('dependencies.sh')}} - name: Restore Yarn Cache - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: tgui/.yarn/cache key: ${{ runner.os }}-yarn-${{ hashFiles('Dockerfile')}} diff --git a/code/__DEFINES/MC.dm b/code/__DEFINES/MC.dm index ae6832e15aba9..e8dd84b3a366c 100644 --- a/code/__DEFINES/MC.dm +++ b/code/__DEFINES/MC.dm @@ -107,14 +107,6 @@ /datum/controller/subsystem/timer/##X/fire() {..() /*just so it shows up on the profiler*/} \ /datum/controller/subsystem/timer/##X -// #define MOVEMENT_SUBSYSTEM_DEF(X) GLOBAL_REAL(SS##X, /datum/controller/subsystem/movement/##X); -// /datum/controller/subsystem/movement/##X/New(){ -// NEW_SS_GLOBAL(SS##X); -// PreInit(); -// } -// /datum/controller/subsystem/movement/##X/fire() {..() /*just so it shows up on the profiler*/} -// /datum/controller/subsystem/movement/##X - #define PROCESSING_SUBSYSTEM_DEF(X) GLOBAL_REAL(SS##X, /datum/controller/subsystem/processing/##X);\ /datum/controller/subsystem/processing/##X/New(){\ NEW_SS_GLOBAL(SS##X);\ diff --git a/code/__DEFINES/_tick.dm b/code/__DEFINES/_tick.dm index abafc4465c380..142d904f31a5a 100644 --- a/code/__DEFINES/_tick.dm +++ b/code/__DEFINES/_tick.dm @@ -22,6 +22,11 @@ /// runs stoplag if tick_usage is above the limit #define CHECK_TICK ( TICK_CHECK ? stoplag() : 0 ) +/// Checks if a sleeping proc is running before or after the master controller +#define RUNNING_BEFORE_MASTER ( Master.last_run != null && Master.last_run != world.time ) +/// Returns true if a verb ought to yield to the MC (IE: queue up to be processed by a subsystem) +#define VERB_SHOULD_YIELD ( TICK_CHECK || RUNNING_BEFORE_MASTER ) + /// Returns true if tick usage is above 95, for high priority usage #define TICK_CHECK_HIGH_PRIORITY ( TICK_USAGE > 95 ) /// runs stoplag if tick_usage is above 95, for high priority usage diff --git a/code/__DEFINES/admin.dm b/code/__DEFINES/admin.dm index e7390d798c61a..904fcd8a0aca0 100644 --- a/code/__DEFINES/admin.dm +++ b/code/__DEFINES/admin.dm @@ -43,29 +43,29 @@ #define R_DEFAULT R_AUTOLOGIN -#define ADMIN_QUE(user) "(?)" -#define ADMIN_FLW(user) "(FLW)" -#define ADMIN_PP(user) "(PP)" -#define ADMIN_VV(atom) "(VV)" -#define ADMIN_SM(user) "(SM)" -#define ADMIN_TP(user) "(TP)" -#define ADMIN_KICK(user) "(KICK)" -#define ADMIN_CENTCOM_REPLY(user) "(RPLY)" -#define ADMIN_SYNDICATE_REPLY(user) "(RPLY)" -#define ADMIN_SC(user) "(SC)" -#define ADMIN_SMITE(user) "(SMITE)" +#define ADMIN_QUE(user) "(?)" +#define ADMIN_FLW(user) "(FLW)" +#define ADMIN_PP(user) "(PP)" +#define ADMIN_VV(atom) "(VV)" +#define ADMIN_SM(user) "(SM)" +#define ADMIN_TP(user) "(TP)" +#define ADMIN_KICK(user) "(KICK)" +#define ADMIN_CENTCOM_REPLY(user) "(RPLY)" +#define ADMIN_SYNDICATE_REPLY(user) "(RPLY)" +#define ADMIN_SC(user) "(SC)" +#define ADMIN_SMITE(user) "(SMITE)" #define ADMIN_LOOKUP(user) "[key_name_admin(user)][ADMIN_QUE(user)]" #define ADMIN_LOOKUPFLW(user) "[key_name_admin(user)][ADMIN_QUE(user)] [ADMIN_FLW(user)]" -#define ADMIN_SET_SD_CODE "(SETCODE)" -#define ADMIN_SET_BC_CODE "(SETBEER)" +#define ADMIN_SET_SD_CODE "(SETCODE)" +#define ADMIN_SET_BC_CODE "(SETBEER)" #define ADMIN_FULLMONTY_NONAME(user) "[ADMIN_QUE(user)] [ADMIN_PP(user)] [ADMIN_VV(user)] [ADMIN_SM(user)] [ADMIN_FLW(user)] [ADMIN_TP(user)] [ADMIN_INDIVIDUALLOG(user)] [ADMIN_SMITE(user)]" #define ADMIN_FULLMONTY(user) "[key_name_admin(user)] [ADMIN_FULLMONTY_NONAME(user)]" -#define ADMIN_JMP(src) "(JMP)" +#define ADMIN_JMP(src) "(JMP)" #define COORD(src) "[src ? "([src.x],[src.y],[src.z])" : "nonexistent location"]" #define AREACOORD(src) "[src ? "[get_area_name(src, TRUE)] ([src.x], [src.y], [src.z])" : "nonexistent location"]" #define ADMIN_COORDJMP(src) "[src ? "[COORD(src)] [ADMIN_JMP(src)]" : "nonexistent location"]" #define ADMIN_VERBOSEJMP(src) "[src ? "[AREACOORD(src)] [ADMIN_JMP(src)]" : "nonexistent location"]" -#define ADMIN_INDIVIDUALLOG(user) "(LOGS)" +#define ADMIN_INDIVIDUALLOG(user) "(LOGS)" #define ADMIN_PUNISHMENT_LIGHTNING "Lightning bolt" #define ADMIN_PUNISHMENT_BRAINDAMAGE "Brain damage" diff --git a/code/__DEFINES/chat.dm b/code/__DEFINES/chat.dm index e73ac4d08dc05..a3e1f5e46e0c2 100644 --- a/code/__DEFINES/chat.dm +++ b/code/__DEFINES/chat.dm @@ -25,12 +25,12 @@ #define MESSAGE_TYPE_MENTORPM "mentorpm" #define MESSAGE_TYPE_DONATOR "donator" +/// Max length of chat message in characters +#define CHAT_MESSAGE_MAX_LENGTH 110 + /// Adds a generic box around whatever message you're sending in chat. Really makes things stand out. #define EXAMINE_BLOCK(str) ("
" + str + "
") -/// Max length of chat message in characters -#define CHAT_MESSAGE_MAX_LENGTH 110 - //debug printing macros (for development and testing) /// Used for debug messages to the world #define debug_world(msg) if (GLOB.Debug2) to_chat(world, \ diff --git a/code/__DEFINES/fonts.dm b/code/__DEFINES/fonts.dm new file mode 100644 index 0000000000000..ba799a62c9c74 --- /dev/null +++ b/code/__DEFINES/fonts.dm @@ -0,0 +1,7 @@ +// Font metrics bitfield +/// Include leading A width and trailing C width in GetWidth() or in DrawText() +#define INCLUDE_AC (1<<0) + +DEFINE_BITFIELD(font_flags, list( + "INCLUDE_AC" = INCLUDE_AC, +)) diff --git a/code/__DEFINES/html_assistant.dm b/code/__DEFINES/html_assistant.dm index 03f95ad20158b..ed16afb481c1f 100644 --- a/code/__DEFINES/html_assistant.dm +++ b/code/__DEFINES/html_assistant.dm @@ -32,3 +32,10 @@ "[(GLOB.tooltips[config_key] ? "
[hover_me][GLOB.tooltips[config_key]]
" : "[hover_me]")]" #define OPEN_WIKI(wiki_url, text) (CONFIG_GET(string/wikiurl) ? "[text]" : "[text]") + + +#define HTML_SKELETON_INTERNAL(head, body) \ +"[head][body]" + +#define HTML_SKELETON_TITLE(title, body) HTML_SKELETON_INTERNAL("[title]", body) +#define HTML_SKELETON(body) HTML_SKELETON_INTERNAL("", body) diff --git a/code/__DEFINES/say.dm b/code/__DEFINES/say.dm index b5c2691b92277..efd51ec77d58c 100644 --- a/code/__DEFINES/say.dm +++ b/code/__DEFINES/say.dm @@ -90,9 +90,9 @@ #define EAVESDROP_EXTRA_RANGE 1 //how much past the specified message_range does the message get starred, whispering only // A link given to ghost alice to follow bob -#define FOLLOW_LINK(alice, bob) "(F)" -#define TURF_LINK(alice, turfy) "(T)" -#define FOLLOW_OR_TURF_LINK(alice, bob, turfy) "(F)" +#define FOLLOW_LINK(alice, bob) "(F)" +#define TURF_LINK(alice, turfy) "(T)" +#define FOLLOW_OR_TURF_LINK(alice, bob, turfy) "(F)" #define LINGHIVE_NONE 0 #define LINGHIVE_OUTSIDER 1 diff --git a/code/__DEFINES/text.dm b/code/__DEFINES/text.dm index dc17aca279b53..459557ee3e0b5 100644 --- a/code/__DEFINES/text.dm +++ b/code/__DEFINES/text.dm @@ -5,6 +5,21 @@ /// Prepares a text to be used for maptext. Use this so it doesn't look hideous. #define MAPTEXT(text) {"[##text]"} +/// Removes characters incompatible with file names. +#define SANITIZE_FILENAME(text) (GLOB.filename_forbidden_chars.Replace(text, "")) + +/// Simply removes the < and > characters, and limits the length of the message. +#define STRIP_HTML_SIMPLE(text, limit) (GLOB.angular_brackets.Replace(copytext(text, 1, limit), "")) + +/// Removes everything enclose in < and > inclusive of the bracket, and limits the length of the message. +#define STRIP_HTML_FULL(text, limit) (GLOB.html_tags.Replace(copytext(text, 1, limit), "")) + +/** + * stuff like `copytext(input, length(input))` will trim the last character of the input, + * because DM does it so it copies until the char BEFORE the `end` arg, so we need to bump `end` by 1 in these cases. +*/ +#define PREVENT_CHARACTER_TRIM_LOSS(integer) (integer + 1) + /** * Pixel-perfect scaled fonts for use in the MAP element as defined in skin.dmf * @@ -49,15 +64,6 @@ return_var = text2num(copytext(_measurement, findtextEx(_measurement, "x") + 1)); \ } while(FALSE); -/// Removes characters incompatible with file names. -#define SANITIZE_FILENAME(text) (GLOB.filename_forbidden_chars.Replace(text, "")) - -/// Simply removes the < and > characters, and limits the length of the message. -#define STRIP_HTML_SIMPLE(text, limit) (GLOB.angular_brackets.Replace(copytext(text, 1, limit), "")) - -/// Removes everything enclose in < and > inclusive of the bracket, and limits the length of the message. -#define STRIP_HTML_FULL(text, limit) (GLOB.html_tags.Replace(copytext(text, 1, limit), "")) - /* * Uses MAPTEXT to format antag points into a more appealing format */ diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index d6dde0b6981a5..dd181e86afa4b 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -1012,6 +1012,9 @@ /// this object has been frozen #define TRAIT_FROZEN "frozen" +/// Is runechat for this atom/movable currently disabled, regardless of prefs or anything? +#define TRAIT_RUNECHAT_HIDDEN "runechat_hidden" + /// Currently fishing #define TRAIT_GONE_FISHING "fishing" @@ -1024,9 +1027,6 @@ /// Makes a species be better/worse at defending against tackling depending on their tail's status #define TRAIT_TACKLING_TAILED_DEFENDER "tackling_tailed_defender" -/// Is runechat for this atom/movable currently disabled, regardless of prefs or anything? -#define TRAIT_RUNECHAT_HIDDEN "runechat_hidden" - /// the object has a label applied #define TRAIT_HAS_LABEL "labeled" diff --git a/code/__DEFINES/vv.dm b/code/__DEFINES/vv.dm index 10d0204ed7ef0..343102e3f591c 100644 --- a/code/__DEFINES/vv.dm +++ b/code/__DEFINES/vv.dm @@ -26,8 +26,8 @@ //#define IS_VALID_ASSOC_KEY(V) (istext(V) || ispath(V) || isdatum(V) || islist(V)) #define IS_VALID_ASSOC_KEY(V) (!isnum(V)) //hhmmm.. //General helpers -#define VV_HREF_TARGET_INTERNAL(target, href_key) "?_src_=vars;[HrefToken()];[href_key]=TRUE;[VV_HK_TARGET]=[REF(target)]" -#define VV_HREF_TARGETREF_INTERNAL(targetref, href_key) "?_src_=vars;[HrefToken()];[href_key]=TRUE;[VV_HK_TARGET]=[targetref]" +#define VV_HREF_TARGET_INTERNAL(target, href_key) "byond://?_src_=vars;[HrefToken()];[href_key]=TRUE;[VV_HK_TARGET]=[REF(target)]" +#define VV_HREF_TARGETREF_INTERNAL(targetref, href_key) "byond://?_src_=vars;[HrefToken()];[href_key]=TRUE;[VV_HK_TARGET]=[targetref]" #define VV_HREF_TARGET(target, href_key, text) "[text]" #define VV_HREF_TARGETREF(targetref, href_key, text) "[text]" #define VV_HREF_TARGET_1V(target, href_key, text, varname) "[text]" //for stuff like basic varedits, one variable @@ -35,7 +35,7 @@ #define GET_VV_TARGET locate(href_list[VV_HK_TARGET]) #define GET_VV_VAR_TARGET href_list[VV_HK_VARNAME] //Helper for getting something to vv_do_topic in general -#define VV_TOPIC_LINK(datum, href_key, text) "text" +#define VV_TOPIC_LINK(datum, href_key, text) "text" //Helpers for vv_get_dropdown() #define VV_DROPDOWN_OPTION(href_key, name) . += "" #define VV_DROPDOWN_SEPERATOR VV_DROPDOWN_OPTION("", "-----") diff --git a/code/__HELPERS/AStar.dm b/code/__HELPERS/AStar.dm index 822d5ebb6239f..3208e7160b0e5 100644 --- a/code/__HELPERS/AStar.dm +++ b/code/__HELPERS/AStar.dm @@ -74,33 +74,33 @@ Actual Adjacent procs : return b.f - a.f //wrapper that returns an empty list if A* failed to find a path -/proc/get_path_to(caller, end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableTurftest, id=null, turf/exclude=null, simulated_only = TRUE, get_best_attempt = FALSE) - var/l = SSpathfinder.mobs.getfree(caller) +/proc/get_path_to(caller_but_not_a_byond_built_in_proc, end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableTurftest, id=null, turf/exclude=null, simulated_only = TRUE, get_best_attempt = FALSE) + var/l = SSpathfinder.mobs.getfree(caller_but_not_a_byond_built_in_proc) while(!l) stoplag(3) - l = SSpathfinder.mobs.getfree(caller) - var/list/path = AStar(caller, end, dist, maxnodes, maxnodedepth, mintargetdist, adjacent,id, exclude, simulated_only, get_best_attempt) + l = SSpathfinder.mobs.getfree(caller_but_not_a_byond_built_in_proc) + var/list/path = AStar(caller_but_not_a_byond_built_in_proc, end, dist, maxnodes, maxnodedepth, mintargetdist, adjacent,id, exclude, simulated_only, get_best_attempt) SSpathfinder.mobs.found(l) if(!path) path = list() return path -/proc/cir_get_path_to(caller, end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableTurftest, id=null, turf/exclude=null, simulated_only = TRUE) - var/l = SSpathfinder.circuits.getfree(caller) +/proc/cir_get_path_to(caller_but_not_a_byond_built_in_proc, end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableTurftest, id=null, turf/exclude=null, simulated_only = TRUE) + var/l = SSpathfinder.circuits.getfree(caller_but_not_a_byond_built_in_proc) while(!l) stoplag(3) - l = SSpathfinder.circuits.getfree(caller) - var/list/path = AStar(caller, end, dist, maxnodes, maxnodedepth, mintargetdist, adjacent,id, exclude, simulated_only) + l = SSpathfinder.circuits.getfree(caller_but_not_a_byond_built_in_proc) + var/list/path = AStar(caller_but_not_a_byond_built_in_proc, end, dist, maxnodes, maxnodedepth, mintargetdist, adjacent,id, exclude, simulated_only) SSpathfinder.circuits.found(l) if(!path) path = list() return path /// Pathfinding for bots -/proc/AStar(caller, _end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableTurftest, id=null, turf/exclude=null, simulated_only = TRUE, get_best_attempt = FALSE) +/proc/AStar(caller_but_not_a_byond_built_in_proc, _end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableTurftest, id=null, turf/exclude=null, simulated_only = TRUE, get_best_attempt = FALSE) //sanitation var/turf/end = get_turf(_end) - var/turf/start = get_turf(caller) + var/turf/start = get_turf(caller_but_not_a_byond_built_in_proc) if(!start || !end) stack_trace("Invalid A* start or destination") return FALSE @@ -150,12 +150,12 @@ Actual Adjacent procs : //is already in open list, check if it's a better way from the current turf CN.bf &= 15^r //we have no closed, so just cut off exceed dir.00001111 ^ reverse_dir.We don't need to expand to checked turf. if((newg < CN.g) ) - if(call(cur.source,adjacent)(caller, T, id, simulated_only)) + if(call(cur.source,adjacent)(caller_but_not_a_byond_built_in_proc, T, id, simulated_only)) CN.setp(cur,newg,CN.h,cur.nt+1) open.ReSort(CN)//reorder the changed element in the list else //is not already in open list, so add it - if(call(cur.source,adjacent)(caller, T, id, simulated_only)) + if(call(cur.source,adjacent)(caller_but_not_a_byond_built_in_proc, T, id, simulated_only)) CN = new(T,cur,newg,call(T,dist)(end),cur.nt+1,15^r) open.Insert(CN) openc[T] = CN @@ -179,8 +179,8 @@ Actual Adjacent procs : //Returns adjacent turfs in cardinal directions that are reachable //simulated_only controls whether only simulated turfs are considered or not -/// Returns a list the src/caller can cross into -/turf/proc/reachableAdjacentTurfs(caller, ID, simulated_only) +/// Returns a list the src/caller_but_not_a_byond_built_in_proc can cross into +/turf/proc/reachableAdjacentTurfs(caller_but_not_a_byond_built_in_proc, ID, simulated_only) var/list/L = new() var/turf/T var/static/space_type_cache = typecacheof(/turf/open/space) @@ -189,12 +189,12 @@ Actual Adjacent procs : T = get_step(src,GLOB.cardinals[k]) if(!T || (simulated_only && space_type_cache[T.type])) continue - if(!T.density && !LinkBlockedWithAccess(T,caller, ID)) + if(!T.density && !LinkBlockedWithAccess(T,caller_but_not_a_byond_built_in_proc, ID)) L.Add(T) return L -/turf/proc/reachableTurftest(caller, turf/T, ID, simulated_only) - if(T && !T.density && !(simulated_only && SSpathfinder.space_type_cache[T.type]) && !LinkBlockedWithAccess(T,caller, ID)) +/turf/proc/reachableTurftest(caller_but_not_a_byond_built_in_proc, turf/T, ID, simulated_only) + if(T && !T.density && !(simulated_only && SSpathfinder.space_type_cache[T.type]) && !LinkBlockedWithAccess(T,caller_but_not_a_byond_built_in_proc, ID)) return TRUE /// Returns adjacent turfs in cardinal directions that are reachable via atmos @@ -202,7 +202,7 @@ Actual Adjacent procs : return atmos_adjacent_turfs /// Check if there is a door that needs access in its way -/turf/proc/LinkBlockedWithAccess(turf/T, caller, ID) +/turf/proc/LinkBlockedWithAccess(turf/T, caller_but_not_a_byond_built_in_proc, ID) var/adir = get_dir(src, T) var/rdir = ((adir & MASK_ODD)<<1)|((adir & MASK_EVEN)>>1) for(var/obj/structure/window/W in src) @@ -212,29 +212,29 @@ Actual Adjacent procs : if(!W.CanAStarPass(ID, adir)) return TRUE for(var/obj/machinery/M in src) - if(!M.CanAStarPass(ID, adir, caller)) + if(!M.CanAStarPass(ID, adir, caller_but_not_a_byond_built_in_proc)) return TRUE for(var/obj/machinery/door/firedoor/border_only/W in src) - if(!W.CanAStarPass(ID, adir, caller)) + if(!W.CanAStarPass(ID, adir, caller_but_not_a_byond_built_in_proc)) return TRUE for(var/obj/O in T) - if(!O.CanAStarPass(ID, rdir, caller)) + if(!O.CanAStarPass(ID, rdir, caller_but_not_a_byond_built_in_proc)) return TRUE return FALSE //yog procs -/turf/proc/reachableTurftestPlayer(caller, turf/T, ID, simulated_only) - if(T && !T.density && !LinkBlockedWithAccess(T, caller, ID) && !(simulated_only && SSpathfinder.space_type_cache[T.type])) +/turf/proc/reachableTurftestPlayer(caller_but_not_a_byond_built_in_proc, turf/T, ID, simulated_only) + if(T && !T.density && !LinkBlockedWithAccess(T, caller_but_not_a_byond_built_in_proc, ID) && !(simulated_only && SSpathfinder.space_type_cache[T.type])) return TRUE -/turf/proc/reachableTurftestdensity(caller, turf/T, ID, simulated_only) //used for the sake of pathfinding while excluding turfs with dense objects - if(T && !T.density && !(simulated_only && SSpathfinder.space_type_cache[T.type]) && !LinkBlockedWithAccess(T,caller, ID)) +/turf/proc/reachableTurftestdensity(caller_but_not_a_byond_built_in_proc, turf/T, ID, simulated_only) //used for the sake of pathfinding while excluding turfs with dense objects + if(T && !T.density && !(simulated_only && SSpathfinder.space_type_cache[T.type]) && !LinkBlockedWithAccess(T,caller_but_not_a_byond_built_in_proc, ID)) for(var/obj/D in T) if(!istype(D, /obj/structure/window) && D.density) //had to do it silly like this so rwindows didn't stop it outright return FALSE return TRUE -/turf/proc/wiringTurfTest(caller, turf/T, ID, simulated_only) +/turf/proc/wiringTurfTest(caller_but_not_a_byond_built_in_proc, turf/T, ID, simulated_only) if(T && !T.density && !istype(T.loc, /area/space)) return TRUE diff --git a/code/__HELPERS/logging/_logging.dm b/code/__HELPERS/logging/_logging.dm index 8924262be636a..1bc4de8e488b1 100644 --- a/code/__HELPERS/logging/_logging.dm +++ b/code/__HELPERS/logging/_logging.dm @@ -301,11 +301,11 @@ if(key) if(C && C.holder && C.holder.fakekey && !include_name) if(include_link) - . += "" + . += "" . += "Administrator" else if(include_link) - . += "" + . += "" . += key if(!C) . += "\[DC\]" diff --git a/code/__HELPERS/roundend.dm b/code/__HELPERS/roundend.dm index b968d7e33ff05..2891b17e7199f 100644 --- a/code/__HELPERS/roundend.dm +++ b/code/__HELPERS/roundend.dm @@ -354,7 +354,7 @@ if(GLOB.round_id) var/statspage = CONFIG_GET(string/roundstatsurl) - var/info = statspage ? "[GLOB.round_id]" : GLOB.round_id + var/info = statspage ? "[GLOB.round_id]" : GLOB.round_id parts += "[GLOB.TAB]Round ID: [info]" parts += "[GLOB.TAB]Shift Duration: [DisplayTimeText(world.time - SSticker.round_start_time)]" parts += "[GLOB.TAB]Station Integrity: [SSgamemode.station_was_nuked ? span_redtext("Destroyed") : "[popcount["station_integrity"]]%"]" @@ -610,7 +610,7 @@ var/datum/action/report/R = new C.player_details.player_actions += R R.Grant(C.mob) - to_chat(C,"Show roundend report again") + to_chat(C,"Show roundend report again") /datum/action/report name = "Show roundend report" diff --git a/code/__HELPERS/stat_tracking.dm b/code/__HELPERS/stat_tracking.dm index 097715b940474..2f55ea0455dfd 100644 --- a/code/__HELPERS/stat_tracking.dm +++ b/code/__HELPERS/stat_tracking.dm @@ -8,6 +8,6 @@ lines += "[entry] => [num2text(data[STAT_ENTRY_TIME], 10)]ms ([data[STAT_ENTRY_COUNT]]) (avg:[num2text(data[STAT_ENTRY_TIME]/(data[STAT_ENTRY_COUNT] || 1), 99)])" if (user) - user << browse("
  1. [lines.Join("
  2. ")]
", "window=[url_encode("stats:[REF(stats)]")]") + user << browse(HTML_SKELETON("
  1. [lines.Join("
  2. ")]
"), "window=[url_encode("stats:[REF(stats)]")]") . = lines.Join("\n") diff --git a/code/__HELPERS/verbs.dm b/code/__HELPERS/verbs.dm index 3606c7d918af9..7c8382fec834b 100644 --- a/code/__HELPERS/verbs.dm +++ b/code/__HELPERS/verbs.dm @@ -43,9 +43,8 @@ for(var/thing in verbs_list) var/procpath/verb_to_add = thing output_list[++output_list.len] = list(verb_to_add.category, verb_to_add.name) - output_list = url_encode(json_encode(output_list)) - target << output("[output_list];", "statbrowser:add_verb_list") + target.stat_panel.send_message("add_verb_list", output_list) /** * handles removing verb and sending it to browser to update, use this for removing verbs @@ -91,6 +90,5 @@ for(var/thing in verbs_list) var/procpath/verb_to_remove = thing output_list[++output_list.len] = list(verb_to_remove.category, verb_to_remove.name) - output_list = url_encode(json_encode(output_list)) - target << output("[output_list];", "statbrowser:remove_verb_list") + target.stat_panel.send_message("remove_verb_list", output_list) diff --git a/code/__byond_version_compat.dm b/code/__byond_version_compat.dm index 7e69aa34da46b..b9b36d93869fa 100644 --- a/code/__byond_version_compat.dm +++ b/code/__byond_version_compat.dm @@ -10,11 +10,11 @@ #endif //If you update these values, update the message in the #error -#define MAX_BYOND_MAJOR 515 -#define MAX_BYOND_MINOR 1647 +#define MAX_BYOND_MAJOR 516 +#define MAX_BYOND_MINOR 1659 #if ((DM_VERSION > MAX_BYOND_MAJOR) || (DM_BUILD > MAX_BYOND_MINOR)) && !defined(SPACEMAN_DMM) #error Your version of BYOND is too new to compile this project. -#error Download version 515.1647 at www.byond.com/download/build/515/515.1642_byond.exe +#error Download version 515.1659 at www.byond.com/download/build/515/515.1659_byond.exe #endif // 515 split call for external libraries into call_ext diff --git a/code/_globalvars/misc.dm b/code/_globalvars/misc.dm index b407cf78d19c5..ffd51d879015f 100644 --- a/code/_globalvars/misc.dm +++ b/code/_globalvars/misc.dm @@ -17,7 +17,7 @@ GLOBAL_LIST_EMPTY(powernets) GLOBAL_VAR_INIT(bsa_unlock, FALSE) //BSA unlocked by head ID swipes -GLOBAL_LIST_EMPTY(player_details) // ckey -> /datum/player_details +GLOBAL_LIST_EMPTY_TYPED(player_details, /datum/player_details) // ckey -> /datum/player_details GLOBAL_LIST_INIT(preview_backgrounds, list( "floor" = "Default Tile", diff --git a/code/_onclick/ai.dm b/code/_onclick/ai.dm index 35ea5b3742829..c93db6a54011a 100644 --- a/code/_onclick/ai.dm +++ b/code/_onclick/ai.dm @@ -222,9 +222,9 @@ to_chat(user, span_warning("Unable to track 'Unknown' persons! Their name must be visible.")) return if(src == user.cameraMemoryTarget) - to_chat(user, span_warning("Stop tracking this individual? \[UNTRACK\]")) + to_chat(user, span_warning("Stop tracking this individual? \[UNTRACK\]")) else - to_chat(user, span_warning("Track this individual? \[TRACK\]")) + to_chat(user, span_warning("Track this individual? \[TRACK\]")) return // diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm index b86586ecfbccb..a3714ea68c04f 100644 --- a/code/_onclick/click.dm +++ b/code/_onclick/click.dm @@ -364,14 +364,14 @@ var/turf/T = get_turf(src) if(T && user.TurfAdjacent(T)) user.listed_turf = T - user.client << output("[url_encode(json_encode(T.name))];", "statbrowser:create_listedturf") + user.client.stat_panel.send_message("create_listedturf", T.name) // Use this instead of /mob/proc/AltClickOn(atom/A) where you only want turf content listing without additional atom alt-click interaction /atom/proc/AltClickNoInteract(mob/user, atom/A) var/turf/T = get_turf(A) if(T && user.TurfAdjacent(T)) user.listed_turf = T - user.client << output("[url_encode(json_encode(T.name))];", "statbrowser:create_listedturf") + user.client.stat_panel.send_message("create_listedturf", T.name) /mob/proc/TurfAdjacent(turf/T) return T.Adjacent(src) diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm index 85099e5bdcd7e..ce9d3464aae19 100644 --- a/code/_onclick/hud/hud.dm +++ b/code/_onclick/hud/hud.dm @@ -107,6 +107,10 @@ GLOBAL_LIST_INIT(available_ui_styles, list( // and avoid needing to make changes to all idk 300 consumers if we want to change the appearance var/list/asset_refs_for_reuse = list() + /// The BYOND version of the client that was last logged into this mob. + /// Currently used to rebuild all plane master groups when going between 515<->516. + var/last_byond_version + /datum/hud/New(mob/owner) mymob = owner @@ -153,8 +157,20 @@ GLOBAL_LIST_INIT(available_ui_styles, list( /datum/hud/proc/client_refresh(datum/source) SIGNAL_HANDLER - RegisterSignal(mymob.canon_client, COMSIG_CLIENT_SET_EYE, PROC_REF(on_eye_change)) - on_eye_change(null, null, mymob.canon_client.eye) + var/client/client = mymob.canon_client + var/new_byond_version = client.byond_version +#if MIN_COMPILER_VERSION > 515 + #warn Fully change default relay_loc to "1,1", rather than changing it based on client version +#endif + if(!isnull(last_byond_version) && new_byond_version != last_byond_version) + var/new_relay_loc = (new_byond_version > 515) ? "1,1" : "CENTER" + for(var/group_key as anything in master_groups) + var/datum/plane_master_group/group = master_groups[group_key] + group.relay_loc = new_relay_loc + group.rebuild_hud() + last_byond_version = new_byond_version + RegisterSignal(client, COMSIG_CLIENT_SET_EYE, PROC_REF(on_eye_change)) + on_eye_change(null, null, client.eye) /datum/hud/proc/clear_client(datum/source) SIGNAL_HANDLER diff --git a/code/_onclick/hud/parallax.dm b/code/_onclick/hud/parallax.dm index c689726a2c454..ef95d619efb54 100755 --- a/code/_onclick/hud/parallax.dm +++ b/code/_onclick/hud/parallax.dm @@ -111,7 +111,7 @@ create_parallax(screen_mob) update_parallax(screen_mob) -// This sets which way the current shuttle is moving (returns true if the shuttle has stopped moving so the caller can append their animation) +// This sets which way the current shuttle is moving (returns true if the shuttle has stopped moving so the caller_but_not_a_byond_built_in_proc can append their animation) /datum/hud/proc/set_parallax_movedir(new_parallax_movedir = 0, skip_windups, mob/viewmob) . = FALSE var/mob/screenmob = viewmob || mymob diff --git a/code/_onclick/hud/rendering/plane_master_group.dm b/code/_onclick/hud/rendering/plane_master_group.dm index 1aa9cd28ace7c..247db0515ad1d 100644 --- a/code/_onclick/hud/rendering/plane_master_group.dm +++ b/code/_onclick/hud/rendering/plane_master_group.dm @@ -47,11 +47,19 @@ stack_trace("Hey brother, our key [key] is already in use by a plane master group on the passed in hud, belonging to [viewing_hud.mymob]. Ya fucked up, why are there dupes") return +#if MIN_COMPILER_VERSION > 516 + #warn Fully change default relay_loc to "1,1", rather than changing it based on client version +#endif + set_hud(viewing_hud) our_hud.master_groups[key] = src show_hud() transform_lower_turfs(our_hud, active_offset) + if(viewing_hud.mymob?.client?.byond_version > 515) + relay_loc = "1,1" + rebuild_plane_masters() + /// Well, refresh our group, mostly useful for plane specific updates /datum/plane_master_group/proc/refresh_hud() hide_hud() @@ -62,6 +70,7 @@ hide_hud() rebuild_plane_masters() show_hud() + our_hud.update_parallax_pref() transform_lower_turfs(our_hud, active_offset) /// Regenerate our plane masters, this is useful if we don't have a mob but still want to rebuild. Such in the case of changing the screen_loc of relays @@ -183,19 +192,16 @@ /// If you wanna try someday feel free, but I can't manage it /datum/plane_master_group/popup -/// This is janky as hell but since something changed with CENTER positioning after build 1614 we have to switch to the bandaid LEFT,TOP positioning -/// using LEFT,TOP *at* or *before* 1614 will result in another broken offset for cameras -#define MAX_CLIENT_BUILD_WITH_WORKING_SECONDARY_MAPS 1614 - +/// Note do not use return ..() because it will cause client crush when screen gets deleted +/// TOOD: Remove this entirely when 516 is stable /datum/plane_master_group/popup/attach_to(datum/hud/viewing_hud) - // If we're about to display this group to a mob who's client is more recent than the last known version with working CENTER, then we need to remake the relays - // with the correct screen_loc using the relay override - if(viewing_hud.mymob?.client?.byond_build > MAX_CLIENT_BUILD_WITH_WORKING_SECONDARY_MAPS) - relay_loc = "LEFT,TOP" - rebuild_plane_masters() - return ..() - -#undef MAX_CLIENT_BUILD_WITH_WORKING_SECONDARY_MAPS + if(viewing_hud.master_groups[key]) + stack_trace("[key] is already in use by a plane master group on the passed in hud, belonging to [viewing_hud.mymob]!") + return + relay_loc = "1,1" + rebuild_plane_masters() + set_hud(viewing_hud) + show_hud() /datum/plane_master_group/popup/transform_lower_turfs(datum/hud/source, new_offset, use_scale = TRUE) return ..(source, new_offset, FALSE) diff --git a/code/_onclick/hud/rendering/render_plate.dm b/code/_onclick/hud/rendering/render_plate.dm index 8cafa5d2599f0..c7cc41040c7dd 100644 --- a/code/_onclick/hud/rendering/render_plate.dm +++ b/code/_onclick/hud/rendering/render_plate.dm @@ -382,7 +382,7 @@ render_relay_planes = list(RENDER_PLANE_MASTER) /** - * Plane master proc called in Initialize() that creates relay objects, and sets them uo as needed + * Plane master proc called in Initialize() that creates relay objects, and sets them up as needed * Sets: * * layer from plane to avoid z-fighting * * planes to relay the render to @@ -392,6 +392,9 @@ * Other vars such as alpha will automatically be applied with the render source */ /atom/movable/screen/plane_master/proc/generate_render_relays() +#if MIN_COMPILER_VERSION > 516 + #warn Fully change default relay_loc to "1,1" +#endif var/relay_loc = home?.relay_loc || "CENTER" // If we're using a submap (say for a popup window) make sure we draw onto it if(home?.map) @@ -426,7 +429,7 @@ if(!length(relays) && !initial(render_target)) render_target = OFFSET_RENDER_TARGET(get_plane_master_render_base(name), offset) if(!relay_loc) - relay_loc = "CENTER" + relay_loc = (show_to?.byond_version > 515) ? "1,1" : "CENTER" // If we're using a submap (say for a popup window) make sure we draw onto it if(home?.map) relay_loc = "[home.map]:[relay_loc]" diff --git a/code/controllers/subsystem/blackbox.dm b/code/controllers/subsystem/blackbox.dm index 68fade5867eaa..aabbc6ac7e9de 100644 --- a/code/controllers/subsystem/blackbox.dm +++ b/code/controllers/subsystem/blackbox.dm @@ -93,7 +93,7 @@ SUBSYSTEM_DEF(blackbox) for(var/player_key in GLOB.player_details) var/datum/player_details/PD = GLOB.player_details[player_key] - record_feedback("tally", "client_byond_version", 1, PD.byond_version) + record_feedback("tally", "client_byond_version", 1, PD.full_byond_version()) /datum/controller/subsystem/blackbox/Shutdown() sealed = FALSE diff --git a/code/controllers/subsystem/dbcore.dm b/code/controllers/subsystem/dbcore.dm index 34152e98ee59a..f1b53498c000b 100644 --- a/code/controllers/subsystem/dbcore.dm +++ b/code/controllers/subsystem/dbcore.dm @@ -427,7 +427,7 @@ Delayed insert mode was removed in mysql 7 and only works with MyISAM type table return FALSE /datum/DBQuery/proc/slow_query_check() - message_admins("HEY! A database query timed out. Did the server just hang? \[YES\]|\[NO\]") + message_admins("HEY! A database query timed out. Did the server just hang? \[YES\]|\[NO\]") /datum/DBQuery/proc/NextRow(async = TRUE) Activity("NextRow") diff --git a/code/controllers/subsystem/lag_switch.dm b/code/controllers/subsystem/lag_switch.dm index 69b740b8dabf6..e4ab27efbf0eb 100644 --- a/code/controllers/subsystem/lag_switch.dm +++ b/code/controllers/subsystem/lag_switch.dm @@ -34,7 +34,7 @@ SUBSYSTEM_DEF(lag_switch) auto_switch = FALSE UnregisterSignal(SSdcs, COMSIG_GLOB_CLIENT_CONNECT) veto_timer_id = addtimer(CALLBACK(src, PROC_REF(set_all_measures), TRUE, TRUE), 20 SECONDS, TIMER_STOPPABLE) - message_admins("Lag Switch population threshold reached. Automatic activation of lag mitigation measures occuring in 20 seconds. (CANCEL)") + message_admins("Lag Switch population threshold reached. Automatic activation of lag mitigation measures occuring in 20 seconds. (CANCEL)") log_admin("Lag Switch population threshold reached. Automatic activation of lag mitigation measures occuring in 20 seconds.") /// (En/Dis)able automatic triggering of switches based on client count diff --git a/code/controllers/subsystem/runechat.dm b/code/controllers/subsystem/runechat.dm index a5193a508ad63..663bb8cf347d7 100644 --- a/code/controllers/subsystem/runechat.dm +++ b/code/controllers/subsystem/runechat.dm @@ -1,243 +1,14 @@ -/// Controls how many buckets should be kept, each representing a tick. (30 seconds worth) -#define BUCKET_LEN (world.fps * 1 * 30) -/// Helper for getting the correct bucket for a given chatmessage -#define BUCKET_POS(scheduled_destruction) (((round((scheduled_destruction - SSrunechat.head_offset) / world.tick_lag) + 1) % BUCKET_LEN) || BUCKET_LEN) -/// Gets the maximum time at which messages will be handled in buckets, used for deferring to secondary queue -#define BUCKET_LIMIT (world.time + TICKS2DS(min(BUCKET_LEN - (SSrunechat.practical_offset - DS2TICKS(world.time - SSrunechat.head_offset)) - 1, BUCKET_LEN - 1))) - -/** - * # Runechat Subsystem - * - * Maintains a timer-like system to handle destruction of runechat messages. Much of this code is modeled - * after or adapted from the timer subsystem. - * - * Note that this has the same structure for storing and queueing messages as the timer subsystem does - * for handling timers: the bucket_list is a list of chatmessage datums, each of which are the head - * of a circularly linked list. Any given index in bucket_list could be null, representing an empty bucket. - */ -SUBSYSTEM_DEF(runechat) +TIMER_SUBSYSTEM_DEF(runechat) name = "Runechat" - flags = SS_TICKER | SS_NO_INIT - wait = 1 priority = FIRE_PRIORITY_RUNECHAT - /// world.time of the first entry in the bucket list, effectively the 'start time' of the current buckets - var/head_offset = 0 - /// Index of the first non-empty bucket - var/practical_offset = 1 - /// world.tick_lag the bucket was designed for - var/bucket_resolution = 0 - /// How many messages are in the buckets - var/bucket_count = 0 - /// List of buckets, each bucket holds every message that has to be killed that byond tick - var/list/bucket_list = list() - /// Queue used for storing messages that are scheduled for deletion too far in the future for the buckets - var/list/datum/chatmessage/second_queue = list() - -/datum/controller/subsystem/runechat/PreInit() - bucket_list.len = BUCKET_LEN - head_offset = world.time - bucket_resolution = world.tick_lag - -/datum/controller/subsystem/runechat/stat_entry(msg) - msg += "ActMsgs:[bucket_count] SecQueue:[length(second_queue)]" - return msg - -/datum/controller/subsystem/runechat/get_metrics() - . = ..() - .["buckets"] = bucket_count - .["second_queue"] = length(second_queue) - -/datum/controller/subsystem/runechat/fire(resumed = FALSE) - // Store local references to datum vars as it is faster to access them this way - var/list/bucket_list = src.bucket_list - - if (MC_TICK_CHECK) - return - - // Check for when we need to loop the buckets, this occurs when - // the head_offset is approaching BUCKET_LEN ticks in the past - if (practical_offset > BUCKET_LEN) - head_offset += TICKS2DS(BUCKET_LEN) - practical_offset = 1 - resumed = FALSE - - // Check for when we have to reset buckets, typically from auto-reset - if ((length(bucket_list) != BUCKET_LEN) || (world.tick_lag != bucket_resolution)) - reset_buckets() - bucket_list = src.bucket_list - resumed = FALSE - - // Store a reference to the 'working' chatmessage so that we can resume if the MC - // has us stop mid-way through processing - var/static/datum/chatmessage/cm - if (!resumed) - cm = null - - // Iterate through each bucket starting from the practical offset - while (practical_offset <= BUCKET_LEN && head_offset + ((practical_offset - 1) * world.tick_lag) <= world.time) - var/datum/chatmessage/bucket_head = bucket_list[practical_offset] - if (!cm || !bucket_head || cm == bucket_head) - bucket_head = bucket_list[practical_offset] - cm = bucket_head - - while (cm) - // If the chatmessage hasn't yet had its life ended then do that now - var/datum/chatmessage/next = cm.next - if (!cm.eol_complete) - cm.end_of_life() - else if (!QDELETED(cm)) // otherwise if we haven't deleted it yet, do so (this is after EOL completion) - qdel(cm) - - if (MC_TICK_CHECK) - return - - // Break once we've processed the entire bucket - cm = next - if (cm == bucket_head) - break - - // Empty the bucket, check if anything in the secondary queue should be shifted to this bucket - bucket_list[practical_offset++] = null - var/i = 0 - for (i in 1 to length(second_queue)) - cm = second_queue[i] - if (cm.scheduled_destruction >= BUCKET_LIMIT) - i-- - break - - // Transfer the message into the bucket, performing necessary circular doubly-linked list operations - bucket_count++ - var/bucket_pos = max(1, BUCKET_POS(cm.scheduled_destruction)) - var/datum/timedevent/head = bucket_list[bucket_pos] - if (!head) - bucket_list[bucket_pos] = cm - cm.next = null - cm.prev = null - continue - - if (!head.prev) - head.prev = head - cm.next = head - cm.prev = head.prev - cm.next.prev = cm - cm.prev.next = cm - if (i) - second_queue.Cut(1, i + 1) - cm = null - -/datum/controller/subsystem/runechat/Recover() - bucket_list |= SSrunechat.bucket_list - second_queue |= SSrunechat.second_queue - -/datum/controller/subsystem/runechat/proc/reset_buckets() - bucket_list.len = BUCKET_LEN - head_offset = world.time - bucket_resolution = world.tick_lag - -/** - * Enters the runechat subsystem with this chatmessage, inserting it into the end-of-life queue - * - * This will also account for a chatmessage already being registered, and in which case - * the position will be updated to remove it from the previous location if necessary - * - * Arguments: - * * new_sched_destruction Optional, when provided is used to update an existing message with the new specified time - */ -/datum/chatmessage/proc/enter_subsystem(new_sched_destruction = 0) - // Get local references from subsystem as they are faster to access than the datum references - var/list/bucket_list = SSrunechat.bucket_list - var/list/second_queue = SSrunechat.second_queue - - // When necessary, de-list the chatmessage from its previous position - if (new_sched_destruction) - if (scheduled_destruction >= BUCKET_LIMIT) - second_queue -= src - else - SSrunechat.bucket_count-- - var/bucket_pos = BUCKET_POS(scheduled_destruction) - if (bucket_pos > 0) - var/datum/chatmessage/bucket_head = bucket_list[bucket_pos] - if (bucket_head == src) - bucket_list[bucket_pos] = next - if (prev != next) - prev.next = next - next.prev = prev - else - prev?.next = null - next?.prev = null - prev = next = null - scheduled_destruction = new_sched_destruction - - // Ensure the scheduled destruction time is properly bound to avoid missing a scheduled event - scheduled_destruction = max(CEILING(scheduled_destruction, world.tick_lag), world.time + world.tick_lag) - - // Handle insertion into the secondary queue if the required time is outside our tracked amounts - if (scheduled_destruction >= BUCKET_LIMIT) - BINARY_INSERT(src, SSrunechat.second_queue, /datum/chatmessage, src, scheduled_destruction, COMPARE_KEY) - return - - // Get bucket position and a local reference to the datum var, it's faster to access this way - var/bucket_pos = BUCKET_POS(scheduled_destruction) - - // Get the bucket head for that bucket, increment the bucket count - var/datum/chatmessage/bucket_head = bucket_list[bucket_pos] - SSrunechat.bucket_count++ - - // If there is no existing head of this bucket, we can set this message to be that head - if (!bucket_head) - bucket_list[bucket_pos] = src - return - - // Otherwise it's a simple insertion into the circularly doubly-linked list - if (!bucket_head.prev) - bucket_head.prev = bucket_head - next = bucket_head - prev = bucket_head.prev - next.prev = src - prev.next = src - - -/** - * Removes this chatmessage datum from the runechat subsystem - */ -/datum/chatmessage/proc/leave_subsystem() - // Attempt to find the bucket that contains this chat message - var/bucket_pos = BUCKET_POS(scheduled_destruction) - - // Get local references to the subsystem's vars, faster than accessing on the datum - var/list/bucket_list = SSrunechat.bucket_list - var/list/second_queue = SSrunechat.second_queue - - // Attempt to get the head of the bucket - var/datum/chatmessage/bucket_head - if (bucket_pos > 0) - bucket_head = bucket_list[bucket_pos] - - // Decrement the number of messages in buckets if the message is - // the head of the bucket, or has a SD less than BUCKET_LIMIT implying it fits - // into an existing bucket, or is otherwise not present in the secondary queue - if(bucket_head == src) - bucket_list[bucket_pos] = next - SSrunechat.bucket_count-- - else if(scheduled_destruction < BUCKET_LIMIT) - SSrunechat.bucket_count-- - else - var/l = length(second_queue) - second_queue -= src - if(l == length(second_queue)) - SSrunechat.bucket_count-- - - // Remove the message from the bucket, ensuring to maintain - // the integrity of the bucket's list if relevant - if(prev != next) - prev.next = next - next.prev = prev - else - prev?.next = null - next?.prev = null - prev = next = null + var/list/datum/callback/message_queue = list() -#undef BUCKET_LEN -#undef BUCKET_POS -#undef BUCKET_LIMIT +/datum/controller/subsystem/timer/runechat/fire(resumed) + . = ..() //poggers + while(message_queue.len) + var/datum/callback/queued_message = message_queue[message_queue.len] + queued_message.Invoke() + message_queue.len-- + if(MC_TICK_CHECK) + return diff --git a/code/controllers/subsystem/shuttle.dm b/code/controllers/subsystem/shuttle.dm index 8a9c1fbbb0d08..7f3e7c427032b 100644 --- a/code/controllers/subsystem/shuttle.dm +++ b/code/controllers/subsystem/shuttle.dm @@ -373,7 +373,7 @@ SUBSYSTEM_DEF(shuttle) if(call_reason) SSblackbox.record_feedback("text", "shuttle_reason", 1, "[call_reason]") log_game("Shuttle call reason: [call_reason]") - message_admins("[ADMIN_LOOKUPFLW(user)] has called the shuttle. (TRIGGER CENTCOM RECALL)") + message_admins("[ADMIN_LOOKUPFLW(user)] has called the shuttle. (TRIGGER CENTCOM RECALL)") /datum/controller/subsystem/shuttle/proc/centcom_recall(old_timer, admiral_message) if(emergency.mode != SHUTTLE_CALL || emergency.timer != old_timer) diff --git a/code/controllers/subsystem/statpanel.dm b/code/controllers/subsystem/statpanel.dm index de3b751f1f15e..3ffe7d8558f1c 100644 --- a/code/controllers/subsystem/statpanel.dm +++ b/code/controllers/subsystem/statpanel.dm @@ -6,16 +6,18 @@ SUBSYSTEM_DEF(statpanels) runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY flags = SS_NO_INIT var/list/currentrun = list() - var/encoded_global_data - var/mc_data_encoded - var/list/cached_images = list() + var/list/global_data + var/list/mc_data + var/list/cached_images = list() ///how many subsystem fires between most tab updates var/default_wait = 10 ///how many subsystem fires between updates of the status tab var/status_wait = 2 ///how many subsystem fires between updates of the MC tab var/mc_wait = 5 + /// how many subsystem fires between updates of the turf examine tab + var/turf_wait = 2 ///how many full runs this subsystem has completed. used for variable rate refreshes. var/num_fires = 0 @@ -23,7 +25,6 @@ SUBSYSTEM_DEF(statpanels) if (!resumed) num_fires++ var/datum/map_config/cached = SSmapping.next_map_config - var/round_time = world.time - SSticker.round_start_time var/storyteller = "Not Set" if(SSgamemode.storyteller) @@ -35,13 +36,13 @@ SUBSYSTEM_DEF(statpanels) if(SSgamemode.secret_storyteller) storyteller = "Secret" - var/list/global_data = list( + global_data = list( "Map: [SSmapping.config?.map_name || "Loading..."]", cached ? "Next Map: [cached.map_name]" : null, "Storyteller: [storyteller]", "Round ID: [GLOB.round_id ? GLOB.round_id : "NULL"]", "Server Time: [time2text(world.timeofday, "YYYY-MM-DD hh:mm:ss")]", - "Round Time: [round_time > MIDNIGHT_ROLLOVER ? "[round(round_time/MIDNIGHT_ROLLOVER)]:[worldtime2text()]" : worldtime2text()]", + "Round Time: [ROUND_TIME()]", "Station Time: [station_time_timestamp()]", "Time Dilation: [round(SStime_track.time_dilation_current,1)]% AVG:([round(SStime_track.time_dilation_avg_fast,1)]%, [round(SStime_track.time_dilation_avg,1)]%, [round(SStime_track.time_dilation_avg_slow,1)]%)" ) @@ -50,38 +51,43 @@ SUBSYSTEM_DEF(statpanels) var/ETA = SSshuttle.emergency.getModeStr() if(ETA) global_data += "[ETA] [SSshuttle.emergency.getTimerStr()]" - encoded_global_data = url_encode(json_encode(global_data)) + src.currentrun = GLOB.clients.Copy() - mc_data_encoded = null + mc_data = null + var/list/currentrun = src.currentrun while(length(currentrun)) var/client/target = currentrun[length(currentrun)] currentrun.len-- - if(!target) - continue - if(!target.statbrowser_ready) + + if(!target.stat_panel.is_ready()) continue + if(target.stat_tab == "Status" && num_fires % status_wait == 0) set_status_tab(target) + if(!target.holder) - target << output("", "statbrowser:remove_admin_tabs") + target.stat_panel.send_message("remove_admin_tabs") else - target << output("[!!(target.prefs.extra_toggles & SPLIT_ADMIN_TABS)]", "statbrowser:update_split_admin_tabs") + target.stat_panel.send_message("update_split_admin_tabs", !!(target.prefs.extra_toggles & SPLIT_ADMIN_TABS)) + if(!("MC" in target.panel_tabs)) - target << output("[url_encode(target.holder.href_token)]", "statbrowser:add_admin_tabs") - - //if(target.stat_tab == "MC" && (num_fires % mc_wait == 0)) - if(target.stat_tab == "MC" && ((num_fires % mc_wait == 0) || (target?.prefs.extra_toggles & FAST_MC_REFRESH))) + target.stat_panel.send_message("add_admin_tabs", target.holder.href_token) + + if(target.stat_tab == "MC" && ((num_fires % mc_wait == 0))) set_MC_tab(target) if(!length(GLOB.sdql2_queries) && ("SDQL2" in target.panel_tabs)) - target << output("", "statbrowser:remove_sdql2") + target.stat_panel.send_message("remove_sdql2") + else if(length(GLOB.sdql2_queries) && (target.stat_tab == "SDQL2" || !("SDQL2" in target.panel_tabs)) && num_fires % default_wait == 0) set_SDQL2_tab(target) + if(target.mob) var/mob/target_mob = target.mob // Handle the action panels of the stat panel + var/update_actions = FALSE // We're on a spell tab, update the tab so we can see cooldowns progressing and such if(target.stat_tab in target.spell_tabs) @@ -95,30 +101,35 @@ SUBSYSTEM_DEF(statpanels) set_action_tabs(target, target_mob) // Handle the examined turf of the stat panel - - if(target_mob?.listed_turf && num_fires % default_wait == 0) - if(!target_mob.TurfAdjacent(target_mob.listed_turf)) - target << output("", "statbrowser:remove_listedturf") + if(target_mob?.listed_turf && num_fires % turf_wait == 0) + if(!target_mob.TurfAdjacent(target_mob.listed_turf) || isnull(target_mob.listed_turf)) + target.stat_panel.send_message("remove_listedturf") target_mob.listed_turf = null else if(target.stat_tab == target_mob?.listed_turf.name || !(target_mob?.listed_turf.name in target.panel_tabs)) set_turf_examine_tab(target, target_mob) + if(MC_TICK_CHECK) return /datum/controller/subsystem/statpanels/proc/set_status_tab(client/target) - if(!encoded_global_data)//statbrowser hasnt fired yet and we were called from immediate_send_stat_data() +#if MIN_COMPILER_VERSION > 515 + #warn 516 is most certainly out of beta, remove this beta notice if you haven't already +#endif + var/static/list/beta_notice = list("", "You are on the BYOND 516 beta, various UIs and such may be broken!", "Please report issues, and switch back to BYOND 515 if things are causing too many issues for you.") + if(!global_data)//statbrowser hasnt fired yet and we were called from immediate_send_stat_data() return - - var/ping_str = url_encode("Ping: [round(target.lastping, 1)]ms (Average: [round(target.avgping, 1)]ms)") - var/other_str = url_encode(json_encode(target.mob?.get_status_tab_items())) - target << output("[encoded_global_data];[ping_str];[other_str]", "statbrowser:update") + target.stat_panel.send_message("update_stat", list( + "global_data" = (target.byond_version < 516) ? global_data : (global_data + beta_notice), + "ping_str" = "Ping: [round(target.lastping, 1)]ms (Average: [round(target.avgping, 1)]ms)", + "other_str" = target.mob?.get_status_tab_items(), + )) /datum/controller/subsystem/statpanels/proc/set_MC_tab(client/target) var/turf/eye_turf = get_turf(target.eye) - var/coord_entry = url_encode(COORD(eye_turf)) - if(!mc_data_encoded) + var/coord_entry = COORD(eye_turf) + if(!mc_data) generate_mc_data() - target << output("[mc_data_encoded];[coord_entry]", "statbrowser:update_mc") + target.stat_panel.send_message("update_mc", list("mc_data" = mc_data, "coord_entry" = coord_entry)) /datum/controller/subsystem/statpanels/proc/set_SDQL2_tab(client/target) var/list/sdql2A = list() @@ -128,7 +139,7 @@ SUBSYSTEM_DEF(statpanels) sdql2B = query.generate_stat() sdql2A += sdql2B - target << output(url_encode(json_encode(sdql2A)), "statbrowser:update_sdql2") + target.stat_panel.send_message("update_sdql2", sdql2A) /// Set up the various action tabs. /datum/controller/subsystem/statpanels/proc/set_action_tabs(client/target, mob/target_mob) @@ -138,7 +149,7 @@ SUBSYSTEM_DEF(statpanels) for(var/action_data in actions) target.spell_tabs |= action_data[1] - target << output("[url_encode(json_encode(target.spell_tabs))];[url_encode(json_encode(actions))]", "statbrowser:update_spells") + target.stat_panel.send_message("update_spells", list(spell_tabs = target.spell_tabs, actions = actions)) /datum/controller/subsystem/statpanels/proc/set_turf_examine_tab(client/target, mob/target_mob) var/list/overrides = list() @@ -175,29 +186,27 @@ SUBSYSTEM_DEF(statpanels) else turfitems[++turfitems.len] = list("[turf_content.name]", REF(turf_content)) - turfitems = url_encode(json_encode(turfitems)) - target << output("[turfitems];", "statbrowser:update_listedturf") + target.stat_panel.send_message("update_listedturf", turfitems) /datum/controller/subsystem/statpanels/proc/generate_mc_data() - var/list/mc_data = list( + mc_data = list( list("CPU:", world.cpu), list("Instances:", "[num2text(world.contents.len, 10)]"), list("World Time:", "[world.time]"), - list("Globals:", GLOB.stat_entry(), "\ref[GLOB]"), - list("[config]:", config.stat_entry(), "\ref[config]"), + list("Globals:", GLOB.stat_entry(), text_ref(GLOB)), + list("[config]:", config.stat_entry(), text_ref(config)), list("Byond:", "(FPS:[world.fps]) (TickCount:[world.time/world.tick_lag]) (TickDrift:[round(Master.tickdrift,1)]([round((Master.tickdrift/(world.time/world.tick_lag))*100,0.1)]%)) (Internal Tick Usage: [round(MAPTICK_LAST_INTERNAL_TICK_USAGE,0.1)]%)"), - list("Master Controller:", Master.stat_entry(), "\ref[Master]"), - list("Failsafe Controller:", Failsafe.stat_entry(), "\ref[Failsafe]"), + list("Master Controller:", Master.stat_entry(), text_ref(Master)), + list("Failsafe Controller:", Failsafe.stat_entry(), text_ref(Failsafe)), list("","") ) for(var/datum/controller/subsystem/sub_system as anything in Master.subsystems) - mc_data[++mc_data.len] = list("\[[sub_system.state_letter()]][sub_system.name]", sub_system.stat_entry(), "\ref[sub_system]") - mc_data[++mc_data.len] = list("Camera Net", "Cameras: [GLOB.cameranet.cameras.len] | Chunks: [GLOB.cameranet.chunks.len]", "\ref[GLOB.cameranet]") - mc_data_encoded = url_encode(json_encode(mc_data)) + mc_data[++mc_data.len] = list("\[[sub_system.state_letter()]][sub_system.name]", sub_system.stat_entry(), text_ref(sub_system)) + mc_data[++mc_data.len] = list("Camera Net", "Cameras: [GLOB.cameranet.cameras.len] | Chunks: [GLOB.cameranet.chunks.len]", text_ref(GLOB.cameranet)) ///immediately update the active statpanel tab of the target client /datum/controller/subsystem/statpanels/proc/immediate_send_stat_data(client/target) - if(!target.statbrowser_ready) + if(!target.stat_panel.is_ready()) return FALSE if(target.stat_tab == "Status") @@ -205,7 +214,7 @@ SUBSYSTEM_DEF(statpanels) return TRUE var/mob/target_mob = target.mob - + // Handle actions var/update_actions = FALSE @@ -220,10 +229,9 @@ SUBSYSTEM_DEF(statpanels) return TRUE // Handle turfs - if(target_mob?.listed_turf) if(!target_mob.TurfAdjacent(target_mob.listed_turf)) - target << output("", "statbrowser:remove_listedturf") + target.stat_panel.send_message("removed_listedturf") target_mob.listed_turf = null else if(target.stat_tab == target_mob?.listed_turf.name || !(target_mob?.listed_turf.name in target.panel_tabs)) @@ -238,7 +246,7 @@ SUBSYSTEM_DEF(statpanels) return TRUE if(!length(GLOB.sdql2_queries) && ("SDQL2" in target.panel_tabs)) - target << output("", "statbrowser:remove_sdql2") + target.stat_panel.send_message("remove_sdql2") else if(length(GLOB.sdql2_queries) && target.stat_tab == "SDQL2") set_SDQL2_tab(target) @@ -248,42 +256,3 @@ SUBSYSTEM_DEF(statpanels) /atom/proc/remove_from_cache() SSstatpanels.cached_images -= REF(src) - -/// verbs that send information from the browser UI -/client/verb/set_tab(tab as text|null) - set name = "Set Tab" - set hidden = TRUE - - stat_tab = tab - SSstatpanels.immediate_send_stat_data(src) - -/client/verb/send_tabs(tabs as text|null) - set name = "Send Tabs" - set hidden = TRUE - - panel_tabs |= tabs - -/client/verb/remove_tabs(tabs as text|null) - set name = "Remove Tabs" - set hidden = TRUE - - panel_tabs -= tabs - -/client/verb/reset_tabs() - set name = "Reset Tabs" - set hidden = TRUE - - panel_tabs = list() - -/client/verb/panel_ready() - set name = "Panel Ready" - set hidden = TRUE - - statbrowser_ready = TRUE - init_verbs() - -/client/verb/update_verbs() - set name = "Update Verbs" - set hidden = TRUE - - init_verbs() diff --git a/code/controllers/subsystem/tgui.dm b/code/controllers/subsystem/tgui.dm index 7550bab63e83a..9b0cc241c7bd7 100644 --- a/code/controllers/subsystem/tgui.dm +++ b/code/controllers/subsystem/tgui.dm @@ -29,7 +29,7 @@ SUBSYSTEM_DEF(tgui) /datum/controller/subsystem/tgui/PreInit() basehtml = file2text('tgui/public/tgui.html') // Inject inline polyfills - var/polyfill = file2text('tgui/public/tgui-polyfill.bundle.js') + var/polyfill = file2text('tgui/public/tgui-polyfill.min.js') polyfill = "" basehtml = replacetextEx(basehtml, "", polyfill) diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm index 8deb18e3b167e..df20e00ca7a97 100755 --- a/code/controllers/subsystem/ticker.dm +++ b/code/controllers/subsystem/ticker.dm @@ -478,7 +478,7 @@ SUBSYSTEM_DEF(ticker) if(!hard_popcap) listclearnulls(queued_players) for (var/mob/dead/new_player/new_player in queued_players) - to_chat(new_player, span_userdanger("The alive players limit has been released!
[html_encode(">>Join Game<<")]")) + to_chat(new_player, span_userdanger("The alive players limit has been released!
[html_encode(">>Join Game<<")]")) SEND_SOUND(new_player, sound('sound/misc/notice1.ogg')) GLOB.latejoin_menu.ui_interact(new_player) queued_players.len = 0 @@ -493,7 +493,7 @@ SUBSYSTEM_DEF(ticker) listclearnulls(queued_players) if(living_player_count() < hard_popcap) if(next_in_line && next_in_line.client) - to_chat(next_in_line, span_userdanger("A slot has opened! You have approximately 20 seconds to join. \>\>Join Game\<\<")) + to_chat(next_in_line, span_userdanger("A slot has opened! You have approximately 20 seconds to join. \>\>Join Game\<\<")) SEND_SOUND(next_in_line, sound('sound/misc/notice1.ogg')) next_in_line.ui_interact(next_in_line) return diff --git a/code/datums/actions/cooldown_action.dm b/code/datums/actions/cooldown_action.dm index 8de28855963f9..3b08ad2a67200 100644 --- a/code/datums/actions/cooldown_action.dm +++ b/code/datums/actions/cooldown_action.dm @@ -224,7 +224,7 @@ return PreActivate(user) /// Intercepts client owner clicks to activate the ability -/datum/action/cooldown/proc/InterceptClickOn(mob/living/caller, params, atom/target) +/datum/action/cooldown/proc/InterceptClickOn(mob/living/caller_but_not_a_byond_built_in_proc, params, atom/target) if(!IsAvailable(feedback = TRUE)) return FALSE if(!target) @@ -235,8 +235,8 @@ // And if we reach here, the action was complete successfully if(unset_after_click) - unset_click_ability(caller, refund_cooldown = FALSE) - caller.next_click = world.time + click_cd_override + unset_click_ability(caller_but_not_a_byond_built_in_proc, refund_cooldown = FALSE) + caller_but_not_a_byond_built_in_proc.next_click = world.time + click_cd_override return TRUE diff --git a/code/datums/actions/innate_action.dm b/code/datums/actions/innate_action.dm index 3f4dde5cd4e47..2f6f952fce010 100644 --- a/code/datums/actions/innate_action.dm +++ b/code/datums/actions/innate_action.dm @@ -76,17 +76,17 @@ on_who.click_intercept = null /// Handles whenever a mob clicks on something -/datum/action/innate/proc/InterceptClickOn(mob/living/caller, params, atom/clicked_on) +/datum/action/innate/proc/InterceptClickOn(mob/living/caller_but_not_a_byond_built_in_proc, params, atom/clicked_on) if(!IsAvailable(feedback = TRUE)) - unset_ranged_ability(caller) + unset_ranged_ability(caller_but_not_a_byond_built_in_proc) return FALSE if(!clicked_on) return FALSE - return do_ability(caller, params, clicked_on) + return do_ability(caller_but_not_a_byond_built_in_proc, params, clicked_on) /// Actually goes through and does the click ability -/datum/action/innate/proc/do_ability(mob/living/caller, params, atom/clicked_on) +/datum/action/innate/proc/do_ability(mob/living/caller_but_not_a_byond_built_in_proc, params, atom/clicked_on) return FALSE /datum/action/innate/Remove(mob/removed_from) diff --git a/code/datums/browser.dm b/code/datums/browser.dm index 815e0dac693bb..f6c2a24f535e3 100644 --- a/code/datums/browser.dm +++ b/code/datums/browser.dm @@ -128,13 +128,13 @@ var/output = {"
[Message]

- [Button1]"} + [Button1]"} if (Button2) - output += {"[Button2]"} + output += {"[Button2]"} if (Button3) - output += {"[Button3]"} + output += {"[Button3]"} output += {"
"} @@ -360,11 +360,11 @@ var/setting = settings["mainsettings"][name] if (setting["type"] == "datum") if (setting["subtypesonly"]) - dat += "[setting["desc"]]: [setting["value"]]
" + dat += "[setting["desc"]]: [setting["value"]]
" else - dat += "[setting["desc"]]: [setting["value"]]
" + dat += "[setting["desc"]]: [setting["value"]]
" else - dat += "[setting["desc"]]: [setting["value"]]
" + dat += "[setting["desc"]]: [setting["value"]]
" if (preview_icon) dat += "" @@ -375,7 +375,7 @@ dat += "" - dat += "
Ok " + dat += "
Ok " dat += "
" diff --git a/code/datums/chatmessage.dm b/code/datums/chatmessage.dm index 5392b5bf5f04b..ae3167c41a0fd 100644 --- a/code/datums/chatmessage.dm +++ b/code/datums/chatmessage.dm @@ -1,38 +1,38 @@ /// How long the chat message's spawn-in animation will occur for -#define CHAT_MESSAGE_SPAWN_TIME (0.2 SECONDS) +#define CHAT_MESSAGE_SPAWN_TIME (0.2 SECONDS) /// How long the chat message will exist prior to any exponential decay -#define CHAT_MESSAGE_LIFESPAN (5 SECONDS) +#define CHAT_MESSAGE_LIFESPAN (5 SECONDS) /// How long the chat message's end of life fading animation will occur for -#define CHAT_MESSAGE_EOL_FADE (0.7 SECONDS) +#define CHAT_MESSAGE_EOL_FADE (0.7 SECONDS) /// Grace period for fade before we actually delete the chat message -#define CHAT_MESSAGE_GRACE_PERIOD (0.2 SECONDS) +#define CHAT_MESSAGE_GRACE_PERIOD (0.2 SECONDS) /// Factor of how much the message index (number of messages) will account to exponential decay -#define CHAT_MESSAGE_EXP_DECAY 0.7 +#define CHAT_MESSAGE_EXP_DECAY 0.7 /// Factor of how much height will account to exponential decay -#define CHAT_MESSAGE_HEIGHT_DECAY 0.9 +#define CHAT_MESSAGE_HEIGHT_DECAY 0.9 /// Approximate height in pixels of an 'average' line, used for height decay -#define CHAT_MESSAGE_APPROX_LHEIGHT 11 +#define CHAT_MESSAGE_APPROX_LHEIGHT 11 /// Max width of chat message in pixels -#define CHAT_MESSAGE_WIDTH 96 +#define CHAT_MESSAGE_WIDTH 112 /// The dimensions of the chat message icons -#define CHAT_MESSAGE_ICON_SIZE 9 +#define CHAT_MESSAGE_ICON_SIZE 9 ///Base layer of chat elements -#define CHAT_LAYER 1 +#define CHAT_LAYER 12.0001 ///Highest possible layer of chat elements -#define CHAT_LAYER_MAX 2 +#define CHAT_LAYER_MAX 12.9999 /// Maximum precision of float before rounding errors occur (in this context) #define CHAT_LAYER_Z_STEP 0.0001 /// The number of z-layer 'slices' usable by the chat message layering #define CHAT_LAYER_MAX_Z (CHAT_LAYER_MAX - CHAT_LAYER) / CHAT_LAYER_Z_STEP /** - * # Chat Message Overlay - * - * Datum for generating a message overlay on the map - */ + * # Chat Message Overlay + * + * Datum for generating a message overlay on the map + */ /datum/chatmessage - /// The visual element of the chat messsage + /// The visual element of the chat message var/image/message /// The location in which the message is appearing var/atom/message_loc @@ -50,18 +50,22 @@ var/datum/chatmessage/prev /// The current index used for adjusting the layer of each sequential chat message such that recent messages will overlay older ones var/static/current_z_idx = 0 + /// When we started animating the message + var/animate_start = 0 + /// Our animation lifespan, how long this message will last + var/animate_lifespan = 0 /** - * Constructs a chat message overlay - * - * Arguments: - * * text - The text content of the overlay - * * target - The target atom to display the overlay at - * * owner - The mob that owns this overlay, only this mob will be able to view it - * * language - The language this message was spoken in - * * extra_classes - Extra classes to apply to the span that holds the text - * * lifespan - The lifespan of the message in deciseconds - */ + * Constructs a chat message overlay + * + * Arguments: + * * text - The text content of the overlay + * * target - The target atom to display the overlay at + * * owner - The mob that owns this overlay, only this mob will be able to view it + * * language - The language this message was spoken in + * * extra_classes - Extra classes to apply to the span that holds the text + * * lifespan - The lifespan of the message in deciseconds + */ /datum/chatmessage/New(text, atom/target, mob/owner, datum/language/language, list/extra_classes = list(), lifespan = CHAT_MESSAGE_LIFESPAN) . = ..() if (!istype(target)) @@ -70,45 +74,47 @@ stack_trace("/datum/chatmessage created with [isnull(owner) ? "null" : "invalid"] mob owner") qdel(src) return - if(!SSlag_switch.measures[DISABLE_RUNECHAT] && !HAS_TRAIT(owner, TRAIT_BYPASS_MEASURES)) - INVOKE_ASYNC(src, PROC_REF(generate_image), text, target, owner, language, extra_classes, lifespan) + INVOKE_ASYNC(src, PROC_REF(generate_image), text, target, owner, language, extra_classes, lifespan) /datum/chatmessage/Destroy() - if (owned_by) + if (!QDELING(owned_by)) + if(REALTIMEOFDAY < animate_start + animate_lifespan) + stack_trace("Del'd before we finished fading, with [(animate_start + animate_lifespan) - REALTIMEOFDAY] time left") + if (owned_by.seen_messages) LAZYREMOVEASSOC(owned_by.seen_messages, message_loc, src) owned_by.images.Remove(message) + owned_by = null message_loc = null message = null - leave_subsystem() return ..() /** - * Calls qdel on the chatmessage when its parent is deleted, used to register qdel signal - */ + * Calls qdel on the chatmessage when its parent is deleted, used to register qdel signal + */ /datum/chatmessage/proc/on_parent_qdel() SIGNAL_HANDLER qdel(src) /** - * Generates a chat message image representation - * - * Arguments: - * * text - The text content of the overlay - * * target - The target atom to display the overlay at - * * owner - The mob that owns this overlay, only this mob will be able to view it - * * language - The language this message was spoken in - * * extra_classes - Extra classes to apply to the span that holds the text - * * lifespan - The lifespan of the message in deciseconds - */ + * Generates a chat message image representation + * + * Arguments: + * * text - The text content of the overlay + * * target - The target atom to display the overlay at + * * owner - The mob that owns this overlay, only this mob will be able to view it + * * language - The language this message was spoken in + * * extra_classes - Extra classes to apply to the span that holds the text + * * lifespan - The lifespan of the message in deciseconds + */ /datum/chatmessage/proc/generate_image(text, atom/target, mob/owner, datum/language/language, list/extra_classes, lifespan) /// Cached icons to show what language the user is speaking var/static/list/language_icons // Register client who owns this message owned_by = owner.client - RegisterSignal(owned_by, COMSIG_QDELETING, PROC_REF(on_parent_qdel), src) + RegisterSignal(owned_by, COMSIG_QDELETING, PROC_REF(on_parent_qdel)) // Remove spans in the message from things like the recorder var/static/regex/span_check = new(@"<\/?span[^>]*>", "gi") @@ -139,6 +145,10 @@ if (!ismob(target)) extra_classes |= "small" + // Why are you yelling? + if(copytext_char(text, -2) == "!!") + extra_classes |= SPAN_YELL + var/list/prefixes // Append radio icon if from a virtual speaker @@ -165,27 +175,66 @@ var/tgt_color = extra_classes.Find("italics") ? target.chat_color_darkened : target.chat_color // Approximate text height - var/complete_text = "[text]" + var/complete_text = "[owner.say_emphasis(text)]" + var/mheight WXH_TO_HEIGHT(owned_by.MeasureText(complete_text, null, CHAT_MESSAGE_WIDTH), mheight) - approx_lines = max(1, mheight / CHAT_MESSAGE_APPROX_LHEIGHT) + if(!VERB_SHOULD_YIELD) + return finish_image_generation(mheight, target, owner, complete_text, lifespan) + + var/datum/callback/our_callback = CALLBACK(src, PROC_REF(finish_image_generation), mheight, target, owner, complete_text, lifespan) + SSrunechat.message_queue += our_callback + return + +///finishes the image generation after the MeasureText() call in generate_image(). +///necessary because after that call the proc can resume at the end of the tick and cause overtime. +/datum/chatmessage/proc/finish_image_generation(mheight, atom/target, mob/owner, complete_text, lifespan) + var/rough_time = REALTIMEOFDAY + approx_lines = max(1, mheight / CHAT_MESSAGE_APPROX_LHEIGHT) + var/starting_height = target.maptext_height // Translate any existing messages upwards, apply exponential decay factors to timers - message_loc = get_atom_on_turf(target) + message_loc = isturf(target) ? target : get_atom_on_turf(target) if (owned_by.seen_messages) var/idx = 1 var/combined_height = approx_lines - for(var/msg in owned_by.seen_messages[message_loc]) - var/datum/chatmessage/m = msg - animate(m.message, pixel_y = m.message.pixel_y + mheight, time = CHAT_MESSAGE_SPAWN_TIME) + for(var/datum/chatmessage/m as anything in owned_by.seen_messages[message_loc]) combined_height += m.approx_lines + var/time_spent = rough_time - m.animate_start + var/time_before_fade = m.animate_lifespan - CHAT_MESSAGE_EOL_FADE + // When choosing to update the remaining time we have to be careful not to update the - // scheduled time once the EOL completion time has been set. - var/sched_remaining = m.scheduled_destruction - world.time - if (!m.eol_complete) - var/remaining_time = (sched_remaining) * (CHAT_MESSAGE_EXP_DECAY ** idx++) * (CHAT_MESSAGE_HEIGHT_DECAY ** combined_height) - m.enter_subsystem(world.time + remaining_time) // push updated time to runechat SS + // scheduled time once the EOL has been executed. + if (time_spent >= time_before_fade) + if(m.message.pixel_y < starting_height) + var/max_height = m.message.pixel_y + m.approx_lines * CHAT_MESSAGE_APPROX_LHEIGHT - starting_height + if(max_height > 0) + animate(m.message, pixel_y = m.message.pixel_y + max_height, time = CHAT_MESSAGE_SPAWN_TIME, flags = ANIMATION_PARALLEL) + else if(mheight + starting_height >= m.message.pixel_y) + animate(m.message, pixel_y = m.message.pixel_y + mheight, time = CHAT_MESSAGE_SPAWN_TIME, flags = ANIMATION_PARALLEL) + continue + + var/remaining_time = time_before_fade * (CHAT_MESSAGE_EXP_DECAY ** idx++) * (CHAT_MESSAGE_HEIGHT_DECAY ** combined_height) + // Ensure we don't accidentially spike alpha up or something silly like that + m.message.alpha = m.get_current_alpha(time_spent) + if (remaining_time > 0) + // Stay faded in for a while, then + animate(m.message, alpha = 255, remaining_time) + // Fade out + animate(alpha = 0, time = CHAT_MESSAGE_EOL_FADE) + m.animate_lifespan = remaining_time + CHAT_MESSAGE_EOL_FADE + else + // Your time has come my son + animate(alpha = 0, time = CHAT_MESSAGE_EOL_FADE) + // We run this after the alpha animate, because we don't want to interrup it, but also don't want to block it by running first + // Sooo instead we do this. bit messy but it fuckin works + if(m.message.pixel_y < starting_height) + var/max_height = m.message.pixel_y + m.approx_lines * CHAT_MESSAGE_APPROX_LHEIGHT - starting_height + if(max_height > 0) + animate(m.message, pixel_y = m.message.pixel_y + max_height, time = CHAT_MESSAGE_SPAWN_TIME, flags = ANIMATION_PARALLEL) + else if(mheight + starting_height >= m.message.pixel_y) + animate(m.message, pixel_y = m.message.pixel_y + mheight, time = CHAT_MESSAGE_SPAWN_TIME, flags = ANIMATION_PARALLEL) // Reset z index if relevant if (current_z_idx >= CHAT_LAYER_MAX_Z) @@ -196,49 +245,53 @@ SET_PLANE_EXPLICIT(message, RUNECHAT_PLANE, message_loc) message.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA | KEEP_APART message.alpha = 0 - message.pixel_y = owner.bound_height * 0.95 + message.pixel_y = starting_height + message.pixel_x = -target.base_pixel_x message.maptext_width = CHAT_MESSAGE_WIDTH - message.maptext_height = mheight + message.maptext_height = mheight * 1.25 // We add extra because some characters are superscript, like actions message.maptext_x = (CHAT_MESSAGE_WIDTH - owner.bound_width) * -0.5 - message.maptext = complete_text + message.maptext = MAPTEXT(complete_text) + + animate_start = rough_time + animate_lifespan = lifespan // View the message LAZYADDASSOCLIST(owned_by.seen_messages, message_loc, src) owned_by.images |= message + + // Fade in animate(message, alpha = 255, time = CHAT_MESSAGE_SPAWN_TIME) + var/time_before_fade = lifespan - CHAT_MESSAGE_SPAWN_TIME - CHAT_MESSAGE_EOL_FADE + // Stay faded in + animate(alpha = 255, time = time_before_fade) + // Fade out + animate(alpha = 0, time = CHAT_MESSAGE_EOL_FADE) - // Register with the runechat SS to handle EOL and destruction - scheduled_destruction = world.time + (lifespan - CHAT_MESSAGE_EOL_FADE) - RegisterSignal(message_loc, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(loc_z_changed)) - enter_subsystem() + // Register with the runechat SS to handle destruction + addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(qdel), src), lifespan + CHAT_MESSAGE_GRACE_PERIOD, TIMER_DELETE_ME, SSrunechat) +/datum/chatmessage/proc/get_current_alpha(time_spent) + if(time_spent < CHAT_MESSAGE_SPAWN_TIME) + return (time_spent / CHAT_MESSAGE_SPAWN_TIME) * 255 -/datum/chatmessage/proc/loc_z_changed(datum/source, turf/old_turf, turf/new_turf, same_z_layer) - SIGNAL_HANDLER - SET_PLANE(message, RUNECHAT_PLANE, new_turf) + var/time_before_fade = animate_lifespan - CHAT_MESSAGE_EOL_FADE + if(time_spent <= time_before_fade) + return 255 -/** - * Applies final animations to overlay CHAT_MESSAGE_EOL_FADE deciseconds prior to message deletion, - * sets time for scheduling deletion and re-enters the runechat SS for qdeling - * - * Arguments: - * * fadetime - The amount of time to animate the message's fadeout for - */ -/datum/chatmessage/proc/end_of_life(fadetime = CHAT_MESSAGE_EOL_FADE) - eol_complete = scheduled_destruction + fadetime - animate(message, alpha = 0, time = fadetime, flags = ANIMATION_PARALLEL) - enter_subsystem(eol_complete) // re-enter the runechat SS with the EOL completion time to QDEL self + return (1 - ((time_spent - time_before_fade) / CHAT_MESSAGE_EOL_FADE)) * 255 /** - * Creates a message overlay at a defined location for a given speaker - * - * Arguments: - * * speaker - The atom who is saying this message - * * message_language - The language that the message is said in - * * raw_message - The text content of the message - * * spans - Additional classes to be added to the message - */ + * Creates a message overlay at a defined location for a given speaker + * + * Arguments: + * * speaker - The atom who is saying this message + * * message_language - The language that the message is said in + * * raw_message - The text content of the message + * * spans - Additional classes to be added to the message + */ /mob/proc/create_chat_message(atom/movable/speaker, datum/language/message_language, raw_message, list/spans, runechat_flags = NONE) + if(HAS_TRAIT(speaker, TRAIT_RUNECHAT_HIDDEN)) + return // Ensure the list we are using, if present, is a copy so we don't modify the list provided to us spans = spans ? spans.Copy() : list() @@ -248,11 +301,11 @@ var/atom/movable/virtualspeaker/v = speaker speaker = v.source spans |= "virtual-speaker" - + //NTSL doesn't pass a speaker when you do broadcast() since technically nothing is actually speaking. if(!speaker) return - + // Ignore virtual speaker (most often radio messages) from ourself if (originalSpeaker != src && speaker == src) return @@ -261,25 +314,24 @@ if(runechat_flags & EMOTE_MESSAGE) new /datum/chatmessage(raw_message, speaker, src, message_language, list("emote", "italics")) else - new /datum/chatmessage(lang_treat(speaker, message_language, raw_message, spans, null, TRUE), speaker, src, message_language, spans) - + new /datum/chatmessage(translate_language(speaker, message_language, raw_message, spans), speaker, src, message_language, spans) // Tweak these defines to change the available color ranges -#define CM_COLOR_SAT_MIN 0.6 -#define CM_COLOR_SAT_MAX 0.7 -#define CM_COLOR_LUM_MIN 0.65 -#define CM_COLOR_LUM_MAX 0.75 +#define CM_COLOR_SAT_MIN 0.6 +#define CM_COLOR_SAT_MAX 0.7 +#define CM_COLOR_LUM_MIN 0.65 +#define CM_COLOR_LUM_MAX 0.75 /** - * Gets a color for a name, will return the same color for a given string consistently within a round.atom - * - * Note that this proc aims to produce pastel-ish colors using the HSL colorspace. These seem to be favorable for displaying on the map. - * - * Arguments: - * * name - The name to generate a color for - * * sat_shift - A value between 0 and 1 that will be multiplied against the saturation - * * lum_shift - A value between 0 and 1 that will be multiplied against the luminescence - */ + * Gets a color for a name, will return the same color for a given string consistently within a round.atom + * + * Note that this proc aims to produce pastel-ish colors using the HSL colorspace. These seem to be favorable for displaying on the map. + * + * Arguments: + * * name - The name to generate a color for + * * sat_shift - A value between 0 and 1 that will be multiplied against the saturation + * * lum_shift - A value between 0 and 1 that will be multiplied against the luminescence + */ /datum/chatmessage/proc/colorize_string(name, sat_shift = 1, lum_shift = 1) // seed to help randomness var/static/rseed = rand(1,26) @@ -316,13 +368,19 @@ if(5) return "#[num2hex(c, 2)][num2hex(m, 2)][num2hex(x, 2)]" -#undef CHAT_MESSAGE_SPAWN_TIME -#undef CHAT_MESSAGE_LIFESPAN + +#undef CHAT_LAYER_MAX_Z +#undef CHAT_LAYER_Z_STEP +#undef CHAT_MESSAGE_APPROX_LHEIGHT +#undef CHAT_MESSAGE_GRACE_PERIOD #undef CHAT_MESSAGE_EOL_FADE #undef CHAT_MESSAGE_EXP_DECAY #undef CHAT_MESSAGE_HEIGHT_DECAY -#undef CHAT_MESSAGE_APPROX_LHEIGHT -#undef CHAT_MESSAGE_WIDTH -#undef CHAT_LAYER_Z_STEP -#undef CHAT_LAYER_MAX_Z #undef CHAT_MESSAGE_ICON_SIZE +#undef CHAT_MESSAGE_LIFESPAN +#undef CHAT_MESSAGE_SPAWN_TIME +#undef CHAT_MESSAGE_WIDTH +#undef CM_COLOR_LUM_MAX +#undef CM_COLOR_LUM_MIN +#undef CM_COLOR_SAT_MAX +#undef CM_COLOR_SAT_MIN diff --git a/code/datums/holocall.dm b/code/datums/holocall.dm index a1f4156126f7b..a4f0a6ba8184b 100644 --- a/code/datums/holocall.dm +++ b/code/datums/holocall.dm @@ -37,10 +37,10 @@ var/call_start_time var/head_call = FALSE //calls from a head of staff autoconnect, if the recieving pad is not secure. -//creates a holocall made by `caller` from `calling_pad` to `callees` -/datum/holocall/New(mob/living/caller, obj/machinery/holopad/calling_pad, list/callees, elevated_access = FALSE) +//creates a holocall made by `caller_but_not_a_byond_built_in_proc` from `calling_pad` to `callees` +/datum/holocall/New(mob/living/caller_but_not_a_byond_built_in_proc, obj/machinery/holopad/calling_pad, list/callees, elevated_access = FALSE) call_start_time = world.time - user = caller + user = caller_but_not_a_byond_built_in_proc calling_pad.outgoing_call = src calling_holopad = calling_pad head_call = elevated_access @@ -302,7 +302,7 @@ var/datum/preset_holoimage/H = new preset_image_type record.caller_image = H.build_image() -//These build caller image from outfit and some additional data, for use by mappers for ruin holorecords +//These build caller_but_not_a_byond_built_in_proc image from outfit and some additional data, for use by mappers for ruin holorecords /datum/preset_holoimage var/nonhuman_mobtype //Fill this if you just want something nonhuman var/outfit_type diff --git a/code/datums/mapgen/dungeon_generators/maintenance_generator/maintenance_generator.dm b/code/datums/mapgen/dungeon_generators/maintenance_generator/maintenance_generator.dm index 2b1748b4eaa2a..aa2f03adc317f 100644 --- a/code/datums/mapgen/dungeon_generators/maintenance_generator/maintenance_generator.dm +++ b/code/datums/mapgen/dungeon_generators/maintenance_generator/maintenance_generator.dm @@ -210,7 +210,7 @@ //We have to rawdog the Astar pathfinding and skip the wrapper proc because that's made specifically for mobs var/list/cable_path = AStar( - caller = our_apc, + caller_but_not_a_byond_built_in_proc = our_apc, _end = closest_apc, dist = /turf/proc/Distance_cardinal, maxnodes = 0, diff --git a/code/datums/profiling.dm b/code/datums/profiling.dm index dcf966750a627..1313655a7e139 100644 --- a/code/datums/profiling.dm +++ b/code/datums/profiling.dm @@ -15,4 +15,6 @@ GLOBAL_REAL_VAR(PROFILE_TIME) var/list/data = PROFILE_STORE[entry] lines += "[entry] => [num2text(data[PROFILE_ITEM_TIME], 10)]ms ([data[PROFILE_ITEM_COUNT]]) (avg:[num2text(data[PROFILE_ITEM_TIME]/(data[PROFILE_ITEM_COUNT] || 1), 99)])" - user << browse("
  1. [lines.Join("
  2. ")]
", "window=[url_encode(GUID())]") + var/datum/browser/browser = new(user, "[url_encode(GUID())]", null, 500, 500) + browser.set_content("
  1. [lines.Join("
  2. ")]
") + browser.open() diff --git a/code/datums/voice_announcements.dm b/code/datums/voice_announcements.dm index 30afbd51c3ba5..e8b7a583cf0ea 100644 --- a/code/datums/voice_announcements.dm +++ b/code/datums/voice_announcements.dm @@ -112,7 +112,7 @@ GLOBAL_LIST_EMPTY(voice_announce_list) fdel(base_file) log_admin("[key_name(client)] has made a voice announcement via [ip], saved to [base_filename]") - message_admins("[key_name_admin(client)] has made a voice announcement. ((CANCEL))") + message_admins("[key_name_admin(client)] has made a voice announcement. ((CANCEL))") announce(snd) else diff --git a/code/game/atom/_atom.dm b/code/game/atom/_atom.dm index f0df0ec8a0371..9eb481155d2c9 100644 --- a/code/game/atom/_atom.dm +++ b/code/game/atom/_atom.dm @@ -1187,7 +1187,7 @@ . = ..() var/refid = REF(src) . += "[VV_HREF_TARGETREF(refid, VV_HK_AUTO_RENAME, "[src]")]" - . += "
<< [dir2text(dir) || dir] >>" + . += "
<< [dir2text(dir) || dir] >>" ///Where atoms should drop if taken from this atom /atom/proc/drop_location() diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm index 5231e4953419b..163c54edc8a55 100644 --- a/code/game/machinery/camera/camera.dm +++ b/code/game/machinery/camera/camera.dm @@ -304,9 +304,9 @@ if(AI.control_disabled || (AI.stat == DEAD)) return if(U.name == "Unknown") - to_chat(AI, "[U] holds \a [itemname] up to one of your cameras ...") + to_chat(AI, "[U] holds \a [itemname] up to one of your cameras ...") else - to_chat(AI, "[U] holds \a [itemname] up to one of your cameras ...") + to_chat(AI, "[U] holds \a [itemname] up to one of your cameras ...") if(istype(X)) info = X.render_body(AI) AI.last_paper_seen = "[itemname][info]" diff --git a/code/game/machinery/computer/card.dm b/code/game/machinery/computer/card.dm index 872c46be78cf3..57cf0e1c92ae7 100644 --- a/code/game/machinery/computer/card.dm +++ b/code/game/machinery/computer/card.dm @@ -139,14 +139,14 @@ GLOBAL_VAR_INIT(time_last_changed_position, 0) dat += "Crew Manifest:
Please use security record computer to modify entries.

" for(var/datum/data/record/t in sortRecord(GLOB.data_core.general)) dat += {"[t.fields["name"]] - [t.fields["rank"]]
"} - dat += "Print

Access ID modification console.
" + dat += "Print

Access ID modification console.
" else if(mode == 2) // JOB MANAGEMENT var/identity_name = "--------" if(user_id) identity_name = html_encode(user_id.name) - dat += {"Return + dat += {"Return || Confirm Identity: [identity_name] @@ -166,7 +166,7 @@ GLOBAL_VAR_INIT(time_last_changed_position, 0) switch(can_open_job(job)) if(JOB_ALLOWED) if(ID) - dat += "Open Position
" + dat += "Open Position
" else dat += "Open Position" if(JOB_COOLDOWN) @@ -180,7 +180,7 @@ GLOBAL_VAR_INIT(time_last_changed_position, 0) switch(can_close_job(job)) if(JOB_ALLOWED) if(ID) - dat += "Close Position" + dat += "Close Position" else dat += "Close Position" if(JOB_COOLDOWN) @@ -197,10 +197,10 @@ GLOBAL_VAR_INIT(time_last_changed_position, 0) else if(ID) if(job in SSjob.prioritized_jobs) - dat += "Deprioritize" + dat += "Deprioritize" else if(SSjob.prioritized_jobs.len < 5) - dat += "Prioritize" + dat += "Prioritize" else dat += "Denied" else @@ -220,14 +220,14 @@ GLOBAL_VAR_INIT(time_last_changed_position, 0) if(!authenticated) header += {"
Please insert the cards into the slots
- Target: [target_name]
+ Target: [target_name]
Confirm Identity: [scan_name]
"} else header += {"

- Remove [target_name] || + Remove [target_name] || Logged in as [scan_name]
- Access Crew Manifest
- Log Out
"} + Access Crew Manifest
+ Log Out"} header += "
" @@ -240,7 +240,7 @@ GLOBAL_VAR_INIT(time_last_changed_position, 0) if (authenticated == 2) var/list/jobs_all = list() for(var/job in (list("Unassigned") + get_jobs() + "Custom")) - jobs_all += "[replacetext(job, " ", " ")] " //make sure there isn't a line break in the middle of a job + jobs_all += "[replacetext(job, " ", " ")] " //make sure there isn't a line break in the middle of a job carddesc += {""}, "window=asset_cache_browser&file=asset_cache_send_verify.htm") + if(byond_version < 516) + src << browse({""}, "window=asset_cache_browser&file=asset_cache_send_verify.htm") + else + src << browse({""}, "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. + stoplag(1) // Lock up the caller_but_not_a_byond_built_in_proc until this is received. t++ if (t < timeout_time) return TRUE diff --git a/code/modules/asset_cache/validate_assets.html b/code/modules/asset_cache/validate_assets.html index 78bcbb92a1ab8..ea5d9edfa6a42 100644 --- a/code/modules/asset_cache/validate_assets.html +++ b/code/modules/asset_cache/validate_assets.html @@ -8,7 +8,7 @@ //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.open('GET', 'byond://?' + text, true); xhr.send(null); } var xhr = new XMLHttpRequest(); diff --git a/code/modules/awaymissions/capture_the_flag.dm b/code/modules/awaymissions/capture_the_flag.dm index 3315166f3d5aa..4618c807b57ed 100644 --- a/code/modules/awaymissions/capture_the_flag.dm +++ b/code/modules/awaymissions/capture_the_flag.dm @@ -327,7 +327,7 @@ dead_barricades.Cut() - notify_ghosts("[name] has been activated!", enter_link="(Click to join the [team] team!) or click on the controller directly!", source = src, action=NOTIFY_ATTACK) + notify_ghosts("[name] has been activated!", enter_link="(Click to join the [team] team!) or click on the controller directly!", source = src, action=NOTIFY_ATTACK) if(!arena_reset) reset_the_arena() diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm index a3d7db2478b39..1158d24f3e5f4 100644 --- a/code/modules/client/client_defines.dm +++ b/code/modules/client/client_defines.dm @@ -130,9 +130,6 @@ /// our current tab var/stat_tab - /// whether our browser is ready or not yet - var/statbrowser_ready = FALSE - /// list of all tabs var/list/panel_tabs = list() diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index 8a9c3875e8e5b..8405769362e54 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -112,7 +112,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( if(href_list["reload_tguipanel"]) nuke_chat() if(href_list["reload_statbrowser"]) - src << browse(file('html/statbrowser.html'), "window=statbrowser") + stat_panel.reinitialize() // Log all hrefs log_href("[src] (usr:[usr]\[[COORD(usr)]\]) : [hsrc ? "[hsrc] " : ""][href]") @@ -231,6 +231,13 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( GLOB.clients += src GLOB.directory[ckey] = src + if(byond_version >= 516) + winset(src, null, list("browser-options" = "find,refresh,byondstorage,devtools")) + + // Instantiate stat panel + stat_panel = new(src, "statbrowser") + stat_panel.subscribe(src, PROC_REF(on_stat_panel_message)) + // Instantiate tgui panel tgui_panel = new(src, "browseroutput") @@ -297,9 +304,12 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( if(GLOB.player_details[ckey]) player_details = GLOB.player_details[ckey] player_details.byond_version = full_version + player_details.byond_version = byond_version + player_details.byond_build = byond_build else player_details = new - player_details.byond_version = full_version + player_details.byond_version = byond_version + player_details.byond_build = byond_build GLOB.player_details[ckey] = player_details if (prefs.unlock_content & DONOR_YOGS) @@ -331,13 +341,16 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( if(SSinput.initialized) set_macros() - src << browse(file('html/statbrowser.html'), "window=statbrowser") + // Initialize stat panel + stat_panel.initialize( + inline_html = file("html/statbrowser.html"), + inline_js = file("html/statbrowser.js"), + inline_css = file("html/statbrowser.css"), + ) + addtimer(CALLBACK(src, PROC_REF(check_panel_loaded)), 30 SECONDS) // Initialize tgui panel - tgui_panel.Initialize() - src << browse(file('html/statbrowser.html'), "window=statbrowser") - addtimer(CALLBACK(src, PROC_REF(check_panel_loaded)), 5 SECONDS) - + tgui_panel.initialize() if(alert_mob_dupe_login) spawn() @@ -369,7 +382,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( msg += "Required version to remove this message: [cwv] or later
" msg += "Visit BYOND's website to get the latest version of BYOND.
" msg += "" - src << browse(msg, "window=warning_popup") + src << browse(HTML_SKELETON(msg), "window=warning_popup") else to_chat(src, span_danger("Your version of byond may be getting out of date:")) to_chat(src, CONFIG_GET(string/client_warn_message)) @@ -1094,19 +1107,21 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( continue panel_tabs |= verb_to_init.category verblist[++verblist.len] = list(verb_to_init.category, verb_to_init.name) - src << output("[url_encode(json_encode(panel_tabs))];[url_encode(json_encode(verblist))]", "statbrowser:init_verbs") + src.stat_panel.send_message("init_verbs", list(panel_tabs = panel_tabs, verblist = verblist)) /client/proc/check_panel_loaded() - if(statbrowser_ready) +/* //in case the window is "ready" but didn't load properly, we'll always give the button, subject to change. + if(stat_panel.is_ready()) return - to_chat(src, span_userdanger("Statpanel failed to load, click here to reload the panel ")) - tgui_panel.Initialize() + to_chat(src, span_userdanger("Statpanel failed to load, click here to reload the panel ")) +*/ + to_chat(src, span_userdanger("If statpanel failed to load, click here to reload it.")) /client/verb/reload_statpanel() set name = "Reload Statpanel" set category = "OOC" - usr << browse(file('html/statbrowser.html'), "window=statbrowser") + stat_panel.reinitialize() /client/verb/stop_client_sounds() set name = "Stop Sounds" @@ -1124,6 +1139,23 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( else SSambience.ambience_listening_clients -= src +/** + * Handles incoming messages from the stat-panel TGUI. + */ +/client/proc/on_stat_panel_message(type, payload) + switch(type) + if("Update-Verbs") + init_verbs() + if("Remove-Tabs") + panel_tabs -= payload["tab"] + if("Send-Tabs") + panel_tabs |= payload["tab"] + if("Reset-Tabs") + panel_tabs = list() + if("Set-Tab") + stat_tab = payload["tab"] + SSstatpanels.immediate_send_stat_data(src) + /client/proc/open_filter_editor(atom/in_atom) if(holder) holder.filteriffic = new /datum/filter_editor(in_atom) diff --git a/code/modules/client/player_details.dm b/code/modules/client/player_details.dm index 7dbfbe8c1ec91..f85355bc3e1db 100644 --- a/code/modules/client/player_details.dm +++ b/code/modules/client/player_details.dm @@ -4,7 +4,16 @@ var/list/post_login_callbacks = list() var/list/post_logout_callbacks = list() var/list/played_names = list() //List of names this key played under this round - var/byond_version = "Unknown" + /// Major version of BYOND this client is using. + var/byond_version + /// Build number of BYOND this client is using. + var/byond_build + +/// Returns the full version string (i.e 515.1642) of the BYOND version and build. +/datum/player_details/proc/full_byond_version() + if(!byond_version) + return "Unknown" + return "[byond_version].[byond_build || "xxx"]" /proc/log_played_names(ckey, ...) if(!ckey) diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm index bd2abb2fe6f39..c8ff9621c1197 100644 --- a/code/modules/client/preferences_savefile.dm +++ b/code/modules/client/preferences_savefile.dm @@ -134,7 +134,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car /datum/preferences/proc/announce_conflict(list/notadded) to_chat(parent, "Keybinding Conflict\n\ - There are new keybindings that default to keys you've already bound. The new ones will be unbound.") + There are new keybindings that default to keys you've already bound. The new ones will be unbound.") for(var/item in notadded) var/datum/keybinding/conflicted = item to_chat(parent, span_danger("[conflicted.category]: [conflicted.full_name] needs updating")) diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm index a159370b1a5cb..14d612379da0c 100644 --- a/code/modules/clothing/clothing.dm +++ b/code/modules/clothing/clothing.dm @@ -273,7 +273,7 @@ else how_cool_are_your_threads += "[src]'s storage opens when dragged to yourself.\n" if (pockets.can_hold?.len) // If pocket type can hold anything, vs only specific items - how_cool_are_your_threads += "[src] can store [pockets.max_items] item\s.\n" + how_cool_are_your_threads += "[src] can store [pockets.max_items] item\s.\n" else how_cool_are_your_threads += "[src] can store [pockets.max_items] item\s that are [weightclass2text(pockets.max_w_class)] or smaller.\n" if(pockets.quickdraw) @@ -284,7 +284,7 @@ . += how_cool_are_your_threads.Join() if(armor.bio || armor.bomb || armor.bullet || armor.energy || armor.laser || armor.melee || armor.fire || armor.acid|| flags_cover & HEADCOVERSMOUTH || flags_cover & HEADCOVERSEYES) - . += "It has a tag listing its protection classes." + . += "It has a tag listing its protection classes." /obj/item/clothing/Topic(href, href_list) . = ..() diff --git a/code/modules/clothing/gloves/miscellaneous.dm b/code/modules/clothing/gloves/miscellaneous.dm index 2df48ebaab521..13d02a2845ad3 100644 --- a/code/modules/clothing/gloves/miscellaneous.dm +++ b/code/modules/clothing/gloves/miscellaneous.dm @@ -172,7 +172,7 @@ /datum/action/cooldown/swipe/Activate(mob/living/target) . = ..() var/turf/open/target_turf = get_turf(target) - var/mob/living/carbon/caller = owner + var/mob/living/carbon/caller_but_not_a_byond_built_in_proc = owner if(!istype(target_turf)) return FALSE if(!(target_turf in range(9, owner))) @@ -188,22 +188,22 @@ if(isanimal(target)) if(target.stat != DEAD) target.adjustBruteLoss(60) - caller.adjustBruteLoss(-13) - caller.adjustFireLoss(-13) - caller.adjustToxLoss(-13) + caller_but_not_a_byond_built_in_proc.adjustBruteLoss(-13) + caller_but_not_a_byond_built_in_proc.adjustFireLoss(-13) + caller_but_not_a_byond_built_in_proc.adjustToxLoss(-13) if(target.stat == DEAD) - to_chat(caller, span_notice("You kill [target], healing yourself more!")) + to_chat(caller_but_not_a_byond_built_in_proc, span_notice("You kill [target], healing yourself more!")) if(target.stat == DEAD) target.gib() - to_chat(caller, span_notice("You're able to consume the body entirely!")) - caller.adjustBruteLoss(-20) - caller.adjustFireLoss(-20) - caller.adjustToxLoss(-20) + to_chat(caller_but_not_a_byond_built_in_proc, span_notice("You're able to consume the body entirely!")) + caller_but_not_a_byond_built_in_proc.adjustBruteLoss(-20) + caller_but_not_a_byond_built_in_proc.adjustFireLoss(-20) + caller_but_not_a_byond_built_in_proc.adjustToxLoss(-20) if(iscarbon(target)) target.adjustBruteLoss(20) - caller.adjustBruteLoss(-5) - caller.adjustFireLoss(-5) - caller.adjustToxLoss(-5) + caller_but_not_a_byond_built_in_proc.adjustBruteLoss(-5) + caller_but_not_a_byond_built_in_proc.adjustFireLoss(-5) + caller_but_not_a_byond_built_in_proc.adjustToxLoss(-5) addtimer(CALLBACK(src, PROC_REF(cooldown_over), owner), cooldown_time) unset_click_ability(owner) return TRUE diff --git a/code/modules/error_handler/error_viewer.dm b/code/modules/error_handler/error_viewer.dm index d355043fff86f..30359029a62e9 100644 --- a/code/modules/error_handler/error_viewer.dm +++ b/code/modules/error_handler/error_viewer.dm @@ -71,7 +71,7 @@ GLOBAL_DATUM(error_cache, /datum/error_viewer/error_cache) if (linear) back_to_param += ";viewruntime_linear=1" - return "[linktext]" + return "[linktext]" /datum/error_viewer/error_cache var/list/errors = list() @@ -182,12 +182,12 @@ GLOBAL_DATUM(error_cache, /datum/error_viewer/error_cache) var/html = build_header(back_to, linear) html += "[name]
[desc]
" if (usr_ref) - html += "
usr: VV" - html += " PP" - html += " Follow" + html += "
usr: VV" + html += " PP" + html += " Follow" if (istype(usr_loc)) - html += "
usr.loc: VV" - html += " JMP" + html += "
usr.loc: VV" + html += " JMP" browse_to(user, html) diff --git a/code/modules/events/_event.dm b/code/modules/events/_event.dm index a2f46331da306..9f9e36d0e418f 100644 --- a/code/modules/events/_event.dm +++ b/code/modules/events/_event.dm @@ -284,12 +284,12 @@ Runs the event if(roundstart) if(!can_run_post_roundstart) return "Force Now
Schedule" - return "Force Now
Schedule" + return "Force Now
Schedule" else - return "Force Now
Force Next
Schedule" + return "Force Now
Force Next
Schedule" else if(roundstart) - return "Force Roundstart
Add Roundstart" + return "Force Roundstart
Add Roundstart" else return "Force Now
Force Next
Schedule" diff --git a/code/modules/food_and_drinks/kitchen_machinery/food_cart.dm b/code/modules/food_and_drinks/kitchen_machinery/food_cart.dm index f5bf4f98449f8..3f55cf58b94b2 100644 --- a/code/modules/food_and_drinks/kitchen_machinery/food_cart.dm +++ b/code/modules/food_and_drinks/kitchen_machinery/food_cart.dm @@ -33,25 +33,25 @@ var/dat dat += "
STORED INGREDIENTS AND DRINKS
" dat += "Remaining glasses: [glasses]
" - dat += "Portion: [portion]
" + dat += "Portion: [portion]
" for(var/datum/reagent/R in reagents.reagent_list) dat += "[R.name]: [R.volume] " - dat += "Purge" + dat += "Purge" if (glasses > 0) - dat += "Pour in a glass" - dat += "Add to the mixer
" + dat += "Pour in a glass" + dat += "Add to the mixer
" dat += "

MIXER CONTENTS
" for(var/datum/reagent/R in mixer.reagents.reagent_list) dat += "[R.name]: [R.volume] " - dat += "Transfer back" + dat += "Transfer back" if (glasses > 0) - dat += "Pour in a glass" + dat += "Pour in a glass" dat += "
" dat += "

STORED FOOD
" for(var/V in stored_food) if(stored_food[V] > 0) - dat += "[V]: [stored_food[V]] Dispense
" - dat += "

RefreshClose" + dat += "[V]: [stored_food[V]]Dispense
" + dat += "
RefreshClose" var/datum/browser/popup = new(user, "foodcart","Food Cart", 500, 350, src) popup.set_content(dat) diff --git a/code/modules/hydroponics/gene_modder.dm b/code/modules/hydroponics/gene_modder.dm index 1f0c451cb5009..cf70d4882cab1 100644 --- a/code/modules/hydroponics/gene_modder.dm +++ b/code/modules/hydroponics/gene_modder.dm @@ -170,19 +170,19 @@ dat += "[span_highlight("[target.get_name()]")] gene with [span_highlight("[disk.gene.get_name()]")]?
" if("insert") dat += "[span_highlight("[disk.gene.get_name()]")] gene into \the [span_highlight("[seed]")]?
" - dat += "
Confirm " - dat += "Abort
" + dat += "
Confirm " + dat += "Abort
" popup.set_content(dat) popup.open() return dat+= "
" - dat += "
" dat += "
JobSlots
[G.get_name()]" if(can_extract) - dat += "Extract" + dat += "Extract" if(can_insert && istype(disk.gene, G.type)) - dat += "Replace" + dat += "Replace" dat += "
" @@ -220,15 +220,15 @@ var/datum/plant_gene/G = a dat += "[G.get_name()]" if(can_extract) - dat += "Extract" - dat += "Remove" + dat += "Extract" + dat += "Remove" dat += "" dat += "" else dat += "No content-related genes detected in sample.
" dat += "" if(can_insert && istype(disk.gene, /datum/plant_gene/reagent)) - dat += "Insert: [disk.gene.get_name()]" + dat += "Insert: [disk.gene.get_name()]" dat += "

Trait Genes

" if(trait_genes.len) @@ -237,14 +237,14 @@ var/datum/plant_gene/G = a dat += "[G.get_name()]" if(can_extract) - dat += "Extract" - dat += "Remove" + dat += "Extract" + dat += "Remove" dat += "" dat += "" else dat += "No trait-related genes detected in sample.
" if(can_insert && istype(disk.gene, /datum/plant_gene/trait)) - dat += "Insert: [disk.gene.get_name()]" + dat += "Insert: [disk.gene.get_name()]" dat += "
" else dat += "
No sample found.
[span_highlight("Please, insert a plant sample to use this device.")]" diff --git a/code/modules/instruments/songs/editor.dm b/code/modules/instruments/songs/editor.dm index 2359155128804..ce8f9586e002c 100644 --- a/code/modules/instruments/songs/editor.dm +++ b/code/modules/instruments/songs/editor.dm @@ -4,30 +4,30 @@ /datum/song/proc/instrument_status_ui() . = list() . += "
" - . += "Current instrument: " + . += "Current instrument: " if(!using_instrument) . += "[span_danger("No instrument loaded!")]
" else . += "[using_instrument.name]
" . += "Playback Settings:
" if(can_noteshift) - . += "Note Shift/Note Transpose: [note_shift] keys / [round(note_shift / 12, 0.01)] octaves
" + . += "Note Shift/Note Transpose: [note_shift] keys / [round(note_shift / 12, 0.01)] octaves
" var/smt var/modetext = "" switch(sustain_mode) if(SUSTAIN_LINEAR) smt = "Linear" - modetext = "Linear Sustain Duration: [sustain_linear_duration / 10] seconds
" + modetext = "Linear Sustain Duration: [sustain_linear_duration / 10] seconds
" if(SUSTAIN_EXPONENTIAL) smt = "Exponential" - modetext = "Exponential Falloff Factor: [sustain_exponential_dropoff]% per decisecond
" - . += "Sustain Mode: [smt]
" + modetext = "Exponential Falloff Factor: [sustain_exponential_dropoff]% per decisecond
" + . += "Sustain Mode: [smt]
" . += modetext . += using_instrument?.ready()? "Status: [span_good("Ready")]
" : "Status: [span_bad("!Instrument Definition Error!")]
" . += "Instrument Type: [legacy? "Legacy" : "Synthesized"]
" - . += "Volume: [volume]
" - . += "Volume Dropoff Threshold: [sustain_dropoff_volume]
" - . += "Sustain indefinitely last held note: [full_sustain_held_note? "Enabled" : "Disabled"].
" + . += "Volume: [volume]
" + . += "Volume Dropoff Threshold: [sustain_dropoff_volume]
" + . += "Sustain indefinitely last held note: [full_sustain_held_note? "Enabled" : "Disabled"].
" . += "
" /datum/song/ui_interact(mob/user) @@ -38,31 +38,31 @@ if(lines.len > 0) dat += "

Playback

" if(!playing) - dat += "Play [span_linkOn("Stop")]

" + dat += "Play [span_linkOn("Stop")]

" dat += "Repeat Song: " - dat += repeat > 0 ? "--" : "[span_linkOff("-")][span_linkOff("-")]" + dat += repeat > 0 ? "--" : "[span_linkOff("-")][span_linkOff("-")]" dat += " [repeat] times " - dat += repeat < max_repeats ? "++" : "[span_linkOff("+")][span_linkOff("+")]" + dat += repeat < max_repeats ? "++" : "[span_linkOff("+")][span_linkOff("+")]" dat += "
" else - dat += "[span_linkOn("Play")] Stop
" + dat += "[span_linkOn("Play")] Stop
" dat += "Repeats left: [repeat]
" if(!editing) - dat += "
Show Editor
" + dat += "
Show Editor
" else dat += "

Editing

" - dat += "Hide Editor" - dat += " Start a New Song" - dat += " Import a Song

" + dat += "Hide Editor" + dat += " Start a New Song" + dat += " Import a Song

" var/bpm = round(600 / tempo) - dat += "Tempo: - [bpm] BPM +

" + dat += "Tempo: - [bpm] BPM +

" var/linecount = 0 for(var/line in lines) linecount += 1 - dat += "Line [linecount]: Edit X [line]
" - dat += "Add Line

" + dat += "Line [linecount]: Edit X [line]
" + dat += "Add Line

" if(help) - dat += "Hide Help
" + dat += "Hide Help
" dat += {" Lines are a series of chords, separated by commas (,), each with notes separated by hyphens (-).
Every note in a chord will play together, with chord timed by the tempo.
@@ -81,7 +81,7 @@ A song may only contain up to [MUSIC_MAXLINES] lines.
"} else - dat += "Show Help
" + dat += "Show Help
" var/datum/browser/popup = new(user, "instrument", parent?.name || "instrument", 700, 500) popup.set_content(dat.Join("")) diff --git a/code/modules/mining/lavaland/necropolis_chests.dm b/code/modules/mining/lavaland/necropolis_chests.dm index d3512956b2520..62f1b128b4040 100644 --- a/code/modules/mining/lavaland/necropolis_chests.dm +++ b/code/modules/mining/lavaland/necropolis_chests.dm @@ -1018,7 +1018,7 @@ GLOBAL_LIST_EMPTY(aide_list) to_chat(user, "You call out for aid, attempting to summon spirits to your side.") notify_ghosts("[user] is raising [user.p_their()] [src], calling for your help!", - enter_link="(Click to help)", + enter_link="(Click to help)", source = user, action=NOTIFY_ORBIT, ignore_key = POLL_IGNORE_SPECTRAL_BLADE) summon_cooldown = world.time + 600 diff --git a/code/modules/mining/machine_processing.dm b/code/modules/mining/machine_processing.dm index a1694474e173d..c9d5818f6f738 100644 --- a/code/modules/mining/machine_processing.dm +++ b/code/modules/mining/machine_processing.dm @@ -118,7 +118,7 @@ if (selected_material == M) dat += " Smelting" else - dat += " Not Smelting " + dat += " Not Smelting " dat += "
" dat += "

" @@ -130,16 +130,16 @@ if (selected_alloy == D.id) dat += " Smelting" else - dat += " Not Smelting " + dat += " Not Smelting " dat += "
" dat += "

" //On or off dat += "Machine is currently " if (on) - dat += "On " + dat += "On " else - dat += "Off " + dat += "Off " return dat diff --git a/code/modules/mining/machine_silo.dm b/code/modules/mining/machine_silo.dm index 595918f80ae4a..8d3832c88bfdd 100644 --- a/code/modules/mining/machine_silo.dm +++ b/code/modules/mining/machine_silo.dm @@ -99,11 +99,11 @@ GLOBAL_LIST_EMPTY(silo_access_logs) var/ref = REF(M) if (sheets) if (sheets >= 1) - ui += "Eject" + ui += "Eject" else ui += span_linkOff("Eject") if (sheets >= 20) - ui += "20x" + ui += "20x" else ui += span_linkOff("20x") ui += "[mat.name]: [sheets] sheets
" @@ -116,8 +116,8 @@ GLOBAL_LIST_EMPTY(silo_access_logs) var/datum/component/remote_materials/mats = C var/atom/parent = mats.parent var/hold_key = "[get_area(parent)]/[mats.category]" - ui += "Remove" - ui += "[holds[hold_key] ? "Allow" : "Hold"]" + ui += "Remove" + ui += "[holds[hold_key] ? "Allow" : "Hold"]" ui += " [parent.name] in [get_area_name(parent, TRUE)]
" if(!connected.len) ui += "Nothing!" @@ -132,7 +132,7 @@ GLOBAL_LIST_EMPTY(silo_access_logs) if(i == page) ui += span_linkOff("[i]") else - ui += "[i]" + ui += "[i]" ui += "
    " any = FALSE diff --git a/code/modules/mining/machine_stacking.dm b/code/modules/mining/machine_stacking.dm index 8f349a6dc9078..136900683a784 100644 --- a/code/modules/mining/machine_stacking.dm +++ b/code/modules/mining/machine_stacking.dm @@ -31,7 +31,7 @@ for(var/O in machine.stack_list) s = machine.stack_list[O] if(s.amount > 0) - dat += text("[capitalize(s.name)]: [s.amount] Release
    ") + dat += text("[capitalize(s.name)]: [s.amount] Release
    ") dat += text("
    Stacking: [machine.stack_amt]

    ") diff --git a/code/modules/mining/mint.dm b/code/modules/mining/mint.dm index 917a36585b4f5..496d438181608 100644 --- a/code/modules/mining/mint.dm +++ b/code/modules/mining/mint.dm @@ -41,20 +41,20 @@ if (chosen == M) dat += "Chosen" else - dat += "Choose" + dat += "Choose" var/datum/material/M = chosen dat += "

    Will produce [coinsToProduce] [lowertext(M.name)] coins if enough materials are available.
    " - dat += "-10 " - dat += "-5 " - dat += "-1 " - dat += "+1 " - dat += "+5 " - dat += "+10 " + dat += "-10 " + dat += "-5 " + dat += "-1 " + dat += "+1 " + dat += "+5 " + dat += "+10 " dat += "

    In total this machine produced [newCoins] coins." - dat += "
    Make coins" + dat += "
    Make coins" user << browse(dat, "window=mint") /obj/machinery/mineral/mint/Topic(href, href_list) diff --git a/code/modules/mob/dead/new_player/poll.dm b/code/modules/mob/dead/new_player/poll.dm index 3edf0f22f1703..9642add1fac90 100644 --- a/code/modules/mob/dead/new_player/poll.dm +++ b/code/modules/mob/dead/new_player/poll.dm @@ -24,7 +24,7 @@ qdel(query_poll_get) output += "" if(!QDELETED(src)) - src << browse(output,"window=playerpolllist;size=500x300") + src << browse(HTML_SKELETON(output),"window=playerpolllist;size=500x300") /mob/dead/new_player/proc/poll_player(pollid) if(!pollid) @@ -119,8 +119,8 @@ output += "" output += "" - src << browse(null ,"window=playerpolllist") - src << browse(output,"window=playerpoll;size=500x500") + src << browse(null, "window=playerpolllist") + src << browse(HTML_SKELETON(jointext(output, "")), "window=playerpoll;size=500x500") if(POLLTYPE_RATING) var/datum/DBQuery/query_rating_get_votes = SSdbcore.NewQuery("SELECT o.text, v.rating FROM [format_table_name("poll_option")] o, [format_table_name("poll_vote")] v WHERE o.pollid = :pollid AND v.ckey = :ckey AND o.id = v.optionid", list("pollid" = pollid, "ckey" = ckey)) if(!query_rating_get_votes.warn_execute()) @@ -177,8 +177,8 @@ output += "

    " output += "" if(!QDELETED(src)) - src << browse(null ,"window=playerpolllist") - src << browse(output,"window=playerpoll;size=500x500") + src << browse(null, "window=playerpolllist") + src << browse(HTML_SKELETON(jointext(output, "")), "window=playerpoll;size=500x500") if(POLLTYPE_MULTI) var/datum/DBQuery/query_multi_get_votes = SSdbcore.NewQuery("SELECT optionid FROM [format_table_name("poll_vote")] WHERE pollid = :pollid AND ckey = :ckey", list("pollid" = pollid, "ckey" = ckey)) if(!query_multi_get_votes.warn_execute()) @@ -229,8 +229,8 @@ if(!votedfor.len) output += "

    " output += "" - src << browse(null ,"window=playerpolllist") - src << browse(output,"window=playerpoll;size=500x250") + src << browse(null, "window=playerpolllist") + src << browse(HTML_SKELETON(jointext(output, "")), "window=playerpoll;size=500x250") if(POLLTYPE_IRV) var/datum/asset/irv_assets = get_asset_datum(/datum/asset/group/irv) irv_assets.send(src) diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index 6e3cdacd213ca..fa280af82ef1e 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -438,7 +438,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp A.add_overlay(source) source.layer = old_layer source.plane = old_plane - to_chat(src, span_ghostalert("(Click to re-enter)")) + to_chat(src, span_ghostalert("(Click to re-enter)")) if(sound) SEND_SOUND(src, sound(sound)) diff --git a/code/modules/mob/living/brain/posibrain.dm b/code/modules/mob/living/brain/posibrain.dm index 4083c01950e1c..aa18556f7762c 100644 --- a/code/modules/mob/living/brain/posibrain.dm +++ b/code/modules/mob/living/brain/posibrain.dm @@ -38,7 +38,7 @@ GLOBAL_VAR(posibrain_notify_cooldown) /obj/item/mmi/posibrain/proc/ping_ghosts(msg, newlymade) if(newlymade || GLOB.posibrain_notify_cooldown <= world.time) - notify_ghosts("[name] [msg] in [get_area(src)]!", ghost_sound = !newlymade ? 'sound/effects/ghost2.ogg':null, notify_volume = 75, enter_link = "(Click to enter)", source = src, action = NOTIFY_ATTACKORBIT, flashwindow = FALSE, ignore_key = POLL_IGNORE_POSIBRAIN, notify_suiciders = FALSE) + notify_ghosts("[name] [msg] in [get_area(src)]!", ghost_sound = !newlymade ? 'sound/effects/ghost2.ogg':null, notify_volume = 75, enter_link = "(Click to enter)", source = src, action = NOTIFY_ATTACKORBIT, flashwindow = FALSE, ignore_key = POLL_IGNORE_POSIBRAIN, notify_suiciders = FALSE) if(!newlymade) GLOB.posibrain_notify_cooldown = world.time + askDelay diff --git a/code/modules/mob/living/carbon/alien/humanoid/alien_powers.dm b/code/modules/mob/living/carbon/alien/humanoid/alien_powers.dm index 6c921a663a5e2..77d6b6f20e286 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/alien_powers.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/alien_powers.dm @@ -272,23 +272,23 @@ Doesn't work on other aliens/AI.*/ // We do this in InterceptClickOn() instead of Activate() // because we use the click parameters for aiming the projectile // (or something like that) -/datum/action/cooldown/alien/acid/neurotoxin/InterceptClickOn(mob/living/caller, params, atom/target) +/datum/action/cooldown/alien/acid/neurotoxin/InterceptClickOn(mob/living/caller_but_not_a_byond_built_in_proc, params, atom/target) . = ..() if(!.) - unset_click_ability(caller, refund_cooldown = FALSE) + unset_click_ability(caller_but_not_a_byond_built_in_proc, refund_cooldown = FALSE) return FALSE // var/modifiers = params2list(params) - caller.visible_message( - span_danger("[caller] spits neurotoxin!"), + caller_but_not_a_byond_built_in_proc.visible_message( + span_danger("[caller_but_not_a_byond_built_in_proc] spits neurotoxin!"), span_alertalien("You spit neurotoxin."), ) - var/obj/projectile/reagent/neurotoxin/neurotoxin = new /obj/projectile/reagent/neurotoxin(caller.loc) - neurotoxin.preparePixelProjectile(target, caller, params) - neurotoxin.firer = caller + var/obj/projectile/reagent/neurotoxin/neurotoxin = new /obj/projectile/reagent/neurotoxin(caller_but_not_a_byond_built_in_proc.loc) + neurotoxin.preparePixelProjectile(target, caller_but_not_a_byond_built_in_proc, params) + neurotoxin.firer = caller_but_not_a_byond_built_in_proc neurotoxin.fire() - caller.newtonian_move(get_dir(target, caller)) + caller_but_not_a_byond_built_in_proc.newtonian_move(get_dir(target, caller_but_not_a_byond_built_in_proc)) return TRUE // Has to return TRUE, otherwise is skipped. diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm index 30cf05bbfea4e..c989ff2812025 100644 --- a/code/modules/mob/living/carbon/human/examine.dm +++ b/code/modules/mob/living/carbon/human/examine.dm @@ -449,7 +449,7 @@ if(perpname) var/datum/data/record/R = find_record("name", perpname, GLOB.data_core.general) if(R) - . += "[span_deptradio("Rank:")] [R.fields["rank"]]\n\[Front photo\]\[Side photo\]" + . += "[span_deptradio("Rank:")] [R.fields["rank"]]\n\[Front photo\]\[Side photo\]" if(istype(H.glasses, /obj/item/clothing/glasses/hud/health) || istype(CIH, /obj/item/organ/cyberimp/eyes/hud/medical)) var/cyberimp_detect for(var/obj/item/organ/cyberimp/CI in internal_organs) @@ -460,12 +460,12 @@ . += cyberimp_detect if(R) var/health_r = R.fields["p_stat"] - . += "\[[health_r]\]" + . += "\[[health_r]\]" health_r = R.fields["m_stat"] - . += "\[[health_r]\]" + . += "\[[health_r]\]" R = find_record("name", perpname, GLOB.data_core.medical) if(R) - . += "\[Medical evaluation\]
    " + . += "\[Medical evaluation\]
    " if(traitstring) . += "Detected physiological traits:\n[traitstring]" @@ -478,11 +478,11 @@ if(R) criminal = R.fields["criminal"] - . += "[span_deptradio("Criminal status:")] \[[criminal]\]" - . += jointext(list("[span_deptradio("Security record:")] \[View\]", - "\[Add crime\]", - "\[View comment log\]", - "\[Add comment\]"), "") + . += "[span_deptradio("Criminal status:")] \[[criminal]\]" + . += jointext(list("[span_deptradio("Security record:")] \[View\]", + "\[Add crime\]", + "\[View comment log\]", + "\[Add comment\]"), "") else if(isobserver(user) && traitstring) . += "Quirks: [traitstring]
    " . += "
    " @@ -499,9 +499,9 @@ else if (HAS_TRAIT(src, TRAIT_DISFIGURED)) //can't identify disfigured flavor_text_link = span_notice("This person has been horribly disfigured, and is unrecognizable!") else if (face_obscured || HAS_TRAIT(src, TRAIT_DISGUISED)) //won't print flavour text of hidden - flavor_text_link = span_notice("\[Examine closely...\]") + flavor_text_link = span_notice("\[Examine closely...\]") else //do it normally - flavor_text_link = span_notice("[preview_text]... \[Look closer?\]") + flavor_text_link = span_notice("[preview_text]... \[Look closer?\]") if (flavor_text_link) . += flavor_text_link diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index b99da96618436..d84b4018e7b37 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -743,7 +743,7 @@ to_chat(src, msg) for(var/obj/item/I in LB.embedded_objects) - combined_msg += "\t There is \a [I] embedded in your [LB.name]!" + combined_msg += "\t There is \a [I] embedded in your [LB.name]!" for(var/t in missing) combined_msg += span_boldannounce("Your [parse_zone(t)] is missing!") diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 7ca700817064d..dc41ae1686e8c 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -1547,15 +1547,15 @@ GLOBAL_LIST_EMPTY(fire_appearances) . = ..() var/refid = REF(src) . += {" -
    [ckey || "No ckey"] / [VV_HREF_TARGETREF_1V(refid, VV_HK_BASIC_EDIT, "[real_name || "no real name"]", NAMEOF(src, real_name))] +
    [ckey || "No ckey"] / [VV_HREF_TARGETREF_1V(refid, VV_HK_BASIC_EDIT, "[real_name || "no real name"]", NAMEOF(src, real_name))]
    - BRUTE:[getBruteLoss()] - BURN:[getFireLoss()] - TOXIN:[getToxLoss()] - OXY:[getOxyLoss()] - CLONE:[getCloneLoss()] - BRAIN:[getOrganLoss(ORGAN_SLOT_BRAIN)] - STAMINA:[getStaminaLoss()] + BRUTE:[getBruteLoss()] + BURN:[getFireLoss()] + TOXIN:[getToxLoss()] + OXY:[getOxyLoss()] + CLONE:[getCloneLoss()] + BRAIN:[getOrganLoss(ORGAN_SLOT_BRAIN)] + STAMINA:[getStaminaLoss()] "} diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm index a8eb6006dfe0c..3eae0db86322d 100644 --- a/code/modules/mob/living/say.dm +++ b/code/modules/mob/living/say.dm @@ -261,6 +261,7 @@ GLOBAL_LIST_INIT(special_radio_keys, list( else deaf_message = span_notice("You can't hear yourself!") deaf_type = 2 // Since you should be able to hear yourself without looking + // Create map text prior to modifying message for goonchat if (client?.prefs.read_preference(/datum/preference/toggle/enable_runechat) && stat != UNCONSCIOUS && (client.prefs.read_preference(/datum/preference/toggle/enable_runechat_non_mobs) || ismob(speaker)) && can_hear()) create_chat_message(speaker, message_language, raw_message, spans) diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index b581e4b1ffe62..a1b8d5cb947f8 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -336,7 +336,7 @@ /mob/living/silicon/ai/proc/ai_alerts() var/dat = "Current Station Alerts\n" - dat += "Close

    " + dat += "Close

    " for (var/cat in alarms) dat += text("[]
    \n", cat) var/list/L = alarms[cat] @@ -350,11 +350,11 @@ if (C && istype(C, /list)) var/dat2 = "" for (var/obj/machinery/camera/I in C) - dat2 += text("[][]", (dat2=="") ? "" : " | ", I.c_tag) + dat2 += text("[][]", (dat2=="") ? "" : " | ", I.c_tag) dat += text("-- [] ([])", A.name, (dat2!="") ? dat2 : "No Camera") else if (C && istype(C, /obj/machinery/camera)) var/obj/machinery/camera/Ctmp = C - dat += text("-- [] ([])", A.name, Ctmp.c_tag) + dat += text("-- [] ([])", A.name, Ctmp.c_tag) else dat += text("-- [] (No Camera)", A.name) if (sources.len > 1) @@ -659,12 +659,12 @@ L[A.name] = list(A, (C) ? C : O, list(alarmsource)) if (O) if (C && C.can_use()) - queueAlarm("--- [class] alarm detected in [A.name]! ([C.c_tag])", class) + queueAlarm("--- [class] alarm detected in [A.name]! ([C.c_tag])", class) else if (CL && CL.len) var/foo = 0 var/dat2 = "" for (var/obj/machinery/camera/I in CL) - dat2 += text("[][]", (!foo) ? "" : " | ", I.c_tag) //I'm not fixing this shit... + dat2 += text("[][]", (!foo) ? "" : " | ", I.c_tag) //I'm not fixing this shit... foo = 1 queueAlarm(text ("--- [] alarm detected in []! ([])", class, A.name, dat2), class) else @@ -962,7 +962,7 @@ var/treated_message = lang_treat(speaker, message_language, raw_message, spans, message_mods) var/start = "Relayed Speech: " var/namepart = "[speaker.GetVoice()][speaker.get_alt_name()]" - var/hrefpart = "" + var/hrefpart = "" var/jobpart = "Unknown" if(istype(speaker, /obj/effect/overlay/holo_pad_hologram)) diff --git a/code/modules/mob/living/silicon/ai/decentralized/ai_data_core.dm b/code/modules/mob/living/silicon/ai/decentralized/ai_data_core.dm index dd9d00f50ca97..2f8b067a5fb74 100644 --- a/code/modules/mob/living/silicon/ai/decentralized/ai_data_core.dm +++ b/code/modules/mob/living/silicon/ai/decentralized/ai_data_core.dm @@ -107,7 +107,7 @@ GLOBAL_VAR_INIT(primary_data_core, null) if(!AI.mind && AI.deployed_shell.mind) to_chat(AI.deployed_shell, span_userdanger("Data core in [get_area(src)] is on the verge of failing! Immediate action required to prevent failure.")) else - to_chat(AI, span_userdanger("Data core in [get_area(src)] is on the verge of failing! Immediate action required to prevent failure.")) + to_chat(AI, span_userdanger("Data core in [get_area(src)] is on the verge of failing! Immediate action required to prevent failure.")) AI.playsound_local(AI, 'sound/machines/engine_alert2.ogg', 30) if(!(stat & (BROKEN|EMPED)) && has_power() && !disableheat) @@ -135,7 +135,7 @@ GLOBAL_VAR_INIT(primary_data_core, null) if(!AI.mind && AI.deployed_shell && AI.deployed_shell.mind) to_chat(AI.deployed_shell, span_userdanger("Warning! Data Core brought offline in [get_area(src)]! Please verify that no malicious actions were taken.")) else - to_chat(AI, span_userdanger("Warning! Data Core brought offline in [get_area(src)]! Please verify that no malicious actions were taken.")) + to_chat(AI, span_userdanger("Warning! Data Core brought offline in [get_area(src)]! Please verify that no malicious actions were taken.")) disconnect_from_ai_network() @@ -270,7 +270,7 @@ GLOBAL_VAR_INIT(primary_data_core, null) if(!AI.mind && AI.deployed_shell.mind) to_chat(AI.deployed_shell, span_userdanger("Data core in [get_area(src)] is on the verge of failing! Immediate action required to prevent failure.")) else - to_chat(AI, span_userdanger("Data core in [get_area(src)] is on the verge of failing! Immediate action required to prevent failure.")) + to_chat(AI, span_userdanger("Data core in [get_area(src)] is on the verge of failing! Immediate action required to prevent failure.")) AI.playsound_local(AI, 'sound/machines/engine_alert2.ogg', 30) diff --git a/code/modules/mob/living/silicon/ai/decentralized/projects/cyborg_management.dm b/code/modules/mob/living/silicon/ai/decentralized/projects/cyborg_management.dm index 7a88062bb1b03..4bceb3d2f6ae2 100644 --- a/code/modules/mob/living/silicon/ai/decentralized/projects/cyborg_management.dm +++ b/code/modules/mob/living/silicon/ai/decentralized/projects/cyborg_management.dm @@ -31,15 +31,15 @@ enable_text = span_notice("You mimick and prepare to send one-time code used by robotics consoles. Click an active connected cyborg to unlock them.") disable_text = span_notice("You decide not to send the one-time code.") -/datum/action/innate/ai/ranged/remote_unlock/do_ability(mob/living/caller, params, atom/clicked_on) +/datum/action/innate/ai/ranged/remote_unlock/do_ability(mob/living/caller_but_not_a_byond_built_in_proc, params, atom/clicked_on) if(!iscyborg(clicked_on)) to_chat(owner, span_warning("You can only unlock cyborgs!")) return FALSE - if(!isAI(caller)) + if(!isAI(caller_but_not_a_byond_built_in_proc)) CRASH("Non-AI has /remote_unlock ability!") var/mob/living/silicon/robot/cyborg = clicked_on - var/mob/living/silicon/ai/ai = caller + var/mob/living/silicon/ai/ai = caller_but_not_a_byond_built_in_proc if(cyborg.stat == DEAD) to_chat(ai, span_warning("You cannot unlock dead cyborgs!")) return FALSE @@ -84,15 +84,15 @@ enable_text = span_notice("You prepare to upload and run to a special program to a cyborg. Click an active connected cyborg to reset them.") disable_text = span_notice("You decide not to upload the program.") -/datum/action/innate/ai/ranged/remote_reset/do_ability(mob/living/caller, params, atom/clicked_on) +/datum/action/innate/ai/ranged/remote_reset/do_ability(mob/living/caller_but_not_a_byond_built_in_proc, params, atom/clicked_on) if(!iscyborg(clicked_on)) to_chat(owner, span_warning("You can only reset cyborgs!")) return FALSE - if(!isAI(caller)) + if(!isAI(caller_but_not_a_byond_built_in_proc)) CRASH("Non-AI has /remote_reset ability!") var/mob/living/silicon/robot/cyborg = clicked_on - var/mob/living/silicon/ai/ai = caller + var/mob/living/silicon/ai/ai = caller_but_not_a_byond_built_in_proc if(cyborg.stat == DEAD) to_chat(ai, span_warning("You cannot reset dead cyborgs!")) return FALSE diff --git a/code/modules/mob/living/silicon/ai/decentralized/projects/induction.dm b/code/modules/mob/living/silicon/ai/decentralized/projects/induction.dm index 4640cba5b3e75..71a54ee66bd7d 100644 --- a/code/modules/mob/living/silicon/ai/decentralized/projects/induction.dm +++ b/code/modules/mob/living/silicon/ai/decentralized/projects/induction.dm @@ -69,7 +69,7 @@ enable_text = span_notice("You prepare bluespace induction coils. Click a borg or APC to charge its cell by 33%") disable_text = span_notice("You power down your induction coils.") -/datum/action/innate/ai/ranged/charge_borg_or_apc/do_ability(mob/living/caller, params, atom/clicked_on) +/datum/action/innate/ai/ranged/charge_borg_or_apc/do_ability(mob/living/caller_but_not_a_byond_built_in_proc, params, atom/clicked_on) if(!iscyborg(clicked_on) && !istype(clicked_on, /obj/machinery/power/apc)) to_chat(owner, span_warning("You can only charge cyborgs or APCs!")) return FALSE diff --git a/code/modules/mob/living/silicon/ai/decentralized/projects/room_lockdown.dm b/code/modules/mob/living/silicon/ai/decentralized/projects/room_lockdown.dm index 9e402d5424bda..8e70e16de3129 100644 --- a/code/modules/mob/living/silicon/ai/decentralized/projects/room_lockdown.dm +++ b/code/modules/mob/living/silicon/ai/decentralized/projects/room_lockdown.dm @@ -48,7 +48,7 @@ -/datum/action/innate/ai/ranged/room_lockdown/do_ability(mob/living/caller, params, atom/target) +/datum/action/innate/ai/ranged/room_lockdown/do_ability(mob/living/caller_but_not_a_byond_built_in_proc, params, atom/target) var/area/A = get_area(target) if(!A) to_chat(owner, span_warning("No area detected!")) @@ -61,7 +61,7 @@ if(lock_room(A)) adjust_uses(-1) to_chat(owner, span_notice("You lock [A].")) - unset_ranged_ability(caller) + unset_ranged_ability(caller_but_not_a_byond_built_in_proc) return TRUE diff --git a/code/modules/mob/living/silicon/ai/life.dm b/code/modules/mob/living/silicon/ai/life.dm index 7c0162a5b06f1..1ba62afdd0943 100644 --- a/code/modules/mob/living/silicon/ai/life.dm +++ b/code/modules/mob/living/silicon/ai/life.dm @@ -176,7 +176,7 @@ sleep(5 SECONDS) to_chat(src, "Receiving control information from APC.") sleep(0.2 SECONDS) - to_chat(src, "APC ready for connection.") + to_chat(src, "APC ready for connection.") apc_override = theAPC apc_override.ui_interact(src) aiRestorePowerRoutine = POWER_RESTORATION_APC_FOUND diff --git a/code/modules/mob/living/silicon/ai/login.dm b/code/modules/mob/living/silicon/ai/login.dm index 03989573fbd4b..225c8afc42328 100644 --- a/code/modules/mob/living/silicon/ai/login.dm +++ b/code/modules/mob/living/silicon/ai/login.dm @@ -9,7 +9,7 @@ O.emotion = "Neutral" O.update() if(lacks_power() && apc_override) //Placing this in Login() in case the AI doesn't have this link for whatever reason. - to_chat(usr, "Main power is unavailable, backup power in use. Diagnostics scan complete. Local APC ready for connection.") + to_chat(usr, "Main power is unavailable, backup power in use. Diagnostics scan complete. Local APC ready for connection.") set_eyeobj_visible(TRUE) if(multicam_on) end_multicam() diff --git a/code/modules/mob/living/silicon/ai/say.dm b/code/modules/mob/living/silicon/ai/say.dm index d92be393568c4..c8c4f406d7ad4 100644 --- a/code/modules/mob/living/silicon/ai/say.dm +++ b/code/modules/mob/living/silicon/ai/say.dm @@ -7,7 +7,7 @@ /mob/living/silicon/ai/compose_track_href(atom/movable/speaker, namepart) var/M = speaker.GetJob() if(M) - return "" + return "" return "" /mob/living/silicon/ai/compose_job(atom/movable/speaker, message_langs, raw_message, radio_freq) @@ -77,7 +77,7 @@ var/index = 0 for(var/word in GLOB.vox_sounds) index++ - dat += "[capitalize(word)]" + dat += "[capitalize(word)]" if(index != GLOB.vox_sounds.len) dat += " / " diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index 72c1ac409ebde..b488dab627ed0 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -979,13 +979,13 @@ return switch(notifytype) if(NEW_BORG) //New Cyborg - to_chat(connected_ai, "

    NOTICE - New cyborg connection detected: [name]
    ") + to_chat(connected_ai, "

    NOTICE - New cyborg connection detected: [name]
    ") if(NEW_MODULE) //New Module to_chat(connected_ai, "

    [span_notice("NOTICE - Cyborg module change detected: [name] has loaded the [designation] module.")]
    ") if(RENAME) //New Name to_chat(connected_ai, "

    [span_notice("NOTICE - Cyborg reclassification detected: [oldname] is now designated as [newname].")]
    ") if(AI_SHELL) //New Shell - to_chat(connected_ai, "

    NOTICE - New cyborg shell detected: [name]
    ") + to_chat(connected_ai, "

    NOTICE - New cyborg shell detected: [name]
    ") if(DISCONNECT) //Tampering with the wires to_chat(connected_ai, "

    [span_notice("NOTICE - Remote telemetry lost with [name].")]
    ") diff --git a/code/modules/mob/living/silicon/say.dm b/code/modules/mob/living/silicon/say.dm index 2dc27c7ff7ee1..efeff7a08c3c2 100644 --- a/code/modules/mob/living/silicon/say.dm +++ b/code/modules/mob/living/silicon/say.dm @@ -24,7 +24,7 @@ M, span_binarysay("\ Robotic Talk, \ - [span_name("[name] ([designation])")] \ + [span_name("[name] ([designation])")] \ [span_message("[quoted_message]")]\ ") ) diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm index 2beb6588a2166..59f8e5873826d 100644 --- a/code/modules/mob/living/silicon/silicon.dm +++ b/code/modules/mob/living/silicon/silicon.dm @@ -121,7 +121,7 @@ if(alarm_types_show["Camera"]) msg += "CAMERA: [alarm_types_show["Camera"]] alarms detected. - " - msg += "\[Show Alerts\]" + msg += "\[Show Alerts\]" to_chat(src, msg) if(alarms_to_clear.len < 3) @@ -146,7 +146,7 @@ if(alarm_types_show["Camera"]) msg += "CAMERA: [alarm_types_clear["Camera"]] alarms cleared. - " - msg += "\[Show Alerts\]" + msg += "\[Show Alerts\]" to_chat(src, msg) diff --git a/code/modules/mob/living/simple_animal/bot/atmosbot.dm b/code/modules/mob/living/simple_animal/bot/atmosbot.dm index 08e32d7847c9b..6e98c1bc53f75 100644 --- a/code/modules/mob/living/simple_animal/bot/atmosbot.dm +++ b/code/modules/mob/living/simple_animal/bot/atmosbot.dm @@ -177,11 +177,11 @@ dat += hack(user) dat += showpai(user) dat += "Atmospheric Stabalizer Controls v1.1

    " - dat += "Status: [on ? "On" : "Off"]
    " + dat += "Status: [on ? "On" : "Off"]
    " dat += "Maintenance panel panel is [open ? "opened" : "closed"]
    " if(!locked || issilicon(user) || IsAdminGhost(user)) - dat += "Breach Pressure: [breached_pressure]
    " - dat += "Patrol Station: [auto_patrol ? "Yes" : "No"]
    " + dat += "Breach Pressure: [breached_pressure]
    " + dat += "Patrol Station: [auto_patrol ? "Yes" : "No"]
    " return dat /mob/living/simple_animal/bot/atmosbot/Topic(href, href_list) diff --git a/code/modules/mob/living/simple_animal/bot/bot.dm b/code/modules/mob/living/simple_animal/bot/bot.dm index e8c0aa51fc65b..48e2c71ce42c7 100644 --- a/code/modules/mob/living/simple_animal/bot/bot.dm +++ b/code/modules/mob/living/simple_animal/bot/bot.dm @@ -535,7 +535,7 @@ Pass a positive integer as an argument to override a bot's default speed. if(mode != BOT_SUMMON && mode != BOT_RESPONDING) access_card.access = prev_access -/mob/living/simple_animal/bot/proc/call_bot(caller, turf/waypoint, message=TRUE) +/mob/living/simple_animal/bot/proc/call_bot(caller_but_not_a_byond_built_in_proc, turf/waypoint, message=TRUE) bot_reset() //Reset a bot before setting it to call mode. //For giving the bot temporary all-access. @@ -544,7 +544,7 @@ Pass a positive integer as an argument to override a bot's default speed. all_access.access = All.get_access() set_path(get_path_to(src, waypoint, /turf/proc/Distance_cardinal, 0, 200, id=all_access)) - calling_ai = caller //Link the AI to the bot! + calling_ai = caller_but_not_a_byond_built_in_proc //Link the AI to the bot! ai_waypoint = waypoint if(path && path.len) //Ensures that a valid path is calculated! @@ -554,7 +554,7 @@ Pass a positive integer as an argument to override a bot's default speed. access_card = all_access //Give the bot all-access while under the AI's command. if(client) reset_access_timer_id = addtimer(CALLBACK (src, PROC_REF(bot_reset)), 600, TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_STOPPABLE) //if the bot is player controlled, they get the extra access for a limited time - to_chat(src, span_notice("Priority waypoint set by [icon2html(calling_ai, src)] [caller]. Proceed to [end_area].
    [path.len-1] meters to destination. You have been granted additional door access for 60 seconds.")) + to_chat(src, span_notice("Priority waypoint set by [icon2html(calling_ai, src)] [caller_but_not_a_byond_built_in_proc]. Proceed to [end_area].
    [path.len-1] meters to destination. You have been granted additional door access for 60 seconds.")) if(message) to_chat(calling_ai, span_notice("[icon2html(src, calling_ai)] [name] called to [end_area]. [path.len-1] meters to destination.")) pathset = 1 @@ -899,9 +899,9 @@ Pass a positive integer as an argument to override a bot's default speed. var/hack if(issilicon(user) || IsAdminGhost(user)) //Allows silicons or admins to toggle the emag status of a bot. hack += "[emagged == 2 ? "Software compromised! Unit may exhibit dangerous or erratic behavior." : "Unit operating normally. Release safety lock?"]
    " - hack += "Harm Prevention Safety System: [emagged ? span_bad("DANGER") : "Engaged"]
    " + hack += "Harm Prevention Safety System: [emagged ? span_bad("DANGER") : "Engaged"]
    " else if(!locked) //Humans with access can use this option to hide a bot from the AI's remote control panel and PDA control. - hack += "Remote network control radio: [remote_disabled ? "Disconnected" : "Connected"]
    " + hack += "Remote network control radio: [remote_disabled ? "Disconnected" : "Connected"]
    " return hack /mob/living/simple_animal/bot/proc/showpai(mob/user) @@ -911,9 +911,9 @@ Pass a positive integer as an argument to override a bot's default speed. eject += "Personality card status: " if(paicard) if(client) - eject += "Active" + eject += "Active" else - eject += "Inactive" + eject += "Inactive" else if(!allow_pai || key) eject += "Unavailable" else diff --git a/code/modules/mob/living/simple_animal/bot/cleanbot.dm b/code/modules/mob/living/simple_animal/bot/cleanbot.dm index 155c20e1ffade..20d896fa96b68 100644 --- a/code/modules/mob/living/simple_animal/bot/cleanbot.dm +++ b/code/modules/mob/living/simple_animal/bot/cleanbot.dm @@ -283,15 +283,15 @@ dat += hack(user) dat += showpai(user) dat += text({" -Status: [on ? "On" : "Off"]
    +Status: [on ? "On" : "Off"]
    Behaviour controls are [locked ? "locked" : "unlocked"]
    Maintenance panel panel is [open ? "opened" : "closed"]"}) if(!locked || issilicon(user)|| IsAdminGhost(user)) - dat += "
    Clean Blood: [blood ? "Yes" : "No"]" - dat += "
    Clean Trash: [trash ? "Yes" : "No"]" - dat += "
    Clean Graffiti: [drawn ? "Yes" : "No"]" - dat += "
    Exterminate Pests: [pests ? "Yes" : "No"]" - dat += "

    Patrol Station: [auto_patrol ? "Yes" : "No"]" + dat += "
    Clean Blood: [blood ? "Yes" : "No"]" + dat += "
    Clean Trash: [trash ? "Yes" : "No"]" + dat += "
    Clean Graffiti: [drawn ? "Yes" : "No"]" + dat += "
    Exterminate Pests: [pests ? "Yes" : "No"]" + dat += "

    Patrol Station: [auto_patrol ? "Yes" : "No"]" return dat /mob/living/simple_animal/bot/cleanbot/Topic(href, href_list) diff --git a/code/modules/mob/living/simple_animal/bot/ed209bot.dm b/code/modules/mob/living/simple_animal/bot/ed209bot.dm index 815540628ebab..099bcc7e19914 100644 --- a/code/modules/mob/living/simple_animal/bot/ed209bot.dm +++ b/code/modules/mob/living/simple_animal/bot/ed209bot.dm @@ -106,7 +106,7 @@ Status: []
    Behaviour controls are [locked ? "locked" : "unlocked"]
    Maintenance panel panel is [open ? "opened" : "closed"]
    "}, -"[on ? "On" : "Off"]" ) +"[on ? "On" : "Off"]" ) if(!locked || issilicon(user)|| IsAdminGhost(user)) if(!lasercolor) @@ -119,12 +119,12 @@ Operating Mode: []
    Report Arrests[]
    Auto Patrol[]"}, -"[idcheck ? "Yes" : "No"]", -"[weaponscheck ? "Yes" : "No"]", -"[check_records ? "Yes" : "No"]", -"[arrest_type ? "Detain" : "Arrest"]", -"[declare_arrests ? "Yes" : "No"]", -"[auto_patrol ? "On" : "Off"]" ) +"[idcheck ? "Yes" : "No"]", +"[weaponscheck ? "Yes" : "No"]", +"[check_records ? "Yes" : "No"]", +"[arrest_type ? "Detain" : "Arrest"]", +"[declare_arrests ? "Yes" : "No"]", +"[auto_patrol ? "On" : "Off"]" ) return dat diff --git a/code/modules/mob/living/simple_animal/bot/firebot.dm b/code/modules/mob/living/simple_animal/bot/firebot.dm index 97244001df166..cbbcdf3e5a02e 100644 --- a/code/modules/mob/living/simple_animal/bot/firebot.dm +++ b/code/modules/mob/living/simple_animal/bot/firebot.dm @@ -107,15 +107,15 @@ dat += hack(user) dat += showpai(user) dat += "Mobile Fire Extinguisher v1.0

    " - dat += "Status: [on ? "On" : "Off"]
    " + dat += "Status: [on ? "On" : "Off"]
    " dat += "Maintenance panel panel is [open ? "opened" : "closed"]
    " dat += "Behaviour controls are [locked ? "locked" : "unlocked"]
    " if(!locked || issilicon(user) || IsAdminGhost(user)) - dat += "Extinguish Fires: [extinguish_fires ? "Yes" : "No"]
    " - dat += "Extinguish People: [extinguish_people ? "Yes" : "No"]
    " - dat += "Patrol Station: [auto_patrol ? "Yes" : "No"]
    " - dat += "Stationary Mode: [stationary_mode ? "Yes" : "No"]
    " + dat += "Extinguish Fires: [extinguish_fires ? "Yes" : "No"]
    " + dat += "Extinguish People: [extinguish_people ? "Yes" : "No"]
    " + dat += "Patrol Station: [auto_patrol ? "Yes" : "No"]
    " + dat += "Stationary Mode: [stationary_mode ? "Yes" : "No"]
    " return dat diff --git a/code/modules/mob/living/simple_animal/bot/floorbot.dm b/code/modules/mob/living/simple_animal/bot/floorbot.dm index 7798c48e080dc..ee09dd88b397a 100644 --- a/code/modules/mob/living/simple_animal/bot/floorbot.dm +++ b/code/modules/mob/living/simple_animal/bot/floorbot.dm @@ -79,28 +79,28 @@ dat += hack(user) dat += showpai(user) dat += "Floor Repairer Controls v1.1

    " - dat += "Status: [on ? "On" : "Off"]
    " + dat += "Status: [on ? "On" : "Off"]
    " dat += "Maintenance panel panel is [open ? "opened" : "closed"]
    " dat += "Special tiles: " if(specialtiles) - dat += "Loaded \[[specialtiles]/[maxtiles]\]
    " + dat += "Loaded \[[specialtiles]/[maxtiles]\]
    " else dat += "None Loaded
    " dat += "Behaviour controls are [locked ? "locked" : "unlocked"]
    " if(!locked || issilicon(user) || IsAdminGhost(user)) - dat += "Add tiles to new hull plating: [autotile ? "Yes" : "No"]
    " - dat += "Place floor tiles: [placetiles ? "Yes" : "No"]
    " - dat += "Replace existing floor tiles with custom tiles: [replacetiles ? "Yes" : "No"]
    " - dat += "Repair damaged tiles and platings: [fixfloors ? "Yes" : "No"]
    " - dat += "Traction Magnets: [anchored ? "Engaged" : "Disengaged"]
    " - dat += "Patrol Station: [auto_patrol ? "Yes" : "No"]
    " + dat += "Add tiles to new hull plating: [autotile ? "Yes" : "No"]
    " + dat += "Place floor tiles: [placetiles ? "Yes" : "No"]
    " + dat += "Replace existing floor tiles with custom tiles: [replacetiles ? "Yes" : "No"]
    " + dat += "Repair damaged tiles and platings: [fixfloors ? "Yes" : "No"]
    " + dat += "Traction Magnets: [anchored ? "Engaged" : "Disengaged"]
    " + dat += "Patrol Station: [auto_patrol ? "Yes" : "No"]
    " var/bmode if(targetdirection) bmode = dir2text(targetdirection) else bmode = "disabled" - dat += "Line Mode : [bmode]
    " + dat += "Line Mode : [bmode]
    " return dat diff --git a/code/modules/mob/living/simple_animal/bot/honkbot.dm b/code/modules/mob/living/simple_animal/bot/honkbot.dm index 67a37735b722f..8a29bbb004f3e 100644 --- a/code/modules/mob/living/simple_animal/bot/honkbot.dm +++ b/code/modules/mob/living/simple_animal/bot/honkbot.dm @@ -93,12 +93,12 @@ Status: []
    Behaviour controls are [locked ? "locked" : "unlocked"]
    Maintenance panel panel is [open ? "opened" : "closed"]"}, -"[on ? "On" : "Off"]" ) +"[on ? "On" : "Off"]" ) if(!locked || issilicon(user) || IsAdminGhost(user)) dat += text({"
    Auto Patrol: []"}, -"[auto_patrol ? "On" : "Off"]" ) +"[auto_patrol ? "On" : "Off"]" ) return dat /mob/living/simple_animal/bot/honkbot/proc/judgement_criteria() diff --git a/code/modules/mob/living/simple_animal/bot/medbot.dm b/code/modules/mob/living/simple_animal/bot/medbot.dm index dc7d3de3e42b9..5c67f89e812f4 100644 --- a/code/modules/mob/living/simple_animal/bot/medbot.dm +++ b/code/modules/mob/living/simple_animal/bot/medbot.dm @@ -156,37 +156,37 @@ dat += hack(user) dat += showpai(user) dat += "Medical Unit Controls v1.1

    " - dat += "Status: [on ? "On" : "Off"]
    " + dat += "Status: [on ? "On" : "Off"]
    " dat += "Maintenance panel panel is [open ? "opened" : "closed"]
    " dat += "Beaker: " if(reagent_glass) - dat += "Loaded \[[reagent_glass.reagents.total_volume]/[reagent_glass.reagents.maximum_volume]\]" + dat += "Loaded \[[reagent_glass.reagents.total_volume]/[reagent_glass.reagents.maximum_volume]\]" else dat += "None Loaded" dat += "
    Behaviour controls are [locked ? "locked" : "unlocked"]


    " if(!locked || issilicon(user) || IsAdminGhost(user)) dat += "Healing Threshold: " - dat += "-- " - dat += "- " + dat += "-- " + dat += "- " dat += "[heal_threshold] " - dat += "+ " - dat += "++" + dat += "+ " + dat += "++" dat += "
    " dat += "Injection Level: " - dat += "- " + dat += "- " dat += "[injection_amount] " - dat += "+ " + dat += "+ " dat += "
    " dat += "Reagent Source: " - dat += "[use_beaker ? "Loaded Beaker (When available)" : "Internal Synthesizer"]
    " + dat += "[use_beaker ? "Loaded Beaker (When available)" : "Internal Synthesizer"]
    " - dat += "Treat Viral Infections: [treat_virus ? "Yes" : "No"]
    " - dat += "The speaker switch is [shut_up ? "off" : "on"]. Toggle
    " - dat += "Critical Patient Alerts: [declare_crit ? "Yes" : "No"]
    " - dat += "Patrol Station: [auto_patrol ? "Yes" : "No"]
    " - dat += "Stationary Mode: [stationary_mode ? "Yes" : "No"]
    " + dat += "Treat Viral Infections: [treat_virus ? "Yes" : "No"]
    " + dat += "The speaker switch is [shut_up ? "off" : "on"]. Toggle
    " + dat += "Critical Patient Alerts: [declare_crit ? "Yes" : "No"]
    " + dat += "Patrol Station: [auto_patrol ? "Yes" : "No"]
    " + dat += "Stationary Mode: [stationary_mode ? "Yes" : "No"]
    " return dat diff --git a/code/modules/mob/living/simple_animal/bot/mulebot.dm b/code/modules/mob/living/simple_animal/bot/mulebot.dm index 9f43dd566ba19..5f667e83aa8b3 100644 --- a/code/modules/mob/living/simple_animal/bot/mulebot.dm +++ b/code/modules/mob/living/simple_animal/bot/mulebot.dm @@ -67,9 +67,13 @@ suffix = null /mob/living/simple_animal/bot/mulebot/Destroy() + if(!isnull(wires)) + QDEL_NULL(wires) unload(0) - qdel(wires) - wires = null + return ..() + +/mob/living/simple_animal/bot/mulebot/death(gibbed) + QDEL_NULL(wires) return ..() /mob/living/simple_animal/bot/mulebot/proc/set_id(new_id) diff --git a/code/modules/mob/living/simple_animal/bot/secbot.dm b/code/modules/mob/living/simple_animal/bot/secbot.dm index f377769ed6b2a..0d802336493d8 100644 --- a/code/modules/mob/living/simple_animal/bot/secbot.dm +++ b/code/modules/mob/living/simple_animal/bot/secbot.dm @@ -123,7 +123,7 @@ Status: []
    Behaviour controls are [locked ? "locked" : "unlocked"]
    Maintenance panel panel is [open ? "opened" : "closed"]"}, -"[on ? "On" : "Off"]" ) +"[on ? "On" : "Off"]" ) if(!locked || issilicon(user) || IsAdminGhost(user)) dat += text({"
    @@ -134,12 +134,12 @@ Operating Mode: []
    Report Arrests[]
    Auto Patrol: []"}, -"[idcheck ? "Yes" : "No"]", -"[weaponscheck ? "Yes" : "No"]", -"[check_records ? "Yes" : "No"]", -"[arrest_type ? "Detain" : "Arrest"]", -"[declare_arrests ? "Yes" : "No"]", -"[auto_patrol ? "On" : "Off"]" ) +"[idcheck ? "Yes" : "No"]", +"[weaponscheck ? "Yes" : "No"]", +"[check_records ? "Yes" : "No"]", +"[arrest_type ? "Detain" : "Arrest"]", +"[declare_arrests ? "Yes" : "No"]", +"[auto_patrol ? "On" : "Off"]" ) return dat diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm index e1c81eef9b309..66e1ff917c261 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm @@ -610,7 +610,7 @@ Difficulty: Very Hard if(..() && !ready_to_deploy) GLOB.poi_list |= src ready_to_deploy = TRUE - notify_ghosts("An anomalous crystal has been activated in [get_area(src)]! This crystal can always be used by ghosts hereafter.", enter_link = "(Click to enter)", ghost_sound = 'sound/effects/ghost2.ogg', source = src, action = NOTIFY_ATTACKORBIT) + notify_ghosts("An anomalous crystal has been activated in [get_area(src)]! This crystal can always be used by ghosts hereafter.", enter_link = "(Click to enter)", ghost_sound = 'sound/effects/ghost2.ogg', source = src, action = NOTIFY_ATTACKORBIT) /obj/machinery/anomalous_crystal/helpers/attack_ghost(mob/dead/observer/user) . = ..() diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/drakeling.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/drakeling.dm index db68f8e8659fd..96b7e575ccc95 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/drakeling.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/drakeling.dm @@ -123,7 +123,7 @@ . = ..() drake = Target || target -/datum/action/cooldown/spell/pointed/drakeling/InterceptClickOn(mob/living/caller, params, atom/target) +/datum/action/cooldown/spell/pointed/drakeling/InterceptClickOn(mob/living/caller_but_not_a_byond_built_in_proc, params, atom/target) . = ..() if(!.) return FALSE diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index 7110e3b736ea4..ae029d6b252d0 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -347,7 +347,7 @@ continue var/orbit_link if (source && (action == NOTIFY_ORBIT || action == NOTIFY_ATTACKORBIT)) - orbit_link = " (Orbit)" + orbit_link = " (Orbit)" to_chat(O, span_ghostalert("[message][(enter_link) ? " [enter_link]" : ""][orbit_link]")) if(ghost_sound) SEND_SOUND(O, sound(ghost_sound, volume = notify_volume)) diff --git a/code/modules/mob/say.dm b/code/modules/mob/say.dm index 5baae6ee9b54c..6818e83cfb4fa 100644 --- a/code/modules/mob/say.dm +++ b/code/modules/mob/say.dm @@ -142,7 +142,7 @@ if(key) K = src.key - var/spanned = say_quote(message) + var/spanned = say_quote(say_emphasis(message)) var/source = "[span_prefix("DEAD:")] [span_name("[(src.client.prefs.chat_toggles & GHOST_CKEY) ? "" : "([K]) "][name]")][alt_name]" // yogs - i have no clue var/rendered = " [span_message("[emoji_parse(spanned)]")]" log_talk(message, LOG_SAY, tag="DEAD") diff --git a/code/modules/modular_computers/computers/item/computer.dm b/code/modules/modular_computers/computers/item/computer.dm index 418735b3c2b8c..e4f71c962b5d6 100644 --- a/code/modules/modular_computers/computers/item/computer.dm +++ b/code/modules/modular_computers/computers/item/computer.dm @@ -399,15 +399,15 @@ * The message that the program wishes to display. */ -/obj/item/modular_computer/proc/alert_call(datum/computer_file/program/caller, alerttext, sound = 'sound/machines/twobeep_high.ogg') - if(!caller || !caller.alert_able || caller.alert_silenced || !alerttext) //Yeah, we're checking alert_able. No, you don't get to make alerts that the user can't silence. +/obj/item/modular_computer/proc/alert_call(datum/computer_file/program/caller_but_not_a_byond_built_in_proc, alerttext, sound = 'sound/machines/twobeep_high.ogg') + if(!caller_but_not_a_byond_built_in_proc || !caller_but_not_a_byond_built_in_proc.alert_able || caller_but_not_a_byond_built_in_proc.alert_silenced || !alerttext) //Yeah, we're checking alert_able. No, you don't get to make alerts that the user can't silence. return play_computer_sound(sound, 50, TRUE) var/mob/living/holder = loc if(istype(holder)) - to_chat(holder, span_notice("\The [src] displays a [caller.filedesc] notification: [alerttext]")) + to_chat(holder, span_notice("\The [src] displays a [caller_but_not_a_byond_built_in_proc.filedesc] notification: [alerttext]")) else - visible_message(span_notice("\The [src] displays a [caller.filedesc] notification: [alerttext]")) + visible_message(span_notice("\The [src] displays a [caller_but_not_a_byond_built_in_proc.filedesc] notification: [alerttext]")) // Function used by NanoUI's to obtain data for header. All relevant entries begin with "PC_" /obj/item/modular_computer/proc/get_header_data() diff --git a/code/modules/modular_computers/computers/item/processor.dm b/code/modules/modular_computers/computers/item/processor.dm index d70592b99a1f6..ae2b809209fa2 100644 --- a/code/modules/modular_computers/computers/item/processor.dm +++ b/code/modules/modular_computers/computers/item/processor.dm @@ -70,8 +70,8 @@ /obj/item/modular_computer/processor/attack_ghost(mob/user) ui_interact(user) -/obj/item/modular_computer/processor/alert_call(datum/computer_file/program/caller, alerttext) - if(!caller || !caller.alert_able || caller.alert_silenced || !alerttext) +/obj/item/modular_computer/processor/alert_call(datum/computer_file/program/caller_but_not_a_byond_built_in_proc, alerttext) + if(!caller_but_not_a_byond_built_in_proc || !caller_but_not_a_byond_built_in_proc.alert_able || caller_but_not_a_byond_built_in_proc.alert_silenced || !alerttext) return playsound(src, 'sound/machines/twobeep_high.ogg', 50, TRUE) - machinery_computer.visible_message("The [src] displays a [caller.filedesc] notification: [alerttext]") + machinery_computer.visible_message("The [src] displays a [caller_but_not_a_byond_built_in_proc.filedesc] notification: [alerttext]") diff --git a/code/modules/modular_computers/file_system/programs/ai_network/ainetworkinterface.dm b/code/modules/modular_computers/file_system/programs/ai_network/ainetworkinterface.dm index 772f852679b40..a79070aa16417 100644 --- a/code/modules/modular_computers/file_system/programs/ai_network/ainetworkinterface.dm +++ b/code/modules/modular_computers/file_system/programs/ai_network/ainetworkinterface.dm @@ -259,9 +259,9 @@ downloading = target if(!downloading.mind && downloading.deployed_shell.mind) - to_chat(downloading.deployed_shell, span_userdanger("Warning! Someone is attempting to download you from [get_area(computer.physical)]! (Click here to finish download instantly)")) + to_chat(downloading.deployed_shell, span_userdanger("Warning! Someone is attempting to download you from [get_area(computer.physical)]! (Click here to finish download instantly)")) else - to_chat(downloading, span_userdanger("Warning! Someone is attempting to download you from [get_area(computer.physical)]! (Click here to finish download instantly)")) + to_chat(downloading, span_userdanger("Warning! Someone is attempting to download you from [get_area(computer.physical)]! (Click here to finish download instantly)")) user_downloading = user download_progress = 0 . = TRUE diff --git a/code/modules/paperwork/clipboard.dm b/code/modules/paperwork/clipboard.dm index e29c311c70f64..ce565aa8a31d0 100644 --- a/code/modules/paperwork/clipboard.dm +++ b/code/modules/paperwork/clipboard.dm @@ -52,19 +52,19 @@ /obj/item/clipboard/attack_self(mob/user) var/dat = "Clipboard" if(haspen) - dat += "Remove Pen

    " + dat += "Remove Pen

    " else - dat += "Add Pen

    " + dat += "Add Pen

    " //The topmost paper. You can't organise contents directly in byond, so this is what we're stuck with. -Pete if(toppaper) var/obj/item/paper/P = toppaper - dat += "Write Remove - [P.name]

    " + dat += "Write Remove - [P.name]

    " for(P in src) if(P == toppaper) continue - dat += "Write Remove Move to top - [P.name]
    " + dat += "Write Remove Move to top - [P.name]
    " dat += "" user << browse(dat, "window=clipboard") onclose(user, "clipboard") diff --git a/code/modules/paperwork/faxmachine.dm b/code/modules/paperwork/faxmachine.dm index 75adb03ab9608..efe9411c44e54 100644 --- a/code/modules/paperwork/faxmachine.dm +++ b/code/modules/paperwork/faxmachine.dm @@ -182,7 +182,7 @@ GLOBAL_LIST_EMPTY(adminfaxes) visible_message("[src] beeps, \"Message transmitted successfully.\"") /obj/machinery/photocopier/faxmachine/proc/send_adminmessage(mob/sender, faxname, obj/item/sent, reply_type, font_colour="#006100") - var/msg = "[faxname]: [key_name(sender, 1)] (PP) (VV) (SM) (JMP) (REPLY): Receiving '[sent.name]' via secure connection ... view message" + var/msg = "[faxname]: [key_name(sender, 1)] (PP) (VV) (SM) (JMP) (REPLY): Receiving '[sent.name]' via secure connection ... view message" msg = span_admin("[msg]") to_chat(GLOB.permissions.admins, type = MESSAGE_TYPE_ADMINLOG, diff --git a/code/modules/paperwork/filingcabinet.dm b/code/modules/paperwork/filingcabinet.dm index a30da6ff696f9..67c310f397dcf 100644 --- a/code/modules/paperwork/filingcabinet.dm +++ b/code/modules/paperwork/filingcabinet.dm @@ -131,7 +131,7 @@ var/i for(i=contents.len, i>=1, i--) var/obj/item/P = contents[i] - dat += "[P.name]" + dat += "[P.name]" dat += "
" user << browse("[name][dat]", "window=filingcabinet;size=350x300") diff --git a/code/modules/paperwork/folders.dm b/code/modules/paperwork/folders.dm index ae42022663acb..09b47fd00a4c6 100644 --- a/code/modules/paperwork/folders.dm +++ b/code/modules/paperwork/folders.dm @@ -57,7 +57,7 @@ var/dat = "[name]" for(var/obj/item/I in src) - dat += "Remove - [I.name]
" + dat += "Remove - [I.name]
" dat += "" user << browse(dat, "window=folder") onclose(user, "folder") diff --git a/code/modules/paperwork/paper.dm b/code/modules/paperwork/paper.dm index 0d5b8e59a2d12..c988e53d32c61 100644 --- a/code/modules/paperwork/paper.dm +++ b/code/modules/paperwork/paper.dm @@ -174,9 +174,9 @@ var/datum/language/paperlang = GLOB.language_datum_instances[X.lang] text += paperlang.scramble_HTML(X.text) else if(links) - text += span_paper_field("" + "write" + "") + text += span_paper_field("" + "write" + "") if(links) - text += span_paper_field("" + "write" + "") + text += span_paper_field("" + "write" + "") if(coloroverride) text += "
" return text @@ -278,7 +278,7 @@ written += templist else written.Insert(text2num(id),templist) // text2num, otherwise it writes to the hashtable index instead of into the array - usr << browse("[name][render_body(usr,TRUE)]
[stamps]
\[?\]
", "window=[name]") // Update the window + usr << browse("[name][render_body(usr,TRUE)]
[stamps]
\[?\]
", "window=[name]") // Update the window update_appearance(UPDATE_ICON) @@ -293,7 +293,7 @@ if(istype(P, /obj/item/pen) || istype(P, /obj/item/toy/crayon)) if(user.is_literate()) - user << browse("[name][render_body(user,TRUE)]
[stamps]
\[?\]
", "window=[name]") + user << browse("[name][render_body(user,TRUE)]
[stamps]
\[?\]
", "window=[name]") return else to_chat(user, span_notice("You don't know how to read or write.")) diff --git a/code/modules/paperwork/paper_bundle.dm b/code/modules/paperwork/paper_bundle.dm index c9a1d7e0e80c6..294bcf77efd1c 100644 --- a/code/modules/paperwork/paper_bundle.dm +++ b/code/modules/paperwork/paper_bundle.dm @@ -90,9 +90,9 @@ /obj/item/paper_bundle/proc/show_content(mob/user as mob) var/dat var/obj/item/W = src[page] - dat += "
[screen != 0 ? "Previous Page" : ""]
" - dat += "
[admin_faxed ? "" : "Remove [(istype(W, /obj/item/paper)) ? "paper" : "photo"]"]
" - dat += "
[screen != 2 ? "Next Page" : ""]


" + dat += "
[screen != 0 ? "Previous Page" : ""]
" + dat += "
[admin_faxed ? "" : "Remove [(istype(W, /obj/item/paper)) ? "paper" : "photo"]"]
" + dat += "
[screen != 2 ? "Next Page" : ""]


" if(istype(src[page], /obj/item/paper)) var/obj/item/paper/P = W var/dist = get_dist(src, user) diff --git a/code/modules/power/generator.dm b/code/modules/power/generator.dm index f5521cc1c9e20..acd3637aa89a4 100644 --- a/code/modules/power/generator.dm +++ b/code/modules/power/generator.dm @@ -157,7 +157,7 @@ else t += span_bad("Unable to locate any parts!") if(include_link) - t += "
Close" + t += "
Close" return t diff --git a/code/modules/power/turbine.dm b/code/modules/power/turbine.dm index 15efed071457b..0381b2af3b291 100644 --- a/code/modules/power/turbine.dm +++ b/code/modules/power/turbine.dm @@ -305,9 +305,9 @@ t += "Turbine: [round(compressor.rpm)] RPM
" - t += "Starter: [ compressor.starter ? "Off On" : "Off On"]" + t += "Starter: [ compressor.starter ? "Off On" : "Off On"]" - t += "
Close" + t += "
Close" t += "" var/datum/browser/popup = new(user, "turbine", name) diff --git a/code/modules/reagents/chemistry/recipes.dm b/code/modules/reagents/chemistry/recipes.dm index 4af86df503742..bce55dcb7fe3e 100644 --- a/code/modules/reagents/chemistry/recipes.dm +++ b/code/modules/reagents/chemistry/recipes.dm @@ -26,7 +26,7 @@ var/atom/A = holder.my_atom var/turf/T = get_turf(A) var/message = "A [reaction_name] reaction has occurred in [ADMIN_VERBOSEJMP(T)]" - message += " (VV)" + message += " (VV)" var/mob/M = get(A, /mob) if(M) diff --git a/code/modules/research/machinery/_production.dm b/code/modules/research/machinery/_production.dm index b446c6931aee1..cbe0df1381585 100644 --- a/code/modules/research/machinery/_production.dm +++ b/code/modules/research/machinery/_production.dm @@ -200,12 +200,12 @@ l += "
[host_research.organization] [department_tag] Department Lathe" l += "Security protocols: [(obj_flags & EMAGGED)? "Disabled" : "Enabled"]" if (materials.mat_container) - l += "Material Amount: [materials.format_amount()]" + l += "Material Amount: [materials.format_amount()]" else l += "No material storage connected, please contact the quartermaster." - l += "Chemical volume: [reagents.total_volume] / [reagents.maximum_volume]" - l += "Synchronize Research" - l += "Main Screen
[RDSCREEN_NOBREAK]" + l += "Chemical volume: [reagents.total_volume] / [reagents.maximum_volume]" + l += "Synchronize Research" + l += "Main Screen[RDSCREEN_NOBREAK]" return l /obj/machinery/rnd/production/proc/ui_screen_materials() @@ -219,20 +219,20 @@ var/amount = materials.mat_container.materials[mat_id] var/ref = REF(M) l += "* [amount] of [M.name]: " - if(amount >= MINERAL_MATERIAL_AMOUNT) l += "Eject [RDSCREEN_NOBREAK]" - if(amount >= MINERAL_MATERIAL_AMOUNT*5) l += "5x [RDSCREEN_NOBREAK]" - if(amount >= MINERAL_MATERIAL_AMOUNT) l += "All[RDSCREEN_NOBREAK]" + if(amount >= MINERAL_MATERIAL_AMOUNT) l += "Eject [RDSCREEN_NOBREAK]" + if(amount >= MINERAL_MATERIAL_AMOUNT*5) l += "5x [RDSCREEN_NOBREAK]" + if(amount >= MINERAL_MATERIAL_AMOUNT) l += "All[RDSCREEN_NOBREAK]" l += "" l += "[RDSCREEN_NOBREAK]" return l /obj/machinery/rnd/production/proc/ui_screen_chemicals() var/list/l = list() - l += "
Disposal All Chemicals in Storage" + l += "
Disposal All Chemicals in Storage" l += "

Chemical Storage:

" for(var/datum/reagent/R in reagents.reagent_list) l += "[R.name]: [R.volume]" - l += "Purge" + l += "Purge" l += "
" return l @@ -273,11 +273,11 @@ c = min(c,t) if (c >= 1) - l += "[D.name][RDSCREEN_NOBREAK]" + l += "[D.name][RDSCREEN_NOBREAK]" if(c >= 5) - l += "x5[RDSCREEN_NOBREAK]" + l += "x5[RDSCREEN_NOBREAK]" if(c >= 10) - l += "x10[RDSCREEN_NOBREAK]" + l += "x10[RDSCREEN_NOBREAK]" l += "[temp_material][RDSCREEN_NOBREAK]" else l += "[span_linkOff("[D.name]")][temp_material][RDSCREEN_NOBREAK]" @@ -369,7 +369,7 @@ l += "" line_length = 1 - l += "[C]" + l += "[C]" line_length++ l += "
" diff --git a/code/modules/research/rdconsole.dm b/code/modules/research/rdconsole.dm index 117152d8bcf81..72925603e4874 100644 --- a/code/modules/research/rdconsole.dm +++ b/code/modules/research/rdconsole.dm @@ -231,7 +231,7 @@ Nothing else in the console has ID requirements. l += "" line_length = 1 - l += "[C]" + l += "[C]" line_length++ l += "" @@ -244,57 +244,57 @@ Nothing else in the console has ID requirements. l += "
[stored_research.organization] Research and Development Network" l += "Available points:
[techweb_point_display_rdconsole(stored_research.research_points, stored_research.last_bitcoins)]" l += "Security protocols: [obj_flags & EMAGGED ? "Disabled" : (anyone_can_research ? "Timed Out" : "Enabled")]" - l += "Main Menu | Back
[RDSCREEN_NOBREAK]" - l += "[ui_mode == 1? span_linkOn("Normal View") : "Normal View"] | [ui_mode == 2? span_linkOn("Expert View") : "Expert View"] | [ui_mode == 3? span_linkOn("List View") : "List View"]" + l += "Main Menu | Back[RDSCREEN_NOBREAK]" + l += "[ui_mode == 1? span_linkOn("Normal View") : "Normal View"] | [ui_mode == 2? span_linkOn("Expert View") : "Expert View"] | [ui_mode == 3? span_linkOn("List View") : "List View"]" return l /obj/machinery/computer/rdconsole/proc/ui_main_menu() var/list/l = list() if(research_control) - l += "

Technology" + l += "

Technology" if(d_disk) - l += "
Design Disk" + l += "
Design Disk" if(t_disk) - l += "
Tech Disk" + l += "
Tech Disk" if(linked_destroy) - l += "
Destructive Analyzer" + l += "
Destructive Analyzer" if(linked_lathe) - l += "
Protolathe" + l += "
Protolathe" if(linked_imprinter) - l += "
Circuit Imprinter" - l += "
Settings

" + l += "
Circuit Imprinter" + l += "
Settings" return l /obj/machinery/computer/rdconsole/proc/ui_locked() - return list("

SYSTEM LOCKED


") + return list("

SYSTEM LOCKED


") /obj/machinery/computer/rdconsole/proc/ui_settings() var/list/l = list() l += "

R&D Console Settings:

" - l += "Device Linkage Menu" - l += "Lock Console
" + l += "Device Linkage Menu" + l += "Lock Console" return l /obj/machinery/computer/rdconsole/proc/ui_device_linking() var/list/l = list() - l += "Settings Menu
" + l += "Settings Menu
" l += "

R&D Console Device Linkage Menu:

" - l += "Re-sync with Nearby Devices" + l += "Re-sync with Nearby Devices" l += "

Linked Devices:

" - l += linked_destroy? "* Destructive Analyzer Disconnect" : "* No Destructive Analyzer Linked" - l += linked_lathe? "* Protolathe Disconnect" : "* No Protolathe Linked" - l += linked_imprinter? "* Circuit Imprinter Disconnect" : "* No Circuit Imprinter Linked" + l += linked_destroy? "* Destructive Analyzer Disconnect" : "* No Destructive Analyzer Linked" + l += linked_lathe? "* Protolathe Disconnect" : "* No Protolathe Linked" + l += linked_imprinter? "* Circuit Imprinter Disconnect" : "* No Circuit Imprinter Linked" l += "
" return l /obj/machinery/computer/rdconsole/proc/ui_protolathe_header() var/list/l = list() - l += "" return l /obj/machinery/computer/rdconsole/proc/ui_protolathe_category_view() //Legacy code @@ -325,11 +325,11 @@ Nothing else in the console has ID requirements. c = min(c,t) if (c >= 1) - l += "[D.name][RDSCREEN_NOBREAK]" + l += "[D.name][RDSCREEN_NOBREAK]" if(c >= 5) - l += "x5[RDSCREEN_NOBREAK]" + l += "x5[RDSCREEN_NOBREAK]" if(c >= 10) - l += "x10[RDSCREEN_NOBREAK]" + l += "x10[RDSCREEN_NOBREAK]" l += "[temp_material][RDSCREEN_NOBREAK]" else l += "[span_linkOff("[D.name]")][temp_material][RDSCREEN_NOBREAK]" @@ -378,11 +378,11 @@ Nothing else in the console has ID requirements. c = min(c,t) if (c >= 1) - l += "[D.name][RDSCREEN_NOBREAK]" + l += "[D.name][RDSCREEN_NOBREAK]" if(c >= 5) - l += "x5[RDSCREEN_NOBREAK]" + l += "x5[RDSCREEN_NOBREAK]" if(c >= 10) - l += "x10[RDSCREEN_NOBREAK]" + l += "x10[RDSCREEN_NOBREAK]" l += "[temp_material][RDSCREEN_NOBREAK]" else l += "[span_linkOff("[D.name]")][temp_material][RDSCREEN_NOBREAK]" @@ -404,9 +404,9 @@ Nothing else in the console has ID requirements. var/amount = mat_container.materials[mat_id] var/ref = REF(M) l += "* [amount] of [M.name]: " - if(amount >= MINERAL_MATERIAL_AMOUNT) l += "Eject [RDSCREEN_NOBREAK]" - if(amount >= MINERAL_MATERIAL_AMOUNT*5) l += "5x [RDSCREEN_NOBREAK]" - if(amount >= MINERAL_MATERIAL_AMOUNT) l += "All[RDSCREEN_NOBREAK]" + if(amount >= MINERAL_MATERIAL_AMOUNT) l += "Eject [RDSCREEN_NOBREAK]" + if(amount >= MINERAL_MATERIAL_AMOUNT*5) l += "5x [RDSCREEN_NOBREAK]" + if(amount >= MINERAL_MATERIAL_AMOUNT) l += "All[RDSCREEN_NOBREAK]" l += "" l += "
[RDSCREEN_NOBREAK]" return l @@ -415,22 +415,22 @@ Nothing else in the console has ID requirements. RDSCREEN_UI_LATHE_CHECK var/list/l = list() l += ui_protolathe_header() - l += "
Disposal All Chemicals in Storage" + l += "
Disposal All Chemicals in Storage" l += "

Chemical Storage:

" for(var/datum/reagent/R in linked_lathe.reagents.reagent_list) l += "[R.name]: [R.volume]" - l += "Purge" + l += "Purge" l += "
" return l /obj/machinery/computer/rdconsole/proc/ui_circuit_header() //Legacy Code var/list/l = list() - l += "" return l /obj/machinery/computer/rdconsole/proc/ui_circuit() //Legacy code @@ -478,7 +478,7 @@ Nothing else in the console has ID requirements. else temp_materials += " [all_materials[M]/coeff] [CallMaterialName(M)]" if (check_materials) - l += "[D.name][temp_materials]" + l += "[D.name][temp_materials]" else l += "[span_linkOff("[D.name]")][temp_materials]" l += "
" @@ -508,7 +508,7 @@ Nothing else in the console has ID requirements. else temp_materials += " [all_materials[M]/coeff] [CallMaterialName(M)]" if (check_materials) - l += "[D.name][temp_materials]" + l += "[D.name][temp_materials]" else l += "[span_linkOff("[D.name]")][temp_materials]" l += "" @@ -518,11 +518,11 @@ Nothing else in the console has ID requirements. RDSCREEN_UI_IMPRINTER_CHECK var/list/l = list() l += ui_circuit_header() - l += "Disposal All Chemicals in Storage
" + l += "Disposal All Chemicals in Storage
" l += "

Chemical Storage:

" for(var/datum/reagent/R in linked_imprinter.reagents.reagent_list) l += "[R.name]: [R.volume]" - l += "Purge" + l += "Purge" return l /obj/machinery/computer/rdconsole/proc/ui_circuit_materials() //Legacy code! @@ -539,9 +539,9 @@ Nothing else in the console has ID requirements. var/amount = mat_container.materials[mat_id] var/ref = REF(M) l += "* [amount] of [M.name]: " - if(amount >= MINERAL_MATERIAL_AMOUNT) l += "Eject [RDSCREEN_NOBREAK]" - if(amount >= MINERAL_MATERIAL_AMOUNT*5) l += "5x [RDSCREEN_NOBREAK]" - if(amount >= MINERAL_MATERIAL_AMOUNT) l += "All[RDSCREEN_NOBREAK]
" + if(amount >= MINERAL_MATERIAL_AMOUNT) l += "Eject [RDSCREEN_NOBREAK]" + if(amount >= MINERAL_MATERIAL_AMOUNT*5) l += "5x [RDSCREEN_NOBREAK]" + if(amount >= MINERAL_MATERIAL_AMOUNT) l += "All[RDSCREEN_NOBREAK]
" l += "" l += "[RDSCREEN_NOBREAK]" return l @@ -549,41 +549,41 @@ Nothing else in the console has ID requirements. /obj/machinery/computer/rdconsole/proc/ui_techdisk() //Legacy code RDSCREEN_UI_TDISK_CHECK var/list/l = list() - l += "
Disk Operations: Clear Disk" - l += "Eject Disk" - l += "Upload All" - l += "Load Technology to Disk
" + l += "
Disk Operations: Clear Disk" + l += "Eject Disk" + l += "Upload All" + l += "Load Technology to Disk
" l += "

Stored Technology Nodes:

" for(var/i in t_disk.stored_research.researched_nodes) var/datum/techweb_node/N = SSresearch.techweb_node_by_id(i) - l += "[N.display_name]" + l += "[N.display_name]" l += "
" return l /obj/machinery/computer/rdconsole/proc/ui_designdisk() //Legacy code RDSCREEN_UI_DDISK_CHECK var/list/l = list() - l += "Disk Operations: Clear DiskUpload AllEject Disk" + l += "Disk Operations: Clear DiskUpload AllEject Disk" for(var/i in 1 to d_disk.max_blueprints) l += "
" if(d_disk.blueprints[i]) var/datum/design/D = d_disk.blueprints[i] - l += "[D.name]" - l += "Operations: Upload to database Clear Slot" + l += "[D.name]" + l += "Operations: Upload to database Clear Slot" else - l += "Empty Slot Operations: Load Design to Slot" + l += "Empty Slot Operations: Load Design to Slot" l += "
" return l /obj/machinery/computer/rdconsole/proc/ui_designdisk_upload() //Legacy code RDSCREEN_UI_DDISK_CHECK var/list/l = list() - l += "Return to Disk Operations
" + l += "Return to Disk Operations
" l += "

Load Design to Disk:

" for(var/v in stored_research.researched_designs) var/datum/design/D = SSresearch.techweb_design_by_id(v) l += "[D.name] " - l += "Copy to Disk" + l += "Copy to Disk" l += "
" return l @@ -594,7 +594,7 @@ Nothing else in the console has ID requirements. l += "
No item loaded. Standing-by...
" else l += "
[RDSCREEN_NOBREAK]" - l += "
[icon2html(linked_destroy.loaded_item, usr)][linked_destroy.loaded_item.name] Eject
[RDSCREEN_NOBREAK]" + l += "
[icon2html(linked_destroy.loaded_item, usr)][linked_destroy.loaded_item.name] Eject
[RDSCREEN_NOBREAK]" l += "Select a node to boost by deconstructing this item. This item can boost:" var/anything = FALSE @@ -610,7 +610,7 @@ Nothing else in the console has ID requirements. l += "This node has already been researched." else if(!length(worth)) // reveal only if (stored_research.hidden_nodes[N.id]) - l += "[N.display_name]" + l += "[N.display_name]" l += "This node will be revealed." else l += span_linkOff("[N.display_name]") @@ -624,7 +624,7 @@ Nothing else in the console has ID requirements. if(amt > 0) differences[i] = amt if (length(differences)) - l += "[N.display_name]" + l += "[N.display_name]" l += "This node will be boosted with the following:
[techweb_point_display_generic(differences)]" else l += span_linkOff("[N.display_name]") @@ -640,13 +640,13 @@ Nothing else in the console has ID requirements. l += span_linkOff("Point Deconstruction") l += "This item's points have already been claimed." else - l += "Point Deconstruction" + l += "Point Deconstruction" l += "This item is worth:
[techweb_point_display_generic(point_values)]!" l += "
[RDSCREEN_NOBREAK]" if(!(linked_destroy.loaded_item.resistance_flags & INDESTRUCTIBLE)) var/list/materials = linked_destroy.loaded_item.materials - l += "
[materials.len? "Material Reclamation" : "Destroy Item"]" + l += "
[materials.len? "Material Reclamation" : "Destroy Item"]" for (var/M in materials) l += "* [CallMaterialName(M)] x [materials[M] * (linked_destroy.decon_mod/10)]" l += "
[RDSCREEN_NOBREAK]" @@ -696,14 +696,14 @@ Nothing else in the console has ID requirements. for(var/datum/techweb_node/N in avail) var/not_unlocked = (stored_research.available_nodes[N.id] && !stored_research.researched_nodes[N.id]) var/has_points = (stored_research.can_afford(N.get_price(stored_research))) - var/research_href = not_unlocked? (has_points? "Research" : "Not Enough Points") : null - l += "[N.display_name][research_href]" + var/research_href = not_unlocked? (has_points? "Research" : "Not Enough Points") : null + l += "[N.display_name][research_href]" l += "

Locked Nodes:

" for(var/datum/techweb_node/N in unavail) - l += "[N.display_name]" + l += "[N.display_name]" l += "

Researched Nodes:

" for(var/datum/techweb_node/N in res) - l += "[N.display_name]" + l += "[N.display_name]" l += "
[RDSCREEN_NOBREAK]" return l @@ -721,7 +721,7 @@ Nothing else in the console has ID requirements. return l var/display_name = node.display_name if (selflink) - display_name = "[display_name]" + display_name = "[display_name]" l += "
[display_name] [RDSCREEN_NOBREAK]" if(minimal) l += "
[node.description]" @@ -730,7 +730,7 @@ Nothing else in the console has ID requirements. l += span_linkOff("Researched") else if(stored_research.available_nodes[node.id]) if(stored_research.can_afford(node.get_price(stored_research))) - l += "
[node.price_display(stored_research)]" + l += "
[node.price_display(stored_research)]" else l += "
[span_linkOff("[node.price_display(stored_research)]")]" // gray - too expensive else @@ -786,11 +786,11 @@ Nothing else in the console has ID requirements. if(selected_design.build_type & IMPRINTER) lathes += "[machine_icon(/obj/machinery/rnd/production/circuit_imprinter)][RDSCREEN_NOBREAK]" if (linked_imprinter && stored_research.researched_designs[selected_design.id]) - l += "Imprint" + l += "Imprint" if(selected_design.build_type & PROTOLATHE) lathes += "[machine_icon(/obj/machinery/rnd/production/protolathe)][RDSCREEN_NOBREAK]" if (linked_lathe && stored_research.researched_designs[selected_design.id]) - l += "Construct" + l += "Construct" if(selected_design.build_type & AUTOLATHE) lathes += "[machine_icon(/obj/machinery/autolathe)][RDSCREEN_NOBREAK]" if(selected_design.build_type & MECHFAB) diff --git a/code/modules/research/server.dm b/code/modules/research/server.dm index 61fe5a120fe8d..9958c43a8ed18 100644 --- a/code/modules/research/server.dm +++ b/code/modules/research/server.dm @@ -182,7 +182,7 @@ dat += "Connected Servers:" dat += "" for(var/obj/machinery/rnd/server/S in GLOB.machines) - dat += "
" + dat += "
" dat += "
ServerOperating TempStatus
[S.name][S.current_temp][S.stat & EMPED || stat & NOPOWER?"Offline":"([S.research_disabled? "Disabled" : "Online"])"]
[S.name][S.current_temp][S.stat & EMPED || stat & NOPOWER?"Offline":"([S.research_disabled? "Disabled" : "Online"])"]

" dat += "Research Log
" diff --git a/code/modules/shuttle/computer.dm b/code/modules/shuttle/computer.dm index bb46be700fdb3..5b24b3a3a94fc 100644 --- a/code/modules/shuttle/computer.dm +++ b/code/modules/shuttle/computer.dm @@ -204,7 +204,7 @@ return COOLDOWN_START(src, request_cooldown, 1 MINUTES) to_chat(usr, span_notice("Your request has been received by CentCom.")) - to_chat(GLOB.permissions.admins, "FERRY: [ADMIN_LOOKUPFLW(usr)] (Move Ferry) is requesting to move the transport ferry to CentCom.") + to_chat(GLOB.permissions.admins, "FERRY: [ADMIN_LOOKUPFLW(usr)] (Move Ferry) is requesting to move the transport ferry to CentCom.") return TRUE /obj/machinery/computer/shuttle/emag_act(mob/user, obj/item/card/emag/emag_card) diff --git a/code/modules/shuttle/custom_shuttle.dm b/code/modules/shuttle/custom_shuttle.dm index f2af05af02e03..cac8db8c848ce 100644 --- a/code/modules/shuttle/custom_shuttle.dm +++ b/code/modules/shuttle/custom_shuttle.dm @@ -40,7 +40,7 @@ var/obj/docking_port/mobile/M = SSshuttle.getShuttle(shuttleId) var/dat = "[M ? "Current Location : [M.getStatusText()]" : "Shuttle link required."]

" if(M) - dat += "Run Flight Calculations
" + dat += "Run Flight Calculations
" dat += "Shuttle Data
" dat += "Shuttle Mass: [calculated_mass/10]tons
" dat += "Engine Force: [calculated_dforce]kN ([calculated_engine_count] engines)
" @@ -59,12 +59,12 @@ break destination_found = TRUE var/dist = round(calculateDistance(S)) - dat += "Target [S.name] (Dist: [dist] | Fuel Cost: [round(dist * calculated_consumption)] | Time: [round(dist / calculated_speed)])
" + dat += "Target [S.name] (Dist: [dist] | Fuel Cost: [round(dist * calculated_consumption)] | Time: [round(dist / calculated_speed)])
" if(!destination_found) dat += "No valid destinations
" dat += "
[targetLocation ? "Target Location : [targetLocation]" : "No Target Location"]" - dat += "
Initate Flight
" - dat += "Close" + dat += "
Initate Flight
" + dat += "Close" popup = new(user, "computer", M ? M.name : "shuttle", 350, 450) popup.set_content("
[dat]
") diff --git a/code/modules/spells/spell.dm b/code/modules/spells/spell.dm index 1d954cd3f6464..9089985d81b44 100644 --- a/code/modules/spells/spell.dm +++ b/code/modules/spells/spell.dm @@ -109,12 +109,12 @@ RegisterSignals(owner, list(COMSIG_MOB_ENTER_JAUNT, COMSIG_MOB_AFTER_EXIT_JAUNT), PROC_REF(update_status_on_signal)) if(owner.client) - owner.client << output(null, "statbrowser:check_spells") + owner.client?.stat_panel.send_message("check_spells") /datum/action/cooldown/spell/Remove(mob/living/remove_from) if(remove_from.client) - remove_from.client << output(null, "statbrowser:check_spells") + remove_from.client?.stat_panel.send_message("check_spells") UnregisterSignal(remove_from, list( COMSIG_MOB_AFTER_EXIT_JAUNT, COMSIG_MOB_ENTER_JAUNT, diff --git a/code/modules/spells/spell_types/pointed/_pointed.dm b/code/modules/spells/spell_types/pointed/_pointed.dm index 4d1f3095bff64..57e14f335c308 100644 --- a/code/modules/spells/spell_types/pointed/_pointed.dm +++ b/code/modules/spells/spell_types/pointed/_pointed.dm @@ -68,7 +68,7 @@ build_all_button_icons() return TRUE -/datum/action/cooldown/spell/pointed/InterceptClickOn(mob/living/caller, params, atom/click_target) +/datum/action/cooldown/spell/pointed/InterceptClickOn(mob/living/caller_but_not_a_byond_built_in_proc, params, atom/click_target) var/atom/aim_assist_target if(aim_assist && isturf(click_target)) // Find any human in the list. We aren't picky, it's aim assist after all @@ -77,9 +77,9 @@ // If we didn't find a human, we settle for any living at all aim_assist_target = locate(/mob/living) in click_target - caller.face_atom(click_target) + caller_but_not_a_byond_built_in_proc.face_atom(click_target) - return ..(caller, params, aim_assist_target || click_target) + return ..(caller_but_not_a_byond_built_in_proc, params, aim_assist_target || click_target) /datum/action/cooldown/spell/pointed/is_valid_target(atom/cast_on) if(cast_on == owner) diff --git a/code/modules/tgui/tgui.dm b/code/modules/tgui/tgui.dm index 993207bb74c7e..9da8497b1d58c 100644 --- a/code/modules/tgui/tgui.dm +++ b/code/modules/tgui/tgui.dm @@ -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 @@ -37,6 +37,8 @@ var/datum/ui_state/state = null /// Rate limit client refreshes to prevent DoS. COOLDOWN_DECLARE(refresh_cooldown) + /// Are byond mouse events beyond the window passed in to the ui + var/mouse_hooked = FALSE /** * public @@ -93,7 +95,8 @@ opened_at = world.time window.acquire_lock(src) if(!window.is_ready()) - window.Initialize( + window.initialize( + strict_mode = TRUE, fancy = user.client.prefs.read_preference(/datum/preference/toggle/tgui_fancy), assets = list( get_asset_datum(/datum/asset/simple/tgui), @@ -111,6 +114,8 @@ window.send_message("update", get_payload( with_data = TRUE, with_static_data = TRUE)) + if(mouse_hooked) + window.set_mouse_macro() SStgui.on_open(src) return TRUE @@ -149,6 +154,18 @@ /datum/tgui/proc/set_autoupdate(autoupdate) src.autoupdate = autoupdate +/** + * public + * + * Enable/disable passing through byond mouse events to the window + * + * required value bool Enable/disable hooking. + */ +/datum/tgui/proc/set_mouse_hook(value) + src.mouse_hooked = value + //Handle unhooking/hooking on already open windows ? + + /** * public * @@ -186,7 +203,7 @@ return if(!COOLDOWN_FINISHED(src, refresh_cooldown)) refreshing = TRUE - addtimer(CALLBACK(src, PROC_REF(send_full_update)), TGUI_REFRESH_FULL_UPDATE_COOLDOWN, TIMER_UNIQUE) + addtimer(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 diff --git a/code/modules/tgui/tgui_window.dm b/code/modules/tgui/tgui_window.dm index 8704c6cf5e11b..db2aaf7f68512 100644 --- a/code/modules/tgui/tgui_window.dm +++ b/code/modules/tgui/tgui_window.dm @@ -26,6 +26,18 @@ var/initial_inline_css var/mouse_event_macro_set = FALSE + /** + * Static list used to map in macros that will then emit execute events to the tgui window + * A small disclaimer though I'm no tech wiz: I don't think it's possible to map in right or middle + * clicks in the current state, as they're keywords rather than modifiers. + */ + var/static/list/byondToTguiEventMap = list( + "MouseDown" = "byond/mousedown", + "MouseUp" = "byond/mouseup", + "Ctrl" = "byond/ctrldown", + "Ctrl+UP" = "byond/ctrlup", + ) + /** * public * @@ -49,11 +61,15 @@ * state. You can begin sending messages right after initializing. Messages * will be put into the queue until the window finishes loading. * - * optional 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/proc/Initialize( +/datum/tgui_window/proc/initialize( + strict_mode = FALSE, fancy = FALSE, assets = list(), inline_html = "", @@ -81,6 +97,7 @@ // Generate page html var/html = SStgui.basehtml html = replacetextEx(html, "\[tgui:windowId]", id) + html = replacetextEx(html, "\[tgui:strictMode]", strict_mode) // Inject assets var/inline_assets_str = "" for(var/datum/asset/asset in assets) @@ -98,14 +115,14 @@ html = replacetextEx(html, "\n", inline_assets_str) // Inject inline HTML if (inline_html) - html = replacetextEx(html, "", inline_html) + html = replacetextEx(html, "", isfile(inline_html) ? file2text(inline_html) : inline_html) // Inject inline JS if (inline_js) - inline_js = "" + inline_js = "" html = replacetextEx(html, "", inline_js) // Inject inline CSS if (inline_css) - inline_css = "" + inline_css = "" html = replacetextEx(html, "", inline_css) // Open the window client << browse(html, "window=[id];[options]") @@ -115,6 +132,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/asset/asset in sent_assets) + send_asset(asset) + /** * public * @@ -198,6 +232,8 @@ /datum/tgui_window/proc/close(can_be_suspended = TRUE) if(!client) return + if(mouse_event_macro_set) + remove_mouse_macro() if(can_be_suspended && can_be_suspended()) log_tgui(client, context = "[id]/close (suspending)", @@ -293,6 +329,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 * @@ -335,16 +383,38 @@ if("openLink") client << link(href_list["url"]) if("cacheReloaded") - // Reinitialize - Initialize( - fancy = initial_fancy, - assets = initial_assets, - inline_html = initial_inline_html, - inline_js = initial_inline_js, - inline_css = initial_inline_css) - // Resend the assets - for(var/asset in sent_assets) - send_asset(asset) + reinitialize() + if("chat/resend") + SSchat.handle_resend(client, payload) /datum/tgui_window/vv_edit_var(var_name, var_value) return var_name != NAMEOF(src, id) && ..() + + +/datum/tgui_window/proc/set_mouse_macro() + if(mouse_event_macro_set) + return + + for(var/mouseMacro in byondToTguiEventMap) + var/command_template = ".output CONTROL PAYLOAD" + var/event_message = TGUI_CREATE_MESSAGE(byondToTguiEventMap[mouseMacro], null) + var target_control = is_browser \ + ? "[id]:update" \ + : "[id].browser:update" + var/with_id = replacetext(command_template, "CONTROL", target_control) + var/full_command = replacetext(with_id, "PAYLOAD", event_message) + + var/list/params = list() + params["parent"] = "default" //Technically this is external to tgui but whatever + params["name"] = mouseMacro + params["command"] = full_command + + winset(client, "[mouseMacro]Window[id]Macro", params) + mouse_event_macro_set = TRUE + +/datum/tgui_window/proc/remove_mouse_macro() + if(!mouse_event_macro_set) + stack_trace("Unsetting mouse macro on tgui window that has none") + for(var/mouseMacro in byondToTguiEventMap) + winset(client, null, "[mouseMacro]Window[id]Macro.parent=null") + mouse_event_macro_set = FALSE diff --git a/code/modules/tgui_input/alert.dm b/code/modules/tgui_input/alert.dm index 84ccc4548cd6c..b84668daa9067 100644 --- a/code/modules/tgui_input/alert.dm +++ b/code/modules/tgui_input/alert.dm @@ -1,7 +1,7 @@ /** * 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. + * This proc should be used to create alerts that the caller_but_not_a_byond_built_in_proc 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. @@ -10,7 +10,7 @@ * * 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) +/proc/tgui_alert(mob/user, message = null, title = null, list/buttons = list("Ok"), timeout = 0, autofocus = TRUE, ui_state = GLOB.always_state) if (!user) user = usr if (!istype(user)) @@ -18,8 +18,10 @@ var/client/client = user user = client.mob else - return - var/datum/tgui_modal/alert = new(user, message, title, buttons, timeout, autofocus) + return null + if(isnull(user.client)) + return null + var/datum/tgui_modal/alert = new(user, message, title, buttons, timeout, autofocus, ui_state) alert.ui_interact(user) alert.wait() if (alert) @@ -74,12 +76,15 @@ var/autofocus /// Boolean field describing if the tgui_modal 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_modal/New(mob/user, message, title, list/buttons, timeout, autofocus) +/datum/tgui_modal/New(mob/user, message, title, list/buttons, timeout, autofocus, ui_state) src.title = title src.message = message src.buttons = buttons.Copy() src.autofocus = autofocus + src.state = ui_state if (timeout) src.timeout = timeout start_time = world.time @@ -87,8 +92,9 @@ /datum/tgui_modal/Destroy(force, ...) SStgui.close_uis(src) + state = null QDEL_NULL(buttons) - . = ..() + return ..() /** * Waits for a user's response to the tgui_modal's prompt before returning. Returns early if @@ -109,7 +115,7 @@ closed = TRUE /datum/tgui_modal/ui_state(mob/user) - return GLOB.always_state + return state /datum/tgui_modal/ui_data(mob/user) . = list( diff --git a/code/modules/tgui_input/checkboxes.dm b/code/modules/tgui_input/checkboxes.dm index 7b1129af30cc2..8e9c381701000 100644 --- a/code/modules/tgui_input/checkboxes.dm +++ b/code/modules/tgui_input/checkboxes.dm @@ -10,20 +10,22 @@ * 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) +/proc/tgui_input_checkboxes(mob/user, message, title = "Select", list/items, min_checked = 1, max_checked = 50, timeout = 0, ui_state = GLOB.always_state) if (!user) user = usr if(!length(items)) - return + return null if (!istype(user)) if (istype(user, /client)) var/client/client = user user = client.mob else - return + return null + if(isnull(user.client)) + return null if(!user.client.prefs.read_preference(/datum/preference/toggle/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) + 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) @@ -50,13 +52,16 @@ 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) +/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 @@ -65,6 +70,7 @@ /datum/tgui_checkbox_input/Destroy(force, ...) SStgui.close_uis(src) + state = null QDEL_NULL(items) return ..() @@ -84,7 +90,7 @@ closed = TRUE /datum/tgui_checkbox_input/ui_state(mob/user) - return GLOB.always_state + return state /datum/tgui_checkbox_input/ui_data(mob/user) var/list/data = list() diff --git a/code/modules/tgui_input/list.dm b/code/modules/tgui_input/list.dm index 66641c7aa51e3..6b83ad756740e 100644 --- a/code/modules/tgui_input/list.dm +++ b/code/modules/tgui_input/list.dm @@ -1,7 +1,7 @@ /** * 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. + * This proc should be used to create alerts that the caller_but_not_a_byond_built_in_proc 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. @@ -9,11 +9,11 @@ * * 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 = "Select", list/buttons, default, timeout = 0) +/proc/tgui_input_list(mob/user, message, title = "Select", list/buttons, default, timeout = 0, ui_state = GLOB.always_state) if (!user) user = usr if(!length(buttons)) - return + return null if (!istype(user)) if (istype(user, /client)) var/client/client = user @@ -27,7 +27,7 @@ /// Client does NOT have tgui_input on: Returns regular input if(!user.client.prefs.read_preference(/datum/preference/toggle/tgui_input)) return input(user, message, title, default) as null|anything in buttons - var/datum/tgui_list_input/input = new(user, message, title, buttons, default, timeout) + var/datum/tgui_list_input/input = new(user, message, title, buttons, default, timeout, ui_state) if(input.invalid) qdel(input) return @@ -88,15 +88,18 @@ 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/buttons, default, timeout) +/datum/tgui_list_input/New(mob/user, message, title, list/buttons, default, timeout, ui_state) src.title = title src.message = message src.buttons = list() src.buttons_map = list() src.default = default + src.state = ui_state // Gets rid of illegal characters var/static/regex/whitelistedWords = regex(@{"([^\u0020-\u8000]+)"}) @@ -118,6 +121,7 @@ /datum/tgui_list_input/Destroy(force) SStgui.close_uis(src) + state = null QDEL_NULL(buttons) return ..() @@ -140,7 +144,7 @@ closed = TRUE /datum/tgui_list_input/ui_state(mob/user) - return GLOB.always_state + return state /datum/tgui_list_input/ui_static_data(mob/user) var/list/data = list() diff --git a/code/modules/tgui_input/number.dm b/code/modules/tgui_input/number.dm index c811eb4316010..d2411f267360b 100644 --- a/code/modules/tgui_input/number.dm +++ b/code/modules/tgui_input/number.dm @@ -1,7 +1,7 @@ /** * 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. + * This proc should be used to create windows for number entry that the caller_but_not_a_byond_built_in_proc 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. * @@ -15,7 +15,7 @@ * * 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 = null, title = "Number Input", default = null, max_value = null, min_value = 0, timeout = 0) +/proc/tgui_input_number(mob/user, message = null, title = "Number Input", default = null, max_value = null, min_value = 0, timeout = 0, ui_state = GLOB.always_state) if (!user) user = usr if (!istype(user)) @@ -23,11 +23,13 @@ var/client/client = user user = client.mob else - return + return null + if (isnull(user.client)) + return null /// Client does NOT have tgui_fancy on: Returns regular input if(!user.client.prefs.read_preference(/datum/preference/toggle/tgui_input)) return input(user, message, title, default) as null | num - var/datum/tgui_input_number/numbox = new(user, message, title, default, max_value, min_value, timeout) + var/datum/tgui_input_number/numbox = new(user, message, title, default, max_value, min_value, timeout, ui_state) numbox.ui_interact(user) numbox.wait() if (numbox) @@ -86,14 +88,17 @@ 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) +/datum/tgui_input_number/New(mob/user, message, title, default, max_value, min_value, timeout, ui_state) src.default = default src.max_value = max_value src.message = message src.min_value = min_value src.title = title + src.state = ui_state if (timeout) src.timeout = timeout start_time = world.time @@ -101,7 +106,8 @@ /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 @@ -122,7 +128,7 @@ closed = TRUE /datum/tgui_input_number/ui_state(mob/user) - return GLOB.always_state + return state /datum/tgui_input_number/ui_static_data(mob/user) . = list( @@ -161,7 +167,7 @@ return TRUE /datum/tgui_input_number/proc/set_entry(entry) - src.entry = entry + src.entry = entry /** * # async tgui_input_number diff --git a/code/modules/tgui_input/text.dm b/code/modules/tgui_input/text.dm index b09ab68b82aa6..8eb9b9feaef3d 100644 --- a/code/modules/tgui_input/text.dm +++ b/code/modules/tgui_input/text.dm @@ -1,7 +1,7 @@ /** * 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. + * This proc should be used to create windows for text entry that the caller_but_not_a_byond_built_in_proc 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. * @@ -14,7 +14,7 @@ * * multiline - Bool that determines if the input box is much larger. Good for large messages, laws, etc. * * 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 = null, title = "Text Input", default = null, max_length = null, multiline = FALSE, timeout = 0) +/proc/tgui_input_text(mob/user, message = null, title = "Text Input", default = null, max_length, multiline = FALSE, timeout = 0, ui_state = GLOB.always_state) if (!user) user = usr if (!istype(user)) @@ -22,17 +22,19 @@ var/client/client = user user = client.mob else - return + return null + if(isnull(user.client)) + return null /// Client does NOT have tgui_fancy on: Returns regular input if(!user.client.prefs.read_preference(/datum/preference/toggle/tgui_input)) if(max_length) if(multiline) - return stripped_multiline_input(user, message, title, default, max_length) + return stripped_multiline_input(user, message, title, default, PREVENT_CHARACTER_TRIM_LOSS(max_length)) else - return stripped_input(user, message, title, default, max_length) + return stripped_input(user, message, title, default, PREVENT_CHARACTER_TRIM_LOSS(max_length)) else return input(user, message, title, default) - var/datum/tgui_input_text/textbox = new(user, message, title, default, max_length, multiline, timeout) + var/datum/tgui_input_text/textbox = new(user, message, title, default, max_length, multiline, timeout, ui_state) textbox.ui_interact(user) textbox.wait() if (textbox) @@ -90,14 +92,17 @@ 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, timeout) +/datum/tgui_input_text/New(mob/user, message, title, default, max_length, multiline, timeout, ui_state) src.default = default 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 @@ -105,7 +110,8 @@ /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 @@ -126,7 +132,7 @@ closed = TRUE /datum/tgui_input_text/ui_state(mob/user) - return GLOB.always_state + return state /datum/tgui_input_text/ui_static_data(mob/user) . = list( @@ -163,7 +169,8 @@ return TRUE /datum/tgui_input_text/proc/set_entry(entry) - src.entry = entry + if(!isnull(entry)) + src.entry = max_length ? trim(entry, PREVENT_CHARACTER_TRIM_LOSS(max_length)) : entry /** * # async tgui_input_text diff --git a/code/modules/tgui_panel/external.dm b/code/modules/tgui_panel/external.dm index 74f0360bc9b7d..1ff2665bb7a8f 100644 --- a/code/modules/tgui_panel/external.dm +++ b/code/modules/tgui_panel/external.dm @@ -19,19 +19,16 @@ // Failed to fix, using tgalert as fallback action = tgalert(src, "Did that work?", "", "Yes", "No, switch to old ui") if (action == "No, switch to old ui") - winset(src, "output", "on-show=&is-disabled=0&is-visible=1") - winset(src, "browseroutput", "is-disabled=1;is-visible=0") + winset(src, "legacy_output_selector", "left=output_legacy") log_tgui(src, "Failed to fix.", context = "verb/fix_tgui_panel") /client/proc/nuke_chat() // Catch all solution (kick the whole thing in the pants) - winset(src, "output", "on-show=&is-disabled=0&is-visible=1") - winset(src, "browseroutput", "is-disabled=1;is-visible=0") + winset(src, "legacy_output_selector", "left=output_legacy") if(!tgui_panel || !istype(tgui_panel)) log_tgui(src, "tgui_panel datum is missing", context = "verb/fix_tgui_panel") tgui_panel = new(src) - tgui_panel.Initialize(force = TRUE) + tgui_panel.initialize(force = TRUE) // Force show the panel to see if there are any errors - winset(src, "output", "is-disabled=1&is-visible=0") - winset(src, "browseroutput", "is-disabled=0;is-visible=1") + winset(src, "legacy_output_selector", "left=output_browser") diff --git a/code/modules/tgui_panel/tgui_panel.dm b/code/modules/tgui_panel/tgui_panel.dm index 5bc18446821a8..a54f56ead0c08 100644 --- a/code/modules/tgui_panel/tgui_panel.dm +++ b/code/modules/tgui_panel/tgui_panel.dm @@ -36,15 +36,17 @@ * * Initializes tgui panel. */ -/datum/tgui_panel/proc/Initialize(force = FALSE) +/datum/tgui_panel/proc/initialize(force = FALSE) set waitfor = FALSE // Minimal sleep to defer initialization to after client constructor sleep(1 TICKS) initialized_at = world.time // Perform a clean initialization - window.Initialize(assets = list( - get_asset_datum(/datum/asset/simple/tgui_panel), - )) + window.initialize( + strict_mode = TRUE, + assets = list( + get_asset_datum(/datum/asset/simple/tgui_panel), + )) window.send_asset(get_asset_datum(/datum/asset/simple/namespaced/fontawesome)) window.send_asset(get_asset_datum(/datum/asset/simple/namespaced/tgfont)) window.send_asset(get_asset_datum(/datum/asset/spritesheet/chat)) @@ -59,7 +61,7 @@ */ /datum/tgui_panel/proc/on_initialize_timed_out() // Currently does nothing but sending a message to old chat. - SEND_TEXT(client, "Failed to load fancy chat, click HERE to attempt to reload it.") + SEND_TEXT(client, span_userdanger("Failed to load fancy chat, click HERE to attempt to reload it.")) /** * private diff --git a/html/admin/search.js b/html/admin/search.js index ded0b9284467a..639a3729fe3a6 100644 --- a/html/admin/search.js +++ b/html/admin/search.js @@ -23,11 +23,11 @@ function updateSearch(){ } if(found == 0) row.style.display='none'; else{ - row.style.display='block'; + row.style.display='table-row'; /* DON'T make tables with block property */ row.className = alt_style; if(alt_style == 'alt') alt_style = 'norm'; else alt_style = 'alt'; } }catch(err) { } } -} \ No newline at end of file +} diff --git a/html/browser/common.js b/html/browser/common.js index 019753c1ef311..80c22ad9eb17f 100644 --- a/html/browser/common.js +++ b/html/browser/common.js @@ -10,7 +10,7 @@ if(document.addEventListener && window.location) { // hey maybe some bozo is sti if(e.which) { if(!anti_spam[e.which]) { anti_spam[e.which] = true; - let href = "?__keydown=" + e.which; + let href = "byond://?__keydown=" + e.which; if(e.ctrlKey === false) href += "&ctrlKey=0" else if(e.ctrlKey === true) href += "&ctrlKey=1" window.location.href = href; @@ -24,7 +24,7 @@ if(document.addEventListener && window.location) { // hey maybe some bozo is sti return; if(e.which) { anti_spam[e.which] = false; - let href = "?__keyup=" + e.which; + let href = "byond://?__keyup=" + e.which; if(e.ctrlKey === false) href += "&ctrlKey=0" else if(e.ctrlKey === true) href += "&ctrlKey=1" window.location.href = href; diff --git a/html/statbrowser.css b/html/statbrowser.css new file mode 100644 index 0000000000000..810013209ca30 --- /dev/null +++ b/html/statbrowser.css @@ -0,0 +1,263 @@ +.light:root { + --scrollbar-base: #f2f2f2; + --scrollbar-thumb: #a7a7a7; +} + +html, +body { + scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-base); +} + +body { + font-family: Verdana, Geneva, Tahoma, sans-serif; + font-size: 12px; + margin: 0 !important; + padding: 0 !important; + overflow: hidden; +} + +a { + color: #003399; + text-decoration: none; +} + +a:hover { + color: #007fff; +} + +h3 { + margin: 0 -0.5em 0.5em; + padding: 1em 0.66em 0.5em; + border-bottom: 0.1667em solid; +} + + +img { + -ms-interpolation-mode: nearest-neighbor; + image-rendering: pixelated; +} + +.stat-container { + display: flex; + flex-direction: column; + height: 100vh; +} + +#menu { + display: flex; + overflow-x: auto; + overflow-y: hidden; + padding: 0.25em 0.25em 0; + background-color: #ffffff; +} + +.menu-wrap { + flex-wrap: wrap-reverse; +} + +#menu.tabs-classic { + padding: 0.15em; +} + +#menu.tabs-classic .button { + min-width: 2em; + margin: 0.1em; + padding: 0.25em 0.4em; + border: 0; + border-radius: 0.25em; +} + +#menu.tabs-classic .button.active { + background-color: #0668b8; + color: white; +} + +.button { + display: inline-table; + cursor: pointer; + user-select: none; + -ms-user-select: none; /* Remove after Byond 516 */ + text-align: center; + font-size: 1em; + min-width: 2.9em; + padding: 0.5em 0.5em 0.4em; + background-color: transparent; + color: rgba(0, 0, 0, 0.5); + border: 0; + border-bottom: 0.1667em solid transparent; + border-radius: 0.25em 0.25em 0 0; +} + +.button:hover { + background-color: #ececec; +} + +.button.active { + cursor: default; + background-color: #dfdfdf; + color: black; + border-bottom-color: #000000; +} + +#under-menu { + height: 0.5em; + background-color: #eeeeee; +} + +#under-content { + height: calc(0.5em - 4px); + background-color: #eeeeee; +} + +#statcontent { + flex: 1; + padding: 0.75em 0.5em; + overflow-y: scroll; + overflow-x: hidden; +} + +.grid-container { + margin: -0.25em; +} + +.grid-item { + display: inline-flex; + position: relative; + user-select: none; + -ms-user-select: none; /* Remove after Byond 516 */ + width: 100%; + max-height: 1.85em; + text-decoration: none; + background-color: transparent; + color: black; +} + +.grid-item:hover, +.grid-item:active { + color: #003399; + z-index: 1; +} + +.grid-item-text { + display: inline-block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + pointer-events: none; + width: 100%; + padding: 0.33em 0.5em; + border-radius: 0.25em; +} + +.grid-item:hover .grid-item-text { + height: 100%; + overflow: visible; + white-space: normal; + background-color: #ececec; +} + +.grid-item:active .grid-item-text { + background-color: #dfdfdf; +} + +@media only screen and (min-width: 300px) { + .grid-item { + width: 50%; + } +} + +@media only screen and (min-width: 430px) { + .grid-item { + width: 33%; + } +} + +@media only screen and (min-width: 560px) { + .grid-item { + width: 25%; + } +} + +@media only screen and (min-width: 770px) { + .grid-item { + width: 20%; + } +} + +.status-info { + margin: 0 0.33em 0.25em; +} + +.interview_panel_stats, +.interview_panel_controls { + margin-bottom: 1em; +} + +/** + * MARK: Dark theme colors + */ +.dark:root { + --scrollbar-base: #151515; + --scrollbar-thumb: #363636; +} + +body.dark { + background-color: #151515; + color: #b2c4dd; + scrollbar-base-color: #1c1c1c; + scrollbar-face-color: #3b3b3b; + scrollbar-3dlight-color: #252525; + scrollbar-highlight-color: #252525; + scrollbar-track-color: #1c1c1c; + scrollbar-arrow-color: #929292; + scrollbar-shadow-color: #3b3b3b; +} + +.dark a { + color: #6699ff; +} + +.dark a:hover, +.dark .grid-item:hover, +.dark .grid-item:active { + color: #80bfff; +} + +.dark #menu { + background-color: #151515; +} + +.dark #menu.tabs-classic .button.active { + background-color: #20b142; +} + +.dark .button { + color: rgba(255, 255, 255, 0.5); +} + +.dark .button:hover { + background-color: #252525; +} + +.dark .button.active { + background-color: #313131; + color: #d4dfec; + border-bottom-color: #d4dfec; +} + +.dark #under-menu, +.dark #under-content { + background-color: #202020; +} + +.dark .grid-item{ + color: #b2c4dd; +} + +.dark .grid-item:hover .grid-item-text { + background-color: #252525; +} + +.dark .grid-item:active .grid-item-text { + background-color: #313131; +} diff --git a/html/statbrowser.html b/html/statbrowser.html index d295b55f59685..d72557c7c305a 100644 --- a/html/statbrowser.html +++ b/html/statbrowser.html @@ -1,1088 +1,6 @@ - - - -Stat Browser - - - - - - - - -
-
- - - +
+ +
+
+
+
diff --git a/html/statbrowser.js b/html/statbrowser.js new file mode 100644 index 0000000000000..78d1671d432a9 --- /dev/null +++ b/html/statbrowser.js @@ -0,0 +1,1012 @@ +// Polyfills and compatibility ------------------------------------------------ +var decoder = decodeURIComponent || unescape; +if (!Array.prototype.includes) { + Array.prototype.includes = function (thing) { + for (var i = 0; i < this.length; i++) { + if (this[i] == thing) return true; + } + return false; + } +} +if (!String.prototype.trim) { + String.prototype.trim = function () { + return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); + }; +} + +// Status panel implementation ------------------------------------------------ +var status_tab_parts = ["Loading..."]; +var current_tab = null; +var mc_tab_parts = [["Loading...", ""]]; +var href_token = null; +var spells = []; +var spell_tabs = []; +var verb_tabs = []; +var verbs = [["", ""]]; // list with a list inside +var tickets = []; +var interviewManager = { status: "", interviews: [] }; +var sdql2 = []; +var permanent_tabs = []; // tabs that won't be cleared by wipes +var turfcontents = []; +var turfname = ""; +var imageRetryDelay = 500; +var imageRetryLimit = 50; +var menu = document.getElementById('menu'); +var statcontentdiv = document.getElementById('statcontent'); +var storedimages = []; +var split_admin_tabs = false; + +// Any BYOND commands that could result in the client's focus changing go through this +// to ensure that when we relinquish our focus, we don't do it after the result of +// a command has already taken focus for itself. +function run_after_focus(callback) { + setTimeout(callback, 0); +} + +function createStatusTab(name) { + if (name.indexOf(".") != -1) { + var splitName = name.split("."); + if (split_admin_tabs && splitName[0] === "Admin") + name = splitName[1]; + else + name = splitName[0]; + } + if (document.getElementById(name) || name.trim() == "") { + return; + } + if (!verb_tabs.includes(name) && !permanent_tabs.includes(name)) { + return; + } + var button = document.createElement("DIV"); + button.onclick = function () { + tab_change(name); + this.blur(); + statcontentdiv.focus(); + }; + button.id = name; + button.textContent = name; + button.className = "button"; + //ORDERING ALPHABETICALLY + button.style.order = ({"Status": 1, "MC": 2})[name] || name.charCodeAt(0); + //END ORDERING + menu.appendChild(button); + SendTabToByond(name); +} + +function removeStatusTab(name) { + if (!document.getElementById(name) || permanent_tabs.includes(name)) { + return; + } + for (var i = verb_tabs.length - 1; i >= 0; --i) { + if (verb_tabs[i] == name) { + verb_tabs.splice(i, 1); + } + } + menu.removeChild(document.getElementById(name)); + TakeTabFromByond(name); +} + +function sortVerbs() { + verbs.sort(function (a, b) { + var selector = a[0] == b[0] ? 1 : 0; + if (a[selector].toUpperCase() < b[selector].toUpperCase()) { + return 1; + } + else if (a[selector].toUpperCase() > b[selector].toUpperCase()) { + return -1; + } + return 0; + }) +} + +function addPermanentTab(name) { + if (!permanent_tabs.includes(name)) { + permanent_tabs.push(name); + } + createStatusTab(name); +} + +function removePermanentTab(name) { + for (var i = permanent_tabs.length - 1; i >= 0; --i) { + if (permanent_tabs[i] == name) { + permanent_tabs.splice(i, 1); + } + } + removeStatusTab(name); +} + +function checkStatusTab() { + for (var i = 0; i < menu.children.length; i++) { + if (!verb_tabs.includes(menu.children[i].id) && !permanent_tabs.includes(menu.children[i].id)) { + menu.removeChild(menu.children[i]); + } + } +} + +function remove_verb(v) { + var verb_to_remove = v; // to_remove = [verb:category, verb:name] + for (var i = verbs.length - 1; i >= 0; i--) { + var part_to_remove = verbs[i]; + if (part_to_remove[1] == verb_to_remove[1]) { + verbs.splice(i, 1) + } + } +} + +function check_verbs() { + for (var v = verb_tabs.length - 1; v >= 0; v--) { + verbs_cat_check(verb_tabs[v]); + } +} + +function verbs_cat_check(cat) { + var tabCat = cat; + if (cat.indexOf(".") != -1) { + var splitName = cat.split("."); + if (split_admin_tabs && splitName[0] === "Admin") + tabCat = splitName[1]; + else + tabCat = splitName[0]; + } + var verbs_in_cat = 0; + var verbcat = ""; + if (!verb_tabs.includes(tabCat)) { + removeStatusTab(tabCat); + return; + } + for (var v = 0; v < verbs.length; v++) { + var part = verbs[v]; + verbcat = part[0]; + if (verbcat.indexOf(".") != -1) { + var splitName = verbcat.split("."); + if (split_admin_tabs && splitName[0] === "Admin") + verbcat = splitName[1]; + else + verbcat = splitName[0]; + } + if (verbcat != tabCat || verbcat.trim() == "") { + continue; + } + else { + verbs_in_cat = 1; + break; // we only need one + } + } + if (verbs_in_cat != 1) { + removeStatusTab(tabCat); + if (current_tab == tabCat) + tab_change("Status"); + } +} + +function findVerbindex(name, verblist) { + for (var i = 0; i < verblist.length; i++) { + var part = verblist[i]; + if (part[1] == name) + return i; + } +} +function wipe_verbs() { + verbs = [["", ""]]; + verb_tabs = []; + checkStatusTab(); // remove all empty verb tabs +} + +function update_verbs() { + wipe_verbs(); + Byond.sendMessage("Update-Verbs"); +} + +function SendTabsToByond() { + var tabstosend = []; + tabstosend = tabstosend.concat(permanent_tabs, verb_tabs); + for (var i = 0; i < tabstosend.length; i++) { + SendTabToByond(tabstosend[i]); + } +} + +function SendTabToByond(tab) { + Byond.sendMessage("Send-Tabs", {tab: tab}); +} + +//Byond can't have this tab anymore since we're removing it +function TakeTabFromByond(tab) { + Byond.sendMessage("Remove-Tabs", {tab: tab}); +} + +function spell_cat_check(cat) { + var spells_in_cat = 0; + var spellcat = ""; + for (var s = 0; s < spells.length; s++) { + var spell = spells[s]; + spellcat = spell[0]; + if (spellcat == cat) { + spells_in_cat++; + } + } + if (spells_in_cat < 1) { + removeStatusTab(cat); + } +} + +function tab_change(tab) { + if (tab == current_tab) return; + if (document.getElementById(current_tab)) + document.getElementById(current_tab).className = "button"; // disable active on last button + current_tab = tab; + set_byond_tab(tab); + if (document.getElementById(tab)) + document.getElementById(tab).className = "button active"; // make current button active + var spell_tabs_thingy = (spell_tabs.includes(tab)); + var verb_tabs_thingy = (verb_tabs.includes(tab)); + if (tab == "Status") { + draw_status(); + } else if (tab == "MC") { + draw_mc(); + } else if (spell_tabs_thingy) { + draw_spells(tab); + } else if (verb_tabs_thingy) { + draw_verbs(tab); + } else if (tab == "Debug Stat Panel") { + draw_debug(); + } else if (tab == "Tickets") { + draw_tickets(); + draw_interviews(); + } else if (tab == "SDQL2") { + draw_sdql2(); + } else if (tab == turfname) { + draw_listedturf(); + } else { + statcontentdiv.textContext = "Loading..."; + } + Byond.winset(Byond.windowId, { + 'is-visible': true, + }); +} + +function set_byond_tab(tab) { + Byond.sendMessage("Set-Tab", {tab: tab}); +} + +function draw_debug() { + statcontentdiv.textContent = ""; + var wipeverbstabs = document.createElement("div"); + var link = document.createElement("a"); + link.onclick = function () { wipe_verbs() }; + link.textContent = "Wipe All Verbs"; + wipeverbstabs.appendChild(link); + document.getElementById("statcontent").appendChild(wipeverbstabs); + var wipeUpdateVerbsTabs = document.createElement("div"); + var updateLink = document.createElement("a"); + updateLink.onclick = function () { update_verbs() }; + updateLink.textContent = "Wipe and Update All Verbs"; + wipeUpdateVerbsTabs.appendChild(updateLink); + document.getElementById("statcontent").appendChild(wipeUpdateVerbsTabs); + var text = document.createElement("div"); + text.textContent = "Verb Tabs:"; + document.getElementById("statcontent").appendChild(text); + var table1 = document.createElement("table"); + for (var i = 0; i < verb_tabs.length; i++) { + var part = verb_tabs[i]; + // Hide subgroups except admin subgroups if they are split + if (verb_tabs[i].lastIndexOf(".") != -1) { + var splitName = verb_tabs[i].split("."); + if (split_admin_tabs && splitName[0] === "Admin") + part = splitName[1]; + else + continue; + } + var tr = document.createElement("tr"); + var td1 = document.createElement("td"); + td1.textContent = part; + var a = document.createElement("a"); + a.onclick = function (part) { + return function () { removeStatusTab(part) }; + }(part); + a.textContent = " Delete Tab " + part; + td1.appendChild(a); + tr.appendChild(td1); + table1.appendChild(tr); + } + document.getElementById("statcontent").appendChild(table1); + var header2 = document.createElement("div"); + header2.textContent = "Verbs:"; + document.getElementById("statcontent").appendChild(header2); + var table2 = document.createElement("table"); + for (var v = 0; v < verbs.length; v++) { + var part2 = verbs[v]; + var trr = document.createElement("tr"); + var tdd1 = document.createElement("td"); + tdd1.textContent = part2[0]; + var tdd2 = document.createElement("td"); + tdd2.textContent = part2[1]; + trr.appendChild(tdd1); + trr.appendChild(tdd2); + table2.appendChild(trr); + } + document.getElementById("statcontent").appendChild(table2); + var text3 = document.createElement("div"); + text3.textContent = "Permanent Tabs:"; + document.getElementById("statcontent").appendChild(text3); + var table3 = document.createElement("table"); + for (var i = 0; i < permanent_tabs.length; i++) { + var part3 = permanent_tabs[i]; + var trrr = document.createElement("tr"); + var tddd1 = document.createElement("td"); + tddd1.textContent = part3; + trrr.appendChild(tddd1); + table3.appendChild(trrr); + } + document.getElementById("statcontent").appendChild(table3); + +} +function draw_status() { + if (!document.getElementById("Status")) { + createStatusTab("Status"); + current_tab = "Status"; + } + statcontentdiv.textContent = ''; + for (var i = 0; i < status_tab_parts.length; i++) { + if (status_tab_parts[i].trim() == "") { + document.getElementById("statcontent").appendChild(document.createElement("br")); + } else { + var div = document.createElement("div"); + div.textContent = status_tab_parts[i]; + div.className = "status-info"; + document.getElementById("statcontent").appendChild(div); + } + } + if (verb_tabs.length == 0 || !verbs) { + Byond.command("Fix-Stat-Panel"); + } +} + +function draw_mc() { + statcontentdiv.textContent = ""; + var table = document.createElement("table"); + for (var i = 0; i < mc_tab_parts.length; i++) { + var part = mc_tab_parts[i]; + var tr = document.createElement("tr"); + var td1 = document.createElement("td"); + td1.textContent = part[0]; + var td2 = document.createElement("td"); + if (part[2]) { + var a = document.createElement("a"); + a.href = "byond://?_src_=vars;admin_token=" + href_token + ";Vars=" + part[2]; + a.textContent = part[1]; + td2.appendChild(a); + } else { + td2.textContent = part[1]; + } + tr.appendChild(td1); + tr.appendChild(td2); + table.appendChild(tr); + } + document.getElementById("statcontent").appendChild(table); +} + +function remove_tickets() { + if (tickets) { + tickets = []; + removePermanentTab("Tickets"); + if (current_tab == "Tickets") + tab_change("Status"); + } + checkStatusTab(); +} + +function remove_sdql2() { + if (sdql2) { + sdql2 = []; + removePermanentTab("SDQL2"); + if (current_tab == "SDQL2") + tab_change("Status"); + } + checkStatusTab(); +} + +function remove_interviews() { + if (tickets) { + tickets = []; + } + checkStatusTab(); +} + +function iconError(e) { + if(current_tab != turfname) { + return; + } + setTimeout(function () { + var node = e.target; + var current_attempts = Number(node.getAttribute("data-attempts")) || 0 + if (current_attempts > imageRetryLimit) { + return; + } + var src = node.src; + node.src = null; + node.src = src + '#' + current_attempts; + node.setAttribute("data-attempts", current_attempts + 1) + draw_listedturf(); + }, imageRetryDelay); +} + +function draw_listedturf() { + statcontentdiv.textContent = ""; + var table = document.createElement("table"); + for (var i = 0; i < turfcontents.length; i++) { + var part = turfcontents[i]; + var clickfunc = function (part) { + // The outer function is used to close over a fresh "part" variable, + // rather than every onmousedown getting the "part" of the last entry. + return function (e) { + e.preventDefault(); + clickcatcher = "byond://?src=" + part[1]; + switch (e.button) { + case 1: + clickcatcher += ";statpanel_item_click=middle" + break; + case 2: + clickcatcher += ";statpanel_item_click=right" + break; + default: + clickcatcher += ";statpanel_item_click=left" + } + if (e.shiftKey) { + clickcatcher += ";statpanel_item_shiftclick=1"; + } + if (e.ctrlKey) { + clickcatcher += ";statpanel_item_ctrlclick=1"; + } + if (e.altKey) { + clickcatcher += ";statpanel_item_altclick=1"; + } + window.location.href = clickcatcher; + } + }(part); + if (storedimages[part[1]] == null && part[2]) { + var img = document.createElement("img"); + img.src = part[2]; + img.id = part[1]; + storedimages[part[1]] = part[2]; + img.onerror = iconError; + img.onmousedown = clickfunc; + table.appendChild(img); + } else { + var img = document.createElement("img"); + img.onerror = iconError; + img.onmousedown = clickfunc; + img.src = storedimages[part[1]]; + img.id = part[1]; + table.appendChild(img); + } + var b = document.createElement("div"); + var clickcatcher = ""; + b.className = "link"; + b.onmousedown = clickfunc; + b.textContent = part[0]; + table.appendChild(b); + table.appendChild(document.createElement("br")); + } + document.getElementById("statcontent").appendChild(table); +} + +function remove_listedturf() { + removePermanentTab(turfname); + checkStatusTab(); + if (current_tab == turfname) { + tab_change("Status"); + } +} + +function remove_mc() { + removePermanentTab("MC"); + if (current_tab == "MC") { + tab_change("Status"); + } +}; + +function draw_sdql2() { + statcontentdiv.textContent = ""; + var table = document.createElement("table"); + for (var i = 0; i < sdql2.length; i++) { + var part = sdql2[i]; + var tr = document.createElement("tr"); + var td1 = document.createElement("td"); + td1.textContent = part[0]; + var td2 = document.createElement("td"); + if (part[2]) { + var a = document.createElement("a"); + a.href = "byond://?src=" + part[2] + ";statpanel_item_click=left"; + a.textContent = part[1]; + td2.appendChild(a); + } else { + td2.textContent = part[1]; + } + tr.appendChild(td1); + tr.appendChild(td2); + table.appendChild(tr); + } + document.getElementById("statcontent").appendChild(table); +} + +function draw_tickets() { + statcontentdiv.textContent = ""; + var table = document.createElement("table"); + if (!tickets) { + return; + } + for (var i = 0; i < tickets.length; i++) { + var part = tickets[i]; + var tr = document.createElement("tr"); + var td1 = document.createElement("td"); + td1.textContent = part[0]; + var td2 = document.createElement("td"); + if (part[2]) { + var a = document.createElement("a"); + a.href = "byond://?_src_=holder;admin_token=" + href_token + ";ahelp=" + part[2] + ";ahelp_action=ticket;statpanel_item_click=left;action=ticket"; + a.textContent = part[1]; + td2.appendChild(a); + } else if (part[3]) { + var a = document.createElement("a"); + a.href = "byond://?src=" + part[3] + ";statpanel_item_click=left"; + a.textContent = part[1]; + td2.appendChild(a); + } else { + td2.textContent = part[1]; + } + tr.appendChild(td1); + tr.appendChild(td2); + table.appendChild(tr); + } + document.getElementById("statcontent").appendChild(table); +} + +function draw_interviews() { + var body = document.createElement("div"); + var header = document.createElement("h3"); + header.textContent = "Interviews"; + body.appendChild(header); + var manDiv = document.createElement("div"); + manDiv.className = "interview_panel_controls" + var manLink = document.createElement("a"); + manLink.textContent = "Open Interview Manager Panel"; + manLink.href = "byond://?_src_=holder;admin_token=" + href_token + ";interview_man=1;statpanel_item_click=left"; + manDiv.appendChild(manLink); + body.appendChild(manDiv); + + // List interview stats + var statsDiv = document.createElement("table"); + statsDiv.className = "interview_panel_stats"; + for (var key in interviewManager.status) { + var d = document.createElement("div"); + var tr = document.createElement("tr"); + var stat_name = document.createElement("td"); + var stat_text = document.createElement("td"); + stat_name.textContent = key; + stat_text.textContent = interviewManager.status[key]; + tr.appendChild(stat_name); + tr.appendChild(stat_text); + statsDiv.appendChild(tr); + } + body.appendChild(statsDiv); + document.getElementById("statcontent").appendChild(body); + + // List interviews if any are open + var table = document.createElement("table"); + table.className = "interview_panel_table"; + if (!interviewManager) { + return; + } + for (var i = 0; i < interviewManager.interviews.length; i++) { + var part = interviewManager.interviews[i]; + var tr = document.createElement("tr"); + var td = document.createElement("td"); + var a = document.createElement("a"); + a.textContent = part["status"]; + a.href = "byond://?_src_=holder;admin_token=" + href_token + ";interview=" + part["ref"] + ";statpanel_item_click=left"; + td.appendChild(a); + tr.appendChild(td); + table.appendChild(tr); + } + document.getElementById("statcontent").appendChild(table); +} + +function draw_spells(cat) { + statcontentdiv.textContent = ""; + var table = document.createElement("table"); + for (var i = 0; i < spells.length; i++) { + var part = spells[i]; + if (part[0] != cat) continue; + var tr = document.createElement("tr"); + var td1 = document.createElement("td"); + td1.textContent = part[1]; + var td2 = document.createElement("td"); + if (part[3]) { + var a = document.createElement("a"); + a.href = "byond://?src=" + part[3] + ";statpanel_item_click=left"; + a.textContent = part[2]; + td2.appendChild(a); + } else { + td2.textContent = part[2]; + } + tr.appendChild(td1); + tr.appendChild(td2); + table.appendChild(tr); + } + document.getElementById("statcontent").appendChild(table); +} + +function make_verb_onclick(command) { + return function () { + run_after_focus(function () { + Byond.command(command); + }); + }; +} + +function draw_verbs(cat) { + statcontentdiv.textContent = ""; + var table = document.createElement("div"); + var additions = {}; // additional sub-categories to be rendered + table.className = "grid-container"; + sortVerbs(); + if (split_admin_tabs && cat.lastIndexOf(".") != -1) { + var splitName = cat.split("."); + if (splitName[0] === "Admin") + cat = splitName[1]; + } + verbs.reverse(); // sort verbs backwards before we draw + for (var i = 0; i < verbs.length; ++i) { + var part = verbs[i]; + var name = part[0]; + if (split_admin_tabs && name.lastIndexOf(".") != -1) { + var splitName = name.split("."); + if (splitName[0] === "Admin") + name = splitName[1]; + } + var command = part[1]; + + if (command && name.lastIndexOf(cat, 0) != -1 && (name.length == cat.length || name.charAt(cat.length) == ".")) { + var subCat = name.lastIndexOf(".") != -1 ? name.split(".")[1] : null; + if (subCat && !additions[subCat]) { + var newTable = document.createElement("div"); + newTable.className = "grid-container"; + additions[subCat] = newTable; + } + + var a = document.createElement("a"); + a.href = "#"; + a.onclick = make_verb_onclick(command.replace(/\s/g, "-")); + a.className = "grid-item"; + var t = document.createElement("span"); + t.textContent = command; + t.className = "grid-item-text"; + a.appendChild(t); + (subCat ? additions[subCat] : table).appendChild(a); + } + } + + // Append base table to view + var content = document.getElementById("statcontent"); + content.appendChild(table); + + // Append additional sub-categories if relevant + for (var cat in additions) { + if (additions.hasOwnProperty(cat)) { + // do addition here + var header = document.createElement("h3"); + header.textContent = cat; + content.appendChild(header); + content.appendChild(additions[cat]); + } + } +} + +function set_theme(which) { + if (which == "light") { + document.body.className = ""; + document.documentElement.className = 'light'; + set_style_sheet("browserOutput_white"); + } else if (which == "dark") { + document.body.className = "dark"; + document.documentElement.className = 'dark'; + set_style_sheet("browserOutput"); + } +} + +function set_font_size(size) { + document.body.style.setProperty('font-size', size); +} + +function set_tabs_style(style) { + if (style == "default") { + menu.classList.add('menu-wrap'); + menu.classList.remove('tabs-classic'); + } else if (style == "classic") { + menu.classList.add('menu-wrap'); + menu.classList.add('tabs-classic'); + } else if (style == "scrollable") { + menu.classList.remove('menu-wrap'); + menu.classList.remove('tabs-classic'); + } +} + +function set_style_sheet(sheet) { + if (document.getElementById("goonStyle")) { + var currentSheet = document.getElementById("goonStyle"); + currentSheet.parentElement.removeChild(currentSheet); + } + var head = document.getElementsByTagName('head')[0]; + var sheetElement = document.createElement("link"); + sheetElement.id = "goonStyle"; + sheetElement.rel = "stylesheet"; + sheetElement.type = "text/css"; + sheetElement.href = sheet + ".css"; + sheetElement.media = 'all'; + head.appendChild(sheetElement); +} + +function restoreFocus() { + run_after_focus(function () { + Byond.winset('map', { + focus: true, + }); + }); +} + +function getCookie(cname) { + var name = cname + '='; + var ca = document.cookie.split(';'); + for (var i = 0; i < ca.length; i++) { + var c = ca[i]; + while (c.charAt(0) == ' ') c = c.substring(1); + if (c.indexOf(name) === 0) { + return decoder(c.substring(name.length, c.length)); + } + } + return ''; +} + +function add_verb_list(payload) { + var to_add = payload; // list of a list with category and verb inside it + to_add.sort(); // sort what we're adding + for (var i = 0; i < to_add.length; i++) { + var part = to_add[i]; + if (!part[0]) + continue; + var category = part[0]; + if (category.indexOf(".") != -1) { + var splitName = category.split("."); + if (split_admin_tabs && splitName[0] === "Admin") + category = splitName[1]; + else + category = splitName[0]; + } + if (findVerbindex(part[1], verbs)) + continue; + if (verb_tabs.includes(category)) { + verbs.push(part); + if (current_tab == category) { + draw_verbs(category); // redraw if we added a verb to the tab we're currently in + } + } else if (category) { + verb_tabs.push(category); + verbs.push(part); + createStatusTab(category); + } + } +}; + +function init_spells() { + var cat = ""; + for (var i = 0; i < spell_tabs.length; i++) { + cat = spell_tabs[i]; + if (cat.length > 0) { + verb_tabs.push(cat); + createStatusTab(cat); + } + } +} + +document.addEventListener("mouseup", restoreFocus); +document.addEventListener("keyup", restoreFocus); + +if (!current_tab) { + addPermanentTab("Status"); + tab_change("Status"); +} + +window.onload = function () { + Byond.sendMessage("Update-Verbs"); +}; + +Byond.subscribeTo('update_spells', function (payload) { + spell_tabs = payload.spell_tabs; + var do_update = false; + if (spell_tabs.includes(current_tab)) { + do_update = true; + } + init_spells(); + if (payload.actions) { + spells = payload.actions; + if (do_update) { + draw_spells(current_tab); + } + } else { + remove_spells(); + } +}); + +Byond.subscribeTo('remove_verb_list', function (v) { + var to_remove = v; + for (var i = 0; i < to_remove.length; i++) { + remove_verb(to_remove[i]); + } + check_verbs(); + sortVerbs(); + if (verb_tabs.includes(current_tab)) + draw_verbs(current_tab); +}); + +// passes a 2D list of (verbcategory, verbname) creates tabs and adds verbs to respective list +// example (IC, Say) +Byond.subscribeTo('init_verbs', function (payload) { + wipe_verbs(); // remove all verb categories so we can replace them + checkStatusTab(); // remove all status tabs + verb_tabs = payload.panel_tabs; + verb_tabs.sort(); // sort it + var do_update = false; + var cat = ""; + for (var i = 0; i < verb_tabs.length; i++) { + cat = verb_tabs[i]; + createStatusTab(cat); // create a category if the verb doesn't exist yet + } + if (verb_tabs.includes(current_tab)) { + do_update = true; + } + if (payload.verblist) { + add_verb_list(payload.verblist); + sortVerbs(); // sort them + if (do_update) { + draw_verbs(current_tab); + } + } + SendTabsToByond(); +}); + +Byond.subscribeTo('update_stat', function (payload) { + status_tab_parts = [payload.ping_str]; + var parsed = payload.global_data; + + for (var i = 0; i < parsed.length; i++) if (parsed[i] != null) status_tab_parts.push(parsed[i]); + + parsed = payload.other_str; + + for (var i = 0; i < parsed.length; i++) if (parsed[i] != null) status_tab_parts.push(parsed[i]); + + if (current_tab == "Status") { + draw_status(); + } else if (current_tab == "Debug Stat Panel") { + draw_debug(); + } +}); + +Byond.subscribeTo('update_mc', function (payload) { + mc_tab_parts = payload.mc_data; + mc_tab_parts.splice(0, 0, ["Location:", payload.coord_entry]); + + if (!verb_tabs.includes("MC")) { + verb_tabs.push("MC"); + } + + createStatusTab("MC"); + + if (current_tab == "MC") { + draw_mc(); + } +}); + +Byond.subscribeTo('remove_spells', function () { + for (var s = 0; s < spell_tabs.length; s++) { + removeStatusTab(spell_tabs[s]); + } +}); + +Byond.subscribeTo('init_spells', function () { + var cat = ""; + for (var i = 0; i < spell_tabs.length; i++) { + cat = spell_tabs[i]; + if (cat.length > 0) { + verb_tabs.push(cat); + createStatusTab(cat); + } + } +}); + +Byond.subscribeTo('check_spells', function () { + for (var v = 0; v < spell_tabs.length; v++) { + spell_cat_check(spell_tabs[v]); + } +}); + +Byond.subscribeTo('create_debug', function () { + if (!document.getElementById("Debug Stat Panel")) { + addPermanentTab("Debug Stat Panel"); + } else { + removePermanentTab("Debug Stat Panel"); + } +}); + +Byond.subscribeTo('create_listedturf', function (TN) { + remove_listedturf(); // remove the last one if we had one + turfname = TN; + addPermanentTab(turfname); + tab_change(turfname); +}); + +Byond.subscribeTo('remove_admin_tabs', function () { + href_token = null; + remove_mc(); + remove_tickets(); + remove_sdql2(); + remove_interviews(); +}); + +Byond.subscribeTo('update_listedturf', function (TC) { + turfcontents = TC; + if (current_tab == turfname) { + draw_listedturf(); + } +}); + +Byond.subscribeTo('update_interviews', function (I) { + interviewManager = I; + if (current_tab == "Tickets") { + draw_interviews(); + } +}); + +Byond.subscribeTo('update_split_admin_tabs', function (status) { + status = (status == true); + + if (split_admin_tabs !== status) { + if (split_admin_tabs === true) { + removeStatusTab("Events"); + removeStatusTab("Fun"); + removeStatusTab("Game"); + } + update_verbs(); + } + split_admin_tabs = status; +}); + +Byond.subscribeTo('add_admin_tabs', function (ht) { + href_token = ht; + addPermanentTab("MC"); +}); + +Byond.subscribeTo('update_sdql2', function (S) { + sdql2 = S; + if (sdql2.length > 0 && !verb_tabs.includes("SDQL2")) { + verb_tabs.push("SDQL2"); + addPermanentTab("SDQL2"); + } + if (current_tab == "SDQL2") { + draw_sdql2(); + } +}); + +Byond.subscribeTo('update_tickets', function (T) { + tickets = T; + if (current_tab == "Tickets") { + draw_tickets(); + } +}); + +Byond.subscribeTo('remove_listedturf', remove_listedturf); + +Byond.subscribeTo('remove_sdql2', remove_sdql2); + +Byond.subscribeTo('remove_mc', remove_mc); + +Byond.subscribeTo('add_verb_list', add_verb_list); diff --git a/interface/fonts/Grand9K_Pixel.ttf b/interface/fonts/Grand9K_Pixel.ttf new file mode 100644 index 0000000000000..cf6fdf44e2ec7 Binary files /dev/null and b/interface/fonts/Grand9K_Pixel.ttf differ diff --git a/interface/fonts/Pixellari.ttf b/interface/fonts/Pixellari.ttf new file mode 100644 index 0000000000000..5a3a3c2b11048 Binary files /dev/null and b/interface/fonts/Pixellari.ttf differ diff --git a/interface/fonts/SpessFont.ttf b/interface/fonts/SpessFont.ttf new file mode 100644 index 0000000000000..8f7c7e08d0d86 Binary files /dev/null and b/interface/fonts/SpessFont.ttf differ diff --git a/interface/fonts/TinyUnicode.ttf b/interface/fonts/TinyUnicode.ttf new file mode 100644 index 0000000000000..74d0d3e386e61 Binary files /dev/null and b/interface/fonts/TinyUnicode.ttf differ diff --git a/interface/fonts/VCR_OSD_Mono.ttf b/interface/fonts/VCR_OSD_Mono.ttf new file mode 100644 index 0000000000000..dcca687a434d5 Binary files /dev/null and b/interface/fonts/VCR_OSD_Mono.ttf differ diff --git a/interface/fonts/fonts_datum.dm b/interface/fonts/fonts_datum.dm new file mode 100644 index 0000000000000..a346706d7fa0f --- /dev/null +++ b/interface/fonts/fonts_datum.dm @@ -0,0 +1,78 @@ +/// A font datum, it exists to define a custom font to use in a span style later. +/datum/font + /// Font name, just so people know what to put in their span style. + var/name + /// The font file we link to. + var/font_family + + /// Font features and metrics + /// Generated by Lummox's dmifontsplus (https://www.byond.com/developer/LummoxJR/DmiFontsPlus) + /// Note: these variable names have been changed, so you can't straight copy/paste from dmifontsplus.exe + + /// list of font size/spacing metrics + var/list/metrics + /// total height of a line + var/height + /// distance above baseline (including whitespace) + var/ascent + /// distance below baseline + var/descent + /// average character width + var/average_width + /// maximum character width + var/max_width + /// extra width, such as from italics, for a line + var/overhang + /// internal leading vertical space, for accent marks + var/in_leading + /// external leading vertical space, just plain blank + var/ex_leading + /// default character (for undefined chars) + var/default_character + /// first character in metrics + var/start + /// last character in metrics + var/end + +/// Get font metrics +/// From Lummox's dmifontsplus (https://www.byond.com/developer/LummoxJR/DmiFontsPlus) +/datum/font/proc/get_metrics(text, flags, first_line) + . = 0 + var/longest = 0 + if(!length(text)) + return + + var/i = 1 + var/idx + while(i <= length(text)) + var/character = text2ascii(text, i++) + if(character <= 10) + if(character <= 7) + . += character // spacers for justification + + if(character <= 9) + continue // soft-break chars + + if(. && idx && !(flags & INCLUDE_AC)) + . -= max(metrics[idx + 3], 0) + + longest = max(longest, . + first_line) + . = 0 + first_line = 0 + idx = 0 + continue + + idx = (character - start) * 3 + if(idx <= 0 || idx >= metrics.len) + idx = (default_character - start) * 3 + + if(!. && !(flags & INCLUDE_AC)) + . -= metrics[idx + 1] + . += metrics[idx + 1] + metrics[idx + 2] + metrics[idx +3] + + if(. && idx && !(flags & INCLUDE_AC)) + . -= max(metrics[idx + 3], 0) + + . = max(. + first_line, longest) + if(. > 0) + . += overhang diff --git a/interface/fonts/grand_9k.dm b/interface/fonts/grand_9k.dm new file mode 100644 index 0000000000000..7993d307bcbe5 --- /dev/null +++ b/interface/fonts/grand_9k.dm @@ -0,0 +1,253 @@ +/// For clean results on map, use only sizing pt, multiples of 6: 6pt 12pt 18pt 24pt etc. - Not for use with px sizing +/// Can be used in TGUI etc, px sizing is pt / 0.75. 6pt = 8px, 12pt = 16px etc. + +/// Base font +/datum/font/grand9k + name = "Grand9K Pixel" + font_family = 'interface/fonts/Grand9K_Pixel.ttf' + +/// For icon overlays +/// Grand9K 6pt metrics generated using Lummox's dmifontsplus (https://www.byond.com/developer/LummoxJR/DmiFontsPlus) +/// Note: these variable names have been changed, so you can't straight copy/paste from dmifontsplus.exe +/datum/font/grand9k/size_6pt + name = "Grand9K Pixel 6pt" + height = 12 + ascent = 10 + descent = 2 + average_width = 4 + max_width = 9 + overhang = 0 + in_leading = 4 + ex_leading = 1 + default_character = 31 + start = 30 + end = 255 + metrics = list( + 0, 5, 1, // char 30 + 0, 5, 1, // char 31 + 0, 1, 1, // char 32 + 0, 1, 1, // char 33 + 0, 3, 1, // char 34 + 0, 6, 1, // char 35 + 0, 5, 1, // char 36 + 0, 7, 1, // char 37 + 0, 5, 1, // char 38 + 0, 1, 1, // char 39 + 0, 3, 1, // char 40 + 0, 3, 1, // char 41 + 0, 5, 1, // char 42 + 0, 5, 1, // char 43 + 0, 1, 1, // char 44 + 0, 4, 1, // char 45 + 0, 1, 1, // char 46 + 0, 3, 1, // char 47 + 0, 5, 1, // char 48 + 0, 2, 1, // char 49 + 0, 5, 1, // char 50 + 0, 4, 1, // char 51 + 0, 5, 1, // char 52 + 0, 5, 1, // char 53 + 0, 5, 1, // char 54 + 0, 5, 1, // char 55 + 0, 5, 1, // char 56 + 0, 5, 1, // char 57 + 0, 1, 1, // char 58 + 0, 1, 1, // char 59 + 0, 4, 1, // char 60 + 0, 4, 1, // char 61 + 0, 4, 1, // char 62 + 0, 4, 1, // char 63 + 0, 7, 1, // char 64 + 0, 5, 1, // char 65 + 0, 5, 1, // char 66 + 0, 4, 1, // char 67 + 0, 5, 1, // char 68 + 0, 4, 1, // char 69 + 0, 4, 1, // char 70 + 0, 5, 1, // char 71 + 0, 5, 1, // char 72 + 0, 1, 1, // char 73 + 0, 5, 1, // char 74 + 0, 5, 1, // char 75 + 0, 5, 1, // char 76 + 0, 5, 1, // char 77 + 0, 5, 1, // char 78 + 0, 5, 1, // char 79 + 0, 5, 1, // char 80 + 0, 6, 1, // char 81 + 0, 5, 1, // char 82 + 0, 5, 1, // char 83 + 0, 5, 1, // char 84 + 0, 5, 1, // char 85 + 0, 5, 1, // char 86 + 0, 5, 1, // char 87 + 0, 5, 1, // char 88 + 0, 5, 1, // char 89 + 0, 5, 1, // char 90 + 0, 3, 1, // char 91 + 0, 3, 1, // char 92 + 0, 3, 1, // char 93 + 0, 5, 1, // char 94 + 0, 4, 0, // char 95 + 0, 2, 1, // char 96 + 0, 4, 1, // char 97 + 0, 4, 1, // char 98 + 0, 3, 1, // char 99 + 0, 4, 1, // char 100 + 0, 4, 1, // char 101 + 0, 4, 1, // char 102 + 0, 4, 1, // char 103 + 0, 4, 1, // char 104 + 0, 1, 1, // char 105 + 0, 3, 1, // char 106 + 0, 4, 1, // char 107 + 0, 1, 1, // char 108 + 0, 5, 1, // char 109 + 0, 4, 1, // char 110 + 0, 4, 1, // char 111 + 0, 4, 1, // char 112 + 0, 4, 1, // char 113 + 0, 4, 1, // char 114 + 0, 4, 1, // char 115 + 0, 4, 1, // char 116 + 0, 4, 1, // char 117 + 0, 5, 1, // char 118 + 0, 5, 1, // char 119 + 0, 5, 1, // char 120 + 0, 4, 1, // char 121 + 0, 5, 1, // char 122 + 0, 4, 1, // char 123 + 0, 1, 1, // char 124 + 0, 4, 1, // char 125 + 0, 6, 1, // char 126 + 0, 5, 1, // char 127 + 0, 5, 1, // char 128 + 0, 5, 1, // char 129 + 0, 1, 1, // char 130 + 0, 5, 1, // char 131 + 0, 3, 1, // char 132 + 0, 5, 1, // char 133 + 0, 5, 1, // char 134 + 0, 5, 1, // char 135 + 0, 5, 1, // char 136 + 0, 5, 1, // char 137 + 0, 5, 1, // char 138 + 0, 3, 1, // char 139 + 0, 6, 1, // char 140 + 0, 5, 1, // char 141 + 0, 5, 1, // char 142 + 0, 5, 1, // char 143 + 0, 5, 1, // char 144 + 0, 1, 1, // char 145 + 0, 1, 1, // char 146 + 0, 3, 1, // char 147 + 0, 3, 1, // char 148 + 0, 1, 1, // char 149 + 0, 5, 1, // char 150 + 0, 5, 1, // char 151 + 0, 5, 1, // char 152 + 0, 8, 1, // char 153 + 0, 4, 1, // char 154 + 0, 3, 1, // char 155 + 0, 5, 1, // char 156 + 0, 5, 1, // char 157 + 0, 5, 1, // char 158 + 0, 5, 1, // char 159 + 0, 1, 1, // char 160 + 0, 1, 1, // char 161 + 0, 4, 1, // char 162 + 0, 5, 1, // char 163 + 0, 5, 1, // char 164 + 0, 5, 1, // char 165 + 0, 1, 1, // char 166 + 0, 5, 1, // char 167 + 0, 3, 1, // char 168 + 0, 8, 1, // char 169 + 0, 5, 1, // char 170 + 0, 6, 1, // char 171 + 0, 4, 1, // char 172 + 0, 5, 1, // char 173 + 0, 8, 1, // char 174 + 0, 5, 1, // char 175 + 0, 3, 1, // char 176 + 0, 5, 1, // char 177 + 0, 5, 1, // char 178 + 0, 5, 1, // char 179 + 0, 2, 1, // char 180 + 0, 4, 1, // char 181 + 0, 5, 1, // char 182 + 0, 1, 1, // char 183 + 0, 2, 1, // char 184 + 0, 5, 1, // char 185 + 0, 5, 1, // char 186 + 0, 6, 1, // char 187 + 0, 5, 1, // char 188 + 0, 5, 1, // char 189 + 0, 5, 1, // char 190 + 0, 4, 1, // char 191 + 0, 5, 1, // char 192 + 0, 5, 1, // char 193 + 0, 5, 1, // char 194 + 0, 6, 0, // char 195 + 0, 5, 1, // char 196 + 0, 5, 1, // char 197 + 0, 6, 1, // char 198 + 0, 4, 1, // char 199 + 0, 4, 1, // char 200 + 0, 4, 1, // char 201 + 0, 4, 1, // char 202 + 0, 4, 1, // char 203 + 1, 2, 0, // char 204 + 0, 2, 1, // char 205 + 0, 3, 0, // char 206 + 0, 3, 0, // char 207 + 0, 6, 1, // char 208 + 0, 6, 0, // char 209 + 0, 5, 1, // char 210 + 0, 5, 1, // char 211 + 0, 5, 1, // char 212 + 0, 6, 1, // char 213 + 0, 5, 1, // char 214 + 0, 5, 1, // char 215 + 0, 5, 1, // char 216 + 0, 5, 1, // char 217 + 0, 5, 1, // char 218 + 0, 5, 1, // char 219 + 0, 5, 1, // char 220 + 0, 5, 1, // char 221 + 0, 5, 1, // char 222 + 0, 5, 1, // char 223 + 0, 4, 1, // char 224 + 0, 4, 1, // char 225 + 0, 4, 1, // char 226 + 0, 4, 1, // char 227 + 0, 4, 1, // char 228 + 0, 4, 1, // char 229 + 0, 5, 1, // char 230 + 0, 3, 1, // char 231 + 0, 4, 1, // char 232 + 0, 4, 1, // char 233 + 0, 4, 1, // char 234 + 0, 4, 1, // char 235 + 0, 2, 1, // char 236 + 1, 2, 0, // char 237 + 0, 3, 0, // char 238 + 0, 3, 0, // char 239 + 0, 5, 0, // char 240 + 0, 4, 1, // char 241 + 0, 4, 1, // char 242 + 0, 4, 1, // char 243 + 0, 4, 1, // char 244 + 0, 4, 1, // char 245 + 0, 4, 1, // char 246 + 0, 5, 1, // char 247 + 0, 4, 1, // char 248 + 0, 4, 1, // char 249 + 0, 4, 1, // char 250 + 0, 4, 1, // char 251 + 0, 4, 1, // char 252 + 0, 4, 1, // char 253 + 0, 4, 1, // char 254 + 0, 4, 1, // char 255 + 226 + ) diff --git a/interface/fonts/license.txt b/interface/fonts/license.txt new file mode 100644 index 0000000000000..9aa70fbac2a9e --- /dev/null +++ b/interface/fonts/license.txt @@ -0,0 +1,13 @@ +Grand9K Pixel created by Jayvee Enaguas. Licensed under Creative Commons Attribution 4.0 International (CC BY 4.0) +(https://creativecommons.org/licenses/by/4.0/) (https://www.dafont.com/grand9k-pixel.font) + +Pixellari created by Zacchary Dempsey-Plante. Website indicates free for commercial use. +(https://www.dafont.com/pixellari.font?fpp=200) + +Spess Font created by MTandi (discord) for /tg/station. + +Tiny Unicode created by Jakob Riedle/DuffsDevice. Website indicates free for commercial use. +(https://fontmeme.com/fonts/tiny-unicode-font/) + +VCR OSD Mono created by Riciery Leal/mrmanet. Website indicates 100% free, author confirms it's free for all to use. +(https://www.dafont.com/font-comment.php?file=vcr_osd_mono) diff --git a/interface/fonts/pixellari.dm b/interface/fonts/pixellari.dm new file mode 100644 index 0000000000000..24fcd1961fecf --- /dev/null +++ b/interface/fonts/pixellari.dm @@ -0,0 +1,252 @@ +/// For clean results on map, use only sizing pt, multiples of 12: 12pt 24pt 48pt etc. - Not for use with px sizing +/// Can be used in TGUI etc, px sizing is pt / 0.75. 12pt = 16px, 24pt = 32px etc. + +/// Base font +/datum/font/pixellari + name = "Pixellari" + font_family = 'interface/fonts/Pixellari.ttf' + +/// For icon overlays +/// Pixellari 12pt metrics generated using Lummox's dmifontsplus (https://www.byond.com/developer/LummoxJR/DmiFontsPlus) +/// Note: these variable names have been changed, so you can't straight copy/paste from dmifontsplus.exe +/datum/font/pixellari/size_12pt + name = "Pixellari 12pt" + height = 16 + ascent = 12 + descent = 4 + average_width = 7 + max_width = 15 + overhang = 0 + in_leading = 0 + ex_leading = 1 + default_character = 31 + start = 30 + end = 255 + metrics = list(\ + 1, 5, 0, /* char 30 */ \ + 1, 5, 0, /* char 31 */ \ + 0, 1, 4, /* char 32 */ \ + 1, 2, 1, /* char 33 */ \ + 1, 5, 1, /* char 34 */ \ + 0, 8, 1, /* char 35 */ \ + 2, 6, 1, /* char 36 */ \ + 0, 13, 1, /* char 37 */ \ + 1, 8, 1, /* char 38 */ \ + 1, 2, 1, /* char 39 */ \ + 1, 3, 1, /* char 40 */ \ + 2, 3, 1, /* char 41 */ \ + 0, 6, 1, /* char 42 */ \ + 1, 6, 1, /* char 43 */ \ + 1, 2, 1, /* char 44 */ \ + 1, 6, 1, /* char 45 */ \ + 1, 2, 1, /* char 46 */ \ + 0, 6, 1, /* char 47 */ \ + 1, 7, 1, /* char 48 */ \ + 2, 6, 1, /* char 49 */ \ + 1, 6, 1, /* char 50 */ \ + 1, 6, 1, /* char 51 */ \ + 1, 7, 1, /* char 52 */ \ + 1, 6, 1, /* char 53 */ \ + 1, 6, 1, /* char 54 */ \ + 1, 7, 1, /* char 55 */ \ + 1, 6, 1, /* char 56 */ \ + 1, 6, 1, /* char 57 */ \ + 1, 2, 1, /* char 58 */ \ + 1, 2, 1, /* char 59 */ \ + 0, 10, 1, /* char 60 */ \ + 1, 6, 1, /* char 61 */ \ + 0, 10, 1, /* char 62 */ \ + 1, 6, 1, /* char 63 */ \ + 1, 12, 1, /* char 64 */ \ + 1, 8, 1, /* char 65 */ \ + 1, 8, 1, /* char 66 */ \ + 2, 7, 1, /* char 67 */ \ + 2, 8, 1, /* char 68 */ \ + 2, 6, 1, /* char 69 */ \ + 2, 6, 1, /* char 70 */ \ + 2, 7, 1, /* char 71 */ \ + 1, 8, 1, /* char 72 */ \ + 1, 4, 1, /* char 73 */ \ + 0, 7, 1, /* char 74 */ \ + 1, 8, 1, /* char 75 */ \ + 1, 6, 1, /* char 76 */ \ + 1, 10, 1, /* char 77 */ \ + 1, 9, 1, /* char 78 */ \ + 2, 8, 1, /* char 79 */ \ + 1, 7, 1, /* char 80 */ \ + 2, 9, 1, /* char 81 */ \ + 1, 8, 1, /* char 82 */ \ + 1, 8, 1, /* char 83 */ \ + 1, 8, 1, /* char 84 */ \ + 2, 8, 1, /* char 85 */ \ + 2, 8, 1, /* char 86 */ \ + 1, 10, 1, /* char 87 */ \ + 1, 8, 1, /* char 88 */ \ + 1, 8, 1, /* char 89 */ \ + 0, 10, 1, /* char 90 */ \ + 1, 3, 1, /* char 91 */ \ + 0, 6, 1, /* char 92 */ \ + 2, 3, 1, /* char 93 */ \ + 0, 7, 1, /* char 94 */ \ + 0, 8, 1, /* char 95 */ \ + 1, 3, 1, /* char 96 */ \ + 1, 6, 1, /* char 97 */ \ + 1, 7, 1, /* char 98 */ \ + 1, 6, 1, /* char 99 */ \ + 1, 7, 1, /* char 100 */ \ + 1, 6, 1, /* char 101 */ \ + 1, 4, 1, /* char 102 */ \ + 1, 7, 1, /* char 103 */ \ + 1, 7, 1, /* char 104 */ \ + 1, 2, 1, /* char 105 */ \ + -1, 4, 1, /* char 106 */ \ + 0, 7, 1, /* char 107 */ \ + 1, 2, 1, /* char 108 */ \ + 1, 10, 1, /* char 109 */ \ + 1, 6, 1, /* char 110 */ \ + 1, 6, 1, /* char 111 */ \ + 1, 7, 1, /* char 112 */ \ + 1, 7, 1, /* char 113 */ \ + 1, 6, 1, /* char 114 */ \ + 1, 6, 1, /* char 115 */ \ + 0, 4, 1, /* char 116 */ \ + 1, 6, 1, /* char 117 */ \ + 1, 6, 1, /* char 118 */ \ + 1, 10, 1, /* char 119 */ \ + 1, 6, 1, /* char 120 */ \ + 1, 6, 1, /* char 121 */ \ + 1, 6, 1, /* char 122 */ \ + 0, 5, 1, /* char 123 */ \ + 1, 2, 1, /* char 124 */ \ + 0, 5, 1, /* char 125 */ \ + 1, 8, 1, /* char 126 */ \ + 1, 5, 0, /* char 127 */ \ + 1, 8, 1, /* char 128 */ \ + 1, 5, 0, /* char 129 */ \ + 1, 5, 0, /* char 130 */ \ + 1, 5, 0, /* char 131 */ \ + 1, 5, 0, /* char 132 */ \ + 1, 5, 0, /* char 133 */ \ + 1, 5, 0, /* char 134 */ \ + 1, 5, 0, /* char 135 */ \ + 1, 5, 0, /* char 136 */ \ + 1, 5, 0, /* char 137 */ \ + 1, 8, 1, /* char 138 */ \ + 1, 5, 0, /* char 139 */ \ + 0, 14, 1, /* char 140 */ \ + 1, 5, 0, /* char 141 */ \ + 0, 10, 1, /* char 142 */ \ + 1, 5, 0, /* char 143 */ \ + 1, 5, 0, /* char 144 */ \ + 1, 5, 0, /* char 145 */ \ + 1, 5, 0, /* char 146 */ \ + 1, 5, 0, /* char 147 */ \ + 1, 5, 0, /* char 148 */ \ + 1, 5, 0, /* char 149 */ \ + 1, 5, 0, /* char 150 */ \ + 1, 5, 0, /* char 151 */ \ + 1, 5, 0, /* char 152 */ \ + 1, 5, 0, /* char 153 */ \ + 1, 6, 1, /* char 154 */ \ + 1, 5, 0, /* char 155 */ \ + 1, 11, 1, /* char 156 */ \ + 1, 5, 0, /* char 157 */ \ + 1, 6, 1, /* char 158 */ \ + 1, 8, 1, /* char 159 */ \ + 0, 1, 4, /* char 160 */ \ + 1, 2, 1, /* char 161 */ \ + 1, 6, 1, /* char 162 */ \ + 0, 8, 1, /* char 163 */ \ + 0, 9, 1, /* char 164 */ \ + 1, 8, 1, /* char 165 */ \ + 1, 2, 1, /* char 166 */ \ + 1, 7, 1, /* char 167 */ \ + 0, 5, 1, /* char 168 */ \ + -1, 12, 1, /* char 169 */ \ + 0, 6, 1, /* char 170 */ \ + 0, 8, 1, /* char 171 */ \ + 1, 8, 1, /* char 172 */ \ + 1, 5, 0, /* char 173 */ \ + -1, 12, 1, /* char 174 */ \ + 2, 4, 1, /* char 175 */ \ + 0, 6, 1, /* char 176 */ \ + 1, 6, 1, /* char 177 */ \ + 0, 5, 1, /* char 178 */ \ + 0, 5, 1, /* char 179 */ \ + 1, 3, 1, /* char 180 */ \ + 1, 6, 1, /* char 181 */ \ + 1, 7, 1, /* char 182 */ \ + 1, 2, 1, /* char 183 */ \ + 1, 3, 1, /* char 184 */ \ + 1, 4, 1, /* char 185 */ \ + 0, 6, 1, /* char 186 */ \ + 0, 8, 1, /* char 187 */ \ + 1, 13, 1, /* char 188 */ \ + 1, 12, 1, /* char 189 */ \ + 0, 13, 1, /* char 190 */ \ + 1, 6, 1, /* char 191 */ \ + 1, 8, 1, /* char 192 */ \ + 1, 8, 1, /* char 193 */ \ + 1, 8, 1, /* char 194 */ \ + 1, 8, 1, /* char 195 */ \ + 1, 8, 1, /* char 196 */ \ + 1, 8, 1, /* char 197 */ \ + 0, 13, 1, /* char 198 */ \ + 2, 7, 1, /* char 199 */ \ + 2, 6, 1, /* char 200 */ \ + 2, 6, 1, /* char 201 */ \ + 2, 6, 1, /* char 202 */ \ + 2, 6, 1, /* char 203 */ \ + 1, 4, 1, /* char 204 */ \ + 1, 4, 1, /* char 205 */ \ + 1, 4, 1, /* char 206 */ \ + 1, 4, 1, /* char 207 */ \ + 0, 10, 1, /* char 208 */ \ + 1, 9, 1, /* char 209 */ \ + 2, 8, 1, /* char 210 */ \ + 2, 8, 1, /* char 211 */ \ + 2, 8, 1, /* char 212 */ \ + 2, 8, 1, /* char 213 */ \ + 2, 8, 1, /* char 214 */ \ + 1, 6, 1, /* char 215 */ \ + -2, 14, 1, /* char 216 */ \ + 2, 8, 1, /* char 217 */ \ + 2, 8, 1, /* char 218 */ \ + 2, 8, 1, /* char 219 */ \ + 2, 8, 1, /* char 220 */ \ + 1, 8, 1, /* char 221 */ \ + 1, 8, 1, /* char 222 */ \ + 1, 8, 1, /* char 223 */ \ + 1, 6, 1, /* char 224 */ \ + 1, 6, 1, /* char 225 */ \ + 1, 6, 1, /* char 226 */ \ + 1, 6, 1, /* char 227 */ \ + 1, 6, 1, /* char 228 */ \ + 1, 6, 1, /* char 229 */ \ + 1, 11, 1, /* char 230 */ \ + 1, 6, 1, /* char 231 */ \ + 1, 6, 1, /* char 232 */ \ + 1, 6, 1, /* char 233 */ \ + 1, 6, 1, /* char 234 */ \ + 1, 6, 1, /* char 235 */ \ + 1, 2, 1, /* char 236 */ \ + 1, 2, 1, /* char 237 */ \ + 0, 4, 1, /* char 238 */ \ + 0, 4, 1, /* char 239 */ \ + 1, 7, 1, /* char 240 */ \ + 1, 6, 1, /* char 241 */ \ + 1, 6, 1, /* char 242 */ \ + 1, 6, 1, /* char 243 */ \ + 1, 6, 1, /* char 244 */ \ + 1, 6, 1, /* char 245 */ \ + 1, 6, 1, /* char 246 */ \ + 1, 6, 1, /* char 247 */ \ + 0, 10, 1, /* char 248 */ \ + 1, 6, 1, /* char 249 */ \ + 1, 6, 1, /* char 250 */ \ + 1, 6, 1, /* char 251 */ \ + 1, 6, 1, /* char 252 */ \ + 1, 6, 1, /* char 253 */ \ + 1, 8, 1, /* char 254 */ \ + 1, 6, 1, /* char 255 */ \ + 226) diff --git a/interface/fonts/spess_font.dm b/interface/fonts/spess_font.dm new file mode 100644 index 0000000000000..07e8ea5b3ba66 --- /dev/null +++ b/interface/fonts/spess_font.dm @@ -0,0 +1,252 @@ +/// For clean results on map, use only sizing pt, multiples of 6: 6t 12pt 18pt etc. - Not for use with px sizing +/// Can be used in TGUI etc, px sizing is pt / 0.75. 12pt = 16px, 24pt = 32px etc. + +/// Base font +/datum/font/spessfont + name = "Spess Font" + font_family = 'interface/fonts/SpessFont.ttf' + +/// For icon overlays +/// Spess Font 6pt metrics generated using Lummox's dmifontsplus (https://www.byond.com/developer/LummoxJR/DmiFontsPlus) +/// Note: these variable names have been changed, so you can't straight copy/paste from dmifontsplus.exe +/datum/font/spessfont/size_6pt + name = "Spess Font 6pt" + height = 8 + ascent = 6 + descent = 2 + average_width = 4 + max_width = 6 + overhang = 0 + in_leading = 0 + ex_leading = 0 + default_character = 31 + start = 30 + end = 255 + metrics = list(\ + 0, 1, 0, /* char 30 */ \ + 0, 1, 0, /* char 31 */ \ + 0, 1, 1, /* char 32 */ \ + 0, 1, 1, /* char 33 */ \ + 0, 3, 1, /* char 34 */ \ + 0, 5, 1, /* char 35 */ \ + 0, 3, 1, /* char 36 */ \ + 0, 5, 1, /* char 37 */ \ + 0, 5, 1, /* char 38 */ \ + 0, 1, 1, /* char 39 */ \ + 0, 2, 1, /* char 40 */ \ + 0, 2, 1, /* char 41 */ \ + 0, 3, 1, /* char 42 */ \ + 0, 3, 1, /* char 43 */ \ + 0, 1, 1, /* char 44 */ \ + 0, 3, 1, /* char 45 */ \ + 0, 1, 1, /* char 46 */ \ + 0, 3, 1, /* char 47 */ \ + 0, 4, 1, /* char 48 */ \ + 0, 2, 1, /* char 49 */ \ + 0, 4, 1, /* char 50 */ \ + 0, 4, 1, /* char 51 */ \ + 0, 4, 1, /* char 52 */ \ + 0, 4, 1, /* char 53 */ \ + 0, 4, 1, /* char 54 */ \ + 0, 4, 1, /* char 55 */ \ + 0, 4, 1, /* char 56 */ \ + 0, 4, 1, /* char 57 */ \ + 0, 1, 1, /* char 58 */ \ + 0, 1, 1, /* char 59 */ \ + 0, 3, 1, /* char 60 */ \ + 0, 3, 1, /* char 61 */ \ + 0, 3, 1, /* char 62 */ \ + 0, 3, 1, /* char 63 */ \ + 0, 4, 1, /* char 64 */ \ + 0, 4, 1, /* char 65 */ \ + 0, 4, 1, /* char 66 */ \ + 0, 4, 1, /* char 67 */ \ + 0, 4, 1, /* char 68 */ \ + 0, 4, 1, /* char 69 */ \ + 0, 4, 1, /* char 70 */ \ + 0, 4, 1, /* char 71 */ \ + 0, 4, 1, /* char 72 */ \ + 0, 3, 1, /* char 73 */ \ + 0, 4, 1, /* char 74 */ \ + 0, 4, 1, /* char 75 */ \ + 0, 4, 1, /* char 76 */ \ + 0, 5, 1, /* char 77 */ \ + 0, 4, 1, /* char 78 */ \ + 0, 4, 1, /* char 79 */ \ + 0, 4, 1, /* char 80 */ \ + 0, 4, 1, /* char 81 */ \ + 0, 4, 1, /* char 82 */ \ + 0, 4, 1, /* char 83 */ \ + 0, 5, 1, /* char 84 */ \ + 0, 4, 1, /* char 85 */ \ + 0, 4, 1, /* char 86 */ \ + 0, 5, 1, /* char 87 */ \ + 0, 5, 1, /* char 88 */ \ + 0, 4, 1, /* char 89 */ \ + 0, 4, 1, /* char 90 */ \ + 0, 2, 1, /* char 91 */ \ + 0, 3, 1, /* char 92 */ \ + 0, 2, 1, /* char 93 */ \ + 0, 3, 1, /* char 94 */ \ + 0, 4, 1, /* char 95 */ \ + 0, 2, 1, /* char 96 */ \ + 0, 3, 1, /* char 97 */ \ + 0, 4, 1, /* char 98 */ \ + 0, 3, 1, /* char 99 */ \ + 0, 4, 1, /* char 100 */ \ + 0, 3, 1, /* char 101 */ \ + 0, 2, 1, /* char 102 */ \ + 0, 4, 1, /* char 103 */ \ + 0, 3, 1, /* char 104 */ \ + 0, 1, 1, /* char 105 */ \ + 0, 1, 1, /* char 106 */ \ + 0, 3, 1, /* char 107 */ \ + 0, 1, 1, /* char 108 */ \ + 0, 5, 1, /* char 109 */ \ + 0, 3, 1, /* char 110 */ \ + 0, 4, 1, /* char 111 */ \ + 0, 4, 1, /* char 112 */ \ + 0, 4, 1, /* char 113 */ \ + 0, 2, 1, /* char 114 */ \ + 0, 3, 1, /* char 115 */ \ + 0, 2, 1, /* char 116 */ \ + 0, 3, 1, /* char 117 */ \ + 0, 3, 1, /* char 118 */ \ + 0, 5, 1, /* char 119 */ \ + 0, 3, 1, /* char 120 */ \ + 0, 3, 1, /* char 121 */ \ + 0, 3, 1, /* char 122 */ \ + 0, 3, 1, /* char 123 */ \ + 0, 1, 1, /* char 124 */ \ + 0, 3, 1, /* char 125 */ \ + 0, 4, 1, /* char 126 */ \ + 0, 1, 0, /* char 127 */ \ + 0, 1, 0, /* char 128 */ \ + 0, 1, 0, /* char 129 */ \ + 0, 1, 0, /* char 130 */ \ + 0, 1, 0, /* char 131 */ \ + 0, 1, 0, /* char 132 */ \ + 0, 1, 0, /* char 133 */ \ + 0, 1, 0, /* char 134 */ \ + 0, 1, 0, /* char 135 */ \ + 0, 1, 0, /* char 136 */ \ + 0, 1, 0, /* char 137 */ \ + 0, 1, 0, /* char 138 */ \ + 0, 1, 0, /* char 139 */ \ + 0, 1, 0, /* char 140 */ \ + 0, 1, 0, /* char 141 */ \ + 0, 1, 0, /* char 142 */ \ + 0, 1, 0, /* char 143 */ \ + 0, 1, 0, /* char 144 */ \ + 0, 1, 0, /* char 145 */ \ + 0, 1, 0, /* char 146 */ \ + 0, 1, 0, /* char 147 */ \ + 0, 1, 0, /* char 148 */ \ + 0, 1, 0, /* char 149 */ \ + 0, 1, 0, /* char 150 */ \ + 0, 1, 0, /* char 151 */ \ + 0, 1, 0, /* char 152 */ \ + 0, 1, 0, /* char 153 */ \ + 0, 1, 0, /* char 154 */ \ + 0, 1, 0, /* char 155 */ \ + 0, 1, 0, /* char 156 */ \ + 0, 1, 0, /* char 157 */ \ + 0, 1, 0, /* char 158 */ \ + 0, 1, 0, /* char 159 */ \ + 0, 1, 0, /* char 160 */ \ + 0, 1, 0, /* char 161 */ \ + 0, 1, 0, /* char 162 */ \ + 0, 1, 0, /* char 163 */ \ + 0, 1, 0, /* char 164 */ \ + 0, 1, 0, /* char 165 */ \ + 0, 1, 0, /* char 166 */ \ + 0, 1, 0, /* char 167 */ \ + 0, 1, 0, /* char 168 */ \ + 0, 1, 0, /* char 169 */ \ + 0, 1, 0, /* char 170 */ \ + 0, 1, 0, /* char 171 */ \ + 0, 1, 0, /* char 172 */ \ + 0, 1, 0, /* char 173 */ \ + 0, 1, 0, /* char 174 */ \ + 0, 1, 0, /* char 175 */ \ + 0, 1, 0, /* char 176 */ \ + 0, 1, 0, /* char 177 */ \ + 0, 1, 0, /* char 178 */ \ + 0, 1, 0, /* char 179 */ \ + 0, 1, 0, /* char 180 */ \ + 0, 1, 0, /* char 181 */ \ + 0, 1, 0, /* char 182 */ \ + 0, 1, 0, /* char 183 */ \ + 0, 1, 0, /* char 184 */ \ + 0, 1, 0, /* char 185 */ \ + 0, 1, 0, /* char 186 */ \ + 0, 1, 0, /* char 187 */ \ + 0, 1, 0, /* char 188 */ \ + 0, 1, 0, /* char 189 */ \ + 0, 1, 0, /* char 190 */ \ + 0, 1, 0, /* char 191 */ \ + 0, 1, 0, /* char 192 */ \ + 0, 1, 0, /* char 193 */ \ + 0, 1, 0, /* char 194 */ \ + 0, 1, 0, /* char 195 */ \ + 0, 1, 0, /* char 196 */ \ + 0, 1, 0, /* char 197 */ \ + 0, 1, 0, /* char 198 */ \ + 0, 1, 0, /* char 199 */ \ + 0, 1, 0, /* char 200 */ \ + 0, 1, 0, /* char 201 */ \ + 0, 1, 0, /* char 202 */ \ + 0, 1, 0, /* char 203 */ \ + 0, 1, 0, /* char 204 */ \ + 0, 1, 0, /* char 205 */ \ + 0, 1, 0, /* char 206 */ \ + 0, 1, 0, /* char 207 */ \ + 0, 1, 0, /* char 208 */ \ + 0, 1, 0, /* char 209 */ \ + 0, 1, 0, /* char 210 */ \ + 0, 1, 0, /* char 211 */ \ + 0, 1, 0, /* char 212 */ \ + 0, 1, 0, /* char 213 */ \ + 0, 1, 0, /* char 214 */ \ + 0, 1, 0, /* char 215 */ \ + 0, 1, 0, /* char 216 */ \ + 0, 1, 0, /* char 217 */ \ + 0, 1, 0, /* char 218 */ \ + 0, 1, 0, /* char 219 */ \ + 0, 1, 0, /* char 220 */ \ + 0, 1, 0, /* char 221 */ \ + 0, 1, 0, /* char 222 */ \ + 0, 1, 0, /* char 223 */ \ + 0, 1, 0, /* char 224 */ \ + 0, 1, 0, /* char 225 */ \ + 0, 1, 0, /* char 226 */ \ + 0, 1, 0, /* char 227 */ \ + 0, 1, 0, /* char 228 */ \ + 0, 1, 0, /* char 229 */ \ + 0, 1, 0, /* char 230 */ \ + 0, 1, 0, /* char 231 */ \ + 0, 1, 0, /* char 232 */ \ + 0, 1, 0, /* char 233 */ \ + 0, 1, 0, /* char 234 */ \ + 0, 1, 0, /* char 235 */ \ + 0, 1, 0, /* char 236 */ \ + 0, 1, 0, /* char 237 */ \ + 0, 1, 0, /* char 238 */ \ + 0, 1, 0, /* char 239 */ \ + 0, 1, 0, /* char 240 */ \ + 0, 1, 0, /* char 241 */ \ + 0, 1, 0, /* char 242 */ \ + 0, 1, 0, /* char 243 */ \ + 0, 1, 0, /* char 244 */ \ + 0, 1, 0, /* char 245 */ \ + 0, 1, 0, /* char 246 */ \ + 0, 1, 0, /* char 247 */ \ + 0, 1, 0, /* char 248 */ \ + 0, 1, 0, /* char 249 */ \ + 0, 1, 0, /* char 250 */ \ + 0, 1, 0, /* char 251 */ \ + 0, 1, 0, /* char 252 */ \ + 0, 1, 0, /* char 253 */ \ + 0, 1, 0, /* char 254 */ \ + 0, 1, 0, /* char 255 */ \ + 226) diff --git a/interface/fonts/tiny_unicode.dm b/interface/fonts/tiny_unicode.dm new file mode 100644 index 0000000000000..d6af265d5182b --- /dev/null +++ b/interface/fonts/tiny_unicode.dm @@ -0,0 +1,253 @@ +/// For clean results on map, use only sizing pt, multiples of 12: 12pt 24pt 48pt etc. - Not for use with px sizing +/// Can be used in TGUI etc, px sizing is pt / 0.75. 12pt = 16px, 24pt = 32px etc. + +/// Base font +/datum/font/tiny_unicode + name = "TinyUnicode" + font_family = 'interface/fonts/TinyUnicode.ttf' + +/// For icon overlays +/// TinyUnicode 12pt metrics generated using Lummox's dmifontsplus (https://www.byond.com/developer/LummoxJR/DmiFontsPlus) +/// Note: these variable names have been changed, so you can't straight copy/paste from dmifontsplus.exe +/datum/font/tiny_unicode/size_12pt + name = "TinyUnicode 12pt" + height = 13 + ascent = 11 + descent = 2 + average_width = 5 + max_width = 11 + overhang = 0 + in_leading = -3 + ex_leading = 1 + default_character = 31 + start = 30 + end = 255 + metrics = list( + 1, 5, 0, // char 30 + 1, 5, 0, // char 31 + 0, 1, 4, // char 32 + 0, 1, 1, // char 33 + 0, 3, 1, // char 34 + 0, 5, 1, // char 35 + 0, 4, 1, // char 36 + 0, 3, 1, // char 37 + 0, 5, 1, // char 38 + 0, 1, 1, // char 39 + 0, 2, 1, // char 40 + 0, 2, 1, // char 41 + 0, 3, 1, // char 42 + 0, 3, 1, // char 43 + 0, 2, 1, // char 44 + 0, 3, 1, // char 45 + 0, 1, 1, // char 46 + 0, 3, 1, // char 47 + 0, 4, 1, // char 48 + 0, 2, 1, // char 49 + 0, 4, 1, // char 50 + 0, 4, 1, // char 51 + 0, 4, 1, // char 52 + 0, 4, 1, // char 53 + 0, 4, 1, // char 54 + 0, 4, 1, // char 55 + 0, 4, 1, // char 56 + 0, 4, 1, // char 57 + 0, 1, 1, // char 58 + 0, 2, 1, // char 59 + 0, 2, 1, // char 60 + 0, 4, 1, // char 61 + 0, 2, 1, // char 62 + 0, 4, 1, // char 63 + 0, 7, 1, // char 64 + 0, 4, 1, // char 65 + 0, 4, 1, // char 66 + 0, 3, 1, // char 67 + 0, 4, 1, // char 68 + 0, 3, 1, // char 69 + 0, 3, 1, // char 70 + 0, 4, 1, // char 71 + 0, 4, 1, // char 72 + 0, 3, 1, // char 73 + 0, 4, 1, // char 74 + 0, 4, 1, // char 75 + 0, 3, 1, // char 76 + 0, 5, 1, // char 77 + 0, 4, 1, // char 78 + 0, 4, 1, // char 79 + 0, 4, 1, // char 80 + 0, 4, 1, // char 81 + 0, 4, 1, // char 82 + 0, 4, 1, // char 83 + 0, 3, 1, // char 84 + 0, 4, 1, // char 85 + 0, 4, 1, // char 86 + 0, 5, 1, // char 87 + 0, 4, 1, // char 88 + 0, 4, 1, // char 89 + 0, 3, 1, // char 90 + 0, 2, 1, // char 91 + 0, 3, 1, // char 92 + 0, 2, 1, // char 93 + 0, 3, 1, // char 94 + 0, 5, 1, // char 95 + 0, 2, 1, // char 96 + 0, 4, 1, // char 97 + 0, 4, 1, // char 98 + 0, 3, 1, // char 99 + 0, 4, 1, // char 100 + 0, 4, 1, // char 101 + 0, 3, 1, // char 102 + 0, 4, 1, // char 103 + 0, 4, 1, // char 104 + 0, 1, 1, // char 105 + 0, 2, 1, // char 106 + 0, 4, 1, // char 107 + 0, 1, 1, // char 108 + 0, 5, 1, // char 109 + 0, 4, 1, // char 110 + 0, 4, 1, // char 111 + 0, 4, 1, // char 112 + 0, 4, 1, // char 113 + 0, 3, 1, // char 114 + 0, 4, 1, // char 115 + 0, 3, 1, // char 116 + 0, 4, 1, // char 117 + 0, 4, 1, // char 118 + 0, 5, 1, // char 119 + 0, 3, 1, // char 120 + 0, 4, 1, // char 121 + 0, 4, 1, // char 122 + 0, 3, 1, // char 123 + 0, 1, 1, // char 124 + 0, 3, 1, // char 125 + 0, 5, 1, // char 126 + 1, 5, 0, // char 127 + 0, 4, 1, // char 128 + 1, 5, 0, // char 129 + 1, 5, 0, // char 130 + 1, 5, 0, // char 131 + 1, 5, 0, // char 132 + 1, 5, 0, // char 133 + 1, 5, 0, // char 134 + 1, 5, 0, // char 135 + 1, 5, 0, // char 136 + 0, 5, 1, // char 137 + 1, 5, 0, // char 138 + 1, 5, 0, // char 139 + 0, 6, 1, // char 140 + 1, 5, 0, // char 141 + 1, 5, 0, // char 142 + 1, 5, 0, // char 143 + 1, 5, 0, // char 144 + 1, 5, 0, // char 145 + 1, 5, 0, // char 146 + 1, 5, 0, // char 147 + 1, 5, 0, // char 148 + 0, 2, 1, // char 149 + 1, 5, 0, // char 150 + 1, 5, 0, // char 151 + 1, 5, 0, // char 152 + 0, 4, 1, // char 153 + 1, 5, 0, // char 154 + 1, 5, 0, // char 155 + 1, 5, 0, // char 156 + 1, 5, 0, // char 157 + 1, 5, 0, // char 158 + 0, 4, 1, // char 159 + 1, 5, 0, // char 160 + 0, 1, 1, // char 161 + 0, 4, 1, // char 162 + 0, 4, 1, // char 163 + 0, 5, 1, // char 164 + 0, 3, 1, // char 165 + 0, 1, 1, // char 166 + 0, 4, 1, // char 167 + 0, 3, 1, // char 168 + 0, 2, 1, // char 169 + 0, 8, 1, // char 170 + 0, 4, 1, // char 171 + 0, 4, 1, // char 172 + 1, 5, 0, // char 173 + 0, 2, 1, // char 174 + 0, 4, 1, // char 175 + 0, 3, 1, // char 176 + 0, 3, 1, // char 177 + 0, 2, 1, // char 178 + 0, 2, 1, // char 179 + 0, 2, 1, // char 180 + 0, 4, 1, // char 181 + 0, 5, 1, // char 182 + 1, 1, 1, // char 183 + 0, 8, 1, // char 184 + 0, 2, 1, // char 185 + 0, 2, 1, // char 186 + 0, 4, 1, // char 187 + 0, 7, 1, // char 188 + 0, 8, 1, // char 189 + 0, 8, 1, // char 190 + 0, 4, 1, // char 191 + 0, 4, 1, // char 192 + 0, 4, 1, // char 193 + 0, 4, 1, // char 194 + 0, 4, 1, // char 195 + 0, 4, 1, // char 196 + 0, 4, 1, // char 197 + 0, 6, 1, // char 198 + 0, 3, 1, // char 199 + 0, 3, 1, // char 200 + 0, 3, 1, // char 201 + 0, 3, 1, // char 202 + 0, 3, 1, // char 203 + 0, 3, 1, // char 204 + 0, 3, 1, // char 205 + 0, 3, 1, // char 206 + 0, 3, 1, // char 207 + 0, 10, 1, // char 208 + 0, 4, 1, // char 209 + 0, 4, 1, // char 210 + 0, 4, 1, // char 211 + 0, 4, 1, // char 212 + 0, 4, 1, // char 213 + 0, 4, 1, // char 214 + 0, 3, 1, // char 215 + 0, 5, 1, // char 216 + 0, 4, 1, // char 217 + 0, 4, 1, // char 218 + 0, 4, 1, // char 219 + 0, 4, 1, // char 220 + 0, 4, 1, // char 221 + 0, 3, 1, // char 222 + 0, 3, 1, // char 223 + 0, 4, 1, // char 224 + 0, 4, 1, // char 225 + 0, 4, 1, // char 226 + 0, 4, 1, // char 227 + 0, 4, 1, // char 228 + 0, 4, 1, // char 229 + 0, 7, 1, // char 230 + 0, 3, 1, // char 231 + 0, 4, 1, // char 232 + 0, 4, 1, // char 233 + 0, 4, 1, // char 234 + 0, 4, 1, // char 235 + 0, 2, 1, // char 236 + 0, 2, 1, // char 237 + 0, 3, 1, // char 238 + 0, 3, 1, // char 239 + 0, 5, 1, // char 240 + 0, 4, 1, // char 241 + 0, 4, 1, // char 242 + 0, 4, 1, // char 243 + 0, 4, 1, // char 244 + 0, 4, 1, // char 245 + 0, 4, 1, // char 246 + 0, 5, 1, // char 247 + 0, 4, 1, // char 248 + 0, 4, 1, // char 249 + 0, 4, 1, // char 250 + 0, 4, 1, // char 251 + 0, 4, 1, // char 252 + 0, 4, 1, // char 253 + 0, 10, 1, // char 254 + 0, 4, 1, // char 255 + 226 + ) diff --git a/interface/fonts/vcr_osd_mono.dm b/interface/fonts/vcr_osd_mono.dm new file mode 100644 index 0000000000000..301d90d2f7ea6 --- /dev/null +++ b/interface/fonts/vcr_osd_mono.dm @@ -0,0 +1,3 @@ +/datum/font/vcr_osd_mono + name = "VCR OSD Mono" + font_family = 'interface/fonts/VCR_OSD_Mono.ttf' diff --git a/interface/skin.dmf b/interface/skin.dmf index 5adb7643d5f3e..3dce80f37e5f3 100644 --- a/interface/skin.dmf +++ b/interface/skin.dmf @@ -8,22 +8,23 @@ macro "default" name = "SHIFT+UP" command = ".winset :map.right-click=true" + menu "menu" - elem + elem name = "&File" command = "" saved-params = "is-checked" - elem + elem name = "&Quick screenshot\tF2" command = ".screenshot auto" category = "&File" saved-params = "is-checked" - elem + elem name = "&Save screenshot as...\tShift+F2" command = ".screenshot" category = "&File" saved-params = "is-checked" - elem + elem name = "" command = "" category = "&File" @@ -33,16 +34,16 @@ menu "menu" command = ".reconnect" category = "&File" saved-params = "is-checked" - elem + elem name = "&Quit\tAlt-F4" command = ".quit" category = "&File" saved-params = "is-checked" - elem + elem "help-menu" name = "&Help" command = "" saved-params = "is-checked" - elem + elem name = "&Admin Help\tF1" command = "adminhelp" category = "&Help" @@ -54,86 +55,38 @@ window "mainwindow" type = MAIN pos = 281,0 size = 640x440 - anchor1 = none - anchor2 = none - background-color = none + anchor1 = -1,-1 + anchor2 = -1,-1 is-default = true saved-params = "pos;size;is-minimized;is-maximized" + statusbar = false icon = 'icons\\ss13_64.png' macro = "default" menu = "menu" - outer-size = 656x518 - inner-size = 640x459 elem "split" type = CHILD - pos = 3,0 - size = 634x417 + pos = 0,0 + size = 640x440 anchor1 = 0,0 anchor2 = 100,100 - background-color = none saved-params = "splitter" left = "mapwindow" right = "infowindow" is-vert = true - elem "input" - type = INPUT - pos = 5,420 - size = 595x20 - anchor1 = 0,100 - anchor2 = 100,100 - background-color = #d3b5b5 - is-default = true - saved-params = "command" - elem "saybutton" - type = BUTTON - pos = 600,420 - size = 40x20 - anchor1 = 100,100 - anchor2 = none - background-color = none - saved-params = "is-checked" - text = "Chat" - command = ".winset \"saybutton.is-checked=true ? input.command=\"!say \\\"\" : input.command=\"\"saybutton.is-checked=true ? mebutton.is-checked=false\"\"saybutton.is-checked=true ? oocbutton.is-checked=false\"" - button-type = pushbox - elem "oocbutton" - type = BUTTON - pos = 520,420 - size = 40x20 - anchor1 = 100,100 - anchor2 = none - background-color = none - saved-params = "is-checked" - text = "OOC" - command = ".winset \"oocbutton.is-checked=true ? input.command=\"!ooc \\\"\" : input.command=\"\"oocbutton.is-checked=true ? mebutton.is-checked=false\"\"oocbutton.is-checked=true ? saybutton.is-checked=false\"" - button-type = pushbox - elem "mebutton" - type = BUTTON - pos = 560,420 - size = 40x20 - anchor1 = 100,100 - anchor2 = none - background-color = none - saved-params = "is-checked" - text = "Me" - command = ".winset \"mebutton.is-checked=true ? input.command=\"!me \\\"\" : input.command=\"\"mebutton.is-checked=true ? saybutton.is-checked=false\"\"mebutton.is-checked=true ? oocbutton.is-checked=false\"" - button-type = pushbox elem "asset_cache_browser" type = BROWSER pos = 0,0 size = 200x200 - anchor1 = none - anchor2 = none - background-color = none + anchor1 = -1,-1 + anchor2 = -1,-1 is-visible = false saved-params = "" - auto-format = false elem "tooltip" type = BROWSER pos = 0,0 size = 999x999 - anchor1 = none - anchor2 = none - background-color = none + anchor1 = -1,-1 + anchor2 = -1,-1 is-visible = false saved-params = "" @@ -142,26 +95,34 @@ window "mapwindow" type = MAIN pos = 281,0 size = 640x480 - anchor1 = none - anchor2 = none - background-color = none + anchor1 = -1,-1 + anchor2 = -1,-1 saved-params = "pos;size;is-minimized;is-maximized" is-pane = true - outer-size = 656x538 - inner-size = 640x499 + on-status = ".winset \"status_bar.text=[[*]]\" " elem "map" type = MAP pos = 0,0 size = 640x480 anchor1 = 0,0 anchor2 = 100,100 - font-family = "Arial" - font-size = 7 - text-color = none + font-family = "Grand9K Pixel" + font-size = 6pt is-default = true right-click = true saved-params = "zoom;letterbox;zoom-mode" - style = ".center { text-align: center; } .maptext { font-family: 'Small Fonts'; font-size: 7px; -dm-text-outline: 1px black; color: white; line-height: 1.1; } .command_headset { font-weight: bold;\tfont-size: 8px; } .small { font-size: 6px; } .big { font-size: 8px; } .reallybig { font-size: 8px; } .extremelybig { font-size: 8px; } .greentext { color: #00FF00; font-size: 7px; } .redtext { color: #FF0000; font-size: 7px; } .clown { color: #FF69Bf; font-size: 7px; font-weight: bold; } .his_grace { color: #15D512; } .hypnophrase { color: #0d0d0d; font-weight: bold; } .yell { font-weight: bold; } .italics { font-size: 6px; }" + style = ".center { text-align: center; } .maptext { font-family: 'Grand9K Pixel'; font-size: 6pt; -dm-text-outline: 1px black; color: white; line-height: 1.0; } .command_headset { font-weight: bold; } .context { font-family: 'Pixellari'; font-size: 12pt; -dm-text-outline: 1px black; } .subcontext { font-family: 'TinyUnicode'; font-size: 12pt; line-height: 0.75; } .small { font-family: 'Spess Font'; font-size: 6pt; line-height: 1.4; } .big { font-family: 'Pixellari'; font-size: 12pt; } .reallybig { font-size: 12pt; } .extremelybig { font-size: 12pt; } .greentext { color: #00FF00; font-size: 6pt; } .redtext { color: #FF0000; font-size: 6pt; } .clown { color: #FF69BF; font-weight: bold; } .his_grace { color: #15D512; } .hypnophrase { color: #0d0d0d; font-weight: bold; } .yell { font-weight: bold; } .italics { font-family: 'Spess Font'; font-size: 6pt; line-height: 1.4; }" + elem "status_bar" + type = LABEL + pos = 0,464 + size = 280x16 + anchor1 = 0,100 + is-visible = true + text = "" + align = left + background-color = #222222 + text-color = #ffffff + border = line window "infowindow" elem "infowindow" @@ -270,62 +231,137 @@ window "infowindow" window "outputwindow" elem "outputwindow" type = MAIN - pos = 281,0 + pos = 0,0 size = 640x480 - anchor1 = none - anchor2 = none - background-color = #ffffff + anchor1 = -1,-1 + anchor2 = -1,-1 + background-color = none saved-params = "pos;size;is-minimized;is-maximized" is-pane = true outer-size = 656x538 inner-size = 640x499 - elem "browseroutput" - type = BROWSER + elem "input" + type = INPUT + pos = 2,460 + size = 517x20 + anchor1 = 0,100 + anchor2 = 100,100 + is-default = true + border = line + saved-params = "command" + elem "oocbutton" + type = BUTTON + pos = 599,460 + size = 40x20 + anchor1 = 100,100 + anchor2 = -1,-1 + background-color = none + border = line + saved-params = "is-checked" + text = "OOC" + command = ".winset \"oocbutton.is-checked=true ? input.command=\"!ooc \\\"\" : input.command=\"\"oocbutton.is-checked=true ? mebutton.is-checked=false\"\"oocbutton.is-checked=true ? saybutton.is-checked=false\"" + is-flat = true + button-type = pushbox + elem "saybutton" + type = BUTTON + pos = 519,460 + size = 40x20 + anchor1 = 100,100 + anchor2 = -1,-1 + background-color = none + border = line + saved-params = "is-checked" + text = "Say" + command = ".winset \"saybutton.is-checked=true ? input.command=\"!say \\\"\" : input.command=\"\"saybutton.is-checked=true ? mebutton.is-checked=false\"\"saybutton.is-checked=true ? oocbutton.is-checked=false\"" + is-flat = true + button-type = pushbox + elem "mebutton" + type = BUTTON + pos = 559,460 + size = 40x20 + anchor1 = 100,100 + anchor2 = -1,-1 + background-color = none + border = line + saved-params = "is-checked" + text = "Me" + command = ".winset \"mebutton.is-checked=true ? input.command=\"!me \\\"\" : input.command=\"\"mebutton.is-checked=true ? saybutton.is-checked=false\"\"mebutton.is-checked=true ? oocbutton.is-checked=false\"" + is-flat = true + button-type = pushbox + elem "legacy_output_selector" + type = CHILD pos = 0,0 - size = 640x480 + size = 640x456 anchor1 = 0,0 anchor2 = 100,100 - is-visible = false - is-disabled = true - saved-params = "" + saved-params = "splitter" + left = "output_legacy" + is-vert = false + +window "output_legacy" + elem "output_legacy" + type = MAIN + pos = 0,0 + size = 640x456 + anchor1 = -1,-1 + anchor2 = -1,-1 + background-color = none + saved-params = "pos;size;is-minimized;is-maximized" + is-pane = true elem "output" type = OUTPUT pos = 0,0 - size = 640x480 + size = 640x456 anchor1 = 0,0 anchor2 = 100,100 is-default = true saved-params = "" +window "output_browser" + elem "output_browser" + type = MAIN + pos = 0,0 + size = 640x456 + anchor1 = -1,-1 + anchor2 = -1,-1 + background-color = none + saved-params = "pos;size;is-minimized;is-maximized" + is-pane = true + elem "browseroutput" + type = BROWSER + pos = 0,0 + size = 640x456 + anchor1 = 0,0 + anchor2 = 100,100 + background-color = none + saved-params = "" + window "popupwindow" elem "popupwindow" type = MAIN pos = 281,0 size = 120x120 - anchor1 = none - anchor2 = none - background-color = none + anchor1 = -1,-1 + anchor2 = -1,-1 is-visible = false saved-params = "pos;size;is-minimized;is-maximized" statusbar = false can-resize = false - outer-size = 136x159 - inner-size = 120x120 window "preferences_window" elem "preferences_window" type = MAIN pos = 281,0 size = 1280x1000 - anchor1 = none - anchor2 = none + anchor1 = -1,-1 + anchor2 = -1,-1 is-visible = false saved-params = "pos;size;is-minimized;is-maximized" statusbar = false elem "preferences_browser" type = BROWSER pos = 0,0 - size = 960x1008 + size = 960x1000 anchor1 = 0,0 anchor2 = 75,100 saved-params = "" @@ -333,8 +369,8 @@ window "preferences_window" type = MAP pos = 960,0 size = 320x1000 - anchor1 = 90,0 - anchor2 = 75,100 + anchor1 = 75,0 + anchor2 = 100,100 right-click = true saved-params = "zoom;letterbox;zoom-mode" @@ -343,19 +379,15 @@ window "statwindow" type = MAIN pos = 281,0 size = 640x480 - anchor1 = none - anchor2 = none - background-color = none + anchor1 = -1,-1 + anchor2 = -1,-1 saved-params = "pos;size;is-minimized;is-maximized" is-pane = true - outer-size = 656x538 - inner-size = 640x499 elem "statbrowser" type = BROWSER pos = 0,0 size = 640x480 anchor1 = 0,0 anchor2 = 100,100 - background-color = none is-visible = false saved-params = "" diff --git a/strings/tips.txt b/strings/tips.txt index 38e2f11a1805c..c00e0b77d8bab 100644 --- a/strings/tips.txt +++ b/strings/tips.txt @@ -1,3 +1,5 @@ +@You can use the |, + and _ characters to emphasize parts of what you say in-game (e.g. say"my _ass_ |is| +heavy+." will be outputted as "my ass is heavy."). You can also escape these emphasizers by appending backslashes before them (e.g. say"1\+2\+3" will come out as "1+2+3" and not "1\2\3"). +♪ Hey, have you ever tried appending the % character before your messages when speaking in-game? ♫ Where the space map levels connect is randomized every round, but are otherwise kept consistent within rounds. Remember that they are not necessarily bidirectional! You can catch thrown items by toggling on your throw mode with an empty hand active. To crack the safe in the vault, use a stethoscope or three plastic explosives on it. diff --git a/tgui/.gitignore b/tgui/.gitignore index bb34d61b3ce82..3bcc68b84319a 100644 --- a/tgui/.gitignore +++ b/tgui/.gitignore @@ -16,7 +16,7 @@ package-lock.json /public/.tmp/**/* /public/**/* !/public/*.html -!/public/tgui-polyfill.bundle.js +!/public/tgui-polyfill.min.js /coverage ## Previously ignored locations that are kept to avoid confusing git diff --git a/tgui/.yarnrc.yml b/tgui/.yarnrc.yml index b6387e8e46e83..f2103314b259c 100644 --- a/tgui/.yarnrc.yml +++ b/tgui/.yarnrc.yml @@ -10,6 +10,11 @@ plugins: - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs spec: "@yarnpkg/plugin-interactive-tools" +packageExtensions: + 'babel-plugin-inferno@*': + dependencies: + '@babel/core': '*' + pnpEnableEsmLoader: false preferAggregateCacheInfo: true diff --git a/tgui/global.d.ts b/tgui/global.d.ts index 348cb9c5165ce..e2a2f86cbbe7c 100644 --- a/tgui/global.d.ts +++ b/tgui/global.d.ts @@ -44,6 +44,11 @@ type ByondType = { */ TRIDENT: number | null; + /** + * Version of Blink engine of WebView2. Null if N/A. + */ + BLINK: number | null; + /** * True if browser is IE8 or lower. */ @@ -64,6 +69,18 @@ type ByondType = { */ IS_LTE_IE11: boolean; + /** + * If `true`, unhandled errors and common mistakes result in a blue screen + * of death, which stops this window from handling incoming messages and + * closes the active instance of tgui datum if there was one. + * + * It can be defined in window.initialize() in DM, or changed in runtime + * here via this property to `true` or `false`. + * + * It is recommended that you keep this ON to detect hard to find bugs. + */ + strictMode: boolean; + /** * Makes a BYOND call. * @@ -169,6 +186,11 @@ type ByondType = { * Loads a script into the document. */ loadJs(url: string): void; + + /** + * Downloads a blob, platform-agnostic + */ + saveBlob(blob: Blob, filename: string, ext: string): void; }; /** @@ -179,4 +201,15 @@ const Byond: ByondType; interface Window { Byond: ByondType; + __store__: Store; + __augmentStack__: (store: Store) => StackAugmentor; + + // IE IndexedDB stuff. + msIndexedDB: IDBFactory; + msIDBTransaction: IDBTransaction; + + // 516 byondstorage API. + hubStorage: Storage; + domainStorage: Storage; + serverStorage: Storage; } diff --git a/tgui/packages/common/redux.js b/tgui/packages/common/redux.js deleted file mode 100644 index ebed11f166b2e..0000000000000 --- a/tgui/packages/common/redux.js +++ /dev/null @@ -1,153 +0,0 @@ -/** - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -import { compose } from './fp'; - -/** - * Creates a Redux store. - */ -export const createStore = (reducer, enhancer) => { - // Apply a store enhancer (applyMiddleware is one of them). - if (enhancer) { - return enhancer(createStore)(reducer); - } - - let currentState; - let listeners = []; - - const getState = () => currentState; - - const subscribe = listener => { - listeners.push(listener); - }; - - const dispatch = action => { - currentState = reducer(currentState, action); - for (let i = 0; i < listeners.length; i++) { - listeners[i](); - } - }; - - // This creates the initial store by causing each reducer to be called - // with an undefined state - dispatch({ - type: '@@INIT', - }); - - return { - dispatch, - subscribe, - getState, - }; -}; - -/** - * Creates a store enhancer which applies middleware to all dispatched - * actions. - */ -export const applyMiddleware = (...middlewares) => { - return createStore => (reducer, ...args) => { - const store = createStore(reducer, ...args); - - let dispatch = () => { - throw new Error( - 'Dispatching while constructing your middleware is not allowed.'); - }; - - const storeApi = { - getState: store.getState, - dispatch: (action, ...args) => dispatch(action, ...args), - }; - - const chain = middlewares.map(middleware => middleware(storeApi)); - dispatch = compose(...chain)(store.dispatch); - - return { - ...store, - dispatch, - }; - }; -}; - -/** - * Combines reducers by running them in their own object namespaces as - * defined in reducersObj paramter. - * - * Main difference from redux/combineReducers is that it preserves keys - * in the state that are not present in the reducers object. This function - * is also more flexible than the redux counterpart. - */ -export const combineReducers = reducersObj => { - const keys = Object.keys(reducersObj); - let hasChanged = false; - return (prevState = {}, action) => { - const nextState = { ...prevState }; - for (let key of keys) { - const reducer = reducersObj[key]; - const prevDomainState = prevState[key]; - const nextDomainState = reducer(prevDomainState, action); - if (prevDomainState !== nextDomainState) { - hasChanged = true; - nextState[key] = nextDomainState; - } - } - return hasChanged - ? nextState - : prevState; - }; -}; - -/** - * A utility function to create an action creator for the given action - * type string. The action creator accepts a single argument, which will - * be included in the action object as a field called payload. The action - * creator function will also have its toString() overriden so that it - * returns the action type, allowing it to be used in reducer logic that - * is looking for that action type. - * - * @param {string} type The action type to use for created actions. - * @param {any} prepare (optional) a method that takes any number of arguments - * and returns { payload } or { payload, meta }. If this is given, the - * resulting action creator will pass it's arguments to this method to - * calculate payload & meta. - * - * @public - */ -export const createAction = (type, prepare = null) => { - const actionCreator = (...args) => { - if (!prepare) { - return { type, payload: args[0] }; - } - const prepared = prepare(...args); - if (!prepared) { - throw new Error('prepare function did not return an object'); - } - const action = { type }; - if ('payload' in prepared) { - action.payload = prepared.payload; - } - if ('meta' in prepared) { - action.meta = prepared.meta; - } - return action; - }; - actionCreator.toString = () => '' + type; - actionCreator.type = type; - actionCreator.match = action => action.type === type; - return actionCreator; -}; - - -// Implementation specific -// -------------------------------------------------------- - -export const useDispatch = context => { - return context.store.dispatch; -}; - -export const useSelector = (context, selector) => { - return selector(context.store.getState()); -}; diff --git a/tgui/packages/common/redux.test.ts b/tgui/packages/common/redux.test.ts new file mode 100644 index 0000000000000..af4e5d4e73eb7 --- /dev/null +++ b/tgui/packages/common/redux.test.ts @@ -0,0 +1,61 @@ +import { Action, Reducer, applyMiddleware, combineReducers, createAction, createStore } from './redux'; + +// Dummy Reducer +const counterReducer: Reducer> = (state = 0, action) => { + switch (action.type) { + case 'INCREMENT': + return state + 1; + case 'DECREMENT': + return state - 1; + default: + return state; + } +}; + +// Dummy Middleware +const loggingMiddleware = (storeApi) => (next) => (action) => { + console.log('Middleware:', action); + return next(action); +}; + +// Dummy Action Creators +const increment = createAction('INCREMENT'); +const decrement = createAction('DECREMENT'); + +describe('Redux implementation tests', () => { + test('createStore works', () => { + const store = createStore(counterReducer); + expect(store.getState()).toBe(0); + }); + + test('createStore with applyMiddleware works', () => { + const store = createStore( + counterReducer, + applyMiddleware(loggingMiddleware) + ); + expect(store.getState()).toBe(0); + }); + + test('dispatch works', () => { + const store = createStore(counterReducer); + store.dispatch(increment()); + expect(store.getState()).toBe(1); + store.dispatch(decrement()); + expect(store.getState()).toBe(0); + }); + + test('combineReducers works', () => { + const rootReducer = combineReducers({ + counter: counterReducer, + }); + const store = createStore(rootReducer); + expect(store.getState()).toEqual({ counter: 0 }); + }); + + test('createAction works', () => { + const incrementAction = increment(); + expect(incrementAction).toEqual({ type: 'INCREMENT' }); + const decrementAction = decrement(); + expect(decrementAction).toEqual({ type: 'DECREMENT' }); + }); +}); diff --git a/tgui/packages/common/redux.ts b/tgui/packages/common/redux.ts new file mode 100644 index 0000000000000..4e618bddafd00 --- /dev/null +++ b/tgui/packages/common/redux.ts @@ -0,0 +1,212 @@ +/** + * @file + * @copyright 2020 Aleksej Komarov + * @license MIT + */ + +export type Reducer = ( + state: State | undefined, + action: ActionType +) => State; + +export type Store = { + dispatch: Dispatch; + subscribe: (listener: () => void) => void; + getState: () => State; +}; + +type MiddlewareAPI = { + getState: () => State; + dispatch: Dispatch; +}; + +export type Middleware = ( + storeApi: MiddlewareAPI +) => (next: Dispatch) => Dispatch; + +export type Action = { + type: TType; +}; + +export type AnyAction = Action & { + [extraProps: string]: any; +}; + +export type Dispatch = ( + action: ActionType +) => void; + +type StoreEnhancer = (createStoreFunction: Function) => Function; + +type PreparedAction = { + payload?: any; + meta?: any; +}; + +/** + * Creates a Redux store. + */ +export const createStore = ( + reducer: Reducer, + enhancer?: StoreEnhancer +): Store => { + // Apply a store enhancer (applyMiddleware is one of them). + if (enhancer) { + return enhancer(createStore)(reducer); + } + + let currentState: State; + let listeners: Array<() => void> = []; + + const getState = (): State => currentState; + + const subscribe = (listener: () => void): void => { + listeners.push(listener); + }; + + const dispatch = (action: ActionType): void => { + currentState = reducer(currentState, action); + for (let i = 0; i < listeners.length; i++) { + listeners[i](); + } + }; + + // This creates the initial store by causing each reducer to be called + // with an undefined state + dispatch({ type: '@@INIT' } as ActionType); + + return { + dispatch, + subscribe, + getState, + }; +}; + +/** + * Creates a store enhancer which applies middleware to all dispatched + * actions. + */ +export const applyMiddleware = ( + ...middlewares: Middleware[] +): StoreEnhancer => { + return ( + createStoreFunction: (reducer: Reducer, enhancer?: StoreEnhancer) => Store + ) => { + return (reducer, ...args): Store => { + const store = createStoreFunction(reducer, ...args); + + let dispatch: Dispatch = () => { + throw new Error( + 'Dispatching while constructing your middleware is not allowed.' + ); + }; + + const storeApi: MiddlewareAPI = { + getState: store.getState, + dispatch: (action, ...args) => dispatch(action, ...args), + }; + + const chain = middlewares.map((middleware) => middleware(storeApi)); + dispatch = chain.reduceRight( + (next, middleware) => middleware(next), + store.dispatch + ); + + return { + ...store, + dispatch, + }; + }; + }; +}; + +/** + * Combines reducers by running them in their own object namespaces as + * defined in reducersObj paramter. + * + * Main difference from redux/combineReducers is that it preserves keys + * in the state that are not present in the reducers object. This function + * is also more flexible than the redux counterpart. + */ +export const combineReducers = ( + reducersObj: Record +): Reducer => { + const keys = Object.keys(reducersObj); + + return (prevState = {}, action) => { + const nextState = { ...prevState }; + let hasChanged = false; + + for (const key of keys) { + const reducer = reducersObj[key]; + const prevDomainState = prevState[key]; + const nextDomainState = reducer(prevDomainState, action); + + if (prevDomainState !== nextDomainState) { + hasChanged = true; + nextState[key] = nextDomainState; + } + } + + return hasChanged ? nextState : prevState; + }; +}; + +/** + * A utility function to create an action creator for the given action + * type string. The action creator accepts a single argument, which will + * be included in the action object as a field called payload. The action + * creator function will also have its toString() overriden so that it + * returns the action type, allowing it to be used in reducer logic that + * is looking for that action type. + * + * @param {string} type The action type to use for created actions. + * @param {any} prepare (optional) a method that takes any number of arguments + * and returns { payload } or { payload, meta }. If this is given, the + * resulting action creator will pass it's arguments to this method to + * calculate payload & meta. + * + * @public + */ +export const createAction = ( + type: TAction, + prepare?: (...args: any[]) => PreparedAction +) => { + const actionCreator = (...args: any[]) => { + let action: Action & PreparedAction = { type }; + + if (prepare) { + const prepared = prepare(...args); + if (!prepared) { + throw new Error('prepare function did not return an object'); + } + action = { ...action, ...prepared }; + } else { + action.payload = args[0]; + } + + return action; + }; + + actionCreator.toString = () => type; + actionCreator.type = type; + actionCreator.match = (action) => action.type === type; + + return actionCreator; +}; + +// Implementation specific +// -------------------------------------------------------- + +export const useDispatch = (context: { + store: Store; +}): Dispatch => { + return context.store.dispatch; +}; + +export const useSelector = ( + context: { store: Store }, + selector: (state: State) => Selected +): Selected => { + return selector(context.store.getState()); +}; diff --git a/tgui/packages/common/storage.js b/tgui/packages/common/storage.ts similarity index 53% rename from tgui/packages/common/storage.js rename to tgui/packages/common/storage.ts index 83dc6d99c1cca..507734e0d46bb 100644 --- a/tgui/packages/common/storage.js +++ b/tgui/packages/common/storage.ts @@ -7,9 +7,14 @@ */ export const IMPL_MEMORY = 0; -export const IMPL_LOCAL_STORAGE = 1; +export const IMPL_HUB_STORAGE = 1; export const IMPL_INDEXED_DB = 2; +type StorageImplementation = + | typeof IMPL_MEMORY + | typeof IMPL_HUB_STORAGE + | typeof IMPL_INDEXED_DB; + const INDEXED_DB_VERSION = 1; const INDEXED_DB_NAME = 'tgui'; const INDEXED_DB_STORE_NAME = 'storage-v1'; @@ -17,7 +22,15 @@ const INDEXED_DB_STORE_NAME = 'storage-v1'; const READ_ONLY = 'readonly'; const READ_WRITE = 'readwrite'; -const testGeneric = testFn => () => { +type StorageBackend = { + impl: StorageImplementation; + get(key: string): Promise; + set(key: string, value: any): Promise; + remove(key: string): Promise; + clear(): Promise; +}; + +const testGeneric = (testFn: () => boolean) => (): boolean => { try { return Boolean(testFn()); } @@ -26,70 +39,76 @@ const testGeneric = testFn => () => { } }; -// Localstorage can sometimes throw an error, even if DOM storage is not -// disabled in IE11 settings. -// See: https://superuser.com/questions/1080011 -const testLocalStorage = testGeneric(() => ( - window.localStorage && window.localStorage.getItem -)); +const testHubStorage = testGeneric( + () => window.hubStorage && !!window.hubStorage.getItem, +); +// TODO: Remove with 516 const testIndexedDb = testGeneric(() => ( (window.indexedDB || window.msIndexedDB) - && (window.IDBTransaction || window.msIDBTransaction) + && !!(window.IDBTransaction || window.msIDBTransaction) )); -class MemoryBackend { +class MemoryBackend implements StorageBackend { + private store: Record; + public impl: StorageImplementation; + constructor() { this.impl = IMPL_MEMORY; this.store = {}; } - get(key) { + async get(key: string): Promise { return this.store[key]; } - set(key, value) { + async set(key: string, value: any): Promise { this.store[key] = value; } - remove(key) { + async remove(key: string): Promise { this.store[key] = undefined; } - clear() { + async clear(): Promise { this.store = {}; } } -class LocalStorageBackend { +class HubStorageBackend implements StorageBackend { + public impl: StorageImplementation; + constructor() { - this.impl = IMPL_LOCAL_STORAGE; + this.impl = IMPL_HUB_STORAGE; } - get(key) { - const value = localStorage.getItem(key); + async get(key: string): Promise { + const value = await window.hubStorage.getItem(key); if (typeof value === 'string') { return JSON.parse(value); } + return undefined; } - set(key, value) { - localStorage.setItem(key, JSON.stringify(value)); + async set(key: string, value: any): Promise { + window.hubStorage.setItem(key, JSON.stringify(value)); } - remove(key) { - localStorage.removeItem(key); + async remove(key: string): Promise { + window.hubStorage.removeItem(key); } - clear() { - localStorage.clear(); + async clear(): Promise { + window.hubStorage.clear(); } } -class IndexedDbBackend { +class IndexedDbBackend implements StorageBackend { + public impl: StorageImplementation; + public dbPromise: Promise; + constructor() { this.impl = IMPL_INDEXED_DB; - /** @type {Promise} */ this.dbPromise = new Promise((resolve, reject) => { const indexedDB = window.indexedDB || window.msIndexedDB; const req = indexedDB.open(INDEXED_DB_NAME, INDEXED_DB_VERSION); @@ -98,7 +117,12 @@ class IndexedDbBackend { req.result.createObjectStore(INDEXED_DB_STORE_NAME); } catch (err) { - reject(new Error('Failed to upgrade IDB: ' + req.error)); + reject( + new Error( + 'Failed to upgrade IDB: ' + + (err instanceof Error ? err.message : String(err)), + ), + ); } }; req.onsuccess = () => resolve(req.result); @@ -108,13 +132,14 @@ class IndexedDbBackend { }); } - getStore(mode) { - return this.dbPromise.then(db => db + private async getStore(mode: IDBTransactionMode): Promise { + const db = await this.dbPromise; + return db .transaction(INDEXED_DB_STORE_NAME, mode) - .objectStore(INDEXED_DB_STORE_NAME)); + .objectStore(INDEXED_DB_STORE_NAME); } - async get(key) { + async get(key: string): Promise { const store = await this.getStore(READ_ONLY); return new Promise((resolve, reject) => { const req = store.get(key); @@ -123,26 +148,19 @@ class IndexedDbBackend { }); } - async set(key, value) { - // The reason we don't _save_ null is because IE 10 does - // not support saving the `null` type in IndexedDB. How - // ironic, given the bug below! - // See: https://github.com/mozilla/localForage/issues/161 - if (value === null) { - value = undefined; - } + async set(key: string, value: any): Promise { // NOTE: We deliberately make this operation transactionless const store = await this.getStore(READ_WRITE); store.put(value, key); } - async remove(key) { + async remove(key: string): Promise { // NOTE: We deliberately make this operation transactionless const store = await this.getStore(READ_WRITE); store.delete(key); } - async clear() { + async clear(): Promise { // NOTE: We deliberately make this operation transactionless const store = await this.getStore(READ_WRITE); store.clear(); @@ -153,9 +171,16 @@ class IndexedDbBackend { * Web Storage Proxy object, which selects the best backend available * depending on the environment. */ -class StorageProxy { +class StorageProxy implements StorageBackend { + private backendPromise: Promise; + public impl: StorageImplementation = IMPL_MEMORY; + constructor() { this.backendPromise = (async () => { + if (!Byond.TRIDENT && testHubStorage()) { + return new HubStorageBackend(); + } + // TODO: Remove with 516 if (testIndexedDb()) { try { const backend = new IndexedDbBackend(); @@ -164,29 +189,29 @@ class StorageProxy { } catch {} } - if (testLocalStorage()) { - return new LocalStorageBackend(); - } + console.warn( + 'No supported storage backend found. Using in-memory storage.', + ); return new MemoryBackend(); })(); } - async get(key) { + async get(key: string): Promise { const backend = await this.backendPromise; return backend.get(key); } - async set(key, value) { + async set(key: string, value: any): Promise { const backend = await this.backendPromise; return backend.set(key, value); } - async remove(key) { + async remove(key: string): Promise { const backend = await this.backendPromise; return backend.remove(key); } - async clear() { + async clear(): Promise { const backend = await this.backendPromise; return backend.clear(); } diff --git a/tgui/packages/tgui-panel/audio/player.js b/tgui/packages/tgui-panel/audio/player.js deleted file mode 100644 index 7848533e845e6..0000000000000 --- a/tgui/packages/tgui-panel/audio/player.js +++ /dev/null @@ -1,117 +0,0 @@ -/** - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -import { createLogger } from 'tgui/logging'; - -const logger = createLogger('AudioPlayer'); - -export class AudioPlayer { - constructor() { - // Doesn't support HTMLAudioElement - if (Byond.IS_LTE_IE9) { - return; - } - // Set up the HTMLAudioElement node - this.node = document.createElement('audio'); - this.node.style.setProperty('display', 'none'); - document.body.appendChild(this.node); - // Set up other properties - this.playing = false; - this.volume = 1; - this.options = {}; - this.onPlaySubscribers = []; - this.onStopSubscribers = []; - // Listen for playback start events - this.node.addEventListener('canplaythrough', () => { - logger.log('canplaythrough'); - this.playing = true; - this.node.playbackRate = this.options.pitch || 1; - this.node.currentTime = this.options.start || 0; - this.node.volume = this.volume; - this.node.play(); - for (let subscriber of this.onPlaySubscribers) { - subscriber(); - } - }); - // Listen for playback stop events - this.node.addEventListener('ended', () => { - logger.log('ended'); - this.stop(); - }); - // Listen for playback errors - this.node.addEventListener('error', e => { - if (this.playing) { - logger.log('playback error', e.error); - this.stop(); - } - }); - // Check every second to stop the playback at the right time - this.playbackInterval = setInterval(() => { - if (!this.playing) { - return; - } - const shouldStop = this.options.end > 0 - && this.node.currentTime >= this.options.end; - if (shouldStop) { - this.stop(); - } - }, 1000); - } - - destroy() { - if (!this.node) { - return; - } - this.node.stop(); - document.removeChild(this.node); - clearInterval(this.playbackInterval); - } - - play(url, options = {}) { - if (!this.node) { - return; - } - logger.log('playing', url, options); - this.options = options; - this.node.src = url; - } - - stop() { - if (!this.node) { - return; - } - if (this.playing) { - for (let subscriber of this.onStopSubscribers) { - subscriber(); - } - } - logger.log('stopping'); - this.playing = false; - this.node.src = ''; - } - - setVolume(volume) { - if (!this.node) { - return; - } - this.volume = volume; - this.node.volume = volume; - } - - onPlay(subscriber) { - if (!this.node) { - return; - } - this.onPlaySubscribers.push(subscriber); - } - - onStop(subscriber) { - if (!this.node) { - return; - } - this.onStopSubscribers.push(subscriber); - } -} diff --git a/tgui/packages/tgui-panel/audio/player.ts b/tgui/packages/tgui-panel/audio/player.ts new file mode 100644 index 0000000000000..d75fab4487e42 --- /dev/null +++ b/tgui/packages/tgui-panel/audio/player.ts @@ -0,0 +1,101 @@ +/** + * @file + * @copyright 2020 Aleksej Komarov + * @license MIT + */ + +import { createLogger } from 'tgui/logging'; + +const logger = createLogger('AudioPlayer'); + +type AudioOptions = { + pitch?: number; + start?: number; + end?: number; +}; + +export class AudioPlayer { + element: HTMLAudioElement | null; + options: AudioOptions; + volume: number; + + onPlaySubscribers: { (): void }[]; + onStopSubscribers: { (): void }[]; + + constructor() { + this.element = null; + + this.onPlaySubscribers = []; + this.onStopSubscribers = []; + } + + destroy(): void { + this.element = null; + } + + play(url: string, options: AudioOptions = {}): void { + if (this.element) { + this.stop(); + } + + this.options = options; + + const audio = (this.element = new Audio(url)); + audio.volume = this.volume; + audio.playbackRate = this.options.pitch || 1; + + logger.log('playing', url, options); + + audio.addEventListener('ended', () => { + logger.log('ended'); + this.stop(); + }); + + audio.addEventListener('error', (error) => { + logger.log('playback error', error); + }); + + if (this.options.end) { + audio.addEventListener('timeupdate', () => { + if ( + this.options.end && + this.options.end > 0 && + audio.currentTime >= this.options.end + ) { + this.stop(); + } + }); + } + + audio.play(); + + this.onPlaySubscribers.forEach((subscriber) => subscriber()); + } + + stop(): void { + if (!this.element) return; + + logger.log('stopping'); + + this.element.pause(); + this.element = null; + + this.onStopSubscribers.forEach((subscriber) => subscriber()); + } + + setVolume(volume: number): void { + this.volume = volume; + + if (!this.element) return; + + this.element.volume = volume; + } + + onPlay(subscriber: () => void): void { + this.onPlaySubscribers.push(subscriber); + } + + onStop(subscriber: () => void): void { + this.onStopSubscribers.push(subscriber); + } +} diff --git a/tgui/packages/tgui-panel/chat/renderer.js b/tgui/packages/tgui-panel/chat/renderer.js index b950661160532..399d142668c9a 100644 --- a/tgui/packages/tgui-panel/chat/renderer.js +++ b/tgui/packages/tgui-panel/chat/renderer.js @@ -70,6 +70,9 @@ const handleImageError = e => { setTimeout(() => { /** @type {HTMLImageElement} */ const node = e.target; + if (!node) { + return; + } const attempts = parseInt(node.getAttribute('data-reload-n'), 10) || 0; if (attempts >= IMAGE_RETRY_LIMIT) { logger.error(`failed to load an image after ${attempts} attempts`); @@ -159,7 +162,7 @@ class ChatRenderer { // Find scrollable parent this.scrollNode = findNearestScrollableParent(this.rootNode); this.scrollNode.addEventListener('scroll', this.handleScroll); - setImmediate(() => { + setTimeout(() => { this.scrollToBottom(); }); // Flush the queue @@ -389,12 +392,9 @@ class ChatRenderer { message.node = node; // Query all possible selectors to find out the message type if (!message.type) { - // IE8: Does not support querySelector on elements that - // are not yet in the document. - const typeDef = !Byond.IS_LTE_IE8 && MESSAGE_TYPES - .find(typeDef => ( - typeDef.selector && node.querySelector(typeDef.selector) - )); + const typeDef = MESSAGE_TYPES.find( + (typeDef) => typeDef.selector && node.querySelector(typeDef.selector), + ); message.type = typeDef?.type || MESSAGE_TYPE_UNKNOWN; } updateMessageBadge(message); @@ -418,7 +418,7 @@ class ChatRenderer { this.rootNode.appendChild(fragment); } if (this.scrollTracking) { - setImmediate(() => this.scrollToBottom()); + setTimeout(() => this.scrollToBottom()); } } // Notify listeners that we have processed the batch @@ -502,7 +502,9 @@ class ChatRenderer { const cssRules = styleSheets[i].cssRules; for (let i = 0; i < cssRules.length; i++) { const rule = cssRules[i]; - cssText += rule.cssText + '\n'; + if (rule && typeof rule.cssText === 'string') { + cssText += rule.cssText + '\n'; + } } } cssText += 'body, html { background-color: #141414 }\n'; @@ -527,13 +529,13 @@ class ChatRenderer { + '\n' + '\n'; // Create and send a nice blob - const blob = new Blob([pageHtml]); + const blob = new Blob([pageHtml], { type: 'text/plain' }); const timestamp = new Date() .toISOString() .substring(0, 19) .replace(/[-:]/g, '') .replace('T', '-'); - window.navigator.msSaveBlob(blob, `ss13-chatlog-${timestamp}.html`); + Byond.saveBlob(blob, `ss13-chatlog-${timestamp}.html`, '.html'); } } diff --git a/tgui/packages/tgui-panel/index.js b/tgui/packages/tgui-panel/index.js index 721fa97fb50c2..a0904908e28c0 100644 --- a/tgui/packages/tgui-panel/index.js +++ b/tgui/packages/tgui-panel/index.js @@ -75,14 +75,8 @@ const setupApp = () => { Byond.subscribe((type, payload) => store.dispatch({ type, payload })); // Unhide the panel - Byond.winset('output', { - 'is-visible': false, - }); - Byond.winset('browseroutput', { - 'is-visible': true, - 'is-disabled': false, - 'pos': '0x0', - 'size': '0x0', + Byond.winset('legacy_output_selector', { + left: 'output_browser', }); // Resize the panel to match the non-browser output diff --git a/tgui/packages/tgui-panel/panelFocus.js b/tgui/packages/tgui-panel/panelFocus.js index 6922418c89977..db7f431e2708b 100644 --- a/tgui/packages/tgui-panel/panelFocus.js +++ b/tgui/packages/tgui-panel/panelFocus.js @@ -15,7 +15,7 @@ import { focusMap } from 'tgui/focus'; // text you can select with the mouse. const MIN_SELECTION_DISTANCE = 10; -const deferredFocusMap = () => setImmediate(() => focusMap()); +const deferredFocusMap = () => setTimeout(() => focusMap()); export const setupPanelFocusHacks = () => { let focusStolen = false; diff --git a/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss b/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss index e441ce1ac03fd..b1ac0c4044fd1 100644 --- a/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss +++ b/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss @@ -22,6 +22,7 @@ img { img.icon { height: 1em; min-height: 16px; + min-width: 16px; width: auto; vertical-align: bottom; } diff --git a/tgui/packages/tgui-polyfill/package.json b/tgui/packages/tgui-polyfill/package.json index e4d6cc70148bb..8a441f8321cd1 100644 --- a/tgui/packages/tgui-polyfill/package.json +++ b/tgui/packages/tgui-polyfill/package.json @@ -3,7 +3,7 @@ "name": "tgui-polyfill", "version": "4.3.0", "scripts": { - "tgui-polyfill:build": "terser 00-html5shiv.js 01-ie8.js 02-dom4.js 03-css-om.js 10-misc.js --ie8 -f ascii_only,comments=false -o ../../public/tgui-polyfill.bundle.js" + "tgui-polyfill:build": "terser 00-html5shiv.js 01-ie8.js 02-dom4.js 03-css-om.js 10-misc.js --ie8 -f ascii_only,comments=false -o ../../public/tgui-polyfill.min.js" }, "dependencies": { "core-js": "^3.16.1", diff --git a/tgui/packages/tgui/assets.js b/tgui/packages/tgui/assets.js deleted file mode 100644 index e64198515574b..0000000000000 --- a/tgui/packages/tgui/assets.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -const EXCLUDED_PATTERNS = [/v4shim/i]; -const loadedMappings = {}; - -export const resolveAsset = name => ( - loadedMappings[name] || name -); - -export const resolveAssetOptional = name => loadedMappings[name]; - -export const assetMiddleware = store => next => action => { - const { type, payload } = action; - if (type === 'asset/stylesheet') { - Byond.loadCss(payload); - return; - } - if (type === 'asset/mappings') { - for (let name of Object.keys(payload)) { - // Skip anything that matches excluded patterns - if (EXCLUDED_PATTERNS.some(regex => regex.test(name))) { - continue; - } - const url = payload[name]; - const ext = name.split('.').pop(); - loadedMappings[name] = url; - if (ext === 'css') { - Byond.loadCss(url); - } - if (ext === 'js') { - Byond.loadJs(url); - } - } - return; - } - next(action); -}; diff --git a/tgui/packages/tgui/assets.ts b/tgui/packages/tgui/assets.ts new file mode 100644 index 0000000000000..e519d0c2f76c4 --- /dev/null +++ b/tgui/packages/tgui/assets.ts @@ -0,0 +1,45 @@ +/** + * @file + * @copyright 2020 Aleksej Komarov + * @license MIT + */ + +import { Action, AnyAction, Middleware } from '../common/redux'; + +import { Dispatch } from 'common/redux'; + +const EXCLUDED_PATTERNS = [/v4shim/i]; +const loadedMappings: Record = {}; + +export const resolveAsset = (name: string): string => + loadedMappings[name] || name; + +export const assetMiddleware: Middleware = + (storeApi) => + (next: Dispatch) => + (action: ActionType) => { + const { type, payload } = action as AnyAction; + if (type === 'asset/stylesheet') { + Byond.loadCss(payload); + return; + } + if (type === 'asset/mappings') { + for (const name of Object.keys(payload)) { + // Skip anything that matches excluded patterns + if (EXCLUDED_PATTERNS.some((regex) => regex.test(name))) { + continue; + } + const url = payload[name]; + const ext = name.split('.').pop(); + loadedMappings[name] = url; + if (ext === 'css') { + Byond.loadCss(url); + } + if (ext === 'js') { + Byond.loadJs(url); + } + } + return; + } + next(action); + }; diff --git a/tgui/packages/tgui/backend.ts b/tgui/packages/tgui/backend.ts index 7ca6eeecde1ce..c6e1e9064477f 100644 --- a/tgui/packages/tgui/backend.ts +++ b/tgui/packages/tgui/backend.ts @@ -154,7 +154,7 @@ export const backendMiddleware = store => { Byond.winset(Byond.windowId, { 'is-visible': false, }); - setImmediate(() => focusMap()); + setTimeout(() => focusMap()); } if (type === 'backend/update') { @@ -184,7 +184,7 @@ export const backendMiddleware = store => { setupDrag(); // We schedule this for the next tick here because resizing and unhiding // during the same tick will flash with a white background. - setImmediate(() => { + setTimeout(() => { perf.mark('resume/start'); // Doublecheck if we are not re-suspended. const { suspended } = selectBackend(store.getState()); diff --git a/tgui/packages/tgui/interfaces/AlertModal.js b/tgui/packages/tgui/interfaces/AlertModal.js index 17c71d10fb155..1b013c9093bf8 100644 --- a/tgui/packages/tgui/interfaces/AlertModal.js +++ b/tgui/packages/tgui/interfaces/AlertModal.js @@ -67,11 +67,39 @@ export class AlertModal extends Component { const { current } = this.state; const focusCurrentButton = () => this.setCurrent(current, false); + // Stolen wholesale from fontcode + const textWidth = (text, font, fontsize) => { + // default font height is 12 in tgui + font = fontsize + 'x ' + font; + const c = document.createElement('canvas'); + const ctx = c.getContext('2d'); + ctx.font = font; + return ctx.measureText(text).width; + }; + + // At least one of the buttons has a long text message + const isVerbose = buttons.some( + (button) => + textWidth(button, '', 12) > + windowWidth / buttons.length - paddingMagicNumber, + ); + + const windowWidth = 345 + (buttons.length > 2 ? 55 : 0); + + // very accurate estimate of padding for each num of buttons + const paddingMagicNumber = 67 / buttons.length + 23; + + // Dynamically sets window dimensions + const windowHeight = + 120 + + (isVerbose ? 15 * buttons.length : 0) + + (message.length > 30 ? Math.ceil(message.length / 4) : 0); + return ( + width={windowWidth} + height={windowHeight}> {timeout && } { { - + {tabMode === TABS.foodtype && diff --git a/tgui/packages/tgui/interfaces/PlaneMasterDebug.tsx b/tgui/packages/tgui/interfaces/PlaneMasterDebug.tsx index 530ee0a8f014f..8ce18afe0d4eb 100644 --- a/tgui/packages/tgui/interfaces/PlaneMasterDebug.tsx +++ b/tgui/packages/tgui/interfaces/PlaneMasterDebug.tsx @@ -655,7 +655,7 @@ const DrawAbovePlane = (props, context) => { - + )} {!!enable_group_view && } @@ -692,7 +692,7 @@ const PlaneWindow = (props, context) => { - + }>
@@ -894,7 +894,7 @@ const GroupDropdown = (props, context) => { ); }; -const RefreshButton = (props, context) => { +const RebuildButton = (props, context) => { const { act } = useBackend(context); const { no_position } = props; @@ -904,8 +904,8 @@ const RefreshButton = (props, context) => { right={no_position ? '' : '6px'} position={no_position ? '' : 'absolute'} icon="recycle" - onClick={() => act('refresh')} - tooltip="Refreshes ALL plane masters. Kinda laggy, but useful" + onClick={() => act('Rebuild')} + tooltip="Rebuilds ALL plane masters. Kinda laggy, but useful" /> ); }; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/index.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/index.ts index d487a282e58cb..76d64d1de1617 100644 --- a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/index.ts +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/index.ts @@ -8,7 +8,7 @@ import { Feature } from "./base"; // while also preventing downstreams from needing to mutate existing files. const features: Record> = {}; -const requireFeature = require.context("./", true, /.tsx$/); +const requireFeature = require.context('./', true, /.tsx$/); for (const key of requireFeature.keys()) { if (key === "index" || key === "base") { diff --git a/tgui/packages/tgui/interfaces/SkillMenu.js b/tgui/packages/tgui/interfaces/SkillMenu.js index 45f13c51a64b8..3ff06eb45d543 100644 --- a/tgui/packages/tgui/interfaces/SkillMenu.js +++ b/tgui/packages/tgui/interfaces/SkillMenu.js @@ -10,8 +10,8 @@ export const SkillMenu = (props, context) => { return (
@@ -88,7 +88,7 @@ const AdjustSkill = (props, context) => { className={classes(['crafting32x32', skill.replace(/ /g, '')])} /> - + diff --git a/tgui/packages/tgui/layouts/Layout.js b/tgui/packages/tgui/layouts/Layout.js index 37b01f0663ad8..f5375ec698d79 100644 --- a/tgui/packages/tgui/layouts/Layout.js +++ b/tgui/packages/tgui/layouts/Layout.js @@ -10,6 +10,7 @@ import { addScrollableNode, removeScrollableNode } from '../events'; export const Layout = (props) => { const { className, theme = 'nanotrasen', children, ...rest } = props; + document.documentElement.className = `theme-${theme}`; return (
() => { - return ( - - - {type === 'notFound' && ( -
Interface {name} was not found.
- )} - {type === 'missingExport' && ( -
Interface {name} is missing an export.
- )} -
-
- ); -}; +const routingError = + (type: 'notFound' | 'missingExport', name: string) => () => { + return ( + + + {type === 'notFound' && ( +
+ Interface {name} was not found. +
+ )} + {type === 'missingExport' && ( +
+ Interface {name} is missing an export. +
+ )} +
+
+ ); + }; +// Displays an empty Window with scrollable content const SuspendedWindow = () => { return ( @@ -33,7 +40,8 @@ const SuspendedWindow = () => { ); }; -export const getRoutedComponent = store => { +// Get the component for the current route +export const getRoutedComponent = (store: Store) => { const state = store.getState(); const { suspended, config } = selectBackend(state); if (suspended) { @@ -48,19 +56,20 @@ export const getRoutedComponent = store => { } const name = config?.interface; const interfacePathBuilders = [ - name => `./${name}.tsx`, - name => `./${name}.js`, - name => `./${name}/index.tsx`, - name => `./${name}/index.js`, + (name: string) => `./${name}.tsx`, + (name: string) => `./${name}.jsx`, + (name: string) => `./${name}.js`, + (name: string) => `./${name}/index.tsx`, + (name: string) => `./${name}/index.jsx`, + (name: string) => `./${name}/index.js`, ]; let esModule; while (!esModule && interfacePathBuilders.length > 0) { - const interfacePathBuilder = interfacePathBuilders.shift(); + const interfacePathBuilder = interfacePathBuilders.shift()!; const interfacePath = interfacePathBuilder(name); try { esModule = requireInterface(interfacePath); - } - catch (err) { + } catch (err) { if (err.code !== 'MODULE_NOT_FOUND') { throw err; } diff --git a/tgui/packages/tgui/store.js b/tgui/packages/tgui/store.js deleted file mode 100644 index 4035b4d1d85e6..0000000000000 --- a/tgui/packages/tgui/store.js +++ /dev/null @@ -1,93 +0,0 @@ -/** - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -import { flow } from 'common/fp'; -import { applyMiddleware, combineReducers, createStore } from 'common/redux'; -import { Component } from 'inferno'; -import { assetMiddleware } from './assets'; -import { backendMiddleware, backendReducer } from './backend'; -import { debugMiddleware, debugReducer, relayMiddleware } from './debug'; -import { createLogger } from './logging'; - -const logger = createLogger('store'); - -export const configureStore = (options = {}) => { - const reducer = flow([ - combineReducers({ - debug: debugReducer, - backend: backendReducer, - }), - options.reducer, - ]); - const middleware = [ - ...(options.middleware?.pre || []), - assetMiddleware, - backendMiddleware, - ...(options.middleware?.post || []), - ]; - if (process.env.NODE_ENV !== 'production') { - middleware.unshift( - loggingMiddleware, - debugMiddleware, - relayMiddleware); - } - const enhancer = applyMiddleware(...middleware); - const store = createStore(reducer, enhancer); - // Globals - window.__store__ = store; - window.__augmentStack__ = createStackAugmentor(store); - return store; -}; - -const loggingMiddleware = store => next => action => { - const { type, payload } = action; - if (type === 'update' || type === 'backend/update') { - logger.debug('action', { type }); - } - else { - logger.debug('action', action); - } - return next(action); -}; - -/** - * Creates a function, which can be assigned to window.__augmentStack__ - * to augment reported stack traces with useful data for debugging. - */ -const createStackAugmentor = store => (stack, error) => { - if (!error) { - error = new Error(stack.split('\n')[0]); - error.stack = stack; - } - else if (typeof error === 'object' && !error.stack) { - error.stack = stack; - } - logger.log('FatalError:', error); - const state = store.getState(); - const config = state?.backend?.config; - let augmentedStack = stack; - augmentedStack += '\nUser Agent: ' + navigator.userAgent; - augmentedStack += '\nState: ' + JSON.stringify({ - ckey: config?.client?.ckey, - interface: config?.interface, - window: config?.window, - }); - return augmentedStack; -}; - -/** - * Store provider for Inferno apps. - */ -export class StoreProvider extends Component { - getChildContext() { - const { store } = this.props; - return { store }; - } - - render() { - return this.props.children; - } -} diff --git a/tgui/packages/tgui/store.ts b/tgui/packages/tgui/store.ts new file mode 100644 index 0000000000000..468298fe07c92 --- /dev/null +++ b/tgui/packages/tgui/store.ts @@ -0,0 +1,119 @@ +/** + * @file + * @copyright 2020 Aleksej Komarov + * @license MIT + */ + +import { Middleware, Reducer, Store, applyMiddleware, combineReducers, createStore } from 'common/redux'; +import { backendMiddleware, backendReducer } from './backend'; +import { debugMiddleware, debugReducer, relayMiddleware } from './debug'; + +import { Component } from 'inferno'; +import { assetMiddleware } from './assets'; +import { createLogger } from './logging'; +import { flow } from 'common/fp'; + +type ConfigureStoreOptions = { + sideEffects?: boolean; + reducer?: Reducer; + middleware?: { + pre?: Middleware[]; + post?: Middleware[]; + }; +}; + +type StackAugmentor = (stack: string, error?: Error) => string; + +type StoreProviderProps = { + store: Store; + children: any; +}; + +const logger = createLogger('store'); + +export const configureStore = (options: ConfigureStoreOptions = {}): Store => { + const { sideEffects = true, reducer, middleware } = options; + const rootReducer: Reducer = flow([ + combineReducers({ + debug: debugReducer, + backend: backendReducer, + }), + reducer, + ]); + + const middlewares: Middleware[] = !sideEffects + ? [] + : [ + ...(middleware?.pre || []), + assetMiddleware, + backendMiddleware, + ...(middleware?.post || []), + ]; + + if (process.env.NODE_ENV !== 'production') { + // We are using two if statements because Webpack is capable of + // removing this specific block as dead code. + if (sideEffects) { + middlewares.unshift(loggingMiddleware, debugMiddleware, relayMiddleware); + } + } + + const enhancer = applyMiddleware(...middlewares); + const store = createStore(rootReducer, enhancer); + + // Globals + window.__store__ = store; + window.__augmentStack__ = createStackAugmentor(store); + + return store; +}; + +const loggingMiddleware: Middleware = (store) => (next) => (action) => { + const { type } = action; + logger.debug( + 'action', + type === 'update' || type === 'backend/update' ? { type } : action + ); + return next(action); +}; + +/** + * Creates a function, which can be assigned to window.__augmentStack__ + * to augment reported stack traces with useful data for debugging. + */ +const createStackAugmentor = + (store: Store): StackAugmentor => + (stack, error) => { + error = error || new Error(stack.split('\n')[0]); + error.stack = error.stack || stack; + + logger.log('FatalError:', error); + const state = store.getState(); + const config = state?.backend?.config; + + return ( + stack + + '\nUser Agent: ' + + navigator.userAgent + + '\nState: ' + + JSON.stringify({ + ckey: config?.client?.ckey, + interface: config?.interface, + window: config?.window, + }) + ); + }; + +/** + * Store provider for Inferno apps. + */ +export class StoreProvider extends Component { + getChildContext() { + const { store } = this.props; + return { store }; + } + + render() { + return this.props.children; + } +} diff --git a/tgui/packages/tgui/stories/ByondUi.stories.js b/tgui/packages/tgui/stories/ByondUi.stories.js index ca88d1028ff2d..b721f77a368a5 100644 --- a/tgui/packages/tgui/stories/ByondUi.stories.js +++ b/tgui/packages/tgui/stories/ByondUi.stories.js @@ -31,7 +31,7 @@ const Story = (props, context) => { buttons={(