Skip to content

Commit

Permalink
readme, uppercased filenames
Browse files Browse the repository at this point in the history
  • Loading branch information
Kristo Isberg committed Dec 24, 2018
1 parent fe4e6d8 commit 5621a06
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 39 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ samp.ban
Debug/
Release/
build/
x64/
.vs/
*.so
*.o
Expand Down
202 changes: 172 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,22 @@
# samp-gps-plugin
# SA-MP GPS Plugin

[![sampctl](https://shields.southcla.ws/badge/sampctl-samp--gps--plugin-2f2f2f.svg?style=for-the-badge)](https://github.com/kristoisberg/samp-gps-plugin)

<!--
Short description of your library, why it's useful, some examples, pictures or
videos. Link to your forum release thread too.
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 under one second and most shorter paths only take 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.

Remember: You can use "forumfmt" to convert this readme to forum BBCode!

What the sections below should be used for:
### Advantages over RouteConnector

`## Installation`: Leave this section un-edited unless you have some specific
additional installation procedure.
* **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` and `IsValidPath`) 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.

`## Testing`: Whether your library is tested with a simple `main()` and `print`,
unit-tested, or demonstrated via prompting the player to connect, you should
include some basic information for users to try out your code in some way.

And finally, maintaining your version number`:
### Disadvantages over RouteConnector

* Follow [Semantic Versioning](https://semver.org/)
* When you release a new version, update `VERSION` and `git tag` it
* Versioning is important for sampctl to use the version control features
* **Performance** - I highly doubt that the plugin performs better than RouteConnector, but since most pathfinding tasks only take a fraction of a second, there should be absolutely no performance issues as long as threaded/asynchronous pathfinding functions are used.
* **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.

Happy Pawning!
-->

## Installation

Expand All @@ -37,27 +29,177 @@ sampctl package install kristoisberg/samp-gps-plugin
Include in your code and begin using the library:

```pawn
#include <samp-gps-plugin>
#include <GPS>
```

## Usage

<!--
Write your code documentation or examples here. If your library is documented in
the source code, direct users there. If not, list your API and describe it well
in this section. If your library is passive and has no API, simply omit this
section.
-->
## API

## Testing

<!--
Depending on whether your package is tested via in-game "demo tests" or
y_testing unit-tests, you should indicate to readers what to expect below here.
-->
### 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`.

`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`.

`GetClosestMapNodeToPoint(Float:x, Float:y, Float:z, &MapNode:nodeid, MapNode:ignorednode = INVALID_MAP_NODE_ID)`
* Passes 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 next closest node is returned instead.

`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.

`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` failed because `GPS.dat` failed to load.
* `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.

```pawn
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, 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.

```pawn
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, 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:

```bash
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. Also the author of the original GPS.dat?
* NaS - Author of the fixed GPS.dat distributed with the plugin.
* Southclaws, IllidanS4, Yashas, J0sh, Johnson_boy/lassir - Helped me with the plugin in some way or another.
2 changes: 1 addition & 1 deletion makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
GPP = g++
GCC = gcc
OUTPUT = "./build/gps.so"
OUTPUT = "./build/GPS.so"
CC_PARAMS =-std=c++17 -c -m32 -fPIC -O3 -w -DLINUX -I./lib/sdk/amx/

all:
Expand Down
12 changes: 6 additions & 6 deletions pawn.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,28 @@
},
"resources": [
{
"name": "gps-windows.zip",
"name": "GPS-windows.zip",
"platform": "windows",
"archive": true,
"includes": [
"gps.inc"
"GPS.inc"
],
"plugins": [
"gps.dll"
"GPS.dll"
],
"files": {
"GPS.dat": "scriptfiles/GPS.dat"
}
},
{
"name": "gps-linux.zip",
"name": "GPS-linux.zip",
"platform": "linux",
"archive": true,
"includes": [
"gps.inc"
"GPS.inc"
],
"plugins": [
"gps.so"
"GPS.so"
],
"files": {
"GPS.dat": "scriptfiles/GPS.dat"
Expand Down
8 changes: 7 additions & 1 deletion src/natives.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,15 @@ namespace Natives {
return GPS_ERROR_INVALID_NODE;
}

int result = Pathfinder::FindPath(start, target);

if (result == -1) {
return GPS_ERROR_INVALID_PATH;
}

cell* addr = NULL;
amx_GetAddr(amx, params[3], &addr);
*addr = Pathfinder::FindPath(start, target);
*addr = result;

return GPS_ERROR_NONE;
}
Expand Down
2 changes: 1 addition & 1 deletion test.pwn
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#include <a_samp>
#include <PawnPlus>

#include "gps.inc"
#include "GPS.inc"


#define THREADING_TEST_COUNT 100
Expand Down

0 comments on commit 5621a06

Please sign in to comment.