Saving data¶
Config files are for settings that the server owner changes. Saving data is for things your plugin remembers by itself, like how many times each player has joined. In this lesson you'll learn how to open a data file, save and load values, and update old saved data when your plugin changes. You'll finish with a plugin that counts every player's joins and remembers them after a restart.
Config and storage
A file you read and write from your plugin is called a storage object, and it works a lot like config, but the names are a bit different. config has setDefault, set and clear, and a storage object has setDefaultValue, setValue and clearValue. Also, config is called with a dot, like config.set(...), while a storage object is called with a colon, like data:setValue(...).
A storage object can open any YAML file, so you can also use one for an extra config file, like messages.yml. Just don't open config.yml that way, because config already looks after it.
Part 1: Save and read a value¶
To store something, first open a file. Add this to your main.lua:
local data = plugin.getStorageObject("data.yml")
plugin.registerEvent("PlayerJoinEvent", function(event)
local total = data:getValue("total-joins") or 0
total = total + 1
data:setValue("total-joins", total)
data:save()
event:getPlayer():sendMessage("The server has had " .. total .. " joins!")
end)
plugin.getStorageObject("data.yml") opens a file called data.yml in your plugin's folder, and creates it if it isn't there yet. The ending of the name matters. Use .yml, which is easy to read and edit.
data:getValue("total-joins") reads a value. The first time, nothing has been saved yet, so it gives back nil. That's what or 0 is for: it means "use the saved number, and if there's nothing saved, use 0". It only picks a fallback for that one line and doesn't save anything. The saving happens afterwards, with setValue and save. Then we add one, and data:setValue stores the new number. That only changes it in memory, so data:save() writes it to the file. Without save, the number is lost when the server restarts.
Try joining a few times and then look at plugins/YourPluginName/data.yml.
Part 2: Save data for each player¶
One counter for the whole server isn't very useful, so let's give every player their own. To tell players apart we use their unique ID, which never changes, even if they change their name. Replace your join event with this:
local data = plugin.getStorageObject("data.yml")
plugin.registerEvent("PlayerJoinEvent", function(event)
local player = event:getPlayer()
local key = "joins." .. tostring(player:getUniqueId())
local joins = (data:getValue(key) or 0) + 1
data:setValue(key, joins)
data:save()
player:sendMessage("Welcome back, " .. player:getName() .. "! This is join number " .. joins .. ".")
end)
Here's what each line does:
local player = event:getPlayer()is the player who just joined.local key = ...makes the label that this player's count is saved under. Every player needs their own label, so we build one from their unique ID, a long code like0b2f4c1e-...that belongs to one player forever.tostringturns the ID into text, so it can be glued onto"joins.", which gives a label likejoins.0b2f4c1e-.... The dot groups every player's count under onejoins:heading in the file.local joins = (data:getValue(key) or 0) + 1works out the new count. It reads this player's saved count, or 0 if they've never joined, and then adds one. So the first time it's0 + 1, which is 1, and the next time it's1 + 1, which is 2. The brackets make sure the reading happens before the+ 1.data:setValue(key, joins)remembers the new count, anddata:save()writes it to the file.- The last line sends the player a message with their count.
Join a few times and check that the number goes up, then restart the server and join again to see that it remembers.
Part 3: Look up and remove data¶
Let's add two commands. The first shows your count, and the second resets it. Add them below the join event:
plugin.addCommand({
name = "joins",
description = "See how many times you have joined",
usage = "/joins"
}, function(event)
local sender = event.getSender()
if event.isPlayerSender() then
local key = "joins." .. tostring(sender:getUniqueId())
local joins = data:getValue(key) or 0
sender:sendMessage("You have joined " .. joins .. " time(s).")
else
sender:sendMessage("Only players have a join count.")
end
end)
plugin.addCommand({
name = "resetjoins",
description = "Reset your join count",
usage = "/resetjoins"
}, function(event)
local sender = event.getSender()
if event.isPlayerSender() then
local key = "joins." .. tostring(sender:getUniqueId())
local removed = data:clearValue(key)
if removed then
data:save()
sender:sendMessage("Your join count was reset.")
else
sender:sendMessage("You didn't have a join count to reset.")
end
else
sender:sendMessage("Only players have a join count.")
end
end)
Both commands start by making the player's key, the same way as in the join event, and /joins uses it to read the count. /resetjoins uses data:clearValue(key), which removes the saved count and tells you whether there was one to remove. It gives back true if it removed something and false if there was nothing there. We save that answer as removed, and the if uses it to pick the right message.
If you want to check a value without reading it, data:exists(key) gives back true or false.
Part 4: Update old data¶
Just like with config, your plugin will change, and the data players have already built up shouldn't be lost. Saved data you read with or, like (data:getValue(key) or 0), already copes with something that's new, because an old file simply doesn't have it yet and the or supplies a starting value.
Moving data takes a bit more. Say version 1 of your plugin saved the server count as total-joins, and version 2 wants it under stats.total-joins. Add this near the top of your main.lua, right after you open the file:
local data = plugin.getStorageObject("data.yml")
-- Version 2 moved "total-joins" to "stats.total-joins"
if (data:getValue("data-version") or 1) < 2 then
local old = data:getValue("total-joins")
if old then
data:setValue("stats.total-joins", old)
data:clearValue("total-joins")
end
data:setValue("data-version", 2)
data:save()
end
Old data has no data-version, so or 1 treats it as version 1, and it goes into the if. If the old total-joins value is there, we copy it to the new place and remove the old one. Then we mark the file as version 2 and save, so this only ever runs once. New data skips the copy, since there's nothing to move.
Unlike config.getValue, a storage object's getValue gives you a real nil when nothing is saved, so if old then works.
Next time you change the layout, bump the number and add another if below this one.
Putting it together¶
Combined, your main.lua is a join counter that every player gets their own count in:
local data = plugin.getStorageObject("data.yml")
plugin.registerEvent("PlayerJoinEvent", function(event)
local player = event:getPlayer()
local key = "joins." .. tostring(player:getUniqueId())
local joins = (data:getValue(key) or 0) + 1
data:setValue(key, joins)
data:save()
event:setJoinMessage(player:getName() .. " joined the game! (join number " .. joins .. ")")
end)
plugin.addCommand({
name = "joins",
description = "See how many times you have joined",
usage = "/joins"
}, function(event)
local sender = event.getSender()
if event.isPlayerSender() then
local key = "joins." .. tostring(sender:getUniqueId())
local joins = data:getValue(key) or 0
sender:sendMessage("You have joined " .. joins .. " time(s).")
else
sender:sendMessage("Only players have a join count.")
end
end)
plugin.addCommand({
name = "resetjoins",
description = "Reset your join count",
usage = "/resetjoins"
}, function(event)
local sender = event.getSender()
if event.isPlayerSender() then
local key = "joins." .. tostring(sender:getUniqueId())
local removed = data:clearValue(key)
if removed then
data:save()
sender:sendMessage("Your join count was reset.")
else
sender:sendMessage("You didn't have a join count to reset.")
end
else
sender:sendMessage("Only players have a join count.")
end
end)
One thing changed in the join event. Instead of sending a message to just that player, event:setJoinMessage(...) changes the message everyone sees when someone joins, so the server announces "Steve joined the game! (join number 3)".
Try these:
- Join the server a few times and watch the number go up.
- Restart the server and join again, and the count carries on.
- Type
/joinsto see your count. - Type
/resetjoins, then/joinsto see it start from zero.
Quick reference¶
These are the things you can do with a storage object. Call them with a colon, like data:save(). Values can be text, numbers, true or false, or tables.
| Call | What it does |
|---|---|
plugin.getStorageObject(file) |
Opens a .yml file in your plugin's folder, and creates it if it isn't there |
data:getValue(name) |
Gives back a saved value, or nil if nothing is saved |
data:setValue(name, value) |
Changes a value, which isn't written to the file until you save |
data:setDefaultValue(name, value) |
Sets a value only if it's missing, and gives back true if it did |
data:exists(name) |
Gives back true if something is saved there |
data:clearValue(name) |
Removes a value, and gives back true if there was one |
data:save() |
Writes your changes to the file |
That's the end of Basics for now. You now know how to make commands, react to events, send colorful messages, work with players, read the Spigot docs, and keep settings and data. More is coming in Advanced.