Show posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.

Show posts Menu

Topics - Sebastian

Here is a short tutorial about how to create/export collision file using Blender 2.x.x

To be able to import the .dff files, I'm using @maxorator 's plugin:

There is another plugin based on maxorator's, made by @PerikiyoXD :

Also, COL Editor:

As a bonus, I'm explaining in there how to decrease the number of polygons an object has, through 'Decimate' modifier.

What I'm presenting here is just a quick way to create a collision, by using a .dff.
The problem is .dff files represent the visual part, which must be pleasant for our eyes, so the objects will always be detailed and good looking.
Collisions, on the other side, must be very light-weight but to cover the whole surface of an object.
It's invisible after all, so the less polygons the better.

Just to remember, a good collision file means something like this:
- low number of polygons
- cover only the exterior which the player can interact with

Take the beach ball as an example. It has a very round form in game, but the collision itself is not that round.
Why? Because we don't need 100 polygons for Tommy Vercetti to interact with.
Every 10 polygons could be simply replaced by 1 polygon, as Tommy Vercetti will anyway not reach a small polygon between 3 big polygons. So that will be useless polygon.

Same happens for the polygons which are inside a collision object. If they cannot be reached, they lose the point of being called a 'collision', so don't need to be part of a .col file. Just remove them. Keep the ones on surface only.

PS: This tutorial was made by request.
Note: Seems to happen when game is loaded in FULL SCREEN mode. (not windowed)


Here are my conclusions, after some more testing.

Vice City's Menu opening speed will start decreasing when there are >20 labels onscreen/hidden, with:
  • FontFlags (no matter which ones)
  • FontSize different than 11 (default)

Here is a code to test on:
function test() {
arr <- array(100);
for (local i = 0; i < arr.len(); i++) {
arr[i] = GUILabel( VectorScreen( 300, 300 ), Colour( 255,255,255 ), "FontSize changing + ESC to MENU speed!" );
arr[i].FontFlags = GUI_FFLAG_OUTLINE;
arr[i].FontSize = 11;
Console.Print( "created label no " + i );

Test it. Works fine right? 100 labels.
Now change .FontSize value to anything else than 11, and try to enter the VC Menu.
Now remove the .FontFlags, and enter the VC Menu again.
Fast, hm ? :p
If you have never put hands or worked with client-side code, then try to read this, it will help a bit.
Original discord conversation:
Link here

For using client-side, you need a main.nut no matter what, inside store/script/
(you can also load compiled .nut files (aka .cnut), but that's another story)
((also, if you need a main.nut doesn't mean you must stick to one file only; you can include( "otherfile.nut" ) / dofile( "otherfile.nut" ) ))

So, inside that main.nut, you have a list of events/functions that you can use here:

In order to send data from SERVER to CLIENT, OR VICEVERSA, you must write/call Streams
Those streams will be received in
function Server::ServerData( stream )if it's sent by server, and

function onClientScriptData( player ) if sent by client

Here is a stream example, from server to client:
Stream.WriteInt(0); // key / id
Stream.WriteInt(51); // needed value
and here is a stream e.g. from CLIENT -> SERVER
local Data = Stream();
Data.WriteInt(0); // key / id
Data.WriteString("Explosion"); // needed value
those keys I've been put into examples must be considered as cases

Sometimes, you send a stream because you want some data related death messages, but sometimes you send a stream because you want position
well, these 2 cases should have an id like. Why ID ? because they are not so heavy
so, when you send data client->server, in function onClientScriptData( player ) you will be able to do something like this:
function onClientScriptData( player )
    local id = Stream.ReadInt();
    switch( id ) {
        case 0: {
            // death message related
            local msg = Stream.ReadString();
            MessagePlayer( "Client data received - death message: " + msg, player );
        } break;

        case 1: {
            // position related
        } break;

        default: {
            // even was called, but with an ID not existing in this switch
        } break;

Same goes for client-side, when receives data from server:
function Server::ServerData( stream )
    local id = stream.ReadInt();
    switch( id ) {
        case 0: {
            // death message related
            local reasonID = stream.ReadInt();
            Console.Print( "Death Reason ID received from Server: " + reasonID );
        } break;

        case 1: {
            // position related
        } break;

        default: {
            // even was called, but with an ID not existing in this switch
        } break;

[spoiler="Stream or stream ??"]
be sure you don't get confused when using stream with big S or small s

if server-side, you need to use Stream with big S.
if client-side, you initiate a custom named/created variable with that Stream() function, like you have seen in the examples above

but if in client-side, using the event Server::ServerData( stream ), then you can see that the parameter is named stream with small s.

Anyway, the main idea when sending streams is to go as fast as posible
if you gonna use a string as a key, like you did, then tons of data will be used everytime you send that string key
while you could identify the cases by Integers, or even better: Bytes (as there is also Stream().WriteByte)
and that ^ uses a lot less data, and that's for identification only
Original discord conversation

PS: Be aware that this fix is a pretty new find, so there can be side-effects.
Didn't yet face anything, but still..

Labels tend to be very buggy sometimes, especially when using different constructors.
(@NewK knows better)

Out of all constructors
Quote from: wikiGUILabel

1. Constructor GUILabel()

2. Constructor GUILabel(position, colour)
Parameter types: VectorScreen, Colour

3. Constructor GUILabel(position, colour, text)
Parameter types: VectorScreen, Colour, string

4. Constructor GUILabel(position, colour, text, flags)
Parameter types: VectorScreen, Colour, string, int

seems that the 4th one works the best. (DecUI is using it too, in it's best way)
Using any other one will lead to weird and obscure behaviour.

Still, here is something that might fix this ^ and can be found here v
label.Size = VectorScreen(0,0);
label.TextPaddingTop = 0; // or Right, or Left, or Bottom, as any of them seem to do the thing
[spoiler=all TextPadding functions]
   label.TextPaddingTop = 0;
   label.TextPaddingBottom = 0;
   label.TextPaddingLeft = 0;
   label.TextPaddingRight = 0;

Just apply these settings once you are done with your label. (so, at the end of it)

[spoiler="in case of using DecUI"]
NewK — 04/26/2022
you can use the postConstruct function which will be called after an element is built

"this" in that case is the GUILabel

Some bugs that can happen when using other constructors than the (4th) one with 4 arguments.

1. Label might not show up at all, when the text is not long/strong/heavy enough, or has some flags.

2. Labels placed in column might be pushed more to the right

One of our brand new players, who just decided to move from SA:MP to VC:MP, asked me today about the known savedpositions.txt feature.
Wait, it's not known anymore, since 0.4 :)
(for the newbies: yeah, the feature was available in vc:mp 0.3)

So, with SLC's I/O Functionality documentation and functions, it was pretty easy to remake. (Thanks!)
Decided to release it, maybe it will be helpful to other newbies too.
MUST create the savedpositions.txt first, before using the command;
if not, it will throw errors.

But then, @DizzasTeR striked with his i/o knowledge and hit hard my code
(before, I was reading and saving the whole file content in a variable, just to add a new line at the end)
So now, we have a way to add a new line without rewritting the text from the file!
(didn't know, didn't google c:)

onPlayerCommand( player, cmd, text )
if( cmd == "s" || cmd == "save" )
local veh = player.Vehicle;
local newline;
if( !veh ) {
// the below script will check all player's weapons and save the first 3 the script founds
local wep = array(3);
wep[0] = { ID = 0, Ammo = 0 };
wep[1] = { ID = 0, Ammo = 0 };
wep[2] = { ID = 0, Ammo = 0 };
local weps = 0;
for( local i = 0; i < 9; i++ ) {
if( player.GetWeaponAtSlot(i) != 0 && weps < 3 ) {
wep[weps] = {
ID = i,
Ammo = player.GetAmmoAtSlot(i)
if( weps == 3 ) break;

// format is AddClass( class, color, skin, position, angle, weapon1, ammo1 ,weapon2, ammo2, weapon3, ammo3 );
newline = "AddClass( " + player.Class + ", RGB(" + player.Colour.r + ", " + player.Colour.g + ", " + player.Colour.b + "), "
+ player.Skin + ", Vector" + player.Pos + ", " + player.Angle + ", "
+ wep[0].ID + ", " + wep[0].Ammo + ", " + wep[1].ID + ", " + wep[1].Ammo + ", " + wep[2].ID + ", " + wep[2].Ammo + " ); \n";
else {
// format is CreateVehicle( model, world, pos, angle, col1, col2 )
newline = "CreateVehicle( " + veh.Model + ", " + veh.World + ", Vector" + veh.Pos + ", " + veh.Rotation.z + ", " + veh.Colour1 + ", " + veh.Colour2 + " ); \n";

local f = file("savedpositions.txt","a+");
foreach (c in newline)
f.writen(c, 'b');

PS: For the some of you who are using the older version, better upgrade to this one.
When you fire bullets in first-person view/mode, client-side Player::PlayerShoot doesn't get called.


What you were doing when the bug happened
Shooting a wall in first-person, expecting to see the Bullet Holes, but they didn't show up.

What you think caused the bug
No script calling the event when player is in first-person ?

1st step: Make sure you have "7-zip" program installed on your computer, because you need it to archive the needed files.
Better to not remove it ever. :)
2nd step: Find (or create) the skin/player model you want to import in your server.
(I will give you a Fat Tommy skin as example, so credits goes to MauriceVC2002)
3rd step: Make sure you have all needed files from this list:

The file that contain skin's settings.
The model of the custom skin you want to import.
The texture of the custom skin.

4th step: Open skinname.xml and write the afferent code information:
<?xml version="1.0" encoding="ASCII"?>
  <animfile></animfile> <!--not really used -->
name = simply your skin name; in our case: Fat Tommy
animgroup and animfiles can be found in VC/data/default.ide
# modelId, ModelName, TxdName, Default pedtype,
# animGroup(man,cop,woman,fatwoman,sexywoman...), cars can drive mask(in hex),
# animfile, radio1, radio2

We gonna pick the animgroup "fatman" and animfile "sunbathe"
20, HMOBE, HMOBE, CIVMALE, STAT_TOURIST, fatman, 437, sunbathe, 7,5
overrideanims is a bool (true/false) – it overrides/not the default Tommy anims.
(if you gonna override them, player won't be able to move left/right anymore, as it will go just forward/backward anyway)
5th step: Open "7-zip" program, and create the ".7z" archive, but we need to think a bit about it's name.
The archive's name format is this:  z<skinID>_skinname.7z
Atention ! Custom skin IDs start from 200 and go up to 299 (iirc)
6th step: Now all are ready to be moved in your server's directory !
The "z<skinID>_skinname.7z" archive should  be moved to ".../server/store/"
and the "objects.xml" should be moved to ".../server/store/skins/"

7th step: Open your server, try your custom skin (id), and have fun testing it ! :D

That's all, I hope you will success ! Good luck ! ;)
Credits: Sebastian (me)
What am I going to explain you ??
Well, VC:MP has now the ability of creating whole new static maps that cannot be edited using scripts.
These static maps are like ".ipl" files, but with different format, that will be permanently created on map, not like objects created by CreateObject function. (<- them are removed when you are far away, and recreated when you are near them)
So if you want to add an island, better to create it using the way I will present you today, because if the island is (for e.g.) at docks (on water), and you are at North Point Mall and want to teleport to it, you will not fall in water, but you will be teleported in the right place. If you would use CreateObject to create that map, you would fall in water because the object would not be created as fast as your teleportation.

1st step: Find (or create) the static map you want to import in your server.
(I will use "Akina Hill 2.0" as example, created by SanyaSpy   )
VC:MP Static Maps can use Vice City's default objects too, but I will use a custom map to can show you that we can add much more files in one ".7z" archive, then just add them in "objects.xml".
VC:MP opens all archives located in "...server/store/" and just choose the respective files you write in "objects.xml".
You can set, for an object from archive "a", the texture from an object from archive "b", and the colision from an object from archive "c".

2nd step: Make sure the static map has ".ipl" files. (the ".ide" file is "optional")
If it has the ".ide" file too, then you can take from there the render distance, and flags value.
Look, I set the distance and flags value that I found in the ".ide" file.
This is what you can find in the "optional" .ide file:
ID, ModelName, TextureName, ObjectCount, DrawDist, Flags

4802, akina01, akina, 1, 299, 0

3rd step: Convert the ".ipl" file to the ".xml" file you need.
^ this is a converter made by Thijn.

You just add the code there, then save it in a file ".xml".
I will leave the link in the description. (like everytime)
Atention (only if you create a static map using custom objects): After you convert the ".ipl" file to ".xml", you should check all object IDs from there. Why ? Because developers of that map used another object IDs than you do. They made the map for single player, but you want it for multiplayer.
So you should change the object IDs with the respective IDs written in "objects.xml".

4th step: Name your map file.
There is no name format, so you are free to name it as you wish.
5th step: Now all are ready to be moved in your server's directory !
The ".xml" file should be located in: "...server/store/maps/"
6th step: Open your server, and take a look at your new static map ! :P

That's all, I hope you will success ! Good luck ! ;)
Credits: Seby (me)

Also, here is a video of how to deal with custom objects in Map Editor (MEd)
1st step: Make sure you have "7-zip" program installed on your computer, because you need it to archive the needed files.
Better to not remove it ever. :)
2nd step: Find (or create) the objects you want to import in your server.
(I will give you an object from SAiVC as example, so credits goes to it's developers)
3rd step: Make sure you have all needed files from this list:

The file that contain object's settings; you set there the .dff, .txd anc .col for that object.
Note: You need just one file like this one, because you will write there all custom objects you add.
Another note: Do not include "objects.xml" in the archive, because it need to be somewhere else in the store directory.
The model of the custom object you want to import.
The texture of the custom object.
objectname.col (if you don't add it, use <collision type="none" />)
The colision of the custom object.
It can be solid, but it's optional.

4th step: Open "7-zip" program, and create the ".7z" archive, but we need to think a bit about it's name.
The archive's name format is this:  objectname_unp.7z
Atention ! Only the ".dff" , ".txd" and ".col" files should be in the archive.
5th step: Since the archive is done, we can take care of "objects.xml".
There, like in any ".xml" file, we need a code that is recognized by VC:MP.
(watch the video to see it)
We need to set every filename and value in the right object group.

6th step: Now all are ready to be moved in your server's directory !
The "objectname_unp.7z" archive should  be moved to ".../server/store/"
and the "objects.xml" should be moved to ".../server/store/objects/"

7th step: Open your server, spawn the object with ID 6000, and have fun testing it ! :D

Note: IDs of Custom Objects starts with 6000.
(<object id="23"> in .xml means 6023)

That's all, I hope you will success ! Good luck ! ;)
Credits: Seby (me) & Thijn (for written tutorial in forum)

Also, here is how to deal with custom objects in Map Editor (MEd)
1st step: Install "7-zip" program, because you will need to archive the needed files into a .7z archive.
(you can download the program from the link in description)
2nd step: Find (or create) the weapon you want to import in your server.
(I will give you a "sniper" as example, converted by Silent Viper from Max Payne 2)
3rd step: Make sure you have all needed files from this list:

Describes the weapon and its settings, like in weapon.dat. logicalid is the same as the <weapon> specified in the filename.
weaponname_icon.png (optional, but you better add it)
The icon for the weapon that will be used in-game. If not specified, a blank icon is used.
animgroup.ifp (optional)
The animation group file used for the weapon. If not given, the client will use the default animation group provided by Vice City.
weaponname.dff (optional, but you better add it)
The model for the weapon. If not given, the client will use the same model as the logical weapon ID.
weaponname.txd (optional, but you better add it)
The texture for the weapon. If not given, the client will use default textures for the logical weapon ID.
stretch_scope.png (optional)
For sniper rifles and RPGs; the texture used for the scope when zoomed in. If not given, the client will use default scopes.
gunshot.wav (optional)
The sound effect that should be played when you fire.

4th step: Open "7-zip" program, and create the ".7z" archive, but we need to think a bit about it's name.

The name should have this format:

<id> - starting from 100 and increasing with each weapon you add; the ID used by the weapon in-game.
<slot> - the weapon slot the weapon uses, with 0 being fist, 1 being melee, etc.
<weapon> - the ID of the weapon your custom weapon behaves like; putting 6 in here will make your weapon behave like a baseball bat.
name - the name of the weapon.

If <weapon> is 81, it will act as a hat, and the player will use fists for melee.

We will use this name:   w100_s8_l28_sniper.7z
100 - because it is the first weapon in my example server.
8 - because it is part of sniper&laser class.
28 - because it is the id of the default sniper of Vice City
sniper - because it is the name of the custom weapon

5th step: Since the archive is done, you can move it to ".../server/store/weapons/"
6th step: Open your server, spawn the weapon with ID 100, and have fun testing it ! :D

That's all, I hope you will success ! Good luck ! ;)
Credits: Seby (me) & Stormeus (for written tutorial in forum)
Videos & Screenshots / Extreme Racing
May 08, 2021, 12:14 AM
Who's the owner of this ? KL (vito told me)
It was fun to play there!



Extract the archive and rename it into ""

(so it will no longer have the server name)

Put the directory in

This is not a bug, but it worths a mention.

So, if we want to use a custom generic.txd instead of VC's default, we can just place our "generic.txd" inside /store/maps/
But, if we gonna load custom objects which use generic.txd as a texture pack, we must place another copy of it inside /store/, so we end up having 2 exact same files, but in different locations... so more data to download.

It would be better to have it only once, and do both jobs.

PS: I also suggest a way for our /store/objects/.xmls to be able to load the default generic.txd, if there is no custom one, so custom objects will fit perfectly no matter what textures a player uses.
Client Scripting / About compiled scripts
Mar 15, 2021, 01:17 AM
Everything you need to know about compiled client-side scripts!

It is posible since rel004 2017-05-24 Update
Quote from: StormeusAdded support for loading precompiled client-side scripts (i.e. .cnut files). These must be compiled on a 32-bit Squirrel instance.

In order to make it work, you have 2 options:
[spoiler=1. Multiple scriptfiles]
1. Multiple scriptfiles - you must give up on calling constants/enums from other files except the ones they are used in.

You are declaring const MAX_PLAYERS=50; in main.cnut and then later trying to call it from utils.cnut. The constant MAX_PLAYERS won't be recognized, and that's why it will throw the following error:
AN ERROR HAS OCCURRED [the index '<constant name>' does not exist]Also, don't forget to use :: prefix when you call global variables from different scriptfiles.
[spoiler=2. One BIG main.cnut]
2. One BIG main file - merge all scriptfiles into one

Doing this way, you won't be needed to avoid constants/enums.
But, be aware of dofile/include functions. They will still call the files they are supposed to.
So I suggest you to create your own function, like I did, and just change a constant:

const wayLoadScripts = 0;
function include2( path )
          if( wayLoadScripts == 0 ) return 0;
          else include( path );

@DizzasTeR 's worthy mention:

to compile .nut into compiled .cnut (or whatever format you like)

When you use compiled scripts, you may notice the following warnings and error in debuglog.txt:
MSG: <filename> line = (1) column = (1) : error expression expected
Warning in CScriptFunctions::DoFile: (script/<filename>.cnut) expression expected
Warning in CScriptFunctions::DoFile: Attempting bytecode read of file.
Don't worry, it is not a problem. The client just tries to read the file as uncompiled first.

MSG: Scripts initialised.
If you have ever added a custom vehicle in your server, and later tried to edit it's handling, you know for sure that
Custom Vehicles return no handling data

This happens because server is not taking the info out of yourcars.7z, so it will always return 0 when tring to read some handling rule/data.
If you played long enough with handlings, you could notice that once you vehicle.SetHandlingData, then vehicle.GetHandlingData will return the value you just setted earlier.

So what this means? We must load the handling everytime we create a new vehicle, no ?

There is SetHandlingRule which sets the handling of the model and not the vehicle instance!
"Yeah, but we knew this already. What's the thing with it?!"
Well, here comes the magic. If you set it once, then all the vehicles with that model will get the wanted handling, and return the correct values whenever using GetHandlingRule( model, rule ) or vehicle.GetHandlingData( rule ).

(the work-around) Personally, I'm setting all the Handling Rules when server stars. This way, no matter when I want to create a new vehicle during the gameplay, it will get the presetted handlings by "default", and I will be able to play with it's handling data however I want.
Even vehicle.ResetHandlingData( rule ) will return back to the value you setted through SetHandlingRule( model, rule, val ), so it is a very safe alternative.

Here is a function example of how it should work:
[noae][noae]function RegisterHandling( model, path )
    print( "Registering handling " + model + " from " + path );

        local file = ReadTextFromFile( path );
        local   bias = GetBetweenEvery( file, "<bias>", "</bias>" ),
                x = GetBetweenEvery( file, "<x>", "</x>" ),
                y = GetBetweenEvery( file, "<y>", "</y>" ),
                z = GetBetweenEvery( file, "<z>", "</z>" ),
                drivetype = GetBetween( file, "<drivetype>", "</drivetype>" ),
                enginetype = GetBetween( file, "<enginetype>", "</enginetype>" );

        if( drivetype == "4" ) drivetype = 52;
        else if( drivetype == "F" ) drivetype = 70;
        else if( drivetype == "R" ) drivetype = 82;

        if( enginetype == "P" ) enginetype = 80;
        else if( enginetype == "D" ) enginetype = 68;
        else if( enginetype == "E" ) enginetype = 69;
        SetHandlingRule( model, 1, GetBetween( file, "<mass>", "</mass>" ).tofloat() );
        if( x.len() == 2 )
            // dimensions
            SetHandlingRule( model, 2, x[0].tofloat() );
            SetHandlingRule( model, 3, y[0].tofloat() );
            SetHandlingRule( model, 4, z[0].tofloat() );

            // centre of mass
            SetHandlingRule( model, 5, x[1].tofloat() );
            SetHandlingRule( model, 6, y[1].tofloat() );
            SetHandlingRule( model, 7, z[1].tofloat() );
            SetHandlingRule( model, 5, x[0].tofloat() );
            SetHandlingRule( model, 6, y[0].tofloat() );
            SetHandlingRule( model, 7, z[0].tofloat() );
        // SetHandlingRule( model, 8, GetBetween( file, "<percentsubmerged>", "</percentsubmerged>" ).tofloat() ); // at the moment, it is buggy to set this value. (vehicles will teleport when touching water)
        SetHandlingRule( model, 9, GetBetween( file, "<multiplier>", "</multiplier>" ).tofloat() );
        SetHandlingRule( model, 10, GetBetween( file, "<loss>", "</loss>" ).tofloat() );
        SetHandlingRule( model, 11, bias[0].tofloat() );
        SetHandlingRule( model, 12, GetBetween( file, "<numofgears>", "</numofgears>" ).tofloat() );
        SetHandlingRule( model, 13, GetBetween( file, "<maxspeed>", "</maxspeed>" ).tofloat() );
        SetHandlingRule( model, 14, GetBetween( file, "<acceleration>", "</acceleration>" ).tofloat() );
        SetHandlingRule( model, 15, drivetype.tofloat() );
        SetHandlingRule( model, 16, enginetype.tofloat() );
        SetHandlingRule( model, 17, GetBetween( file, "<deceleration>", "</deceleration>" ).tofloat() );
        SetHandlingRule( model, 18, bias[1].tofloat() );
        SetHandlingRule( model, 19, GetBetween( file, "<steeringlock>", "</steeringlock>" ).tofloat() ); // "<abs>", "</abs>" ).tofloat() );
        SetHandlingRule( model, 20, GetBetween( file, "<forcelevel>", "</forcelevel>" ).tofloat() );
        SetHandlingRule( model, 21, GetBetween( file, "<dampening>", "</dampening>" ).tofloat() );
        SetHandlingRule( model, 22, GetBetween( file, "<seatoffset>", "</seatoffset>" ).tofloat() );
        SetHandlingRule( model, 23, GetBetween( file, "<damagemultiplier>", "</damagemultiplier>" ).tofloat() );
        SetHandlingRule( model, 24, GetBetween( file, "<upperlimit>", "</upperlimit>" ).tofloat() );
        SetHandlingRule( model, 25, GetBetween( file, "<lowerlimit>", "</lowerlimit>" ).tofloat() );
        SetHandlingRule( model, 26, bias[2].tofloat() );
        SetHandlingRule( model, 27, GetBetween( file, "<antidive>", "</antidive>" ).tofloat() );
        SetHandlingRule( model, 28, ("0x" + GetBetween( file, "<flags>", "</flags>" )).tofloat() );
        SetHandlingRule( model, 29, GetBetween( file, "<front>", "</front>" ).tofloat() );
        SetHandlingRule( model, 30, GetBetween( file, "<rear>", "</rear>" ).tofloat() );
        SetHandlingRule( model, 31, 0.0 );
        SetHandlingRule( model, 32, 0.0 );
        SetHandlingRule( model, 33, 0.0 );
        print( ". Loaded handling for " + model );
Example: RegisterHandling( 6400, "/XMLs/mycar.xml" );
I/O Functionality thanks to @SLC !

So, you must have your XMLs extracted out of the .7z archives, somewhere (e.g: server/XMLs/here) in your server files, for the server to access them.
Unfortunately, I don't know how to open an archive through script, to load them directly from inside. If you know how to, land a hand ;p

PS: The only thing you should not do is using ResetHandlingRule( model, rule ), because it will return to the 'default' value: 0
PS2: SetHandlingRule( model, 8, .. ) was commented due to a weird behaviour of the game.
( no matter what value you set it, the vehicle will get teleported to Vector(0,0,0) when touching th water )
(( in order to recover things back, just use ResetHandlingData/Rule( 8 ) ))

Vehicle Showroom / Bobster
Mar 03, 2021, 10:38 PM
Took a look in some OLD files, since when @maxorator was just spoiling the custom vehicles' feature




Racing System
(with checkpoints)

This is basic and ready for you to use and even customize !

Some info about how it works?! Somebody /startrace, and then, for the next 20seconds (by def), people can join it and race.
It loads all the saved tracks to check if everything is alright, then free the table so we don't unnecessary keep a lot of data in the memory. So, we load it only when we need it.
(didn't start the script with this idea though, so IF -big if- there are some bugs, I'm pretty sure it is because of the few changes I did to the 'mechanism')
The racing track is using checkpoints, together with 2 markers on radar: 1 for the next cp, 1 for the second next cp.
You can join as many racers as you want in a race, but I've limited it to const race_max_spots = 20;
(so, just change that if you want more)
There is a likes variable too, saved in the database, but never used. I consider it needs to be a lil' bit more hardcoded
in each server, as you need to know who voted already, when players have some time for voting, etc
For more, check it out!

My Racing System is something everybody can use in their servers, as a side activity.
I also suggest you to use some GUI on it :P

Available commands:
  • /race - info and list of cmds
  • /startrace //<trackID> <newVehModel> <newLaps> - all params are optional!
  • /joinrace
  • /quitrace
  • /flip
  • /lastcp

Developer mode:
  • /racedev - toggle developer mode
  • /createtrack <name> //<laps> - name must be 1-word
  • /createcp - creates the next checkpoint position


Some things/functions you might already have

[noae]const COLOR_BLUE = "[#4baaff]";
const COLOR_GREY = "[#b0b0b0]";
const COLOR_WHITE = "[#ffffff]";

[noae]function random( min, max )
        if ( min < max )
                return rand() % (max - min + 1) + min.tointeger();
        else if ( min > max )
                return rand() % (min - max + 1) + max.tointeger();
        else if ( min == max )
                return min.tointeger();

function GetTok(string, separator, n, ...)
    local m = vargv.len() > 0 ? vargv[0] : n,
    tokenized = split(string, separator),
    text = "";
    if (n > tokenized.len() || n < 1) return null;
    for (; n <= m; n++)
        text += text == "" ? tokenized[n-1] : separator + tokenized[n-1];
    return text;

function NumTok( string, separator )
    local tokenized = split(string, separator);
    return tokenized.len();

Some a lil' bit more custom, but still
thought, you might want to edit them
[noae]function devprint( text )
MSG( COLOR_GREY + text + "" );
print( text );

function pMSG( text, player )
return MessagePlayer( COLOR_BLUE + text, player );

function MSG( text )
return Message( COLOR_BLUE + text );

function SQLC( db, column )
    return GetSQLColumnData( db, column );


1.First, we must load the RacingSystem.nut
then, for the future, we gonna check if tracks data is good
[noae]function onScriptLoad()
dofile( "scripts/RacingSystem.nut" );
2.Then we make sure we rage quit the players when needed
[noae]function onPlayerPart( player, reason )
if( racedev == player.ID ) racedev = null;
QuitTrack( player );
function onPlayerDeath( player, reason )
QuitTrack( player );

function onPlayerKill( player, killer, reason, bodypart )
QuitTrack( player );

function onPlayerTeamKill( player, killer, reason, bodypart )
QuitTrack( player );

3.Now, onPlayerCommand
[noae]else if ( cmd == "race" )
pMSG( "Racing System by Sebastian (aka sseebbyy)", player );
pMSG( "Commands: (/) racedev, startrace, joinrace, quitrace, flip, lastcp", player );

else if( cmd == "startrace" )
if( !text ) return AnnounceRace();

local param = array( NumTok( text, " " ) );

local i = 0;
for( i = 0; i < param.len(); i++ ) param[i] = GetTok( text, " ", i+1 );

if( ( param.len() == 1 ) && IsNum( param[0] ) ) return AnnounceRace( param[0] );
else if( ( param.len() == 2 ) && IsNum( param[1] ) ) return AnnounceRace( param[0], param[1] );
else if( ( param.len() == 3 ) && IsNum( param[2] ) ) return AnnounceRace( param[0], param[1], param[2] );
else return pMSG( COLO_GREY + "Error - One/more params are not numbers. Every parameter must be number.", player );

else if( cmd == "joinrace" )
JoinTrack( player );

else if ( cmd == "quitrace" )
QuitTrack( player );

else if ( cmd == "flip" )
local veh = player.Vehicle;
            if ( !veh ) pMSG( COLOR_GREY + "You need to be in a vehicle to use this command.", player );
local rot = veh.Rotation;
                veh.Rotation = Quaternion( 0.0, 0.0, veh.z, veh.w ); // by Xmair
                pMSG( COLOR_BLUE + "Vehicle flipped.", player);

else if ( cmd == "lastcp" )
LastCP( player );

else if( cmd == "racedev" )
if( racedev == player.ID ) return devExit( player );
else if( racedev == null ) return devEnter( player );

else if( cmd == "createtrack" )
if( racedev == null ) return pMSG( COLOR_GREY + "Error - You must be in the developer mode!", player );
else if( racedev != player.ID ) return pMSG( COLOR_GREY + "Error - You must be in the developer mode!", player );
else if( !text ) return pMSG( COLOR_GREY + "Error - Correct Syntax: /" + cmd + " <name> <laps?>", player );

local track,
name = GetTok( text, " ", 1 );

if( NumTok( text, " " ) != 2 )
track = CreateTrack( name, player );
if( track )
Race.ID = track.ID;
pMSG( COLOR_BLUE + "[RACEDEV] " + player.Name + " created a new racetrack " + name, player );

local laps = GetTok( text, " ", 2 );
if( ( laps != null ) && !IsNum( laps ) ) return pMSG( COLOR_GREY + "Error - Second parameter '<laps>' must be a number!", player );

track = CreateTrack( name, player, laps.tointeger() );
if( track )
Race.ID = track.ID;
pMSG( COLOR_BLUE + "[RACEDEV] " + player.Name + " created a new racetrack " + name + " with " + laps + " laps.", player );

else if( cmd == "createcp" )
if( racedev == null ) return pMSG( COLOR_GREY + "Error - You must be in the developer mode!", player );
else if( racedev != player.ID ) return pMSG( COLOR_GREY + "Error - You must be in the developer mode!", player );

local tid = Race.ID;
if( tid == null ) return pMSG( "Unfortunately, there is no race announced yet.", player );

local track = trackTable.rawget( tid );
CreateCP( tid, player.Pos );
pMSG( COLOR_BLUE + "[RACEDEV] " + track.Name + " got a new checkpoint by " + player.Name, player );

4.Finally, the magic function
[noae]function onCheckpointEntered( player, checkpoint )
if( Race.ID != null && trackTable.rawin( Race.ID ) )
local tid = Race.ID,
track = trackTable.rawget( tid );

if( track.pptTable.rawin( player.ID ) )
local ppt = track.pptTable.rawget( player.ID );
if( ppt.UCP.ID != checkpoint.ID ) return 0;

ReachCP( player );