mixster
2009-08-30 21:20:21
Satellite Positioning Around Map / System.
Basically, it uses the built in RS world map to generate routes to various destinations.
At the moment, it's in a beta stage - I would like to see it go much further and it is functioning to some point, but not quite as much as I would like it too. The system basically collects all the road points it can find, puts them through TPAToATPA then collects midpoints to filter it out a bit for speed. Finally, it uses the A* algorithm to generate the shortest path from where you are to your destination, with where you are being the centre of the world map and in this first version the destination is a bank (not the nearest if there's 2 though).
So anyway, converting the path you generate to be able to use in RS is the next step. I initially use TPAToATPA again to make the steps much larger so that it's not crap loads of tiny steps and instead a medium amount of medium steps. After that, go through all the points and get the difference between the current point and the previous point to determine where to click then apply a ratio to it (75% default for world map = x and y offset from MMCX/Y would be * 4/3 or 1.333) and click! Continue this until the end and you should, hopefully, end up where you wanted to.
So, how to get the destination? That is the question! At the moment, I'm just using the bank symbol.
Now, how does this help if you can't get back? Well, you can! Just invert the TPA you used to walk and run through it again and you end up roughly where you began. Else, you know where you started and where you ended up, so you know roughly where you want to walk to and so could set that as your target, use the main map centred as your starting point then produce another path and walk that one!
Now, for actual codey stuff:
This is AStar in Scar for if you don't use the plugin or don't want to. Paste it at the top of the script.
This very ugly implementation of SPAMS *should* make it to the nearest bank and, although slightly less likely, *should* make it back to where it started. It requires that you are somewhere very close to a path though to start and I have been testing it around Draynor with walking from the mill north of Lumby to Draynor bank and back. It is very ugly and needs a lot of improvement, but it's just a beta version and it can be improved quite easily :)
So lastly, why am I sharing? The contributions of a dozen are better than just I, so it is more a public request to help set it up. At the moment, I have been thinking of converting the road related autocolour functions as part of SRL to work using the world map. Also, setting up a file of functions related to the world map - opening it, toggling functions, searching for places, clicking on the highlight buttons etc. Lastly, I still need some ideas of how to make this able to walk globally. A Method` of obtaining a world map and relating in game positions to it would be the best and if it's a static world map, then the road and so path finding could be improved heavily to make it kick ass.
Any questions, feel free to ask (oh and a debug image is saved in the Scar directory to show what it did, it initially does one of walkable nodes, then if it starts walking it draws the path it used at the end of the walk to) :)
Edit: Attached plugin, separated script into AStar and rest of script - the Scar version of AStar is a bit older, so plugin version will work slightly better. Sauce is attached with plugin, so
Edit2: Attached SPAMS.tar.gz which now uses Dijkstra. This version also comes with an old account of mine if you wish to steal it and a copy of the new SPAMS.scar file (that also has an awesome woodcutting procedure produce by Dan Cardin with adaptation by myself). If you do run it, you most likely want to remove the repeat … until (false); loop.
Basically, it uses the built in RS world map to generate routes to various destinations.
At the moment, it's in a beta stage - I would like to see it go much further and it is functioning to some point, but not quite as much as I would like it too. The system basically collects all the road points it can find, puts them through TPAToATPA then collects midpoints to filter it out a bit for speed. Finally, it uses the A* algorithm to generate the shortest path from where you are to your destination, with where you are being the centre of the world map and in this first version the destination is a bank (not the nearest if there's 2 though).
So anyway, converting the path you generate to be able to use in RS is the next step. I initially use TPAToATPA again to make the steps much larger so that it's not crap loads of tiny steps and instead a medium amount of medium steps. After that, go through all the points and get the difference between the current point and the previous point to determine where to click then apply a ratio to it (75% default for world map = x and y offset from MMCX/Y would be * 4/3 or 1.333) and click! Continue this until the end and you should, hopefully, end up where you wanted to.
So, how to get the destination? That is the question! At the moment, I'm just using the bank symbol.
Now, how does this help if you can't get back? Well, you can! Just invert the TPA you used to walk and run through it again and you end up roughly where you began. Else, you know where you started and where you ended up, so you know roughly where you want to walk to and so could set that as your target, use the main map centred as your starting point then produce another path and walk that one!
Now, for actual codey stuff:
This is AStar in Scar for if you don't use the plugin or don't want to. Paste it at the top of the script.
type
node = record
parent: Integer;
x, y: Integer;
f, g, h: Integer;
cl, op: Boolean;
end;
function FilterPointsDist2(var inds: TIntegerArray; x, y, dist: Integer; pts: TPointArray): Boolean;
var
h, i, p: Integer;
begin
Result := False;
p := 0;
h := High(pts);
SetLength(inds, h + 1);
for i := 0 to h do
begin
if (Distance(pts[i].x, pts[i].y, x, y) <= dist) then
begin
inds[p] := i;
p := p + 1;
end;
end;
SetLength(inds, p);
Result := p <> 0;
end;
function AStarCus(var path: TPointArray; pts: TPointArray; stI, enI, lowFilt, highFilt: Integer): Boolean;
var
nodes: array of node;
close, open, neigh: TIntegerArray;
en: TPoint;
ph, ch, oh, i, ti, tf: Integer;
begin
Result := False;
SetLength(path, 0);
if (Length(pts) < Max(enI, stI)) then
begin
Writeln('Invalid stI or enI passed to AStar');
exit;
end;
ph := High(pts) + 1;
SetLength(close, ph);
SetLength(open, ph);
SetLength(nodes, ph);
en := pts[enI];
ph := ph - 1;
for i := 0 to ph do
begin
with nodes[i] do
begin
parent := -1;
x := pts[i].x;
y := pts[i].y;
g := 0;
h := Round(Distance(x, 0, en.x, 0) + Distance(0, y, 0, en.y));
f := g + h;
cl := False;
op := False;
end;
end;
oh := 0;
ch := -1;
open[0] := stI;
nodes[stI].op := True;
repeat
if (oh = -1) then
begin
Writeln('Ran out of open nodes, exiting...');
exit;
end;
ti := 0;
tf := nodes[open[0]].f;
for i := 1 to oh do
begin
if (nodes[open[i]].f < tf) then
begin
ti := i;
tf := nodes[open[i]].f;
end;
end;
i := ti;
ti := open[ti];
nodes[ti].cl := True;
nodes[ti].op := False;
Swap(open[i], open[oh]);
oh := oh - 1;
ch := ch + 1;
close[ch] := ti;
if (Distance(nodes[ti].x, nodes[ti].y, en.x, en.y) <= highFilt) then
break;
if (not (FilterPointsDist2(neigh, nodes[ti].x, nodes[ti].y, lowFilt, pts))) then
if (not (FilterPointsDist2(neigh, nodes[ti].x, nodes[ti].y, highFilt, pts))) then
continue;
for i := 0 to High(neigh) do
begin
if (nodes[neigh[i]].cl) then
continue;
if (nodes[neigh[i]].op) then
begin
if (nodes[ti].g < nodes[neigh[i]].g) then
begin
nodes[neigh[i]].parent := ti;
nodes[neigh[i]].g := nodes[ti].g + Round(Distance(nodes[ti].x, nodes[ti].y, nodes[neigh[i]].x, nodes[neigh[i]].y));
nodes[neigh[i]].f := nodes[neigh[i]].g + nodes[neigh[i]].h;
end;
end
else
begin
nodes[neigh[i]].parent := ti;
nodes[neigh[i]].g := nodes[ti].g + Round(Distance(nodes[ti].x, nodes[ti].y, nodes[neigh[i]].x, nodes[neigh[i]].y));
nodes[neigh[i]].f := nodes[neigh[i]].g + nodes[neigh[i]].h;
nodes[neigh[i]].op := True;
oh := oh + 1;
open[oh] := neigh[i];
end;
end;
until False;
SetLength(path, 0);
repeat
SetLength(path, High(path) + 2);
path[High(path)] := IntToPoint(nodes[ti].x, nodes[ti].y);
ti := nodes[ti].parent;
until (nodes[ti].parent = -1);
SetLength(path, High(path) + 2);
path[High(path)] := IntToPoint(nodes[ti].x, nodes[ti].y);
ti := nodes[ti].parent;
InvertTPA(path);
Result := True;
end;{.include SRL/SRL/misc/Smart.scar}
{.include SRL/SRL.scar}
procedure DeclarePlayers;
begin
HowManyPlayers := 1;
NumberOfPlayers(HowManyPlayers);
CurrentPlayer := 0;
with Players[0] do
begin
Name := 'retsxim';
Pass := '0mgl0luTr13dTh15';
Active := True;
end;
end;
const
WMX1 = 7;
WMX2 = 756;
WMY1 = 7;
WMY2 = 460;
WMCX = 375;
WMCY = 232;
CRAP = 1.33333;
var
b, t, w, h, x, y: Integer;
p, a: TPointArray;
ap: array of TPointArray;
begin
Writeln('Begin');
SetupSRL;
DeclarePlayers;
Writeln('Finished setting up');
Wait(5000);
Writeln('Logging in now');
LoginPlayer;
w := 764;
h := 504;
Wait(5000);
Writeln('Logged...');
Mouse(MMX1 - 15, MMY2 - 18, 4, 4, True);
Writeln('Opened map');
Wait(5000);
x := GetColor(100, 100);
if (not (SimilarColors(x, clBlack, 5))) then
begin
Writeln('Looks like the map loaded uber fast, hoping it isn''t a mistake');
Wait(2500);
end else
begin
Writeln('Picked color ' + IntToStr(x));
t := GetSystemTime + 12000;
while ((GetSystemTime < t) and (GetColor(100, 100) = x)) do
Wait(999);
if (GetSystemTime >= t) then
begin
Writeln('Opening the world map seems to have lagged a bit - giving it an extra 15 seconds max');
t := GetSystemTime + 15000;
while ((GetSystemTime < t) and (GetColor(100, 100) = x)) do
Wait(999);
end;
if (GetColor(100, 100) = x) then
begin
Writeln('Looks like epic failure on the map loading front, terminating...');
TerminateScript;
end;
end;
Mouse(MMCX, MMCY, 100, 100, True);
Writeln('Anti-log click');
Writeln('Now trying to detect a nearby path colour');
for b := 1 to 8 do
if FindColorSpiralTolerance(x, y, 7434361, WMCX - 15, WMCX - 15, WMCX + 15, WMCY + 15, b * 5) then
break;
if b = 9 then
begin
FindColorsTolerance(p, 7434361, WMX1, WMY1, WMX2, WMY2, 50);
Writeln('Finding nearby road colour didn''t work too well, so using preset colour with high tolerance');
end
else
begin
FindColorsTolerance(p, GetColor(x, y), WMX1, WMY1, WMX2, WMY2, 10);
Writeln('Finding nearby road colour succeeded (hopefully)');
end;
if (Length(p) < 100) then
begin
Writeln('First search failed... searching with steroids!');
FindColorsTolerance(p, 7434361, WMX1, WMY1, WMX2, WMY2, 60);
end;
if (Length(p) < 100) then
begin
Writeln('Darn, looks like our path locating flailed :<');
Logout;
TerminateScript;
end;
Writeln('Found the paths!');
FindColorsTolerance(a, 12632256, WMX1, WMY1, WMX2, WMY2, 15);
if(Length(a) < 50) then
begin
Writeln('First symbol colour finding failed, pumping up');
FindColorsTolerance(a, 12632256, WMX1, WMY1, WMX2, WMY2, 25);
end;
if(Length(a) < 50) then
Writeln('Symbol colour finding failed again, so let''s hope we won''t need it')
else
Writeln('Found the symbols');
p := CombineTPA(p, a);
Writeln('Have a total of ' + IntToStr(Length(p)) + ' points found');
ap := TPAtoATPA(p, 3);
SetLength(p, Length(ap));
b := BitmapFromString(w, h, '');
SafeCopyCanvas(GetClientCanvas, GetBitmapCanvas(b), 0, 0, w, h, 0, 0, w, h);
for x := 0 to High(ap) do
begin
p[x] := MiddleTPA(ap[x]);
FastSetPixel(b, p[x].x, p[x].y, clRed);
end;
GetBitmapCanvas(b).Ellipse(WMCX - 1, WMCY - 1, WMCX + 1, WMCY + 1);
SaveBitmap(b, AppPath + 'debug.bmp');
Writeln(AppPath + 'debug.bmp');
x := -1;
repeat
if FindSymbolIn(x, y, 'bank', 100, 50, w - 100, h - 50) then
break;
SymbolAccuracy := SymbolAccuracy - 0.1;
until SymbolAccuracy < 0.4;
if (x = -1) then
begin
Writeln('Failed a bit at finding a bank, our bad');
Logout;
TerminateScript;
end;
SortTPAFrom(p, Point(x, y));
InvertTPA(p);
SetLength(p, Length(p) + 1);
InvertTPA(p);
p[0] := Point(WMCX, WMCY);
Writeln('Going from ' + IntToStr(p[0].x) + ',' + IntToStr(p[0].y));
Writeln('Going to ' + IntToStr(p[1].x) + ',' + IntToStr(p[1].y) + '(' + FloatToStr(SymbolAccuracy) + ')');
Writeln(IntToStr(Length(p)));
t := GetSystemTime;
AStarCus(a, p, 0, 1, 8, 14);
t := GetSystemTime - t;
Writeln('Took ' + IntToStr(t) + 'ms on first run');
if (Length(a) = 0) then
begin
Writeln('Darn, it failed :<');
Writeln('Lets try pumping up the filter and crossing fingers!');
t := GetSystemTime;
AStarCus(a, p, 0, 1, 8, 18);
t := GetSystemTime - t;
Writeln('Took ' + IntToStr(t) + 'ms on second run');
end;
if (Length(a) = 0) then
begin
Writeln('Yup, it failed...');
Logout;
TerminateScript;
end;
Writeln('Path made!');
Mouse(740, 10, 3, 3, True);
Wait(2500);
ap := TPAtoATPA(a, 15);
SetLength(a, Length(ap));
for x := 0 to High(a) do
a[x] := MiddleTPA(ap[x]);
SetLength(p, Length(a));
p[0] := Point(a[0].x - MMCX, a[0].y - MMCY);
GetBitmapCanvas(b).MoveTo(a[0].x, a[0].y);
for x := 1 to High(a) do
begin
GetBitmapCanvas(b).LineTo(a[x].x, a[x].y);
p[x] := Point(Round((a[x].x - a[x - 1].x) * CRAP), Round((a[x].y - a[x - 1].y) * CRAP));
MouseMM(MMCX + p[x].x, MMCY + p[x].y, 1, 1);
Flag;
Wait(500 + Random(500));
end;
SaveBitmap(b, AppPath + 'debug.bmp');
if (FindSymbolIn(x, y, 'bank', MMX1, MMY1, MMX2, MMY2)) then
begin
Writeln('Wow, we made it to a bank! I would like to thank so many people for making this possible. My parents, Man Flardin, R0b0t1, Markus - all of you, so much love!');
Writeln('Now, for added win, let''s try walking back!');
end
else
begin
SymbolAccuracy := 0.5;
Writeln('Lowered symbol accuracy in case it was a false negative');
if (FindSymbolIn(x, y, 'bank', MMX1, MMY1, MMX2, MMY2)) then
begin
Writeln('Wow, we made it to a bank! I would like to thank so many people for making this possible. My parents, Man Flardin, R0b0t1, Markus - all of you, so much love!');
Writeln('Now, for added win, let''s try walking back!');
end
else
begin
Writeln('Well, we walked but did not make it to the bank. Darn :<');
Logout;
TerminateScript;
end;
end;
InvertTPA(a);
p[0] := Point(a[0].x - MMCX, a[0].y - MMCY);
for x := 1 to High(a) do
begin
p[x] := Point(Round((a[x].x - a[x - 1].x) * CRAP), Round((a[x].y - a[x - 1].y) * CRAP));
MouseMM(MMCX + p[x].x, MMCY + p[x].y, 1, 1);
Flag;
Wait(500 + Random(500));
end;
Writeln('End');
end.
This very ugly implementation of SPAMS *should* make it to the nearest bank and, although slightly less likely, *should* make it back to where it started. It requires that you are somewhere very close to a path though to start and I have been testing it around Draynor with walking from the mill north of Lumby to Draynor bank and back. It is very ugly and needs a lot of improvement, but it's just a beta version and it can be improved quite easily :)
So lastly, why am I sharing? The contributions of a dozen are better than just I, so it is more a public request to help set it up. At the moment, I have been thinking of converting the road related autocolour functions as part of SRL to work using the world map. Also, setting up a file of functions related to the world map - opening it, toggling functions, searching for places, clicking on the highlight buttons etc. Lastly, I still need some ideas of how to make this able to walk globally. A Method` of obtaining a world map and relating in game positions to it would be the best and if it's a static world map, then the road and so path finding could be improved heavily to make it kick ass.
Any questions, feel free to ask (oh and a debug image is saved in the Scar directory to show what it did, it initially does one of walkable nodes, then if it starts walking it draws the path it used at the end of the walk to) :)
Edit: Attached plugin, separated script into AStar and rest of script - the Scar version of AStar is a bit older, so plugin version will work slightly better. Sauce is attached with plugin, so
Edit2: Attached SPAMS.tar.gz which now uses Dijkstra. This version also comes with an old account of mine if you wish to steal it and a copy of the new SPAMS.scar file (that also has an awesome woodcutting procedure produce by Dan Cardin with adaptation by myself). If you do run it, you most likely want to remove the repeat … until (false); loop.