SRL Archive - Test corner

Critique my include!


Foundry

2014-03-01 23:33:37

Anyone who has seen my application knows that I have been working on an include for Wurm Online. Since I first applied I have rewritten a good bit of it and am looking to improve it even further. I'm gonna post stuff here periodically when I think it is good to get second opinions. I understand hardly anyone plays Wurm, so here is a picture of the regular interface (you can move all of the windows around, this is just the way I like it). [ATTACH=CONFIG]23034[/ATTACH]

If you want to take a look at the whole deal, the Github is located here.

Today I bring you…

All of the functions that handle the chatbox.
{$include_once srlutilities\srlutilities.simba}
// The functions in window getter get the bounds of interfaces based
// on a text file Wurm saves the window positions to on logout.
{$include_once windowgetter.simba}

{==============================================================================]
TYPE
----
A type that will have the bounds of the chatbox, tabs, etc.
[==============================================================================}
type
TChatboxTab = record
Bounds: TBox;
Text: String;
Active, Red: Boolean;
end;

TChatbox = record
Bounds, SpecialBox: TBox;
Tabs: array of TChatboxTab;
end;

// By Foundry
{==============================================================================]
VAR
---
These two variables are for the right and left chat tabs and such.
[==============================================================================}
var
LocalChat, EventChat: TChatbox;

// By Foundry
{==============================================================================]
CONSTANTS
---------
Just the colors of the text in the different tabs. Also some to represent
different tabs.
[==============================================================================}
const
CHAT_ACTIVE = 16777215;
CHAT_RED = 51;
CHAT_INACTIVE = 0;

TAB_LOCAL = 'Local';
TAB_CA_HELP = 'HELP';
TAB_GL_FREEDOM = 'GL-Freedom';
TAB_FREEDOM = 'Freedom';
TAB_EVENT = 'Event';
TAB_FRIENDS = 'Friends';
TAB_SUPPORT = 'Support';
TAB_SKILLS = 'Skills';

// By Foundry
{==============================================================================]
Gets the bounds of the chatbox by reading the txt file in the player folder.
[==============================================================================}
function TChatbox.getBounds(EventChat: Boolean = false): TBox;
var
SearchFor: String = 'chat';
begin
if not((Bounds.x2 = 0) and (Bounds.y2 = 0)) then
exit(Bounds);
if EventChat then
SearchFor := 'event';
if not getStoredBounds(SearchFor, Bounds) then
begin
writeLn('Couldn''t get the bounds of the chatbox! Did you enter the wrong' +
' char name?');
exit([-1, -1, -1, -1]);
end;
if EventChat and (Bounds.x1 = Bounds.x2) then
Bounds.x2 := ResX;
exit(Bounds);
end;

// By Foundry
{==============================================================================]
Gets the tabs of the chatbox and also sets the current tab. Will take a look
at it later and add comments.
[==============================================================================}
function TChatbox.getTabs(): Boolean;
var
textcolors, atpa: T2DPointArray;
i, j, k: Integer;
copy: TBox;
const
colors = [CHAT_INACTIVE, CHAT_RED, CHAT_ACTIVE];
begin
copy := getBounds();
copy.y2 := copy.y1 + 15;
setLength(textcolors, 3);
for i := 0 to high(colors) do
begin
if findColors(textcolors[i], colors[i], copy) then
begin
atpa := textcolors[i].cluster(6, 2);
atpa.filterBetween(0, 15);
for j := 0 to high(atpa) do
begin
if (inRange(atpa[j].getBounds().getHeight(), 8, 15)) and
(atpa[j].getBounds().getWidth() > 10) then
begin
setLength(Tabs, k + 1);
Tabs[k].Active := (i = 2);
Tabs[k].Red := (i = 1);
Tabs[k].Bounds := atpa[j].getBounds();
try
Tabs[k].Text := atpa[j].getBounds().getText(colors[i]);
except begin
writeLn('Failed to get a tab... must have been a false positive.');
continue;
end;
end;
result := true;
inc(k);
end;
end;
end;
end;
end;

// By Foundry
{==============================================================================]
Clicks the tab. Relies on the bounds stored from getTabs().
[==============================================================================}
procedure TChatboxTab.click();
begin
mouseBox(Bounds, MOUSE_LEFT);
end;

// By Foundry
{==============================================================================]
Clicks the tab with the given text.
[==============================================================================}
function TChatbox.setTab(TabName: String): Boolean;
var
i: Integer;
textmatch: Boolean;
begin
getTabs();
for i := 0 to high(Tabs) do
begin
if (TabName = TAB_GL_FREEDOM) or
(TabName = TAB_FREEDOM) then
textmatch := (TabName = Tabs[i].Text)
else
textmatch := (pos(TabName, Tabs[i].Text) > 0);
if not textmatch then
continue;
if not Tabs[i].Active then
Tabs[i].click();
exit(true);
end;
end;

// By Foundry
{==============================================================================]
Gets the bounds of the local name box. Uses the arrows that are located on the
left, right, and bottom.
[==============================================================================}
function TChatbox.isTabPresent(TabName: String): Boolean;
var
i: Integer;
begin
getTabs();
for i := 0 to high(Tabs) do
begin
if (pos(TabName, Tabs[i].Text) > 0) then
exit(true);
end;
end;

// By Foundry
{==============================================================================]
Gets the bounds of the local name box. Uses the arrows that are located on the
left, right, and bottom.
[==============================================================================}
function TChatbox.getLocalNameBox(): Boolean;
var
LeftArrow, RightArrow, UpArrow: Integer;
begin
LeftArrow := BitmapFromString(8, 9, 'meJxz8gr1CIp1DoqydQ+0dguwcvfn' +
'FxB29Yt2CQh39gmx9wx2D4kXFBblFxDKL6sBMoLikr1Co0XEJMUkp' +
'Bvb+upaOpra+4RExF39I4GC9a09K9ZsXLJi/eoNW+YtXi4sJsnHL5' +
'iZV1xW07Rr/+Enz18eOXm2d9J0MQkZIysXASFhY3Pb9OyiC1eu7z1' +
'4tHfqDGFRCV0zBx0zex5+QQEhUVsnN1fvwOqGdgEhESsXH0uXQGvP' +
'QDMnb34hYX5Boay8EqC4d0isX1hcYGSCR2Ckd0i0T2i0kIgYkAsA4' +
'KhFmw==');
RightArrow := BitmapFromString(8, 9, 'meJxz8AwTEZVw8o+ycvG18Qh28I2w' +
'94nwDUkUlZAys3IQFZfiFxK29w5z8A538o3wi0wRlZD2D46YMHVOe' +
'88kQWFRASFRK3dvB99gYVGJwIi4leu3rN+yc9O2nVNmLRYRk3D2CR' +
'cUFnN28920Y++Z85ev3ry9btP2grJqIRExfiERGwf3GXMXHThyYu+' +
'BozWN7UDTrD18gaSJpZ2Vg4unX5CYpIyYhLS+paOJvRvQPZZ2rqLi' +
'kiLikk4+oe5BkXZeAb6R8UGxiQLCop4B4e7+YXbuPj7BEd5B4W4+o' +
'QCtPkM8');
UpArrow := BitmapFromString(9, 8, 'meJxz9Apz8Amz9Qyy8woVFBa19Q71' +
'DI73Dkn0D4m38Qi0dPI2sXcXEBLJK6oSEhHzi0jxi0kLiEzwi4h38' +
'AoGqm9s6+6eOCM0JkFMUsY3Os43LNo9NCEmp1xSRn7vgcM3bt+fu2' +
'iFlIyiR2CUm2+YhbOPsKjEpOlz123edvHK9YNHT7j5BAqLigdGJ5r' +
'YugPNb+uZNHHa3C07967btD0lKw8o5Rkczi8orGNoamZt5xcUmZpV' +
'EBwV5xMUbmZlD3SVvW+EiJiEiLikhLSspIycrIKSpLSsmISUR0AYA' +
'Bp+RHg=');
if findBitmapToleranceIn(LeftArrow, SpecialBox.x1, SpecialBox.y2, Bounds, 10) and
findBitmapToleranceIn(UpArrow, SpecialBox.x1, SpecialBox.y1, intToBox(SpecialBox.x1 - 50, 0, SpecialBox.x1 + 15, SpecialBox.y2 - 10), 10) and
findBitmapToleranceIn(RightArrow, SpecialBox.x2, SpecialBox.y2, intToBox(SpecialBox.x1 + 20, SpecialBox.y2 - 30, SpecialBox.x1 + 200, SpecialBox.y2 + 30), 10) then
begin
SpecialBox.x2 := SpecialBox.x2 + 20;
result := true;
end else begin
result := false;
writeLn('Failed to find the local name box.');
end;
freeBitmaps([LeftArrow, RightArrow, UpArrow]);
end;

// By Foundry
{==============================================================================]
This gets the bounds of the timer bar (the one that fills up as you are doing
an action) and returns whether it found it or not.
[==============================================================================}
function TChatbox.getTimerBox(): Boolean;
var
tpa: TPointArray;
atpa: T2DPointArray;
begin
getBounds(true);
SpecialBox.edit(Bounds.x1, Bounds.y2 - 30, Bounds.x2, Bounds.y2 - 16);
result := (countColorTolerance(5069157, SpecialBox, 6, colorSetting(2, 0.31, 0.55)) > (SpecialBox.getWidth() * 2))
if not result then
begin
writeLn('Failed to find the timer bar!');
terminateScript();
end;
end;

// By Foundry
{==============================================================================]
Snatches the names from the local name box.
[==============================================================================}
function TChatbox.ignoreLocalNames(): Boolean;
var
i, j: Integer;
tpa, tpa2: TPointArray;
atpa: T2DPointArray;
tmpstr: String;
begin
if findColors(tpa, BLUE_TEXT_COLOR, SpecialBox) or
findColors(tpa, 8454016, SpecialBox) then
begin
findColors(tpa2, 16777215, SpecialBox);
tpa.combine(tpa2);
atpa := tpa.cluster(SpecialBox.getWidth(), 3);
atpa.filterBetween(0, 10);
for i := 0 to high(atpa) do
begin
tmpstr := extractTextFromTPA(atpa[i], 1, 4, 3, 'WurmF');
// Ignore event actions from anyone but the player.
if not (lowercase(tmpstr) = lowercase(CharName)) then
begin
setLength(IgnoreNames, j + 1);
IgnoreNames[j] := tmpstr;
inc(j);
end;
end;
exit(true);
end else
begin
writeLn('Failed to grab local names.');
terminateScript();
end;
end;

// By Foundry
{==============================================================================]
Initializes the chatbox. If EventChat is true it will initialize as the
right side event chat.
[==============================================================================}
procedure TChatbox.init(EventChat: Boolean = false);
begin
getBounds(EventChat);
if EventChat then
begin
if (not isTabPresent(TAB_EVENT)) then
mouse(Bounds.getMiddle().rand(15), MOUSE_LEFT);
if (not getTimerBox()) then
begin
writeLn('Couldn''t set up timer box');
terminateScript();
end;
exit;
end;
if not setTab(TAB_LOCAL) then
writeLn('Couldn''t set the tab to local');
if (not getLocalNameBox()) or (not ignoreLocalNames()) then
begin
writeLn('Couldn''t set up the chatbox. Is your resolution right?');
terminateScript();
end;
end;

// By Foundry
{==============================================================================]
Checks to see if a GM is in local by searching for GM_COLOR in the local name
box.
[==============================================================================}
procedure TChatbox.GMCheck();
begin
if SpecialBox.colorExists(GM_COLOR) then
begin
writeLn('A GM IN LOCAL!!! ABANDONING SHIP!!!');
// play sound here if sounds enabled.
terminateScript();
end;
end;

// By Foundry
{==============================================================================]
This returns whether or not the given 'Activity' is being performed.
[==============================================================================}
function TChatbox.doingActivity(Activity: String = 'None'): Boolean;
begin
if SpecialBox.colorExists(7955803, 14) then
begin
if Activity = 'None' then
exit(true);
if SpecialBox.colorExists(16777215) then
begin
if pos(Activity, SpecialBox.getText()) > 0 then
exit(true);
end else
if pos(Activity, SpecialBox.getText(45568)) > 0 then
exit(true);
end;
end;

// By Foundry
{==============================================================================]
Waits for the current activity to finish. Will work on queues.
[==============================================================================}
function TChatbox.waitOnActivity(Activity: String = 'None'): Boolean;
var
t, randomtimeout: Integer;
begin
randomtimeout := randomrange(2000, 4000);
t := getSystemTime();
result := true;
repeat
wait(200 + random(300));
if getSystemTime() - t > randomtimeout then
begin
writeLn('waitOnActivity couldn''t detect activity after ' + toStr(round((getSystemTime() - t) / 1000)) + ' seconds.');
exit(false);
end;
until(doingActivity(Activity));
repeat
wait(randomrange(900, 1200));
if getSystemTime() - t > 200000 then
begin
writeLn('waitOnActivity timed out after ' + toStr(round((getSystemTime() - t) / 1000)) + ' seconds.');
exit(false);
end;
until(not doingActivity(Activity));
end;

… and the functions for the toolbelt.

{$include_once srlutilities/srlutilities.simba}
{$include_once hotkeys.simba}

type
// By Foundry
{==============================================================================]
TYPE
----
The TToolbeltSlot type holds the data for a given slot in the toolbelt.
[==============================================================================}
TToolbeltSlot = record
Box: TBox;
ID: Integer;
end;

// By Foundry
{==============================================================================]
TYPE
----
The TToolbelt type holds the data for th whole toolbelt. Such as the bounds
and currently active slot.
[==============================================================================}
TToolbelt = record
Bounds: TBox;
Slots: array of TToolbeltSlot;
ActiveSlot: Integer;
end;

// By Foundry
{==============================================================================]
GLOBAL VAR
----------
Toolbelt record that is automatically initialized at script startup.
[==============================================================================}
var
Toolbelt: TToolbelt;

// By Foundry
{==============================================================================]
Returns whether or not the toolbelt was found. If it is, then it will apply
the bounds.
[==============================================================================}
function TToolbelt.getBounds(): TBox;
var
BoxesToCheck: TBoxArray;
i, j: Integer;
begin
// If the bounds have already been found then no need to find them again.
if Bounds.x2 > 0 and Bounds.y2 > 0 then
exit(Bounds);
if getStoredBounds('toolbelt', Bounds) then
begin
Bounds.x1 := Bounds.x1 + 39;
Bounds.x2 := Bounds.x1 + 200; // This is the maximum length the toolbelt can
// be. It can go up to 9 slots.
Bounds.y2 := Bounds.y1 + 20;
end else begin
writeLn('Couldn''t get the bounds of the toolbelt.');
terminateScript();
end;
// I just tinkered with this until it worked.
BoxesToCheck := grid(7, 1, 20, 15, 28, 0, point(Bounds.x1 + 13, Bounds.y1 + 17));
// The following checks each box made by the grid to see if it is a part of the
// toolbelt.
for i := 0 to high(BoxesToCheck) do
begin
if countColorTolerance(2307923, BoxesToCheck[i], 5, colorSetting(2, 0.26, 1.07)) > 80 then
begin
setLength(Slots, j + 1);
Slots[j].Box := BoxesToCheck[i];
inc(j);
end;
end;
end;

// By Foundry
{==============================================================================]
Applies the improvement type (actually the tool type, same thing) to each slot
object by searching in the slot box.
[==============================================================================}
procedure TToolbelt.getImpTypes();
var
i: Integer;
begin
for i := 0 to high(Slots) do
begin
Slots[i].ID := getImpType(Slots[i].Box);
end;
end;

// By Foundry
{==============================================================================]
Initializes the toolbelt. If it fails it will display a nice error message.
[==============================================================================}
procedure TToolbelt.init();
begin
getBounds();
getImpTypes();
end;

// By Foundry
{==============================================================================]
The wonderful part of this file. This function returns whether or not you have
the tool for a given job in your toolbelt.
[==============================================================================}
function TToolbelt.isToolPresent(ID: Integer): Boolean;
var
i: Integer;
begin
for i := 0 to high(Slots) do
begin
if Slots[i].ID = ID then
exit(true)
else if (ID = CHISEL) and // Chisel and knife are the same bitmap
(Slots[i].ID = KNIFE) then
exit(true);
end;
end;

// By Foundry
{==============================================================================]
Like the above function but this returns the slot in which that tool is found.
[==============================================================================}
function TToolbelt.getToolSlot(ID: Integer): Integer;
var
i: Integer;
begin
for i := 0 to high(Slots) do
begin
if Slots[i].ID = ID then
exit(i + 1)
else
if (ID = CHISEL) and
(Slots[i].ID = KNIFE) then
exit(i + 1);
end;
end;

// By Foundry
{==============================================================================]
Moves the mouse to the desired slot of the toolbelt.
[==============================================================================}
procedure TToolbelt.mouseSlot(Slot: Integer; clicktype: Integer = MOUSE_MOVE);
begin
Slots[Slot - 1].Box.mouse(clicktype);
end;

// By Foundry
{==============================================================================]
Activates the desired tool in the toolbelt by either clicking on it or
pressing the corresponding key.
[==============================================================================}
procedure TToolbelt.activateSlot(Slot: Integer; click: Boolean);
begin
if click then
mouseSlot(Slot, MOUSE_LEFT)
else
WurmPressKey(toStr(Slot));
end;

Brandon

2014-03-02 12:08:01

chatbox.getbounds should not terminate the script.. return Box(-1, -1, -1, -1);


if not textmatch then
continue;
if textmatch and (not Tabs[i].Active) then


is redundant because you're already checking if textmatch is set.. why check again in the second if statement? Textmatch
is guaranteed to be true before it hits this statement due to the continue statement above.

Calling setlength in loops is an optimisation flaw I see a lot of scripters doing.. It'd be better to set it to the max amount of tabs before the loop and after the loop setlength to k or whatever counter the loop was using.. unless there is no max or there is a reason that I'm not seeing then you're losing out.


Lots of minor things but other than that, the two files above seem good to me. Did not check out the rest/github. So far though, I say good job.

Foundry

2014-03-02 14:11:28

From: Brandon
chatbox.getbounds should not terminate the script.. return Box(-1, -1, -1, -1);


if not textmatch then
continue;
if textmatch and (not Tabs[i].Active) then


is redundant because you're already checking if textmatch is set.. why check again in the second if statement? Textmatch
is guaranteed to be true before it hits this statement due to the continue statement above.

Calling setlength in loops is an optimisation flaw I see a lot of scripters doing.. It'd be better to set it to the max amount of tabs before the loop and after the loop setlength to k or whatever counter the loop was using.. unless there is no max or there is a reason that I'm not seeing then you're losing out.


Lots of minor things but other than that, the two files above seem good to me. Did not check out the rest/github. So far though, I say good job.


Thanks, I didn't notice the redundancy there. The amount of tabs you can have open is like 20 but usually people only have 5 or 6 open. Are you saying it would be better to set the length in the beginning to the max and then set the length at the end to however many were used? If that is the case, I need to change a lot of what i have done XD.

Brandon

2014-03-02 14:23:00

From: Foundry
Thanks, I didn't notice the redundancy there. The amount of tabs you can have open is like 20 but usually people only have 5 or 6 open. Are you saying it would be better to set the length in the beginning to the max and then set the length at the end to however many were used? If that is the case, I need to change a lot of what i have done XD.


Yeah it's nice to get rid of the redundancies although it might be micro-optimisation and slight mistakes.


You'll have to test it then. The reason is because imagine how resizing an array works. It doesn't just magically add one to the array's length.

What it does is this:


Procedure resize(var OldArr: TIntegerArray; NewSize: Integer);
var
I, L: Integer;
Arr: TIntegerArray;
begin
SetLength(Arr, NewSize);
L := Min(Length(OldArr), Arr);

For I := 0 To L Do
Arr[I] := OldArr[I];

OldArr := Arr;
end;


It creates a whole new array of specified size. It then copies the contents of the old array into the new array. Doing this every loop can certainly be a drag. However, what some compilers do is they allocate MORE space than you ask for and then when you ask to resize, it doesn't need to resize. It just inserts the new value.

It really depends on how Lape does it tbh.. I'd test it since your array is small. If it is small, then it is fine but if it is large, then it is wise to avoid resizing in loops.