Posted by
| Nick Gammon
Australia (23,102 posts) Bio
Forum Administrator |
Message
| We have been asked many times in this forum:
"How do I pause in a script?"
For example, someone wants to do this in an alias:
Send ("prepare heal")
-- wait 2 seconds
Send ("cast heal")
-- wait 3 seconds
Send ("eat bread"
Up till now this hasn't been possible, and the occasional attempt to achieve this by making a loop like this, doesn't work very well:
Send ("prepare heal")
-- loop for a second
for i = 1 to 100000
next
Send ("cast heal")
It doesn't work very well because all that achieves is to "hang" the entire PC for a while. Apart from the problems that causes, the 1-second delay will still probably come in the wrong place, because while it is hanging the original send ("prepare heal") probably has not even been sent over the network yet.
What you need is some way of causing the script to pause for a second, and then resume where you left off.
Finally that is possible, using Lua. The script below demonstrates how to do that. The first part can be put in your script file and shared between every trigger and alias that needs to have pauses in it.
The basic technique is to use the Lua "thread" model. This lets you define "co-routines" or "threads" that can yield execution back to MUSHclient when they have finished for the moment.
Then we will make a timer to resume them at the desired time.
The first part, which is shared between all scripts that need it is:
- A table of outstanding threads, keyed by timer name
- A routine called when the timer fires, which resumes the thread
- A "wait" script which is called when we want to pause.
The "wait" script:
- Generates a unique timer name to be used in our table of threads
- Adds a timer with this unique timer name for the specified time
- Adds the timer name and the thread address to the table of threads
- Yields execution (pauses)
-- table of outstanding threads that are waiting
wait_table = {}
-- called by a timer to resume a thread
function wait_timer_resume (name)
thread = wait_table [name]
if thread then
assert (coroutine.resume (thread))
end -- if
end -- function wait_timer_resume
-- we call this to wait in a script
function wait (thread, seconds)
id = "wait_timer_" .. GetUniqueNumber ()
hours = math.floor (seconds / 3600)
seconds = seconds - (hours * 3600)
minutes = math.floor (seconds / 60)
seconds = seconds - (minutes * 60)
status = AddTimer (id, hours, minutes, seconds, "",
timer_flag.Enabled + timer_flag.OneShot +
timer_flag.Temporary + timer_flag.Replace,
"wait_timer_resume")
assert (status == error_code.eOK, error_desc [status])
wait_table [id] = thread
coroutine.yield ()
end -- function wait
Now with those sitting in our script file, we are ready to make an alias with pauses in it. This is really a two-step process. In order to get a thread to yield, we first need a thread to execute in the first place. So, the real work is going to be done in my_alias_thread, which is started by the alias, however it has a fourth argument, which is the thread address. This looks pretty simple, and you can see in it the pauses (calls to "wait"). We need to pass down the address of our current thread to the wait routine, so it knows which thread to eventually resume.
In this example I am doing a "Note" however you can do "Send" or whatever you need.
function my_alias_thread (thread, name, line, wildcards)
Send ("prepare heal")
wait (thread, 1)
Send ("cast heal")
wait (thread, 2)
Send ("eat bread")
wait (thread, 3)
Note ("Done")
end -- function my_alias_thread
Finally a small "stub" which is called by the actual alias - this simply creates a coroutine (a thread) which is the script just above, and then resumes it. The word "thread" appears twice when we resume it. The first time we are telling coroutine.resume which thread to resume, the second one is passed down to the script itself, so it can be used for pausing purposes. The assert function is there to trap errors, as errors in a resumed function are not automatically reported.
function my_alias (name, line, wildcards)
thread = coroutine.create (my_alias_thread)
assert (coroutine.resume (thread, thread, name, line, wildcards))
end -- function my_alias
Finally the alias which I will use to test it:
<aliases>
<alias
name="test_alias"
script="my_alias"
match="test"
enabled="y"
sequence="100"
>
</alias>
</aliases>
Doing the alias as "send to script"
It is possible to have the alias (or trigger) as a "send to script" and still use the inbuilt pauses. You still need the wait_table, wait_timer_resume and wait functions in your script file. However the rest can be done in "send to script" by using an anonymous function created on-the-fly.
Below is an example. You bracket what you are trying to do with an extra line at the start and end, in bold below. I think it still looks pretty easy to read ...
<aliases>
<alias
match="test2"
enabled="y"
send_to="12"
sequence="100"
>
<send>
do local t = coroutine.create (function (t)
Note ("prepare heal")
wait (t, 1)
Note ("cast heal")
wait (t, 2)
Note ("eat bread")
wait (t, 3)
Note ("Done")
end) assert (coroutine.resume (t, t)) end
</send>
</alias>
</aliases>
|
- Nick Gammon
www.gammon.com.au, www.mushclient.com | Top |
|