-
Notifications
You must be signed in to change notification settings - Fork 18
Examples
IS4 edited this page Dec 23, 2024
·
8 revisions
// 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);
}
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;
}
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;
}
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.
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).
}
// 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);
}
}
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);
}
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);
}
}
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.
}
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.