IMIsle Modkit
Pages in this area

Bans, whitelist and permissions

Four namespaces decide who may join and who may do what. modkit.bans keeps players out, modkit.whitelist and modkit.reserved limit who gets in, and modkit.acl hands out permissions that commands check.

All of it is stored as JSON under data\ next to server.cfg, in bans.json, whitelist.json and acl.json. The files survive restarts and you can edit them while the server is stopped.

How the join check works

The plugin looks at a player when it first sees them in the game, up to half a second after the join. The order is ban list, then sv_admins (always allowed), then whitelist, then reserved slots. A player who fails is kicked with the reason as kick message. Your scripts get playerRejected(player, reason) for that player and no playerConnected.

The launcher check comes after that. A client without a script link is kicked after 90 seconds, and the same event fires with the reason "launcher". See server.cfg.

Permissions in three steps

Define groups once, for example in a small resource that every server of yours loads.

modkit.acl.setGroup("everyone", { permissions = { "help", "garage.use" } })
modkit.acl.setGroup("vip", { permissions = { "garage.vip" } })
modkit.acl.setGroup("moderator", { permissions = { "moderation.*" }, inherits = { "vip" } })

Put players into groups. This is saved, you do it once per player.

modkit.acl.addMember("76561198000000001", "moderator")

Ask for a permission where it matters. For commands that is the third argument.

modkit.commands.add("kick", function(admin, name, ...)
  local found = modkit.players.find(name or "")
  if #found ~= 1 then admin:message(#found .. " players match") return end
  found[1]:kick(table.concat({ ... }, " "))
end, "moderation.kick")

Steam IDs from sv_admins are in the built-in group admin and pass every check. The old form modkit.commands.add(name, fn, true) still means "only sv_admins".

A complete moderation resource

resources/moderation/manifest.lua

name "moderation"
server "server.lua"

resources/moderation/server.lua

modkit.acl.setGroup("moderator", { permissions = { "moderation.*" } })

local function target(admin, name)
  local found = modkit.players.find(name or "")
  if #found == 1 then return found[1] end
  admin:message(#found == 0 and "Nobody matches." or "Several players match, be more exact.")
end

modkit.commands.add("kick", function(admin, name, ...)
  local player = target(admin, name)
  if player then player:kick(table.concat({ ... }, " ")) end
end, "moderation.kick")

modkit.commands.add("ban", function(admin, name, hours, ...)
  local player = target(admin, name)
  if not player then return end
  local reason = table.concat({ ... }, " ")
  modkit.bans.add(player, { reason = reason ~= "" and reason or "banned", hours = tonumber(hours) or 0, by = admin.name })
  modkit.chat.broadcast(player.name .. " was banned.", "Moderation")
end, "moderation.ban")

modkit.commands.add("banid", function(admin, steamId, hours, ...)
  if not steamId then admin:message("usage: /banid <steamId> <hours> <reason>") return end
  modkit.bans.add(steamId, { reason = table.concat({ ... }, " "), hours = tonumber(hours) or 0, by = admin.name })
  admin:message("Banned " .. steamId)
end, "moderation.ban")

modkit.commands.add("unban", function(admin, steamId)
  admin:message(modkit.bans.remove(steamId or "") and "Unbanned." or "That Steam ID is not banned.")
end, "moderation.ban")

modkit.commands.add("bans", function(admin)
  for _, ban in ipairs(modkit.bans.list()) do
    local left = ban.expires == 0 and "forever" or modkit.util.formatTime(ban.expires - os.time())
    admin:message(ban.steamId .. " " .. ban.name .. ": " .. ban.reason .. " (" .. left .. ")")
  end
end, "moderation.ban")

modkit.commands.add("mod", function(admin, steamId)
  if not steamId then admin:message("usage: /mod <steamId>") return end
  modkit.acl.addMember(steamId, "moderator")
  admin:message(steamId .. " is a moderator now")
end, true)

modkit.commands.add("whitelist", function(admin, action, steamId)
  if action == "on" or action == "off" then
    modkit.whitelist.setEnabled(action == "on")
  elseif action == "add" and steamId then
    modkit.whitelist.add(steamId)
  elseif action == "remove" and steamId then
    modkit.whitelist.remove(steamId)
  end
  admin:message("whitelist " .. (modkit.whitelist.isEnabled() and "on" or "off") .. ", " .. #modkit.whitelist.list() .. " players")
end, true)

modkit.events.add("playerRejected", function(player, reason)
  modkit.log.info("rejected " .. player.name .. " (" .. player.steamId .. "): " .. reason)
end)

Things to keep in mind

  • A ban from modkit.bans is separate from the ban list of the game itself.
  • A timed ban ends on its own. The entry is removed the next time someone asks for it.
  • Switching the whitelist on does not kick players who are already on the server.
  • Reserved slots need sv_maxplayers in server.cfg. With 40 slots and modkit.reserved.setSlots(2), only players on the reserved list get slot 39 and 40.
  • Commands never trust a client event for a permission. The chat line comes through the game connection, which carries the real Steam ID.