Garage
The garage lets players store a dinosaur and take it out again later. The server plugin does the saving, the removal of the parked dinosaur and the restore. Your resource adds the commands and the rules. All functions are in modkit.garage.
Read how park and restore work first. Two facts matter for your rules. A restore needs a living dinosaur of the same species, so the player spawns a fresh one and the saved state is written onto it. And a slot is only used up once the state was written.
A complete resource
resources/garage/manifest.lua
name "garage"
server "server.lua"
resources/garage/server.lua
local COOLDOWN = 15 * 60
local MIN_GROWTH = 0.3
local MIN_HEALTH = 0.7
local SAFE_DISTANCE = 15000
modkit.garage.setLimit(3)
modkit.garage.setLimitCallback(function(player)
return player:hasPermission("garage.vip") and 8 or 3
end)
local function cooldownLeft(player)
local last = modkit.storage.get("restore:" .. player.steamId, 0)
return math.max(0, last + COOLDOWN - os.time())
end
local function enemyNearby(player)
for _, other in ipairs(modkit.players.list()) do
if other.steamId ~= player.steamId and other.hasDino and other.alive
and not player:inSameGroup(other) then
local distance = player:distanceTo(other)
if distance and distance < SAFE_DISTANCE then return true end
end
end
return false
end
modkit.events.add("garageParking", function(player)
if player.growth < MIN_GROWTH then
player:message("You need 30% growth to park.")
return false
end
if player.health < player.maxHealth * MIN_HEALTH then
player:message("You need 70% health to park.")
return false
end
if enemyNearby(player) then
player:message("Another player is within 150 m.")
return false
end
end)
modkit.events.add("garageRestoring", function(player, slot)
local wait = cooldownLeft(player)
if wait > 0 then
player:message("Next restore in " .. modkit.util.formatTime(wait))
return false
end
if modkit.population.isBlocked(slot.species) then
player:message(slot.species .. " is full right now.")
return false
end
if enemyNearby(player) then
player:message("Another player is within 150 m.")
return false
end
end)
modkit.events.add("garageRestored", function(player, slot)
modkit.storage.set("restore:" .. player.steamId, os.time())
player:notify(slot.name .. " is back.")
end)
modkit.events.add("garageRestoreFailed", function(steamId, slotId, reason)
modkit.log.warn("restore of slot " .. slotId .. " for " .. steamId .. " ended early: " .. reason)
end)
modkit.commands.add("park", function(player, ...)
local name = table.concat({ ... }, " ")
local slot, why = modkit.garage.park(player, { name = name ~= "" and name or nil })
if slot then
player:message("Parked as slot " .. slot.id .. ". Spawn a new " .. slot.species .. " and use /restore " .. slot.id)
else
player:message("Not parked: " .. why)
end
end)
modkit.commands.add("garage", function(player)
local slots = modkit.garage.list(player)
if #slots == 0 then player:message("Your garage is empty.") return end
for _, slot in ipairs(slots) do
player:message(string.format("%d: %s, %s, %d%%", slot.id, slot.name, slot.species, math.floor(slot.growth * 100)))
end
player:message(#slots .. " of " .. modkit.garage.getLimit(player) .. " slots used")
end)
modkit.commands.add("restore", function(player, slotId)
local id = tonumber(slotId)
if not id then player:message("usage: /restore <slot>") return end
local slot, why = modkit.garage.restore(player, id, { atParkPosition = false })
player:message(slot and ("Restoring " .. slot.name .. " ...") or ("Not restored: " .. why))
end)
modkit.commands.add("givedino", function(admin, steamId, species, growth)
if not steamId or not species then admin:message("usage: /givedino <steamId> <species> [growth]") return end
local slot, why = modkit.garage.give(steamId, { species = species, growth = tonumber(growth) or 1, fill = true }, { name = "Gift " .. species })
admin:message(slot and ("Slot " .. slot.id .. " created") or why)
end, "garage.give")
Add load garage to server.cfg and restart.
Notes on the rules
The checks sit in the two "before" events and not in the commands. That way they also hold when another resource calls modkit.garage.park for the player.
garageRestoring fires after the plugin checked the slot, the species and that no other restore runs. If your handler lets it through, the restore almost always starts, so the cooldown is written in garageRestored.
The cooldown is kept in modkit.storage, so it survives a restart.
enemyNearby uses snapshot positions, which can be half a second old. For a 150 m rule that does not matter.
atParkPosition = true would put the dinosaur back where it was parked. Most servers leave it off, because it lets players teleport across the map through the garage.
What players will notice
- Parking kills the dinosaur. The player sees the death screen and picks a new character. The game has no clean way to take a dinosaur away from a player, so the plugin uses the death.
- To restore, the player spawns as the same species first and then uses
/restore. The fresh dinosaur grows to the saved size within a few seconds. - The kill feed and
playerDiedfire for a parked dinosaur. If you count deaths, skip a death that follows agarageParkedof the same player within a few seconds.
Where the data is
data\garage\<steamId>.json under the server root. One file per player with all slots. You can delete a file to empty a garage while the server is stopped. The format of state is the one of player:getState().