SRL6 Script Walkthrough
Introduction
My goal is to help people looking for some basic exposure and introduction to coding scripts for Runescape3 using the SRL6 library. The main focus will be on SRL6 usage within the script(s). We will only be looking at a small subset of the library functionality. Hopefully enough foundation and concepts will be shared to enable you to branch into the other areas independently.
This paper will walk through developing a script that makes pastry dough at a bank chest. The program will be presented in stages with discussion about SRL usage in the presented code.
Forgive me if I refer to calls as SRL when some of them actually come from the base Simba program. If you can't find a function in the SRL documentation check the Simba documentation to see if it is inherited.
Scripts will have a download link so you can try them out. It may be best to read the paper with the code opened up in another window for reference.
Audience
The target audience for this paper is familiar with basic programming concepts and hopefully with the Lape language used in SRL6. Seasoned SRL5 scripters may not find much value, especially if you have already worked through the basic gist of SRL6.
Novice programmers may be better served by starting with other tutorials here on villavu or elsewhere. Lape is very similar to Pascal so these online tutorials might be of help to beginning programmers:
Suggested Prerequisites
First off, you need to have Simba, SMART, and SRL6 installed and working! (Guide)
Any scripter should be familiar with two tools and concepts.
- The Auto Color Aid tool is used for choosing colors for your script.
- The DTM editor is built into simba (you need to enable it in the View->Extensions menu). There are many DTM tutorials in the Scripting Help and Tutorials forums.
@YoHoJo; made some videos covering ACA and DTMs. I recommend these to anyone needing to learn more.
Other links:
A key concept
Many of the SRL include files use an object oriented aproach. They define a type to hold the data stucture needed to manage the object. Then they implement a number of functions that work on the type to perform the task at hand.
They often define an instance of this type, a global variable that you will use in your code to work with that datatype.
When you look in the documentation you will see the function listed as type.function() but you will use it as globalvar.function(). This is true for most things that would only have one instance like an inventory, minimap, or bankscreen.
For example, let's say we want to set the minimap to point south. The minimap documentation lists a function for that:
Simba Code:
function TRSMinimap.setAngle(angleDegrees: integer): boolean;
Near the top of the minimap doc page, you will find "var minimap". This is the global TRSMinimap variable that SRL defined for us.
Simba Code:
var minimap
The variable that holds all minimap information. It used when calling any functions in this file.
So, to implement our call, we call setAngle on the minimap variable:
Simba Code:
minimap.setAngle(MM_DIRECTION_SOUTH);
Some types are more generic, like a TPoint, and you will make your own variable to work with. There are no defined global instances.
I am posting a listing of these globals after the main article.
Phase 0: Using SRL to open a Runescape window
This is the most basic script. It opens a SMART window and starts a Runscape session.
You can use this script to play the game in the SMART window instead of a web browser. Run the script, wait for RS to load, then press the "Disable SMART" button. This takes mouse and keyboard control away from Simba and puts them back in control of the user.
Then you can log in and play manually.
Simba Code:
program new;
{$DEFINE SMART}
{$i srl-6/srl.simba}
begin
clearDebug();
setupSRL();
end.
SRL usage in this script:
Setting this flag indicates you will use SMART as your browser for RS. It must be defined before including the SRL library.
Without it you need to use the crosshair icon in Simba to choose another program that is running the RS client. As far as I know this is not currently supported on SRL6.
This line includes the SRL library files into your script. Include files are located in SIMBADIR/Includes. You should become familiar with these script libraries.
Most of the code is brief and understandable. This is an early release of a rewritten library so don't be suprised if you bump into a few glitches along the way. It would be helpful if you try to figure out the root cause. Detailed bug reports are a lot more helpful than "xyz doesn't work".
clearDebug() isn't required but it is usually called. This clears out the debug box at the bottom of your Simba window. I did not find documentation for clearDebug().
This initializes the SRL data structures and launches a SMART window running the RS client. It must be called and should probably be called very early in your script.
If you already have a SMART window open setupSRL() will use that window instead of opening a new one.
I did not find documentation for setupSRL().
Phase 1: Logging in
The next script will open RS, log your player in, paint some boxes on the screen and log out.
Download script for phase 1: makestuff1.simba
Simba Code:
program makestuff1;
{$DEFINE SMART}
{$i srl-6/srl.simba}
const
scriptName = 'makestuff1';
playerNames = ['WillyWonka']; // change this to your username or nickname
playerFile = 'default'; // change this to your playerFile name
desiredWorld = 0; // set this to 0 for any world
var
i : integer;
procedure scriptTerminate();
begin
print('****** ' + scriptName + ': Script shutting down', TDebug.DEBUG);
takeScreenshot(scriptName + '.png');
if isLoggedIn() then
players[currentPlayer].logout();
end;
procedure doSomeStuff();
var
x,y : integer;
begin
for x := 10 to 50 with 10 do
for y := 10 to 50 with 10 do
begin
smartImage.drawBox(intToBox(x, y, x+100, y+100), true, clFuchsia);
smartImage.drawBox(intToBox(x, y, x+100, y+100), clLime);
wait(1000);
smartImage.clear();
end;
players[currentPlayer].isActive := false;
end
begin
// initialize and prepare things
//
addOnTerminate('scriptTerminate');
ClearDebug();
smartEnableDrawing := true;
SetupSRL();
players.setup(playerNames, playerFile);
currentPlayer := 0;
for i := 0 to high(players) do
begin
players[i].world := desiredWorld;
players[i].isActive := true;
end;
// do the work
//
while (players.getActive() > 0) do
begin
if (not isLoggedIn()) then
begin
if (not players[currentPlayer].login()) then break;
mainscreen.setangle(MS_ANGLE_HIGH);
exitSquealOfFortune();
end;
doSomeStuff();
end;
end.
You need to add a player to your default player store with the alias WillyWonka. Open Simba and the player manager (SRL->Player Manager). Choose File->Open and navigate to SIMBADIR/includes/players to open default.xml. Add your entry and save it.
Note: I've bumped into a bug where my player store gets cleared out. For now I recommend maintaining a manual backup of the xml file for a safety copy.
Alternatively, you can edit the sample scripts and change the playerNames constant at the top to reflect your user name or alias.
SRL usage in this script:
We're going to skip the procedures for now and start with the main program.
Simba Code:
addOnTerminate('scriptTerminate');
addOnTerminate adds a function to the list of tasks when a script is shutting down. This can be triggered by pressing the stop button in Simba, a function calling scriptTerminate(), or the program may have simply reached the end of it's main begin/end.
It's a good idea to have a terminate function to ensure you free up any allocated items like bitmaps or dtms, log out the user, etc. You can add more than one function to the list by calling addOnTerminate more than once. This doesn't have much value in your main program but if you have a number of includes it's handy for each of them to have their own cleanup function registered.
I did not find documentation for addOnTerminate().
Simba Code:
smartEnableDrawing := true;
There are many functions available for writing or drawing on top of the client window. You need to set this flag if you want to utilize these functions. This is helpful for debugging and can be used to display status information or jazz up your script by displaying a logo when it runs.
You call these functions against the smartImage global variable (as seen in the doSomeStuff() procedure).
Note: smartImage is listed as an internal variable in smart.simba so it does not show up on the documentation page.
Simba Code:
players.setup(playerNames, playerFile);
currentPlayer := 0;
for i := 0 to high(players) do
begin
players[i].world := desiredWorld;
players[i].isActive := true;
end;
This code segment loads the data from your player file, sets the currentPlayer indicator and a couple of fields in the array that was loaded.
Recall the discussion near the top about "key concepts". SIMBADIR/includes/srl-6/core/players.simba defines the global variable players for us, which is an instance of TPlayerArray. When we call players.setup() this array is initialized from our stored XML file for us.
The functions in players.simba can be used to manage a pool of players. They enable you to control switching from one to another. If you code your script for it, you can have one character log in and do some work, log out, the next character log in, and so on.
The global variable currentPlayer is used to indicate which player in the players array is in current use.
Here we are setting the world and isActive fields for our player. If you set the world field it will use that specific world (0 will pick one randomly). If you set the isActive field to false the other functions will not use the player, so we set it to true.
Personally, I don't recommend using the multiple player capability. It's been made clear that they look into more than one account logging into a given machine/client and this leaves a highly suspicious login trail that's pretty easy to spot. Just my opinion; do what you want.
Simba Code:
while (players.getActive() > 0) do
begin
...
end;
The main loop of the script keeps repeating until all players in the players array are marked as inactive (isActive=false). Other parts of the script will have to do this for us or it will loop around forever.
Simba Code:
if (not isLoggedIn()) then
begin
if (not players[currentPlayer].login()) then break;
mainscreen.setangle(MS_ANGLE_HIGH);
exitSquealOfFortune();
end;
doSomeStuff();
Next the program checks to see if the current player is logged in. If not, it logs it in, sets the screen angle to the overhead view, and closes that annoying squeal of fortune panel. Finally, it calls the worker function, doSomeStuff().
exitSquealOfFortune() is in antiban.simba, which seems to be missing from the documentation at the moment. This include file has a number of functions which are helpful in making your script seem more human. You should check it out!
Let's go to the top and take a look at the functions now.
Simba Code:
procedure scriptTerminate();
begin
print('****** ' + scriptName + ': Script shutting down', TDebug.DEBUG);
takeScreenshot(scriptName + '.png');
if isLoggedIn() then
players[currentPlayer].logout();
end;
This is the function we registered with addOnTerminate() to be called at shutdown time.
It uses the print() procedure from debug.simba to write a message to the simba debug window (and the log file in SIMBADIR/includes/srl-6/logs).
It calls takeScreenshot() to save an image to the SIMBADIR/includes/srl6-/logs folder. This might be helpful to see why the script was quitting.
The last thing this procedure does is log out the current player.
Simba Code:
procedure doSomeStuff();
var
x,y : integer;
begin
for x := 10 to 50 with 10 do
for y := 10 to 50 with 10 do
begin
smartImage.drawBox(intToBox(x, y, x+100, y+100), true, clFuchsia);
smartImage.drawBox(intToBox(x, y, x+100, y+100), clLime);
wait(1000);
smartImage.clear();
end;
players[currentPlayer].isActive := false;
end
The doSomeStuff() procedure is just to make the script do something other than a quick login/logout. It uses the drawing functions referenced earlier to paint some boxes on the screen.
After that it marks the current player as inactive (which will end up making our main loop quit since there is only one player being used).
Phase 2: Putting some structure in place
Download script for phase 2: makestuff2.simba
Simba Code:
program makestuff2;
{$DEFINE SMART}
{$i srl-6/srl.simba}
const
scriptName = 'makestuff2';
playerNames = ['WillyWonka']; // change this to your username or nickname
playerFile = 'default'; // change this to your playerFile name
desiredWorld = 0; // set this to 0 for any world
type
Tstate = (MAKING, BANKING, UNKNOWN);
TstateRec = record
state : Tstate;
stateCheck : function:boolean;
stateExec : function:Tstate;
end;
var
i, stateIndex : integer;
expectedNextState : Tstate;
stateList : array[0..1] of TstateRec;
firstTime : boolean;
unexpectedCount : integer;
// these are temporary so we can simulate the work
FAKE_INV_CONTAINS_RAW_GOODS : boolean = false;
FAKE_COUNTER : integer;
function checkIfMaking() : boolean;
begin
if (FAKE_INV_CONTAINS_RAW_GOODS) then
result := true
else
result := false;
end;
function doMaking() : Tstate;
begin
print('****** ' + scriptName + ': Pretending to make things', TDebug.DEBUG);
wait(2000);
result := Tstate.BANKING;
FAKE_INV_CONTAINS_RAW_GOODS := false;
inc(FAKE_COUNTER)
if (FAKE_COUNTER > 10) then
begin
print('****** ' + scriptName + ': Simulating fatal issue', TDebug.DEBUG);
terminateScript();
end;
end;
function checkIfBanking() : boolean
begin
if (not FAKE_INV_CONTAINS_RAW_GOODS) then
result := true
else
result := false;
end;
function doBanking() : Tstate;
begin
print('****** ' + scriptName + ': Pretending to deposit and withdraw things', TDebug.DEBUG);
wait(2000);
result := Tstate.MAKING;
FAKE_INV_CONTAINS_RAW_GOODS := true;
end;
procedure scriptTerminate();
begin
print('****** ' + scriptName + ': Script shutting down', TDebug.DEBUG);
takeScreenshot(scriptName + '.png');
if isLoggedIn() then
players[currentPlayer].logout();
end;
begin
// initialize and prepare things
//
addOnTerminate('scriptTerminate');
ClearDebug();
smartEnableDrawing := true;
SetupSRL();
players.setup(playerNames, playerFile);
currentPlayer := 0;
for i := 0 to high(players) do
begin
players[i].world := desiredWorld;
players[i].isActive := true;
end;
with stateList[0] do
begin
state := Tstate.MAKING;
stateCheck := @checkIfMaking;
stateExec := @doMaking;
end;
with stateList[1] do
begin
state := Tstate.BANKING;
stateCheck := @checkIfBanking;
stateExec := @doBanking;
end;
// do the work
//
firstTime := true;
while (players.getActive() > 0) do
begin
if (not isLoggedIn()) then
begin
if (not players[currentPlayer].login()) then break;
mainscreen.setangle(MS_ANGLE_HIGH);
exitSquealOfFortune();
end;
for stateIndex := 0 to high(stateList) do
with stateList[stateIndex] do
if stateCheck() then
begin
if ((not firstTime) and (expectedNextState <> state)) then
begin
print('****** ' + scriptName + ': Unexpected state condition detected!', TDebug.DEBUG);
if (inc(unexpectedCount) > 5) then
begin
print('****** ' + scriptName + ': Too many unexpected state conditions. Aborting.', TDebug.DEBUG);
terminateScript();
end;
end;
expectedNextState := stateExec();
break; // stop looping through states, we found and executed the current one
end;
if (expectedNextState = Tstate.UNKNOWN) then
begin
// at this point we should probably try to boot ourselves into a known state.
print('****** ' + scriptName + ': We ended up in an unknown state', TDebug.DEBUG);
terminateScript();
end;
firstTime := false;
end;
end.
As stated in the beginning, the example script will make pastry dough. This version puts real structure in place instead of calling doSomeStuff() to draw boxes. It logs the player in, simulates the process of making the dough, then logs out.
There are no new SRL functions being called. Given this, I'm not going to go into detail on the code changes. If people have questions I can address them in the thread.
The script structure is based on using states to determine what condition the script is currently in and what should be performed next. I read a tutorial that @Blumblebee posted a while back about this topic and it's a very sensible approach to bot design. A good implementation can make your script very tolerant of surprises, especially when first starting up.
Phase 3: A functional script
Download script for phase 3: makestuff3.simba
Simba Code:
program makestuff3;
{$DEFINE SMART}
{$i srl-6/srl.simba}
const
scriptName = 'makestuff3';
playerNames = ['WillyWonka']; // change this to your username or nickname
playerFile = 'default'; // change this to your playerFile name
desiredWorld = 0; // set this to 0 for any world
type
Tstate = (MAKING, BANKING, UNKNOWN);
TstateRec = record
state : Tstate;
stateCheck : function:boolean;
stateExec : function:Tstate;
end;
var
i, stateIndex : integer;
expectedNextState : Tstate;
stateList : array[0..1] of TstateRec;
potOfFlour, bowlOfWater, pastryDough : integer;
firstTime : boolean;
unexpectedCount : integer;
function openBank() : boolean;
var
x, y, t : integer;
begin
if (mainscreen.findObject(x, y, 9207917, 12, colorSetting(2, 0.16, 0.68),
mainscreen.getCenterPoint(), 50, 50, 50, ['ank'], MOUSE_LEFT)) then
begin
t := getSystemTime() + 10000;
repeat
wait(250);
until (bankscreen.isOpen() or (getSystemTime() >= t));
result := bankscreen.isOpen();
end
else
result := false;
end;
function withDrawItem(dtmId: integer; mouseText: TStringArray; numToWithdraw: integer) : boolean;
var
x, y : integer;
withdrawStr : string;
begin
withdrawStr := 'Withdraw-' + intToStr(numtoWithdraw);
result := findDTM(dtmId, x, y, bankscreen.getBankSlotBoxes().getBounds());
if result then
begin
mouse(Point(x,y).rand(5));
if isMouseOverText(mouseText) then
begin
fastclick(MOUSE_RIGHT);
if chooseOption.optionsExist([withdrawStr]) then
chooseOption.select([withdrawStr])
else if chooseOption.select(['Withdraw-X']) then
begin
wait(randomRange(600,1200));
typeSend(intToStr(numToWithdraw), true);
end
else
result := false;
// give the bank a sec to do it, it lags a little
// should really wait until DTM is on inventory side of bank
wait(randomRange(700,1400));
end
else
result := false;
end;
end;
function checkIfMaking() : boolean;
var
flourCount, waterCount : integer = 0;
begin
// if the bank is sitting open we will presume to be banking, not making.
// it would be more accurate to check the bank inventory for flour/water
if (not bankscreen.isOpen()) then
begin
if (not tabBackpack.isOpen()) then
tabBackpack.open();
flourCount := tabBackpack.countDTM(potOfFlour);
waterCount := tabBackpack.countDTM(bowlOfWater);
end;
result := ((flourCount > 0) and (waterCount > 0));
end;
function doMaking() : Tstate;
var
craftBox, blueButton : TBox;
x, y, t, flourCount, waterCount : integer;
flourSlots, waterSlots : TIntegerArray;
tpa : TPointArray;
begin
if (bankscreen.isOpen()) then // probably not needed but just in case
bankscreen.close();
if (not tabBackpack.isOpen()) then
tabBackpack.open();
flourSlots := findItem(potOfFlour, tabBackpack.getSlotBoxes(), true);
waterSlots := findItem(bowlOfWater, tabBackpack.getSlotBoxes(), true);
if ((length(flourSlots) = 0) or (length(waterSlots) = 0)) then
begin
result := Tstate.UNKNOWN;
exit;
end;
// have to add 1 to the slot index to get the slot number
tabBackpack.mouseSlot(flourSlots[0]+1, MOUSE_LEFT); // "use flour"
wait(randomRange(1000,1800));
tabBackpack.mouseSlot(waterSlots[0]+1, MOUSE_LEFT); // "with pot of water"
blueButton := intToBox( trunc(smartClientWidth/2),
trunc(smartClientHeight/2)+136,
trunc(smartClientWidth/2)+226,
trunc(smartClientHeight/2)+161);
t := getSystemTime() + 10000;
repeat
wait(250);
// detect the big blue button on the craft menu
findColorsTolerance(tpa, 8938247, blueButton, 10, colorSetting(2, 0.02, 0.20))
until ((high(tpa) > 100) or (getSystemTime() >= t));
if (high(tpa) < 100) then
begin
result := Tstate.UNKNOWN;
exit;
end;
craftBox := intToBox( trunc(smartClientWidth/2)-250,
trunc(smartClientHeight/2)-136,
trunc(smartClientWidth/2)-37,
trunc(smartClientHeight/2)+150);
findDTM(pastryDough, x, y, craftBox);
mouse(point(x,y).rand(8), MOUSE_LEFT); // choose the pastry dough
wait(randomRange(800,1500));
blueButton.mouse(MOUSE_LEFT); // and press the blue button to make it
t := getSystemTime() + 120000;
repeat
wait(400);
flourCount := tabBackpack.countDTM(potOfFlour);
waterCount := tabBackpack.countDTM(bowlOfWater);
until (((flourCount = 0) or (waterCount = 0)) or (getSystemTime() >= t));
if ((flourCount = 0) or (waterCount = 0)) then
begin
wait(randomRange(400,800));
result := Tstate.BANKING;
end
else
result := Tstate.UNKNOWN;
end;
function checkIfBanking() : boolean
begin
// if we're not making then we are banking.
result := (not checkIfMaking());
end;
function doBanking() : Tstate;
var
t : integer;
begin
if (not bankscreen.isOpen()) then
openBank();
wait(randomRange(500,800));
bankscreen.quickDeposit(QUICK_DEPOSIT_INVENTORY);
wait(randomRange(500,800));
if (not withDrawItem(potOfFlour, ['lour'], 9)) then
begin
print('****** ' + scriptName + ': Could not locate flour in bank', TDebug.DEBUG);
result := Tstate.UNKNOWN;
exit;
end;
if (not withDrawItem(bowlOfWater, ['ater'], 9)) then
begin
print('****** ' + scriptName + ': Could not locate water in bank', TDebug.DEBUG);
result := Tstate.UNKNOWN;
exit;
end;
// bumped into case where bank took longer to close than bankscreen.close() allowed for.
// wait an additional 20 seconds if needed.
bankscreen.close();
t := getSystemTime() + 20000;
while ((bankscreen.isOpen()) and (getSystemTime() <= t)) do
begin
wait(250);
typeByteWait(VK_ESCAPE,150);
end;
if bankscreen.isOpen() then
result := Tstate.UNKNOWN
else
begin
result := Tstate.MAKING;
wait(randomRange(1200,1800));
end;
end;
procedure scriptTerminate();
begin
print('****** ' + scriptName + ': Script shutting down', TDebug.DEBUG);
takeScreenshot(scriptName + '.png');
freeDTM(potOfFlour);
freeDTM(bowlOfWater);
freeDTM(pastryDough);
if isLoggedIn() then
players[currentPlayer].logout();
end;
begin
// initialize and prepare things
//
addOnTerminate('scriptTerminate');
ClearDebug();
smartEnableDrawing := true;
SetupSRL();
players.setup(playerNames, playerFile);
currentPlayer := 0;
for i := 0 to high(players) do
begin
players[i].world := desiredWorld;
players[i].isActive := true;
end;
with stateList[0] do
begin
state := Tstate.MAKING;
stateCheck := @checkIfMaking;
stateExec := @doMaking;
end;
with stateList[1] do
begin
state := Tstate.BANKING;
stateCheck := @checkIfBanking;
stateExec := @doBanking;
end;
// potOfFlour := DTMFromString('mggAAAHicY2NgYNjBxMCwDYj3APFhIN4JxGuAuIyRgSEXikHsBihdEmjEsGvTJoap/X0MTnqyDDne+gw9SbYMIkCzsGFGHBgCAAkGDwo=');
potOfFlour := DTMFromString('mggAAAHicY2NgYOBhgABeIBYHYgEg5gJiTigWhtIwUBJoxNCTZMuQ463P4KQnyzC1v49h16ZNDBpAOWyYEQeGAABCKwja');
// bowlOfWater := DTMFromString('mrAAAAHic42BgYFBiZmAQAmININYDYi0gVgNiWSAWB+LfjAwMv4D4CxD/AGJGJgYGVigGsQ3mz2boTRNm6E8XZuhKFmZIdudliHDgYSgOFmCYmCnCIAK0Ax9mJIBhAACbAw2I');
bowlOfWater := DTMFromString('mrAAAAHic42BgYBAAYlYg5gZiESCWAGIZIBZjgAB2IOYFYkEoBrFZoHJsQGwwfzbDxEwRhuJgAYYIBx6GZHdehq5kYYb+dGGG3jRhBjmgGnyYkQCGAQBkmQmE');
pastryDough := DTMFromString('mwQAAAHic42RgYFjAxMAwG4rnAfFCIF4KxMuh9HwgVgBiWSBWBmIVIFaDYhBfHirX21vFMGNGM8PKlZMYZs9uA5rMCMdhoV4MLS1FDCJAHiHMSASGAwCF9hEY');
// do the work
//
firstTime := true;
while (players.getActive() > 0) do
begin
if (not isLoggedIn()) then
begin
if (not players[currentPlayer].login()) then break;
mainscreen.setangle(MS_ANGLE_HIGH);
exitSquealOfFortune();
end;
for stateIndex := 0 to high(stateList) do
with stateList[stateIndex] do
if stateCheck() then
begin
if ((not firstTime) and (expectedNextState <> state)) then
begin
print('****** ' + scriptName + ': Unexpected state condition detected!', TDebug.DEBUG);
if (inc(unexpectedCount) > 5) then
begin
print('****** ' + scriptName + ': Too many unexpected state conditions. Aborting.', TDebug.DEBUG);
terminateScript();
end;
end;
expectedNextState := stateExec();
break; // stop looping through states, we found and executed the current one
end;
if (expectedNextState = Tstate.UNKNOWN) then
begin
// at this point we should probably try to boot ourselves into a known state.
print('****** ' + scriptName + ': We ended up in an unknown state', TDebug.DEBUG);
terminateScript();
end;
firstTime := false;
end;
end.
Phase 2 presented a working skeleton. In phase 3 the state functions (checkIfMaking, doMaking, checkIfBanking, and doBanking) are implemented so the program actually makes dough.
To run the script, make sure you have Pots of Flour and Bowls of Water visible in your bank when you open it. Stand in front of a one-click bank chest and run the script. I tested it in Burthorpe, Lumbridge, and north of the Dueling arena.
Other than the state functions, there is little change from the Phase 2 program. I created DTMs for the Pot of Flour, Bowl of Water, and Pastry Dough. These are loaded in the main program initialization with DTMFromString().
Simba Code:
potOfFlour := DTMFromString('mggAAAHicY2NgYNjBxMCwDYj3APFhIN4JxGuAuIyRgSEXikHsBihdEmjEsGvTJoap/X0MTnqyDDne+gw9SbYMIkCzsGFGHBgCAAkGDwo=');
bowlOfWater := DTMFromString('mrAAAAHic42BgYFBiZmAQAmININYDYi0gVgNiWSAWB+LfjAwMv4D4CxD/AGJGJgYGVigGsQ3mz2boTRNm6E8XZuhKFmZIdudliHDgYSgOFmCYmCnCIAK0Ax9mJIBhAACbAw2I');
pastryDough := DTMFromString('mwQAAAHic42RgYFjAxMAwG4rnAfFCIF4KxMuh9HwgVgBiWSBWBmIVIFaDYhBfHirX21vFMGNGM8PKlZMYZs9uA5rMCMdhoV4MLS1FDCJAHiHMSASGAwCF9hEY');
The DTMs are freed up in our scriptTerminate() function so we are most likely to be polite and give the memory back.
Simba Code:
freeDTM(potOfFlour);
freeDTM(bowlOfWater);
freeDTM(pastryDough);
The state functions use SRL calls to perform the work. I'm going to describe them at a high level, hopefully you're able to poke at the documentation and make sense of the details!
Simba Code:
function checkIfMaking() : boolean;
var
flourCount, waterCount : integer = 0;
begin
// if the bank is sitting open we will presume to be banking, not making.
// it would be more accurate to check the bank inventory for flour/water
if (not bankscreen.isOpen()) then
begin
if (not tabBackpack.isOpen()) then
tabBackpack.open();
flourCount := tabBackpack.countDTM(potOfFlour);
waterCount := tabBackpack.countDTM(bowlOfWater);
end;
result := ((flourCount > 0) and (waterCount > 0));
end;
checkIfMaking() uses a couple of functions from the bankscreen and tabBackpack global variables.
It makes sure the bank is not open, checks if the backpack tab is open, and opens it if needed (bankscreen.isOpen(), tabBackpack.isOpen(), tabBackpack.open()).
The tabBackpack.countDTM() function is used to get a count of how many flours and waters are in the inventory.
If it finds at least one flour and one water, it returns true, indicating we are indeed in the MAKING state.
Simba Code:
function checkIfBanking() : boolean
begin
// if we're not making then we are banking.
result := (not checkIfMaking());
end;
checkIfBanking() is overly simple. As the comment says, if we're not making, we are banking.
doMaking() has a lot more logic. If doMaking() gets called this means we have raw materials to make at least one pastry dough.
Simba Code:
function doMaking() : Tstate;
var
craftBox, blueButton : TBox;
x, y, t, flourCount, waterCount : integer;
flourSlots, waterSlots : TIntegerArray;
tpa : TPointArray;
begin
if (bankscreen.isOpen()) then // probably not needed but just in case
bankscreen.close();
if (not tabBackpack.isOpen()) then
tabBackpack.open();
First it makes sure the bank is closed and the backpack is open.
Simba Code:
flourSlots := findItem(potOfFlour, tabBackpack.getSlotBoxes(), true);
waterSlots := findItem(bowlOfWater, tabBackpack.getSlotBoxes(), true);
if ((length(flourSlots) = 0) or (length(waterSlots) = 0)) then
begin
result := Tstate.UNKNOWN;
exit;
end;
Then it locates flour and water in the inventory using findItem() from SIMBADIR/includes/srl-6/lib/misc/items.simba. These calls search through tabBackpack.getSlotBoxes() (an array of Tboxes defining each of the inventory slots).
Simba Code:
// have to add 1 to the slot index to get the slot number
tabBackpack.mouseSlot(flourSlots[0]+1, MOUSE_LEFT); // "use flour"
wait(randomRange(1000,1800));
tabBackpack.mouseSlot(waterSlots[0]+1, MOUSE_LEFT); // "with pot of water"
Next it clicks on a flour and then a water using tabBackpack.mouseSlot()
Simba Code:
blueButton := intToBox( trunc(smartClientWidth/2),
trunc(smartClientHeight/2)+136,
trunc(smartClientWidth/2)+226,
trunc(smartClientHeight/2)+161);
t := getSystemTime() + 10000;
repeat
wait(250);
// detect the big blue button on the craft menu
findColorsTolerance(tpa, 8938247, blueButton, 10, colorSetting(2, 0.02, 0.20))
until ((high(tpa) > 100) or (getSystemTime() >= t));
if (high(tpa) < 100) then
begin
result := Tstate.UNKNOWN;
exit;
end;
That causes the cooking window pop up. The function waits until it detects the large blue button on that window. It uses getSystemTime() to implement a timeout and findColorsTolerance() to check for the button. I believe getSystemTime is inherited from simba (docs not found).
Simba Code:
craftBox := intToBox( trunc(smartClientWidth/2)-250,
trunc(smartClientHeight/2)-136,
trunc(smartClientWidth/2)-37,
trunc(smartClientHeight/2)+150);
findDTM(pastryDough, x, y, craftBox);
mouse(point(x,y).rand(8), MOUSE_LEFT); // choose the pastry dough
wait(randomRange(800,1500));
blueButton.mouse(MOUSE_LEFT); // and press the blue button to make it
Once the blue button has been detected, it calls findDTM() with the pastry dough DTM to locate the selection for making pastry dough. It clicks that icon and clicks the blue Make button.
Simba Code:
t := getSystemTime() + 120000;
repeat
wait(400);
flourCount := tabBackpack.countDTM(potOfFlour);
waterCount := tabBackpack.countDTM(bowlOfWater);
until (((flourCount = 0) or (waterCount = 0)) or (getSystemTime() >= t));
if ((flourCount = 0) or (waterCount = 0)) then
begin
wait(randomRange(400,800));
result := Tstate.BANKING;
end
else
result := Tstate.UNKNOWN;
end;
Finally, it sits in a loop counting the number of flours and waters in the inventory until one of them reaches 0. If we ran out of raw materials, we're done making!
Let's move onto doBanking() and see some of the SRL calls involved there.
Simba Code:
function doBanking() : Tstate;
var
t : integer;
begin
if (not bankscreen.isOpen()) then
openBank();
wait(randomRange(500,800));
bankscreen.quickDeposit(QUICK_DEPOSIT_INVENTORY);
wait(randomRange(500,800));
It makes sure the bank is open using an internal openBank() function we didn't look at yet. Then it uses bankscreen.quickDeposit() to dump the inventory into the bank.
Simba Code:
if (not withDrawItem(potOfFlour, ['lour'], 9)) then
begin
print('****** ' + scriptName + ': Could not locate flour in bank', TDebug.DEBUG);
result := Tstate.UNKNOWN;
exit;
end;
if (not withDrawItem(bowlOfWater, ['ater'], 9)) then
begin
print('****** ' + scriptName + ': Could not locate water in bank', TDebug.DEBUG);
result := Tstate.UNKNOWN;
exit;
end;
Then it withdraws 9 each of flour and water using another internal function withDrawItem() that we'll look at later.
Simba Code:
// bumped into case where bank took longer to close than bankscreen.close() allowed for.
// wait an additional 10 seconds if needed.
t := getSystemTime() + 20000;
repeat
wait(250);
bankscreen.close();
until ((not bankscreen.isOpen()) or (getSystemTime() >= t));
if bankscreen.isOpen() then
result := Tstate.UNKNOWN
else
result := Tstate.MAKING;
end;
Finally, it closes the bank. The player should now have raw meterials in their inventory.
The openBank() function we saw is pretty simple:
Simba Code:
function openBank() : boolean;
var
x, y, t : integer;
begin
if (mainscreen.findObject(x, y, 9207917, 12, colorSetting(2, 0.16, 0.68),
mainscreen.getCenterPoint(), 50, 50, 50, ['ank'], MOUSE_LEFT)) then
begin
t := getSystemTime() + 10000;
repeat
wait(250);
until (bankscreen.isOpen() or (getSystemTime() >= t));
result := bankscreen.isOpen();
end
else
result := false;
end;
I used ACA to find a good color for the bank chest straps. This uses mainscreen.findObject() to locate and click on it. That should open the bank.
It waits in a timeout loop for the bank to fully open.
Simba Code:
function withDrawItem(dtmId: integer; mouseText: TStringArray; numToWithdraw: integer) : boolean;
var
x, y : integer;
withdrawStr : string;
begin
withdrawStr := 'Withdraw-' + intToStr(numtoWithdraw);
result := findDTM(dtmId, x, y, bankscreen.getBankSlotBoxes().getBounds());
if result then
begin
mouse(Point(x,y).rand(5));
if isMouseOverText(mouseText) then
begin
fastclick(MOUSE_RIGHT);
First withdrawItem() finds the item using the DTM passed in and right clicks it.
It uses findDTM(), mouse(), isMouseOverText() and fastclick() to do this.
Simba Code:
if chooseOption.optionsExist([withdrawStr]) then
chooseOption.select([withdrawStr])
else if chooseOption.select(['Withdraw-X']) then
begin
wait(randomRange(600,1200));
typeSend(intToStr(numToWithdraw), true);
end
else
result := false;
// give the bank a sec to do it, it lags a little
// should really wait until DTM is on inventory side of bank
wait(randomRange(700,1400));
end
else
result := false;
end;
end;
Then it tries to choose Withdraw-9. If it can't find that in the list, it chooses Withdraw-X and types in the 9 (so we ought to find it the next time around).
chooseOption is a global variable set in SIMBADIR/includes/srl-6/lib/core/text.simba
Wrap up
That covers the whole sample script. With any luck you gained some insight into using the SRL library to write your scripts.
You may notice how easy it would be to turn the example script into many more useful scripts. The same base could be used to cut up pineapples, clean herbs, and even fletch bows. It wouldn't take too much modification to perform any task where you sit at the bank and withdraw/make.
The future I see for it is to make it data driven so one code base could perform all these tasks.
This paper has grown larger than I expected. I hope it didn't bore you too much!