There have been various forum posts about "smarter" speedwalks. The plugin below attempts to address some of those issues.
The problem with normal speedwalks is that they can be:
The plugin below lets you call a "smart speedwalk" alias, like: ! 4s 3e
You can also type:
The speedwalk waits for a "new room" to appear by looking for a key line (such as "Exits: north south"). It also looks for an error message "Alas, you cannot go that way."). Finally, it incorporates a timeout, so that if neither of the above appear within (say) 10 seconds it auto-aborts. This might be because of an unusual line (eg. "the gate is locked").
All of the above can be customised by changing the entity lines at the start of the plugin.
[EDIT] Improved version further down - 4th June 2008.
#1
Australia Forum Administrator
#2
#3
Australia Forum Administrator
#4
USA
#5
Australia Forum Administrator
#6
Australia Forum Administrator
#7
#8
The problem with normal speedwalks is that they can be:
- Too fast - so the MUD server doesn't process them all
- Too slow - you build in a pause and then it takes forever
- Doesn't handle "you can't go that way" messages
- Cannot be paused while you chat, or kill a mob
- Cannot be aborted if you change your mind
The plugin below lets you call a "smart speedwalk" alias, like: ! 4s 3e
You can also type:
- pause speedwalk - this pauses until you resume it
- resume speedwalk - resume a paused speedwalk
- abort speedwalk - aborts a speedwalk sequence
The speedwalk waits for a "new room" to appear by looking for a key line (such as "Exits: north south"). It also looks for an error message "Alas, you cannot go that way."). Finally, it incorporates a timeout, so that if neither of the above appear within (say) 10 seconds it auto-aborts. This might be because of an unusual line (eg. "the gate is locked").
All of the above can be customised by changing the entity lines at the start of the plugin.
[EDIT] Improved version further down - 4th June 2008.
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE muclient
[
<!ENTITY speedwalk_alias "!*" >
<!ENTITY pause_speedwalk_alias "pause speedwalk" >
<!ENTITY resume_speedwalk_alias "resume speedwalk" >
<!ENTITY abort_speedwalk_alias "abort speedwalk" >
<!ENTITY timeout_secs "10" >
<!ENTITY exits_trigger
"^((?P<exits>Exits: .*\.)|(.*)Alas, you cannot go that way\.)$" >
]>
<!-- Saved on Monday, October 24, 2005, 7:21 AM -->
<!-- MuClient version 3.67 -->
<!-- Plugin "Slow_speedwalk" generated by Plugin Wizard -->
<!--
Customise the entities above to modify for the exact MUD output you get.
speedwalk_alias - the alias you want to invoke a speedwalk (eg. !*)
pause_speedwalk_alias - the alias you type to pause a speedwalk (eg. 'pause speedwalk')
resume_speedwalk_alias - the alias you type to resume a speedwalk (eg. 'resume speedwalk')
abort_speedwalk_alias - the alias you type to abort a speedwalk (eg. 'abort speedwalk')
timeout_secs - the time to wait for a new room "exits" line to arrive (eg. 10)
exits_trigger - this detects either that we have gone to a new room, or we can't move
The named wildcard portion (?P<exits> ... ) is designed to be tested in the script
so that if it is not empty we know we hit an exits line, rather than "you cannot go that
way".
-->
<muclient>
<plugin
name="Slow_speedwalk"
author="Nick Gammon"
id="56c9c5763d0c9c6ccf1e5b60"
language="Lua"
purpose="Speedwalks, waiting for each room to arrive"
date_written="2005-10-24 07:16:09"
requires="3.67"
version="1.0"
>
<description trim="y">
<![CDATA[
Usage
-----
! <speedwalk> - walks in that direction, eg. ! 5n 3w
pause speedwalk - pauses the speedwalk so you can do something else
resume speedwalk - resumes a paused speedwalk
abort speedwalk - aborts the current speedwalk
The speedwalk waits for the appropriate "Exits" message to appear so it knows it has reached a new room.
If it receives a "cannot go that way" message, the speedwalk is aborted.
If an "exits" message does not appear within 5 seconds the speedwalk is aborted.
]]>
</description>
</plugin>
<!-- Aliases -->
<aliases>
<alias
match="&speedwalk_alias;"
enabled="y"
send_to="12"
sequence="100"
>
<send>do local t = coroutine.create (function (t)
local lines = {}
sw = EvaluateSpeedwalk ("%1")
if string.sub (sw, 1, 1) == "*" then
ColourNote ("white", "red", string.sub (sw, 2))
return
end -- if
rex.new ("(.+)"):gmatch (sw,
-- build speedwalk lines into a table
function (m)
table.insert (lines, m)
end)
pause_speedwalk = false -- no pause yet
abort_speedwalk = false -- no abort yet
speedwalk_thread = t -- remember this thread
-- iterate the table, sending each line to the MUD
for i, line in ipairs (lines) do
-- see if pausing wanted (global variable set)
if pause_speedwalk then
ColourNote ("white", "green", "Speedwalk paused.")
ret = coroutine.yield ()
if ret == "abort" then
ColourNote ("black", "yellow", "Speedwalk abandoned.")
speedwalk_thread = nil
return
end -- abort speedwalk wanted
ColourNote ("white", "green", "Speedwalk resumed.")
pause_speedwalk = false
end -- if
-- see if aborting wanted
if abort_speedwalk then
ColourNote ("black", "yellow", "Speedwalk abandoned.")
speedwalk_thread = nil
return
end -- if
-- send the speedwalk
Send (line)
-- now wait for an appropriate response
line, wildcards = waitforregexp (t,
"&exits_trigger;", &timeout_secs;)
-- check for timeout
if not line then
ColourNote ("white", "red", "Speedwalk timed-out")
speedwalk_thread = nil
return -- give up
end -- if
-- check we didn't get told it was impossible
if wildcards.exits == "" then
ColourNote ("white", "red", "Speedwalk cancelled")
speedwalk_thread = nil
return -- give up
end -- if
end -- of iterating through each speedwalk line
-- all done!
ColourNote ("white", "blue", "Speedwalk done.")
speedwalk_thread = nil
end) assert (coroutine.resume (t, t)) end
</send>
</alias>
<alias
match="&pause_speedwalk_alias;"
enabled="y"
send_to="12"
sequence="100"
>
<send>if speedwalk_thread then
if pause_speedwalk then
ColourNote ("black", "yellow", "The speedwalk is already paused.")
else
pause_speedwalk = true
end
else
ColourNote ("black", "yellow", "No speedwalk is active.")
end
</send>
</alias>
<alias
match="&resume_speedwalk_alias;"
enabled="y"
send_to="12"
sequence="100"
>
<send>if speedwalk_thread then
if pause_speedwalk then
assert (coroutine.resume (speedwalk_thread, "resume"))
else
ColourNote ("black", "yellow", "The speedwalk is not paused.")
end
else
ColourNote ("black", "yellow", "No speedwalk is active.")
end</send>
</alias>
<alias
match="&abort_speedwalk_alias;"
enabled="y"
send_to="12"
sequence="100"
>
<send>if speedwalk_thread then
if pause_speedwalk then
assert (coroutine.resume (speedwalk_thread, "abort"))
else
abort_speedwalk = true
end
else
ColourNote ("black", "yellow", "No speedwalk is active.")
end</send>
</alias>
</aliases>
<!-- Script -->
<script>
<![CDATA[
function tprint (t, indent, done)
done = done or {}
indent = indent or 0
for key, value in pairs (t) do
Tell (string.rep (" ", indent)) -- indent it
if type (value) == "table" and not done [value] then
done [value] = true
Note (tostring (key), ":");
tprint (value, indent + 2, done)
else
Tell (tostring (key), "=")
print (value)
end
end
end
-- ----------------------------------------------------------
-- 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
a, b = coroutine.resume (thread)
if not a then
ColourNote ("white", "red", "Error in wait_timer_resume: " .. b)
end -- if error resuming
end -- if
end -- function wait_timer_resume
-- ----------------------------------------------------------
-- called by a trigger to resume a thread
-- ----------------------------------------------------------
function wait_trigger_resume (name, line, wildcards)
EnableTrigger (name, false) -- don't want it to fire again
DoAfterSpecial (1, "DeleteTrigger ('" .. name .. "')", 12) -- delete it
thread = wait_table [name]
if thread then
a, b = coroutine.resume (thread, line, wildcards)
if not a then
ColourNote ("white", "red", "Error in wait_trigger_resume : " .. b)
end -- if error resuming
end -- if
end -- function wait_trigger_resume
-- ----------------------------------------------------------
-- we call this to wait in a script
-- ----------------------------------------------------------
function wait (thread, seconds, id)
id = id or "wait_" .. 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,
"DeleteTrigger ('" .. id .. "')",
timer_flag.Enabled + timer_flag.OneShot +
timer_flag.Temporary + timer_flag.Replace,
"wait_timer_resume")
assert (status == error_code.eOK, error_desc [status])
SetTimerOption (id, "send_to", "12") -- send to script
wait_table [id] = thread
return coroutine.yield ()
end -- function wait
-- ----------------------------------------------------------
-- we call this to wait for a trigger with a regexp
-- ----------------------------------------------------------
function waitforregexp (thread, regexp, timeout)
local id = "wait_" .. GetUniqueNumber ()
status = AddTriggerEx (id, regexp,
"DeleteTimer ('" .. id .. "')", -- script to delete the timeout timer
trigger_flag.Enabled + trigger_flag.RegularExpression +
trigger_flag.Temporary + trigger_flag.Replace,
custom_colour.NoChange,
0, "", -- wildcard number, sound file name
"wait_trigger_resume",
12, 100) -- send to scripting
assert (status == error_code.eOK, error_desc [status])
if timeout and timeout > 0 then
return wait (thread, timeout, id)
end -- if having a timeout
wait_table [id] = thread
return coroutine.yield () -- return line, wildcards
end -- function waitforregexp
-- ----------------------------------------------------------
-- we call this to wait for a trigger (not a regexp)
-- ----------------------------------------------------------
function waitfor (thread, match, timeout)
return waitforregexp (thread, MakeRegularExpression (match), timeout)
end -- function waitfor
]]>
</script>
<!-- Plugin help -->
<aliases>
<alias
script="OnHelp"
match="Slow_speedwalk:help"
enabled="y"
>
</alias>
</aliases>
<script>
<![CDATA[
function OnHelp ()
world.Note (world.GetPluginInfo (world.GetPluginID (), 3))
end
]]>
</script>
</muclient>
This is pretty nice! I was trying to extend it to pause the speedwalk when a certain mob was triggered. I made a trigger to pause speedwalk, but it doesn't get applied until it has already moved to the next room. Would you please add a trigger to this pluging to stop when one of a list of mobs is encountered?
Can you paste your trigger so I see how you went about it please?
I used the slow speedwalk plugin with one modification:
<!ENTITY exits_trigger
"^((?P<exits>(\\\\w+\\\\s?)+\\\\050(.*)\\\\051)|(.*)You can't go that way!)"
Here's the trigger, which stops the speedwalk one room too late.
<triggers>
<trigger
enabled="y"
match="flock of seagulls"
regexp="y"
send_to="10"
sequence="100"
>
<send>pause speedwalk</send>
</trigger>
</triggers>
<!ENTITY exits_trigger
"^((?P<exits>(\\\\w+\\\\s?)+\\\\050(.*)\\\\051)|(.*)You can't go that way!)"
Here's the trigger, which stops the speedwalk one room too late.
<triggers>
<trigger
enabled="y"
match="flock of seagulls"
regexp="y"
send_to="10"
sequence="100"
>
<send>pause speedwalk</send>
</trigger>
</triggers>
I am guessing this is because of the sequence. You are probably seeing:
(or similar)
Thus, it is acting on the new room (the Exits line) before it notices the seagulls.
I'm not sure how to easily fix this, unless you use some other method of recognising when you enter a room (one that appears after the list of mobs).
Alternatively, maybe remember the last direction moved, and simply go back.
Exits: North
You see a flock of seagulls.
(or similar)
Thus, it is acting on the new room (the Exits line) before it notices the seagulls.
I'm not sure how to easily fix this, unless you use some other method of recognising when you enter a room (one that appears after the list of mobs).
Alternatively, maybe remember the last direction moved, and simply go back.
Could you use a timer to delay the execution of the next direction for like half a second after it picks up the Exits: line and if it picks up a mob in that time, it'll override? It might be tricky but it should work. It might slow down the whole speedwalk a bit though. And it would further complicate the plugin.
I have amended the plugin to make it use the new Lua modules that now come with MUSHclient. This makes it a bit easier to read. Also added is a pause after reaching each room.
To save and install the Slow_speedwalk plugin do this:
- Copy the code below (in the code box) to the Clipboard
- Open a text editor (such as Notepad) and paste the plugin code into it
- Save to disk on your PC, preferably in your plugins directory, as
Slow_speedwalk.xml- The "plugins" directory is usually under the "worlds" directory inside where you installed MUSHclient.
- Go to the MUSHclient File menu -> Plugins
- Click "Add"
- Choose the file
Slow_speedwalk.xml(which you just saved in step 3) as a plugin - Click "Close"
- Save your world file, so that the plugin loads next time you open it.
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE muclient
[
<!ENTITY speedwalk_alias "!*" >
<!ENTITY pause_speedwalk_alias "pause speedwalk" >
<!ENTITY resume_speedwalk_alias "resume speedwalk" >
<!ENTITY abort_speedwalk_alias "abort speedwalk" >
<!ENTITY help_speedwalk_alias "help speedwalk" >
<!ENTITY timeout_secs "10" >
<!ENTITY delay_secs "0.5" >
<!ENTITY exits_trigger
"^((?P<exits>\\[Exits\\: .+\\])|(.*)Alas, you cannot go that way\\.)$" >
]>
<!-- Saved on Monday, October 24, 2005, 7:21 AM -->
<!-- MuClient version 3.67 -->
<!-- Plugin "Slow_speedwalk" generated by Plugin Wizard -->
<!--
Customise the entities above to modify for the exact MUD output you get.
speedwalk_alias - the alias you want to invoke a speedwalk (eg. !*)
pause_speedwalk_alias - the alias you type to pause a speedwalk (eg. 'pause speedwalk')
resume_speedwalk_alias - the alias you type to resume a speedwalk (eg. 'resume speedwalk')
abort_speedwalk_alias - the alias you type to abort a speedwalk (eg. 'abort speedwalk')
timeout_secs - the time to wait for a new room "exits" line to arrive (eg. 10)
delay_secs - the time to wait after each speedwalk succeeds, before doing another one (eg. 0.5)
exits_trigger - this detects either that we have gone to a new room, or we can't move
In the exits_trigger any backslashes have to be doubled, as the first one is processed by Lua,
so we need two to reach the regular expression parser.
The named wildcard portion (?P<exits> ... ) is designed to be tested in the script
so that if it is not empty we know we hit an exits line, rather than "you cannot go that
way". eg. [Exits: North South]
-->
<!-- Version 2 uses wait.lua for extra compactness -->
<muclient>
<plugin
name="Slow_speedwalk"
author="Nick Gammon"
id="56c9c5763d0c9c6ccf1e5b60"
language="Lua"
purpose="Speedwalks, waiting for each room to arrive"
date_written="2005-10-24 07:16:09"
date_modified="2008-06-4 06:56:09"
requires="3.67"
version="2.0"
>
<description trim="y">
Usage
-----
! <speedwalk> - walks in that direction, eg. ! 5n 3w
&pause_speedwalk_alias; - pauses the speedwalk so you can do something else
&resume_speedwalk_alias; - resumes a paused speedwalk
&abort_speedwalk_alias; - aborts the current speedwalk
&help_speedwalk_alias; - this message
The speedwalk waits for the appropriate "Exits" message to appear so it knows it has reached a new room.
If it receives a "cannot go that way" message, the speedwalk is aborted.
If an "exits" message does not appear within 10 seconds the speedwalk is aborted.
</description>
</plugin>
<!-- Aliases -->
<aliases>
<alias
match="&speedwalk_alias;"
enabled="y"
script="func_handle_speedwalk"
>
</alias>
<alias
match="&pause_speedwalk_alias;"
enabled="y"
script="func_pause_speedwalk"
>
</alias>
<alias
match="&resume_speedwalk_alias;"
enabled="y"
script="func_resume_speedwalk"
>
</alias>
<alias
match="&abort_speedwalk_alias;"
enabled="y"
script="func_abort_speedwalk"
>
</alias>
<alias
match="&help_speedwalk_alias;"
enabled="y"
script="func_help_speedwalk"
>
</alias>
</aliases>
<!-- Script -->
<script>
require "tprint"
require "wait"
require "getlines"
-- main function called when you type a speedwalk
function func_handle_speedwalk (name, line, wildcards)
wait.make (function () --- coroutine below here
sw = EvaluateSpeedwalk (wildcards [1])
if string.sub (sw, 1, 1) == "*" then
ColourNote ("white", "red", string.sub (sw, 2))
return
end -- if
pause_speedwalk = false -- no pause yet
abort_speedwalk = false -- no abort yet
speedwalk_thread = coroutine.running () -- remember this thread
-- iterate the speedwalk string, line by line, sending each line to the MUD
for walk_line in getlines (sw) do
if string.match (walk_line, "^%s*$") then
break
end -- empty line - probably end of speedwalk
-- see if pausing wanted (global variable set)
if pause_speedwalk then
ColourNote ("white", "green", "Speedwalk paused.")
ret = coroutine.yield ()
if ret == "abort" then
ColourNote ("black", "yellow", "Speedwalk abandoned.")
speedwalk_thread = nil
return
end -- abort speedwalk wanted
ColourNote ("white", "green", "Speedwalk resumed.")
pause_speedwalk = false
end -- if
-- see if aborting wanted
if abort_speedwalk then
ColourNote ("black", "yellow", "Speedwalk abandoned.")
speedwalk_thread = nil
return
end -- if
-- send the speedwalk
Send (walk_line)
-- now wait for an appropriate response
line, wildcards = wait.regexp ("&exits_trigger;", &timeout_secs;)
-- check for timeout
if not line then
ColourNote ("white", "red", "Speedwalk timed-out")
speedwalk_thread = nil
return -- give up
end -- if
-- check we didn't get told it was impossible
if wildcards.exits == "" then
ColourNote ("white", "red", "Speedwalk cancelled")
speedwalk_thread = nil
return -- give up
end -- if
-- pause before doing another if wanted
if &delay_secs; > 0 then
wait.time (&delay_secs;)
end -- if pause wanted
end -- of iterating through each speedwalk line
-- all done!
ColourNote ("white", "blue", "Speedwalk done.")
speedwalk_thread = nil
end) -- end of coroutine
end -- function func_handle_speedwalk
-- called to pause the current speedwalk
function func_pause_speedwalk ()
if speedwalk_thread then
if pause_speedwalk then
ColourNote ("black", "yellow", "The speedwalk is already paused.")
else
pause_speedwalk = true
end
else
ColourNote ("black", "yellow", "No speedwalk is active.")
end
end -- func_pause_speedwalk
-- called to resume after a pause
function func_resume_speedwalk ()
if speedwalk_thread then
if pause_speedwalk then
assert (coroutine.resume (speedwalk_thread, "resume"))
else
ColourNote ("black", "yellow", "The speedwalk is not paused.")
end
else
ColourNote ("black", "yellow", "No speedwalk is active.")
end
end -- func_resume_speedwalk
-- called to abort the speedwalk
function func_abort_speedwalk ()
if speedwalk_thread then
if pause_speedwalk then
assert (coroutine.resume (speedwalk_thread, "abort"))
else
abort_speedwalk = true
end
else
ColourNote ("black", "yellow", "No speedwalk is active.")
end
end -- func_abort_speedwalk
function func_help_speedwalk ()
world.Note (world.GetPluginInfo (world.GetPluginID (), 3))
end
DoAfterSpecial (1, 'ColourNote ("white", "green", "Slow speedwalk plugin installed.")', 12);
</script>
</muclient>
What you can do if you need to stop automatically and fight is make a trigger that reacts to something, and sends "pause speedwalk" (or "abort speedwalk") to "execute" so that the plugin notices it, like this:
This doesn't have to be in the plugin, as sending to execute will be picked up by the plugin. However if you amended the plugin it would be able to detect if it was doing a speedwalk or not, thus avoiding the "No speedwalk is active." message if you aren't speedwalking.
<triggers>
<trigger
enabled="y"
match="You are under attack!"
send_to="10"
sequence="100"
>
<send>pause speedwalk</send>
</trigger>
</triggers>
This doesn't have to be in the plugin, as sending to execute will be picked up by the plugin. However if you amended the plugin it would be able to detect if it was doing a speedwalk or not, thus avoiding the "No speedwalk is active." message if you aren't speedwalking.
Okay, the regular speedwalk allows for other commands like "Fly" or "land". This speedwalk plugin does not seem to allow for that. How do I add lines other than "Exits: " so that the command line loop continues?
Thanks in advance.
Thanks in advance.