Skip to content

Some misc upstream fixes (mostly 516 and movement related) #5945

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Mar 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions code/__DEFINES/text.dm
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@
/// 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)

/// Folder directory for strings
#define STRING_DIRECTORY "strings"

Expand Down
2 changes: 1 addition & 1 deletion code/datums/components/jetpack.dm
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@
return
if(user.throwing)//You don't must use jet if you thrown
return
if(length(user.client.keys_held & user.client.movement_keys))//You use jet when press keys. yes.
if(user.client.intended_direction)//You use jet when press keys. yes.
thrust()

/datum/component/jetpack/proc/pre_move_react(mob/user)
Expand Down
2 changes: 1 addition & 1 deletion code/datums/components/scope.dm
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
stop_zooming(user_mob)
return
tracker.calculate_params()
if(!length(user_client.keys_held & user_client.movement_keys))
if(!user_client.intended_direction)
user_mob.face_atom(tracker.given_turf)
animate(user_client, world.tick_lag, pixel_x = tracker.given_x, pixel_y = tracker.given_y)

Expand Down
3 changes: 3 additions & 0 deletions code/modules/client/client_defines.dm
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,9 @@
var/list/keys_held = list()
/// A buffer for combinations such of modifiers + keys (ex: CtrlD, AltE, ShiftT). Format: `"key"` -> `"combo"` (ex: `"D"` -> `"CtrlD"`)
var/list/key_combos_held = list()
/// The direction we WANT to move, based off our keybinds
/// Will be udpated to be the actual direction later on
var/intended_direction = NONE
/*
** These next two vars are to apply movement for keypresses and releases made while move delayed.
** Because discarding that input makes the game less responsive.
Expand Down
1 change: 1 addition & 0 deletions code/modules/client/client_procs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -1176,6 +1176,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
winset(src, "default-[REF(key)]", "parent=default;name=[key];command=[msay]")
else
winset(src, "default-[REF(key)]", "parent=default;name=[key];command=")
calculate_move_dir()

/client/proc/change_view(new_size)
if (isnull(new_size))
Expand Down
36 changes: 26 additions & 10 deletions code/modules/keybindings/bindings_atom.dm
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,43 @@
// Only way to do that is to tie the behavior into the focus's keyLoop().

/atom/movable/keyLoop(client/user)
var/movement_dir = NONE
for(var/_key in user?.keys_held)
movement_dir = movement_dir | user.movement_keys[_key]
if(user?.next_move_dir_add)
movement_dir |= user.next_move_dir_add
if(user?.next_move_dir_sub)
// Clients don't go null randomly. They do go null unexpectedly though, when they're poked in particular ways
// keyLoop is called by a for loop over mobs. We're guarenteed that all the mobs have clients at the START
// But the move of one mob might poke the client of another, so we do this
if(!user)
return FALSE
var/movement_dir = user.intended_direction | user.next_move_dir_add
// If we're not movin anywhere, we aren't movin anywhere
// Safe because nothing adds to movement_dir after this moment
if(!movement_dir)
// No input == our removal would have done nothing
// So we can safely forget about it
user.next_move_dir_sub = NONE
return FALSE

if(user.next_move_dir_sub)
movement_dir &= ~user.next_move_dir_sub
// Sanity checks in case you hold left and right and up to make sure you only go up
if((movement_dir & NORTH) && (movement_dir & SOUTH))
movement_dir &= ~(NORTH|SOUTH)
if((movement_dir & EAST) && (movement_dir & WEST))
movement_dir &= ~(EAST|WEST)

if(user && movement_dir) //If we're not moving, don't compensate, as byond will auto-fill dir otherwise
if(user.dir != NORTH && movement_dir) //If we're not moving, don't compensate, as byond will auto-fill dir otherwise
movement_dir = turn(movement_dir, -dir2angle(user.dir)) //By doing this we ensure that our input direction is offset by the client (camera) direction

//turn without moving while using the movement lock key, unless something wants to ignore it and move anyway
if(user?.movement_locked && !(SEND_SIGNAL(src, COMSIG_MOVABLE_KEYBIND_FACE_DIR, movement_dir) & COMSIG_IGNORE_MOVEMENT_LOCK))
if(user.movement_locked && !(SEND_SIGNAL(src, COMSIG_MOVABLE_KEYBIND_FACE_DIR, movement_dir) & COMSIG_IGNORE_MOVEMENT_LOCK))
keybind_face_direction(movement_dir)
else
user?.Move(get_step(src, movement_dir), movement_dir)
// Null check cause of the signal above
else if(user)
user.Move(get_step(src, movement_dir), movement_dir)
return !!movement_dir //true if there was actually any player input

return FALSE

/client/proc/calculate_move_dir()
var/movement_dir = NONE
for(var/_key in keys_held)
movement_dir |= movement_keys[_key]
intended_direction = movement_dir
14 changes: 8 additions & 6 deletions code/modules/keybindings/bindings_client.dm
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,10 @@

//the time a key was pressed isn't actually used anywhere (as of 2019-9-10) but this allows easier access usage/checking
keys_held[_key] = world.time
if(!movement_locked)
var/movement = movement_keys[_key]
if(!(next_move_dir_sub & movement))
var/movement = movement_keys[_key]
if(movement)
calculate_move_dir()
if(!movement_locked && !(next_move_dir_sub & movement))
next_move_dir_add |= movement

// Client-level keybindings are ones anyone should be able to do at any time
Expand Down Expand Up @@ -94,9 +95,10 @@

keys_held -= _key

if(!movement_locked)
var/movement = movement_keys[_key]
if(!(next_move_dir_add & movement))
var/movement = movement_keys[_key]
if(movement)
calculate_move_dir()
if(!movement_locked && !(next_move_dir_add & movement))
next_move_dir_sub |= movement

// We don't do full key for release, because for mod keys you
Expand Down
4 changes: 2 additions & 2 deletions code/modules/mob/mob_movement.dm
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@
/client/Move(new_loc, direct)
if(world.time < move_delay) //do not move anything ahead of this check please
return FALSE
next_move_dir_add = 0
next_move_dir_sub = 0
next_move_dir_add = NONE
next_move_dir_sub = NONE
var/old_move_delay = move_delay
move_delay = world.time + world.tick_lag //this is here because Move() can now be called mutiple times per tick
if(!direct || !new_loc)
Expand Down
18 changes: 13 additions & 5 deletions code/modules/tgui_input/alert.dm
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,19 @@
* * timeout - The timeout of the alert, after which the modal will close and qdel itself. Set to zero for no timeout.
* * autofocus - The bool that controls if this alert should grab window focus.
*/
/proc/tgui_alert(mob/user, message = "", title, list/buttons = list("Ok"), timeout = 0, autofocus = TRUE)
/proc/tgui_alert(mob/user, message = "", title, list/buttons = list("Ok"), timeout = 0, autofocus = TRUE, ui_state = GLOB.always_state)
if (!user)
user = usr
if (!istype(user))
if (istype(user, /client))
var/client/client = user
user = client.mob
else
return
return null

if(isnull(user.client))
return null

// A gentle nudge - you should not be using TGUI alert for anything other than a simple message.
if(length(buttons) > 3)
log_tgui(user, "Error: TGUI Alert initiated with too many buttons. Use a list.", "TguiAlert")
Expand All @@ -29,7 +33,7 @@
return alert(user, message, title, buttons[1], buttons[2])
if(length(buttons) == 3)
return alert(user, message, title, buttons[1], buttons[2], buttons[3])
var/datum/tgui_alert/alert = new(user, message, title, buttons, timeout, autofocus)
var/datum/tgui_alert/alert = new(user, message, title, buttons, timeout, autofocus, ui_state)
alert.ui_interact(user)
alert.wait()
if (alert)
Expand Down Expand Up @@ -59,19 +63,23 @@
var/autofocus
/// Boolean field describing if the tgui_alert was closed by the user.
var/closed
/// The TGUI UI state that will be returned in ui_state(). Default: always_state
var/datum/ui_state/state

/datum/tgui_alert/New(mob/user, message, title, list/buttons, timeout, autofocus)
/datum/tgui_alert/New(mob/user, message, title, list/buttons, timeout, autofocus, ui_state)
src.autofocus = autofocus
src.buttons = buttons.Copy()
src.message = message
src.title = title
src.state = ui_state
if (timeout)
src.timeout = timeout
start_time = world.time
QDEL_IN(src, timeout)

/datum/tgui_alert/Destroy(force)
SStgui.close_uis(src)
state = null
QDEL_NULL(buttons)
return ..()

Expand All @@ -94,7 +102,7 @@
closed = TRUE

/datum/tgui_alert/ui_state(mob/user)
return GLOB.always_state
return state

/datum/tgui_alert/ui_static_data(mob/user)
var/list/data = list()
Expand Down
20 changes: 14 additions & 6 deletions code/modules/tgui_input/checkboxes.dm
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,24 @@
* 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)
Expand All @@ -50,13 +54,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
Expand All @@ -65,6 +72,7 @@

/datum/tgui_checkbox_input/Destroy(force)
SStgui.close_uis(src)
state = null
QDEL_NULL(items)

return ..()
Expand All @@ -84,7 +92,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()
Expand Down
20 changes: 14 additions & 6 deletions code/modules/tgui_input/list.dm
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,25 @@
* * default - If an option is already preselected on the UI. Current values, etc.
* * timeout - The timeout of the input box, after which the menu will close and qdel itself. Set to zero for no timeout.
*/
/proc/tgui_input_list(mob/user, message, title = "Select", list/items, default, timeout = 0)
/proc/tgui_input_list(mob/user, message, title = "Select", list/items, default, 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

/// 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 items
var/datum/tgui_list_input/input = new(user, message, title, items, default, timeout)
var/datum/tgui_list_input/input = new(user, message, title, items, default, timeout, ui_state)
input.ui_interact(user)
input.wait()
if (input)
Expand Down Expand Up @@ -56,13 +60,16 @@
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

/datum/tgui_list_input/New(mob/user, message, title, list/items, default, timeout)
/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]+)"})
Expand All @@ -81,6 +88,7 @@

/datum/tgui_list_input/Destroy(force)
SStgui.close_uis(src)
state = null
QDEL_NULL(items)
return ..()

Expand All @@ -103,7 +111,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()
Expand Down
18 changes: 13 additions & 5 deletions code/modules/tgui_input/number.dm
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,24 @@
* * timeout - The timeout of the number input, after which the modal will close and qdel itself. Set to zero for no timeout.
* * round_value - whether the inputted number is rounded down into an integer.
*/
/proc/tgui_input_number(mob/user, message, title = "Number Input", default = 0, max_value = 10000, min_value = 0, timeout = 0, round_value = TRUE)
/proc/tgui_input_number(mob/user, message, title = "Number Input", default = 0, max_value = 10000, min_value = 0, timeout = 0, round_value = TRUE, ui_state = GLOB.always_state)
if (!user)
user = usr
if (!istype(user))
if (istype(user, /client))
var/client/client = user
user = client.mob
else
return
return null

if (isnull(user.client))
return null

// Client does NOT have tgui_input on: Returns regular input
if(!user.client.prefs.read_preference(/datum/preference/toggle/tgui_input))
var/input_number = input(user, message, title, default) as null|num
return clamp(round_value ? round(input_number) : input_number, min_value, max_value)
var/datum/tgui_input_number/number_input = new(user, message, title, default, max_value, min_value, timeout, round_value)
var/datum/tgui_input_number/number_input = new(user, message, title, default, max_value, min_value, timeout, round_value, ui_state)
number_input.ui_interact(user)
number_input.wait()
if (number_input)
Expand Down Expand Up @@ -62,14 +66,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, round_value)
/datum/tgui_input_number/New(mob/user, message, title, default, max_value, min_value, timeout, round_value, ui_state)
src.default = default
src.max_value = max_value
src.message = message
src.min_value = min_value
src.title = title
src.round_value = round_value
src.state = ui_state
if (timeout)
src.timeout = timeout
start_time = world.time
Expand All @@ -87,6 +94,7 @@

/datum/tgui_input_number/Destroy(force)
SStgui.close_uis(src)
state = null
return ..()

/**
Expand All @@ -108,7 +116,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)
var/list/data = list()
Expand Down
Loading
Loading