This plugin works well, the accuracy is still the same 5 pixels, but I think it's a bit faster and it uses the whole minimap for better reliability. Note that the tolerance value is ~500 with the new, faster formula.
I don't upload my own compiled plugin, because for some reason it's like 13k big when it should be closer to 1.5k.
library sps;
{$mode objfpc}{$H+}
uses
Classes, sysutils, FileUtil, mufasatypes, bitmaps, math, Interfaces;
type
T3DIntegerArray = array of T2DIntegerArray;
T4DIntegerArray = array of T3DIntegerArray;
TMufasaBitmapArray = array of TMufasaBitmap;
function SPS_ColorBoxesMatchInline(B1, B2: TIntegerArray; tol: extended): boolean; inline;
begin
Result := False;
if (abs(B1[0] - B2[0]) < tol) then
if (abs(B1[1] - B2[1]) < tol) then
if (abs(B1[2] - B2[2]) < tol) then
Result := True;
end;
function SPS_MakeColorBox(bmp: TMufasaBitmap; x1, y1, SideLength: integer): TIntegerArray; register;
var
x, y, width: integer;
C: TRGB32;
begin
SetLength(Result, 3);
width := bmp.Width;
for x := (x1 + SideLength - 1) downto x1 do
for y := (y1 + SideLength - 1) downto y1 do
begin
try
C := bmp.FData[y * width + x];
Result[0] := Result[0] + C.R;
Result[1] := Result[1] + C.G;
Result[2] := Result[2] + C.B;
except end;
end;
end;
procedure SPS_FilterMinimap(var Minimap: TMufasaBitmap); register;
var
W, H, x, y: integer;
C: TRGB32;
begin
W := Minimap.width;
H := Minimap.height;
for x := W - 1 downto 0 do
for y := H - 1 downto 0 do
begin
if hypot(abs(75.0 - x), abs(75.0 - y)) > 75 then
begin
Minimap.FastSetPixel(x, y, 0);
continue;
end;
C := Minimap.FData[y * width + x];
end;
end;
function SPS_BitmapToMap(bmp: TMufasaBitmap; SideLength: integer): T3DIntegerArray; register;
var
X, Y, HighX, HighY: integer;
begin
HighX := Trunc(bmp.Width / (SideLength*1.0));
HighY := Trunc(bmp.Height / (SideLength*1.0));
SetLength(Result, HighX);
for X := 0 to HighX - 1 do
begin
SetLength(Result[X], HighY);
for Y := 0 to HighY - 1 do
begin
Result[X][Y] := SPS_MakeColorBox(bmp, X * SideLength, Y * SideLength, SideLength);
end;
end;
end;
function SPS_FindMapInMap(out fx, fy: integer; LargeMap: T4DIntegerArray; SmallMap: T3DIntegerArray; tol: extended; out FoundMatches: integer): integer; register;
var
x, y, HighX, HighY, cm, L: integer;
xx, yy: integer;
Matching: integer;
BoxesInViewX, BoxesInViewY: integer;
b: Boolean;
begin
fX := -1;
fY := -1;
Result := -1;
FoundMatches := 0;
L := Length(LargeMap);
BoxesInViewX := Length(SmallMap);
BoxesInViewY := Length(SmallMap[0]);
for cm := 0 to L-1 do
begin
HighX := High(LargeMap[cm]) - BoxesInViewX - 1;
HighY := High(LargeMap[cm][0]) - BoxesInViewY - 1;
for x := 0 to HighX do
for y := 0 to HighY do
begin
Matching := 0;
for xx := BoxesInViewX - 1 downto 0 do
for yy := BoxesInViewY - 1 downto 0 do
begin
b:= SPS_ColorBoxesMatchInline(LargeMap[cm][x+xx][y+yy], SmallMap[xx][yy], tol);
if (b) then Inc(Matching);
end;
if (Matching > FoundMatches) then
begin
FoundMatches := Matching;
Result := cm;
fX := x;
fY := y;
end;
end;
end;
end;
////// EXPORTING /////////////////////////////////////////////////////////////
procedure SetPluginMemoryManager(MemMgr : TMemoryManager); stdcall; export;
begin
SetMemoryManager(MemMgr);
end;
function GetTypeCount(): Integer; stdcall; export;
begin
Result := 3;
end;
function GetTypeInfo(x: Integer; var sType, sTypeDef: string): integer; stdcall; export;
begin
case x of
0: begin
sType := 'T3DIntegerArray';
sTypeDef := 'array of T2DIntegerArray;';
end;
1: begin
sType := 'T4DIntegerArray';
sTypeDef := 'array of T3DIntegerArray;';
end;
2: begin
sType := 'TMufasaBitmapArray';
sTypeDef := 'array of TMufasaBitmap';
end;
else
x := -1;
end;
Result := x;
end;
function GetFunctionCount(): Integer; stdcall; export;
begin
Result := 4;
end;
function GetFunctionCallingConv(x : Integer) : Integer; stdcall; export;
begin
Result := 0;
case x of
0..3: Result := 1;
end;
end;
function GetFunctionInfo(x: Integer; var ProcAddr: Pointer; var ProcDef: PChar): Integer; stdcall; export;
begin
case x of
0:
begin
ProcAddr := @SPS_FindMapInMap;
StrPCopy(ProcDef, 'function SPS_FindMapInMap(out fx, fy: integer; LargeMap: T4DIntegerArray; SmallMap: T3DIntegerArray; tol: extended; out FoundMatches: integer): integer;');
end;
1:
begin
ProcAddr := @SPS_BitmapToMap;
StrPCopy(ProcDef, 'function SPS_BitmapToMap(bmp: TMufasaBitmap; SideLength: integer): T3DIntegerArray;');
end;
2:
begin
ProcAddr := @SPS_MakeColorBox;
StrPCopy(ProcDef, 'function SPS_MakeColorBox(bmp: TMufasaBitmap; x1, y1, SideLength: integer): TIntegerArray;');
end;
3:
begin
ProcAddr := @SPS_FilterMinimap;
StrPCopy(ProcDef, 'procedure SPS_FilterMinimap(var Minimap: TMufasaBitmap);');
end
else
x := -1;
end;
Result := x;
end;
exports SetPluginMemoryManager;
exports GetTypeCount;
exports GetTypeInfo;
exports GetFunctionCount;
exports GetFunctionInfo;
exports GetFunctionCallingConv;
begin
end.
Here's the sps.simba I used:
(*
SPS ~ SRL Positioning System
============================
Concept and original work done by marpis @ SRL-Forums.
*)
{$loadlib sps2}
const
// Path where all the SPS files are
SPS_IMG_PATH = IncludePath + 'SPS\img\';
SPS_IMG_FMT = '.png';
// Surfaces
RUNESCAPE_SURFACE = 0;
RUNESCAPE_SURFACE_FOLDER = 'runescape_surface\';
RUNESCAPE_DUNGEONS = 1;
RUNESCAPE_DUNGEONS_FOLDER = 'runescape_dungeons\';
// SPS Global variables
var
SPS_Debug: boolean;
SPS_Areas: TStringArray;
SPS_AreaMaps: T4DIntegerArray;
SPS_Tolerance, SPS_MinimumMatches: Extended;
SPS_Surface: integer;
function SPS_GatherMinimap: TMufasaBitmap;
var
c: TClient;
begin
try
Result := TMufasaBitmap.Create;
Result.SetSize(150, 150);
c := getTClient;
Result.CopyClientToBitmap(
c.IOManager, false, 0,0, MMCX-75, MMCY-75, MMCX+75, MMCY+75
);
except
Writeln('[ERROR] in SPS_GatherMinimap: '+ExceptionToString(ExceptionType, ExceptionParam));
end;
end;
procedure SPS_GetAreaCoords(Area: string; var x, y: integer);
var
p: integer;
begin
p := pos('_', Area);
if (p <= 0) then
begin
x := 0;
y := 0;
Exit;
end;
x := StrToIntDef(copy(Area, 1, p-1), -1);
y := StrToIntDef(copy(Area, p+1, Length(Area)-p), -1);
end;
// Converts a point from a map piece to a point on the entire map
function SPS_LocalToGlobal(Area: string; x, y: integer): TPoint;
var
cx, cy: integer;
begin
SPS_GetAreaCoords(Area, cx, cy);
Result.x := cx * 400 + x;
Result.y := cy * 400 + y;
end;
function SPS_GlobalToLocal(x, y: integer): TPoint;
var
cx, cy: integer;
begin
cx := Floor(x / 400);
cy := Floor(y / 400);
Result.x := x - cx * 400;
Result.y := y - cy * 400;
end;
function SPS_AreaCoordsToString(x, y: integer): string;
begin
Result := IntToStr(x)+'_'+IntToStr(y);
end;
function SPS_GetArea(Area: string; surface: integer): TMufasaBitmap;
var
x, y: integer;
S: string;
begin
SPS_GetAreaCoords(Area, x, y);
case surface of
RUNESCAPE_SURFACE: s := RUNESCAPE_SURFACE_FOLDER;
RUNESCAPE_DUNGEONS: s := RUNESCAPE_DUNGEONS_FOLDER;
end;
try
Result := TMufasaBitmap.Create;
Result.LoadFromFile(SPS_IMG_PATH + s + Area + SPS_IMG_FMT);
except
Writeln('[SPS] ERROR: SPS_GetArea: '+ExceptionToString(ExceptionType, ExceptionParam));
end;
end;
// SPS2
function SPS_GetMyPos: TPoint;
var
Minimap: TMufasaBitmap;
SmallMap: T3DIntegerArray;
t, map, FoundMatches: integer;
P: TPoint;
begin
Result := Point(-1, -1);
if (SPS_Tolerance = 0.0) then
SPS_Tolerance := 500.0;
if (SPS_MinimumMatches = 0.0) then
SPS_MinimumMatches := 0.35;
if not LoggedIn then
Exit;
if (inRange(round(rs_GetCompassAngleDegrees), 10, 350)) then
MakeCompass('N');
t := getSystemTime;
Minimap := SPS_GatherMinimap;
SPS_FilterMinimap(Minimap);
//DrawBitmapDebugImg(Minimap.Index);
//DisplayDebugImgWindow(150, 150);
SmallMap := [];
SmallMap := SPS_BitmapToMap(Minimap, 5);
map := SPS_FindMapInMap(P.x, P.y, SPS_AreaMaps, SmallMap, SPS_Tolerance, FoundMatches);
if (FoundMatches/706.0) > SPS_MinimumMatches then
Result := SPS_LocalToGlobal(SPS_Areas[map], P.x*5 + 75, P.y*5 + 75);
Minimap.Free;
t := GetSystemTime - t;
if SPS_Debug then
Writeln('[SPS] SPS_GetMyPos() finished in '+ToStr(t)+' ms. Result = '+ToStr(Result));
end;
// Finds position P in minimap by checking your own location
function SPS_PosToMM(P: TPoint): TPoint;
var
MyPos: TPoint;
begin
if not LoggedIn then Exit;
Result := Point(-1, -1);
MyPos := SPS_GetMyPos;
if Distance(MyPos.X, MyPos.Y, P.X, P.Y) < 75 then
Result := Point(MMCX + P.X - MyPos.X,
MMCY + P.Y - MyPos.Y);
end;
// Walks to position. If walking paths, please use WalkPath.
function SPS_WalkToPos(P: TPoint): boolean;
var
MM: TPoint;
begin
if not LoggedIn then Exit;
MM := SPS_PosToMM(P);
if (MM.X > 0) then
begin
Mouse(MM.X, MM.Y, 0, 0, True);
if WaitFunc(@IsMoving, 1, 3000 + random(500)) then
while IsMoving do
Flag;
Result := True;
end;
end;
// Returns true if the point "Pt" is on the minimap
function SPS_PosOnMM(Pt: TPoint): Boolean;
var
p: TPoint;
begin
p := SPS_PosToMM(Pt);
Result := rs_OnMinimap(p.x, p.y);
end;
// Walks the path "Path"; always walks to the furthest point possible
function SPS_WalkPath(Path: TPointArray): boolean;
var
I, H, T, D: integer;
P, MM: TPoint;
begin
H := High(Path);
T := GetSystemTime + 20000 + Random(5000);
while (not Result) and (GetSystemTime < T) do
begin
RunEnergy(20);
P := SPS_GetMyPos;
for I := H downto 0 do
begin
MM.X := MMCX + Path[I].X - P.X;
MM.Y := MMCY + Path[I].Y - P.Y;
D := Distance(MM.X, MM.Y, MMCX, MMCY);
if (D < 10) then
break
else begin
if (D < 70) then
begin
MouseFlag(MM.X, MM.Y, 0, 0, Integer(I<>H)*15);
T := getSystemTime + 20000 + Random(1000);
Break;
end;
end;
end;
Result := (I = H);
end;
end;
procedure SPS_Setup(surface: integer; Areas: TStringArray);
var
L, i, t: integer;
tmp: TMufasaBitmap;
begin
t := GetSystemTime;
L := Length(Areas);
SetLength(SPS_AreaMaps, L);
SetLength(SPS_Areas, L);
SPS_Surface := surface;
for i := 0 to (L - 1) do
begin
tmp := SPS_GetArea(Areas[i], surface);
SPS_AreaMaps[i] := SPS_BitmapToMap(tmp, 5);
SPS_Areas[i] := Areas[i];
tmp.Free;
end;
t := GetSystemTime - t;
Writeln('[SPS] SPS_Setup() took '+ToStr(t)+' ms. Loaded '+ToStr(L)+' areas.');
end;
I tested running around Lumbridge using this script. The path starts from Varrock south gates, IIRC.
program SPS2_Test;
{.include srl/srl.simba}
{.include sps/sps2.simba}
{.loadlib sps2.dll}
var
i: integer;
MyPath: TPointArray;
begin
SetupSRL;
SPS_Debug := True;
SPS_Setup(['11_7','11_8','11_9'], RUNESCAPE_SURFACE);
myPath := [Point(4678, 3132), Point(4654, 3154), Point(4628, 3166), Point(4597, 3173), Point(4575, 3179), Point(4553, 3192), Point(4547, 3208), Point(4540, 3233), Point(4542, 3250), Point(4542, 3271), Point(4544, 3294), Point(4542, 3328), Point(4539, 3348), Point(4546, 3386), Point(4549, 3416), Point(4537, 3437), Point(4508, 3457), Point(4503, 3477), Point(4503, 3491), Point(4503, 3527), Point(4496, 3551), Point(4483, 3567), Point(4478, 3590), Point(4480, 3614), Point(4488, 3632), Point(4516, 3636), Point(4535, 3645), Point(4552, 3663), Point(4576, 3672), Point(4606, 3677), Point(4627, 3669), Point(4649, 3666), Point(4678, 3668), Point(4700, 3673), Point(4695, 3694)];
if SPS_WalkPath(MyPath) then
writeln('askldfaklsdkljasd');
end.
I did not test the dungeons at all yet. I decided that dungeons are
not split into smaller areas, because it's so much easier to use them as areas when they are whole. Now when you want to use SPS in a dungeon, you use RUNESCAPE_DUNGEONS as the surface, set in SPS_Setup(). Ancient cavern is simply 'ancient_cavern', and so on. Runecrafting altars are found in the same place, /SPS/img/runescape_dungeons/.
A big big thankyou to
Hero for making the dungeon maps!