Player Data System

One of the main performance issues with Skript is the variable system.
If used correctly it can be great, but it is often abused/overused which can cause major performance issues with a server.

Let's do some basic math:
Let's say per-player you have the following variables:
- Money
- Points
- Homes (5)
- A player-vault with 27 slots
- Nickname
- team/group/clan
This alone is 36 variables.... per player. (Most servers have a lot more than this, this is just a small example)
Now let's say over a short period of time, you've had 1000 unique joins.
That is 36,000 variables saved in one file.
Each time your server loads, it loads ALL of these variables into RAM. Most of these players won't ever come back but you've got a lot of variables loading each time.

Things like this can cause great stress on your server. So what can we do to alleviate that pressure on your server? Well, this is where I introduce to you, a PlayerData system using skript-yaml.

This system is quite easy to use, and is very flexible. The goal here is to create a PlayerData file per player, and only load into RAM the players that are online.

So let's get started.
This system uses skript-yaml, if you need more info check out the SKRIPT-YAML REPO for more information.

Loading PlayerData into RAM:
When the player joins, we will load their data into RAM for easy/quick access.
As stated, this allows us to load into RAM the data for players which are currently online, and not the 100s or 1000s of players who won't ever come back.
We will also set their current name and last-login for future use
on join:
load yaml "plugins/MyData/%uuid of player%.yml" as "data-%uuid of player%"
set yaml value "name" in "data-%uuid of player%" to name of player
set yaml value "last-login" in "data-%uuid of player%" to now
save yaml "data-%uuid of player%"


Unloading PlayerData:
When the player quits, we will save/unload their data.
This again will help keep our RAM at efficient usage.
on quit:
save yaml "data-%uuid of player%"
unload yaml "data-%uuid of player%"


Saving PlayerData periodically to file:
Rather than saving PlayerData to file every time we make a change to it, we will do it periodically.
Why you ask? Thing of systems like jobs, where players get payed money everytime they do an action, like for example mining. Now imaging 100 players online, mining hundreds of blocks a second. We don't want to constantly be writing to file, this would put a lot of strain on the server and affect performance.
So we periodically save to file.
Don't worry, the PlayerData will always be in ram, you can write to/read from without affecting performance. The save we are talking about here is writing to your yaml file.
# Every 5 minutes we will save all current online player data just to be safe
# Adjust time to your liking
every 5 minutes:
loop all loaded yaml:
save loop-value
# We add a small wait between each player data to prevent
# a massive amount of lag if a lot of players are online
# Adjust to suit your server's needs
wait 2 ticks


Accessing/Modifying our PlayerData:
FUNCTIONS
We will create some functions for quick access to player data
I'm using money and points as an example, but you can use whatever data you need for your server.
These simple functions allow you to easily get/set/add/remove info from PlayerData files.


# Money
function getMoney(p: player) :: number:
return yaml value "money" from "data-%uuid of {_p}%"

function setMoney(p: player, n: number):
set yaml value "money" in "data-%uuid of {_p}%" to {_n}

function addMoney(p: player, n: number):
set {_money} to yaml value "money" in "data-%uuid of {_p}%"
add {_n} to {_money}
set yaml value "money" in "data-%uuid of {_p}%" to {_money}

function removeMoney(p: player, n: number):
set {_money} to yaml value "money" in "data-%uuid of {_p}%"
remove {_n} from {_money}
set yaml value "money" in "data-%uuid of {_p}%" to {_money}

# Points
function getPoints(p: player) :: number:
return yaml value "points" from "data-%uuid of {_p}%"

function setPoints(p: player, n: number):
set yaml value "points" in "data-%uuid of {_p}%" to {_n}

function addPoints(p: player, n: number):
set {_points} to yaml value "points" in "data-%uuid of {_p}%"
add {_n} to {_points}
set yaml value "points" in "data-%uuid of {_p}%" to {_points}

function removePoints(p: player, n: number):
set {_points} to yaml value "points" in "data-%uuid of {_p}%"
remove {_n} from {_points}
set yaml value "points" in "data-%uuid of {_p}%" to {_points}



Usage:
So how do we use these functions? Well I'm glad you asked, if you haven't used functions before I recommend checking out the FUNCTIONS WIKI for more info. If you have used functions before you will know how extremely easy they are to use.

Here is an example command using our newly created functions:
# Now that we have our base set up, lets use our data
# Quick example of a points command using our data functions
# In this example i will be skipping some obvious conditions for args
# I just want to show the basic usage of the functions
# I will also not be putting obvious messages, again, just to simply show how this works

command /points <text> [<player=%player%>] [<number>]:
trigger:
if arg-1 = "get":
# here we can simply print a message with the points of the player/arg
send "Points for %arg-2%: %getPoints(arg-2)%"
else if arg-1 = "set":
# here we can set points
setPoints(arg-2, arg-3)
else if arg-1 = "add":
# Here we can add to the points
addPoints(arg-2, arg-3)
else if arg-1 = "remove":
# Here we can remove from the points
removePoints(arg-2, arg-3)


Here is a sample event using our newly created functions:
# We're going to use on join, to set the defaults for the player
# If the values are not currently set, let's set them

on join:
# Here we set the player's points to 10 when they join if its not currently set
if getPoints(player) is not set:
setPoints(player, 10)
# Here we set the player's money to 500 when they join if its not currently set
if getMoney(player) is not set:
setMoney(player, 500.00)


Clearing old PlayerData:
Lastly, lets say we want to clear out old player data (player's who havent logged in for a while)
We can simply run a command to clear out old data
Obviously this command will need permission checks and stuff, again, this is just an example

Special Note: A command like this should not be run when a lot of players are online
command /clearOldData:
trigger:
set {_n} to 0 # lets create a counter
loop all offline players:
# here we are going to load each offline player's data, and check the difference between now and last-login
# if grater than 100 days, we will delete their data
# else we will just unload it and move onto the next
load yaml "plugins/MyData/%uuid of loop-offline player%.yml" as "off-%uuid of loop-offline player%"
set {_t} to yaml value "last-login" from "off-%uuid of loop-offline player%"
if difference between now and {_t} > 100 days:
delete yaml "off-%uuid of loop-offline player%"
# lets just let the console know we're deleting this
send "Deleting data for %loop-offline player%" to console
# We will add 1 to {_n} so we know how many player data's we cleared
add 1 to {_n}
else:
unload yaml "off-%uuid of loop-offline player%"
wait 1 tick
# Now we tell the console how many data's we cleared
send "Removed player data for %{_n}% inactive player(s)" to console


Example PlayerData file:
Here is an example of what MY PlayerData file looks like, using this code:
name: ShaneBee

last-login: !skriptdate '2020-03-23T13:59:42.570-07:00'

points: 10

money: 500.0