Skip to main content

Creating an Auto-miner Using UORazor

·3651 words·18 mins·
Derek McCammond
Author
Derek McCammond
Senior Dev | Master’s in Software Engineering

Introduction
#

I am a long-time Ultima Online player. I have gone through the game during many of its periods, experienced the game before assistant programs were widely used, and I have also used many different macroing systems. This is what sparked my interest in computer science and ultimately what I cut my teeth on when I was first starting out. I wouldn’t suggest that anyone goes about it the way I did, especially since, for many servers (and many other games for that matter), what I was doing was considered cheating. Luckily the server that I was playing at the time, UOHybrid, did not consider macroing cheating so long as you were using an approved client assistant program and you were there at your PC watching it work.

One of the first client assistant programs that came out that got widely used (and, for some reason, still is used today even though there are other, better programs out there that replace the client completely with something that is not stuck at <30 FPS) is UORazor. It’s a program that is used to act as a MitM with the client and the server to allow for packets to be interpreted and injected by the assistant to allow for very basic automated actions to occur (think opening doors when you get close to them so you don’t have to manually open them. This is, after all, a game from the 90’s.)

While I had moved on to things like UOSteam to really take advantage of being able to type out scripts, having the ability to hold state in variables (revolutionary, I know), and other client-side niceties, a fair number stayed on UORazor and were screaming for help to deal with its shortcomings. One of the never-ending debates that came from the forums for UO were what assistant to use. This was largely a factor of what the server allowed, but, by and large, they were unable to detect (or simply didn’t care enough to programmatically track) someone doing otherwise.

During some of these debates on what clients people should be using, the topic of how capable they are of automation would inevitably be one of the factors. UOSteam and others were considered, and rightfully so, capable of more automation and thus considered cheater clients. One argument I usually posited was that, regardless of what is or is not cheating, I was having fun. Learning how to program, implementing it, and seeing things run was very rewarding. Using those experiences, I landed and excelled in a process automation developer role. Others were completely willing to overlook the fun aspect of what I was attempting to do, so I generally also would posit as an aside that UORazor was also likely capable of that same sort of full process automation as well. It is that statement of likelihood that I would like to address in this article and fully flesh out whether it is in fact possible or if I was making false claims.

Environment Setup
#

One thing I was not looking forward to was figuring out how best to set up my environment given that it is two old Windows programs working together. However, the process was made simple by using Lutris. I followed the instructions given in this Reddit thread. In case it goes down, they are as follows:

Steps to install Windows app using Lutris

(Posted by u/E3FxGaming on r/wine_gaming 2020/01/03)

Install Wine Version with Lutris:
#

Note: Alternatively if you’ve already installed something like Wine-staging on your PC you could theoretically skip this. You’ll always have the option to pick the Wine version installed on your system, however the versions Lutris offers also take advantage of fixes introduced by Proton, so you may want to follow this guide part even with Wine-staging installed.

  1. In Lutris use the three vertical dots in the top right corner > activate “Show Left Side Panel” if it’s deactivated
  2. Hover your mouse cursor over the “Wine” entry in the left side panel, there should be a download icon “Manage Versions” - click that.
  3. Tick the Wine version you want to use. It’ll download the Wine version - you can click the “OK” button to close the dialogue afterwards.

Install Game:
#

  1. Create a new folder somewhere under which the new wine prefix will be set-up.
  2. In Lutris in the top left corner click the plus icon > “Add Games…”
  3. Set the game name, choose “Wine (Runs Windows games)” as a runner (this is important because it changes the content of the “Game options” tab
  4. On the “Game options” tab select the executable you’ve got (the installer one)
  5. On the “Game options” tab select the Wine prefix (just the empty folder you created during step 1)
  6. On the “Runner options” tab make sure your desired Wine version is used.
  7. Click the “Save” button in the bottom right corner to close the dialogue. Select the newely created game entry in your Lutris library and use the “Play” button (right side panel - if not enabled you can enable it with the three vertical dots menu in the top right corner).
  8. Follow and complete the installation wizard.

Edit Stuff and Install Additional Patches:
#

  1. Right click on your newely created Lutris library entry > “Configure”.
  2. On the “Game options” tab point the executable entry towards the exe that would launch your game (should be in some sub-folder of the wine prefix folder you created). Do not point it to one of your patches, there is an easier way to install those than constantly editing your Lutris game configuration.
  3. Click the “Save” button in the bottom right corner to save your change and close the dialogue.
  4. Once more use the “Play” button to confirm your base game works. After confirming it close your game. (you can use the “Kill all Wine processes” option in the right side panel to ensure all Wine processes have ended)
  5. Eiter right click your game or use the right side panel > “Run EXE inside wine prefix” > choose your patch exe and follow the wizard instructions.

Congrats, game should be installed and patched, the “Play” button should still start the game like normal.

It was honestly very easy. I do not know why I have had issues in the past installing things outside of Steam because it was straightforward. I had a bit of an issue initially because UO.com now only ships a lite installer that does not initially do the updating necessary to get the client.exe and other files necessary to play. So I had to go in and run UO.exe to update to get the needed files. After that was done, it was simple to install UORazor and it automatically detected the version of UO installed within the container.

A re-look at UORazor’s macroing system
#

First thing first, I wanted to try to start fresh by going over the macroing system as if I were viewing it for the first time. Since the last time I opened the program, I have gone through an entire master’s program and have several years of development experience under my belt now.

Blank Macro Screen

The things I noticed
#

  • The macroing system does allow for typing in commands now, but albeit not in a way that is readily usable. It is still better to record actions, copy them out, and then paste them into the editor than trying to type them out.
  • It is incredibly hard to get all of the actions done in a way that it would be able to record all in one go, but that seems to be the way the editor wishes one to do things.
  • I tried to chain macros by calling one after another, but it seems to treat a macro call like a callback function.
    • This does mean however that that called macro can call another macro and they can be daisy-chained in such a way that they allow for the control flow to be passed along. Not much is lost using this method because we do not have the ability to have state-holding variables anyway.
    • One seriously irksome thing though with this is that, if you are using categories to try to keep your macros in a sane directory tree, those macros need to be placed into there respective categories first before being called in another macro otherwise it creates a duplicate of the called macro in the previous location where it existed.

Getting around limitations
#

Variables for holding state
#

Unfortunately the assistant does not have the ability to save any variables whatsoever. This is very bothersome for managing the locations that we are going to teleport to and ensuring that a progressive order is being kept.

The way that I found to get around this however is to keep a bundle of a specific item that is not used for this process in the main container space of my bank. This allows me to check its count and use that as a way to know, using the limited conditional statement availability, where to go next.

 1If there are no arrows,
 2  take from the main stack of arrows the maximum rune count number in bank box
 3  
 4Cast Recall
 5  
 6If count >= 16:
 7  teleport to rune 16
 8Else:
 9  If count >= 15:
10    teleport to rune 15
11  Else:
12    etc...
13  End If
14Enf If

Trying to make sure we are not overweight when teleporting back home
#

There really is not a good way to do this, even assistants like UOSteam were not great at handling this. The best thing to do would be to match the ore colors and combine them into their lightest type (It doesn’t make sense, but there is a small pile graphic that is much lighter.)

In order to deal with this, we will instead for-loop enough times that, even if we got the big piles every time, we would be at or below our max weight.

Getting the most ore back possible
#

When overweight, it might make sense to jettison some of the collected ore. We want to do that as little as possible. There is also the problem of leaving a vein not mined completely so we are not getting as much bang for our teleport. We have to deal with this limitation by analyzing the amount of loops we are doing and ensuring a good balance is struck.

Leaving as few seconds wasted per location as possible
#

There are very clearly areas where the macro is going to be slow, especially with recalling, ensuring that things are not stepping on each other as state is not held back while queued actions happen, and server latency is an issue. This is again not something with an elegant solution, it is just a matter of multiple runs and playing with times until things generally work most of the time.

Trying not to die/get caught by game masters
#

The best way that I have found to handle this is not with code but by ensuring the locations that the user goes to collect resources are in areas that do not have monsters nor any real foot traffic. These may even be in areas in town which rarely get looked at.

Dealing with in-game server saves
#

This unfortunately is one that is just going to be a pain and not one that we can really get around in a good way.

The way that I could imagine dealing with this is by pigeon holing the system message when it happens, teleporting to a safe location, and closing out of the game. Another process would be watching for the game client process and restart it if the game client closes with the last log messages being that a save was happening and not something like the player died or ran out of resources.

Resources suck and you run out of them quicker than you think
#

With RunUO’s buy agent, it is possible that a full cycle bot could be created so long as the player was flush with cash or was also placing items on a vendor to sell for money. That is pretty deep, usually the place I stopped because I had issues getting it to work correctly, so I am going to say that this is likely possible with a bunch of asterisks applied.

Z-Axis Targeting
#

For some reason, some of the time when targeting the ground it wants to take into consideration the Z-Axis which means that we may be targeting an area that is above or below the actual ground as the terrain is not flat. While the RunUO code does not appear to care, Razor apparently does. Some of the runes that were previously marked for other assistant programs may need to be re-marked in locations where the terrain is flat enough for Razor to register.

Runes with issues:
#

In the end after going through all 32 runes several times, I came up with a list of the ones that I had to make changes to and the reason why:

  • 3: Unhappy alligator
  • 8: Unable to target
  • 13: Unable to target
  • 15: Unable to target
  • 16: A harpy lives here, does not appreciate company

Solution Design
#

Having done some research into what is possible, it was time to put that work to use and figure out the best way to put those lessons learned into practice.

Pseudocode
#

 1while not dead
 2
 3  for each mining location
 4  
 5    put any ore in inventory in the bank  
 6
 7    check for resources and restock
 8    
 9    if we do not have enough resources
10      break with exception message
11        
12    teleport to location
13    
14    mine until the spot is empty or we are overweight
15    
16      if overweight, dump ore until at or under weight limit
17      
18      if our tools break, make a new one
19      
20    teleport back

Logical submacros
#

While-loops are not available in the code, at least not by name. We also cannot search for and for-loop over a range, so that does not allow us to deal with the runebook GUIs to be able to recall to different locations. Taking these considerations into mind from the previous research, I broke up the logic into several chunks:

  • Banking Ore
  • Restocking inventory
  • Teleporting to next location
  • Mining Ore
  • Teleporting back to the bank

The state machine diagram is as follows:

Submacro state machine

Implementation
#

Originally when trying to implement this years ago, I believe I had given up due to the constraints thinking that there was no real way around them, or, rightfully so, that, if I was going to achieve this feat, it was going to be with better tools.

Setbacks
#

I had completely forgotten about server saves until I loaded up the game and started taking inventory of the stuff that I had. As soon as the first one happened, I knew immediately that it was going to return and haunt me. What happens is, every so often, the server pauses and performs a save. This means that anything you are doing is paused, the server does not allow for any new input, and things are in a holding pattern until the save completes, anywhere from less than a second for a personal server to well over a minute in some cases for very large servers. In other assistants, they have the ability to read system log messages and conditionally handle them, which allows them to wait out saves until the save complete message is given. Not for us however, this is just going to be a thorn in my side the whole time.

Unfortunately, I believe they were having race conditions, the macro window did not know which scope I was in half the time, and it was just very clear that Razor was not intended nor really capable of handling what I was trying to do.

Workarounds
#

What I found is that there is a problem with the restock/organizer agents in that they continue on as though they spawned off another process to get them done. This means that the macros were continuing on even though there were item moves being added to a movement queue that was causing issues. This was smoothed over with waits, but it is not an elegant solution by any means.

To get around the macro window issue, I copy and pasted all of my working code into a single macro. Luckily it was a straight shot through, there were no real areas were things were interconnected except for the exception message I was giving if I ran out of resources.

Recording
#

Autominer bot running through some locations.

Code
#

UORazor Miner Bot Code
  1!Loop
  2Assistant.Macros.SpeechAction|0|52|3|ENU|2|16|2|Banco
  3Assistant.Macros.PauseAction|00:00:01
  4Assistant.Macros.HotKeyAction|0|Organizer Agent-1
  5Assistant.Macros.PauseAction|00:00:01
  6Assistant.Macros.WaitForStatAction|0|1|95|3600
  7Assistant.Macros.WaitForStatAction|1|1|95|3600
  8Assistant.Macros.HotKeyAction|0|Restock Agent-1
  9Assistant.Macros.AbsoluteTargetAction|0|0|214848|5272|3971|37|400
 10Assistant.Macros.PauseAction|00:00:01
 11Assistant.Macros.IfAction|50|0|0|Black Pearl
 12Assistant.Macros.HotKeyAction|0|Play: AutoMiner_SubMacros\AutoMiner_Exceptions\Exception_RanOutOfResources
 13Assistant.Macros.EndIfAction
 14Assistant.Macros.IfAction|50|0|0|Mandrake Root
 15Assistant.Macros.HotKeyAction|0|Play: AutoMiner_SubMacros\AutoMiner_Exceptions\Exception_RanOutOfResources
 16Assistant.Macros.EndIfAction
 17Assistant.Macros.IfAction|50|0|0|Blood Moss
 18Assistant.Macros.HotKeyAction|0|Play: AutoMiner_SubMacros\AutoMiner_Exceptions\Exception_RanOutOfResources
 19Assistant.Macros.EndIfAction
 20Assistant.Macros.DoubleClickAction|1074419899|3705
 21Assistant.Macros.PauseAction|00:00:01
 22Assistant.Macros.IfAction|50|0|0|Arrows
 23Assistant.Macros.HotKeyAction|0|Restock Agent-2
 24Assistant.Macros.AbsoluteTargetAction|0|0|1074419899|29|123|0|3705
 25Assistant.Macros.PauseAction|00:00:03
 26Assistant.Macros.EndIfAction
 27Assistant.Macros.PauseAction|00:00:01
 28Assistant.Macros.LiftTypeAction|3903|1
 29Assistant.Macros.LiftAction|1081626294|1|3903
 30Assistant.Macros.DropAction|0x400A58BB|(-1, -1, 0)|0
 31Assistant.Macros.PauseAction|00:00:01
 32Assistant.Macros.MacroCastSpellAction|32
 33Assistant.Macros.WaitForTargetAction|30
 34Assistant.Macros.IfAction|50|0|1|Arrows
 35Assistant.Macros.AbsoluteTargetAction|0|0|1074530688|45|66|0|7956
 36Assistant.Macros.ElseAction
 37Assistant.Macros.IfAction|50|0|2|Arrows
 38Assistant.Macros.AbsoluteTargetAction|0|0|1074527814|60|66|0|7956
 39Assistant.Macros.ElseAction
 40Assistant.Macros.IfAction|50|0|3|Arrows
 41Assistant.Macros.AbsoluteTargetAction|0|0|1074528518|75|66|0|7956
 42Assistant.Macros.ElseAction
 43Assistant.Macros.IfAction|50|0|4|Arrows
 44Assistant.Macros.AbsoluteTargetAction|0|0|1074529318|90|66|0|7956
 45Assistant.Macros.ElseAction
 46Assistant.Macros.IfAction|50|0|5|Arrows
 47Assistant.Macros.AbsoluteTargetAction|0|0|1074529671|106|66|0|7956
 48Assistant.Macros.ElseAction
 49Assistant.Macros.IfAction|50|0|6|Arrows
 50Assistant.Macros.AbsoluteTargetAction|0|0|1074528271|122|66|0|7956
 51Assistant.Macros.ElseAction
 52Assistant.Macros.IfAction|50|0|7|Arrows
 53Assistant.Macros.AbsoluteTargetAction|0|0|1074529940|139|66|0|7956
 54Assistant.Macros.ElseAction
 55Assistant.Macros.IfAction|50|0|8|Arrows
 56Assistant.Macros.AbsoluteTargetAction|0|0|1074527427|45|83|0|7956
 57Assistant.Macros.ElseAction
 58Assistant.Macros.IfAction|50|0|9|Arrows
 59Assistant.Macros.AbsoluteTargetAction|0|0|1074531668|61|82|0|7956
 60Assistant.Macros.ElseAction
 61Assistant.Macros.IfAction|50|0|10|Arrows
 62Assistant.Macros.AbsoluteTargetAction|0|0|1074529472|76|82|0|7956
 63Assistant.Macros.ElseAction
 64Assistant.Macros.IfAction|50|0|11|Arrows
 65Assistant.Macros.AbsoluteTargetAction|0|0|1074531101|90|82|0|7956
 66Assistant.Macros.ElseAction
 67Assistant.Macros.IfAction|50|0|12|Arrows
 68Assistant.Macros.AbsoluteTargetAction|0|0|1074529038|106|81|0|7956
 69Assistant.Macros.ElseAction
 70Assistant.Macros.IfAction|50|0|13|Arrows
 71Assistant.Macros.AbsoluteTargetAction|0|0|1074531976|122|81|0|7956
 72Assistant.Macros.ElseAction
 73Assistant.Macros.IfAction|50|0|14|Arrows
 74Assistant.Macros.AbsoluteTargetAction|0|0|1074527078|140|80|0|7956
 75Assistant.Macros.ElseAction
 76Assistant.Macros.IfAction|50|0|15|Arrows
 77Assistant.Macros.AbsoluteTargetAction|0|0|1074526834|45|100|0|7956
 78Assistant.Macros.ElseAction
 79Assistant.Macros.IfAction|50|0|16|Arrows
 80Assistant.Macros.AbsoluteTargetAction|0|0|1074531896|61|100|0|7956
 81Assistant.Macros.ElseAction
 82Assistant.Macros.IfAction|50|0|17|Arrows
 83Assistant.Macros.AbsoluteTargetAction|0|0|1082145013|124|96|0|7956
 84Assistant.Macros.ElseAction
 85Assistant.Macros.IfAction|50|0|18|Arrows
 86Assistant.Macros.AbsoluteTargetAction|0|0|1082145029|141|97|0|7956
 87Assistant.Macros.ElseAction
 88Assistant.Macros.IfAction|50|0|19|Arrows
 89Assistant.Macros.AbsoluteTargetAction|0|0|1082145041|45|114|0|7956
 90Assistant.Macros.ElseAction
 91Assistant.Macros.IfAction|50|0|20|Arrows
 92Assistant.Macros.AbsoluteTargetAction|0|0|1082145008|61|114|0|7956
 93Assistant.Macros.ElseAction
 94Assistant.Macros.IfAction|50|0|21|Arrows
 95Assistant.Macros.AbsoluteTargetAction|0|0|1082144978|76|114|0|7956
 96Assistant.Macros.ElseAction
 97Assistant.Macros.IfAction|50|0|22|Arrows
 98Assistant.Macros.AbsoluteTargetAction|0|0|1082144984|91|113|0|7956
 99Assistant.Macros.ElseAction
100Assistant.Macros.IfAction|50|0|23|Arrows
101Assistant.Macros.AbsoluteTargetAction|0|0|1082145010|108|113|0|7956
102Assistant.Macros.ElseAction
103Assistant.Macros.IfAction|50|0|24|Arrows
104Assistant.Macros.AbsoluteTargetAction|0|0|1082144985|124|112|0|7956
105Assistant.Macros.ElseAction
106Assistant.Macros.IfAction|50|0|25|Arrows
107Assistant.Macros.AbsoluteTargetAction|0|0|1082144973|141|113|0|7956
108Assistant.Macros.ElseAction
109Assistant.Macros.IfAction|50|0|26|Arrows
110Assistant.Macros.AbsoluteTargetAction|0|0|1082145026|45|129|0|7956
111Assistant.Macros.ElseAction
112Assistant.Macros.IfAction|50|0|27|Arrows
113Assistant.Macros.AbsoluteTargetAction|0|0|1082145017|61|129|0|7956
114Assistant.Macros.ElseAction
115Assistant.Macros.IfAction|50|0|28|Arrows
116Assistant.Macros.AbsoluteTargetAction|0|0|1082145015|75|129|0|7956
117Assistant.Macros.ElseAction
118Assistant.Macros.IfAction|50|0|29|Arrows
119Assistant.Macros.AbsoluteTargetAction|0|0|1082145018|90|129|0|7956
120Assistant.Macros.ElseAction
121Assistant.Macros.IfAction|50|0|30|Arrows
122Assistant.Macros.AbsoluteTargetAction|0|0|1082144989|108|129|0|7956
123Assistant.Macros.ElseAction
124Assistant.Macros.IfAction|50|0|31|Arrows
125Assistant.Macros.AbsoluteTargetAction|0|0|1082145031|122|129|0|7956
126Assistant.Macros.ElseAction
127Assistant.Macros.IfAction|50|0|32|Arrows
128Assistant.Macros.AbsoluteTargetAction|0|0|1082145049|141|129|0|7956
129Assistant.Macros.EndIfAction
130Assistant.Macros.EndIfAction
131Assistant.Macros.EndIfAction
132Assistant.Macros.EndIfAction
133Assistant.Macros.EndIfAction
134Assistant.Macros.EndIfAction
135Assistant.Macros.EndIfAction
136Assistant.Macros.EndIfAction
137Assistant.Macros.EndIfAction
138Assistant.Macros.EndIfAction
139Assistant.Macros.EndIfAction
140Assistant.Macros.EndIfAction
141Assistant.Macros.EndIfAction
142Assistant.Macros.EndIfAction
143Assistant.Macros.EndIfAction
144Assistant.Macros.EndIfAction
145Assistant.Macros.EndIfAction
146Assistant.Macros.EndIfAction
147Assistant.Macros.EndIfAction
148Assistant.Macros.EndIfAction
149Assistant.Macros.EndIfAction
150Assistant.Macros.EndIfAction
151Assistant.Macros.EndIfAction
152Assistant.Macros.EndIfAction
153Assistant.Macros.EndIfAction
154Assistant.Macros.EndIfAction
155Assistant.Macros.EndIfAction
156Assistant.Macros.EndIfAction
157Assistant.Macros.EndIfAction
158Assistant.Macros.EndIfAction
159Assistant.Macros.EndIfAction
160Assistant.Macros.EndIfAction
161Assistant.Macros.ForAction|13
162Assistant.Macros.IfAction|50|0|1|Tinker's Tools
163Assistant.Macros.PauseAction|00:00:00.7500000
164Assistant.Macros.DoubleClickTypeAction|7864|True
165Assistant.Macros.WaitForGumpAction|949095101|True|300
166Assistant.Macros.GumpResponseAction|23|0|0
167Assistant.Macros.WaitForGumpAction|949095101|True|300
168Assistant.Macros.GumpResponseAction|0|0|0
169Assistant.Macros.EndIfAction
170Assistant.Macros.PauseAction|00:00:00.2500000
171Assistant.Macros.IfAction|50|0|0|Pickaxe
172Assistant.Macros.PauseAction|00:00:01.5000000
173Assistant.Macros.DoubleClickTypeAction|7864|True
174Assistant.Macros.WaitForGumpAction|949095101|True|300
175Assistant.Macros.GumpResponseAction|114|0|0
176Assistant.Macros.WaitForGumpAction|949095101|True|300
177Assistant.Macros.GumpResponseAction|0|0|0
178Assistant.Macros.EndIfAction
179Assistant.Macros.DoubleClickTypeAction|3718|True
180Assistant.Macros.WaitForTargetAction|30
181Assistant.Macros.TargetRelLocAction|0|-1
182Assistant.Macros.PauseAction|00:00:00.9500000
183Assistant.Macros.EndForAction
184Assistant.Macros.MacroCastSpellAction|32
185Assistant.Macros.WaitForTargetAction|30
186Assistant.Macros.AbsoluteTargetAction|0|0|1086580354|45|66|0|3834

Analytics
#

While static analysis is impractical and code quality is lacking, analysis can still be performed on the output of the process to ensure that it is functioning correctly and within a reasonable timeframe.

Methodology
#

While in-game monitoring was only going to allow me to measure things in the aggregate, I opted to have the in-game logs output to a file and analyze the results that way. It was able to give me per-teleport and per-run numbers.

In order to measure a full run’s worth of material, I cleared out my ore bag, counted my starting resource amounts, and run the process again after modifying the code such, when it would replenish my arrow stack to start over again, it instead stops with a message saying that the run was complete.

Tooling
#

The tools used to perform the analysis were the standard UO client, UORazor, and linux-based commandline tools (grep, wc, etc.) along with Vim.

Changes to Process
#

  • In order to have data to analyze, the client needed to be configured to output the log file which required some configuration changes.
  • We also should have done a single click on the arrow type in the backpack to be able to know which spot was being mined.

Results
#

Starting Resources:
#

  • Iron Ingots: 9141
  • Black Pearl: 826
  • Mandrake Root: 1238
  • Blood Moss: 1188

Ending Resources:
#

  • Iron Ingots: 9085
  • Black Pearl: 638
  • Mandrake Root: 1052
  • Blood Moss: 1000

Mining Outcomes:
#

  • Iron: 628
  • Shadow: 492
  • Failed to mine: 79
  • Resources depleted: 22

Ore Mined:
#

Iron: 4194 Shadow: 3344 Gold: 76

All ore was converted down into the smallest ore piles *

Tools Broken:
#

  • Pickaxes: 14

Conclusion
#

Overall assessment
#

Due to the new achievement system, I was working with a clean slate. By the time I felt like I had a working script done, I was just passed 20k ore mined, which is quite an achievement and something that would have taken some time to mine by hand.

I feel accomplished, like I had gotten something done that was nagging me in the back of my mind for a while. Although not perfect, I feel like I have proven within an afternoon that this is possible, additional polish may be applied to get it working even better, but what I have presented works.

You know that what you made works when it is the case that you feel comfortable taking your eyes off of it. During the results run, I was able to have the process run for nearly two hours without intervention before an errant lag spike caused it to trip up.

Things that could be better
#

Teleporting
#

One of the slowest parts of the bot is the selection for which spot to teleport next. What I did works, but the nested if statements are all parsed in order so there is a significant lag as it goes through all of the Ifs and then the End Ifs. The way that I could think of doing this that will scale is to do a binary search using submacros. This will get around most of the nested If’s where possible.

Locations
#

I was using the books and spots that I had. However, these were not necessarily the best spots. I do not know if the two books I had were not repeats.

Applicability
#

While I did this specifically for mining ore, the technique could readily be applied to other types of resources. I am thinking specifically lumberjacking as a potential candidate.

Suggestible?
#

In no way would I suggest someone do this. Not only is it potentially not allowed by the server that one is playing on, it also is just silly. There are other clients, other means of doing the same thing. This was, as stated in the introduction, merely to show it is possible.