Register forum user name Search FAQ

Gammon Forum

Notice: Any messages purporting to come from this site telling you that your password has expired, or that you need to verify your details, confirm your email, resolve issues, making threats, or asking for money, are spam. We do not email users with any such messages. If you have lost your password you can obtain a new one by using the password reset link.
 Entire forum ➜ MUSHclient ➜ Getting Started ➜ Making a script to count mobs killed

Making a script to count mobs killed

It is now over 60 days since the last post. This thread is closed.     Refresh page


Pages: 1 2  3  

Posted by Nick Gammon   Australia  (23,102 posts)  Bio   Forum Administrator
Date Tue 08 Jan 2008 04:55 AM (UTC)
Message

This tutorial will show, with screen captures, the general process involved in making a script to count something useful (like the number of times you have killed various mobs), and how to turn that into a plugin.

We will start by getting an example message up from the MUD, and then Shift+Double-click to select the entire line:

Copying message

It is best to copy an actual message so we get the spelling right, and the spacing. The next thing to do is make a trigger that matches this message, so we press Alt+Enter to enter world configuration, and select the Trigger section, and click Add to make a new trigger:

Add a trigger

Now we paste in the death message as the trigger match field:

Trigger text

However the mob name will vary, so we need to replace its name with an asterisk, to make it a wildcard. Then to make sure we see it matching I changed the colour to Custom2, and the "send to" field to "Script". Finally we click on the little button that lets us edit the script in the "Send" box:

Make a wildcard

Inside the send text, a small Lua script is used to count the number of mobs we have seen killed. The first line makes the table "killed_mobs" if it doesn't already exist. Then we add this mob to the table, if it isn't there already. After that we add 1 to the count, and make a note of the time it was killed.:

Trigger script

If you want to copy and paste the code, here it is in text form:



killed_mobs = killed_mobs or {}  -- make mobs table

mob_name = "%1"  -- this mob's name (first wildcard)

-- add this mob if first time

killed_mobs [mob_name] = killed_mobs [mob_name] or { count = 0 }

-- add 1 to count of mobs
killed_mobs [mob_name].count = killed_mobs [mob_name].count + 1

-- remember when we last killed it
killed_mobs [mob_name].last_time = os.time ()


Next, to test it we kill a few mobs, and see if the script is working. First we should see the "death" line in yellow, as that shows the trigger has matched. To see if the Lua table is being built correctly I make sure that the scripting prefix is a slash (and to help with error messages I checked "note errors"):

Scripting configuration

Now we can use a slash as a script prefix to load the "tprint" module:

Require tprint

With tprint (table print) loaded we can use it to print the killed_mobs table:

Show mobs table

So far, so good. The table is showing the mobs I killed, how many times, and when. Let's make an alias to display the information neatly. First go into world configuration and add an alias:

Add an alias

I will make up a name for it (show_killed) but you can call it anything you like. It will call a script so I change the "send to" field to "script" (like for the trigger) and click the button to edit the script field:

Edit alias

Now we make a small script that goes through each entry in the table, and lists the mob name, count and date formatted nicely:

Alias script

If you want to copy and paste the code, here it is in text form:



if not killed_mobs or next (killed_mobs) == nil then
  ColourNote ("white", "blue", "No mobs killed yet")
  return
end -- if nothing

-- go through each one

count = 0
for k, v in pairs (killed_mobs) do
  Note (string.format ("%%-30s x %%i (last at %%s)",
        k, 
        v.count,
        os.date ("%%H:%%M %%d %%b %%Y", v.last_time)))
  count = count + v.count
end -- for loop

-- show total

Note (string.format ("%%5i mobs killed.", count))


I have doubled the number of % symbols used in string.format and os.date because in the "send" box MUSHclient tries to treat % followed by something as a wildcard, so you need to use %% for a single % to be sent to the script engine. Once we have got our alias entered we can test it:

Test the alias

Time to turn it into a plugin. First I use the File menu to start the Plugin Wizard. First we enter the plugin name, purpose, author and required version of MUSHclient:

Plugin wizard - general

Next we put in a simple description, in case the player clicks on the "Show Info" button in the plugins list:

Plugin wizard - description

Next, we move to the Triggers tab. In my case there is only my trigger there, but if you had others you would click "Select None" and then click to select any required triggers:

Plugin wizard - triggers

Now, we move to the Aliases tab. In my case there is only my alias there, but if you had others you would click "Select None" and then click to select any required aliases:

Plugin wizard - aliases

I skipped the Timers tab, as this script does not use timers, and move onto Variables. An important thing to do here is check "Retain State" as this lets the plugin save and restore variables, which is how we remember the mob count from one session to the next:

Plugin wizard - variables

Finally we move to the Script tab. I don't use any "standard constants" so "Include Standard Constants" can be unchecked. Then click Edit to add a bit more scripting:

Plugin wizard - script

What we need to do now is add two functions: OnPluginInstall and OnPluginSaveState. The first is called when the plugin is loaded, the second when the plugin is saving its state file. We "require" the Serialize module, which is a helper module for saving the plugin's variables as a string. Then we make an empty killed_mobs table, in case this is the first time the plugin is run. Finally we use loadstring to convert any variables serialized earlier into the Lua table. At plugin save time we use serialize.save_simple to convert the Lua table back into a string:

Edit plugin script

If you want to copy and paste the code, here it is in text form:



-- on plugin install, convert variable into Lua table
function OnPluginInstall ()
  require "serialize"  -- needed to serialize table to string
  killed_mobs = {}  -- ensure table exists, if not loaded from variable
  assert (loadstring (GetVariable ("killed_mobs") or "")) ()
end -- function OnPluginInstall

-- on saving state, convert Lua table back into string variable
function OnPluginSaveState ()
  SetVariable ("killed_mobs", "killed_mobs = " ..
               serialize.save_simple (killed_mobs))
end -- function OnPluginSaveState


If you check out the state file (double-click the RH mouse button on the plugin name in the plugins list) after using the plugin you will see something like this:



<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE muclient>
<!-- Saved on Tuesday, January 08, 2008, 3:42  -->
<!-- MuClient version 4.20 -->
<!-- Written by Nick Gammon -->
<!-- Home Page: http://www.mushclient.com/ -->

<!-- Plugin state saved. Plugin: "Count_Mobs_Killed". World: "Realms of Depair". -->

<muclient>

<!-- variables -->

<variables
   muclient_version="4.20"
   world_file_version="15"
   date_saved="2008-01-08 15:42:07"
  >
  <variable name="killed_mobs">killed_mobs = {
  ["A large grey rat"] = {
    count = 1,
    last_time = 1199767236,
    },
  ["A Fire Ant"] = {
    count = 1,
    last_time = 1199767275,
    },
  }</variable>
</variables>
</muclient>


Now we can hit the Create button to make our new plugin:

Create plugin

We can accept the suggested name for the plugin:

Plugin name

Now to test it! Go to the File Menu, select Plugins, and you will see something like this:

Plugin list

Choose our newly created plugin file:

Choose plugin

It now appears in the list of plugins:

Plugin list with new plugin

We can now type "show_killed" - this time it should report "No mobs killed yet" because each plugin has its own script space, separate from the main world script space. However we can soon kill a few more mobs and the show_killed alias should now work. Also we can close and re-open the world and it will remember the mobs we killed last time, thanks to the script to save and restore the killed_mobs table.


- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,102 posts)  Bio   Forum Administrator
Date Reply #1 on Tue 08 Jan 2008 04:58 AM (UTC)

Amended on Wed 19 Aug 2009 03:54 AM (UTC) by Nick Gammon

Message
Below is how the entire plugin looks, if you edit the plugin file.

Template:saveplugin=Count_Mobs_Killed To save and install the Count_Mobs_Killed plugin do this:
  1. Copy between the lines below (to the Clipboard)
  2. Open a text editor (such as Notepad) and paste the plugin into it
  3. Save to disk on your PC, preferably in your plugins directory, as Count_Mobs_Killed.xml
  4. Go to the MUSHclient File menu -> Plugins
  5. Click "Add"
  6. Choose the file Count_Mobs_Killed.xml (which you just saved in step 3) as a plugin
  7. Click "Close"



<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE muclient>
<!-- Saved on Tuesday, January 08, 2008, 3:26  -->
<!-- MuClient version 4.20 -->

<!-- Plugin "Count_Mobs_Killed" generated by Plugin Wizard -->

<muclient>
<plugin
   name="Count_Mobs_Killed"
   author="Nick Gammon"
   id="f11e3bb0e48d526152798439"
   language="Lua"
   purpose="Counts how many mobs I have killed"
   save_state="y"
   date_written="2008-01-08 15:17:18"
   requires="4.00"
   version="1.0"
   >
<description trim="y">
<![CDATA[
Counts how many mobs have been killed.

Type "show_killed" to see a count.
]]>
</description>

</plugin>


<!--  Triggers  -->

<triggers>
  <trigger
   custom_colour="2"
   enabled="y"
   match="* is DEAD!!"
   send_to="12"
   sequence="100"
  >
  <send>killed_mobs = killed_mobs or {}  -- make mobs table

mob_name = "%1"  -- this mob's name (first wildcard)

-- add this mob if first time

killed_mobs [mob_name] = killed_mobs [mob_name] or { count = 0 }

-- add 1 to count of mobs
killed_mobs [mob_name].count = killed_mobs [mob_name].count + 1

-- remember when we last killed it
killed_mobs [mob_name].last_time = os.time ()

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

<!--  Aliases  -->

<aliases>
  <alias
   match="show_killed"
   enabled="y"
   send_to="12"
   sequence="100"
  >
  <send>if not killed_mobs or next (killed_mobs) == nil then
  ColourNote ("white", "blue", "No mobs killed yet")
  return
end -- if nothing

-- go through each one

count = 0
for k, v in pairs (killed_mobs) do
  Note (string.format ("%%-30s x %%i (last at %%s)",
        k, 
        v.count,
        os.date ("%%H:%%M %%d %%b %%Y", v.last_time)))
  count = count + v.count
end -- for loop

-- show total

Note (string.format ("%%5i mobs killed.", count))</send>
  </alias>
</aliases>

<!--  Script  -->


<script>
<![CDATA[

-- on plugin install, convert variable into Lua table
function OnPluginInstall ()
  require "serialize"  -- needed to serialize table to string
  killed_mobs = {}  -- ensure table exists, if not loaded from variable
  assert (loadstring (GetVariable ("killed_mobs") or "")) ()
end -- function OnPluginInstall

-- on saving state, convert Lua table back into string variable
function OnPluginSaveState ()
  SetVariable ("killed_mobs", "killed_mobs = " ..
               serialize.save_simple (killed_mobs))
end -- function OnPluginSaveState
]]>
</script>


<!--  Plugin help  -->

<aliases>
  <alias
   script="OnHelp"
   match="Count_Mobs_Killed:help"
   enabled="y"
  >
  </alias>
</aliases>

<script>
<![CDATA[
function OnHelp ()
  world.Note (world.GetPluginInfo (world.GetPluginID (), 3))
end
]]>
</script> 

</muclient>



You can see that the trigger and alias are near the top, and the script stuff follows.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Grug   (15 posts)  Bio
Date Reply #2 on Sun 25 Jan 2009 02:32 PM (UTC)
Message
So here's a question...

How do you add to that count?

Like say, add 50 lizards to the kill count, that you haven't killed, without spamming?
Top

Posted by Nick Gammon   Australia  (23,102 posts)  Bio   Forum Administrator
Date Reply #3 on Sun 25 Jan 2009 07:45 PM (UTC)

Amended on Sun 25 Jan 2009 07:48 PM (UTC) by Nick Gammon

Message
Add another alias to the plugin. For example, just before the line with "<!-- Script -->" in it, paste this:


<aliases>
  <alias
   match="^killed (-?\d+) (.*)$"
   enabled="y"
   regexp="y"
   send_to="12"
   sequence="100"
  >
  <send>

killed_mobs = killed_mobs or {}  -- make mobs table

mob_name = "%2"  -- this mob's name (second wildcard)

-- add this mob if first time

killed_mobs [mob_name] = killed_mobs [mob_name] or { count = 0 }

-- add to count of mobs (first wildcard)
killed_mobs [mob_name].count = killed_mobs [mob_name].count + %1

-- remember when we last killed it
killed_mobs [mob_name].last_time = os.time ()

Note (string.format ('Added kill of %1 x "%2" to mob count (total now %%d).',
      killed_mobs [mob_name].count))

</send>
  </alias>
</aliases>



Now you type "killed <count> <mobname>", like this:


killed 20 kobold
Added kill of 10 x "kobold" to mob count (total now 30).


You need to get the spelling and capitalization exactly right, or it will add a different mob to the table. (eg. if you said "killed 20 kobolds" it would add it under "kobolds" not "kobold").

If you make a mistake, you can use a negative number, eg.


killed -20 kobold
Added kill of -20 x "kobold" to mob count (total now 10).


- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Grug   (15 posts)  Bio
Date Reply #4 on Sun 25 Jan 2009 09:04 PM (UTC)
Message
Awesome help, thank you!

Definitely recommend this counter script to everyone. Really easy to customize and use for ANYTHING you can think of.
Top

Posted by Chyort   USA  (58 posts)  Bio
Date Reply #5 on Fri 06 Feb 2009 11:09 PM (UTC)
Message
Mostly what im trying to do is just get my toes wet with scripting in general... and after looking around i figured this thread was a decent place to start. But im more interested in tracking how often skills hit/miss. than number of kills/time

I had some success when i first started playing around with it. But after closing the window and reopening it. I started getting errors. Below are the 2 triggers im playing around with, and the error message.

Run-time error
World: Erehwonmai
Immediate execution
[string "Trigger: "]:7: attempt to perform arithmetic on field 'miss' (a nil value)
stack traceback:
[string "Trigger: "]:7: in main chunk

<triggers>
<trigger
enabled="y"
keep_evaluating="y"
match="([A-Za-z]+) says \'hit\'$"
regexp="y"
send_to="12"
sequence="100"
>
<send>player_stats = player_stats or {}

player_name = "%1"

player_stats [player_name] = player_stats [player_name] or { hit = 0 }

player_stats [player_name].hit = player_stats [player_name].hit + 1</send>
</trigger>
<trigger
enabled="y"
keep_evaluating="y"
match="([A-Za-z]+) says \'miss\'$"
regexp="y"
send_to="12"
sequence="100"
>
<send>player_stats = player_stats or {}

player_name = "%1"

player_stats [player_name] = player_stats [player_name] or { miss = 0 }

player_stats [player_name].miss = player_stats [player_name].miss + 1</send>
</trigger>
</triggers>
Top

Posted by Nick Gammon   Australia  (23,102 posts)  Bio   Forum Administrator
Date Reply #6 on Sat 07 Feb 2009 12:37 AM (UTC)
Message
OK, your problem here is, if the first thing that happens is a hit, you set up a default entry for that player.

For example:


Nick says 'hit'

/require "tprint";tprint (player_stats)

"Nick":
  "hit"=1



However you haven't set up miss to be zero, so when you get your first miss, it doesn't create miss = 0, because there is now an entry for Nick.

So for either case, hit or miss, you need to set up both hit and miss to be zero, like this:


<triggers>
  <trigger
   enabled="y"
   keep_evaluating="y"
   match="([A-Za-z]+) says \'hit\'$"
   regexp="y"
   send_to="12"
   sequence="100"
  >
  <send>
player_stats = player_stats or {}

player_name = "%1"

player_stats [player_name] = player_stats [player_name] or 
    { hit = 0, miss = 0 }

player_stats [player_name].hit = player_stats [player_name].hit + 1
</send>
  </trigger>
  <trigger
   enabled="y"
   keep_evaluating="y"
   match="([A-Za-z]+) says \'miss\'$"
   regexp="y"
   send_to="12"
   sequence="100"
  >
  <send>
player_stats = player_stats or {}

player_name = "%1"

player_stats [player_name] = player_stats [player_name] or 
    { hit = 0, miss = 0 }

player_stats [player_name].miss = player_stats [player_name].miss + 1
</send>
  </trigger>
</triggers>



- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,102 posts)  Bio   Forum Administrator
Date Reply #7 on Sat 07 Feb 2009 12:41 AM (UTC)

Amended on Sat 07 Feb 2009 12:42 AM (UTC) by Nick Gammon

Message
An alternative way, which is probably more flexible is to do the "or" when adding, that way you don't have to make the table construction handle all the future things you might add. Like this:


<triggers>
  <trigger
   enabled="y"
   keep_evaluating="y"
   match="([A-Za-z]+) says \'hit\'$"
   regexp="y"
   send_to="12"
   sequence="100"
  >
  <send>
player_stats = player_stats or {}

player_name = "%1"

player_stats [player_name] = player_stats [player_name] or {}

player_stats [player_name].hit = (player_stats [player_name].hit or 0) + 1
</send>
  </trigger>
  <trigger
   enabled="y"
   keep_evaluating="y"
   match="([A-Za-z]+) says \'miss\'$"
   regexp="y"
   send_to="12"
   sequence="100"
  >
  <send>
player_stats = player_stats or {}

player_name = "%1"

player_stats [player_name] = player_stats [player_name] or {} 

player_stats [player_name].miss = (player_stats [player_name].miss or 0) + 1
</send>
  </trigger>
</triggers>



Now we simply create an empty table if necessary, and then when adding, do "or 0" to take into account that this might be the first time. The parentheses are necessary to make it evaluate correctly.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,102 posts)  Bio   Forum Administrator
Date Reply #8 on Sat 07 Feb 2009 12:50 AM (UTC)

Amended on Sat 07 Feb 2009 12:52 AM (UTC) by Nick Gammon

Message
And if you want to get fancy, and - hey - why not?, then use a metatable on the player entry, that returns 0 for any missing item. This may or may not be what you really want, but this lets you handle the first zero entry a bit more naturally.


<triggers>
  <trigger
   enabled="y"
   keep_evaluating="y"
   match="([A-Za-z]+) says \'hit\'$"
   regexp="y"
   send_to="12"
   sequence="100"
  >
  <send>

player_stats = player_stats or {}

player_name = "%1"

player_stats [player_name] = player_stats [player_name] or 
              setmetatable ( {}, { __index = function () return 0 end } )

player_stats [player_name].hit = player_stats [player_name].hit + 1

</send>
  </trigger>
  <trigger
   enabled="y"
   keep_evaluating="y"
   match="([A-Za-z]+) says \'miss\'$"
   regexp="y"
   send_to="12"
   sequence="100"
  >
  <send>

player_stats = player_stats or {}

player_name = "%1"

player_stats [player_name] = player_stats [player_name] or 
              setmetatable ( {}, { __index = function () return 0 end } )

player_stats [player_name].miss = player_stats [player_name].miss + 1

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


The metatable here consists of an entry "__index = function () return 0 end".

What that does is, if you attempt to access any missing item in the table, it returns zero. Thus, you can then add to it.


- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Chyort   USA  (58 posts)  Bio
Date Reply #9 on Sat 07 Feb 2009 01:07 AM (UTC)

Amended on Sat 07 Feb 2009 01:15 AM (UTC) by Chyort

Message
Nice, not 1, but 3 examples :P Thanks... I'll keep poking around with it, and possibly beg for more help later as i build/expand on it
Top

Posted by Chyort   USA  (58 posts)  Bio
Date Reply #10 on Sat 07 Feb 2009 06:02 PM (UTC)
Message
Second question, how do i go about making a output alias for this?

I can mostly follow whats going on in the example, but i dont know how to modify it to do what i want it to.

Something like this.


Name        Hit    Miss
Player1     2      2
Player2     2      1
Total       4      3


and the more commented any example might be, the better :P so i can see exactly whats going on/why.
Top

Posted by Nick Gammon   Australia  (23,102 posts)  Bio   Forum Administrator
Date Reply #11 on Sat 07 Feb 2009 09:57 PM (UTC)
Message

-- test data

player_stats = {
  Nick = { hit = 5, miss = 10 },
  Chyort  = { hit = 4, miss = 20 },
  Bruce = { hit = 2, miss = 1 },
  Larissa = { hit = 40, miss = 3},
}

-- zero totals
local total_hit, total_miss = 0, 0

-- heading
Note (string.format ("%-15s %5s %5s", "Name", "Hit", "Miss"))

-- so names appear in alphabetic order
require "pairsbykeys"

-- for each player
for name, stats in pairsByKeys (player_stats) do

  -- show stats
  Note (string.format ("%-15s %5d %5d", name, stats.hit, stats.miss))

  -- add to total
  total_hit = total_hit + stats.hit
  total_miss = total_miss + stats.miss
end -- for

-- show totals
Note (string.format ("%-15s %5d %5d", "Total", total_hit, total_miss))


Displays:


Name              Hit  Miss
Bruce               2     1
Chyort              4    20
Larissa            40     3
Nick                5    10
Total              51    34



- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Chyort   USA  (58 posts)  Bio
Date Reply #12 on Sat 07 Feb 2009 11:45 PM (UTC)
Message
have to double up the %'s but i saw that earlier when i was browsing the forums. thanks again.

now to play around with it a bit. :)
Top

Posted by WizardsEye   (24 posts)  Bio
Date Reply #13 on Mon 23 Feb 2009 04:40 PM (UTC)
Message
Hmm, I'm a bit different I guess, Keeping track is good, but wouldn't it be nice to be able to use a command to wipe out the data and start over fast? I like to keep a weeks count myself. That way, I can track my progress for a week and know on friday if I need to catch back up.
Top

Posted by Nick Gammon   Australia  (23,102 posts)  Bio   Forum Administrator
Date Reply #14 on Mon 23 Feb 2009 06:56 PM (UTC)
Message
Another simple alias should do that:


<aliases>
  <alias
   match="reset_mob_counts"
   enabled="y"
   send_to="12"
   sequence="100"
  >
  <send>

killed_mobs =  {}  -- clear mobs table

</send>
  </alias>
</aliases>



- Nick Gammon

www.gammon.com.au, www.mushclient.com
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.


121,127 views.

This is page 1, subject is 3 pages long: 1 2  3  [Next page]

It is now over 60 days since the last post. This thread is closed.     Refresh page

Go to topic:           Search the forum


[Go to top] top

Information and images on this site are licensed under the Creative Commons Attribution 3.0 Australia License unless stated otherwise.