Porting to open.mp
It has been clear for a long time that Kalcor was no longer interested in maintaining SA:MP; which in itself is fine, but as the only person with official source code access this made him a bottleneck to new updates. Both YSF and fixes.inc were created to fill this void - fix the bugs and inconsistencies in the server without having source code access; the former as a plugin and the latter as an include. Despite some monumental efforts to make these (and other) projects as stable, comprehensive, and easy to use as possible, they naturally started hitting their limits and a new generation of fixes were required. This is where open.mp comes in.
Founded on the same principles, and taking all the myriad improvements developed over a decade by the community, open.mp is a ground-up rewrite of the original SA:MP server with all the fixes from its direct predecessors, and many more that were either unmanageable or outright impossible. Granted this approach was not without controversy - some servers had developed their own private methods of dealing with SA:MP's quirks independent of the community's efforts, but these are not techniques that every scripter should have to develop for themselves, and this article will help with porting over existing code.
We hope to address the major sticking points, but if there are any we miss, feel free to reach out via discord or github and we'll be happy to ammend the guide.
The alternative option is to undo the fixes using a library which is the twin of fixes.inc - breaks.inc:
https://github.com/pawn-lang/sa-mp-fixes/blob/master/breaks.inc
So feel free to install that to revert to all the old behaviours transparently via hooks.
Tags
The open.mp includes add many new tags to functions, though still trying to strike a balance between much needed upgrades and invasiveness. Due to how wide-spread these changes can be we wrote a tool to automate a lot of it:
HideMenuForPlayer
This function has always taken a menu ID parameter, but in SA:MP this ID was not used. So whatever value was given the player's current menu would be closed, even if they weren't looking at the one you said to close.
Old code may have looked like:
gShopMenu = CreateMenu("text", 2, 100.0, 30.0, 7.0);
HideMenuForPlayer(gShopMenu, playerid);
That would always close the player's current menu, regardless of which one they were actually looking at. Now you will need to remember which one they are looking at, or just get it:
gShopMenu = CreateMenu("text", 2, 100.0, 30.0, 7.0);
HideMenuForPlayer(GetPlayerMenu(playerid), playerid);
SetPlayerAttachedObject
Attached objects in SA:MP would survive a gamemode change, but in open.mp they do not. If you want a player to keep their objects over a mode restart you will have to re-add them in OnPlayerConnect
:
enum E_ATTACHMENT_DATA
{
E_ATTACHMENT_DATA_MODEL,
E_ATTACHMENT_DATA_BONE,
E_ATTACHMENT_DATA_OFFSET_X,
E_ATTACHMENT_DATA_OFFSET_Y,
E_ATTACHMENT_DATA_OFFSET_Z,
E_ATTACHMENT_DATA_ROT_X,
E_ATTACHMENT_DATA_ROT_Y,
E_ATTACHMENT_DATA_ROT_Z,
E_ATTACHMENT_DATA_SCALE_X,
E_ATTACHMENT_DATA_SCALE_Y,
E_ATTACHMENT_DATA_SCALE_Z,
E_ATTACHMENT_DATA_COLOUR_1,
E_ATTACHMENT_DATA_COLOUR_2,
}
public OnPlayerConnect(playerid)
{
for (new i = 0; i != MAX_OBJECT_ATTACHMENT_SLOTS; ++i)
{
SetPlayerAttachedObject(
playerid,
i,
gAttachementData[playerid][E_ATTACHMENT_DATA_MODEL],
gAttachementData[playerid][E_ATTACHMENT_DATA_BONE],
gAttachementData[playerid][E_ATTACHMENT_DATA_OFFSET_X],
gAttachementData[playerid][E_ATTACHMENT_DATA_OFFSET_Y],
gAttachementData[playerid][E_ATTACHMENT_DATA_OFFSET_Z],
gAttachementData[playerid][E_ATTACHMENT_DATA_ROT_X],
gAttachementData[playerid][E_ATTACHMENT_DATA_ROT_Y],
gAttachementData[playerid][E_ATTACHMENT_DATA_ROT_Z],
gAttachementData[playerid][E_ATTACHMENT_DATA_SCALE_X],
gAttachementData[playerid][E_ATTACHMENT_DATA_SCALE_Y],
gAttachementData[playerid][E_ATTACHMENT_DATA_SCALE_Z],
gAttachementData[playerid][E_ATTACHMENT_DATA_COLOUR_1],
gAttachementData[playerid][E_ATTACHMENT_DATA_COLOUR_2]
);
}
}
ClearAnimations
ClearAnimations
is the dual of ApplyAnimation
to stop a player performing the action previously requested. However, when used on a player in a vehicle this would also cause the player to be removed from the vehicle. This is a useful function, as it happens instantly, but is not within the purview of the ClearAnimations
function. To force remove a player from a vehicle instantly use:
RemovePlayerFromVehicle(playerid, true);
Death money
When a player dies in San Andreas they get $100 deducted from them to cover hospital bills automatically. This feature remains in SA:MP, but is removed from open.mp to allow scripts to manage all their own money. Several scripts attempt to fix this already by adding $100 to a player after death, or on spawn. If this is your script simply delete the additional fix, although the code in open.mp does attempt to account for scripts that do this. If your script relied on this feature, simply add the following code to OnPlayerDeath
:
GivePlayerMoney(playerid, -100);
OnPlayerConnect
When a gamemode starts or restarts in SA:MP OnPlayerConnect
is immediately called for all players already connected to the server, but it isn't when a filterscript starts or restarts. While the latter behaviour more closely matches the name, the former behaviour is extremely widely exploited in scripts, and so was extended to all script types in open.mp to maintain consistency.
Scripts which initialise data for a player no longer need to perform this code in two different locations:
public OnFilterScriptInit()
{
for (new playerid = 0; playerid != MAX_PLAYERS; ++playerid)
{
if (IsPlayerConnected(playerid))
{
InitialisePlayer(playerid);
}
}
}
public OnPlayerConnect(playerid)
{
InitialisePlayer(playerid);
}
The loop in OnFilterScriptInit
can now be removed:
public OnPlayerConnect(playerid)
{
InitialisePlayer(playerid);
}
If a script exploited this fact to only run code for new players joining the server after the scripts starts, and not for those who were on before, this will no longer work, but is again easilly fixed:
static bool:gAlreadyHere[MAX_PLAYERS];
public OnFilterScriptInit()
{
for (new playerid = 0; playerid != MAX_PLAYERS; ++playerid)
{
gAlreadyHere[playerid] = IsPlayerConnected(playerid);
}
}
public OnPlayerConnect(playerid)
{
if (gAlreadyHere[playerid])
{
gAlreadyHere[playerid] = false;
}
else
{
SendClientMessage(playerid, COLOUR_WARN, "You're late!");
}
}
This may look to simply trade one loop in OnFilterScriptInit
off for another one, but wanting to exclude current players from some code is a less common use-case than wanting to do something for everyone, so this is overall a net improvement; and as stated before vastly less invasive than not calling OnPlayerConnect
in gamemodes.
Game texts
SA:MP has six different game text styles, but several of them are basically unusable. One fades in and out constantly, one disappears after a set time regardless of the time you put, and one never disappears regardless of the time selected. However it turns out that all of these game text styles can be accurately* reproduced using text draws. Thus fixes.inc and subsequently open.mp did so. The appearance of the game texts is the same as before, the advantage being that all styles are usable, with the downside being that they no longer fade in and out.
There is currently no way to bypass this feature to get the fading back, except for using Pawn.RakNet and sending the game text messages directly:
FadingGameTextForPlayer(playerid, const format[], time, style)
{
if (style > 6)
{
// There's no fading version of these styles.
GameTextForPlayer(playerid, format, time, style)
}
else
{
// Send a raw message via Pawn.RakNet
}
}
* With one notable exception - the new clock game text style. For some unknown reason the colour of the clock is different for different people, which led to a lot of back-and-forth discussion on how best to replicate this style until the discrepancy was found. We had to pick one of the two for consistency.
Pool sizes
GetPlayerPoolSize
, GetActorPoolSize
, and GetVehiclePoolSize
were somewhat pointless when they were first introduced; returning the highest connected ID, which bears no relation to the number of connected players, and long after much better looping methods already existed. Being a bit silly is not in itself a reason to remove the functions, but unfortunately they are also broken and return incorrect data when there are no connected players. There is no way to fix these return values in a way that is both backwards compatible and correct going forward (believe me, we tried). Given these facts, we opted to simply remove the functions. Just use a normal loop or foreach
:
foreach (new i : Player)
{
}
Some scripts did crash when this change was introduced, but only when using the following loop form:
for (new i = 0; i != GetPlayerPoolSize(); ++i)
{
}
Though since the highest value is a real player when there are people online this code is wrong anyway - it misses out a person.
Spellings
SA:MP is very inconsistent in its code spellings - some things use English, some things use American:
Bumper
- EnglishHood
- AmericanArmour
- EnglishStereo
- American
We have unified these, and settled on English spellings. So for example:
TextDrawBoxColor(Text:textid, boxColor);
Is now:
TextDrawBoxColour(Text:textid, boxColour);
The upgrade tool will take care of most of these automatically.