diff --git a/code/datums/components/uplink.dm b/code/datums/components/uplink.dm index 4a9c558d39..433e57abbe 100644 --- a/code/datums/components/uplink.dm +++ b/code/datums/components/uplink.dm @@ -206,6 +206,9 @@ GLOBAL_LIST_EMPTY(uplinks) return data /datum/component/uplink/ui_act(action, params) + . = ..() + if(.) + return if(!active) return switch(action) diff --git a/code/game/machinery/colormate.dm b/code/game/machinery/colormate.dm index d059d492ae..281538e7a8 100644 --- a/code/game/machinery/colormate.dm +++ b/code/game/machinery/colormate.dm @@ -23,6 +23,8 @@ /obj/item/storage/backpack, /obj/item/storage/belt ) + /// Temporary messages + var/temp /obj/machinery/gear_painter/Initialize(mapload) . = ..() @@ -69,6 +71,8 @@ if(!QDELETED(H)) H.release() insert_mob(victim, user) + temp = "[victim] has been inserted." + SStgui.update_uis(src) if(is_type_in_list(I, allowed_types) && is_operational()) if(!user.transferItemToLoc(I, src)) @@ -80,6 +84,9 @@ inserted = I update_icon() + temp = "[I] has been inserted." + SStgui.update_uis(src) + else return ..() @@ -101,137 +108,159 @@ /obj/machinery/gear_painter/AltClick(mob/user) . = ..() - if(!user.CanReach(src)) + drop_item() + +/obj/machinery/gear_painter/proc/drop_item() + if(!usr.CanReach(src)) return if(!inserted) return - to_chat(user, "You remove [inserted] from [src]") + to_chat(usr, "You remove [inserted] from [src]") inserted.forceMove(drop_location()) + var/mob/living/user = usr + if(istype(user)) + user.put_in_hands(inserted) inserted = null update_icon() - updateUsrDialog() + SStgui.update_uis(src) -/obj/machinery/gear_painter/ui_interact(mob/user) - if(!is_operational()) - return - user.set_machine(src) - var/list/dat = list("Color Mate Control Panel
") - if(!inserted) - dat += "No item inserted." +/obj/machinery/gear_painter/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Colormate", src.name) + ui.set_autoupdate(FALSE) //This might be a bit intensive, better to not update it every few ticks + ui.open() + +/obj/machinery/gear_painter/ui_data(mob/user) + . = list() + .["matrixactive"] = matrix_mode + .["matrixcolors"] = list( + "rr" = color_matrix_last[1], + "rg" = color_matrix_last[2], + "rb" = color_matrix_last[3], + "gr" = color_matrix_last[4], + "gg" = color_matrix_last[5], + "gb" = color_matrix_last[6], + "br" = color_matrix_last[7], + "bg" = color_matrix_last[8], + "bb" = color_matrix_last[9], + "cr" = color_matrix_last[10], + "cg" = color_matrix_last[11], + "cb" = color_matrix_last[12] + ) + if(temp) + .["temp"] = temp + if(inserted) + .["item"] = list() + .["item"]["name"] = inserted.name + .["item"]["sprite"] = icon2base64(getFlatIcon(inserted,defdir=SOUTH,no_anim=TRUE)) + .["item"]["preview"] = icon2base64(build_preview()) else - dat += "Item inserted: [inserted]
" - dat += "Matrix mode: [matrix_mode? "On" : "Off"]" - if(!matrix_mode) - dat += "Select new color.
" - dat += "Color: " - dat += "Apply new color.

" + .["item"] = null + +/obj/machinery/gear_painter/ui_act(action, params) + . = ..() + if(.) + return + if(inserted) + switch(action) + if("switch_modes") + matrix_mode = text2num(params["mode"]) + return TRUE + if("choose_color") + var/chosen_color = input(usr, "Choose a color: ", "Colormate color picking", activecolor) as color|null + if(chosen_color) + activecolor = chosen_color + return TRUE + if("paint") + if(!check_valid_color(activecolor, usr)) + return TRUE + inserted.add_atom_colour(activecolor, FIXED_COLOUR_PRIORITY) + playsound(src, 'sound/effects/spray3.ogg', 50, 1) + temp = "Painted Successfully!" + return TRUE + if("drop") + temp = "Ejected \the [inserted]!" + drop_item() + return TRUE + if("clear") + inserted.remove_atom_colour(FIXED_COLOUR_PRIORITY) + playsound(src, 'sound/effects/spray3.ogg', 50, 1) + temp = "Cleared Successfully!" + return TRUE + if("set_matrix_color") + color_matrix_last[params["color"]] = params["value"] + return TRUE + if("matrix_paint") + var/list/cm = rgb_construct_color_matrix( + text2num(color_matrix_last[1]), + text2num(color_matrix_last[2]), + text2num(color_matrix_last[3]), + text2num(color_matrix_last[4]), + text2num(color_matrix_last[5]), + text2num(color_matrix_last[6]), + text2num(color_matrix_last[7]), + text2num(color_matrix_last[8]), + text2num(color_matrix_last[9]), + text2num(color_matrix_last[10]), + text2num(color_matrix_last[11]), + text2num(color_matrix_last[12]) + ) + if(!check_valid_color(cm, usr)) + return TRUE + inserted.add_atom_colour(cm, FIXED_COLOUR_PRIORITY) + playsound(src, 'sound/effects/spray3.ogg', 50, 1) + temp = "Matrix Painted Successfully!" + return TRUE + +/// Produces the preview image of the item, used in the UI, the way the color is not stacking is a sin. +/obj/machinery/gear_painter/proc/build_preview() + if(inserted) //sanity + if(matrix_mode) + var/list/cm = rgb_construct_color_matrix( + text2num(color_matrix_last[1]), + text2num(color_matrix_last[2]), + text2num(color_matrix_last[3]), + text2num(color_matrix_last[4]), + text2num(color_matrix_last[5]), + text2num(color_matrix_last[6]), + text2num(color_matrix_last[7]), + text2num(color_matrix_last[8]), + text2num(color_matrix_last[9]), + text2num(color_matrix_last[10]), + text2num(color_matrix_last[11]), + text2num(color_matrix_last[12]) + ) + if(!check_valid_color(cm, usr)) + temp = "Failed to generate preview: Invalid color!" + return getFlatIcon(inserted, defdir=SOUTH, no_anim=TRUE) + + var/cur_color = inserted.color + inserted.color = null + inserted.color = cm + var/icon/preview = getFlatIcon(inserted, defdir=SOUTH, no_anim=TRUE) + inserted.color = cur_color + + . = preview + else - // POGGERS -#define MATRIX_FIELD(field, default) " " - dat += "
" - dat += "" - dat += "[cm] is far too dark (min lightness [minimum_normal_lightness]!") + temp = "[cm] is far too dark (min lightness [minimum_normal_lightness]!" return FALSE return TRUE else // matrix @@ -245,6 +274,6 @@ COLORTEST("FFFFFF", cm) #undef COLORTEST if(passed < minimum_matrix_tests) - to_chat(user, "[english_list(color)] is not allowed (pased [passed] out of 4, minimum [minimum_matrix_tests], minimum lightness [minimum_matrix_lightness]).") + temp = "[english_list(color)] is not allowed (passed [passed] out of 4, minimum [minimum_matrix_tests], minimum lightness [minimum_matrix_lightness])." return FALSE return TRUE diff --git a/code/game/machinery/computer/crew.dm b/code/game/machinery/computer/crew.dm index 19da4f75d8..bf690398a4 100644 --- a/code/game/machinery/computer/crew.dm +++ b/code/game/machinery/computer/crew.dm @@ -200,6 +200,9 @@ GLOBAL_DATUM_INIT(crewmonitor, /datum/crewmonitor, new) return b["totaldam"] - a["totaldam"] /datum/crewmonitor/ui_act(action,params) + . = ..() + if(.) + return var/mob/living/silicon/ai/AI = usr if(!istype(AI)) return diff --git a/code/game/machinery/telecomms/machine_interactions.dm b/code/game/machinery/telecomms/machine_interactions.dm index 9876206724..808dc4877b 100644 --- a/code/game/machinery/telecomms/machine_interactions.dm +++ b/code/game/machinery/telecomms/machine_interactions.dm @@ -86,6 +86,9 @@ .["machine"]["chang_freq_value"] = change_freq_value /obj/machinery/telecomms/ui_act(action, params, datum/tgui/ui) + . = ..() + if(.) + return if(!canInteract(usr)) if(ui) ui.close() //haha no. diff --git a/code/modules/asset_cache/asset_list.dm b/code/modules/asset_cache/asset_list.dm index 221febbe14..94c7630bd9 100644 --- a/code/modules/asset_cache/asset_list.dm +++ b/code/modules/asset_cache/asset_list.dm @@ -11,6 +11,10 @@ GLOBAL_LIST_EMPTY(asset_datums) /datum/asset var/_abstract = /datum/asset + var/cached_url_mappings + + /// Whether or not this asset should be loaded in the "early assets" SS + var/early = FALSE /datum/asset/New() GLOB.asset_datums[type] = src @@ -19,6 +23,13 @@ GLOBAL_LIST_EMPTY(asset_datums) /datum/asset/proc/get_url_mappings() return list() +/// Returns a cached tgui message of URL mappings +/datum/asset/proc/get_serialized_url_mappings() + if (isnull(cached_url_mappings)) + cached_url_mappings = TGUI_CREATE_MESSAGE("asset/mappings", get_url_mappings()) + + return cached_url_mappings + /datum/asset/proc/register() return @@ -169,6 +180,8 @@ GLOBAL_LIST_EMPTY(asset_datums) I = icon(I, icon_state=icon_state, dir=dir, frame=frame, moving=moving) if (!I || !length(icon_states(I))) // that direction or state doesn't exist return + //any sprite modifications we want to do (aka, coloring a greyscaled asset) + I = ModifyInserted(I) var/size_id = "[I.Width()]x[I.Height()]" var/size = sizes[size_id] @@ -185,6 +198,15 @@ GLOBAL_LIST_EMPTY(asset_datums) sizes[size_id] = size = list(1, I, null) sprites[sprite_name] = list(size_id, 0) +/** + * A simple proc handing the Icon for you to modify before it gets turned into an asset. + * + * Arguments: + * * I: icon being turned into an asset + */ +/datum/asset/spritesheet/proc/ModifyInserted(icon/pre_asset) + return pre_asset + /datum/asset/spritesheet/proc/InsertAll(prefix, icon/I, list/directions) if (length(prefix)) prefix = "[prefix]-" @@ -217,6 +239,19 @@ GLOBAL_LIST_EMPTY(asset_datums) var/size_id = sprite[SPR_SIZE] return {"[name][size_id] [sprite_name]"} +/** + * Returns the size class (ex design32x32) for a given sprite's icon + * + * Arguments: + * * sprite_name - The sprite to get the size of + */ +/datum/asset/spritesheet/proc/icon_size_id(sprite_name) + var/sprite = sprites[sprite_name] + if (!sprite) + return null + var/size_id = sprite[SPR_SIZE] + return "[name][size_id]" + #undef SPR_SIZE #undef SPR_IDX #undef SPRSZ_COUNT @@ -224,6 +259,24 @@ GLOBAL_LIST_EMPTY(asset_datums) #undef SPRSZ_STRIPPED +/datum/asset/changelog_item + _abstract = /datum/asset/changelog_item + var/item_filename + +/datum/asset/changelog_item/New(date) + item_filename = sanitize_filename("[date].yml") + SSassets.transport.register_asset(item_filename, file("html/changelogs/archive/" + item_filename)) + +/datum/asset/changelog_item/send(client) + if (!item_filename) + return + . = SSassets.transport.send_assets(client, item_filename) + +/datum/asset/changelog_item/get_url_mappings() + if (!item_filename) + return + . = list("[item_filename]" = SSassets.transport.get_asset_url(item_filename)) + /datum/asset/spritesheet/simple _abstract = /datum/asset/spritesheet/simple var/list/assets @@ -315,3 +368,28 @@ GLOBAL_LIST_EMPTY(asset_datums) /datum/asset/simple/namespaced/proc/get_htmlloader(filename) return url2htmlloader(SSassets.transport.get_asset_url(filename, assets[filename])) +/// A subtype to generate a JSON file from a list +/datum/asset/json + _abstract = /datum/asset/json + /// The filename, will be suffixed with ".json" + var/name + +/datum/asset/json/send(client) + return SSassets.transport.send_assets(client, "data/[name].json") + +/datum/asset/json/get_url_mappings() + return list( + "[name].json" = SSassets.transport.get_asset_url("data/[name].json"), + ) + +/datum/asset/json/register() + var/filename = "data/[name].json" + fdel(filename) + text2file(json_encode(generate()), filename) + SSassets.transport.register_asset(filename, fcopy_rsc(filename)) + fdel(filename) + +/// Returns the data that will be JSON encoded +/datum/asset/json/proc/generate() + SHOULD_CALL_PARENT(FALSE) + CRASH("generate() not implemented for [type]!") diff --git a/code/modules/cargo/expressconsole.dm b/code/modules/cargo/expressconsole.dm index 1c9c21e0d7..c065c387c5 100644 --- a/code/modules/cargo/expressconsole.dm +++ b/code/modules/cargo/expressconsole.dm @@ -128,6 +128,9 @@ return data /obj/machinery/computer/cargo/express/ui_act(action, params, datum/tgui/ui) + . = ..() + if(.) + return switch(action) if("LZCargo") usingBeacon = FALSE diff --git a/code/modules/library/soapstone.dm b/code/modules/library/soapstone.dm index 0d312c485a..fd268a7774 100644 --- a/code/modules/library/soapstone.dm +++ b/code/modules/library/soapstone.dm @@ -232,6 +232,9 @@ return data /obj/structure/chisel_message/ui_act(action, params, datum/tgui/ui) + . = ..() + if(.) + return var/mob/user = usr var/is_admin = check_rights_for(user.client, R_ADMIN) var/is_creator = user.ckey == creator_key diff --git a/code/modules/tgui/external.dm b/code/modules/tgui/external.dm index 565c595473..d858c2d6bf 100644 --- a/code/modules/tgui/external.dm +++ b/code/modules/tgui/external.dm @@ -74,7 +74,7 @@ * return bool If the user's input has been handled and the UI should update. */ /datum/proc/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) - // SHOULD_CALL_PARENT(TRUE) + SHOULD_CALL_PARENT(TRUE) // If UI is not interactive or usr calling Topic is not the UI user, bail. if(!ui || ui.status != UI_INTERACTIVE) return TRUE @@ -145,6 +145,7 @@ * client/verb/uiclose(), which closes the ui window */ /datum/proc/ui_close(mob/user) + SIGNAL_HANDLER /** * verb diff --git a/code/modules/tgui/tgui.dm b/code/modules/tgui/tgui.dm index 55497b150a..a0c39258b9 100644 --- a/code/modules/tgui/tgui.dm +++ b/code/modules/tgui/tgui.dm @@ -58,11 +58,16 @@ src.interface = interface if(title) src.title = title - src.state = src_object.ui_state() + src.state = src_object.ui_state(user) // Deprecated if(ui_x && ui_y) src.window_size = list(ui_x, ui_y) +/datum/tgui/Destroy() + user = null + src_object = null + return ..() + /** * public * diff --git a/code/modules/tgui/tgui_alert.dm b/code/modules/tgui/tgui_alert.dm index d144588ad9..83a26c2963 100644 --- a/code/modules/tgui/tgui_alert.dm +++ b/code/modules/tgui/tgui_alert.dm @@ -8,8 +8,9 @@ * * title - The of the alert modal, shown on the top of the TGUI window. * * buttons - The options that can be chosen by the user, each string is assigned a button on the UI. * * timeout - The timeout of the alert, after which the modal will close and qdel itself. Set to zero for no timeout. + * * autofocus - The bool that controls if this alert should grab window focus. */ -/proc/tgui_alert(mob/user, message = null, title = null, list/buttons = list("Ok"), timeout = 0) +/proc/tgui_alert(mob/user, message = null, title = null, list/buttons = list("Ok"), timeout = 0, autofocus = TRUE) if (!user) user = usr if (!istype(user)) @@ -18,7 +19,7 @@ user = client.mob else return - var/datum/tgui_modal/alert = new(user, message, title, buttons, timeout) + var/datum/tgui_modal/alert = new(user, message, title, buttons, timeout, autofocus) alert.ui_interact(user) alert.wait() if (alert) @@ -36,8 +37,9 @@ * * buttons - The options that can be chosen by the user, each string is assigned a button on the UI. * * callback - The callback to be invoked when a choice is made. * * timeout - The timeout of the alert, after which the modal will close and qdel itself. Disabled by default, can be set to seconds otherwise. + * * autofocus - The bool that controls if this alert should grab window focus. */ -/proc/tgui_alert_async(mob/user, message = null, title = null, list/buttons = list("Ok"), datum/callback/callback, timeout = 0) +/proc/tgui_alert_async(mob/user, message = null, title = null, list/buttons = list("Ok"), datum/callback/callback, timeout = 0, autofocus = TRUE) if (!user) user = usr if (!istype(user)) @@ -46,7 +48,7 @@ user = client.mob else return - var/datum/tgui_modal/async/alert = new(user, message, title, buttons, callback, timeout) + var/datum/tgui_modal/async/alert = new(user, message, title, buttons, callback, timeout, autofocus) alert.ui_interact(user) /** @@ -68,13 +70,16 @@ var/start_time /// The lifespan of the tgui_modal, after which the window will close and delete itself. var/timeout + /// The bool that controls if this modal should grab window focus + var/autofocus /// Boolean field describing if the tgui_modal was closed by the user. var/closed -/datum/tgui_modal/New(mob/user, message, title, list/buttons, timeout) +/datum/tgui_modal/New(mob/user, message, title, list/buttons, timeout, autofocus) src.title = title src.message = message src.buttons = buttons.Copy() + src.autofocus = autofocus if (timeout) src.timeout = timeout start_time = world.time @@ -110,7 +115,8 @@ . = list( "title" = title, "message" = message, - "buttons" = buttons + "buttons" = buttons, + "autofocus" = autofocus ) if(timeout) @@ -140,8 +146,8 @@ /// The callback to be invoked by the tgui_modal upon having a choice made. var/datum/callback/callback -/datum/tgui_modal/async/New(mob/user, message, title, list/buttons, callback, timeout) - ..(user, message, title, buttons, timeout) +/datum/tgui_modal/async/New(mob/user, message, title, list/buttons, callback, timeout, autofocus) + ..(user, message, title, buttons, timeout, autofocus) src.callback = callback /datum/tgui_modal/async/Destroy(force, ...) diff --git a/code/modules/tgui/tgui_input_list.dm b/code/modules/tgui/tgui_input_list.dm index 242b69a934..a02dfac5f5 100644 --- a/code/modules/tgui/tgui_input_list.dm +++ b/code/modules/tgui/tgui_input_list.dm @@ -82,6 +82,7 @@ src.message = message src.buttons = list() src.buttons_map = list() + var/list/repeat_buttons = list() // Gets rid of illegal characters var/static/regex/whitelistedWords = regex(@{"([^\u0020-\u8000]+)"}) @@ -89,6 +90,9 @@ for(var/i in buttons) var/string_key = whitelistedWords.Replace("[i]", "") + //avoids duplicated keys E.g: when areas have the same name + string_key = avoid_assoc_duplicate_keys(string_key, repeat_buttons) + src.buttons += string_key src.buttons_map[string_key] = i @@ -144,7 +148,7 @@ if("choose") if (!(params["choice"] in buttons)) return - choice = buttons_map[params["choice"]] + set_choice(buttons_map[params["choice"]]) SStgui.close_uis(src) return TRUE if("cancel") @@ -152,6 +156,9 @@ closed = TRUE return TRUE +/datum/tgui_list_input/proc/set_choice(choice) + src.choice = choice + /** * # async tgui_list_input * @@ -162,23 +169,17 @@ var/datum/callback/callback /datum/tgui_list_input/async/New(mob/user, message, title, list/buttons, callback, timeout) - ..(user, title, message, buttons, timeout) + ..(user, message, title, buttons, timeout) src.callback = callback /datum/tgui_list_input/async/Destroy(force, ...) QDEL_NULL(callback) . = ..() -/datum/tgui_list_input/async/ui_close(mob/user) +/datum/tgui_list_input/async/set_choice(choice) . = ..() - qdel(src) - -/datum/tgui_list_input/async/ui_act(action, list/params) - . = ..() - if (!. || choice == null) - return - callback.InvokeAsync(choice) - qdel(src) + if(!isnull(src.choice)) + callback?.InvokeAsync(src.choice) /datum/tgui_list_input/async/wait() return diff --git a/code/modules/tgui/tgui_window.dm b/code/modules/tgui/tgui_window.dm index ae54b2dd3f..af6b5bdc74 100644 --- a/code/modules/tgui/tgui_window.dm +++ b/code/modules/tgui/tgui_window.dm @@ -258,7 +258,7 @@ if(istype(asset, /datum/asset/spritesheet)) var/datum/asset/spritesheet/spritesheet = asset send_message("asset/stylesheet", spritesheet.css_filename()) - send_message("asset/mappings", asset.get_url_mappings()) + send_raw_message(asset.get_serialized_url_mappings()) /** * private diff --git a/tgui/packages/tgui/interfaces/Colormate.js b/tgui/packages/tgui/interfaces/Colormate.js new file mode 100644 index 0000000000..6783764bcf --- /dev/null +++ b/tgui/packages/tgui/interfaces/Colormate.js @@ -0,0 +1,298 @@ +import { useBackend } from '../backend'; +import { Button, Flex, NoticeBox, NumberInput, Section, Tabs } from '../components'; +import { Window } from '../layouts'; + +export const Colormate = (props, context) => { + const { act, data } = useBackend(context); + const { matrixactive, temp } = data; + const item = data.item || []; + return ( + + + {temp ? ( + {temp} + ) : (null)} + {Object.keys(item).length ? ( + <> + +
+
Item:
+ +
+
+
Preview:
+ +
+
+
+ + act('switch_modes', { + mode: "0", + })} > + Regular coloring + + act('switch_modes', { + mode: "1", + })} > + Matrixed coloring + + + {matrixactive ? ( + <> +
Coloring: {item.name}
+ + + ) : ( + <> +
Coloring: {item.name}
+ + + )} +
+ + ) : ( +
+
No item inserted.
+
+ )} +
+
+ ); +}; + +export const ColormateNoMatrix = (props, context) => { + const { act, data } = useBackend(context); + return ( +
+ + +
+ ); +}; + +export const ColormateMatrix = (props, context) => { + const { act, data } = useBackend(context); + const matrixcolors = data.matrixcolors || []; + return ( +
+ + +
+ ); +};