mirror of
https://github.com/Aurorastation/Aurora.3.git
synced 2026-08-22 04:22:39 +01:00
Smooth Progress Bar (#23093)
* Progress bars now use smooth, colour-changing fills and support visual themes. https://github.com/user-attachments/assets/ac493f6d-dce7-4b11-a105-985a076f4904 Ported from https://github.com/NebulaSS13/Nebula/pull/1574, which ported from the original PR: https://github.com/ParadiseSS13/Paradise/pull/15695 ### Asset Licenses The following assets that **have not** been created by myself are included in this PR: | Path | Original Author | License | | --- | --- | --- | | <img width="532" height="309" alt="image" src="https://github.com/user-attachments/assets/c75a9e8f-83f4-4d28-9888-3da075598f88" /> | [dearmochi](https://github.com/dearmochi) (ParadiseSS13) | Creative Commons 3.0 BY-SA | AI usage disclosure: Ported using GPT 5.6 Sol.
This commit is contained in:
@@ -585,8 +585,9 @@ Turf and target are seperate in case you want to teleport some distance from a t
|
||||
* * display_progress - Boolean, if the progress bar is shown
|
||||
* * extra_checks - A `/datum/callback` that is invoked to perform extra checks and validate that the action can continue to be performed,
|
||||
* if it returns `FALSE` or an algebraic equivalent the action is aborted
|
||||
* * progressbar_type - The progress bar theme to display
|
||||
*/
|
||||
/proc/do_mob(mob/user, mob/target, delay = 30, needhand = TRUE, display_progress = TRUE, datum/callback/extra_checks) //This is quite an ugly solution but i refuse to use the old request system.
|
||||
/proc/do_mob(mob/user, mob/target, delay = 30, needhand = TRUE, display_progress = TRUE, datum/callback/extra_checks, progressbar_type = /datum/progressbar/default) //This is quite an ugly solution but i refuse to use the old request system.
|
||||
if(!user || !target)
|
||||
stack_trace("do_mob called without either an user or a target!")
|
||||
return FALSE
|
||||
@@ -600,7 +601,7 @@ Turf and target are seperate in case you want to teleport some distance from a t
|
||||
var/atom/loc_check = target
|
||||
for(var/i = 0; !isturf(loc_check.loc) && i < 5; i++)
|
||||
loc_check = target.loc
|
||||
progbar = new(user, delay, loc_check)
|
||||
progbar = new progressbar_type(user, delay, loc_check)
|
||||
|
||||
var/endtime = world.time + delay
|
||||
var/starttime = world.time
|
||||
@@ -621,7 +622,7 @@ Turf and target are seperate in case you want to teleport some distance from a t
|
||||
break
|
||||
|
||||
if (progbar)
|
||||
qdel(progbar)
|
||||
progbar.end_progress()
|
||||
|
||||
/**
|
||||
* Timed actions involving one mob user and (optionally) one target.
|
||||
@@ -635,17 +636,18 @@ Turf and target are seperate in case you want to teleport some distance from a t
|
||||
* * do_flags: Flags that determine what the user and target can and cannot do, defined in [mobs.dm]. Defaults to DO_DEFAULT.
|
||||
* * incapacitation_flags: Incapacitation flags that determines if the user can be incapacitated. Defaults to INCAPACITATION_DEFAULT.
|
||||
* * extra_checks: Optional extra checks, that uses a callback. See [datum/callback].
|
||||
* * progressbar_type: The progress bar theme to display.
|
||||
*
|
||||
*/
|
||||
/proc/do_after(mob/user, delay, atom/target, do_flags = DO_DEFAULT, incapacitation_flags = INCAPACITATION_DEFAULT, datum/callback/extra_checks)
|
||||
return !do_after_detailed(user, delay, target, do_flags, incapacitation_flags, extra_checks)
|
||||
/proc/do_after(mob/user, delay, atom/target, do_flags = DO_DEFAULT, incapacitation_flags = INCAPACITATION_DEFAULT, datum/callback/extra_checks, progressbar_type = /datum/progressbar/default)
|
||||
return !do_after_detailed(user, delay, target, do_flags, incapacitation_flags, extra_checks, progressbar_type)
|
||||
|
||||
/**
|
||||
* See [/proc/do_after]
|
||||
* Returns the exact error, defined in [mobs.dm] for custom error messages.
|
||||
* Overlaps with do_flags, with some extra error messages available.
|
||||
*/
|
||||
/proc/do_after_detailed(mob/user, delay, atom/target, do_flags = DO_DEFAULT, incapacitation_flags = INCAPACITATION_DEFAULT, datum/callback/extra_checks)
|
||||
/proc/do_after_detailed(mob/user, delay, atom/target, do_flags = DO_DEFAULT, incapacitation_flags = INCAPACITATION_DEFAULT, datum/callback/extra_checks, progressbar_type = /datum/progressbar/default)
|
||||
if(!delay)
|
||||
return FALSE
|
||||
|
||||
@@ -686,7 +688,7 @@ Turf and target are seperate in case you want to teleport some distance from a t
|
||||
var/datum/progressbar/progbar
|
||||
if ((do_flags & DO_SHOW_PROGRESS) && user.client && (user.client.prefs.toggles_secondary & PROGRESS_BARS))
|
||||
var/progbar_pos = (do_flags & DO_PLACE_PROGRESSBAR_ON_USER) ? user : (target || user)
|
||||
progbar = new(user, delay, progbar_pos)
|
||||
progbar = new progressbar_type(user, delay, progbar_pos)
|
||||
|
||||
SEND_SIGNAL(user, COMSIG_DO_AFTER_BEGAN)
|
||||
|
||||
|
||||
+158
-72
@@ -1,25 +1,45 @@
|
||||
#define PROGRESSBAR_HEIGHT 6
|
||||
#define PROGRESSBAR_ANIMATION_TIME 5
|
||||
|
||||
/datum/progressbar
|
||||
///The progress bar visual element.
|
||||
/// The icon file containing the backdrop, mask, and fill states for this theme.
|
||||
var/icon = 'icons/effects/progress_bar/default.dmi'
|
||||
/// The total height in pixels of the themed progress bar.
|
||||
var/height = 7
|
||||
/// The width in pixels of the theme's fill sprite.
|
||||
var/fill_width = 22
|
||||
/// The time taken for the progress bar to appear.
|
||||
var/animation_time_appear = 0.5 SECONDS
|
||||
/// The time taken for the progress bar to fade away.
|
||||
var/animation_time_fade = 0.5 SECONDS
|
||||
/// The time taken for stacked progress bars to shift down.
|
||||
var/animation_time_shift = 0.5 SECONDS
|
||||
|
||||
/// The visual element containing the backdrop and fill.
|
||||
var/image/bar
|
||||
///The target where this progress bar is applied and where it is shown.
|
||||
/// The backdrop visual element.
|
||||
var/image/backdrop
|
||||
/// The fill visual element.
|
||||
var/image/fill
|
||||
/// The target where this progress bar is applied and where it is shown.
|
||||
var/atom/bar_loc
|
||||
///The mob whose client sees the progress bar.
|
||||
/// The mob whose client sees the progress bar.
|
||||
var/mob/user
|
||||
///The client seeing the progress bar.
|
||||
/// The client seeing the progress bar.
|
||||
var/client/user_client
|
||||
///Effectively the number of steps the progress bar will need to do before reaching completion.
|
||||
/// The number of steps the progress bar needs to reach completion.
|
||||
var/goal = 1
|
||||
///Control check to see if the progress was interrupted before reaching its goal.
|
||||
/// The most recently displayed progress value.
|
||||
var/last_progress = 0
|
||||
///Variable to ensure smooth visual stacking on multiple progress bars.
|
||||
/// The progress bar's position in the stack over its target.
|
||||
var/listindex = 0
|
||||
///The type of our last value for bar_loc, for debugging
|
||||
/// The type of our last value for bar_loc, for debugging.
|
||||
var/location_type
|
||||
///Where to draw the progress bar above the icon
|
||||
/// Horizontal offset needed to centre the bar over oversized icons.
|
||||
var/offset_x
|
||||
/// Vertical offset needed to place the bar over oversized icons.
|
||||
var/offset_y
|
||||
/// Whether the bar has started its ending animation.
|
||||
var/stopping = FALSE
|
||||
/// Whether this bar has already been removed from the user's stack.
|
||||
var/removed_from_stack = FALSE
|
||||
|
||||
/datum/progressbar/New(mob/User, goal_number, atom/target)
|
||||
. = ..()
|
||||
@@ -27,33 +47,30 @@
|
||||
stack_trace("Invalid target [target] passed in")
|
||||
qdel(src)
|
||||
return
|
||||
if(QDELETED(User) || !istype(User))
|
||||
if (QDELETED(User) || !istype(User))
|
||||
stack_trace("/datum/progressbar created with [isnull(User) ? "null" : "invalid"] user")
|
||||
qdel(src)
|
||||
return
|
||||
if(!isnum(goal_number))
|
||||
if (!isnum(goal_number))
|
||||
stack_trace("/datum/progressbar created with [isnull(User) ? "null" : "invalid"] goal_number")
|
||||
qdel(src)
|
||||
return
|
||||
|
||||
goal = goal_number
|
||||
bar_loc = target
|
||||
location_type = bar_loc.type
|
||||
user = User
|
||||
|
||||
var/list/icon_offsets = target.get_oversized_icon_offsets()
|
||||
var/offset_x = icon_offsets["x"]
|
||||
offset_x = icon_offsets["x"]
|
||||
offset_y = icon_offsets["y"]
|
||||
|
||||
bar = image('icons/effects/progressbar.dmi', bar_loc, "prog_bar_0", pixel_x = offset_x)
|
||||
// SET_PLANE_EXPLICIT(bar, ABOVE_HUD_PLANE, User)
|
||||
bar.plane = HUD_PLANE
|
||||
bar.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
|
||||
user = User
|
||||
init_images()
|
||||
|
||||
LAZYADDASSOCLIST(user.progressbars, bar_loc, src)
|
||||
var/list/bars = user.progressbars[bar_loc]
|
||||
listindex = bars.len
|
||||
listindex = length(bars)
|
||||
|
||||
if(user.client)
|
||||
if (user.client)
|
||||
user_client = user.client
|
||||
add_prog_bar_image_to_client()
|
||||
|
||||
@@ -62,111 +79,180 @@
|
||||
RegisterSignal(user, COMSIG_MOB_LOGIN, PROC_REF(on_user_login))
|
||||
RegisterSignal(bar_loc, COMSIG_QDELETING, PROC_REF(on_bar_loc_delete))
|
||||
|
||||
|
||||
/datum/progressbar/Destroy()
|
||||
if(user)
|
||||
if (user)
|
||||
UnregisterSignal(user, list(COMSIG_QDELETING, COMSIG_MOB_LOGOUT, COMSIG_MOB_LOGIN))
|
||||
if(isliving(user))
|
||||
var/mob/living/L = user
|
||||
if(L.stamina_bar == src)
|
||||
L.stamina_bar = null
|
||||
if (isliving(user))
|
||||
var/mob/living/living_user = user
|
||||
if (living_user.stamina_bar == src)
|
||||
living_user.stamina_bar = null
|
||||
|
||||
for(var/pb in user.progressbars[bar_loc])
|
||||
var/datum/progressbar/progress_bar = pb
|
||||
if(progress_bar == src || progress_bar.listindex <= listindex)
|
||||
continue
|
||||
progress_bar.listindex--
|
||||
|
||||
progress_bar.bar.pixel_y = world.icon_size + offset_y + (PROGRESSBAR_HEIGHT * (progress_bar.listindex - 1))
|
||||
var/dist_to_travel = world.icon_size + offset_y + (PROGRESSBAR_HEIGHT * (progress_bar.listindex - 1)) - PROGRESSBAR_HEIGHT
|
||||
animate(progress_bar.bar, pixel_y = dist_to_travel, time = PROGRESSBAR_ANIMATION_TIME, easing = SINE_EASING)
|
||||
|
||||
LAZYREMOVEASSOC(user.progressbars, bar_loc, src)
|
||||
remove_from_stack()
|
||||
user = null
|
||||
|
||||
if(user_client)
|
||||
if (user_client)
|
||||
clean_user_client()
|
||||
|
||||
if(bar_loc)
|
||||
if (bar_loc)
|
||||
UnregisterSignal(bar_loc, COMSIG_QDELETING)
|
||||
bar_loc = null
|
||||
|
||||
bar = null
|
||||
backdrop = null
|
||||
fill = null
|
||||
|
||||
return ..()
|
||||
|
||||
/// Initializes the holder, backdrop, and masked fill images used by the theme.
|
||||
/datum/progressbar/proc/init_images()
|
||||
bar = image('icons/effects/effects.dmi', bar_loc, "nothing", HUD_ABOVE_ITEM_LAYER, pixel_x = offset_x)
|
||||
bar.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
|
||||
bar.alpha = 0
|
||||
bar.plane = HUD_PLANE
|
||||
bar.appearance_flags = KEEP_TOGETHER | APPEARANCE_UI_IGNORE_ALPHA
|
||||
|
||||
///Called right before the user's Destroy()
|
||||
backdrop = image(icon, bar_loc, "backdrop")
|
||||
fill = image(icon, bar_loc, "fill")
|
||||
fill.filters = filter(type = "alpha", icon = icon(icon, "mask"), x = -fill_width)
|
||||
bar.overlays += list(backdrop, fill)
|
||||
|
||||
/// Removes this bar from its stack and smoothly shifts any bars above it down.
|
||||
/datum/progressbar/proc/remove_from_stack()
|
||||
if (removed_from_stack || !user || !bar_loc)
|
||||
return
|
||||
|
||||
var/list/bars = user.progressbars?[bar_loc]
|
||||
if (bars)
|
||||
for (var/pb in bars)
|
||||
var/datum/progressbar/progress_bar = pb
|
||||
if (progress_bar == src || progress_bar.listindex <= listindex)
|
||||
continue
|
||||
progress_bar.listindex--
|
||||
progress_bar.shift_down()
|
||||
LAZYREMOVEASSOC(user.progressbars, bar_loc, src)
|
||||
|
||||
removed_from_stack = TRUE
|
||||
|
||||
/// Shifts this progress bar down one position in its stack.
|
||||
/datum/progressbar/proc/shift_down()
|
||||
animate(bar,
|
||||
pixel_y = world.icon_size + offset_y + (height * (listindex - 1)),
|
||||
time = animation_time_shift,
|
||||
easing = SINE_EASING | EASE_OUT,
|
||||
flags = ANIMATION_PARALLEL
|
||||
)
|
||||
|
||||
/// Called right before the user's Destroy().
|
||||
/datum/progressbar/proc/on_user_delete(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
user.progressbars = null //We can simply nuke the list and stop worrying about updating other prog bars if the user itself is gone.
|
||||
user.progressbars = null
|
||||
removed_from_stack = TRUE
|
||||
user = null
|
||||
qdel(src)
|
||||
|
||||
|
||||
///Called right before the bar_loc's Destroy()
|
||||
/// Called right before the bar_loc's Destroy().
|
||||
/datum/progressbar/proc/on_bar_loc_delete(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
qdel(src)
|
||||
|
||||
///Removes the progress bar image from the user_client and nulls the variable, if it exists.
|
||||
/// Removes the progress bar image from the current client.
|
||||
/datum/progressbar/proc/clean_user_client(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(!user_client) //Disconnected, already gone.
|
||||
if (!user_client)
|
||||
return
|
||||
user_client.images -= bar
|
||||
user_client = null
|
||||
|
||||
|
||||
///Called by user's Login(), it transfers the progress bar image to the new client.
|
||||
/// Transfers the progress bar image to the user's new client after login.
|
||||
/datum/progressbar/proc/on_user_login(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(user_client)
|
||||
if(user_client == user.client) //If this was not client handling I'd condemn this sanity check. But clients are fickle things.
|
||||
if (user_client)
|
||||
if (user_client == user.client)
|
||||
return
|
||||
clean_user_client()
|
||||
if(!user.client) //Clients can vanish at any time, the bastards.
|
||||
if (!user.client)
|
||||
return
|
||||
user_client = user.client
|
||||
add_prog_bar_image_to_client()
|
||||
|
||||
|
||||
///Adds a smoothly-appearing progress bar image to the player's screen.
|
||||
/// Adds a smoothly appearing progress bar image to the player's client.
|
||||
/datum/progressbar/proc/add_prog_bar_image_to_client()
|
||||
bar.pixel_y = 0
|
||||
bar.alpha = 0
|
||||
user_client.images += bar
|
||||
animate(bar, pixel_y = world.icon_size + offset_y + (PROGRESSBAR_HEIGHT * (listindex - 1)), alpha = 255, time = PROGRESSBAR_ANIMATION_TIME, easing = SINE_EASING)
|
||||
animate(bar,
|
||||
pixel_y = world.icon_size + offset_y + (height * (listindex - 1)),
|
||||
alpha = 255,
|
||||
time = animation_time_appear,
|
||||
easing = SINE_EASING | EASE_OUT
|
||||
)
|
||||
|
||||
|
||||
///Updates the progress bar image visually.
|
||||
/// Updates the progress bar's masked fill.
|
||||
/datum/progressbar/proc/update(progress)
|
||||
if (QDELETED(bar_loc))
|
||||
qdel(src)
|
||||
return
|
||||
|
||||
progress = clamp(progress, 0, goal)
|
||||
if(progress == last_progress)
|
||||
if (progress == last_progress)
|
||||
return
|
||||
last_progress = progress
|
||||
bar.icon_state = "prog_bar_[round(((progress / goal) * 100), 5)]"
|
||||
refresh_fill()
|
||||
|
||||
/// Removes and re-adds the fill so its filtered appearance updates immediately.
|
||||
/datum/progressbar/proc/refresh_fill()
|
||||
if (!fill || !bar)
|
||||
return
|
||||
bar.overlays -= fill
|
||||
update_fill(clamp(last_progress / goal, 0, 1))
|
||||
bar.overlays += fill
|
||||
|
||||
///Called on progress end, be it successful or a failure. Wraps up things to delete the datum and bar.
|
||||
/// Updates the fill image for the current theme.
|
||||
/datum/progressbar/proc/update_fill(fraction)
|
||||
UNLINT(fill?.filters[1]?.x = -fill_width * (1 - fraction))
|
||||
if (!stopping)
|
||||
fill.color = rgb_gradient(fraction, 0, "#cc0033", 0.25, "#cc6633", 0.5, "#d1cc33", 0.75, "#00cc33")
|
||||
else if (last_progress == goal)
|
||||
fill.color = COLOR_YELLOW
|
||||
|
||||
/// Ends the progress bar and schedules its deletion after the fade animation.
|
||||
/datum/progressbar/proc/end_progress()
|
||||
if(last_progress != goal)
|
||||
bar.icon_state = "[bar.icon_state]_fail"
|
||||
if (stopping)
|
||||
return
|
||||
stopping = TRUE
|
||||
refresh_fill()
|
||||
remove_from_stack()
|
||||
animate(bar, alpha = 0, time = animation_time_fade, flags = ANIMATION_PARALLEL)
|
||||
QDEL_IN(src, animation_time_fade)
|
||||
|
||||
animate(bar, alpha = 0, time = PROGRESSBAR_ANIMATION_TIME)
|
||||
|
||||
QDEL_IN(src, PROGRESSBAR_ANIMATION_TIME)
|
||||
|
||||
///Progress bars are very generic, and what hangs a ref to them depends heavily on the context in which they're used
|
||||
///So let's make hunting harddels easier yeah?
|
||||
/// Progress bars are generic, so record the target type to make harddel debugging easier.
|
||||
/datum/progressbar/dump_harddel_info()
|
||||
if(harddel_deets_dumped)
|
||||
if (harddel_deets_dumped)
|
||||
return
|
||||
harddel_deets_dumped = TRUE
|
||||
return "Owner's type: [location_type]"
|
||||
|
||||
#undef PROGRESSBAR_ANIMATION_TIME
|
||||
#undef PROGRESSBAR_HEIGHT
|
||||
/datum/progressbar/default
|
||||
|
||||
/datum/progressbar/default/slim
|
||||
icon = 'icons/effects/progress_bar/default_slim.dmi'
|
||||
height = 5
|
||||
|
||||
/// A warning theme that pulses between yellow and red after its halfway point.
|
||||
/datum/progressbar/warning
|
||||
icon = 'icons/effects/progress_bar/warning.dmi'
|
||||
|
||||
/datum/progressbar/warning/update_fill(fraction)
|
||||
. = ..()
|
||||
if (fraction <= 0.5)
|
||||
fill.color = COLOR_YELLOW
|
||||
else
|
||||
fill.color = rgb_gradient(fraction * 10, 0.5, COLOR_YELLOW, 0.5, COLOR_RED, "loop")
|
||||
|
||||
/datum/progressbar/warning/slim
|
||||
icon = 'icons/effects/progress_bar/warning_slim.dmi'
|
||||
height = 5
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
author: Geeves
|
||||
|
||||
delete-after: True
|
||||
|
||||
changes:
|
||||
- rscadd: "Progress bars now use smooth, colour-changing fills and support visual themes."
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 312 B |
Binary file not shown.
|
After Width: | Height: | Size: 307 B |
Binary file not shown.
|
After Width: | Height: | Size: 315 B |
Binary file not shown.
|
After Width: | Height: | Size: 304 B |
Binary file not shown.
|
Before Width: | Height: | Size: 1013 B |
Reference in New Issue
Block a user