Files
siliconsandGitHub 21883078ce /datum/emote - emote refactor, adds vocal cues (#6725)
datumizes and standardizes all emotes because that file is in the
fucking way of my saycode rewrite
adds vocal cues
2025-11-14 23:54:43 +01:00

476 lines
16 KiB
Plaintext

/**
* Viewport system
*
* Even numbers are filtered out in various points other than for lock overrides for debugging.
* This is becaues CENTER-50% or CENTER-7 alignment systems don't work with even numbers.
*/
/client/proc/change_view(new_size, forced, translocate)
set waitfor = FALSE // to async temporary view
// todo: refactor this, client view changes should be ephemeral.
var/list/L = decode_view_size(new_size)
set_temporary_view(L[1], L[2])
/**
* directly sets our view
* you should probably be using perspective datums most of the time instead
* WARNING: this is verbatim; aka, view = 7 is 15 width 15 height, NOT 7x7!
*
* furthermore, this proc is BLOCKING.
*/
/client/proc/set_temporary_view(width, height)
if(!width || !height || width < 0 || height < 0)
reset_temporary_view()
return
using_temporary_viewsize = TRUE
// round up; even views are illegal.
if(!(width % 2))
width++
if(!(height % 2))
height++
temporary_viewsize_width = width
temporary_viewsize_height = height
request_viewport_update()
/**
* resets our temporary view
* you should probably be using perspective datums most of the time instead
*
* furthermore, this proc is BLOCKING
*/
/client/proc/reset_temporary_view()
using_temporary_viewsize = FALSE
temporary_viewsize_height = null
temporary_viewsize_width = null
request_viewport_update()
// these two variables control max dynamic resize for viewport
GLOBAL_VAR_INIT(max_client_view_x, 19)
GLOBAL_VAR_INIT(max_client_view_y, 15)
// these two variables **reflect** (config updates, not this!) effective default viewsize for view(), range(), etc.
GLOBAL_VAR_INIT(game_view_x, 19)
GLOBAL_VAR_INIT(game_view_y, 15)
// minimum size viewports can be
GLOBAL_VAR_INIT(min_client_view_x, 15)
GLOBAL_VAR_INIT(min_client_view_y, 15)
// the radius that things like saycode operates on
// todo: update this as needed
GLOBAL_VAR_INIT(game_view_radius, 9)
// these two variables, if set, lock all clients to a certain viewsize no matter what
GLOBAL_VAR(lock_client_view_x)
GLOBAL_VAR(lock_client_view_y)
/**
* forces world viewsize; use for config VAS
*/
/datum/controller/configuration/proc/update_world_viewsize()
//! BYONd world.view is immutable, for now we're stuck read-onlying it.
if(isnum(world.view))
GLOB.game_view_x = GLOB.game_view_y = world.view
else
// is text
var/list/viewsize = splittext(world.view, "x")
GLOB.game_view_x = viewsize[1]
GLOB.game_view_y = viewsize[2]
/**
* forces screensize update; use for config VAS
*/
/datum/controller/configuration/proc/update_player_viewsize()
var/viewsize_raw = CONFIG_GET(string/max_viewport_size)
var/list/viewsize = splittext(viewsize_raw, "x")
GLOB.max_client_view_x = text2num(viewsize[1])
GLOB.max_client_view_y = text2num(viewsize[2])
//! Core viewport procs
//! These don't obey the read-write lock, and should only be called from the synchronized procs!
/**
* populates important vars that things may read early before we sleep
*/
/client/proc/pre_init_viewport()
PRIVATE_PROC(TRUE)
current_viewport_width = GLOB.max_client_view_x
current_viewport_height = GLOB.max_client_view_y
/**
* called on client init to do this without blocking client/New
*/
/client/proc/init_viewport_blocking()
PRIVATE_PROC(TRUE)
fetch_viewport()
refit_viewsize()
// release the initial lock
viewport_rwlock = FALSE
/**
* called to manually update viewport vars since the skin macro is only triggered on resize
*
* return TRUE if changed
*/
/client/proc/fetch_viewport()
PRIVATE_PROC(TRUE)
. = FALSE
// get vars only; they have to manually refit
var/list/got = params2list(winget(src, SKIN_MAP_ID_VIEWPORT, "size;zoom;letterbox"))
var/got_zoom = text2num(got["zoom"]) || 0
var/got_box = (got["letterbox"] == "true")
var/list/split = splittext(got["size"], "x")
var/got_spx
var/got_spy
if(length(split) == 2)
got_spx = text2num(split[1]) || (WORLD_ICON_SIZE * GLOB.game_view_x)
got_spy = text2num(split[2]) || (WORLD_ICON_SIZE * GLOB.game_view_y)
else
stack_trace("fetch_viewport failed to get spx/spy")
got_spx = (WORLD_ICON_SIZE * GLOB.game_view_x)
got_spy = (WORLD_ICON_SIZE * GLOB.game_view_y)
if(got_zoom != assumed_viewport_zoom || got_spx != assumed_viewport_spx || got_spy != assumed_viewport_spy || got_box != assumed_viewport_box)
. = TRUE
assumed_viewport_zoom = got_zoom
assumed_viewport_spx = got_spx
assumed_viewport_spy = got_spy
assumed_viewport_box = got_box
/**
* called to refit our view size as necessary
*
* this is automatically called every time something modifies us
*/
/client/proc/refit_viewsize(no_fit)
PRIVATE_PROC(TRUE)
if(!isnull(GLOB.lock_client_view_x) && !isnull(GLOB.lock_client_view_y))
view = "[GLOB.lock_client_view_x]x[GLOB.lock_client_view_y]"
on_refit_viewsize(GLOB.lock_client_view_x, GLOB.lock_client_view_y, no_fit)
return
if(using_temporary_viewsize)
view = "[temporary_viewsize_width]x[temporary_viewsize_height]"
on_refit_viewsize(temporary_viewsize_width, temporary_viewsize_height, no_fit)
return
var/widescreen = is_widescreen_enabled()
if(!widescreen)
// if not widescreen, we should just force to 15 x 15 and their augmented view
var/width = 15 + (using_perspective.augment_view_width * 2)
var/height = 15 + (using_perspective.augment_view_height * 2)
view = "[width]x[height]"
on_refit_viewsize(width, height, no_fit)
return
var/stretch_to_fit = assumed_viewport_zoom == 0
using_perspective.ensure_view_cached()
var/max_width = using_perspective.cached_view_width
var/max_height = using_perspective.cached_view_height
// even numbers not allowed
if(!(max_width % 2))
max_width++
if(!(max_height % 2))
max_height++
if(stretch_to_fit)
// option 1: they're stretching to fit
if(assumed_viewport_box)
// fit everything
view = "[max_width]x[max_height]"
on_refit_viewsize(max_width, max_height)
return
// option 2: they're stretching to fit the longest side
else
// todo: handle horizontally-wider view sizes
// for now we only care about horizontal fit
var/stretch_pixel_amount = assumed_viewport_spy / max_height
var/available_width = assumed_viewport_spx / stretch_pixel_amount
available_width = CEILING(available_width, 1)
available_width = clamp(available_width, GLOB.min_client_view_x, max_width)
// even numbers not allowed
if(!(available_width % 2))
available_width++
view = "[available_width]x[max_height]"
on_refit_viewsize(available_width, max_height, no_fit)
return
// option 3: scale as necessary
var/pixels_per_tile = assumed_viewport_zoom * WORLD_ICON_SIZE
var/div_x = assumed_viewport_spx / pixels_per_tile
var/div_y = assumed_viewport_spy / pixels_per_tile
div_x = CEILING(div_x, 1)
div_y = CEILING(div_y, 1)
var/desired_width = clamp(div_x, GLOB.min_client_view_x, max_width)
var/desired_height = clamp(div_y, GLOB.min_client_view_y, max_height)
// evens numbers not allowed
if(!(desired_width % 2))
desired_width++
if(!(desired_height % 2))
desired_height++
view = "[desired_width]x[desired_height]"
on_refit_viewsize(desired_width, desired_height, no_fit)
/client/proc/on_refit_viewsize(new_width, new_height, no_fit)
var/changed = (current_viewport_height != new_height) || (current_viewport_width != new_width)
if(changed)
current_viewport_width = new_width
current_viewport_height = new_height
if(!no_fit && is_auto_fit_viewport_enabled())
fit_viewport()
post_refit_viewsize(changed)
/**
* updates everything when our viewport changes
*/
/client/proc/post_refit_viewsize(changed)
if(!changed)
return
// force perspective swap for ??? reasons (???)
var/old_perspective = perspective
perspective = MOB_PERSPECTIVE
if(old_perspective != perspective)
perspective = old_perspective
// i don't understand the above
mob?.refit_rendering()
/**
* called to fit our viewport to what we **should** have to get our effective view.
*/
/client/proc/fit_viewport()
// by now we already fetched viewport
// figure out how big they want it to be based on their settings
// note: we only care about horizontal because the splitter.. is, well, horizontal.
var/desired_pixel_width
// todo: pref
// maximum % they're willing to have their map
var/maximum_splitter_percent = 80
// splitter intrinsic width
var/assumed_splitter_width = 4
// effective size
var/widescreen = is_widescreen_enabled()
var/effective_view_width
var/effective_view_height
// todo: optimize the shit out of this proc and the view system
if(!isnull(GLOB.lock_client_view_x) && !isnull(GLOB.lock_client_view_y))
effective_view_width = GLOB.lock_client_view_x
effective_view_height = GLOB.lock_client_view_y
else if(widescreen)
effective_view_width = clamp(temporary_viewsize_width || using_perspective.cached_view_width, GLOB.min_client_view_x, 67)
effective_view_height = clamp(temporary_viewsize_height || using_perspective.cached_view_height, GLOB.min_client_view_y, 67)
else
effective_view_width = clamp(temporary_viewsize_width || (15 + (using_perspective.augment_view_width * 2)), GLOB.min_client_view_x, 67)
effective_view_height = clamp(temporary_viewsize_height || (15 + (using_perspective.augment_view_height * 2)), GLOB.min_client_view_y, 67)
if(assumed_viewport_zoom == 0)
// they're stretching to fit; this makes it annoying
var/aspect_ratio = effective_view_width / effective_view_height
// we have to fit everything
// viewport_spy should never change since that's not what the player can drag,
// unless they resize the outer window itself.
desired_pixel_width = assumed_viewport_spy * aspect_ratio
else
// they're not using stretch to fit; this makes it very easy
desired_pixel_width = WORLD_ICON_SIZE * assumed_viewport_zoom * effective_view_width
// grab their screen size (or what counts as it)
var/list/fetching = winget(src, SKIN_SPLITTER_ID_MAIN, "size")
var/list/parsed = splittext(fetching, "x")
var/screen_width = text2num(parsed[1])
if(screen_width == 0)//catch a rare div by zero case
return
// grab what percent we should go to
var/current_percent = min(100 * ((desired_pixel_width + assumed_splitter_width) / screen_width), maximum_splitter_percent)
// initial set
winset(src, SKIN_SPLITTER_ID_MAIN, "splitter=[current_percent]")
// funny method of correcting for misalignments via binary-search-like behavior
var/delta
fetching = winget(src, SKIN_MAP_ID_VIEWPORT, "size")
parsed = splittext(fetching, "x")
var/current_width = text2num(parsed[1])
var/last_width = current_width
if(current_width == desired_pixel_width)
// done
return
// nope, start adjusting
delta = 100 * ((desired_pixel_width - current_width) / screen_width)
for(var/safety in 1 to 10)
current_percent = min(current_percent + delta, maximum_splitter_percent)
winset(src, SKIN_SPLITTER_ID_MAIN, "splitter=[current_percent]")
fetching = winget(src, SKIN_MAP_ID_VIEWPORT, "size")
parsed = splittext(fetching, "x")
current_width = text2num(parsed[1])
// done?
if(current_width == desired_pixel_width)
return
// if we're literally down to a pixel how about like Don't
if(last_width == current_width)
return
last_width = current_width
// keep adjusting
// overshot?
if ((delta > 0 && current_width > desired_pixel_width) || (delta < 0 && current_width < desired_pixel_width))
// reverse in half
delta = -delta / 2
//! Synchronized procs; calling these from outside is fine.
/**
* call this when things change to queue an update
*
* should only be called **in code**, not by the skin!
*
* warning: this proc doesn't refit instantly, it only queues.
* client decides what time is best to refit.
*/
/client/proc/request_viewport_update()
set waitfor = FALSE
if(!viewport_rwlock)
_request_viewport_update()
if(!viewport_queued)
viewport_queued = TRUE
addtimer(CALLBACK(src, PROC_REF(_request_viewport_update)), 0)
/**
* call this when things change to queue an update
*
* should only be called **in code**, not by the skin!
*
* blocks until finished
*/
/client/proc/request_viewport_update_blocking()
if(!viewport_rwlock)
_request_viewport_update()
return
if(!viewport_queued)
viewport_queued = TRUE
addtimer(CALLBACK(src, PROC_REF(_request_viewport_update)), 0)
UNTIL(!viewport_queued)
// todo : locks are probably bad when working with request fit
/client/proc/_request_viewport_update(no_fit, no_fetch)
PRIVATE_PROC(TRUE)
// if rwlocked, wait; people should be queuing and not calling this directly
UNTIL(!viewport_rwlock)
// clear queued
viewport_queued = FALSE
// lock
viewport_rwlock = TRUE
if(!no_fetch)
fetch_viewport()
// refit size based on viewport
refit_viewsize(no_fit)
// release lock
viewport_rwlock = FALSE
/**
* call this to request a viewport fit
*/
/client/proc/request_viewport_fit(no_recalc)
set waitfor = FALSE
_request_viewport_fit(no_recalc)
/**
* call this to request a viewport fit
* forcefully fits
* blocks until finished
* don't call this from non-buttons because it blocks and
* absolutely can stack and ruin everyone's day.
*/
/client/proc/viewport_fit_blocking(no_recalc)
UNTIL(!viewport_rwlock)
return _request_viewport_fit(no_recalc)
// todo : locks are probably bad when working with request update
/client/proc/_request_viewport_fit(no_recalc)
PRIVATE_PROC(TRUE)
// if viewport is locked, ignore it
if(viewport_rwlock)
return
// acquire lock
viewport_rwlock = TRUE
// just in case because this is called by a lot of things that are too shitcoded to update (like my own code)
fetch_viewport()
// fit viewport
fit_viewport()
// fetch values; we do not trust on-size to trigger
fetch_viewport()
// then refit the viewsize if needed
if(!no_recalc)
refit_viewsize()
// release lock
viewport_rwlock = FALSE
/**
* called directly by the skin when resizing
*
* @params
* - w - width of viewport in pixels
* - h - height of viewport in pixels
* - z - zoom of viewport
* - b - are we letterboxing?
*/
/client/verb/on_viewport(w as text, h as text, z as num, b as num)
set name = ".on_viewport"
set hidden = TRUE
if(viewport_rwlock) // something is fucking around, don't edit for them
return
// get vars
assumed_viewport_spx = text2num(w)
assumed_viewport_spy = text2num(h)
assumed_viewport_zoom = z
assumed_viewport_box = b
// refit
_request_viewport_update(TRUE, TRUE)
/**
* automatically fit their viewport to show everything optimally
*/
/client/verb/user_fit_viewport()
set name = "Fit Viewport"
set category = VERB_CATEGORY_OOC
set desc = "Fit the width of the map window to match the viewport"
request_viewport_fit()
/client/verb/force_map_zoom(n as num)
set name = ".viewport_zoom"
set hidden = TRUE
set category = "Debug"
if(!isnum(n) || n < 0)
to_chat(usr, SPAN_WARNING("invalid number"))
return
if(viewport_rwlock)
to_chat(usr, SPAN_WARNING("Viewport is rwlocked; try again later."))
return
winset(src, SKIN_MAP_ID_VIEWPORT, "zoom=[n]")
assumed_viewport_zoom = n
request_viewport_update(TRUE)
/client/verb/force_map_box(n as num)
set name = ".viewport_box"
set hidden = TRUE
set category = "Debug"
if(viewport_rwlock)
to_chat(usr, SPAN_WARNING("Viewport is rwlocked; try again later."))
return
n = !!n // force bool
winset(src, SKIN_MAP_ID_VIEWPORT, "letterbox=[n? "true" : "false"]")
assumed_viewport_box = n
request_viewport_update(TRUE)
/client/verb/force_onresize_view_update()
set name = ".viewport_update"
set hidden = TRUE
set category = "Debug"
if(viewport_rwlock)
to_chat(usr, SPAN_WARNING("Viewport is rwlocked; try again later."))
return
request_viewport_update()
/client/verb/show_winset_debug_values()
set name = ".viewport_debug"
set hidden = TRUE
set category = "Debug"
var/divisor = text2num(winget(src, "mapwindow.map", "icon-size")) || world.icon_size
var/winsize_string = winget(src, "mapwindow.map", "size")
to_chat(usr, "Current client view: [view]")
to_chat(usr, "Icon size: [divisor]")
to_chat(usr, "xDim: [round(text2num(winsize_string) / divisor)]")
to_chat(usr, "yDim: [round(text2num(copytext(winsize_string,findtext(winsize_string,"x")+1,0)) / divisor)]")
to_chat(usr, "server thinks viewport is:[assumed_viewport_spx]/[assumed_viewport_spy]/[assumed_viewport_zoom]/[assumed_viewport_box]")
to_chat(usr, "server thinks view is:[current_viewport_width]x[current_viewport_height]")