ID:834208
 
(See the best response by .screw.)
Code:
    Rest()
set category = "Skills"
if(!Resting)
icon_state = "Resting"
Resting = 1

else
Resting = 0
icon_state = ""


Problem description:

I'd like to make this wait a bit before you can use it again, but, a verb doesn't have to finish running in order for a new instance of it to be created. If I hold down a Rest macro, and bunch of rests can run consecutively. So far, using sleep() while manipulating the variables has proved to be a solution, but what's the best way to prevent the verb from being spammed?
Actually, a verb does have to finish in order for another instance to be created. BYOND is single-threaded, and only does one process at a time.

Anyways, you can just use spawn() to tell it when you can rest.

Rest()
if(!resting)
// rest
resting = 1
spawn(rest_time)
resting = 0
else
resting = 0
// Note: the else clause will still cause you to be able to turn resting on and off, producing the illusion of spamming.


Of course you could do it other ways, all depending on how you plan on managing "resting". If you want them to stop resting when stat A is fully recharged, then you are going to either want to loop to check when it is recharged, or call a proc to recharge it that will return a value to tell when it is recharged.

I know all of the above was complicated, but my point was that you could handle resting times many ways depending on what you want to do while resting and what conditions will cause resting to stop.
Thanks for the quick reply!

In this instance, I'd like for the player to be able to Rest until they use the Rest verb again, they would end it. Since resting would do more than just refill health/energy, it'd be up to them how long to rest.

I need it to where, they can't just keep running it over and over. Take my Attack verb for example;

    Attack()
set category = "Skills"
if(!Attacking && Chi >= 10)
flick("Attack", src)
for(var/mob/M in get_step(src, src.dir))
var/Damage = max(1, round((BP * StrMod) - (M.BP * (M.DurMod/1.5))))
M.TakeDamage(Damage, src)
Chi -= 10
Attacking = 1
sleep(20/src.SpdMod) // Stopping consecutive attacks
Attacking = 0


This prevents the person from attacking for a bit, as they can't attack if already "Attacking".

If I code it like this though;

    Attack2()
set category = "Skills"
if(!Attacking && Chi >= 10)
flick("Attack", src)
for(var/mob/M in get_step(src, src.dir))
var/Damage = max(1, round((BP * StrMod) - (M.BP * (M.DurMod/1.5))))
M.TakeDamage(Damage, src)
Chi -= 10
Attacking = 1
sleep(20/SpdMod) // Stopping consecutive attacks
else if(Attacking)
Attacking = 0


I can actually spam attack, the sleep() doesn't finish before I'm able to attack again.
That's because sleep() doesn't halt things, it just gives way to the next process in line, and then jumps back in later.

The else if() is what is causing it. You are calling Attack2() while the sleep() is going, which means sleep() is going to let Attack2() go in front of it in line. Attack2() runs, and turns itself off via the else if() clause.

You'll need to do an external cooldown of sorts. There are systems out there like Stephen001's EventScheduler that will serve this purpose better, but for simplicity's sake, here is a crude example:

mob/proc/cooldown()
if(attack_cooldown)
attack_cooldown -= 1
spawn(1)

mob/proc/attack()
if(attack_cooldown)
return
//attack stuff
attack_cooldown = 10

mob/New()
..()
cooldown()
Ahhh, okay, I understand most of this. I could even rewrite cooldown() to have arguments, so I can use it in multiple procedures without modifying it.. woot~

Anyhow, the only thing that throws me off is how this continues to run.

Here is how I've been getting things to continually run (this is a proc which allows passive health regeneration);

mob/proc

Regenerates() // Passive Health recovering.
while(src)
if(Health + Reg*Regeneration <= MaxHealth)
Health += Reg*Regeneration
else if(Health > MaxHealth)
Health = MaxHealth
sleep(20)

mob
New()
spawn(-1) src.Regenerates()


Is it better to use spawn or sleep in this case? I'm having a hard time differentiating between the two. It seems like sleep allows the proc to pause and let other things run, whereas spawn stops it dead, maybe?

Also...

mob/New()
..()
cooldown()


I read ..() calls a parent procedure and returns it's value, but what is the parent procedure of mob/New()?
In response to Kitsueki
Kitsueki wrote:
Is it better to use spawn or sleep in this case? I'm having a hard time differentiating between the two. It seems like sleep allows the proc to pause and let other things run, whereas spawn stops it dead, maybe?

This should explain how they work a little better for you.

Also...

> mob/New()
> ..()
> cooldown()
>

I read ..() calls a parent procedure and returns it's value, but what is the parent procedure of mob/New()?

You are overriding mob/New() in the above example. Therefore, you use ..() to call mob/New()'s default behavior, which is, you know, initializing the mob, and then call cooldown().
In response to Albro1
Initializing the mob happens before New() is called, hence why you can interact with it in New() itself. There is no default behavior, so you only really need to call ..() if you plan on defining mob/New() in multiple places (which is usually bad from an organizational standpoint) or if you plan on overriding atom/New() or atom/movable/New().

If a built-in process has a default behavior, it'll list it in the reference under "default action" or describe it in the description.
In response to DarkCampainger
Ah, sorry. My mistake.
Okay, that was a good read.

..I'm still a bit clueless as to how the cooldown() proc is continuing to run. Wouldn't this be an infinite counter?
Infinite loops are created when you have a loop that has no way of ending and no way of giving up it's spot in the thread to another process.

spawn() lets another process take over, so it doesn't create an infinite loop.
Well, what I mean is.. wouldn't this require something to continuously loop itself, to work? I don't see how it's doing that, maybe I don't understand the procs involved fully.
I'm sorry, I forgot the most crucial part.
mob/proc/cooldown()
if(attack_cooldown)
attack_cooldown -= 1
spawn(1)
cooldown()


I didn't even realize I forgot it. Sorry.
So it continually runs itself with a 1 second delay. That's a pretty interesting way to go about it. Are there any problems with calling a procedure within itself?

It's cool though, lol, I'm happy you're willing to explain this stuff to me.
Best response
There are slight performance differences between using while(), for(), and Albro1's loop method. But I doubt it is anything visually significant. I suggest going with the loop that looks organized in your own opinion.
In response to Kitsueki
Kitsueki wrote:
So it continually runs itself with a 1 second delay.

Correction: 1 tick, or 1/10th of a second.
I thought it was tick, wasn't sure if it used the ticks, or just a second delay. Thanks for clearing that up.