garage
Server
modkit.garage stores dinosaurs. A player parks the dinosaur they play, the server saves its state into a slot, and the player can later put that state back onto a new dinosaur of the same species.
Slots live in one file per player, data\garage\<steamId>.json under the server root. Files are written on a background thread with a temp file and a rename, and they survive restarts. The garage works without any script setting it up.
How park and restore work
Park. The server reads the state, stores the slot, and then removes the dinosaur in the same tick. There is no pause between saving and removing, because a pause is a window for a duplicate. Removing means growth is set to 25 percent and the dinosaur is killed. The low growth keeps the corpse from being a food source. If the body is still alive 3 seconds later it is destroyed. With keepAlive = true the dinosaur stays, which copies it.
Restore. The player must be alive on a dinosaur of the same species as the slot. The state is written in four steps.
| Step | When | What |
|---|---|---|
| 0 | at once | growth, maximum values, parent and elder mutations, nutrients, injuries cleared, prime state, elder stacks, health, stamina, hunger, thirst, skin |
| 1 | after 0.5 s | unlocked mutations, active mutation slots, growth again so the game recalculates maximum health |
| 2 | after 1 s | nutrients and vitals again, injuries cleared again, character saved to disk, optional teleport, HUD refresh. The slot is removed here and garageRestored fires. |
| 3 | when growth stops changing | hunger, thirst and food once more, because the game raises the maximum values over several seconds |
No duplicates. A slot stays when step 0 could not run, for example wrong species or no dinosaur. Once step 0 ran, the living dinosaur carries the state. If it dies or the player leaves before step 2, the slot is removed anyway and garageRestoreFailed fires. Otherwise the player would keep the slot and the dinosaur the game saved on logout.
While a restore runs for a player, a second restore and a park are refused.
There is no swap to another species during restore. Use player:setSpecies first if you need that, see player.
The slot table
| Field | Type | Description |
|---|---|---|
id |
integer | slot id, unique per player, never reused |
name |
string | label, the species name by default |
parkedAt |
integer | Unix time |
species, growth, female, prime |
copied from the state for lists | |
state |
table | the full state, same format as player:getState() |
modkit.garage.park
modkit.garage.park(player [, options])
| Parameter | Type | Description |
|---|---|---|
| player | player table or string | player or Steam ID |
| options.name | string | label for the slot |
| options.keepAlive | boolean | true leaves the dinosaur in the world. Default false. |
Returns: the slot table, or nil and a reason. Reasons are "a restore is still running", "player has no live dinosaur", "garage is full", "cancelled by a script", "dinosaur state could not be read".
modkit.commands.add("park", function(player, name)
local slot, why = modkit.garage.park(player, { name = name })
player:message(slot and ("Parked in slot " .. slot.id) or ("Not parked: " .. why))
end)
modkit.garage.restore
modkit.garage.restore(player, slotId [, options])
| Parameter | Type | Description |
|---|---|---|
| player | player table or string | player or Steam ID |
| slotId | integer | slot id |
| options.fillStats | boolean | true fills hunger, thirst, food, diets, health and stamina instead of using the saved values. Default is the fill field of the state, which is false for parked dinosaurs. |
| options.atParkPosition | boolean | true moves the dinosaur to the place where it was parked. Default false. |
Returns: the slot table when the restore started, or nil and a reason. Reasons are "slot not found", "a restore is still running", "player has no live dinosaur", "player must play <species> first", "cancelled by a script", "state could not be applied".
The function returns right after step 0. Use the garageRestored event to know when it finished.
modkit.commands.add("restore", function(player, slotId)
local slot, why = modkit.garage.restore(player, tonumber(slotId) or 0, { fillStats = true })
player:message(slot and "Restoring " .. slot.name or "Not restored: " .. why)
end)
modkit.garage.list
modkit.garage.list(player)
Returns: array of slot tables, oldest first. Works for players who are offline.
for _, slot in ipairs(modkit.garage.list(player)) do
player:message(slot.id .. ": " .. slot.name .. " " .. math.floor(slot.growth * 100) .. "%")
end
modkit.garage.get, remove, rename
modkit.garage.get(steamId, slotId) returns the slot table or nil.
modkit.garage.remove(steamId, slotId) deletes a slot and returns true when it existed.
modkit.garage.rename(steamId, slotId, name) returns true when the slot exists.
All three accept a player table in place of the Steam ID.
modkit.garage.rename(player, 3, "Old Stego")
modkit.garage.give
modkit.garage.give(steamId, state [, options])
Puts a dinosaur into a player's garage, for shops and rewards. The player does not have to be online.
| Parameter | Type | Description |
|---|---|---|
| steamId | player table or string | target |
| state | table | needs species. growth defaults to 1. Every other field of the state format is optional. Set fill = true so the dinosaur comes out with full stats. |
| options.name | string | label |
| options.ignoreLimit | boolean | store the slot even when the garage is full |
Returns: the slot table, or nil and "garage is full".
modkit.garage.give("76561198000000001", { species = "Stegosaurus", growth = 1, fill = true }, { name = "Shop Stego" })
modkit.garage.setLimit, getLimit, setLimitCallback
modkit.garage.setLimit(slots) sets the slot limit for everybody. The default is 10.
modkit.garage.getLimit([player]) returns the general limit, or the limit for one player.
modkit.garage.setLimitCallback(fn) sets a function fn(player) that returns the limit for that player. nil removes it. Only one callback exists for the whole server, the last call wins.
modkit.garage.setLimit(3)
modkit.garage.setLimitCallback(function(player)
return player:hasPermission("garage.vip") and 10 or 3
end)
Events
| Event | Arguments | Cancel |
|---|---|---|
garageParking |
player, name |
return false to stop the park |
garageParked |
player, slot |
|
garageRestoring |
player, slot |
return false to stop the restore |
garageRestored |
player, slot |
|
garageRestoreFailed |
steamId, slotId, reason |
The two "before" events fire after the built-in checks passed, so a handler can take payment or start a cooldown without undoing it later. The one exception is "state could not be applied", which can still happen after garageRestoring.
modkit.events.add("garageParking", function(player)
if player.health < player.maxHealth * 0.7 then
player:message("You need 70% health to park.")
return false
end
end)