ID:1947589
 
(See the best response by Kaiochao.)
Code:
//mob/var/tmp/GUIstamina
proc/UpdateStamina(mob/player,value)
spawn()
//initialise with a variance of 2 so the code below animates to right value from next closest value
if(!player.GUIstamina) player.GUIstamina = round(((player.stamina.value) / player.stamina.max_value) * 100)-2

var/_stamina = player.stamina.value

//changes the raw value of the stat
player.stamina.Set_CurrentValue(_stamina + value)

var
//value of stamina as a % of maximum
_percent = round(((player.stamina.value) / player.stamina.max_value) * 100)
//the value we are changing to, using stam because its a var defined outside of this proc
_target = player.stamina.value
//the difference between _percent and the % value currently displayed
_difference = round(((player.stamina.value) / player.stamina.max_value) * 100) - player.GUIstamina
//this is the integer used to modify the display value below
n = 1

//sets 'n' according to the direction we're going (higher or lower)
if(_difference > 0) n = 1
if(_difference < 0) n = -1
//if the stamina isn't different, don't animate
if(_difference == 0) return


if(player.client && player.GUIstamina!=_percent)
var/statHUD/s = locate() in player.client.screen
if(s)
//animation loops once per % difference between display and intended value
for(var/i = abs(_difference), i>0, i--)
//change the HUD by 1 in whichever direction requried
player.GUIstamina += n
s._HUDUpdate(player.GUIstamina)

//stop the loop if the HUD display == the stamina % or if the player's stamina value has changed since animating
//if the stamina valua HAS changed, this proc will be called all over again.
if(player.GUIstamina == _percent || _target != player.stamina.value)
break
//0.1 is slower than 0, but way faster than 1; cannot work out why. Reference is silent on it.
var/r = rand(0,1)/10
sleep(r)


Problem description:
Extra notes on the code:

* What its doing each loop is changing the icon_state value of the HUD icon. Between 1-100 for the stamina, which is a sphere expanding and contracting. And between 0-300 for magic, which is a horizontal bar, 300 pixels wide.
* I'm not using animate because the HUD icons are high quality icons made in photoshop, even photoshop-level content-aware re-scaling would distort them beyond recognition. So AFAIK animate() is not an option, until Lummox does an animate() proc for changing numerics / or has that intuitively put into animate(icon_state).



The above code works great! But I'm a little concerned it could be resource-greedy, esp when applied to 3 HUD bars, per player.

There's also the concern that I've done it the hard way, or that there's a better way, or that there's a gaping potential calamity with this method?
Technically being able to reliably 'animate()' the icon_state var (which is an integer) in a more efficient way would be a significant upgrade)

Any input or feedback greatly appreciated.
Best response
Instead of wrapping the body of the proc in spawn, you should either:
* Use spawn when you don't want to wait for the proc to finish before moving on
// e.g.
proc/UpdateStamina(mob/player, value)
if(!player.GUIstamina) // etc.

// call with spawn:
spawn UpdateStamina(player, value)

* Or use "set waitfor = FALSE" in the proc.
// e.g.
proc/UpdateStamina(mob/player, value)
set waitfor = FALSE
if(!player.GUIstamina) // etc.


This proc should really be called AddStamina() or ModStamina() since it adds a value to the current stamina. Or, you could split this proc into an AddStamina(), which changes the stamina and calls UpdateStamina(), which updates the HUD.

It's not clear why you're including that -2 in the GUIstamina calculation.

Your _difference var duplicates your _percent calculation.

You should have a object-level variable to contain the HUD object instead of looking for it every time.

Early exit conditions should happen sooner.
// yours
//sets 'n' according to the direction we're going (higher or lower)
if(_difference > 0) n = 1
if(_difference < 0) n = -1
//if the stamina isn't different, don't animate
if(_difference == 0) return

// improved:
//sets 'n' according to the direction we're going (higher or lower)
if(_difference > 0) n = 1
else if(_difference < 0) n = -1
//if the stamina isn't different, don't animate
else if(_difference == 0) return

The most common case might be when _difference > 0. It comes first, so the other two checks don't even happen.

Here's how you could've used animate():
mob
var
statHUD/stamina_bar

proc
AddStamina(Amount)
var old_stamina = stamina.value
var new_stamina = old_value + Amount
stamina.Set_CurrentValue(new_stamina)

if(!stamina_bar) stamina_bar = locate() in client.screen
if(!stamina_bar) return // don't update nonexistent HUD

var to_percent = 100 / stamina.max_value // common conversion factor
var old_percent = min(max(round(old_stamina * to_percent, 1), 0), 100) // rounded and clamped between 0-100
var new_percent = min(max(round(new_stamina * to_percent, 1), 0), 100)
var difference = new_percent - old_percent
if(!difference) return
var sign = difference > 0 ? 1 : -1

// start animation (time = 0 might not be necessary)
// this also interrupts animations currently running
// this also assumes stamina_bar is the actual HUD object
animate(stamina_bar, time = 0)
for(var/value in old_stamina to new_stamina step sign)
// stamina_bar left out because these are keyframes
// world.tick_lag is the fastest it can update without being instant
animate(time = world.tick_lag, icon_state = "[value]")
Thanks for the reply! I'll respond point by point if that's ok? :)

* I originally tried using set waitfor = 0, it spat out a compile error so I did that and moved on. As a result I assumed you could only use settings for mob/obj procs? Thought it was weird tbh.

* I guess I could split them up; not sure what the purpose would be? If you call the proc without defining 'value', it simply updates the HUD. Am I missing something?
* Side Note: 'value' is given as a negative number when taking damage (UpdateStamina(mob,-damage))

* Calling -2 to initialise the GUI value at 2% different to actual value, so that its not caught by if(!difference). Cant specifically recall, but +-1 didnt sovle problem either.

* The duplicate _percent calc was half paranoid about runtimes, half making the proc design make sense in my head without worrying about errors/sanity checks.

* early exits /elseifs: yep good call, doing that for sure, thanks.


...And now I've just read your proc. So much cleaner - and client side! Thank you so much!

Wait, is it still client side if its being chucked through the for loop? I'm guessing no.

Either way, better.
// world.tick_lag is the fastest it can update without being instant

This isn't true for animate().
Wait, is it still client side if its being chucked through the for loop? I'm guessing no.
It is, since it's animate(). All the animate() calls are happening in the same tick, so the client gets all the information about the whole animation all at once, and then plays it out over time. The animation itself is played out client-side, but the logic that builds the animation is of course performed server-side.

// world.tick_lag is the fastest it can update without being instant

This isn't true for animate().
I assumed every frame had to be visible. The map doesn't redraw at a higher frequency than world.fps just because some object is animating faster.
The map doesn't redraw at a higher frequency than world.fps just because some object is animating faster.

...Actually...

http://www.byond.com/forum/?post=1918661

But yeah, animate() can't run faster than FPS. There are just some bugs with images that cause DS to refresh prematurely.