mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-07-22 04:24:20 +01:00
Adds Bitfields to the VV menu (#27266)
* Adds Bitfields to the VV menu * oops * fixed * oops * woo --------- Co-authored-by: Burzah <116982774+Burzah@users.noreply.github.com>
This commit is contained in:
@@ -19,4 +19,5 @@
|
||||
#define VV_NULL "NULL"
|
||||
#define VV_RESTORE_DEFAULT "Restore to Default"
|
||||
#define VV_MARKED_DATUM "Marked Datum"
|
||||
#define VV_BITFIELD "Bitfield"
|
||||
#define VV_REGEX "Regex"
|
||||
|
||||
@@ -1,6 +1,95 @@
|
||||
GLOBAL_LIST_INIT(bitfields, generate_bitfields())
|
||||
|
||||
/// Specifies a bitfield for smarter debugging
|
||||
/datum/bitfield
|
||||
/// The variable name that contains the bitfield
|
||||
var/variable
|
||||
/// An associative list of the readable flag and its true value
|
||||
var/list/flags
|
||||
|
||||
/datum/bitfield/can_vv_delete()
|
||||
return FALSE
|
||||
|
||||
/datum/bitfield/vv_edit_var(var_name, var_value)
|
||||
return FALSE // no.
|
||||
|
||||
/// Turns /datum/bitfield subtypes into a list for use in debugging
|
||||
/proc/generate_bitfields()
|
||||
var/list/bitfields = list()
|
||||
for(var/_bitfield in subtypesof(/datum/bitfield))
|
||||
var/datum/bitfield/bitfield = new _bitfield
|
||||
bitfields[bitfield.variable] = bitfield.flags
|
||||
return bitfields
|
||||
|
||||
/proc/translate_bitfield(variable_type, variable_name, variable_value)
|
||||
if(variable_type != VV_BITFIELD)
|
||||
return variable_value
|
||||
|
||||
var/list/flags = list()
|
||||
for(var/flag in GLOB.bitfields[variable_name])
|
||||
if(variable_value & GLOB.bitfields[variable_name][flag])
|
||||
flags += flag
|
||||
if(length(flags))
|
||||
return jointext(flags, ", ")
|
||||
return "NONE"
|
||||
|
||||
/proc/input_bitfield(mob/user, bitfield, current_value)
|
||||
if(!user || !(bitfield in GLOB.bitfields))
|
||||
return
|
||||
var/list/currently_checked = list()
|
||||
for(var/name in GLOB.bitfields[bitfield])
|
||||
currently_checked[name] = (current_value & GLOB.bitfields[bitfield][name])
|
||||
|
||||
var/list/result = tgui_input_checkbox_list(user, "Editing bitfield for [bitfield].", "Editing bitfield", currently_checked)
|
||||
if(isnull(result) || !islist(result))
|
||||
return
|
||||
|
||||
var/new_result = 0
|
||||
for(var/name in GLOB.bitfields[bitfield])
|
||||
if(result[name])
|
||||
new_result |= GLOB.bitfields[bitfield][name]
|
||||
return new_result
|
||||
|
||||
// MARK: Default byond bitfields
|
||||
|
||||
DEFINE_BITFIELD(appearance_flags, list(
|
||||
"KEEP_APART" = KEEP_APART,
|
||||
"KEEP_TOGETHER" = KEEP_TOGETHER,
|
||||
"LONG_GLIDE" = LONG_GLIDE,
|
||||
"NO_CLIENT_COLOR" = NO_CLIENT_COLOR,
|
||||
"PIXEL_SCALE" = PIXEL_SCALE,
|
||||
"PLANE_MASTER" = PLANE_MASTER,
|
||||
"RESET_ALPHA" = RESET_ALPHA,
|
||||
"RESET_COLOR" = RESET_COLOR,
|
||||
"RESET_TRANSFORM" = RESET_TRANSFORM,
|
||||
"TILE_BOUND" = TILE_BOUND,
|
||||
"PASS_MOUSE" = PASS_MOUSE,
|
||||
"TILE_MOVER" = TILE_MOVER,
|
||||
))
|
||||
|
||||
DEFINE_BITFIELD(sight, list(
|
||||
"BLIND" = BLIND,
|
||||
"SEE_BLACKNESS" = SEE_BLACKNESS,
|
||||
"SEE_INFRA" = SEE_INFRA,
|
||||
"SEE_MOBS" = SEE_MOBS,
|
||||
"SEE_OBJS" = SEE_OBJS,
|
||||
"SEE_PIXELS" = SEE_PIXELS,
|
||||
"SEE_SELF" = SEE_SELF,
|
||||
"SEE_THRU" = SEE_THRU,
|
||||
"SEE_TURFS" = SEE_TURFS,
|
||||
))
|
||||
|
||||
DEFINE_BITFIELD(vis_flags, list(
|
||||
"VIS_HIDE" = VIS_HIDE,
|
||||
"VIS_INHERIT_DIR" = VIS_INHERIT_DIR,
|
||||
"VIS_INHERIT_ICON" = VIS_INHERIT_ICON,
|
||||
"VIS_INHERIT_ICON_STATE" = VIS_INHERIT_ICON_STATE,
|
||||
"VIS_INHERIT_ID" = VIS_INHERIT_ID,
|
||||
"VIS_INHERIT_LAYER" = VIS_INHERIT_LAYER,
|
||||
"VIS_INHERIT_PLANE" = VIS_INHERIT_PLANE,
|
||||
"VIS_UNDERLAY" = VIS_UNDERLAY,
|
||||
))
|
||||
|
||||
|
||||
// MARK: Other bitfields
|
||||
|
||||
|
||||
@@ -541,6 +541,8 @@
|
||||
else
|
||||
item = "<a href='byond://?_src_=vars;VarsList=\ref[L]'>[VV_HTML_ENCODE(name)] = /list ([length(L)])</a>"
|
||||
|
||||
else if(name in GLOB.bitfields)
|
||||
item = "[VV_HTML_ENCODE(name)] = <span class='value'>[VV_HTML_ENCODE(translate_bitfield(VV_BITFIELD, name, value))]</span>"
|
||||
else
|
||||
item = "[VV_HTML_ENCODE(name)] = <span class='value'>[VV_HTML_ENCODE(value)]</span>"
|
||||
|
||||
|
||||
@@ -65,14 +65,14 @@
|
||||
if(prompt != "Continue")
|
||||
return
|
||||
|
||||
default = vv_get_class(var_value)
|
||||
default = vv_get_class(variable, var_value)
|
||||
|
||||
if(isnull(default))
|
||||
to_chat(src, "Unable to determine variable type.")
|
||||
else
|
||||
to_chat(src, "Variable appears to be <b>[uppertext(default)]</b>.")
|
||||
|
||||
to_chat(src, "Variable contains: [var_value]")
|
||||
to_chat(src, "Variable contains: [translate_bitfield(default, variable, var_value)]")
|
||||
|
||||
if(default == VV_NUM)
|
||||
var/dir_text = ""
|
||||
@@ -89,7 +89,7 @@
|
||||
if(dir_text)
|
||||
to_chat(src, "If a direction, direction is: [dir_text]")
|
||||
|
||||
var/value = vv_get_value(default_class = default)
|
||||
var/value = vv_get_value(class = (default == VV_BITFIELD ? VV_BITFIELD : null), default_class = default, var_name = variable)
|
||||
var/new_value = value["value"]
|
||||
var/class = value["class"]
|
||||
|
||||
@@ -207,7 +207,7 @@
|
||||
|
||||
log_world("### MassVarEdit by [src]: [O.type] (A/R [accepted]/[rejected]) [variable]=[html_encode("[O.vars[variable]]")]([list2params(value)])")
|
||||
log_admin("[key_name(src)] mass modified [original_name]'s [variable] to [O.vars[variable]] (Type: [class]) ([accepted] objects modified)")
|
||||
message_admins("[key_name_admin(src)] mass modified [original_name]'s [variable] to [html_encode("[O.vars[variable]]")] (Type: [class]) ([accepted] objects modified)")
|
||||
message_admins("[key_name_admin(src)] mass modified [original_name]'s [variable] to [html_encode(translate_bitfield(default, variable, O.vars[variable]))] (Type: [class]) ([accepted] objects modified)")
|
||||
|
||||
/proc/get_all_of_type(T, subtypes = TRUE)
|
||||
var/list/typecache = list()
|
||||
|
||||
@@ -12,12 +12,15 @@ GLOBAL_PROTECT(VVckey_edit)
|
||||
GLOBAL_PROTECT(VVpixelmovement)
|
||||
GLOBAL_PROTECT(VVmaint_only)
|
||||
|
||||
/client/proc/vv_get_class(var_value)
|
||||
/client/proc/vv_get_class(var_name, var_value)
|
||||
if(isnull(var_value))
|
||||
. = VV_NULL
|
||||
|
||||
else if(isnum(var_value))
|
||||
. = VV_NUM
|
||||
if(var_name in GLOB.bitfields)
|
||||
. = VV_BITFIELD
|
||||
else
|
||||
. = VV_NUM
|
||||
|
||||
else if(istext(var_value))
|
||||
if(findtext(var_value, "\n"))
|
||||
@@ -61,7 +64,7 @@ GLOBAL_PROTECT(VVmaint_only)
|
||||
else
|
||||
. = VV_NULL
|
||||
|
||||
/client/proc/vv_get_value(class, default_class, current_value, list/restricted_classes, list/extra_classes, list/classes)
|
||||
/client/proc/vv_get_value(class, default_class, current_value, list/restricted_classes, list/extra_classes, list/classes, var_name)
|
||||
. = list("class" = class, "value" = null)
|
||||
if(!class)
|
||||
if(!classes)
|
||||
@@ -120,6 +123,11 @@ GLOBAL_PROTECT(VVmaint_only)
|
||||
.["class"] = null
|
||||
return
|
||||
|
||||
if(VV_BITFIELD)
|
||||
.["value"] = input_bitfield(usr, var_name, current_value)
|
||||
if(.["value"] == null)
|
||||
.["class"] = null
|
||||
return
|
||||
|
||||
if(VV_ATOM_TYPE)
|
||||
.["value"] = pick_closest_path(FALSE)
|
||||
@@ -451,7 +459,7 @@ GLOBAL_PROTECT(VVmaint_only)
|
||||
else
|
||||
variable = L[index]
|
||||
|
||||
default = vv_get_class(variable)
|
||||
default = vv_get_class(objectvar, variable)
|
||||
|
||||
to_chat(src, "Variable appears to be <b>[uppertext(default)]</b>.")
|
||||
|
||||
@@ -582,14 +590,14 @@ GLOBAL_PROTECT(VVmaint_only)
|
||||
|
||||
var_value = O.vars[variable]
|
||||
|
||||
var/default = vv_get_class(var_value)
|
||||
var/default = vv_get_class(variable, var_value)
|
||||
|
||||
if(isnull(default))
|
||||
to_chat(src, "Unable to determine variable type.")
|
||||
else
|
||||
to_chat(src, "Variable appears to be <b>[uppertext(default)]</b>.")
|
||||
|
||||
to_chat(src, "Variable contains: [var_value]")
|
||||
to_chat(src, "Variable contains: [translate_bitfield(default, variable, var_value)]")
|
||||
|
||||
if(default == VV_NUM)
|
||||
var/dir_text = ""
|
||||
@@ -611,7 +619,7 @@ GLOBAL_PROTECT(VVmaint_only)
|
||||
default = VV_MESSAGE
|
||||
class = default
|
||||
|
||||
var/list/value = vv_get_value(class, default, var_value, extra_classes = list(VV_LIST))
|
||||
var/list/value = vv_get_value(class, default, var_value, extra_classes = list(VV_LIST), var_name = variable)
|
||||
class = value["class"]
|
||||
|
||||
if(!class)
|
||||
@@ -645,5 +653,4 @@ GLOBAL_PROTECT(VVmaint_only)
|
||||
SEND_GLOBAL_SIGNAL(COMSIG_GLOB_VAR_EDIT, args)
|
||||
log_world("### VarEdit by [src]: [O.type] [variable]=[html_encode("[var_new]")] (Type: [class])")
|
||||
log_admin("[key_name(src)] modified [original_name]'s [variable] to [var_new] (Type: [class])")
|
||||
var/msg = "[key_name_admin(src)] modified [original_name]'s [variable] to [html_encode("[var_new]")] (Type: [class])"
|
||||
message_admins(msg)
|
||||
message_admins("[key_name_admin(src)] modified [original_name]'s [variable] to [html_encode(translate_bitfield(default, variable, var_new))] (Type: [class])")
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Creates a TGUI input list window and returns the user's response in a ranked order.
|
||||
*
|
||||
* Arguments:
|
||||
* * user - The user to show the input box to.
|
||||
* * message - The content of the input box, shown in the body of the TGUI window.
|
||||
* * title - The title of the input box, shown on the top of the TGUI window.
|
||||
* * items - The options that can be chosen by the user, each string is assigned a button on the UI.
|
||||
* * default - If an option is already preselected on the UI. Current values, etc.
|
||||
* * timeout - The timeout of the input box, after which the menu will close and qdel itself. Set to zero for no timeout.
|
||||
*/
|
||||
/proc/tgui_input_checkbox_list(mob/user, message, title = "Select", list/items, default, timeout = 0, ui_state = GLOB.always_state)
|
||||
if(!user)
|
||||
user = usr
|
||||
|
||||
if(!length(items))
|
||||
CRASH("[user] tried to open an empty TGUI Input Checkbox List. Contents are: [items]")
|
||||
|
||||
if(!istype(user))
|
||||
if(!isclient(user))
|
||||
CRASH("We passed something that wasn't a user/client in a TGUI Input Checkbox List! The passed user was [user]!")
|
||||
var/client/client = user
|
||||
user = client.mob
|
||||
|
||||
if(isnull(user.client))
|
||||
return
|
||||
|
||||
var/datum/tgui_list_input/checkbox/input = new(user, message, title, items, default, timeout, ui_state)
|
||||
|
||||
if(input.invalid)
|
||||
qdel(input)
|
||||
return
|
||||
|
||||
input.ui_interact(user)
|
||||
input.wait()
|
||||
if(input)
|
||||
. = input.choice
|
||||
qdel(input)
|
||||
|
||||
/**
|
||||
* # tgui_list_input/ranked
|
||||
*
|
||||
* Datum used for allowing a user to sort a TGUI-controlled list input that prompts the user with
|
||||
* a message and shows a list of rankable options
|
||||
*/
|
||||
/datum/tgui_list_input/checkbox
|
||||
modal_type = "CheckboxListInputModal"
|
||||
|
||||
/datum/tgui_list_input/checkbox/handle_new_items(list/_items)
|
||||
var/list/repeat_items = list()
|
||||
// Gets rid of illegal characters
|
||||
var/static/regex/blacklisted_words = regex(@{"([^\u0020-\u8000]+)"})
|
||||
|
||||
for(var/key in _items)
|
||||
var/string_key = blacklisted_words.Replace("[key]", "")
|
||||
|
||||
// Avoids duplicated keys E.g: when areas have the same name
|
||||
string_key = avoid_assoc_duplicate_keys(string_key, repeat_items)
|
||||
src.items += list(list(
|
||||
"key" = string_key,
|
||||
"checked" = (_items[key] ? TRUE : FALSE)
|
||||
))
|
||||
src.items_map = _items // we use this differently
|
||||
|
||||
/datum/tgui_list_input/checkbox/handle_submit_action(params)
|
||||
var/list/associated = list()
|
||||
for(var/list/sublist in params["entry"])
|
||||
associated[sublist["key"]] = (sublist["checked"] in list(1, "1", "true"))
|
||||
|
||||
if(!lists_equal_unordered(associated, items_map))
|
||||
return FALSE
|
||||
set_choice(associated)
|
||||
return TRUE
|
||||
|
||||
|
||||
@@ -76,25 +76,15 @@
|
||||
/// The TGUI modal to use for this popup
|
||||
var/modal_type = "ListInputModal"
|
||||
|
||||
/datum/tgui_list_input/New(mob/user, message, title, list/items, default, timeout, ui_state)
|
||||
/datum/tgui_list_input/New(mob/user, message, title, list/_items, default, timeout, ui_state)
|
||||
src.title = title
|
||||
src.message = message
|
||||
src.items = list()
|
||||
src.items_map = list()
|
||||
src.default = default
|
||||
src.state = ui_state
|
||||
var/list/repeat_items = list()
|
||||
|
||||
// Gets rid of illegal characters
|
||||
var/static/regex/whitelistedWords = regex(@{"([^\u0020-\u8000]+)"})
|
||||
|
||||
for(var/i in items)
|
||||
var/string_key = whitelistedWords.Replace("[i]", "")
|
||||
|
||||
// Avoids duplicated keys E.g: when areas have the same name
|
||||
string_key = avoid_assoc_duplicate_keys(string_key, repeat_items)
|
||||
src.items += string_key
|
||||
src.items_map[string_key] = i
|
||||
handle_new_items(_items)
|
||||
|
||||
if(length(src.items) == 0)
|
||||
invalid = TRUE
|
||||
@@ -173,3 +163,16 @@
|
||||
|
||||
/datum/tgui_list_input/proc/set_choice(choice)
|
||||
src.choice = choice
|
||||
|
||||
/datum/tgui_list_input/proc/handle_new_items(list/_items)
|
||||
var/list/repeat_items = list()
|
||||
// Gets rid of illegal characters
|
||||
var/static/regex/blacklisted_words = regex(@{"([^\u0020-\u8000]+)"})
|
||||
|
||||
for(var/i in items)
|
||||
var/string_key = blacklisted_words.Replace("[i]", "")
|
||||
|
||||
// Avoids duplicated keys E.g: when areas have the same name
|
||||
string_key = avoid_assoc_duplicate_keys(string_key, repeat_items)
|
||||
src.items += string_key
|
||||
src.items_map[string_key] = i
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/**
|
||||
* Creates a TGUI input list window and returns the user's response.
|
||||
* Creates a TGUI ranked input list window and returns the user's response in a ranked order.
|
||||
*
|
||||
* This proc should be used to create alerts that the caller will wait for a response from.
|
||||
* Arguments:
|
||||
* * user - The user to show the input box to.
|
||||
* * message - The content of the input box, shown in the body of the TGUI window.
|
||||
@@ -15,11 +14,11 @@
|
||||
user = usr
|
||||
|
||||
if(!length(items))
|
||||
CRASH("[user] tried to open an empty TGUI Input List. Contents are: [items]")
|
||||
CRASH("[user] tried to open an empty TGUI Input Ranked List. Contents are: [items]")
|
||||
|
||||
if(!istype(user))
|
||||
if(!isclient(user))
|
||||
CRASH("We passed something that wasn't a user/client in a TGUI Input List! The passed user was [user]!")
|
||||
CRASH("We passed something that wasn't a user/client in a TGUI Input Ranked List! The passed user was [user]!")
|
||||
var/client/client = user
|
||||
user = client.mob
|
||||
|
||||
|
||||
@@ -2955,6 +2955,7 @@
|
||||
#include "code\modules\tgui\states\viewer_state.dm"
|
||||
#include "code\modules\tgui\tgui_input\alert_input.dm"
|
||||
#include "code\modules\tgui\tgui_input\color_input.dm"
|
||||
#include "code\modules\tgui\tgui_input\input_checkbox.dm"
|
||||
#include "code\modules\tgui\tgui_input\keycombo_input.dm"
|
||||
#include "code\modules\tgui\tgui_input\list_input.dm"
|
||||
#include "code\modules\tgui\tgui_input\number_input.dm"
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import { Loader } from './common/Loader';
|
||||
import { InputButtons } from './common/InputButtons';
|
||||
import { Button, Section, Stack } from '../components';
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
import { Window } from '../layouts';
|
||||
import { createLogger } from '../logging';
|
||||
import { BooleanLike } from 'common/react';
|
||||
|
||||
type ListInputData = {
|
||||
init_value: string;
|
||||
items: CheckboxData[];
|
||||
message: string;
|
||||
timeout: number;
|
||||
title: string;
|
||||
};
|
||||
|
||||
interface CheckboxData {
|
||||
key: string;
|
||||
checked: BooleanLike;
|
||||
}
|
||||
export const CheckboxListInputModal = (props, context) => {
|
||||
const { act, data } = useBackend<ListInputData>(context);
|
||||
const { items = [], message = '', init_value, timeout, title } = data;
|
||||
const [edittedItems, setEdittedItems] = useLocalState<CheckboxData[]>(context, 'edittedItems', items);
|
||||
|
||||
const windowHeight = 330 + Math.ceil(message.length / 3);
|
||||
|
||||
const onClick = (new_item: CheckboxData | null = null) => {
|
||||
let updatedItems = [...edittedItems];
|
||||
updatedItems = updatedItems.map((item) =>
|
||||
item.key === new_item.key ? { ...item, checked: !new_item.checked } : item
|
||||
);
|
||||
setEdittedItems(updatedItems);
|
||||
};
|
||||
|
||||
return (
|
||||
<Window title={title} width={325} height={windowHeight}>
|
||||
{timeout && <Loader value={timeout} />}
|
||||
<Window.Content>
|
||||
<Section className="ListInput__Section" fill title={message}>
|
||||
<Stack fill vertical>
|
||||
<Stack.Item grow>
|
||||
<ListDisplay filteredItems={edittedItems} onClick={onClick} />
|
||||
</Stack.Item>
|
||||
<Stack.Item mt={0.5}>
|
||||
<InputButtons input={edittedItems} />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Displays the list of selectable items.
|
||||
* If a search query is provided, filters the items.
|
||||
*/
|
||||
const ListDisplay = (props, context) => {
|
||||
const { filteredItems, onClick } = props;
|
||||
|
||||
return (
|
||||
<Section fill scrollable tabIndex={0}>
|
||||
{filteredItems.map((item, index) => {
|
||||
return (
|
||||
<Button.Checkbox
|
||||
fluid
|
||||
id={index}
|
||||
key={index}
|
||||
onClick={() => onClick(item)}
|
||||
checked={item.checked}
|
||||
style={{
|
||||
'animation': 'none',
|
||||
'transition': 'none',
|
||||
}}
|
||||
>
|
||||
{item.key.replace(/^\w/, (c) => c.toUpperCase())}
|
||||
</Button.Checkbox>
|
||||
);
|
||||
})}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
@@ -8,7 +8,7 @@ type InputButtonsData = {
|
||||
};
|
||||
|
||||
type InputButtonsProps = {
|
||||
input: string | number | string[];
|
||||
input: string | number | string[] | Object;
|
||||
message?: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
+57
-57
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user