[Home] [Downloads] [Search] [Help/forum]

Gammon Software Solutions forum

See www.mushclient.com/spam for dealing with forum spam. Please read the MUSHclient FAQ!

[Folder]  Entire forum
-> [Folder]  MUSHclient
. -> [Folder]  Lua
. . -> [Subject]  Building pauses into scripts
Home  |  Users  |  Search  |  FAQ
Username:
Register forum user name
Password:
Forgotten password?

Building pauses into scripts

[Reply to this subject]  Reply to this subject   [New subject]  Start a new subject   [Refresh] Refresh page


Posted by Nick Gammon   Australia  (15,382 posts)  [Biography] bio
Date Wed 01 Dec 2004 08:30 PM (UTC)  quote  ]

Amended on Tue 26 Jun 2007 10:18 PM (UTC) by Nick Gammon

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
[Go to top] top

Posted by Nick Gammon   Australia  (15,382 posts)  [Biography] bio
Date Thu 08 Mar 2007 07:57 PM (UTC)  quote  ]

Amended on Tue 26 Jun 2007 10:18 PM (UTC) by Nick Gammon

Message
Under Lua 5.1 (MUSHclient version 3.80 onwards) the whole thing is simpler, because a coroutine can obtain its own thread.


-- 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
    wait_table [name] = nil  -- remove from table
    assert (coroutine.resume (thread))
  end -- if
end -- function wait_timer_resume 

-- we call this to wait in a script
function wait (seconds)

  local id = "wait_timer_" .. GetUniqueNumber ()
  wait_table [id] = assert (coroutine.running (), "Must be in coroutine")

  local hours = math.floor (seconds / 3600)
  local seconds = seconds - (hours * 3600)
  local minutes = math.floor (seconds / 60)
  local 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])

  coroutine.yield ()
end -- function wait


The "wait" routine has been changed to find its own coroutine address (line in bold), so we don't need to pass it as an argument.

That simplifies creating the thread, as we can omit the "thread" argument, so it looks like this:


function my_alias_thread (name, line, wildcards)

  Send ("prepare heal")
  wait (1)

  Send ("cast heal")
  wait (2)

  Send ("eat bread")
  wait (3)

  Note ("Done")

end -- function my_alias_thread 


function my_alias (name, line, wildcards)

  assert (coroutine.resume (coroutine.create (my_alias_thread), 
          name, line, wildcards))

end -- function my_alias



And now the "inline scripting" version is simpler, too:


<aliases>
  <alias
   match="test2"
   enabled="y"
   send_to="12"
   sequence="100"
  >
  <send>
coroutine.wrap (function ()

  Send ("prepare heal")
  wait (1)

  Send ("cast heal")
  wait (2)

  Send ("eat bread")
  wait (3)

  Note ("Done")

end) ()
</send>
  </alias>
</aliases>


We don't need to find the thread variable, so we can simplify creating the thread, using coroutine.wrap.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Nick Gammon   Australia  (15,382 posts)  [Biography] bio
Date Thu 08 Mar 2007 08:07 PM (UTC)  quote  ]

Amended on Tue 26 Jun 2007 10:17 PM (UTC) by Nick Gammon

Message
This functionality is pre-supplied now in the "wait.lua" file that ships with MUSHclient. Thus, without needing a script file at all, you can "require" the wait file, and make an inline waiting alias, like this:


<aliases>
  <alias
   match="test"
   enabled="y"
   send_to="12"
   sequence="100"
  >
  <send>
require "wait"
wait.make (function ()

  Send ("prepare heal")
  wait.time (1)

  Send ("cast heal")
  wait.time (2)

  Send ("eat bread")
  wait.time (3)

  Note ("Done")

end) 
</send>
  </alias>
</aliases>



The lines in bold are what you need to copy into your own scripts. Now waiting for a few seconds is recoded was "wait.time" which is the "time delay" function built into the wait.lua file.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).

To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.


12,712 views.

[Reply to this subject]  Reply to this subject   [New subject]  Start a new subject   [Refresh] Refresh page

Go to topic:           Search the forum


[Go to top] top

[Home]

Written by Nick Gammon - 5K

Comments to: Gammon Software support
[RH click to get RSS URL] Forum RSS feed ( http://www.gammon.com.au/rss/forum.xml )

[Best viewed with any browser - 2K]    [Internet Contents Rating Association (ICRA) - 2K]    [Web site powered by FutureQuest.Net]