-
-
Notifications
You must be signed in to change notification settings - Fork 500
Description
Introduction
Before I start, I have to say I read #1835 and #1936 as well as their comments more than once, I did the same for the discussion on Discord. So, this is my version of how to implement this feature based on my analysis of what everyone in those places said. I did not come up with it alone and I would like the people whom this is based off to comment here too. Here I try to take a simpler and less intrusive approach to solve the issue, while also giving more control to scripters and server owners.
Serverside Functions
bool allocateModel(string elementType, int parentID, string modelName)
- This function allocates a new model of the specified type and associates it with a model name. The serverside internal model integer ID is never exposed, the server can access the model only by using the model name.
- Returns true on successful serverside allocation.
- Returns false if given invalid element type, invalid parent ID or server ran out of available model IDs internally.
- If the model was successfully allocated serverside you can then use requestModel() (see function below) to allocate it clientside.
- Model names are namespaced to the resource that allocates them, this means resources can only access model names allocated within them (both clientside and serverside).
- To access model names from other resources, a namespace modifier is required like the one present in the xmlCreateFile() function (i.e. ":resourceName/modelName" or any other format you think is better).
- The allocated model will inherit all properties of the parent model (i.e. DFF/TXD/COL (once requested clientside), friendly name, vehicle handling, sounds, etc).
- Some of these properties (i.e. friendly vehicle name like "BF Injection") don't have functions to change them, this can be fixed by creating new functions, like setVehicleModelName(), setSkinModelName(), setVehicleModelSeatCount(), etc. These functions would also naturally work with standard models (i.e. for resources that replace them with custom models).
- The model name can be used on all functions that take an integer model ID as an argument, like createVehicle(), createObject(), setElementModel(), setModelHandling(), etc. This means all these functions need to be updated to work with model names (which would work both with custom and standard models) too.
- If an integer model ID is passed to those functions, they will work like they always did (i.e. assuming it is a standard model), nothing changes.
- This whole behavior is the most important, as by default it avoids model name collisions between resources and doesn't break older resources, nothing changes if you never use this function and the other ones related to it.
bool requestModel(string modelName [, table/player requestTo])
- This function causes an invisible engineRequestModel() call on the specified client (or clients), which will associate the returned integer model ID from that function with the serverside model name (this syncs the model between client and server). During the request, the server sends the properties of the model to the client, like type and parent model ID (it is not necessary to give them as arguments).
- Returns true on successful request, but this does not necessarily mean successful clientside allocation (see event below, which is also the reason for this function's existence).
- Returns false if given invalid model name or none of the specified clients are connected (probably unnecessary to check since this function has an associated event).
- If requestTo is not given (nil), the function will default to requesting the model on all currently connected clients.
- Successfully allocating a serverside model on the client prevents engineRequestModel() from allocating a clientside model with the specified model name.
- If the specified model name is already in use clientside, the server overrides it and makes the model name serverside, causing the same effect above. The previously allocated clientside model is automatically freed before the override takes place.
event onModelRequest(string modelName, bool success)
- The source of this event is the player who the server requested a model to be allocated clientside on.
- success is true if the player successfully allocated the model clientside and associated it with the serverside model name.
- success is false if the player ran out of available integer model IDs internally.
- You can use this event to decide what to do with the player if they can't allocate your desired models clientside (i.e. notify them, ask them to reconnect or restart their game, kick them, try requestModel() again at a different time, etc), which is important as it gives the server owner more control of the situation so they can decide what the best course of action is.
- It is important to not hardcode what is done with the player on failure because not every server owner will want the same result, some may even have fallback mechanics (i.e. let the player play with the parent model ID of a custom vehicle), because at the end of the day it is impossible to know how many servers the player had played on before joining, how long their game has been running and most importantly how many free model IDs they still have available (a serverside function to get clientside free model ID count can be made if desired).
- This also avoids the need to allocate serverside models on the client immediately on join, letting them to be allocated on successful login or at any other point in time by the server.
- This event can also be used to let the player know a clientside model was overridden by a serverside model. The server always take precedence on this, no matter what. Perhaps the integer model ID allocated clientside should be sent as an argument to this event to let the server send more precise information to the client.
bool replaceModelCOL(string colFile / string rawData, string modelName)
bool replaceModelTXD(string txdFile / string rawData, string modelName)
bool replaceModelDFF(string dffFile / string rawData, string modelName [, bool alphaTransparency = false])
- These functions work similarly to their clientside counterpart, the difference is they simply set what DFF/TXD/COL will be allocated clientside when requestModel() is called on a client.
- Returns false if given file doesn't exist.
bool freeModel(string modelName)
- Returns true on successful serverside free.
- Returns false if given non-existent model.
- This frees the model serverside (and clientside on players whom requestModel() was used on).
Clientside Functions
int engineRequestModel(string elementType, int parentID [, string modelName])
- This function retains its original behavior, the only difference is an optional argument.
- If modelName is given, the returned integer model ID will be associated with a model name which the scripter can now optionally use instead of the integer ID (this can be considered best practice).
- Integer model IDs remain exposed clientside for those who want them, which is not a problem since the scripter is only supposed to use clientside integer model IDs allocated within their resource (however, this can be considered bad practice).
- Returns false if the client ran out of available integer model IDs or the given model name is already in use serverside.
- Like with the serverside functions, clientside functions that take an integer model ID can now take a model name as an argument (applies to both clientside and serverside models).
bool engineReplaceCol(col theCollision, int modelID / string modelName)
bool engineImportTXD(txd theTexture, int modelID / string modelName)
bool engineReplaceModel (dff theModel, int modelID / string modelName [, bool alphaTransparency = false])
bool engineFreeModel(int modelID / string modelName)
- These functions retain their original behavior, the only difference is they can now take a model name instead of an integer model ID.
- It can now return false if the given model is allocated serverside (only the server can modify the DFF/TXD/COL of serverside models and free them). This specific behavior might be optional, up to you, but I think the server should retain control here.
Behavior Summary
- Models allocated serverside can only be accessed by the server through their model name. The serverside internal integer model IDs are never exposed because they do not necessarily match the clientside integer model IDs.
- The server always has priority on model allocation and can override models allocated clientside, this is necessary to avoid desync, simplify logic and avoid unnecessary edge cases. It is the responsibility of the scripter to ensure clientside models aren't being unnecessarily overriden by the server.
- Clientside integer model IDs, however, can remain exposed. Clients can access serverside models both by model name and clientside integer model ID (with serverside models this integer ID would not be automatically exposed to the client, unless a function to get it is created or the onModelRequest() event above shows it to the scripter).
- Clientside integer model IDs remaining exposed is fine because they are not synced between clients, the server can always override them, and only models allocated serverside are synced between clients.
- Allocating a model serverside and allocating the same model clientside is done by different functions along with an event, this is important as it keeps serverside and clientside model allocation failure separate (increases resource reliability), and gives scripters and server owners more possibilities on how to handle each case and lets them decide when to sync serverside and clientside models.
- Because model names are namespaced to the resource that uses them, collisions between different resources is automatically avoided. Multiple resources can use, for example, the same "myCustomModel" model name without issues, and a namespace modifier is required to access model names from other resources.
- I'm leaving the method of seeing what model IDs are available serverside up to you, I'm not sure how to go about that. But in any case, the pool of free model IDs will be extendable without breaking resources in the future.
- The general behavior I outlined here also avoids breaking older resources, as the effects of these functions (when done this way) are entirely optional. This is important because while this feature is a big game changer in MTA, it should be introduced as nonintrusively as possible to make it easier for everyone to adapt to it and keep older resources working normally.
Final Note
I think this is a much simpler way of implementing this new feature. It avoids the brittleness of integer model IDs, and the overhead and unnecessary complication (in my opinion) of doing it all through a new custom Model element. Hopefully this helps everyone come to a decision.