In this very opinionated write-up, we'll go over alternative libraries and plugins that can be used to replace some functionality found in YSI.
YSI is a set of libraries for SA-MP/open.mp servers that are admittedly useful, but often criticized for their bloated, complicated nature and occasional questionable actions by its main developer. (Sneaking in malicious code to hinder the experience of unassuming SA-MP Android users)
Over the years, people have expressed displeasure when using these libraries since they are over-engineered and can often cause issues that are confusing for less experienced pawn scripters. We'll go over some alternative libraries that are more plug-and-play, are way less hacky and can be used to produce cleaner code. (in my opinion)
A lot of the libraries and snippets I'll be sending depend on the PawnPlus plugin. PawnPlus is a very neat plugin that greatly expands the potential of pawn scripting in SA-MP/open.mp. It adds a handful of dynamic memory containers that are easy to use alongside many other very powerful additions and enhancements. I'll go over some of its capabilities here, but check out the plugin's wiki page for more extensive documentation.
I'll be honest, I'm not too fond of y_inline. I think it makes code look incredibly ugly and harder to read for little benefit. The most common y_inline use case is with dialog responses and MySQL query results. Fortunately, there's two PawnPlus-powered libraries that can be used to handle both of these things asynchronously!
Using Graber's pawn-plus-mysql library, we can handle MySQL queries asynchronously inside of a single function.
Let's toss up an example of selecting some weapon data from your database. With y_inline, it would look something like this.
LoadPlayerWeapons(playerid)
{
inline WeaponQuery()
{
new weapon, ammo;
for(new i = 0, r = cache_mum_rows(); i < r; ++i)
{
cache_get_value_name_int(i, "weapon", weapon);
cache_get_value_name_int(i, "ammo", ammo);
GivePlayerWeapon(playerid, weapon, ammo);
}
}
new query[128];
mysql_format(connection, query, sizeof(query), "SELECT weapon, ammo FROM player_weapons WHERE id = %i", AccountID[playerid]);
MySQL_TQueryInline(connection, using inline WeaponQuery, query);
}
While this example isn't too particularly difficult to read, y_inline can quickly spiral out of control and turn into this insanity, so let's see how we can write this same query asynchronously with PawnPlus and the accompanying pawn-plus-mysql library.
LoadPlayerWeapons(playerid)
{
await mysql_aquery_s(connection, str_format("SELECT weapon, ammo FROM player_weapons WHERE id = %i", AccountID[playerid]));
new weapon, ammo;
for(new i = 0, r = cache_num_rows(); i < r; ++i)
{
cache_get_value_name_int(i, "weapon", weapon);
cache_get_value_name_int(i, "ammo", ammo);
GivePlayerWeapon(playerid, weapon, ammo);
}
}
This is significantly more readable and requires less code. It also results in significantly cleaner code in more advanced uses.
Now let's move on to dialog responses. With samp-async-dialogs, another library written by Graber, we can also handle dialog responses asynchronously in a single function.
Firstly, we'll take a look at a dialog response handled through y_inline. This example will used a modified version of an admin teleport command from my old server.
// y_inline
CMD<AD1>:teleport(cmdid, playerid, params[])
{
inline TeleportDialog(playerid, response, listitem, inputtext[])
{
HandleAdminTeleport(playerid, playerid, listitem);
}
new string[1024];
for(new i = 0; i < sizeof(Teleports); i++)
{
strcat(string, sprintf("%s\n", Teleports[i][TPName]));
}
Dialog_ShowCallback(playerid, using inline TeleportDialog, "TP player to ...", string, "Teleport", "Cancel");
return true;
}
// samp-async-dialogs
CMD<AD1>:teleport(cmdid, playerid, params[])
{
new string[1024];
for(new i = 0; i < sizeof(Teleports); i++)
{
strcat(string, sprintf("%s\n", Teleports[i][TPName]));
}
yield 1; //we yield 1 here so when the function execution is paused while waiting for the dialog response, the script doesn't return 0 and think the command failed
new response[e_DIALOG_RESPONSE_INFO];
await_arr(response) ShowPlayerAsyncDialog(playerid, DIALOG_STYLE_LIST, "TP player to ...", string, "Teleport", "Cancel");
if(!response[E_DIALOG_RESPONSE_Response]) return true;
HandleAdminTeleport(playerid, playerid, response[E_DIALOG_RESPONSE_Listitem]);
return true;
}
While these aren't the most comprehensive examples out there, you can read the respective library's documentation for a bit more information.
samp-async-dialogs can be used to write a seamless, fully nested dialog trees with ease. To prove my point, I'll provide my house editing feature from an old project which fully utilizes the library. Do note that I have some companion functions for the async dialog results that aren't built in to the library itself.
Let's start simple with replacing y_iterate. Fortunately, a competent and feature-rich standalone version already exists. It's the OpenGTO fork of foreach, which is essentially just an older version of y_iterate. It contains a handful of the features found in modern y_iterate, such as vehicle iterators, iterators for streamed entities such as nearby players, nearby vehicles and etc. However, it's missing things like Iter_SafeRemove
being made redundant, special iterators and multidimensional iterators. If you don't use these features, it should be a relatively simple replacement.
You can somewhat replicate the multidimensional iterator functionality by using PawnPlus' dynamic memory containers. In this example, we'll use a map alongside a list. In y_iterate's documentation, an iterator of player-owned vehicle is used as an example, so we'll do that here too.
Firstly, let's declare the PawnPlus map and assign a player's vehicle data to it
// create a map for vehicle ownership. the owner's player ID will be used as a key, and the value will be a list of their owned vehicle IDs
new Map:OwnedVehicles = map_new();
// a dynamic list containing the player's owned vehicle IDs
new List:mycars = list_new();
list_add(mycars, 2);
list_add(mycars, 4);
list_add(mycars, 6);
// assign the list containing vehicle IDs 2, 4 and 6 to player ID 1
map_set(OwnedVehicles, 1, mycars);
Then we can try looping through a player's owned vehicles
// loop through the vehicles owned by player ID 1
for_list(i: List:map_get(OwnedVehicles, 1))
{
printf("vehicle %i", iter_get(i));
}
The output of that code would be
vehicle 2
vehicle 4
vehicle 6
While the code above can be used to loosely replicate y_iterate's multidimensional iterators, it isn't as memory safe and doesn't prevent you from accidentally assigning a vehicle to multiple owners in your code, so be wary of that. Also, since we're using dynamic memory, you don't need to worry about preallocating maximum player or vehicle limits. On the flip side, that means we'll need to explicitly clean up after players when they disconnect. In this instance, if a player disconnects from the server, we'll want to clear that player ID's owned vehicles so the next player doesn't inherit vehicle IDs that don't belong to them. Fortunately, it's very easy to do.
public OnPlayerDisconnect(playerid, reason)
{
// check if disconnecting player's ID is present in the OwnedVehicles map
if(map_has_key(OwnedVehicles, playerid))
{
// remove their list of vehicles from the container if so.
// the reason why we use map_remove_deep is because we used a dynamic list to store the vehicle IDs
// by using map_remove_deep, it ensures all nested dynamic containers are also cleaned up and avoids memory leaks
map_remove_deep(OwnedVehicles, playerid);
}
}
y_hooks is arguably the most widely used YSI component, and for good reason. It's crucial for any well-developed and organized gamemode. Fortunately for us, there's an alternative hooking library we can replace it with. It's called pawn-plus-hooks, developed by AGraber. As the name would imply, it uses PawnPlus under the hood to achieve callback/function hooking.
Basic hooks function the same as in y_hooks.
// y_hooks
#include <YSI_Coding\y_hooks>
hook OnPlayerConnect(playerid)
{
SendClientMessage(playerid, -1, "hello world");
}
// pawn-plus-hooks
#include <pp-hooks>
hook OnPlayerConnect(playerid)
{
SendClientMessage(playerid, -1, "hello world");
}
Native hooks are also supported, but they're slightly different than in y_hooks.
// y_hooks
#include <YSI_Coding\y_hooks>
hook function SetPlayerVirtualWorld(playerid, worldid)
{
new originalworldid = GetPlayerVirtualWorld(playerid);
if(worldid != originalworldid)
{
pawn_call_public("OnPlayerVirtualWorldChange", "iii", playerid, worldid, originalworldid);
}
return continue(playerid, worldid);
}
// pawn-plus-hooks
#include <pp-hooks>
hook native SetPlayerVirtualWorld(playerid, worldid)
{
new originalworldid = GetPlayerVirtualWorld(playerid);
if(worldid != originalworldid)
{
pawn_call_public("OnPlayerVirtualWorldChange", "iii", playerid, worldid, originalworldid);
}
return SetPlayerVirtualWorld(playerid, worldid);
}
The handling of return values is different than y_hooks. I'm not super privy to how return values are handled in y_hooks, so you'll need to read the documentation of pawn-plus-hooks to find the differences yourself.
Do people actually use this? I don't actually know how to use this library and have no interest in learning. Just use PawnPlus dynamic memory containers and you'll be glad you did. They're extremely flexible, practical and more akin to containers you'll actually find in other (real) programming languages.
Just use SetTimer
and SetTimerEx
with regular callbacks. Timers in SA-MP/open.mp are very simple and I think y_timers is attempting to solve problems that don't actually exist. Alternatively, PawnPlus' task feature does have functions that make it viable to use for timers. Read its documentation here.