Auto Rescue(sort of)

Posted by ReallyCurious on Tue 10 Apr 2007 02:43 AM — 29 posts, 97,702 views.

USA #0
I'm trying to create an autorescue throughout multiple worlds that can be toggled on/off through any world.

I think I could do this with just a handful of variables/triggs/aliases but I'm having a hard time.

I'm stuck on something like this right now.

<triggers>
<trigger
enabled="y"
expand_variables="y"
match="(?U)^(?!You)(.*), fighting (?!you)(@to_rescue).$"
regexp="y"
send_to="12"
sequence="100"
>
<send>Send ("rescue %2")</send>
</trigger>
</triggers>

The small spider is standing here, fighting Warrior.
rescue Warrior
Warrior the Male Human is standing here, fighting the small spider.

This is fine for auto rescuing 1 person. But if I want an auto rescue list with dozens of names I can't seem to do that with a variable.

If I change the variable 'to_rescue' to Warrior|Druid|Cleric for example, the trigger doesn't work.

Any ideas?
USA #1
match="^(?!You)(.*), fighting (Warrior|Druid|Cleric).$"
Should work... Don't see why it wouldn't capture the variable at all.

Odd question though. Why would you put anything before a "^" in a regex pattern? The carat forces the pattern to match only if it's at the start of a line, and adding anything in front kind of defeats the purpose. Unless (?U) does something I am not familiar with (which is entirely possible).
Amended on Tue 10 Apr 2007 03:37 AM by Shaun Biggs
USA #2
ok, after a little bit of brainstorming...
<triggers>
  <trigger
    enabled="y"
    expand_variables="y"
    match="^(?!You)(.*), fighting (\w+).$"
    regexp="y"
    send_to="12"
    sequence="100"
  >
  <send>resc = {"Warrior", "Druid", "Cleric"}
for i,v in pairs(resc) do
  if v = "%2" then
    Send ("rescue "..v)</send>
  end -- match player
end -- search values
  </trigger>
</triggers>

This way you can capture pretty much anything and only rescue whoever is in your list. An added benefit is that you can define the resc variable somewhere else, and just have an alias set up to add names with table.insert( resc, "foo" ) and another to remove them, scanning for indexes and using table.remove( resc, index ). Then just have the value save state, and you can dynamically adjust them without having to muck about with your triggers each time.
Australia Forum Administrator #3
Quote:

Unless (?U) does something I am not familiar with (which is entirely possible).


It seems that (?U) turns on the "ungreedy" option for subsequent matches, and putting that at the start of the regexp makes sense.
Amended on Tue 10 Apr 2007 04:30 AM by Nick Gammon
Australia Forum Administrator #4
Quote:

If I change the variable 'to_rescue' to Warrior|Druid|Cleric for example, the trigger doesn't work.


Inside the help for editing a trigger is this note:


If you want to use variables inside regular expressions and not have special characters "fixed" by putting backslashes inside them, use the syntax:


@!variable


Thus you want:


match="(?U)^(?!You)(.*), fighting (?!you)(@!to_rescue).$"


USA #5
I thought the triggers weren't greedy to begin with. Either way, that's a neat trick that I'll have to try and remember.
USA #6
I put resc table in the default script.lau(nothing else is there anyway). So, for every char I want to have on the rescue list I can do it easily. So the send on the trigger is this:

for i,v in pairs(resc) do
if v == "%2" then
Send ("rescue "..v)
end
end

Can you explain what this is doing? As if I were a small child. :)

I think what I'm going to do from here is put this trigger and the multiple ones I need to make like it in each world I want to have a "rescuer_type" class and make an alias to toggle 'enable_trigger' on/off from a main window.

Thanks again.

p.s. About (?U). Yeaaa I didn't know what it meant when I saw it explained in this link: http://www.gammon.com.au/forum/bbshowpost.php?id=84 and I still don't. What is a greedy match? :) I just saw Nick used it in the example, sooo!
Australia Forum Administrator #7
For greedy/ungreedy matches, look at this post:

http://www.gammon.com.au/forum/bbshowpost.php?bbsubject_id=5089

Search for "greedy matches" on that page.

The default is greedy, as that post shows.
Australia Forum Administrator #8

for i,v in pairs(resc) do
  if v == "%2" then 
    Send ("rescue "..v)
  end
end


Quote:

Can you explain what this is doing? As if I were a small child. :)


This is iterating through the table "resc". As it is a numerically keyed table I would have used ipairs instead of pairs, but the result will be the same in this case.

For each table item (ie. each time through the 'for' loop) we get two things: the key of the table (i) and the table value (v).

In his example, the keys would simply be 1, 2, 3 so we don't use them further. The values will be "Warrior", "Druid", "Cleric" (one at a time).

So, he is checking if we get a match on the wildcard against one of those 3 words.
USA #9
thanks.

it's very useful. I was trying to get this exact same outcome when I first started this but I was trying to use a variable instead of a table. Using a table with loop is much easier.
USA #10
How do I disable/enable triggers by group?

/world.DisableTriggerGroup = ("Auto_Rescue")

This did something, not sure what. But the triggers grouped into Auto_Rescue are still enabled.

Also,

<triggers>
<trigger
enabled="y"
expand_variables="y"
group="Auto_Rescue"
match="(?U)^(?!You)(.*) stops fighting (/w+) opponent and turns to (/w+).$"
regexp="y"
sequence="100"
>
<send>for i,v in ipairs(resc) do
if v == "%3" then
Send ("rescue" ..v)</send>
</trigger>
</triggers>

I just want to double check this. Using %3 here is correct isn't it?

Also,

Match: (?U)^(?!You)(.*) stops fighting (/w+) opponent and turns to (?P<rescueme>/w+).$

and in send:
<send>for i,v in ipairs(resc) do
if v == "rescueme" then
Send ("rescue" ..v)</send>

Can I get something like this to work with the named wildcard.
Amended on Tue 10 Apr 2007 04:54 PM by ReallyCurious
USA #11
Enabling and disabling triggers are done with the same trigger.

EnableTriggerGroup( "foo", true ) enables the group foo
EnableTriggerGroup( "foo", false ) disables the group foo
USA #12
Thanks. This works great now with aliases to toggle the group on/off using world sends. I think I'm going to work on getting a script to do an 'inventory_check' and stock up on any supplies they need when in a town. This is going to be looking at more than one string from the mud though, I haven't done anything like that yet.

Australia Forum Administrator #13
Quote:



match="(?U)^(?!You)(.*) stops fighting (/w+) opponent and turns to (/w+).$"


I just want to double check this. Using %3 here is correct isn't it?


%3 is the third capture. If you are in doubt the best thing is to use named captures, like this:


match="(?U)^(?!You)(?P<mob>.*) stops fighting (?P<player>/w+) opponent and turns to (?P<otherplayer>/w+).$"


Now we have named the wildcards "mob", "player" and "otherplayer". Then you can refer to them in the body of the trigger like this:


if v == "%<player>" then


USA #14
Thanks.

On evaluating more than one string.

example:

You are carrying:
a big waterskin [2]
a loaf of bread
a small waterskin
a note with a brown feather
a small leather bag
a map
a long, black sword

I want to check this and make sure that 'a big waterskin [2]' is 'a big waterskin [5]', so I'd send directions to go buy the appropriate amount of skins to get to 5 if i had less or drop the necessary amount if i had more. Also, I want to check for items that might not be here, and if they aren't I'll go stock up on those too.

Any suggestions on the best way to approach something like this?
Australia Forum Administrator #15
One approach here is multi-line triggers. I suggest searching the forum for that, there have been a few posts about them.

Basically they work best if you have a well-defined "marker" at the start and end of the multiple lines. For example:


You are carrying:  <-- start marker
blah   
blah
blah
3 items.   <-- end marker


That way a multi-line regexp can match reliably. The end marker could be something less specific (like a prompt line), eg.


You are carrying:  <-- start marker
blah   
blah
blah

<28hp 105m 110mv>    <-- end marker


Or it can be something that the other lines don't have, eg.


You are carrying:  <-- start marker
  blah   
  blah
  blah

The Cathedral Altar  <-- end marker (not indented)
You are before the cathedral's altar.  Those of ill luck or poor faith often
come here to pray to their God for guidance, or chance.  An aisle leads back
south through the cathedral, while a large public board lies to the west.


In this example the inventory lines are indented, but normal lines are not. So, the multi-line trigger could look for a line that is not indented.

Whatever method you use, the matching text will now be a multiple-line string, that you can then parse and extract out individual info (like being short of water).

A handy way of iterating through a multi-line string is to use the getlines.lua extension supplied with MUSHclient.

Here is an example to get you started, although it isn't perfect:


require "getlines"

test = [[
You are carrying:
a big waterskin [2]
a loaf of bread
a small waterskin
a note with a brown feather
a small leather bag
a map
a long, black sword
]]

for line in getlines (test) do

  -- check water
  count = string.match (line, "a big waterskin %[(%d+)%]")
  if count and tonumber (count) < 5 then
    print "We are low on water"
  end -- match and too little water

end -- for


This uses the getlines iterator inside a for loop to read one line at a time. It then does a string.match to find a line with "a big waterskin [n]" in it.

However this particular one wouldn't handle "a big waterskin" on its own (presumably one of them), or having no waterskins at all. I'll leave you to play with that.
USA #16
I'm not sure how to use this.

Example:

The wooden surface of the chest is in perfect condition.
chest (carried) :
a cup of tea [2]
a cup of coffee [2]

I can set up a multi-line trigger to match: \AThe wooden surface of the chest is in perfect condition.
and then specify 4 lines for multi-lines.

But I'm not sure how to iterate this using getlines.

The trigger will capture those four lines into a string, but the string is going to be dynamic for each time I look into the chest so I'm not sure how to word the test = [[]]

Can you give me an example on how to use getlines with the example from above?

thanks
Amended on Wed 11 Apr 2007 08:28 AM by ReallyCurious
Australia Forum Administrator #17
Show us your trigger and let's take it from there. With the trigger matching you have a start point.
USA #18
<triggers>
<trigger
enabled="y"
expand_variables="y"
lines_to_match="4"
match="\AThe wooden surface of the chest is in perfect condition.(.*)"
multi_line="y"
regexp="y"
send_to="12"
sequence="100"
>
<send>require "getlines"
test = [[%1]]

count = string.match (line, "a cup of tea %[(%d+)%]")
if count and tonumber (count) &lt; 5 then
ColourNote ("white", "green", "LOW ON TEA!")
end


</send>
</trigger>
</triggers>


er, well, basically I was trying to do something like this. Define 'test' to equal every line afterwards using a wildcard.

The wooden surface of the chest is in perfect condition.
chest (carried) :
a cup of tea [2]
a cup of coffee [2]

And then I tried to changed match to:
\AThe wooden surface of the chest is in perfect condition.\n(?P<line1>.*)\n(?P<line2>.*)\n(?P<line3>.*)\z

At least the last one didn't give me an error :/ even though nothing happened.
Amended on Thu 12 Apr 2007 10:02 AM by ReallyCurious
USA #19
count = string.match (line, "a big waterskin %[(%d+)%]")

Also, about this line. %[(%d+)%] - Could this just be [/d+] ?


p.s. What's the syntax for 'does not equal'?

example:

for i,v in ipairs(resc) do
if v == "%2" then

I want if v 'does not equal' "%2" then


cheers
Amended on Thu 12 Apr 2007 10:27 AM by ReallyCurious
USA #20
bumpbump
USA #21
The Lua matching uses % instead of a \ for escaping characters. You need to escape brackets because they would normally define a list of valid characters. [A-Z] matches anything from "A" to "Z", while %[A-Z%] matches "[A-Z]" What you are trying to grab is within brackets, so you have to declare that the number is inside of it. The %d+ is the same as \d+ when declaring the trigger.

And ~= is does not equal.

if v ~= "%2" then
USA #22
Another quick observation. If there is only one cup of tea, then you will get an error, since the string.match will return a nil value. To fix that:

count = tonumber( string.match(line, "a cup of tea %[(%d+)%]") ) or 1
if count < 5 then
  ColourNote ("white", "green", "LOW ON TEA!")
end
Australia Forum Administrator #23
I have been experimenting with this a bit. Multi-line triggers are the devil to get right, but this is moving in the right direction.


<triggers>
  <trigger
   enabled="y"
   expand_variables="y"
   lines_to_match="4"
   match="\AThe wooden surface of the chest is in perfect condition\.(?s)(.*)"
   multi_line="y"
   regexp="y"
   send_to="12"
   sequence="100"
  >
  <send>
require "getlines"
test = [[%1]]

function get_count (s)
  local count = string.match (s, ".- %[(%d+)%]") or 1
  return tonumber (count)
end -- get_count 

local tea = 0
local coffee = 0

for line in getlines (test) do

  if string.match (line, "a cup of tea") then
    tea = get_count (line)
  end -- some tea

  if string.match (line, "a cup of coffee") then
    coffee = get_count (line)
  end -- some coffee 

end -- for


print ("We have", tea, "tea")

if tea &lt; 5 then
  ColourNote ("white", "green", "LOW ON TEA!")
end

print ("We have", coffee, "coffee")

if coffee &lt; 5 then
  ColourNote ("white", "green", "LOW ON COFFEE!")
end

</send>
  </trigger>
</triggers>




There were a number of problems with your initial one, for one thing it wasn't matching at all, on the subsequent lines, which you could see if you had printed out %1.

I have added the '(?s)' option to the regexp. This is "dot matches all" option. Without it, newlines are not considered a match for dot. With it added it considers everything, including newlines, for the wildcard.

Next, in processing tea and coffee, you need to allow for 3 possibilities:

  • Not mentioned --> that is, no tea at all
  • a cup of tea --> that is, a single cup (no number given)
  • a cup of tea [n] --> that is, more than one cup


Thus, the first thing I do, before the getlines loop, is to set tea and coffee counts to zero initially. If we never get a line about tea or coffee, then they stay zero.

Next, we look for a line matching "a cup of tea" or "a cup of coffee", without a number necessarily.

Then, if we get such a line we pass it to a function (get_count), which extracts out the number. If it doesn't find a number we assume a single item.

Finally, outside the loop, we test for having enough tea and coffee.
Australia Forum Administrator #24
You still have a problem here, that you are matching only 4 lines. If you get something like this:


The wooden surface of the chest is in perfect condition.
chest (carried) :
a fig leaf
a cup of tea [2]
a cup of coffee [2]


Then it will not notice the coffee because that is in the 5th line. The obvious thing to do is bump up the number of lines to match, but then you hit another problem.

Say you make it 50 lines, and then receive this:


The wooden surface of the chest is in perfect condition.
chest (carried) :
a fig leaf
a cup of tea [2]
a cup of coffee [2]

The Cathedral Altar
You are before the cathedral's altar.  Those of ill luck or poor faith often
come here to pray to their God for guidance, or chance.  An aisle leads back
south through the cathedral, while a large public board lies to the west.
Exits: south west.


Now the problem is that it won't match at all, because it hasn't received 50 lines yet.
Australia Forum Administrator #25
The ideal thing is to have something that marks the end of the inventory, and put that as part of the regular expression, and make a "tail match" rather than a "head match".

That is, rather than \A at the start of the regular expression put \Z at the end of it.

It would be nice if the inventory list finished with something you can rely on (like: 10 items).

If not, you might have to force something out. For example:


inventory
think End of inventory


Anything like that which will put a "marker" at the end of your inventory list.
USA #26
Wouldn't it just be easier to have a table stored in the world script space that you add to each time you get a line from the inventory? That way you could have a trigger matching on anything that doesn't look like an inventory item to turn it off. That's how I generally deal with this stuff, since I can't ever get multi line triggers to work quite right 100% of the time.
Australia Forum Administrator #27
All in all that is probably safer, and easier at the end of the day. Here is an example of doing that:


<triggers>

  <trigger
   match="(^&lt;)|^$"
   name="inventory_stopper"
   regexp="y"
   send_to="12"
   sequence="40"
  >
  <send>
-- don't need to track inventories any more
EnableTrigger ("inventory_line", false)
EnableTrigger ("inventory_stopper", false)

-- in case no table yet
inventory_table = inventory_table or {}

for _, item in ipairs (inventory_table) do
  print ("Found inventory item", item)
end -- for
</send>
  </trigger>

  <trigger
   match="*"
   name="inventory_line"
   send_to="12"
   sequence="50"
  >
  <send>
table.insert (inventory_table, "%0")  -- add inventory item to our table
</send>
  </trigger>

  <trigger
   enabled="y"
   match="The wooden surface of the chest is in perfect condition."
   send_to="12"
   sequence="100"
  >
  <send>
inventory_table = {}
EnableTrigger ("inventory_line", true)
EnableTrigger ("inventory_stopper", true)
</send>
  </trigger>

</triggers>



We have 3 triggers here. Two of them are initially not enabled.

The third one matches the start of an inventory list (in this case "The wooden surface of the chest is in perfect condition.". It empties out the inventory_table.

It enables the other two triggers. One is designed to capture inventory lines and add them to the table. The other is designed to detect the end of an inventory list. At present I have it set to detect blank lines, and lines starting with "<" (ie. a prompt).

All I am doing at present at the end of the inventory list is to list the items it has found, however this is the place to analyze the inventory and see what you have. This trigger also turns off the capture of inventory lines, so that we need an "inventory starter" to start the whole process off again.
Amended on Sun 15 Apr 2007 12:26 AM by Nick Gammon
USA #28
I tried out both ways and after a while of messin around I decided to stay without using getlines(had some trouble with it still after hours). This is working great now. So, using these three triggers I 'PRETTY MUCH' have a multi-line trigger. Here's the end result:



<triggers>
  <trigger
   expand_variables="y"
   match="^(\d+)H|^$"
   name="inventory_stopper"
   regexp="y"
   send_to="12"
   sequence="40"
  >
  <send>
-- don't need to track inventories any more
EnableTrigger ("inventory_line", false)
EnableTrigger ("inventory_stopper", false)

-- in case no table yet
inventory_table = inventory_table or {}

local tea = 0
local coffee = 0

function get_count (item)
  local count = string.match (item, ".- %[(%d+)%]") or 1
  return tonumber (count)
 end

for _, item in ipairs (inventory_table) do
       if string.match (item, "a cup of tea") then
    tea = get_count(item)
 end
       if string.match (item, "a cup of coffee") then
    coffee = get_count(item)

 end
end

print ("tea", tea)
print ("coffee", coffee)


</send>
  </trigger>
  <trigger
   enabled="y"
   match="The wooden surface of the chest is in perfect condition."
   send_to="12"
   sequence="100"
  >
  <send>
inventory_table = {}
EnableTrigger ("inventory_line", true)
EnableTrigger ("inventory_stopper", true)
</send>
  </trigger>
  <trigger
   match="*"
   name="inventory_line"
   send_to="12"
   sequence="50"
  >
  <send>
table.insert (inventory_table, "%0")  -- add inventory item to our table
</send>
  </trigger>
</triggers>


Thanks for the help.