Skip to content
On this page

Prototyping the Player

Written on March 13, 2023

Let me preface by saying do not do prototyping, the limitations far outweigh the benefits.

Prototyping the default alt:V Player class has its benefits but it also has downsides as well.

If you are unaware of what Prototyping is; it allows you to 'extend' the Player interface to allow for custom properties, functions, etc. to be found to the player instance itself.

Here's an example of how to prototype, but also how it could be used.

ts
declare module "alt-server" {
    export interface Player {
        test: string
    }
}
ts
alt.on('playerConnect', (player: alt.Player) => {
    player.test = 'hello world!'
});

At first glance this is really interesting, but also really convenient behavior you can bind data to a player.

What are the downsides?

You might be thinking that's amazing! I wish I knew this earlier.

That's great; but let me explain why it is horrible behavior that limits your gamemode drastically.

Let's say for example you want to create a way to listen to the changes of that specific property.

Well, you would have one option and it's not great.

ts
const lastValues: { [id: string]: string}  = {};

alt.setInterval(() => {
    alt.Player.all.forEach((player) => {
        if (lastValues[player.id] === player.test) {
            // It's the same value, do not do anything
            return;
        }

        // It's not the same value, invoke the listen event
        lastValues[player.id] = player.test;
        invokeListenCallback(player);
    })
}, 100)

Why is this bad you may be asking? You have just created an interval for a single property that constantly loops through all changes to do a compare / contrast of their current properties and their previous properties. This is not only performance intensive, but also just genuinely awful code.

You will have to do this for almost every single property you introduce.

What is the alternative?

The alternative is simply not using a prototype at all, and either going with a Class or a what we did in Athena which was a get / set cache system that fires off events for specific property keys when they are set on a player.

What do I mean by this?

In V5 I created a system that internally caches the data that belongs to a given authenticated player. This data is stored in memory until their session has ended.

Data is stored in a single variable that uses the player.id and has an object such as { example: [] } assigned to it.

Now when I want to set a single variable, we use a function to set a key to a specific value.

ts
Athena.document.character.set<{ example: Array<string> }>(player, 'example', ['hello world']);

Internally the set function here applies the value to the specific key, and also updates the database by pushing out the changes after setting the cache first.

This is important to keep the server fluid without waiting for database calls.

Now, what is important here is that I said you can listen to property key changes.

With another function we can build functionality around changes that occur to the player's data by listening to those changes.

ts
Athena.document.player.onChange<{ example: Array<string> }>('example', (player, newValue, oldValue) => {
    // do something with this changed data
    // newValue = ['hello world']
    // oldValue = undefined
});

What we are doing here is pretty much a fire and forget pattern where we do not care if a function responds.

The function is there to provide a specific purpose and that is up to the individual developer to do something with those changes.

This is also way more performance efficient since we are not running a constant interval that is comparing properties. Instead, it just pushes out that data and if there are callbacks setup to listen for that property key they will get their invoke.

Created by Stuyk | Est. 2020