[FilterScript] Dynamic Map System
#1

Dynamic Map System
Dynamically load a map / set of objects from a SQLite database
This filterscript is simply as the title says a Dynamic Map System. It connects to a (SQLite) database(namely "objects.db") executes a query and loads objects matching the query(which simply checks for objects in the table with the spawn column equaling 1).

When the filterscript exits the database connection is closed and all created objects(created by the filterscript) are destroyed from the server. The GitHub repository has two versions of the filterscript, one using dynamic objects(using the streamer plugin) and one using native SA:MP objects(CreateObject).

The GitHub repository also has an "import.pwn" filterscript which you can use to convert your file based objects into a database format. Simply replace CreateObject or CreateDynamicObject with ImportObject and copy the objects into the OnFilterScriptInit callback. Then run the import.amx script ONCE so it loads into the database. You can also copy the ImportObject function and automate it into your script for easier and more efficient use.

You can simply set the `Spawn` column to 0 if you want it in the DB but not to spawn. As of now, the filterscript only supports the position & parameter(plus the obvious objectID parameter) for both the streamer and normal versions.

Download: GitHub
Reply
#2

Nice nice nice. Usefull.

Good job mate
Reply
#3

I haven't looked at the source code, but by the looks of it, it would be nice to have something like this for an event system. Also, I created a map voting/management system a while back, mind including it here or something? It would make this somehow more complex and it might turn out to be good for events and such.

pawn Код:
// [ DEVELOPMENT GAMEMODE ]

// INCLUDES:

#include <a_samp>
#include <sscanf2>
#include <foreach>
#include <zcmd>

// DEFINES:

// GENERAL:

#define MAX_MAPS 100
#define MAX_MAP_INFO 2
#define MAX_PAGE_RESULTS 20
#define VOTE_INTERVAL 30
#define INTERVAL_TIME 180000

// FUNCTIONS:

#define function%0(%1) forward%0(%1); public%0(%1)
#define isnull(%1) ((!(%1[0])) || (((%1[0]) == '\1') && (!(%1[1]))))
#define strcpy(%0,%1,%2) strcat((%0[0] = '\0', %0), %1, %2)
#define GetPageCount(%0,%1) floatround((%1 - 1) / %0, floatround_floor) + 1
#define plural_singular(%0,%1,%2) ((%0) == 1)?((#%1)):((#%2))

// DIALOGS:

#define DIALOG_OK 0
#define DIALOG_MAP_NAME 1
#define DIALOG_MAP_POSITION 2
#define DIALOG_MAP_CONFIRMATION 3
#define DIALOG_MAP_LIST 4
#define DIALOG_MAP_VOTE 5
#define DIALOG_MAP_VOTE_OPTIONS 6

// DATABASE:

#define DATABASE_PATH "maps.db"
#define TABLE_MAPS "Maps"

#define MAP_NAME "name"
#define MAP_POSITION "position"

// MAP INFO:

#define MAP_INFO_NAME 0
#define MAP_INFO_POSITION 1

// ARRAYS AND ENUMERATORS:

enum eMapInfo
{
    map_name[128],
    Float:map_x,
    Float:map_y,
    Float:map_z,
    Float:map_angle,
    map_votes
};

new aMapInfo[MAX_MAPS][eMapInfo];

// VARIABLES:

// GENERAL:

new gMapCount,
gVoteCountDown,
gTopVotes,
gLeadingMap;

// DATABASE:

new DB:database;

// TIMERS:

new gtmMapVoting,
gtmLoadMap,
gtmChangeMap;

// TEXTDRAWS:

new Text:tCountDown;

// PER-PLAYER VARIABLES:

// GENERAL:

new pNewMapString[MAX_PLAYERS][MAX_MAP_INFO][128],
pLastPage[MAX_PLAYERS],
pMap[MAX_PLAYERS];

// STATES:

new bool:sVoting[MAX_PLAYERS] = false;

// MAIN:

main()
{
    print("Development Mode: map_voting.amx");
}

// CALLBACKS:

public OnGameModeInit()
{
    LoadDatabase();
    LoadTextdraws();

    new query[144], DBResult:result, results, position[128], count;
    format(query, sizeof(query), "SELECT `%s`, `%s` FROM `%s`",
    MAP_NAME,
    MAP_POSITION,
    TABLE_MAPS);
    result = db_query(database, query);

    results = db_num_rows(result);
    gMapCount = results;

    if(results != 0)
    {
        if(count <= MAX_MAPS)
        {
            db_get_field_assoc(result, MAP_NAME, aMapInfo[count][map_name], 128);
            db_get_field_assoc(result, MAP_POSITION, position, sizeof(position));
            format(position, sizeof(position), "%s", ReplaceString(",", " ", position));
            sscanf(position, "ffff", aMapInfo[count][map_x], aMapInfo[count][map_y], aMapInfo[count][map_z], aMapInfo[count][map_angle]);

            count ++;

            while(db_next_row(result))
            {
                if(count > MAX_MAPS) break;

                db_get_field_assoc(result, MAP_NAME, aMapInfo[count][map_name], 128);
                db_get_field_assoc(result, MAP_POSITION, position, sizeof(position));
                format(position, sizeof(position), "%s", ReplaceString(",", " ", position));
                sscanf(position, "ffff", aMapInfo[count][map_x], aMapInfo[count][map_y], aMapInfo[count][map_z], aMapInfo[count][map_angle]);

                count ++;
            }
        }
    }

    db_free_result(result);

    gtmChangeMap = SetTimer("ChangeMap", INTERVAL_TIME, true);
    return 1;
}

public OnGameModeExit()
{
    return 1;
}

public OnDialogResponse(playerid, dialogid, response, listitem, inputtext[])
{
    switch(dialogid)
    {
        case DIALOG_OK: return 1;
        case DIALOG_MAP_NAME:
        {
            if(!response) return 1;
            else if(response)
            {
                if(isnull(inputtext)) return ShowEnterMapName(playerid);

                strcpy(pNewMapString[playerid][MAP_INFO_NAME], inputtext, 128);
                return ShowEnterMapPosition(playerid);
            }
        }
        case DIALOG_MAP_POSITION:
        {
            if(!response) return ShowEnterMapName(playerid);
            else if(response)
            {
                if(isnull(inputtext)) return ShowEnterMapPosition(playerid);

                strcpy(pNewMapString[playerid][MAP_INFO_POSITION], inputtext, 128);
                return ShowAddMapConfirmation(playerid);
            }
        }
        case DIALOG_MAP_CONFIRMATION:
        {
            if(!response) return ShowEnterMapPosition(playerid);
            else if(response)
            {
                new query[256];
                format(query, sizeof(query), "INSERT INTO `%s` ", TABLE_MAPS);
                format(query, sizeof(query), "%s(`%s`,", query, MAP_NAME);
                format(query, sizeof(query), "%s`%s`) ", query, MAP_POSITION);

                format(query, sizeof(query), "%sVALUES('%s',", query, pNewMapString[playerid][MAP_INFO_NAME]); // MAP_NAME
                format(query, sizeof(query), "%s'%s')", query, pNewMapString[playerid][MAP_INFO_POSITION]); // MAP_POSITION
                db_query(database, query);

                gMapCount ++;
                strcpy(aMapInfo[gMapCount][map_name], pNewMapString[playerid][MAP_INFO_NAME], 128);
                sscanf(pNewMapString[playerid][MAP_INFO_POSITION], "ffff", aMapInfo[gMapCount][map_x], aMapInfo[gMapCount][map_y], aMapInfo[gMapCount][map_z], aMapInfo[gMapCount][map_angle]);
            }
        }
        case DIALOG_MAP_LIST:
        {
            if(!response) return 1;
            else if(response)
            {
                pLastPage[playerid] ++;
                return ShowMaps(playerid);
            }
        }
        case DIALOG_MAP_VOTE:
        {
            if(!response)
            {
                new pages = GetPageCount(MAX_PAGE_RESULTS, gMapCount);
                if(pLastPage[playerid] < pages) pLastPage[playerid] ++;
                return ShowMaps(playerid);
            }
            else if(response)
            {
                new mapid = (((MAX_PAGE_RESULTS * pLastPage[playerid]) - MAX_PAGE_RESULTS) + listitem);
                pMap[playerid] = mapid;
                return ShowMapVoteOptions(playerid);
            }
        }
        case DIALOG_MAP_VOTE_OPTIONS:
        {
            if(!response) return ShowMaps(playerid);
            else if(response)
            {
                switch(listitem)
                {
                    case 0: // Vote for map
                    {
                        new string[144], mapid = pMap[playerid];
                        aMapInfo[mapid][map_votes] ++;

                        if(aMapInfo[mapid][map_votes] > gTopVotes)
                        {
                            gTopVotes = aMapInfo[mapid][map_votes];
                            gLeadingMap = mapid;
                        }

                        format(string, sizeof(string), "You have voted for the map: %s.", aMapInfo[mapid][map_name]);
                        SendClientMessage(playerid, -1, string);

                        sVoting[playerid] = false;
                    }
                    case 1: // Previous page
                    {
                        if(pLastPage[playerid] > 1) pLastPage[playerid] --;
                        return ShowMaps(playerid);
                    }
                    case 2: // Next page
                    {
                        new pages = GetPageCount(MAX_PAGE_RESULTS, gMapCount);
                        if(pLastPage[playerid] < pages) pLastPage[playerid] ++;
                        return ShowMaps(playerid);
                    }
                }
            }
        }
    }
    return 1;
}

// COMMANDS:

CMD:voteoff(playerid, cmd[], cmdtext[])
{
    KillTimer(gtmChangeMap);
    return 1;
}

CMD:voteon(playerid, cmd[], cmdtext[])
{
    KillTimer(gtmChangeMap);
    gtmChangeMap = SetTimer("ChangeMap", INTERVAL_TIME, true);
    return 1;
}

CMD:addmap(playerid, cmd[], cmdtext[])
{
    if(IsPlayerVoting(playerid)) return SendClientMessage(playerid, -1, "You cannot use this command while voting for a map.");
    if(gMapCount >= MAX_MAPS) return SendClientMessage(playerid, -1, "The max amount of maps has been reached, you cannot create another one.");

    ShowEnterMapName(playerid);
    return 1;
}

CMD:maps(playerid, cmd[], cmdtext[])
{
    if(IsPlayerVoting(playerid)) return SendClientMessage(playerid, -1, "You cannot use this command while voting for a map.");

    pLastPage[playerid] = 1;
    ShowMaps(playerid);
    return 1;
}

CMD:forcevote(playerid, params[])
{
    if(IsPlayerVoting(playerid)) return SendClientMessage(playerid, -1, "You cannot use this command while voting for a map.");
    if(gMapCount <= 0) return SendClientMessage(playerid, -1, "There are no maps for players to vote for.");

    gTopVotes = 0;
    gLeadingMap = 0;

    for(new i = 0; i < gMapCount; i ++)
    {
        aMapInfo[i][map_votes] = 0;
    }

    foreach(new i: Player)
    {
        sVoting[i] = true;
        pLastPage[i] = 1;
        ShowMaps(i);
    }

    gVoteCountDown = VOTE_INTERVAL;

    KillTimer(gtmMapVoting);
    gtmMapVoting = SetTimer("MapVoting", 1000, true);
    return 1;
}

// FUNCTIONS:

stock LoadDatabase()
{
    new query[128];
    database = db_open(DATABASE_PATH);

    format(query, sizeof(query), "CREATE TABLE IF NOT EXISTS `%s` ", TABLE_MAPS);
    format(query, sizeof(query), "%s(`%s`,", query, MAP_NAME);
    format(query, sizeof(query), "%s`%s`)", query, MAP_POSITION);
    db_query(database, query);
}

stock LoadTextdraws()
{
    tCountDown = TextDrawCreate(321.000000, 411.000000, "");
    TextDrawAlignment(tCountDown, 2);
    TextDrawBackgroundColor(tCountDown, 255);
    TextDrawFont(tCountDown, 2);
    TextDrawLetterSize(tCountDown, 0.319999, 1.400000);
    TextDrawColor(tCountDown, -1);
    TextDrawSetOutline(tCountDown, 0);
    TextDrawSetProportional(tCountDown, 1);
    TextDrawSetShadow(tCountDown, 1);
    TextDrawSetSelectable(tCountDown, 0);
    return 1;
}

stock ShowEnterMapName(playerid)
{
    new string[144];
    strcat(string, "{FFFFFF}Please enter the name of the map below.");
    return ShowPlayerDialog(playerid, DIALOG_MAP_NAME, DIALOG_STYLE_INPUT, "{FFFF00}Add Map: Map Name", string, "Submit", "Close");
}

stock ShowEnterMapPosition(playerid)
{
    new string[256];
    strcat(string, "{FFFFFF}Please enter the position of the map's spawn below.\n");
    strcat(string, "Example: {9C00FF}0.0 0.0 0.0 0.0, 0.0, 0.0, 0.0, 0.0\n");
    strcat(string, "or 0.0,0.0,0.0,0.0 (x, y, z, angle).\n\n");
    strcat(string, "{FF0000}Using the wrong syntax might cause issues.");
    return ShowPlayerDialog(playerid, DIALOG_MAP_POSITION, DIALOG_STYLE_INPUT, "{FFFF00}Add Map: Map Position", string, "Submit", "Back");
}

stock ShowAddMapConfirmation(playerid)
{
    new string[256], temp[128];
    strcat(string, "{FFFFFF}You are about to add a new map.\n");
    format(temp, sizeof(temp), "- Name: %s\n", pNewMapString[playerid][MAP_INFO_NAME]);
    strcat(string, temp);
    format(temp, sizeof(temp), "- Position: %s\n\n", pNewMapString[playerid][MAP_INFO_POSITION]);
    strcat(string, temp);
    strcat(string, "Are these fields correct?");
    return ShowPlayerDialog(playerid, DIALOG_MAP_CONFIRMATION, DIALOG_STYLE_MSGBOX, "{FFFF00}Add Map: Confirmation", string, "Yes", "No");
}

stock ShowMaps(playerid)
{
    new title[128], string[300], pages = GetPageCount(MAX_PAGE_RESULTS, gMapCount), resultcount = ((MAX_PAGE_RESULTS * pLastPage[playerid]) - MAX_PAGE_RESULTS), bool:nextpage = false;
    strcat(string, "ID\tName\tVotes");

    for(new i = resultcount; i < gMapCount; i ++)
    {
        resultcount ++;
        if(resultcount <= MAX_PAGE_RESULTS * pLastPage[playerid])
        {
            format(string, sizeof(string), "%s\n%d\t%s\t%s", string, resultcount, aMapInfo[i][map_name], ConvertToAmount(aMapInfo[i][map_votes]));
        }
        else if(resultcount > MAX_PAGE_RESULTS * pLastPage[playerid])
        {
            nextpage = true;
            break;
        }
    }

    if(IsPlayerVoting(playerid))
    {
        format(title, sizeof(title), "{414DE9}Maps - Page %s of %s", ConvertToAmount(pLastPage[playerid]), ConvertToAmount(pages));

        if(nextpage) return ShowPlayerDialog(playerid, DIALOG_MAP_VOTE, DIALOG_STYLE_TABLIST_HEADERS, title, string, "Options", "Next");
        ShowPlayerDialog(playerid, DIALOG_MAP_VOTE, DIALOG_STYLE_TABLIST_HEADERS, title, string, "Options", "");
    }
    else
    {
        format(title, sizeof(title), "{414DE9}Maps - Page %s of %s", ConvertToAmount(pLastPage[playerid]), ConvertToAmount(pages));

        if(nextpage ) return ShowPlayerDialog(playerid, DIALOG_MAP_LIST, DIALOG_STYLE_TABLIST_HEADERS, title, string, "Next", "Close");
        ShowPlayerDialog(playerid, DIALOG_OK, DIALOG_STYLE_TABLIST_HEADERS, title, string, "Close", "");
    }
    return 1;
}

stock ShowMapVoteOptions(playerid)
{
    new title[128], string[256];
    format(string, sizeof(string), "{FFFFFF}Vote for map\n");
    format(string, sizeof(string), "%sPrevious page\n", string);
    format(string, sizeof(string), "%sNext page", string);

    format(title, sizeof(title), "{414DE9}Map: %s", aMapInfo[pMap[playerid]][map_name]);
    return ShowPlayerDialog(playerid, DIALOG_MAP_VOTE_OPTIONS, DIALOG_STYLE_LIST, title, string, "Select", "Back");
}

stock ConvertToAmount(value)
{
    new string[128], count = -1;
    valstr(string, value);

    for(new i = strlen(string); i > 0; i --)
    {
        count++;
        if(count == 3)
        {
            strins(string, ",", i);
            count = 0;
        }
    }
    return string;
}

stock ReplaceString(search[], replace[], source[])
{
    new string[256], length;
    for(new i = 0; i < strlen(source); i ++)
    {
        if(strlen(search) > 1 && i != (strlen(source) - 1))
        {
            new bool:match = false, start = i;
            for(new j = 0; j < strlen(search) && !match; j ++)
            {
                if(source[i] != search[j] && j == 0)
                {
                    string[length] = source[i];
                    match = true;
                }
                else
                {
                    if(source[i] == search[j]) i ++;
                    else match = true;
                }
            }

            if(match == true)
            {
                while(start <= i)
                {
                    string[length] = source[start];
                    length ++;
                    start ++;
                }
            }
            else
            {
                for(new j; j < strlen(replace); j ++)
                {
                    string[length] = replace[j];
                    length ++;
                }

                i = (start + (strlen(search) - 1));
            }
        }
        else
        {
            if(strlen(search) == 1 && source[i] == search[0])
            {
                for(new j; j < strlen(replace); j ++)
                {
                    string[length] = replace[j];
                    length ++;
                }
            }
            else
            {
                string[length] = source[i];
                length ++;
            }
        }
    }

    string[length] = EOS;
    return string;
}

function bool:IsPlayerVoting(playerid) return sVoting[playerid];

function MapVoting()
{
    new string[144];
    if(gVoteCountDown > 0)
    {
        if(gLeadingMap == 0 && gTopVotes == 0)
        {
            format(string, sizeof(string), "~r~MAP VOTING: ~y~%s~n~~p~~h~N/A (N/A votes)", ConvertToAmount(gVoteCountDown));
        }
        else
        {
            format(string, sizeof(string), "~r~MAP VOTING: ~y~%s~n~~p~~h~%s (%s %s)", ConvertToAmount(gVoteCountDown), aMapInfo[gLeadingMap][map_name], \
            ConvertToAmount(aMapInfo[gLeadingMap][map_votes]), plural_singular(aMapInfo[gLeadingMap][map_votes], "vote", "votes"));
        }

        TextDrawSetString(tCountDown, string);
        TextDrawShowForAll(tCountDown);
    }
    else if(gVoteCountDown == 0)
    {
        foreach(new i: Player)
        {
            ShowPlayerDialog(i, -1, DIALOG_STYLE_MSGBOX, " ", " ", " ", " ");
        }

        if(gLeadingMap == 0 && gTopVotes == 0)
        {
            format(string, sizeof(string), "~r~NEXT MAP:~n~~p~~h~Random (0 votes)");
        }
        else
        {
            format(string, sizeof(string), "~r~NEXT MAP:~n~~p~~h~%s (%s %s)", aMapInfo[gLeadingMap][map_name], ConvertToAmount(aMapInfo[gLeadingMap][map_votes]), \
            plural_singular(aMapInfo[gLeadingMap][map_votes], "vote", "votes"));
        }

        TextDrawSetString(tCountDown, string);
        TextDrawShowForAll(tCountDown);
    }
    else if(gVoteCountDown <= -3)
    {
        TextDrawHideForAll(tCountDown);
        KillTimer(gtmMapVoting);

        new selected;
        if(gLeadingMap == 0 && gTopVotes == 0) selected = random(gMapCount);
        else selected = gLeadingMap;

        GameTextForAll("Loading Map...", 5000, 5);

        foreach(new i: Player)
        {
            TogglePlayerControllable(i, false);
            SetPlayerPos(i, aMapInfo[selected][map_x], aMapInfo[selected][map_y], aMapInfo[selected][map_z]);
            SetPlayerFacingAngle(i, aMapInfo[selected][map_angle]);
            SetCameraBehindPlayer(i);

            KillTimer(gtmLoadMap);
            gtmLoadMap = SetTimer("LoadMap", 5000, false);
        }
    }

    gVoteCountDown --;
    return 1;
}

function LoadMap()
{
    GameTextForAll("Map Loaded!", 3000, 5);

    foreach(new i: Player)
    {
        TogglePlayerControllable(i, true);
    }
    return 1;
}

function ChangeMap()
{
    if(gMapCount > 0)
    {
        gTopVotes = 0;
        gLeadingMap = 0;

        for(new i = 0; i < gMapCount; i ++)
        {
            aMapInfo[i][map_votes] = 0;
        }

        foreach(new i: Player)
        {
            sVoting[i] = true;
            pLastPage[i] = 1;
            ShowMaps(i);
        }

        gVoteCountDown = VOTE_INTERVAL;

        KillTimer(gtmMapVoting);
        gtmMapVoting = SetTimer("MapVoting", 1000, true);
    }
    return 1;
}
So, basically you can add server-called maps (name and position), let people vote and then bring them there after an interval has surpassed itself. You can also force them to vote for a map. Example: "DM event coming up: Vote for a map!" and then they have to vote; otherwise, other actions will be taken once the interval is over, depending whether they voted or not.

So what you do is create a group of objects with the dynamic map system and as an add-on with the map voting system you can make it so it loads that group of objects which composes a server map and let the system do the rest.

If you do not want to, then it's all good. It would a great addition, though.
Reply
#4

If I do expand the script to be something such as that, I'd probably end up making it(or atleast addon to as a separate script) as the primary goal of this script is to load & unload objects from a database. I'll give expanding the script a thought, but for now I have made a PHP script to convert your maps to a database format for the filterscript:

https://evanabagail.net/import/

Simply enter your mapping, select CreateObject or CreateDynamicObject(depending on the code's your using), and hit submit. The script will generate & download a .db file with your map.
Reply
#5

Nice one.Good job !!!
Reply
#6

Updated.
- Added a "group" flag allowing you to organize your objects into one "map" - a combination of objects, you can type in rcon,
"loadgroup (groupid)"

or
"unloadgroup (groupid)"

Also similar: /loadall, and /unloadall. The filterscript also now requires the sscanf plugin.

You'll need to add the "ObjectGroup" column if you've already created a database.

Also, check two posts up for a PHP tool to generate your maps into a .db format! The only thing you'll need to do is remove any additional parameters(extending past the Z rotation) if using dynamic object codes, that's not supported at the current moment however comments are taken account for(unless they are the ones using /* which aren't, so don't use those).
Reply
#7

nice. Usefull.
Reply
#8

Awesome. I wanted to make a script with dynamic map changing once but I thought of doing it by changing loaded filterscripts.
Have you run any tests to see how much does it take to load i.e. 500 objects?
Reply
#9

Not yet, but I'll go ahead and run some benchmarks - though ofcourse since the system also has to load the data from the files it will probably be a bit slower than when loading from files.

EDIT:
It seems simply placing 500 lines of CreateObject in a file and loading it as a filterscript on init takes for me anywhere between 1 ms to 4 ms. I got around 9 ms to 12 ms when loading it using this filterscript, it doesn't seem like too much longer.

I am also planning an update to allow textured objects and text objects soon, stay tuned!
Reply
#10

I really wish this had support for RemoveBuildingForPlayer; then I'd be perfect!
Reply
#11

Tried it.
Liked it.
Deleted it.
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)