Skip to content

Examples

IS4 edited this page Dec 23, 2024 · 8 revisions

Strings

Creating, concatenating, and passing dynamic strings

// Modify SendClientMessageToAll(color, const message[]), replacing any occurences of "const x[]" with "AmxString:x".
native SendClientMessageToAllStr(color, AmxString:message) = SendClientMessageToAll;

// Creating a dynamic string from a string buffer.
stock String:GetPlayerNameStr(playerid)
{
    new name[MAX_PLAYER_NAME];
    GetPlayerName(playerid, name, sizeof(name));
    return str_new(name);
}

public OnPlayerConnect(playerid)
{
    // Dynamic strings are tagged "String". The + operator can be used to concat them. str_val converts any value to a string.
    new String:name = GetPlayerNameStr(playerid);
    new String:msg = @("Player ")+name+@(" (")+str_val(playerid)+@(") has joined the server.");
    
    // Calls SendClientMessageToAll but passes a dynamic string instead of an array as the second parameter.
    SendClientMessageToAllStr(-1, msg);
}

String lifetime

new String:str;

public OnFilterScriptInit()
{
    str = str_new("Goodbye world!");
    str_acquire(str);
    // Without str_acquire, the string would be collected at the end of this execution.
    // Calling the function tells the garbage collector that the string shouldn't be collected.
}

public OnFilterScriptExit()
{
    print_s(str);
    str_release(str);
    // If you plan on reusing the variable, you should also set it to STRING_NULL to prevent errors
    str = STRING_NULL;
}

Split and compare on dynamic strings

native SendClientMessageStr(playerid, color, AmxString:message) = SendClientMessage;

public OnPlayerCommandText(playerid, cmdtext[])
{
    new String:cmd = str_new(cmdtext);
    
    new String:name = cmd;
    new String:args = STRING_NULL;
    
    // Finding a space in a string and using it to split it.
    new len = str_len(cmd);
    for(new i = 0; i < len; i++)
    {
        if(str_getc(cmd, i) == ' ')
        {
            name = str_sub(cmd, 0, i);
            args = str_sub(cmd, i+1);
            break;
        }
    }
    
    // Comparing with a string value.
    if(name == str_new("/test"))
    {
        SendClientMessageStr(playerid, -1, args);
        return true;
    }
    return false;
}

Tasks

Waiting a specific amount of time

stock Countdown()
{
    SendClientMessageToAll(-1, "3");
    wait_ms(1000); // Non-blocking sleep (i.e. there is no code running and checking the time).
    SendClientMessageToAll(-1, "2");
    wait_ms(1000); // await task_ms(1000); can be also used
    SendClientMessageToAll(-1, "1");
    wait_ms(1000);
    SendClientMessageToAll(-1, "0");
}
// Note: Waiting blocks the execution of the whole code up to the first public function. To make a function that returns immediately, use CallLocalFunction.

Returning from an asynchronous function

public OnFilterScriptInit()
{
    new ret = CallLocalFunction(#Func, "");
    printf("%d", ret);
}

forward Func();
public Func()
{
    yield 12;
    // The execution of the code is paused every time a call to wait_ms occurs, but the calling function still
    // expects a result. Call yield to provide the immediate result to the calling function.
    wait_ms(1000);
    return 13; // This value is lost. 12 is printed instead (immediately on script init).
}

Waiting for a user-defined event

// A MoveObject wrapper returning a waitable task, representing the event of finishing the movement of a certain object.
stock Task:MoveObjectTask(objectid, Float:X, Float:Y, Float:Z, Float:Speed, Float:RotX = -1000.0, Float:RotY = -1000.0, Float:RotZ = -1000.0)
{
    // A task represents an abstract process which can be finished, optionally having a result.
    // task_new creates a new empty (unfinished) task.
    new Task:t = task_new();

    // This registers a new handler for the OnObjectMoved callback. Public function SingleFireObjectTask
    // will be called every time OnObjectMoved is to be called (before it).
    // Additional parameters can be prepended to the handler, whose values are passed to pawn_register_callback.
    // Format specifier "e" doesn't require its value and is translated to the ID of the handler instance
    // (the same ID is also returned from pawn_register_callback).
    pawn_register_callback(#OnObjectMoved, #SingleFireObjectTask, _, "edd", t, objectid);
    MoveObject(objectid, X, Y, Z, Speed, RotX, RotY, RotZ);
    return t;
}

stock ObjectTest()
{
    new obj = CreateObject(19300, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
    print("Object created!");
    // awaits pauses the execution. The function will be executed when the task is completed.
    await MoveObjectTask(obj, 0.0, 0.0, 10.0, 5.0);
    print("Object moved!");
}

// The new handler of OnObjectMoved is extended with three new parameters (id, task, obj)
// whose values are determined in pawn_register_callback. The rest of the parameters comes
// from the callback itself.

// The point of this handler is to compare the third argument with the fourth, complete a task
// in the case of success, and unregister itself. It can be used for any single entity event
// (players, vehicles, actors, objects etc.).
forward SingleFireObjectTask(CallbackHandler:id, Task:task, obj, objectid);
public SingleFireObjectTask(CallbackHandler:id, Task:task, obj, objectid)
{
    if(obj == objectid)
    {
        // The handler is unregistered if the handled object triggered the callback.
        pawn_unregister_callback(id);
        // The respective task is set to completed.
        task_set_result(task, objectid);
    }
}

Combining strings and tasks

public OnFilterScriptInit()
{
    new String:str = str_new("Hello world!");
    str_acquire(str);
    // Since the lifetime of the string would normally end here (wait_ms returns from the callback),
    // it has to be acquired first.
    wait_ms(1000);
    // After the wait, better move the string to the local pool again in case you forget to free it.
    str_release(str);
    print_s(str);
}

It is better to use a guard for this purpose:

public OnFilterScriptInit()
{
    new String:str = str_new("Hello world!");
    pawn_guard(str);
    // The guard automatically extends the lifetime of the string until the whole execution ends.
    wait_ms(1000);
    print_s(str);
}

Waiting for a player return (by IP address)

stock Task:WhenPlayerReturns(ip[])
{
    new Task:t = task_new();
    pawn_register_callback(#OnPlayerConnect, #SingleFireIpTaskHandler, _, "eds", t, ip);
    return t;
}

// A handler that checks the IP address of a player and compares it to the one it is bound to.
forward SingleFireIpTaskHandler(CallbackHandler:id, Task:task, ip[], playerid);
public SingleFireIpTaskHandler(CallbackHandler:id, Task:task, ip[], playerid)
{
    new ip2[16];
    GetPlayerIp(playerid, ip2, sizeof ip2);
    if(!strcmp(ip, ip2))
    {
        pawn_unregister_callback(id);
        task_set_result(task, playerid);
    }
}

public OnPlayerConnect(playerid)
{
    new ip[16];
    GetPlayerIp(playerid, ip, sizeof ip);
    new Task:t = task_new();
    pawn_register_callback(#OnPlayerDisconnect, #SingleFireIdTaskHandler, _, "eds", t, playerid);
    await t;
    // GetPlayerIp does not work on disconnect, so it is temporarily stored here.
    printf("%d disconnected!", playerid);
    playerid = await(WhenPlayerReturns(ip));
    printf("%d is back!", playerid);
}

forward SingleFireIdTaskHandler(CallbackHandler:id, Task:task, stored_id, test_id);
public SingleFireIdTaskHandler(CallbackHandler:id, Task:task, stored_id, test_id)
{
    if(stored_id == test_id)
    {
        pawn_unregister_callback(id);
        task_set_result(task, test_id);
    }
}

Comparing information between two player updates

forward WhenPlayerUpdate_Handler(CallbackHandler:handler, Handle:th, testplayerid, playerid);
public WhenPlayerUpdate_Handler(CallbackHandler:handler, Handle:th, testplayerid, playerid)
{
    if(testplayerid == playerid)
    {
        pawn_unregister_callback(handler);
        if(handle_alive(th))
        {
            handle_release(th);
            task_set_result(Task:handle_get(th), true);
        }
    }
}

stock Task:WhenPlayerUpdate(playerid)
{
    new Task:t = task_new();
    new Handle:th = handle_new(t, .weak=true);
    handle_acquire(th);
    pawn_register_callback(#OnPlayerUpdate, pawn_nameof(WhenPlayerUpdate_Handler), _, "edd", _:th, playerid);
    BindToPlayer(playerid, t); // Uses the BindToPlayer function from below to delete the task when the player disconnects.
    return t;
}

public OnPlayerUpdate(playerid)
{
    task_yield(true); // OnPlayerUpdate should normally return true.
    new Float:x, Float:y, Float:z;
    GetPlayerPos(playerid, x, y, z);
    task_await(WhenPlayerUpdate(playerid));
    new Float:dist = GetPlayerDistanceFromPoint(playerid, x, y, z); // This is technically in the next update.
}

Handles

Binding an object to a player

forward HandleOnPlayerDisconnect(CallbackHandler:handler, Handle:h, testplayerid, playerid);
public HandleOnPlayerDisconnect(CallbackHandler:handler, Handle:h, testplayerid, playerid)
{
    if(!handle_alive(h) || testplayerid == playerid)
    {
        // The final condition is hit, or the handle no longer protects a living object, so no point to maintaining the callback
        pawn_unregister_callback(handler);
        handle_delete(h);
        // A released handle may live longer than required, you can delete it immediately.
    }
}

stock BindToPlayer(playerid, AnyTag:value, tag_id=tagof(value))
{
    new Handle:h = handle_new(value, .tag_id=tag_id);
    // A non-weak handle always destroys the object when it is deleted (if the object is still alive).
    handle_acquire(h); // Without this, the handle would be collected by the garbage collector.
    pawn_register_callback(#OnPlayerDisconnect, pawn_nameof(HandleOnPlayerDisconnect), _, "edd", _:h, playerid);
}

BindToPlayer can be used for any object with a lifetime. It is especially useful for tasks, if you want the playerid to remain valid when a task is completed.

Clone this wiki locally