SA-MP Forums

Go Back   SA-MP Forums > SA-MP Scripting and Plugins > Plugin Development

Reply
 
Thread Tools Display Modes
Old 24/12/2018, 12:27 AM   #1
kvann
Huge Clucker
 
kvann's Avatar
 
Join Date: Jun 2012
Location: Estonia
Posts: 413
Reputation: 173
Default Modern GPS plugin

SA-MP GPS Plugin



This plugin offers a way of accessing the data of San Andreas map nodes and finding paths between them. It is intended to be a modern and straightforward replacement for RouteConnector. The plugin uses a simple implementation of the A* algorithm for pathfinding. Finding a path from the top-leftmost node on the map to the bottom-rightmost node that consists of 684 nodes takes just a few milliseconds. It is worth noting that this is my first successful project in C++ and the last time I tried writing something in C++ was over half a year ago so there is a lot of room for improvement and I am planning to continue improving the code of this plugin as I progress.

Advantages over RouteConnector
  • Safer API - Unlike RouteConnector, this plugins does not give you an array of nodes as the result of pathfinding. Instead of that, it gives you the ID of the found path that can be used later on. Each function (except IsValidMapNode, IsValidPath and GetHighestMapNodeID) returns an error code and the real result of them is passed by reference.
  • Compatibility - RouteConnector has a compatibility issue with some part of YSI that makes it call a wrong public function instead of the actual GPS_WhenRouteIsCalculated callback. This plugin lets you call a custom callback and pass arguments to it. In addition to that, RouteConnector uses Intel Threading Building Blocks for threading that has caused numerous compatibility (and PEBCAK) issues on Linux servers. This plugin uses std::thread for threading and does not have any dependencies. This plugin is also compatible with PawnPlus and supports asynchronous pathfinding out of box.
  • Active development - RouteConnector has not been updated for over three years. As I said previously, I am planning to continue active development of this plugin for a long period of time.
  • Performance - I have not done any benchmarks, but even with older versions users claimed that this plugin is multiple times faster than RouteConnector. A fix in the algorithm in version 1.2.0 made it around 30 times faster than it previously was.

Disadvantages over RouteConnector
  • Functionality - At the current time, this plugin only replaces RouteConnector in the areas of accessing map node data and pathfinding. There is no way of managing nodes or their connections in runtime or modifying the GPS.dat file, however these features will be added in future versions.

Installation

Simply install to your project:

Code:
sampctl package install kristoisberg/samp-gps-plugin
Include in your code and begin using the library:

Code:
#include <GPS>
API

Functions

bool:IsValidMapNode(MapNode:nodeid)
  • Returns if the map node with the specified ID is valid.

GetMapNodePos(MapNode:nodeid, &Float:x, &Float:y, &Float:z)
  • If the specified map node is valid, returns GPS_ERROR_NONE and passes the position of it to x, y and z, otherwise returns GPS_ERROR_INVALID_NODE.

GetDistanceBetweenMapNodes(MapNode:first, MapNode:second, &Float:distance)
  • If both of the specified map nodes are valid, returns GPS_ERROR_NONE and passes the distance between them to distance, otherwise returns GPS_ERROR_INVALID_NODE.

GetAngleBetweenMapNodes(MapNode:first, MapNode:second, &Float:angle)
  • If both of the specified map nodes are valid, returns GPS_ERROR_NONE and passes the angle between them to angle, otherwise returns GPS_ERROR_INVALID_NODE.

GetMapNodeDistanceFromPoint(MapNode:nodeid, Float:x, Float:y, Float:z, &Float:distance)
  • If the specified map node is valid, returns GPS_ERROR_NONE and passes the distance of the map node from the specified position to distance, otherwise returns GPS_ERROR_INVALID_NODE.

GetMapNodeAngleFromPoint(MapNode:nodeid, Float:x, Float:y, &Float:angle)
  • If the specified map node is valid, returns GPS_ERROR_NONE and passes the angle of the map node from the specified position to angle, otherwise returns GPS_ERROR_INVALID_NODE.

GetClosestMapNodeToPoint(Float:x, Float:y, Float:z, &MapNode:nodeid, MapNode:ignorednode = INVALID_MAP_NODE_ID)
  • Passes the ID of the closest map node to the specified position to nodeid. If ignorednode is specified and it is the closest node to the position, it is ignored and the ID of the next closest node is passed to nodeid instead. Returns GPS_ERROR_INVALID_NODE if no nodes exist, otherwise returns GPS_ERROR_NONE.

GetMapNodeConnectionCount(MapNode:nodeid, &count)
  • If the specified map node is valid, returns GPS_ERROR_NONE and passes the amount of its connections to count, otherwise returns GPS_ERROR_INVALID_NODE. If count is larger than 2, the node is an intersection.

GetHighestMapNodeID()
  • Returns the ID of the map node with the highest ID. Could be used for iteration purposes.

GetRandomMapNode(&MapNode:nodeid)
  • Passes the ID of a random map node, found using Mersenne Twister, to nodeid. Returns GPS_ERROR_INVALID_NODE if no map nodes exist, otherwise returns GPS_ERROR_NONE.

FindPath(MapNode:start, MapNode:target, &Path:pathid)
  • If both of the specified map nodes are valid, returns GPS_ERROR_NONE and tries to find a path from start to target and pass its ID to pathid, otherwise returns GPS_ERROR_INVALID_NODE. If pathfinding fails, returns GPS_ERROR_INVALID_PATH.

FindPathThreaded(MapNode:start, MapNode:target, const callback[], const format[] = "", {Float, _}:...)
  • If both of the specified map nodes are valid, returns GPS_ERROR_NONE and tries to find a path from start to target. After pathfinding is finished, calls the specified callback and passes the path ID (could be INVALID_PATH_ID if pathfinding fails) and the specified arguments to it.

Task:FindPathAsync(MapNode:start, MapNode:target)
  • Pauses the current function and continues it after it is finished. Throws an AMX error if pathfinding fails for any reason. Only available if PawnPlus is included before GPS. Usage explained below.

bool:IsValidPath(Path:pathid)
  • Returns if the path with the specified ID is valid.

GetPathSize(Path:pathid, &size)
  • If the specified path is valid, returns GPS_ERROR_NONE and passes the amount of nodes in it to size, otherwise returns GPS_ERROR_INVALID_PATH.

GetPathNode(Path:pathid, index, &MapNode:nodeid)
  • If the specified path is valid and the index contains a node, returns GPS_ERROR_NONE and passes the ID of the node at that index to nodeid, otherwise returns GPS_ERROR_INVALID_PATH or GPS_ERROR_INVALID_NODE depending on the error.

GetPathNodeIndex(Path:pathid, MapNode:nodeid, &index)
  • If the specified path is valid and the specified map node is a part of the path, returns GPS_ERROR_NONE and passes the index of the map node to index, otherwise returns GPS_ERROR_INVALID_PATH or GPS_ERROR_INVALID_NODE depending on the error.

GetPathLength(Path:pathid, &Float:length)
  • If the specified path is valid, returns GPS_ERROR_NONE and passes the length of the path in metres to length, otherwise returns GPS_ERROR_INVALID_PATH.

DestroyPath(Path:pathid)
  • If the specified path is valid, returns GPS_ERROR_NONE and destroys the path, otherwise returns GPS_ERROR_INVALID_PATH.

Error codes
  • GPS_ERROR_NONE - The function was executed successfully.
  • GPS_ERROR_INVALID_PARAMS - An invalid amount of arguments was passed to the function. Should never happen without the PAWN compiler noticing it unless the versions of the plugin and include are different.
  • GPS_ERROR_INVALID_PATH - An invalid path ID as passed to the function or threaded pathfinding was not successful.
  • GPS_ERROR_INVALID_NODE - An invalid map node ID/index was passed to the function or GetClosestMapNodeToPoint or GetRandomMapNode failed because no map nodes exist.
  • GPS_ERROR_INTERNAL - An internal error happened - threaded pathfinding failed because dispatching a thread failed.

Examples

Threaded pathfinding

Finding a path from the position of the player to the LSPD building.

Code:
CMD:pathtols(playerid) {
    new Float:x, Float:y, Float:z, MapNode:start;
    GetPlayerPos(playerid, x, y, z);

    if (GetClosestMapNodeToPoint(x, y, z, start) != GPS_ERROR_NODE) {
        return SendClientMessage(playerid, COLOR_RED, "Finding a node near you failed, GPS.dat was not loaded.");
    }

    new MapNode:target;

    if (GetClosestMapNodeToPoint(1258.7352, -2036.7100, 59.4561, target)) { // this is also valid since the value of GPS_ERROR_NODE is 0.
        return SendClientMessage(playerid, COLOR_RED, "Finding a node near LSPD failed, GPS.dat was not loaded.");
    }

    if (FindPathThreaded(start, target, "OnPathToLSFound", "ii", playerid, GetTickCount())) {
        return SendClientMessage(playerid, COLOR_RED, "Pathfinding failed for some reason, you should store this error code and print it out since there are multiple ways it could fail.");
    }

    SendClientMessage(playerid, COLOR_WHITE, "Finding the path...");
    return 1;
}


forward public OnPathToLSFound(Path:pathid, playerid, start_time);
public OnPathToLSFound(Path:pathid, playerid, start_time) {
    if (!IsValidPath(pathid)) {
        return SendClientMessage(playerid, COLOR_RED, "Pathfinding failed!");
    }

    new string[128], size, length;
    GetPathSize(size);
    GetPathLength(length);

    format(string, sizeof(string), "Found a path in %ims. Amount of nodes: %i, length: %fm.", GetTickCount() - start_time, size, length);

    new MapNode:nodeid, Float:x, Float:y, Float:z;

    for (new index; index < size; index++) {
        GetPathNode(pathid, index, nodeid);
        GetMapNodePos(nodeid, x, y, z);
        CreateDynamicPickup(1318, 1, x, y, z);
    }

    DestroyPath(pathid);
    return 1;
}
Asynchronous pathfinding

What if you could continue the process within the command while still taking advantage of the benefits of threaded pathfinding? You can, using the magic of PawnPlus tasks.

Code:
CMD:pathtols(playerid) {
    new Float:x, Float:y, Float:z, MapNode:start;
    GetPlayerPos(playerid, x, y, z);

    if (GetClosestMapNodeToPoint(x, y, z, start)) {
        return SendClientMessage(playerid, COLOR_RED, "Finding a node near you failed, GPS.dat was not loaded.");
    }

    new MapNode:target;

    if (GetClosestMapNodeToPoint(1258.7352, -2036.7100, 59.4561, target)) { 
        return SendClientMessage(playerid, COLOR_RED, "Finding a node near LSPD failed, GPS.dat was not loaded.");
    }

    SendClientMessage(playerid, COLOR_WHITE, "Finding the path...");

    new Path:pathid = task_await(FindPathAsync(start, target)); // no error handling here, an AMX error will be thrown instead if the pathfinding fails

    new string[128], size, length;
    GetPathSize(size);
    GetPathLength(length);

    format(string, sizeof(string), "Found a path in %ims. Amount of nodes: %i, length: %fm.", GetTickCount() - start_time, size, length);

    new MapNode:nodeid, Float:x, Float:y, Float:z, index;

    while (!GetPathNode(pathid, index, nodeid)) // also note the alternative method of iterating through path nodes here
        GetMapNodePos(nodeid, x, y, z);
        CreateDynamicPickup(1318, 1, x, y, z);

        index++;
    }

    DestroyPath(pathid);
    return 1;
}
Testing

To test, simply run the package:

Code:
sampctl package run
Credits
  • kvann - Creator of the plugin.
  • Gamer_Z - Creator of the original RouteConnector plugin which helped me understand the structure of GPS.dat and influenced this plugin a lot, the author of the original GPS.dat.
  • NaS - Author of the fixed GPS.dat distributed with the plugin.
  • Southclaws, IllidanS4 - Helped me with the plugin in major ways (there were other helpful people as well, I appreciate it all).
__________________






Last edited by kvann; 01/01/2019 at 03:52 PM.
kvann is offline   Reply With Quote
Old 24/12/2018, 12:37 AM   #2
Alexis17
Little Clucker
 
Join Date: Nov 2017
Posts: 35
Reputation: 0
Default Re: Modern GPS plugin

I had thought to use Router Connector, but ĦBut a more active plugin !, I congratulate you boy!
__________________
RIP SPANISH

Alexis17 is offline   Reply With Quote
Old 24/12/2018, 01:49 AM   #3
RogueDrifter
High-roller
 
RogueDrifter's Avatar
 
Join Date: Dec 2017
Location: SA-MP Drifting world.
Posts: 1,242
Reputation: 379
Default Re: Modern GPS plugin

Very modern, much wow.
__________________
Be creative.

[Github]:Link [Gists]:Link [Forum]:Link [Server]:Link [Discord]:Link


RogueDrifter is offline   Reply With Quote
Old 24/12/2018, 03:40 AM   #4
Crayder
High-roller
 
Crayder's Avatar
 
Join Date: Sep 2013
Location: Flames of Hell
Posts: 3,852
Reputation: 610
Default Re: Modern GPS plugin

Could you make a way to create our own GPS.dat like RouteConnector?
__________________
Those who deserve reputation, do not need to beg for it.
Also, don't expect the help you need when offering reputation, you'll just be attracting Rep Hunters.
Join SA-MP Discord!
Crayder is offline   Reply With Quote
Old 24/12/2018, 10:55 AM   #5
[HLF]Southclaw
Godfather
 
[HLF]Southclaw's Avatar
 
Join Date: Apr 2009
Location: England
Posts: 5,016
Reputation: 1581
Default Re: Modern GPS plugin

Southclaws approved! Nice work!
__________________
Tools:

Plugins:

Links:

[HLF]Southclaw is offline   Reply With Quote
Old 24/12/2018, 11:40 AM   #7
PT
Godfather
 
PT's Avatar
 
Join Date: Nov 2012
Location: Portugal
Posts: 7,216
Reputation: 780
Default Re: Modern GPS plugin

Very very Nice job!

-

Happy Christmas ��
__________________
"Quem caiu e se levantou honrado era e honrado ficou..."


CVU vai voltar e.e


http://steamcommunity.com/id/pt_player/
PT is offline   Reply With Quote
Old 24/12/2018, 02:10 PM   #8
Logic_
High-roller
 
Logic_'s Avatar
 
Join Date: Jun 2015
Location: Pakistan
Posts: 1,570
Reputation: 290
Default Re: Modern GPS plugin

Mashallah. Good work.
__________________
Developer @ Nevada State Prison RP/ Core Roleplay;

Cheap game-host (SA-MP, SA-MP Hosted tab, G-MOD etc) @ www.prestigesteve.com

Former developer @ COD:BO3, COD:AW, I:RP, PC:RP, NEG, ZL TDM, CookieDM and IW:TDM.

  • You can't forcefully correct someone and you shouldn't waste your time on it because you can bring a horse to water but can't force it to drink.
  • Please don't message me asking for help, because by doing that, you're only limiting your support to one person when the whole community can help you here. Refer to scripting help section instead.
Logic_ is offline   Reply With Quote
Old 24/12/2018, 04:01 PM   #9
Marshall32
Big Clucker
 
Join Date: Jun 2011
Posts: 186
Reputation: 48
Default Re: Modern GPS plugin

Good job
Marshall32 is offline   Reply With Quote
Old 24/12/2018, 05:05 PM   #10
cuber
Gangsta
 
cuber's Avatar
 
Join Date: Oct 2016
Location: CookieDM - https://discord.gg/p7ahv8s
Posts: 920
Reputation: 171
Default Re: Modern GPS plugin

very std::thread, very wow.

will use it for sure.
cuber is offline   Reply With Quote
Reply

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
[Plugin] Chrono - Modern Plugin for Working With Dates and Times [HLF]Southclaw Plugin Development 48 29/01/2019 07:59 PM
[Map] Modern interior Caster Maps 5 22/10/2017 03:43 PM
[Map] Modern Apartment leosmile Maps 11 07/10/2017 10:21 PM
[Map] Modern House Jonask Maps 11 17/02/2016 11:28 PM
[GameMode] [GM] Modern T-DM v1.0 Soumi Gamemode Scripts 49 07/07/2012 09:10 AM


All times are GMT. The time now is 08:31 PM.


Powered by vBulletin® Version 3.8.6
Copyright ©2000 - 2019, Jelsoft Enterprises Ltd.