├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CONTRIBUTORS ├── Info.lua ├── LICENSE ├── README.md ├── banlist.lua ├── cmd_clear.lua ├── cmd_effect.lua ├── cmd_kick.lua ├── cmd_kill.lua ├── cmd_list.lua ├── cmd_listgroups.lua ├── cmd_listranks.lua ├── cmd_me.lua ├── cmd_numchunks.lua ├── cmd_op.lua ├── cmd_players.lua ├── cmd_portal.lua ├── cmd_rank.lua ├── cmd_reload.lua ├── cmd_saveall.lua ├── cmd_say.lua ├── cmd_scoreboard.lua ├── cmd_seed.lua ├── cmd_setspawn.lua ├── cmd_spawn.lua ├── cmd_spawnpoint.lua ├── cmd_stop.lua ├── cmd_summon.lua ├── cmd_tell.lua ├── cmd_toggledownfall.lua ├── cmd_tps.lua ├── cmd_unloadchunks.lua ├── cmd_unrank.lua ├── cmd_version.lua ├── cmd_viewdistance.lua ├── cmd_weather.lua ├── cmd_worlds.lua ├── console.lua ├── core_functions.lua ├── core_hardcore.lua ├── core_itemrepair.lua ├── core_motd.lua ├── core_worlds.lua ├── difficulties.lua ├── do.lua ├── enchant.lua ├── give.lua ├── gm.lua ├── help.lua ├── main.lua ├── plugins.lua ├── regen.lua ├── sqlite.lua ├── teleport.lua ├── tests └── FuzzCommands.lua ├── time.lua ├── web_chat.lua ├── web_manageserver.lua ├── web_permissions.lua ├── web_playerranks.lua ├── web_players.lua ├── web_plugins.lua ├── web_ranks.lua ├── web_serversettings.lua ├── web_utils.lua ├── web_weather.lua ├── web_whitelist.lua └── whitelist.lua /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This is the GitHub Actions configuration file to enable CI tests 2 | # It installs Lua, LuaRocks and LuaFileSystem, lsqlite3, luasocket and luasec on the CI worker, then downloads the CuberitePluginChecker and API descriptions 3 | # Finally it runs the Checker on the plugin 4 | 5 | name: CI 6 | 7 | on: [push, pull_request] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Install Lua 17 | run: | 18 | sudo apt install lua5.1 luarocks libsqlite3-dev 19 | sudo luarocks install luafilesystem 20 | sudo luarocks install lsqlite3 21 | sudo luarocks install luasocket 22 | sudo luarocks install luasec OPENSSL_LIBDIR=/usr/lib/x86_64-linux-gnu 23 | 24 | - name: Set up environment 25 | run: | 26 | wget -O ../InfoReg.lua https://raw.githubusercontent.com/cuberite/cuberite/master/Server/Plugins/InfoReg.lua 27 | mkdir ~/AutoAPI 28 | wget -O ~/AutoAPI.zip 'https://ci.appveyor.com/api/projects/cuberite/cuberite/artifacts/AutoAPI.zip?job=Windows-x64&pr=false&branch=master' 29 | unzip ~/AutoAPI.zip -d ~/AutoAPI 30 | wget -O ~/ManualAPI.zip 'https://ci.appveyor.com/api/projects/cuberite/cuberite/artifacts/ManualAPI.zip?job=Windows-x64&pr=false&branch=master' 31 | unzip ~/ManualAPI.zip -d ~ 32 | git clone https://github.com/cuberite/CuberitePluginChecker ~/Checker 33 | 34 | - name: Run tests 35 | run: | 36 | cd ~/Checker && lua CuberitePluginChecker.lua -p $GITHUB_WORKSPACE -a ~/AutoAPI -e ~/ManualAPI.lua -i APIImpl/All.lua -s $GITHUB_WORKSPACE/tests/FuzzCommands.lua -g 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.ini 2 | *.deuser 3 | @EnableMobDebug.lua 4 | forum_info.txt 5 | tags 6 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | This file contains all known copyright holders of this software, as far as is 2 | practically possible to ascertain. 3 | 4 | If you contribute to this software you must add yourself to this file, to 5 | indicate your agreement to license your contributions according to the license 6 | as provided in the LICENSE file. 7 | 8 | 36451 9 | bearbin (Alexander Harkness) 10 | Bond-009 11 | ccuser44 12 | daniel0916 13 | Etmix 14 | Howaner 15 | isavegas 16 | iulian3144 (Iulian PAUN) 17 | jamestait 18 | JK2K 19 | jonfabe 20 | LogicParrot 21 | mathiascode 22 | NiLSPACE 23 | peterbell10 24 | Prox32Haybale 25 | satoshinm 26 | Seadragon91 27 | sphinxc0re (Julian Laubstein) 28 | Taugeshtu 29 | tigerw (Tiger Wang) 30 | tonibm19 31 | tonitch (Debucquoy Anthony) 32 | xoft (Mattes Dolak/madmaxoft) 33 | 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2013-2020 Core Contributors 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | ---------------------------------------------------------------------------- 16 | 17 | A full list of known copyright holders can be found in the CONTRIBUTORS file 18 | to be distributed with all copies of this software. 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Implements some of the basic commands needed to run a simple server. 2 | 3 | # Commands 4 | 5 | ### General 6 | | Command | Permission | Description | 7 | | ------- | ---------- | ----------- | 8 | |/about | core.version | Shows the server software version.| 9 | |/ban | core.ban | Bans a player.| 10 | |/clear | core.clear | Clears the inventory of a player.| 11 | |/difficulty | core.difficulty | Changes the difficulty level of the world you're located in.| 12 | |/deop | core.unrank | Add a player to the default rank.| 13 | |/do | core.do | Runs a command as a player.| 14 | |/effect | core.effect | Adds an effect to a player.| 15 | |/enchant | core.enchant | Adds an enchantment to a specified player's held item.| 16 | |/gamemode | core.changegm | Changes a player's gamemode.| 17 | |/give | core.give | Gives an item to a player.| 18 | |/help | core.help | Shows available commands.| 19 | |/ienchant | core.enchant.self | Adds an enchantment to an item.| 20 | |/item | core.item | Gives your player an item.| 21 | |/kick | core.kick | Kicks a player.| 22 | |/kill | core.kill | Kills a player.| 23 | |/list | core.list | Shows a list of connected players.| 24 | |/listgroups | core.listgroups | Shows a list of the available groups.| 25 | |/listranks | core.listranks | Shows a list of the available ranks.| 26 | |/me | core.me | Broadcasts what you are doing.| 27 | |/motd | core.motd | Shows the message of the day.| 28 | |/op | core.rank | Add a player to the administrator rank.| 29 | |/plugins | core.plugins | Shows a list of the plugins.| 30 | |/portal | core.portal | Moves your player to a different world.| 31 | |/r | core.tell | Replies to the latest private message you received.| 32 | |/rank | core.rank | Shows or sets a player's rank.| 33 | |/regen | core.regen | Regenerates a chunk.| 34 | |/reload | core.reload | Reloads all plugins.| 35 | |/save-all | core.save-all | Saves all worlds.| 36 | |/say | core.say | Sends a message in the chat to other players.| 37 | |/seed | core.seed | Shows the seed of the given world name or current world, if not given.| 38 | |/setspawn | core.setspawn | Changes the world's spawn point.| 39 | |/spawn | core.spawn | Returns a player to the spawn point.| 40 | |/spawnpoint | core.spawnpoint | Sets the spawn point for a player.| 41 | |/stop | core.stop | Stops the server.| 42 | |/sudo | core.sudo | Runs a command as a player, ignoring permissions.| 43 | |/summon | core.summon | Summons an entity in the world.| 44 | |/tell | core.tell | Sends a private message to a player.| 45 | |/time | | Sets or displays the time.| 46 | |/time add | core.time.set | Adds a given value to the current time.| 47 | |/time day | core.time.set | Sets the time to day.| 48 | |/time night | core.time.set | Sets the time to night.| 49 | |/time query daytime | core.time.query.daytime | Displays the current time.| 50 | |/time query gametime | core.time.query.gametime | Displays the amount of time elapsed since start.| 51 | |/time set | core.time.set | Sets the time to a given value.| 52 | |/toggledownfall | core.toggledownfall | Toggles the weather between clear skies and rain.| 53 | |/tp | core.teleport | Teleports your player to another player.| 54 | |/tps | core.tps | Returns the tps (ticks per second) from the server.| 55 | |/unban | core.unban | Unbans a player.| 56 | |/unrank | core.unrank | Add a player to the default rank.| 57 | |/unsafegive | core.give.unsafe | Gives an item to a player, even if the item is blacklisted.| 58 | |/unsafeitem | core.item.unsafe | Gives your player an item, even if the item is blacklisted.| 59 | |/version | core.version | Shows the server software version.| 60 | |/viewdistance | core.viewdistance | Changes your view distance.| 61 | |/weather | core.weather | Changes the world's weather.| 62 | |/whitelist | | Manages the whitelist.| 63 | |/whitelist add | core.whitelist | Adds a player to the whitelist.| 64 | |/whitelist list | core.whitelist | Shows a list of all players on the whitelist.| 65 | |/whitelist off | core.whitelist | Turns whitelist processing off.| 66 | |/whitelist on | core.whitelist | Turns whitelist processing on.| 67 | |/whitelist remove | core.whitelist | Removes a player from the whitelist.| 68 | |/worlds | core.worlds | Shows a list of all the worlds.| 69 | 70 | 71 | 72 | # Permissions 73 | | Permissions | Description | Commands | Recommended groups | 74 | | ----------- | ----------- | -------- | ------------------ | 75 | | core.ban | | `/ban` | | 76 | | core.changegm | Allows players to change gamemodes. | `/gamemode` | admins | 77 | | core.clear | | `/clear` | | 78 | | core.difficulty | | `/difficulty` | | 79 | | core.do | | `/do` | | 80 | | core.effect | | `/effect` | | 81 | | core.enchant | Allows players to add an enchantment to a player's held item. | `/enchant` | admins | 82 | | core.enchant.self | Allows players to add an enchantment to their own held item. | `/ienchant` | admins | 83 | | core.give | Allows players to give items to other players. | `/give` | admins | 84 | | core.give.unsafe | Allows players to give items to other players, even if the item is blacklisted. | `/unsafegive` | none | 85 | | core.help | | `/help` | | 86 | | core.item | Allows players to give items to themselves. | `/item` | admins | 87 | | core.item.unsafe | Allows players to give items to themselves, even if the item is blacklisted. | `/unsafeitem` | none | 88 | | core.kick | | `/kick` | | 89 | | core.kill | | `/kill` | | 90 | | core.list | | `/list` | | 91 | | core.listgroups | | `/listgroups` | | 92 | | core.listranks | | `/listranks` | | 93 | | core.me | | `/me` | | 94 | | core.motd | | `/motd` | | 95 | | core.plugins | | `/plugins` | | 96 | | core.portal | | `/portal` | | 97 | | core.rank | | `/rank`, `/op` | | 98 | | core.regen | | `/regen` | | 99 | | core.reload | | `/reload` | | 100 | | core.save-all | | `/save-all` | | 101 | | core.say | | `/say` | | 102 | | core.seed | | `/seed` | | 103 | | core.setspawn | | `/setspawn` | | 104 | | core.spawn | | `/spawn` | | 105 | | core.spawnpoint | | `/spawnpoint` | | 106 | | core.stop | | `/stop` | | 107 | | core.sudo | | `/sudo` | | 108 | | core.summon | | `/summon` | | 109 | | core.teleport | | `/tp` | | 110 | | core.tell | | `/r`, `/tell` | | 111 | | core.time.query.daytime | Allows players to display the time of day. | `/time query daytime` | everyone | 112 | | core.time.query.gametime | Allows players to display how long the world has existed. | `/time query gametime` | | 113 | | core.time.set | Allows players to set the time of day. | `/time night`, `/time day`, `/time set`, `/time add` | admins | 114 | | core.toggledownfall | Allows players to toggle the weather between clear skies and rain. | `/toggledownfall` | admins | 115 | | core.tps | | `/tps` | | 116 | | core.unban | | `/unban` | | 117 | | core.unrank | | `/deop`, `/unrank` | | 118 | | core.version | | `/version`, `/about` | | 119 | | core.viewdistance | | `/viewdistance` | | 120 | | core.weather | Allows players to change the weather. | `/weather` | admins | 121 | | core.whitelist | Allows players to manage the whitelist. | `/whitelist off`, `/whitelist list`, `/whitelist remove`, `/whitelist on`, `/whitelist add` | admins | 122 | | core.worlds | | `/worlds` | | 123 | -------------------------------------------------------------------------------- /banlist.lua: -------------------------------------------------------------------------------- 1 | 2 | -- banlist.lua 3 | 4 | -- Implements the banlist-related commands, console commands, API and storage 5 | 6 | 7 | 8 | 9 | --- The SQLite handle to the banlist database: 10 | local BanlistDB 11 | 12 | 13 | 14 | 15 | 16 | --- Adds the specified IP address to the banlist, with the specified ban reason 17 | local function AddIPToBanlist(a_IP, a_Reason, a_BannedBy) 18 | -- Check params: 19 | assert(type(a_IP) == "string") 20 | assert(type(a_BannedBy) == "string") 21 | a_Reason = a_Reason or "banned" 22 | 23 | -- Insert into DB: 24 | return BanlistDB:ExecuteStatement( 25 | "INSERT INTO BannedIPs (IP, Reason, Timestamp, BannedBy) VALUES (?, ?, ?, ?)", 26 | { a_IP, a_Reason, os.time(), a_BannedBy, } 27 | ) 28 | end 29 | 30 | 31 | 32 | 33 | 34 | --- Adds the specified player to the banlist, with the specified ban reason 35 | -- Resolves the player UUID, if needed, but only through cache, not to block 36 | function AddPlayerToBanlist(a_PlayerName, a_Reason, a_BannedBy) 37 | -- Check params: 38 | assert(type(a_PlayerName) == "string") 39 | assert(type(a_BannedBy) == "string") 40 | a_Reason = a_Reason or "banned" 41 | 42 | -- Resolve the player name to OfflineUUID and possibly OnlineUUID (if server is in online mode): 43 | local UUID = "" 44 | if (cRoot:Get():GetServer():ShouldAuthenticate()) then 45 | UUID = cMojangAPI:GetUUIDFromPlayerName(a_PlayerName, true) 46 | -- If the UUID cannot be resolved, leave it as an empty string, it will be resolved on next startup / eventually 47 | end 48 | local OfflineUUID = cClientHandle:GenerateOfflineUUID(a_PlayerName) 49 | 50 | -- Insert into DB: 51 | return BanlistDB:ExecuteStatement( 52 | "INSERT INTO BannedNames (Name, UUID, OfflineUUID, Reason, Timestamp, BannedBy) VALUES (?, ?, ?, ?, ?, ?)", 53 | { 54 | a_PlayerName, UUID, OfflineUUID, 55 | a_Reason, os.time(), a_BannedBy, 56 | } 57 | ) 58 | end 59 | 60 | 61 | 62 | 63 | 64 | --- Checks if the IP address is banned 65 | -- Returns true and reason if banned, false if not 66 | local function IsIPBanned(a_IP) 67 | -- Check params: 68 | assert(type(a_IP) == "string") 69 | assert(a_IP ~= "") 70 | 71 | -- Query the DB: 72 | local Reason 73 | assert(BanlistDB:ExecuteStatement( 74 | "SELECT Reason FROM BannedIPs WHERE IP = ?", 75 | { a_IP }, 76 | function (a_Row) 77 | Reason = a_Row["Reason"] 78 | end 79 | )) 80 | 81 | -- Process the DB results: 82 | if (Reason == nil) then 83 | -- Not banned 84 | return false 85 | else 86 | -- Banned with a reason: 87 | return true, Reason 88 | end 89 | end 90 | 91 | 92 | 93 | 94 | 95 | --- Checks if the player is banned 96 | -- Returns true and reason if banned, false if not 97 | -- Uses UUID for the check, and the playername with an empty UUID for a secondary check 98 | local function IsPlayerBanned(a_PlayerUUID, a_PlayerName) 99 | -- Check params: 100 | assert(type(a_PlayerUUID) == "string") 101 | assert(type(a_PlayerName) == "string") 102 | local UUID = a_PlayerUUID 103 | if (UUID == "") then 104 | -- There is no UUID supplied for the player, do not search by the UUID by using a dummy impossible value: 105 | UUID = "DummyImpossibleValue" 106 | end 107 | 108 | -- Query the DB: 109 | local OfflineUUID = cClientHandle:GenerateOfflineUUID(a_PlayerName) 110 | local Reason 111 | assert(BanlistDB:ExecuteStatement( 112 | [[ 113 | SELECT Reason FROM BannedNames WHERE 114 | (UUID = ?) OR 115 | (OfflineUUID = ?) OR 116 | ((UUID = '') AND (Name = ?)) 117 | ]], 118 | { UUID, OfflineUUID, a_PlayerName }, 119 | function (a_Row) 120 | Reason = a_Row["Reason"] 121 | end 122 | )) 123 | 124 | -- Process the DB results: 125 | if (Reason == nil) then 126 | -- Not banned 127 | return false 128 | else 129 | -- Banned with a reason: 130 | return true, Reason 131 | end 132 | end 133 | 134 | 135 | 136 | 137 | 138 | --- Returns an array-table of all banned players 139 | local function ListBannedPlayers() 140 | local res = {} 141 | BanlistDB:ExecuteStatement( 142 | "SELECT Name FROM BannedNames", {}, 143 | function (a_Columns) 144 | table.insert(res, a_Columns["Name"]) 145 | end 146 | ) 147 | return res 148 | end 149 | 150 | 151 | 152 | 153 | 154 | --- Returns an array-table of all banned ips 155 | local function ListBannedIPs() 156 | local res = {} 157 | BanlistDB:ExecuteStatement( 158 | "SELECT IP FROM BannedIPs", {}, 159 | function (a_Columns) 160 | table.insert(res, a_Columns["IP"]) 161 | end 162 | ) 163 | return res 164 | end 165 | 166 | 167 | 168 | 169 | 170 | --- Removes the specified IP from the banlist 171 | -- No action if the IP is not banned 172 | local function RemoveIPFromBanlist(a_IP) 173 | -- Check params: 174 | assert(type(a_IP) == "string") 175 | 176 | -- Remove from the DB: 177 | assert(BanlistDB:ExecuteStatement( 178 | "DELETE FROM BannedIPs WHERE IP = ?", 179 | { a_IP } 180 | )) 181 | end 182 | 183 | 184 | 185 | 186 | 187 | --- Removes the specified player from the banlist 188 | -- No action if the player is not banned 189 | local function RemovePlayerFromBanlist(a_PlayerName) 190 | -- Check params: 191 | assert(type(a_PlayerName) == "string") 192 | 193 | -- Remove from the DB: 194 | assert(BanlistDB:ExecuteStatement( 195 | "DELETE FROM BannedNames WHERE Name = ?", 196 | { a_PlayerName } 197 | )) 198 | end 199 | 200 | 201 | 202 | 203 | 204 | --- Resolves the UUIDs for players that don't have their UUIDs in the DB 205 | -- This may happen when banning a player who never connected to the server and thus is not yet cached in the UUID lookup 206 | local function ResolveUUIDs() 207 | -- If the server is offline, bail out: 208 | if not(cRoot:Get():GetServer():ShouldAuthenticate()) then 209 | return 210 | end 211 | 212 | -- Collect the names of players without their UUIDs: 213 | local NamesToResolve = {} 214 | BanlistDB:ExecuteStatement( 215 | "SELECT Name From BannedNames WHERE UUID = ''", {}, 216 | function (a_Columns) 217 | table.insert(NamesToResolve, a_Columns["PlayerName"]) 218 | end 219 | ) 220 | if (#NamesToResolve == 0) then 221 | return; 222 | end 223 | 224 | -- Resolve the names: 225 | LOGINFO("Resolving player UUIDs in the banlist from Mojang servers. This may take a while...") 226 | local ResolvedNames = cMojangAPI:GetUUIDsFromPlayerNames(NamesToResolve) 227 | LOGINFO("Resolving finished.") 228 | 229 | -- Update the names in the DB: 230 | for name, uuid in pairs(ResolvedNames) do 231 | BanlistDB:ExecuteStatement( 232 | "UPDATE BannedNames SET UUID = ? WHERE PlayerName = ?", 233 | { uuid, name } 234 | ) 235 | end 236 | end 237 | 238 | 239 | 240 | 241 | 242 | function HandleBanCommand(a_Split, a_Player) 243 | -- Check params: 244 | if (a_Split[2] == nil) then 245 | SendMessage(a_Player, "Usage: " .. a_Split[1] .. " [reason ...]") 246 | return true 247 | end 248 | 249 | -- If the player supplied a reason, use that, else use a default reason. 250 | if (a_Split[3] ~= nil) then 251 | local Reason = table.concat(a_Split, " ", 3) 252 | else 253 | local Reason = "No reason." 254 | end 255 | 256 | -- Add the player to the banlist: 257 | AddPlayerToBanlist(a_Split[2], Reason, a_Player:GetName()); 258 | 259 | -- Try akd kick the banned player, and send an appropriated response to the banner. 260 | if (KickPlayer(a_Split[2], Reason)) then 261 | SendMessageSuccess(a_Player, "Successfully kicked and banned " .. a_Split[2]) 262 | else 263 | SendMessageFailure(a_Player, "Successfully banned " .. a_Split[2]) 264 | end 265 | 266 | return true 267 | 268 | end 269 | 270 | 271 | 272 | 273 | 274 | function HandleUnbanCommand(a_Split, a_Player) 275 | -- Check params: 276 | if ((a_Split[2] == nil) or (a_Split[3] ~= nil)) then 277 | SendMessage(a_Player, "Usage: " .. a_Split[1] .. " ") 278 | return true 279 | end 280 | 281 | -- Remove the player from the banlist: 282 | RemovePlayerFromBanlist(a_Split[2]) 283 | 284 | -- Notify success: 285 | LOGINFO(a_Player:GetName() .. " unbanned " .. a_Split[2]) 286 | SendMessageSuccess(a_Player, "Unbanned " .. a_Split[2]) 287 | return true 288 | end 289 | 290 | 291 | 292 | 293 | 294 | function HandleConsoleBan(a_Split) 295 | -- Check params: 296 | if (a_Split[2] == nil) then 297 | return true, "Usage: " .. a_Split[1] .. " [reason ...]" 298 | end 299 | local PlayerName = a_Split[2] 300 | 301 | -- Compose the reason, if given: 302 | local Reason = cChatColor.Red .. "You have been banned." 303 | if (a_Split[3] ~= nil) then 304 | Reason = table.concat(a_Split, " ", 3) 305 | end 306 | 307 | -- Ban the player: 308 | AddPlayerToBanlist(PlayerName, Reason, "") 309 | 310 | -- Kick the player, if they're online: 311 | if not(KickPlayer(PlayerName, Reason)) then 312 | LOGINFO("Could not find player " .. PlayerName .. ", but banned them anyway.") 313 | else 314 | LOGINFO("Successfully kicked and banned player " .. PlayerName) 315 | end 316 | 317 | return true 318 | end 319 | 320 | 321 | 322 | 323 | 324 | function HandleConsoleBanIP(a_Split) 325 | -- Check params: 326 | if (a_Split[2] == nil) then 327 | return true, "Usage: " .. a_Split[1] .. " [reason ..]" 328 | end 329 | local BanIP = a_Split[2] 330 | 331 | -- Compose the reason, if given: 332 | local Reason = cChatColor.Red .. "You have been banned." 333 | if (a_Split[3] ~= nil) then 334 | Reason = table.concat(a_Split, " ", 3) 335 | end 336 | 337 | -- Ban the player: 338 | AddIPToBanlist(BanIP, Reason, "") 339 | 340 | -- Kick the player, if they're online: 341 | cRoot:Get():ForEachPlayer( 342 | function (a_Player) 343 | local Client = a_Player:GetClientHandle() 344 | if (Client and Client:GetIPString() == BanIP) then 345 | Client:Kick(Reason) 346 | end 347 | end 348 | ) 349 | 350 | -- Report: 351 | LOGINFO("Successfully banned IP " .. BanIP) 352 | return true 353 | end 354 | 355 | 356 | 357 | 358 | 359 | function HandleConsoleBanList(a_Split) 360 | if (a_Split[2] == nil) then 361 | return true, table.concat(ListBannedPlayers(), ", ") 362 | end 363 | 364 | if (string.lower(a_Split[2]) == "ips") then 365 | return true, table.concat(ListBannedIPs(), ", ") 366 | end 367 | 368 | return true, "Unknown banlist subcommand" 369 | end 370 | 371 | 372 | 373 | 374 | 375 | function HandleConsoleUnban(a_Split) 376 | -- Check params: 377 | if ((a_Split[2] == nil) or (a_Split[3] ~= nil)) then 378 | return true, "Usage: " .. a_Split[1] .. " " 379 | end 380 | 381 | -- Unban the player: 382 | RemovePlayerFromBanlist(a_Split[2]) 383 | 384 | -- Inform the admin: 385 | LOGINFO("Unbanned " .. a_Split[2]) 386 | return true 387 | end 388 | 389 | 390 | 391 | 392 | 393 | function HandleConsoleUnbanIP(a_Split) 394 | -- Check params: 395 | if ((a_Split[2] == nil) or (a_Split[3] ~= nil)) then 396 | return true, "Usage: " .. a_Split[1] .. " " 397 | end 398 | 399 | -- Unban the player: 400 | RemoveIPFromBanlist(a_Split[2]) 401 | 402 | -- Inform the admin: 403 | LOGINFO("Unbanned " .. a_Split[2]) 404 | return true 405 | end 406 | 407 | 408 | 409 | 410 | 411 | --- Opens the banlist DB and checks that all the tables have the needed structure 412 | local function InitializeDB() 413 | -- Open the DB: 414 | local ErrMsg 415 | BanlistDB, ErrMsg = NewSQLiteDB("banlist.sqlite") 416 | if not(BanlistDB) then 417 | LOGWARNING("Cannot open the banlist database, banlist not available. SQLite: " .. (ErrMsg or "")) 418 | error(ErrMsg) 419 | end 420 | 421 | -- Define the needed structure: 422 | local NameListColumns = 423 | { 424 | "Name", 425 | "UUID", 426 | "OfflineUUID", 427 | "Reason", 428 | "Timestamp", 429 | "BannedBy", 430 | } 431 | local IPListColumns = 432 | { 433 | "IP", 434 | "Reason", 435 | "Timestamp", 436 | "BannedBy", 437 | } 438 | 439 | -- Check structure: 440 | if ( 441 | not(BanlistDB:CreateDBTable("BannedNames", NameListColumns)) or 442 | not(BanlistDB:CreateDBTable("BannedIPs", IPListColumns)) 443 | ) then 444 | LOGWARNING("Cannot initialize the banlist database, banlist not available.") 445 | error("Banlist DB failure") 446 | end 447 | end 448 | 449 | 450 | 451 | 452 | 453 | 454 | --- Callback for the HOOK_PLAYER_JOINED hook 455 | -- Kicks the player if they are banned by UUID or Name 456 | -- Also sets the UUID for the player in the DB, if not present 457 | local function OnPlayerJoined(a_Player) 458 | local UUID = a_Player:GetUUID() 459 | local Name = a_Player:GetName() 460 | 461 | -- Update the UUID in the DB, if empty: 462 | assert(BanlistDB:ExecuteStatement( 463 | "UPDATE BannedNames SET UUID = ? WHERE ((UUID = '') AND (Name = ?))", 464 | { UUID, Name } 465 | )) 466 | 467 | -- Kick if banned: 468 | local IsBanned, Reason = IsPlayerBanned(UUID, Name) 469 | if (IsBanned) then 470 | a_Player:GetClientHandle():Kick("You have been banned: " .. Reason) 471 | return true 472 | end 473 | end 474 | 475 | 476 | 477 | 478 | 479 | --- Callback for the HOOK_LOGIN hook 480 | -- Kicks the player if their IP is banned 481 | local function OnLogin(a_Client) 482 | local IsBanned, Reason = IsIPBanned(a_Client:GetIPString()) 483 | if (IsBanned) then 484 | a_Client:Kick("You have been banned: " .. Reason) 485 | return true 486 | end 487 | end 488 | 489 | 490 | 491 | 492 | 493 | --- Init function to be called upon plugin startup 494 | -- Opens the banlist DB and refreshes the player names stored within 495 | function InitializeBanlist() 496 | -- Initialize the Banlist DB: 497 | InitializeDB() 498 | ResolveUUIDs() 499 | 500 | cPluginManager:AddHook(cPluginManager.HOOK_PLAYER_JOINED, OnPlayerJoined) 501 | cPluginManager:AddHook(cPluginManager.HOOK_LOGIN, OnLogin) 502 | end 503 | -------------------------------------------------------------------------------- /cmd_clear.lua: -------------------------------------------------------------------------------- 1 | function HandleClearCommand(Split, Player) 2 | local Response 3 | 4 | local ClearInventory = function(OtherPlayer) 5 | OtherPlayer:GetInventory():Clear() 6 | 7 | if Split[2] then 8 | Response = SendMessageSuccess(Player, "You cleared the inventory of player \"" .. OtherPlayer:GetName() .. "\"") 9 | else 10 | Response = SendMessageSuccess(OtherPlayer, "You cleared your own inventory") 11 | end 12 | end 13 | 14 | if not Split[2] then 15 | if not Player then 16 | Response = SendMessage(nil, "Usage: " .. Split[1] .. " ") 17 | else 18 | ClearInventory(Player) 19 | end 20 | elseif not Player or Player:HasPermission("core.admin.clear") then 21 | if Split[2] == "" or not cRoot:Get():FindAndDoWithPlayer(Split[2], ClearInventory) then 22 | Response = SendMessageFailure(Player, "Player \"" .. Split[2] .. "\" not found") 23 | end 24 | end 25 | return true, Response 26 | end 27 | 28 | function HandleConsoleClear(Split) 29 | return HandleClearCommand(Split) 30 | end 31 | -------------------------------------------------------------------------------- /cmd_effect.lua: -------------------------------------------------------------------------------- 1 | function HandleEffectCommand(Split, Player) 2 | local Response 3 | 4 | if not Split[4] then 5 | Split[4] = 30 6 | end 7 | 8 | if not Split[5] then 9 | Split[5] = 0 10 | end 11 | 12 | local EffectID = tonumber(Split[3]) 13 | local Amplifier = tonumber(Split[5]) 14 | 15 | local ApplyEffect = function(OtherPlayer) 16 | local Seconds = (tonumber(Split[4]) * 20) 17 | 18 | OtherPlayer:AddEntityEffect(EffectID, Seconds, Amplifier) 19 | 20 | if Split[2] ~= "@a" then 21 | Response = SendMessageSuccess(Player, "Successfully added effect to player \"" .. OtherPlayer:GetName() .. "\" for " .. Split[4] .. " seconds") 22 | end 23 | end 24 | 25 | local RemoveEffects = function(OtherPlayer) 26 | OtherPlayer:ClearEntityEffects() 27 | 28 | if Split[2] ~= "@a" then 29 | Response = SendMessageSuccess(Player, "Successfully removed effects from player \"" .. OtherPlayer:GetName() .. "\"") 30 | end 31 | end 32 | 33 | if Split[3] ~= "clear" then 34 | if not Split[3] then 35 | Response = SendMessage(Player, "Usage: " .. Split[1] .. " [seconds] [amplifier] OR " .. Split[1] .. " clear") 36 | elseif not EffectID or EffectID < 1 or EffectID > 23 then 37 | Response = SendMessageFailure(Player, "Invalid effect ID \"" .. Split[3] .. "\"") 38 | elseif not tonumber(Split[4]) then 39 | Response = SendMessageFailure(Player, "Invalid duration \"" .. Split[4] .. "\"") 40 | elseif tonumber(Split[4]) < 0 then 41 | Response = SendMessageFailure(Player, "The duration in seconds must be at least 0") 42 | elseif tonumber(Split[4]) > 1000000 then 43 | Response = SendMessageFailure(Player, "The duration in seconds must be at most 1000000") 44 | elseif not Amplifier then 45 | Response = SendMessageFailure(Player, "Invalid amplification amount \"" .. Split[5] .. "\"") 46 | elseif Amplifier < 0 then 47 | Response = SendMessageFailure(Player, "The amplification amount must be at least 0") 48 | elseif Amplifier > 255 then 49 | Response = SendMessageFailure(Player, "The amplification amount must be at most 255") 50 | elseif Split[2] == "@a" then 51 | cRoot:Get():ForEachPlayer(ApplyEffect) 52 | Response = SendMessageSuccess(Player, "Successfully added effect to every player for " .. Split[4] .. " seconds") 53 | elseif not cRoot:Get():FindAndDoWithPlayer(Split[2], ApplyEffect) then 54 | Response = SendMessageFailure(Player, "Player \"" .. Split[2] .. "\" not found") 55 | end 56 | 57 | elseif Split[2] == "@a" then 58 | cRoot:Get():ForEachPlayer(RemoveEffects) 59 | Response = SendMessageSuccess(Player, "Successfully removed effects from every player") 60 | 61 | elseif not cRoot:Get():FindAndDoWithPlayer(Split[2], RemoveEffects) then 62 | Response = SendMessageFailure(Player, "Player \"" .. Split[2] .. "\" not found") 63 | end 64 | return true, Response 65 | end 66 | 67 | function HandleConsoleEffect(Split) 68 | return HandleEffectCommand(Split) 69 | end 70 | -------------------------------------------------------------------------------- /cmd_kick.lua: -------------------------------------------------------------------------------- 1 | function HandleKickCommand(Split, Player) 2 | local Response 3 | local Reason 4 | 5 | local KickPlayer = function(OtherPlayer) 6 | if KickPlayer(Split[2], Reason) then 7 | Response = SendMessageSuccess(Player, "Successfully kicked player \"" .. OtherPlayer:GetName() .. "\"") 8 | else 9 | Response = SendMessageFailure(Player, "Failed to kick player \"" .. OtherPlayer:GetName() .. "\"") 10 | end 11 | end 12 | 13 | if not Split[2] then 14 | Response = SendMessage(Player, "Usage: " .. Split[1] .. " [reason ...]") 15 | else 16 | if Split[3] then 17 | Reason = table.concat(Split, " ", 3) 18 | end 19 | 20 | if Split[2] == "" or not cRoot:Get():FindAndDoWithPlayer(Split[2], KickPlayer) then 21 | Response = SendMessageFailure(Player, "Player \"" .. Split[2] .. "\" not found") 22 | end 23 | end 24 | return true, Response 25 | end 26 | 27 | function HandleConsoleKick(Split) 28 | return HandleKickCommand(Split) 29 | end 30 | -------------------------------------------------------------------------------- /cmd_kill.lua: -------------------------------------------------------------------------------- 1 | function HandleKillCommand(Split, Player) 2 | local Response 3 | 4 | local KillPlayer = function(OtherPlayer) 5 | OtherPlayer:TakeDamage(dtPlugin, nil, 1000, 1000, 0) 6 | 7 | if Split[2] then 8 | Response = SendMessageSuccess(Player, "Successfully killed player \"" .. OtherPlayer:GetName() .. "\"") 9 | end 10 | end 11 | 12 | if not Split[2] then 13 | if not Player then 14 | Response = SendMessage(nil, "Usage: " .. Split[1] .. " ") 15 | else 16 | KillPlayer(Player) 17 | end 18 | elseif Split[2] == "" or not cRoot:Get():FindAndDoWithPlayer(Split[2], KillPlayer) then 19 | Response = SendMessageFailure(Player, "Player \"" .. Split[2] .. "\" not found") 20 | end 21 | return true, Response 22 | end 23 | 24 | function HandleConsoleKill(Split) 25 | return HandleKillCommand(Split) 26 | end 27 | -------------------------------------------------------------------------------- /cmd_list.lua: -------------------------------------------------------------------------------- 1 | function HandleListCommand(Split, Player) 2 | local PlayerTable = {} 3 | 4 | local ForEachPlayer = function(a_Player) 5 | table.insert(PlayerTable, a_Player:GetName()) 6 | end 7 | cRoot:Get():ForEachPlayer(ForEachPlayer) 8 | table.sort(PlayerTable) 9 | 10 | local Response = SendMessage(Player, "Players (" .. #PlayerTable .. "): " .. table.concat(PlayerTable, ", ")) 11 | return true, Response 12 | end 13 | 14 | function HandleConsoleList(Split) 15 | return HandleListCommand(Split) 16 | end 17 | -------------------------------------------------------------------------------- /cmd_listgroups.lua: -------------------------------------------------------------------------------- 1 | function HandleListGroupsCommand(Split, Player) 2 | local Response 3 | 4 | if Split[3] then 5 | return true, SendMessage("Usage: " .. Split[1] .. " [rank]") 6 | end 7 | 8 | -- If no params are given, list all groups that the manager knows: 9 | local RankName = Split[2] 10 | 11 | if not RankName then 12 | -- Get all the groups: 13 | local Groups = cRankManager:GetAllGroups() 14 | 15 | Response = SendMessage(Player, "Available groups: " .. table.concat(Groups, ", ") .. " (total: " .. #Groups .. ")") 16 | else 17 | -- A rank name is given, list the groups in that rank: 18 | local Groups = cRankManager:GetRankGroups(RankName) 19 | 20 | Response = SendMessage(Player, "Groups in rank " .. RankName .. ": " .. table.concat(Groups, ", ") .. " (total: " .. #Groups .. ")") 21 | end 22 | return true, Response 23 | end 24 | 25 | function HandleConsoleListGroups(Split) 26 | return HandleListGroupsCommand(Split) 27 | end 28 | -------------------------------------------------------------------------------- /cmd_listranks.lua: -------------------------------------------------------------------------------- 1 | function HandleListRanksCommand(Split, Player) 2 | local Ranks = cRankManager:GetAllRanks() 3 | return true, SendMessage(Player, "Available ranks: " .. table.concat(Ranks, ", ") .. " (total: " .. #Ranks .. ")") 4 | end 5 | 6 | function HandleConsoleListRanks(Split) 7 | return HandleListRanksCommand(Split) 8 | end 9 | -------------------------------------------------------------------------------- /cmd_me.lua: -------------------------------------------------------------------------------- 1 | function HandleMeCommand(Split, Player) 2 | if not Split[2] then 3 | SendMessage(Player, "Usage: " .. Split[1] .. " ") 4 | else 5 | cRoot:Get():BroadcastChat("* " .. Player:GetName() .. " " .. table.concat(Split , " " , 2)) 6 | end 7 | return true 8 | end 9 | -------------------------------------------------------------------------------- /cmd_numchunks.lua: -------------------------------------------------------------------------------- 1 | function HandleNumChunksCommand(Split, Player) 2 | -- List each world's chunk count into a table, sum the total chunk count: 3 | local Response = {} 4 | local Total = 0 5 | 6 | local GetWorldChunks = function(World) 7 | local numchunks = World:GetNumChunks() 8 | table.insert(Response, World:GetName() .. ": " .. numchunks .. " chunks") 9 | Total = Total + numchunks 10 | end 11 | 12 | table.insert(Response, "Number of loaded chunks:") 13 | cRoot:Get():ForEachWorld(GetWorldChunks) 14 | table.sort(Response) 15 | 16 | table.insert(Response, "Total: " .. Total .. " chunks") 17 | 18 | -- Return the complete report: 19 | return true, SendMessage(Player, table.concat(Response, "\n")) 20 | end 21 | 22 | function HandleConsoleNumChunks(Split) 23 | return HandleNumChunksCommand(Split) 24 | end 25 | -------------------------------------------------------------------------------- /cmd_op.lua: -------------------------------------------------------------------------------- 1 | local function GetAdminRankName() 2 | local Ranks = cRankManager:GetAllRanks() 3 | for _, Rank in ipairs(Ranks) do 4 | local Permissions = cRankManager:GetRankPermissions(Rank) 5 | for _, Permission in ipairs(Permissions) do 6 | if Permission == "*" then 7 | return Rank 8 | end 9 | end 10 | end 11 | end 12 | 13 | function HandleOpCommand(Split, Player) 14 | local Response 15 | 16 | if not Split[2] then 17 | Response = SendMessage(Player, "Usage: " .. Split[1] .. " ") 18 | else 19 | local PlayerName = Split[2] 20 | local AdminRankName = GetAdminRankName() 21 | 22 | if not AdminRankName then 23 | Response = SendMessage(Player, "No admin rank found, missing * permission") 24 | else 25 | return HandleRankCommand({"rank", PlayerName, AdminRankName}, Player) 26 | end 27 | end 28 | return true, Response 29 | end 30 | 31 | function HandleConsoleOp(Split) 32 | return HandleOpCommand(Split) 33 | end 34 | -------------------------------------------------------------------------------- /cmd_players.lua: -------------------------------------------------------------------------------- 1 | function HandlePlayersCommand(Split, Player) 2 | local Response = {} 3 | 4 | local AddToResponse = function(OtherPlayer) 5 | table.insert(Response, " " .. OtherPlayer:GetName() .. " @ " .. OtherPlayer:GetIP()) 6 | end 7 | 8 | local ForEachPlayer = function(World) 9 | table.insert(Response, "World " .. World:GetName() .. ":") 10 | World:ForEachPlayer(AddToResponse) 11 | end 12 | 13 | table.insert(Response, "Players online:") 14 | cRoot:Get():ForEachWorld(ForEachPlayer) 15 | 16 | return true, SendMessage(Player, table.concat(Response, "\n")) 17 | end 18 | 19 | function HandleConsolePlayers(Split) 20 | return HandlePlayersCommand(Split) 21 | end 22 | -------------------------------------------------------------------------------- /cmd_portal.lua: -------------------------------------------------------------------------------- 1 | function HandlePortalCommand(Split, Player) 2 | local WorldName = Split[2] 3 | 4 | if Split[3] then 5 | SendMessage(Player, "Usage: " .. Split[1] .. " [world]") 6 | elseif not WorldName then 7 | SendMessage(Player, "You are in world \"" .. Player:GetWorld():GetName() .. "\"") 8 | elseif Player:GetWorld():GetName() == WorldName then 9 | SendMessageFailure(Player, "You are already in world \"" .. Split[2] .. "\"!") 10 | elseif not cRoot:Get():GetWorld(WorldName) then 11 | SendMessageFailure(Player, "Could not find world \"" .. Split[2] .. "\"!") 12 | elseif not Player:MoveToWorld(WorldName) then 13 | SendMessageFailure(Player, "Could not move to world \"" .. Split[2] .. "\"!") 14 | else 15 | SendMessageSuccess(Player, "Successfully moved to world \"" .. Split[2] .. "\"! :D") 16 | end 17 | 18 | return true 19 | end 20 | -------------------------------------------------------------------------------- /cmd_rank.lua: -------------------------------------------------------------------------------- 1 | function HandleRankCommand(Split, Player) 2 | local Response 3 | local PlayerName = Split[2] 4 | local NewRank = Split[3] 5 | 6 | local InformLoadRank = function(OtherPlayer) 7 | if PlayerName == OtherPlayer:GetName() then 8 | local Actor = "the server console" 9 | 10 | if Player then 11 | Actor = "player \"" .. Player:GetName() .. "\"" 12 | end 13 | 14 | OtherPlayer:SendMessageInfo("You were assigned the rank " .. NewRank .. " by " .. Actor) 15 | OtherPlayer:LoadRank() 16 | end 17 | end 18 | 19 | if not PlayerName then 20 | Response = SendMessage(Player, "Usage: " .. Split[1] .. " [rank]") 21 | else 22 | -- Translate the PlayerName to a UUID: 23 | local PlayerUUID = GetPlayerUUID(PlayerName) 24 | 25 | if not PlayerUUID or string.len(PlayerUUID) ~= 32 then 26 | Response = SendMessage(Player, "There is no player with the name \"" .. PlayerName .. "\"") 27 | else 28 | -- View the player's rank, if requested: 29 | if not NewRank then 30 | -- "/rank " usage, display the rank: 31 | local CurrentRank = cRankManager:GetPlayerRankName(PlayerUUID) 32 | 33 | if CurrentRank == "" then 34 | Response = SendMessage(Player, "Player \"" .. PlayerName .. "\" has no rank assigned to them.") 35 | else 36 | Response = SendMessage(Player, "The rank of player \"" .. PlayerName .. "\" is " .. CurrentRank) 37 | end 38 | else 39 | -- Change the player's rank: 40 | if not cRankManager:RankExists(NewRank) then 41 | Response = SendMessage(Player, "The specified rank does not exist!") 42 | else 43 | cRankManager:SetPlayerRank(PlayerUUID, PlayerName, NewRank) 44 | 45 | -- Let the player know: 46 | SafeDoWithPlayer(PlayerName, InformLoadRank) 47 | 48 | local CurrentRank = cRankManager:GetPlayerRankName(PlayerUUID) 49 | Response = SendMessageSuccess(Player, "Player \"" .. PlayerName .. "\" is now in rank " .. CurrentRank) 50 | end 51 | end 52 | end 53 | end 54 | return true, Response 55 | end 56 | 57 | function HandleConsoleRank(Split) 58 | return HandleRankCommand(Split) 59 | end 60 | -------------------------------------------------------------------------------- /cmd_reload.lua: -------------------------------------------------------------------------------- 1 | function HandleReloadCommand(Split, Player) 2 | cRoot:Get():BroadcastChat(cChatColor.Rose .. "[WARNING] " .. cChatColor.White .. "Reloading all plugins!") 3 | cRoot:Get():GetPluginManager():ReloadPlugins() 4 | return true 5 | end 6 | -------------------------------------------------------------------------------- /cmd_saveall.lua: -------------------------------------------------------------------------------- 1 | function HandleSaveAllCommand(Split, Player) 2 | local Response 3 | 4 | cRoot:Get():SaveAllChunks() 5 | 6 | if not Player then 7 | Response = SendMessage(nil, "Saving all worlds!") 8 | else 9 | cRoot:Get():BroadcastChat(cChatColor.Rose .. "[WARNING] " .. cChatColor.White .. "Saving all worlds!") 10 | end 11 | return true, Response 12 | end 13 | 14 | function HandleConsoleSaveAll(Split) 15 | return HandleSaveAllCommand(Split) 16 | end 17 | -------------------------------------------------------------------------------- /cmd_say.lua: -------------------------------------------------------------------------------- 1 | function HandleSayCommand(Split, Player) 2 | local Response 3 | 4 | if not Split[2] then 5 | Response = SendMessage(Player, "Usage: " .. Split[1] .. " ") 6 | elseif not Player then 7 | cRoot:Get():BroadcastChat(cChatColor.Gold .. "[SERVER] " .. cChatColor.Yellow .. table.concat(Split, " ", 2)) 8 | else 9 | cRoot:Get():BroadcastChat("[" .. Player:GetName() .. "] " .. table.concat(Split, " ", 2)) 10 | end 11 | return true, Response 12 | end 13 | 14 | function HandleConsoleSay(Split) 15 | return HandleSayCommand(Split) 16 | end 17 | -------------------------------------------------------------------------------- /cmd_scoreboard.lua: -------------------------------------------------------------------------------- 1 | local Criterias = 2 | { 3 | ["achievement"] = cObjective.otAchievement, 4 | ["deathCount"] = cObjective.otDeathCount, 5 | ["dummy"] = cObjective.otDummy, 6 | ["health"] = cObjective.otHealth, 7 | ["playerKillCount"] = cObjective.otPlayerKillCount, 8 | ["stat"] = cObjective.otStat, 9 | ["statBlockMine"] = cObjective.otStatBlockMine, 10 | ["statEntityKill"] = cObjective.otStatEntityKill, 11 | ["statEntityKilledBy"] = cObjective.otStatEntityKilledBy, 12 | ["statItemBreak"] = cObjective.otStatItemCraft, 13 | ["statItemUse"] = cObjective.otStatItemUse, 14 | ["totalKillCount"] = cObjective.otTotalKillCount, 15 | } 16 | 17 | local Slots = 18 | { 19 | ["list"] = cScoreboard.dsList, 20 | ["belowname"] = cScoreboard.dsName, 21 | ["sidebar"] = cScoreboard.dsSidebar, 22 | ["count"] = cScoreboard.dsCount, 23 | } 24 | 25 | 26 | function HandleScoreboardObjectivesCommand(Split, Player) 27 | local Response 28 | local Scoreboard 29 | local MultiLineResponse = {} 30 | 31 | if Player then 32 | Scoreboard = Player:GetWorld():GetScoreBoard() 33 | else 34 | Scoreboard = cRoot:Get():GetDefaultWorld():GetScoreBoard() 35 | end 36 | 37 | local SendListObjectives = function(Objective) 38 | table.insert(MultiLineResponse, SendMessage(Player, Objective:GetDisplayName() .. " -> " .. Objective:GetName() .. ": " .. get_key_for_value(Criterias, Objective:GetType()))) 39 | end 40 | 41 | if not Split[3] then 42 | Response = SendMessageFailure(Player, "add, remove, list, setdisplay, modify") 43 | 44 | elseif Split[3] == "list" then 45 | Response = SendMessage(Player, cCompositeChat():AddTextPart("DisplayName -> Name : Type", "2n")) 46 | Scoreboard:ForEachObjective(SendListObjectives) 47 | 48 | elseif Split[3] == "remove" then 49 | if not Split[4] then 50 | Response = SendMessage(Player, "/scoreboard objectives remove ") 51 | elseif Scoreboard:RemoveObjective(Split[4]) then 52 | Response = SendMessageSuccess(Player, Split[4] .. " has been removed") 53 | else 54 | Response = SendMessageFailure(Player, Split[4] .. " does not exist") 55 | end 56 | 57 | elseif Split[3] == "add" then 58 | if not Split[4] or not Criterias[Split[5]:lower()] or not Split[6] then 59 | Response = SendMessage(Player, "/scoreboard objectives add ") 60 | else 61 | local Name = Split[4] 62 | local Criteria = Criterias[Split[5]:lower()] 63 | local DisplayName = Split[6] 64 | 65 | Scoreboard:RegisterObjective(Name, DisplayName, Criteria) 66 | Response = SendMessageSuccess(Player, "Objective " .. DisplayName .. " has been created") 67 | end 68 | 69 | elseif Split[3] == "setdisplay" then 70 | if not Slots[Split[4]:lower()] or not Split[5] then 71 | Response = SendMessage(Player, "/scoreboard objectives setdisplay ") 72 | else 73 | local Slot = Slots[Split[4]:lower()] 74 | local Objective = Scoreboard:GetObjective(Split[5]):GetName() 75 | 76 | Scoreboard:SetDisplay(Objective, Slot) 77 | Response = SendMessageSuccess(Player, "Objective " .. Objective .. " has been placed on " .. get_key_for_value(Slots, Slot)) 78 | end 79 | 80 | elseif Split[3] == "modify" then 81 | if not Split[4] or Split[5] ~= "displayName" or Split[6] then 82 | Response = SendMessage(Player, "/scoreboard objectives modify displayname ") 83 | else 84 | local Objective = Scoreboard:GetObjective(Split[4]) 85 | local NewName = Split[6] 86 | 87 | Objective:SetDisplayName(NewName) 88 | Response = SendMessageSuccess(Player, "Objective " .. Objective:getName() .. " has been renamed to " .. NewName) 89 | end 90 | end 91 | 92 | table.insert(MultiLineResponse, 1, Response) -- Add the response of the command as first line 93 | return true, table.concat(MultiLineResponse, "\n") 94 | end 95 | 96 | function HandleScoreboardPlayersCommand(Split, Player) 97 | local Scoreboard 98 | local Response 99 | local TargetedPlayer = Split[4] 100 | local MultiLineResponse = {} 101 | 102 | if Player then 103 | Scoreboard = Player:GetWorld():GetScoreBoard() 104 | else 105 | Scoreboard = cRoot:Get():GetDefaultWorld():GetScoreBoard() 106 | end 107 | 108 | local ListPlayerObjective = function(Objective) 109 | local Score = Objective:GetScore(TargetedPlayer) 110 | if Score then 111 | table.insert(MultiLineResponse,SendMessage(GlobalPlayer, Objective:GetDisplayName() .. " -> " .. Score)) 112 | end 113 | end 114 | 115 | local ResetAllObjectives = function(Objective) 116 | Objetive:ResetScore(TargetedPlayer) 117 | end 118 | 119 | if not Split[3] then 120 | Response = SendMessageFailure(Player, "list, reset, get, set, add, remove, operation") 121 | 122 | elseif Split[3] == "list" then 123 | if not Split[4] then 124 | Response = SendMessage(Player, "/scoreboard players list ") 125 | else 126 | Response = SendMessage(Player, "List of scores for " .. TargetedPlayer .. ": ") 127 | Scoreboard:ForEachObjective(ListPlayerObjective) 128 | end 129 | 130 | elseif Split[3] == "reset" then 131 | if not Split[4] then 132 | Response = SendMessage(Player, "/scoreboard players reset ()") 133 | else 134 | if Split[5] then 135 | local Objective = Scoreboard:GetObjective(Split[5]) 136 | Objective:ResetScore(TargetedPlayer) 137 | else 138 | Scoreboard:ForEachObjective(ResetAllObjectives) 139 | end 140 | Response = SendMessageSuccess(Player, "Player's Score(s) has successfully been reseted") 141 | end 142 | 143 | elseif Split[3] == "get" then 144 | if not Split[4] or not Split[5] then 145 | Response = SendMessage(Player, "/scoreboard players get ") 146 | else 147 | local TargetedObjective = Scoreboard:GetObjective(Split[5]) 148 | Response = SendMessage(Player, TargetedObjective:GetScore(TargetedPlayer)) 149 | end 150 | 151 | elseif Split[3] == "set" then 152 | if not Split[4] or not Split[5] or not Split[6] then 153 | Response = SendMessage(Player, "/scoreboard players set ") 154 | else 155 | local TargetedObjective = Scoreboard:GetObjective(Split[5]) 156 | TargetedObjective:SetScore(TargetedPlayer, Split[6]) 157 | Response = SendMessageSuccess(Player, "Score " .. Split[6] .. " successfully assigned to " .. TargetedPlayer) 158 | end 159 | 160 | elseif Split[3] == "add" then 161 | if not Split[4] or not Split[5] or not Split[6] then 162 | Response = SendMessage(Player, "/scoreboard players set ") 163 | else 164 | local TargetedObjective = Scoreboard:GetObjective(Split[5]) 165 | TargetedObjective:AddScore(TargetedPlayer, Split[6]) 166 | Response = SendMessageSuccess(Player, "Score " .. Split[6] .. " successfully increased to " .. TargetedPlayer) 167 | end 168 | 169 | elseif Split[3] == "remove" then 170 | if not Split[4] or not Split[5] or not Split[6] then 171 | Response = SendMessage(Player, "/scoreboard players set ") 172 | else 173 | local TargetedObjective = Scoreboard:GetObjective(Split[5]) 174 | TargetedObjective:SubScore(TargetedPlayer, Split[6]) 175 | Response = SendMessageSuccess(Player, "Score " .. Split[6] .. " successfully decreased to " .. TargetedPlayer) 176 | end 177 | 178 | elseif Split[3] == "operation" then 179 | if not Split[4] or not Split[5] or not Split[6] or not Split[7] or not Split[8] then 180 | Response = SendMessage(Player, "/scoreboard players operation ") 181 | else 182 | local TargetedObjective = Scoreboard:GetObjective(Split[5]) 183 | local Operator = Split[6] 184 | local SourcePlayer = Split[7] 185 | local SourceObjective = Scoreboard:GetObjective(Split[8]) 186 | 187 | if Operator == "+=" then 188 | TargetedObjective:SetScore(TargetedPlayer, TargetedObjective:GetScore(TargetedPlayer) + SourceObjective:GetScore(SourcePlayer)) 189 | Response = SendMessage(Player, TargetedObjective:GetDisplayName() .. ":" .. TargetedPlayer:GetName() .. " increased by " .. SourceObjective:GetDisplayName() .. ":" .. SourcePlayer:GetName()) 190 | elseif Operator == "-=" then 191 | TargetedObjective:SetScore(TargetedPlayer, TargetedObjective:GetScore(TargetedPlayer) - SourceObjective:GetScore(SourcePlayer)) 192 | Response = SendMessage(Player, TargetedObjective:GetDisplayName() .. ":" .. TargetedPlayer:GetName() .. " decreased by " .. SourceObjective:GetDisplayName() .. ":" .. SourcePlayer:GetName()) 193 | elseif Operator == "*=" then 194 | Response = SendMessage(Player, TargetedObjective:GetDisplayName() .. ":" .. TargetedPlayer:GetName() .. " multiplied by " .. SourceObjective:GetDisplayName() .. ":" .. SourcePlayer:GetName()) 195 | TargetedObjective:SetScore(TargetedPlayer, TargetedObjective:GetScore(TargetedPlayer) * SourceObjective:GetScore(SourcePlayer)) 196 | elseif Operator == "/=" then 197 | Response = SendMessage(Player, TargetedObjective:GetDisplayName() .. ":" .. TargetedPlayer:GetName() .. " divided by " .. SourceObjective:GetDisplayName() .. ":" .. SourcePlayer:GetName()) 198 | TargetedObjective:SetScore(TargetedPlayer, TargetedObjective:GetScore(TargetedPlayer) / SourceObjective:GetScore(SourcePlayer)) 199 | elseif Operator == "%=" then 200 | Response = SendMessage(Player, TargetedObjective:GetDisplayName() .. ":" .. TargetedPlayer:GetName() .. " moduled by " .. SourceObjective:GetDisplayName() .. ":" .. SourcePlayer:GetName()) 201 | TargetedObjective:SetScore(TargetedPlayer, TargetedObjective:GetScore(TargetedPlayer) % SourceObjective:GetScore(SourcePlayer)) 202 | elseif Operator == "=" then 203 | Response = SendMessage(Player, TargetedObjective:GetDisplayName() .. ":" .. TargetedPlayer:GetName() .. " set to " .. SourceObjective:GetDisplayName() .. ":" .. SourcePlayer:GetName()) 204 | TargetedObjective:SetScore(TargetedPlayer, SourceObjective:GetScore(SourcePlayer)) 205 | elseif Operator == "<" then 206 | if SourceObjective:GetScore(SourcePlayer) < TargetedObjective:GetScore(TargetedPlayer) then 207 | TargetedObjective:SetScore(TargetedPlayer, SourceObjective:GetScore(SourcePlayer)) 208 | Response = SendMessage(Player, TargetedObjective:GetDisplayName() .. ":" .. TargetedPlayer:GetName() .. " set to " .. SourceObjective:GetDisplayName() .. ":" .. SourcePlayer:GetName()) 209 | end 210 | elseif Operator == ">" then 211 | if SourceObjective:GetScore(SourcePlayer) > TargetedObjective:GetScore(TargetedPlayer) then 212 | TargetedObjective:SetScore(TargetedPlayer, SourceObjective:GetScore(SourcePlayer)) 213 | Response = SendMessage(Player, TargetedObjective:GetDisplayName() .. ":" .. TargetedPlayer:GetName() .. " set to " .. SourceObjective:GetDisplayName() .. ":" .. SourcePlayer:GetName()) 214 | end 215 | elseif Operator == "><" then 216 | local temp = SourceObjective:GetScore(SourcePlayer) 217 | SourceObjective:SetScore(TargetedObjective:GetScore(TargetedPlayer)) 218 | TargetedObjective:SetScore(TargetedPlayer, SourceObjective:GetScore(SourcePlayer)) 219 | Response = SendMessage(Player, TargetedObjective:GetDisplayName() .. ":" .. TargetedPlayer:GetName() .. " and " .. SourceObjective:GetDisplayName() .. ":" .. SourcePlayer:GetName() .. " have been switched") 220 | else 221 | Response = SendMessage(Player, "operators: +=, -=, *=, /=, %=, =, <, >, ><") 222 | end 223 | end 224 | end 225 | 226 | table.insert(MultiLineResponse, 1, Response) -- Add the response of the command as first line 227 | return true, table.concat(MultiLineResponse, "\n") 228 | end 229 | 230 | -- Handle commands for console 231 | 232 | function HandleConsoleScoreboardObjectives(Split) 233 | return HandleScoreboardObjectivesCommand(Split) 234 | end 235 | 236 | function HandleConsoleScoreboardPlayers(Split) 237 | return HandleScoreboardPlayersCommand(Split) 238 | end 239 | -------------------------------------------------------------------------------- /cmd_seed.lua: -------------------------------------------------------------------------------- 1 | function HandleSeedCommand(Split, Player) 2 | local Response 3 | 4 | local WorldName = Split[2] 5 | local World = GetWorld(WorldName, Player) 6 | 7 | if not World then 8 | Response = SendMessage(Player, "There is no world \"" .. WorldName .. "\"") 9 | else 10 | Response = SendMessage(Player, "Seed of world \"" .. World:GetName() .. "\": " .. World:GetSeed()) 11 | end 12 | return true, Response 13 | end 14 | 15 | function HandleConsoleSeed(Split) 16 | return HandleSeedCommand(Split) 17 | end 18 | -------------------------------------------------------------------------------- /cmd_setspawn.lua: -------------------------------------------------------------------------------- 1 | function HandleSetSpawnCommand(Split, Player) 2 | local Response 3 | 4 | local World = Player:GetWorld() 5 | 6 | local PlayerX = Player:GetPosX() 7 | local PlayerY = Player:GetPosY() 8 | local PlayerZ = Player:GetPosZ() 9 | 10 | if World:SetSpawn(PlayerX, PlayerY, PlayerZ) then 11 | Response = SendMessageSuccess(Player, string.format("Successfully changed spawn position to [X:%i Y:%i Z:%i]", PlayerX, PlayerY, PlayerZ)) 12 | else 13 | Response = SendMessageFailure(Player, "Failed to change spawn position") 14 | end 15 | return true, Response 16 | end 17 | -------------------------------------------------------------------------------- /cmd_spawn.lua: -------------------------------------------------------------------------------- 1 | function HandleSpawnCommand(Split, Player) 2 | local Response 3 | 4 | local MoveToSpawn = function(OtherPlayer) 5 | local World = OtherPlayer:GetWorld() 6 | local SpawnX = World:GetSpawnX() 7 | local SpawnY = World:GetSpawnY() 8 | local SpawnZ = World:GetSpawnZ() 9 | 10 | OtherPlayer:TeleportToCoords(SpawnX, SpawnY, SpawnZ) 11 | 12 | if Split[2] then 13 | Response = SendMessageSuccess(Player, "Successfully returned player \"" .. OtherPlayer:GetName() .. "\" to world spawn") 14 | else 15 | Response = SendMessageSuccess(Player, "Successfully moved to world spawn") 16 | end 17 | end 18 | 19 | if not Split[2] then 20 | if not Player then 21 | Response = SendMessage(nil, "Usage: " .. Split[1] .. " ") 22 | else 23 | MoveToSpawn(Player) 24 | end 25 | elseif not Player or Player:HasPermission("core.spawn.others") then 26 | if Split[2] == "" or not cRoot:Get():FindAndDoWithPlayer(Split[2], MoveToSpawn) then 27 | Response = SendMessageFailure(Player, "Player \"" .. Split[2] .. "\" not found") 28 | end 29 | end 30 | return true, Response 31 | end 32 | 33 | function HandleConsoleSpawn(Split) 34 | return HandleSpawnCommand(Split) 35 | end 36 | -------------------------------------------------------------------------------- /cmd_spawnpoint.lua: -------------------------------------------------------------------------------- 1 | function HandleSpawnPointCommand(Split, Player) 2 | local Response 3 | 4 | local X 5 | local Y 6 | local Z 7 | local World = cRoot:Get():GetDefaultWorld() 8 | 9 | if Player then 10 | X = Player:GetPosX() 11 | Y = Player:GetPosY() 12 | Z = Player:GetPosZ() 13 | World = Player:GetWorld() 14 | end 15 | 16 | local SetSpawnPoint = function(OtherPlayer) 17 | if Split[5] then 18 | if not Player then 19 | X = OtherPlayer:GetPosX() 20 | Y = OtherPlayer:GetPosY() 21 | Z = OtherPlayer:GetPosZ() 22 | end 23 | 24 | X = RelativeCommandCoord(Split[3], X) 25 | 26 | if not X then 27 | Response = SendMessageFailure(Player, "'" .. Split[3] .. "' is not a valid number") 28 | end 29 | 30 | Y = RelativeCommandCoord(Split[4], Y) 31 | 32 | if not Y then 33 | Response = SendMessageFailure(Player, "'" .. Split[4] .. "' is not a valid number") 34 | end 35 | 36 | Z = RelativeCommandCoord(Split[5], Z) 37 | 38 | if not Z then 39 | Response = SendMessageFailure(Player, "'" .. Split[5] .. "' is not a valid number") 40 | end 41 | end 42 | 43 | if Split[6] then 44 | World = cRoot:Get():GetWorld(Split[6]) 45 | 46 | if not World then 47 | Response = SendMessageFailure(Player, "Invalid world \"" .. Split[6] .. "\"") 48 | end 49 | end 50 | 51 | if not Response then 52 | OtherPlayer:SetRespawnPosition(Vector3i(X, Y, Z), World) 53 | 54 | Response = SendMessageSuccess(Player, "Set the spawn point of player \"" .. OtherPlayer:GetName() .. "\" to (" .. math.floor(X) .. ", " .. math.floor(Y) .. ", " .. math.floor(Z) .. ") in world \"" .. World:GetName() .. "\"") 55 | end 56 | end 57 | 58 | if (not Player and not Split[5]) or (Split[3] and not Split[5]) then 59 | Response = SendMessage(Player, "Usage: " .. Split[1] .. " [world]") 60 | elseif not Split[2] then 61 | SetSpawnPoint(Player) 62 | elseif Split[2] == "" or not cRoot:Get():FindAndDoWithPlayer(Split[2], SetSpawnPoint) then 63 | Response = SendMessageFailure(Player, "Player \"" .. Split[2] .. "\" not found") 64 | end 65 | return true, Response 66 | end 67 | 68 | function HandleConsoleSpawnPoint(Split) 69 | return HandleSpawnPointCommand(Split) 70 | end 71 | -------------------------------------------------------------------------------- /cmd_stop.lua: -------------------------------------------------------------------------------- 1 | function HandleStopCommand(Split, Player) 2 | cRoot:Get():BroadcastChat(cChatColor.Red .. "[WARNING] " .. cChatColor.White .. "Server is terminating!") 3 | cRoot:Get():QueueExecuteConsoleCommand("stop") 4 | return true 5 | end 6 | -------------------------------------------------------------------------------- /cmd_summon.lua: -------------------------------------------------------------------------------- 1 | local Minecarts = 2 | { 3 | ["minecart"] = E_ITEM_MINECART, 4 | ["chest_minecart"] = E_ITEM_CHEST_MINECART, 5 | ["furnace_minecart"] = E_ITEM_FURNACE_MINECART, 6 | ["hopper_minecart"] = E_ITEM_MINECART_WITH_HOPPER, 7 | ["tnt_minecart"] = E_ITEM_MINECART_WITH_TNT, 8 | 9 | -- 1.10 and below 10 | ["MinecartChest"] = E_ITEM_CHEST_MINECART, 11 | ["MinecartFurnace"] = E_ITEM_FURNACE_MINECART, 12 | ["MinecartHopper"] = E_ITEM_MINECART_WITH_HOPPER, 13 | ["MinecartRideable"] = E_ITEM_MINECART, 14 | ["MinecartTNT"] = E_ITEM_MINECART_WITH_TNT 15 | } 16 | 17 | local Mobs = 18 | { 19 | ["bat"] = mtBat, 20 | ["blaze"] = mtBlaze, 21 | ["cave_spider"] = mtCaveSpider, 22 | ["chicken"] = mtChicken, 23 | ["cow"] = mtCow, 24 | ["creeper"] = mtCreeper, 25 | ["ender_dragon"] = mtEnderDragon, 26 | ["enderman"] = mtEnderman, 27 | ["ghast"] = mtGhast, 28 | ["giant"] = mtGiant, 29 | ["guardian"] = mtGuardian, 30 | ["horse"] = mtHorse, 31 | ["iron_golem"] = mtIronGolem, 32 | ["magma_cube"] = mtMagmaCube, 33 | ["mooshroom"] = mtMooshroom, 34 | ["ocelot"] = mtOcelot, 35 | ["pig"] = mtPig, 36 | ["rabbit"] = mtRabbit, 37 | ["sheep"] = mtSheep, 38 | ["silverfish"] = mtSilverfish, 39 | ["skeleton"] = mtSkeleton, 40 | ["slime"] = mtSlime, 41 | ["snowman"] = mtSnowGolem, 42 | ["spider"] = mtSpider, 43 | ["squid"] = mtSquid, 44 | ["villager"] = mtVillager, 45 | ["witch"] = mtWitch, 46 | ["wither"] = mtWither, 47 | ["wither_skeleton"] = mtWitherSkeleton, 48 | ["wolf"] = mtWolf, 49 | ["zombie"] = mtZombie, 50 | ["zombie_pigman"] = mtZombiePigman, 51 | ["zombie_villager"] = mtZombieVillager, 52 | 53 | -- 1.10 and below 54 | ["Bat"] = mtBat, 55 | ["Blaze"] = mtBlaze, 56 | ["CaveSpider"] = mtCaveSpider, 57 | ["Chicken"] = mtChicken, 58 | ["Cow"] = mtCow, 59 | ["Creeper"] = mtCreeper, 60 | ["EnderDragon"] = mtEnderDragon, 61 | ["Enderman"] = mtEnderman, 62 | ["Ghast"] = mtGhast, 63 | ["Giant"] = mtGiant, 64 | ["Guardian"] = mtGuardian, 65 | ["Horse"] = mtHorse, 66 | ["LavaSlime"] = mtMagmaCube, 67 | ["MushroomCow"] = mtMooshroom, 68 | ["Ozelot"] = mtOcelot, 69 | ["Pig"] = mtPig, 70 | ["Rabbit"] = mtRabbit, 71 | ["Sheep"] = mtSheep, 72 | ["Silverfish"] = mtSilverfish, 73 | ["Skeleton"] = mtSkeleton, 74 | ["Slime"] = mtSlime, 75 | ["SnowMan"] = mtSnowGolem, 76 | ["Spider"] = mtSpider, 77 | ["Squid"] = mtSquid, 78 | ["Villager"] = mtVillager, 79 | ["VillagerGolem"] = mtIronGolem, 80 | ["Witch"] = mtWitch, 81 | ["Wither"] = mtWither, 82 | ["WitherSkeleton"] = mtWitherSkeleton, 83 | ["Wolf"] = mtWolf, 84 | ["Zombie"] = mtZombie, 85 | ["PigZombie"] = mtZombiePigman, 86 | ["ZombieVillager"] = mtZombieVillager 87 | } 88 | 89 | local Projectiles = 90 | { 91 | ["arrow"] = cProjectileEntity.pkArrow, 92 | ["egg"] = cProjectileEntity.pkEgg, 93 | ["ender_pearl"] = cProjectileEntity.pkEnderPearl, 94 | ["fireworks_rocket"] = cProjectileEntity.pkFirework, 95 | ["fireball"] = cProjectileEntity.pkGhastFireball, 96 | ["potion"] = cProjectileEntity.pkSplashPotion, 97 | ["small_fireball"] = cProjectileEntity.pkFireCharge, 98 | ["snowball"] = cProjectileEntity.pkSnowball, 99 | ["wither_skull"] = cProjectileEntity.pkWitherSkull, 100 | ["xp_bottle"] = cProjectileEntity.pkExpBottle, 101 | 102 | -- 1.10 and below 103 | ["Arrow"] = cProjectileEntity.pkArrow, 104 | ["Fireball"] = cProjectileEntity.pkGhastFireball, 105 | ["FireworksRocketEntity"] = cProjectileEntity.pkFirework, 106 | ["SmallFireball"] = cProjectileEntity.pkFireCharge, 107 | ["Snowball"] = cProjectileEntity.pkSnowball, 108 | ["ThrownEgg"] = cProjectileEntity.pkEgg, 109 | ["ThrownEnderpearl"] = cProjectileEntity.pkEnderPearl, 110 | ["ThrownExpBottle"] = cProjectileEntity.pkExpBottle, 111 | ["ThrownPotion"] = cProjectileEntity.pkSplashPotion, 112 | ["WitherSkull"] = cProjectileEntity.pkWitherSkull 113 | } 114 | 115 | local function SpawnEntity(EntityName, World, X, Y, Z, Player) 116 | if EntityName == "boat" or EntityName == "Boat" then 117 | local Material = cBoat.bmOak 118 | 119 | World:SpawnBoat(Vector3d(X, Y, Z), Material) 120 | elseif EntityName == "falling_block" or EntityName == "FallingSand" then 121 | local BlockType = E_BLOCK_SAND 122 | local BlockMeta = 0 123 | 124 | World:SpawnFallingBlock(Vector3i(X, Y, Z), BlockType, BlockMeta) 125 | elseif EntityName == "lightning_bolt" or EntityName == "LightningBolt" then 126 | World:CastThunderbolt(Vector3i(X, Y, Z)) 127 | elseif Minecarts[EntityName] then 128 | World:SpawnMinecart(Vector3d(X, Y, Z), Minecarts[EntityName]) 129 | elseif Mobs[EntityName] then 130 | World:SpawnMob(X, Y, Z, Mobs[EntityName]) 131 | elseif Projectiles[EntityName] then 132 | World:CreateProjectile(Vector3d(X, Y, Z), Projectiles[EntityName], Player, Player:GetEquippedItem(), Player:GetLookVector() * 20) 133 | elseif EntityName == "tnt" or EntityName == "PrimedTnt" then 134 | World:SpawnPrimedTNT(Vector3d(X, Y, Z)) 135 | elseif EntityName == "xp_orb" or EntityName == "XPOrb" then 136 | local Reward = 1 137 | 138 | World:SpawnExperienceOrb(Vector3d(X, Y, Z), Reward) 139 | elseif EntityName == "ender_crystal" or EntityName == "EnderCrystal" then 140 | World:SpawnEnderCrystal(Vector3d(X, Y, Z), false) 141 | else 142 | return false 143 | end 144 | return true 145 | end 146 | 147 | function HandleSummonCommand(Split, Player) 148 | local Response 149 | 150 | if not Split[2] or (not Player and not Split[5]) then 151 | Response = SendMessage(Player, "Usage: " .. Split[1] .. " [x] [y] [z]") 152 | else 153 | local X 154 | local Y 155 | local Z 156 | local World = cRoot:Get():GetDefaultWorld() 157 | 158 | if Player then 159 | X = Player:GetPosX() 160 | Y = Player:GetPosY() 161 | Z = Player:GetPosZ() 162 | World = Player:GetWorld() 163 | end 164 | 165 | if Split[5] then 166 | X = RelativeCommandCoord(Split[3], X) 167 | Y = RelativeCommandCoord(Split[4], Y) 168 | Z = RelativeCommandCoord(Split[5], Z) 169 | 170 | if not X then 171 | return true, SendMessageFailure(Player, "'" .. Split[3] .. "' is not a valid number") 172 | elseif not Y then 173 | return true, SendMessageFailure(Player, "'" .. Split[4] .. "' is not a valid number") 174 | elseif not Z then 175 | return true, SendMessageFailure(Player, "'" .. Split[5] .. "' is not a valid number") 176 | end 177 | end 178 | 179 | if SpawnEntity(Split[2], World, X, Y, Z, Player) then 180 | Response = SendMessageSuccess(Player, "Successfully summoned entity at [X:" .. math.floor(X) .. " Y:" .. math.floor(Y) .. " Z:" .. math.floor(Z) .. "]") 181 | else 182 | Response = SendMessageFailure(Player, "Unknown entity '" .. Split[2] .. "'") 183 | end 184 | end 185 | return true, Response 186 | end 187 | 188 | function HandleConsoleSummon(Split) 189 | return HandleSummonCommand(Split) 190 | end 191 | -------------------------------------------------------------------------------- /cmd_tell.lua: -------------------------------------------------------------------------------- 1 | local LastSender = {} 2 | 3 | function HandleTellCommand(Split, Player) 4 | local Response 5 | 6 | local SenderName = "Server" 7 | local SenderUUID = "CuberiteServerConsoleSender" 8 | 9 | if Player then 10 | SenderName = Player:GetName() 11 | SenderUUID = Player:GetUUID() 12 | end 13 | 14 | local SendPrivateMessage = function(OtherPlayer) 15 | local Message = table.concat(Split, " ", 2) 16 | 17 | local ReceiverName = "Server" 18 | local ReceiverUUID = "CuberiteServerConsoleSender" 19 | 20 | if OtherPlayer then 21 | ReceiverName = OtherPlayer:GetName() 22 | ReceiverUUID = OtherPlayer:GetUUID() 23 | 24 | OtherPlayer:SendMessagePrivateMsg(Message, SenderName) 25 | else 26 | LOG("[MSG:" .. SenderName .. "] " .. Message) 27 | end 28 | LastSender[ReceiverUUID] = SenderUUID 29 | 30 | Response = SendMessageSuccess(Player, "Message to \"" .. ReceiverName .. "\" sent!") 31 | end 32 | 33 | if Split[1] == "r" or Split[1] == "/r" then 34 | if not Split[2] then 35 | Response = SendMessage(Player, "Usage: " .. Split[1] .. " ") 36 | elseif not LastSender[SenderUUID] then 37 | Response = SendMessageFailure(Player, "No last sender found") 38 | elseif "CuberiteServerConsoleSender" == LastSender[SenderUUID] then 39 | SendPrivateMessage() 40 | else 41 | local ReceiverName = cMojangAPI:GetPlayerNameFromUUID(LastSender[SenderUUID], true) 42 | if not cRoot:Get():FindAndDoWithPlayer(ReceiverName, SendPrivateMessage) then 43 | Response = SendMessageFailure(Player, "Player \"" .. ReceiverName .. "\" not found") 44 | end 45 | end 46 | else 47 | if not Split[3] then 48 | Response = SendMessage(Player, "Usage: " .. Split[1] .. " ") 49 | elseif Split[2] == "" or not cRoot:Get():FindAndDoWithPlayer(Split[2], SendPrivateMessage) then 50 | Response = SendMessageFailure(Player, "Player \"" .. Split[2] .. "\" not found") 51 | end 52 | end 53 | return true, Response 54 | end 55 | 56 | function HandleConsoleTell(Split) 57 | return HandleTellCommand(Split) 58 | end 59 | 60 | function HandleRCommand(Split, Player) 61 | return HandleTellCommand(Split, Player) 62 | end 63 | 64 | function HandleConsoleR(Split) 65 | return HandleRCommand(Split) 66 | end 67 | -------------------------------------------------------------------------------- /cmd_toggledownfall.lua: -------------------------------------------------------------------------------- 1 | function HandleToggleDownfallCommand(Split, Player) 2 | local Response 3 | 4 | local WorldName = Split[2] 5 | local World = GetWorld(WorldName, Player) -- Function is in functions.lua 6 | 7 | if not World then 8 | Response = SendMessage(Player, "There is no world \"" .. WorldName .. "\"") 9 | else 10 | -- Toggle between sun and rain 11 | World:SetWeather(World:IsWeatherWet() and wSunny or wRain) 12 | 13 | Response = SendMessageSuccess(Player, "Toggled downfall in world \"" .. World:GetName() .. "\"") 14 | end 15 | return true, Response 16 | end 17 | 18 | function HandleConsoleToggleDownfall(Split) 19 | return HandleToggleDownfallCommand(Split) 20 | end 21 | -------------------------------------------------------------------------------- /cmd_tps.lua: -------------------------------------------------------------------------------- 1 | local TpsCache = {} 2 | local GlobalTps = {} 3 | 4 | local function GetAverageNum(Table) 5 | local Sum = 0 6 | for i,Num in ipairs(Table) do 7 | Sum = Sum + Num 8 | end 9 | return math.floor(Sum / #Table * 100) / 100 10 | end 11 | 12 | function OnWorldTick(World, TimeDelta) 13 | local WorldTps = TpsCache[World:GetName()] 14 | if (WorldTps == nil) then 15 | WorldTps = {} 16 | TpsCache[World:GetName()] = WorldTps 17 | end 18 | 19 | if (#WorldTps >= 10) then 20 | table.remove(WorldTps, 1) 21 | end 22 | 23 | table.insert(WorldTps, 1000 / TimeDelta) 24 | end 25 | 26 | function OnTick(TimeDelta) 27 | if (#GlobalTps >= 10) then 28 | table.remove(GlobalTps, 1) 29 | end 30 | 31 | table.insert(GlobalTps, 1000 / TimeDelta) 32 | end 33 | 34 | function HandleTpsCommand(Split, Player) 35 | local Response = {} 36 | 37 | table.insert(Response, "Global TPS: " .. GetAverageNum(GlobalTps)) 38 | for WorldName, WorldTps in pairs(TpsCache) do 39 | table.insert(Response, "World \"" .. WorldName .. "\": " .. GetAverageNum(WorldTps) .. " TPS") 40 | end 41 | return true, SendMessage(Player, table.concat(Response, "\n")) 42 | end 43 | 44 | function HandleConsoleTps(Split) 45 | return HandleTpsCommand(Split) 46 | end 47 | -------------------------------------------------------------------------------- /cmd_unloadchunks.lua: -------------------------------------------------------------------------------- 1 | function HandleUnloadChunksCommand(Split, Player) 2 | local UnloadChunks = function(World) 3 | World:QueueUnloadUnusedChunks() 4 | end 5 | 6 | cRoot:Get():ForEachWorld(UnloadChunks) 7 | return true, SendMessage(Player, "Successfully unloaded unused chunks") 8 | end 9 | 10 | function HandleConsoleUnloadChunks(Split) 11 | return HandleUnloadChunksCommand(Split) 12 | end 13 | -------------------------------------------------------------------------------- /cmd_unrank.lua: -------------------------------------------------------------------------------- 1 | function HandleUnrankCommand(Split, Player) 2 | -- Also handles the /deop command 3 | local Response 4 | local PlayerName = Split[2] 5 | 6 | local InformLoadRank = function(OtherPlayer) 7 | local AuthorName = "the server console" 8 | 9 | if Player then 10 | AuthorName = "\"" .. Player:GetName() .. "\"" 11 | end 12 | 13 | OtherPlayer:SendMessageInfo("You were unranked by " .. AuthorName) 14 | OtherPlayer:LoadRank() 15 | end 16 | 17 | if not PlayerName then 18 | Response = SendMessage(Player, "Usage: " .. Split[1] .. " ") 19 | else 20 | -- Translate the PlayerName to a UUID: 21 | local PlayerUUID = GetPlayerUUID(PlayerName) 22 | 23 | if not PlayerUUID or string.len(PlayerUUID) ~= 32 then 24 | Response = SendMessage(Player, "There is no player with the name \"" .. PlayerName .. "\"") 25 | else 26 | -- Unrank the player: 27 | cRankManager:RemovePlayerRank(PlayerUUID) 28 | 29 | -- Let the player know: 30 | SafeDoWithPlayer(PlayerName, InformLoadRank) 31 | 32 | Response = SendMessage(Player, "Player \"" .. PlayerName .. "\" is now in the default rank") 33 | end 34 | end 35 | return true, Response 36 | end 37 | 38 | function HandleConsoleUnrank(Split) 39 | return HandleUnrankCommand(Split) 40 | end 41 | -------------------------------------------------------------------------------- /cmd_version.lua: -------------------------------------------------------------------------------- 1 | function HandleVersionCommand(Split, Player) 2 | -- Send output to player: 3 | Response = SendMessage(Player, string.format( 4 | "This server is running Cuberite %s - %s (%s - %s) with Lua version %s and SQLite version %s.", 5 | cRoot:GetBuildSeriesName(), 6 | cRoot:GetBuildID(), 7 | cRoot:GetBuildCommitID(), 8 | cRoot:GetBuildDateTime(), 9 | _VERSION, 10 | sqlite3.version() 11 | )) 12 | return true, Response 13 | end 14 | -------------------------------------------------------------------------------- /cmd_viewdistance.lua: -------------------------------------------------------------------------------- 1 | function HandleViewDistanceCommand(Split, Player) 2 | if not Split[2] then 3 | SendMessage(Player, "Usage: /viewdistance <".. cClientHandle.MIN_VIEW_DISTANCE .." - ".. cClientHandle.MAX_VIEW_DISTANCE ..">") 4 | else 5 | -- Check if the param is a number: 6 | local ViewDistance = tonumber(Split[2]) 7 | 8 | if ViewDistance then 9 | Player:GetClientHandle():SetViewDistance(ViewDistance) 10 | 11 | SendMessageSuccess(Player, "Your view distance is now " .. Player:GetClientHandle():GetViewDistance()) 12 | else 13 | SendMessageFailure(Player, "Invalid view distance value \"" .. Split[2] .. "\"") 14 | end 15 | end 16 | return true 17 | end 18 | -------------------------------------------------------------------------------- /cmd_weather.lua: -------------------------------------------------------------------------------- 1 | 2 | -- Translate from weather descriptors to the weather value 3 | -- Non-Vanilla weather descriptors were kept from previous console implementation 4 | local WeatherNames = 5 | { 6 | ["clear"] = wSunny, 7 | ["sunny"] = wSunny, 8 | ["sun"] = wSunny, 9 | ["rain"] = wRain, 10 | ["rainy"] = wRain, 11 | ["storm"] = wStorm, 12 | ["thunder"] = wStorm, 13 | ["thunderstorm"] = wStorm, 14 | ["lightning"] = wStorm, 15 | } 16 | 17 | -- Strings displayed when changing to the specified weather conditions 18 | local WeatherChanges = 19 | { 20 | [wSunny] = "Changing to clear weather in world: ", 21 | [wRain] = "Changing to rainy weather in world: ", 22 | [wStorm] = "Changing to rain and thunder in world: ", 23 | } 24 | 25 | 26 | --- Handles In-game and Console `weather` commands 27 | -- 28 | -- @param Player is nil when called by console command 29 | -- 30 | function HandleWeatherCommand(Split, Player) 31 | local Response 32 | 33 | -- Parse the command into its components 34 | local Weather = WeatherNames[Split[2]] 35 | local TPS = 20 36 | local TicksToChange = (tonumber(Split[3]) or 0) * TPS 37 | local WorldName = Split[4] 38 | 39 | if not tonumber(Split[3]) then 40 | WorldName = Split[3] 41 | end 42 | 43 | local World = GetWorld(WorldName, Player) -- Function is in functions.lua 44 | 45 | if not Weather then 46 | Response = SendMessage(Player, "Usage: " .. Split[1] .. " [duration in seconds] [world]") 47 | elseif not World then 48 | Response = SendMessage(Player, "There is no world \"" .. WorldName .. "\"") 49 | else 50 | World:SetWeather(Weather) 51 | 52 | if TicksToChange ~= 0 then 53 | World:SetTicksUntilWeatherChange(TicksToChange) 54 | end 55 | 56 | Response = SendMessageSuccess(Player, WeatherChanges[Weather] .. World:GetName()) 57 | end 58 | return true, Response 59 | end 60 | 61 | function HandleConsoleWeather(Split) 62 | return HandleWeatherCommand(Split) 63 | end 64 | -------------------------------------------------------------------------------- /cmd_worlds.lua: -------------------------------------------------------------------------------- 1 | function HandleWorldsCommand(Split, Player) 2 | local NumWorlds = 0 3 | local Worlds = {} 4 | local Response = {} 5 | 6 | local ListWorld = function(World) 7 | NumWorlds = NumWorlds + 1 8 | Worlds[NumWorlds] = World:GetName() 9 | end 10 | 11 | cRoot:Get():ForEachWorld(ListWorld) 12 | 13 | -- Start creating the actual response 14 | table.insert(Response, SendMessage(Player, "There are " .. NumWorlds .. " worlds:")) 15 | table.insert(Response, SendMessage(Player, table.concat(Worlds, ", "))) 16 | 17 | if Player then 18 | SendMessage(Player, "You are in world " .. Player:GetWorld():GetName()) 19 | end 20 | 21 | return true, table.concat(Response, "\n") 22 | end 23 | 24 | function HandleConsoleWorlds(Split) 25 | return HandleWorldsCommand(Split) 26 | end 27 | -------------------------------------------------------------------------------- /console.lua: -------------------------------------------------------------------------------- 1 | 2 | -- console.lua 3 | 4 | -- Implements things related to console commands 5 | -- TODO: remove this file, migrate commands below to unified cmd_*.lua format 6 | 7 | 8 | 9 | 10 | 11 | function HandleConsolePlugins(Split) 12 | -- Enumerate the plugins: 13 | local PluginTable = {} 14 | cPluginManager:Get():ForEachPlugin( 15 | function (a_CBPlugin) 16 | table.insert(PluginTable, 17 | { 18 | Name = a_CBPlugin:GetName(), 19 | Folder = a_CBPlugin:GetFolderName(), 20 | Status = a_CBPlugin:GetStatus(), 21 | LoadError = a_CBPlugin:GetLoadError() 22 | } 23 | ) 24 | end 25 | ) 26 | table.sort(PluginTable, 27 | function (a_Plugin1, a_Plugin2) 28 | return (string.lower(a_Plugin1.Folder) < string.lower(a_Plugin2.Folder)) 29 | end 30 | ) 31 | 32 | -- Prepare a translation table for the status: 33 | local StatusName = 34 | { 35 | [cPluginManager.psLoaded] = "Loaded ", 36 | [cPluginManager.psUnloaded] = "Unloaded", 37 | [cPluginManager.psError] = "Error ", 38 | [cPluginManager.psNotFound] = "NotFound", 39 | [cPluginManager.psDisabled] = "Disabled", 40 | } 41 | 42 | -- Generate the output: 43 | local Out = {} 44 | table.insert(Out, "There are ") 45 | table.insert(Out, #PluginTable) 46 | table.insert(Out, " plugins, ") 47 | table.insert(Out, cPluginManager:Get():GetNumLoadedPlugins()) 48 | table.insert(Out, " loaded:\n") 49 | for _, plg in ipairs(PluginTable) do 50 | table.insert(Out, " ") 51 | table.insert(Out, StatusName[plg.Status] or " ") 52 | table.insert(Out, " ") 53 | table.insert(Out, plg.Folder) 54 | if (plg.Name ~= plg.Folder) then 55 | table.insert(Out, " (API name ") 56 | table.insert(Out, plg.Name) 57 | table.insert(Out, ")") 58 | end 59 | if (plg.Status == cPluginManager.psError) then 60 | table.insert(Out, " ERROR: ") 61 | table.insert(Out, plg.LoadError or "") 62 | end 63 | table.insert(Out, "\n") 64 | end 65 | return true, table.concat(Out, "") 66 | end 67 | 68 | 69 | 70 | 71 | 72 | function HandleConsoleTeleport(Split) 73 | local TeleportToCoords = function(Player) 74 | if (Player:GetName() == Split[2]) then 75 | IsPlayerOnline = true 76 | Player:TeleportToCoords(Split[3], Split[4], Split[5]) 77 | end 78 | end 79 | 80 | local IsPlayerOnline = false 81 | local FirstPlayerOnline = false 82 | local GetPlayerCoords = function(Player) 83 | if (Player:GetName() == Split[3]) then 84 | PosX = Player:GetPosX() 85 | PosY = Player:GetPosY() 86 | PosZ = Player:GetPosZ() 87 | FirstPlayerOnline = true 88 | end 89 | end 90 | 91 | local TeleportToPlayer = function(Player) 92 | if (Player:GetName() == Split[2]) then 93 | Player:TeleportToCoords(PosX, PosY, PosZ) 94 | IsPlayerOnline = true 95 | end 96 | end 97 | 98 | if (#Split == 3) then 99 | cRoot:Get():FindAndDoWithPlayer(Split[3], GetPlayerCoords) 100 | if (FirstPlayerOnline) then 101 | cRoot:Get():FindAndDoWithPlayer(Split[2], TeleportToPlayer) 102 | if (IsPlayerOnline) then 103 | return true, "Teleported " .. Split[2] .." to " .. Split[3] 104 | end 105 | else 106 | return true, "Player " .. Split[3] .." not found" 107 | end 108 | elseif (#Split == 5) then 109 | cRoot:Get():FindAndDoWithPlayer(Split[2], TeleportToCoords) 110 | if (IsPlayerOnline) then 111 | return true, "You teleported " .. Split[2] .. " to [X:" .. Split[3] .. " Y:" .. Split[4] .. " Z:" .. Split[5] .. "]" 112 | else 113 | return true, "Player not found" 114 | end 115 | else 116 | return true, "Usage: '" .. Split[1] .. " ' or 'tp '" 117 | end 118 | end 119 | -------------------------------------------------------------------------------- /core_functions.lua: -------------------------------------------------------------------------------- 1 | -- Returns the online mode UUID for a player name, if it exists 2 | -- Otherwise returns an offline UUID 3 | function GetPlayerUUID(PlayerName) 4 | if cRoot:Get():GetServer():ShouldAuthenticate() then 5 | -- The server is in online-mode, get the UUID from Mojang servers and check for validity: 6 | return cMojangAPI:GetUUIDFromPlayerName(PlayerName) 7 | end 8 | -- The server is in offline mode, generate an offline-mode UUID, no validity check is possible: 9 | return cClientHandle:GenerateOfflineUUID(PlayerName) 10 | end 11 | 12 | -- Returns the world object of the specified world name. 13 | -- If a name isn't provided, the function returns the world of the specified player. 14 | -- If a player isn't specified (e.g. console), the function returns the default world. 15 | function GetWorld(WorldName, Player) 16 | if not WorldName then 17 | return Player and Player:GetWorld() or cRoot:Get():GetDefaultWorld() 18 | end 19 | return cRoot:Get():GetWorld(WorldName) 20 | end 21 | 22 | -- Kicks a player by name, with the specified reason; returns bool whether found 23 | function KickPlayer(PlayerName, Reason) 24 | if not Reason then 25 | Reason = "You have been kicked" 26 | end 27 | 28 | local KickPlayer = function(Player) 29 | Player:GetClientHandle():Kick(Reason) 30 | end 31 | 32 | if not cRoot:Get():FindAndDoWithPlayer(PlayerName, KickPlayer) then 33 | -- Could not find player 34 | return false 35 | end 36 | 37 | -- Player has been kicked 38 | return true 39 | end 40 | 41 | function RelativeCommandCoord(Split, Coord) 42 | if Split then 43 | if string.sub(Split, 1, 1) == "~" then 44 | local Relative = tonumber(string.sub(Split, 2, -1)) 45 | 46 | if Coord then 47 | if Relative then 48 | return Coord + Relative 49 | end 50 | return Coord 51 | end 52 | return Relative 53 | end 54 | return tonumber(Split) 55 | end 56 | return Split 57 | end 58 | 59 | -- Safer method to find players 60 | function SafeDoWithPlayer(PlayerName, Function) 61 | local DoWithPlayer = function(World) 62 | World:DoWithPlayer(PlayerName, Function) 63 | end 64 | 65 | local QueueTask = function(World) 66 | World:QueueTask(DoWithPlayer) 67 | end 68 | 69 | cRoot:Get():ForEachWorld(QueueTask); 70 | end 71 | 72 | -- If the target is a player, the SendMessage function takes care of sending the message to the player. 73 | -- If the target is a command block or the console, the message is simply returned to the calling function, 74 | -- which delivers it appropriately 75 | function SendMessage(Player, Message) 76 | if Player then 77 | if type(Message) == "string" then 78 | Player:SendMessageInfo(Message) 79 | else 80 | Player:SendMessage(Message) -- for cCompositeChat 81 | end 82 | return nil 83 | end 84 | if type(Message) == "string" then 85 | return Message 86 | end 87 | return Message:ExtractText() 88 | end 89 | 90 | function SendMessageSuccess(Player, Message) 91 | if Player then 92 | Player:SendMessageSuccess(Message) 93 | return nil 94 | end 95 | return Message 96 | end 97 | 98 | function SendMessageFailure(Player, Message) 99 | if Player then 100 | Player:SendMessageFailure(Message) 101 | return nil 102 | end 103 | return Message 104 | end 105 | 106 | -- Teleports a_SrcPlayer to a player named a_DstPlayerName; if a_TellDst is true, will send a notice to the destination player 107 | -- TODO: cleanup 108 | function TeleportToPlayer( a_SrcPlayer, a_DstPlayerName, a_TellDst ) 109 | 110 | local teleport = function(a_DstPlayerName) 111 | 112 | if a_DstPlayerName == a_SrcPlayer then 113 | -- Asked to teleport to self? 114 | SendMessageFailure( a_SrcPlayer, "Y' can't teleport to yerself" ) 115 | else 116 | -- If destination player is not in the same world, move to the correct world 117 | if a_SrcPlayer:GetWorld() ~= a_DstPlayerName:GetWorld() then 118 | a_SrcPlayer:MoveToWorld( a_DstPlayerName:GetWorld(), true, Vector3d( a_DstPlayerName:GetPosX() + 0.5, a_DstPlayerName:GetPosY(), a_DstPlayerName:GetPosZ() + 0.5 ) ) 119 | else 120 | a_SrcPlayer:TeleportToEntity( a_DstPlayerName ) 121 | end 122 | SendMessageSuccess( a_SrcPlayer, "You teleported to " .. a_DstPlayerName:GetName() ) 123 | if (a_TellDst) then 124 | SendMessage( a_DstPlayerName, a_SrcPlayer:GetName().." teleported to you" ) 125 | end 126 | end 127 | 128 | end 129 | 130 | if not cRoot:Get():FindAndDoWithPlayer( a_DstPlayerName, teleport ) then 131 | SendMessageFailure( a_SrcPlayer, "Player " .. a_DstPlayerName .. " not found" ) 132 | end 133 | 134 | end 135 | 136 | -- Return the key from a value in an array list. 137 | -- @param a_List The list to get the value from. 138 | -- @parma a_Value The value to look for. 139 | -- @return the key from the value found or nil if nothing is found 140 | function get_key_for_value( a_List, a_Value ) 141 | for k, v in pairs(a_List) do 142 | if v == a_Value then return k end 143 | end 144 | return nil 145 | end 146 | -------------------------------------------------------------------------------- /core_hardcore.lua: -------------------------------------------------------------------------------- 1 | -- Implements the OnKilling hook handler that effectively implements the hardcore mode of the server 2 | 3 | -- If the server is in hardcore mode, bans the killed player 4 | function OnKilling(Victim, Killer) 5 | if (Victim:IsPlayer()) then 6 | if (cRoot:Get():GetServer():IsHardcore()) then 7 | local Reason = "Killed in hardcore mode" 8 | 9 | AddPlayerToBanlist(Victim:GetName(), Reason, "Server-Core") 10 | KickPlayer(Victim:GetName(), Reason) 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /core_itemrepair.lua: -------------------------------------------------------------------------------- 1 | -- how much "extra" points are healed per a repair operation (fraction of full health) 2 | local BONUS = 0.05 3 | 4 | function OnCraftingNoRecipe(Player, Grid, Recipe) 5 | local Items = {} 6 | for x = 0, Grid:GetWidth() - 1 do 7 | for y = 0, Grid:GetHeight() - 1 do 8 | local Item = Grid:GetItem(x, y) 9 | if (Item.m_ItemType ~= E_ITEM_EMPTY) then 10 | Item.x = x 11 | Item.y = y 12 | table.insert(Items, Item) 13 | end 14 | end 15 | end 16 | 17 | if (#Items ~= 2) then 18 | -- Only two items together can be fixed 19 | return false 20 | end 21 | 22 | if (not Items[1]:IsSameType(Items[2])) then 23 | -- Only items of the same type can be fixed 24 | return false 25 | end 26 | 27 | local MaxDamage = Items[1]:GetMaxDamage() 28 | if (MaxDamage == 0) then 29 | -- Not repairable 30 | return false 31 | end 32 | 33 | local BonusHealth = MaxDamage * BONUS 34 | local FirstItemHealth = MaxDamage - Items[1].m_ItemDamage 35 | local SecondItemHealth = MaxDamage - Items[2].m_ItemDamage 36 | local NewDamage = MaxDamage - (FirstItemHealth + SecondItemHealth + BonusHealth) 37 | NewDamage = math.max(0, NewDamage) -- Not lower than zero 38 | 39 | Recipe:SetResult(Items[1].m_ItemType, 1, NewDamage) 40 | Recipe:SetIngredient(Items[1].x, Items[1].y, Items[1]) 41 | Recipe:SetIngredient(Items[2].x, Items[2].y, Items[2]) 42 | return true 43 | end 44 | -------------------------------------------------------------------------------- /core_motd.lua: -------------------------------------------------------------------------------- 1 | local MOTD = {} 2 | 3 | function LoadMOTD() 4 | -- Check if the file 'motd.txt' exists, if not, create it with default content: 5 | if not cFile:IsFile("motd.txt") then 6 | CreateFile = io.open("motd.txt", "w") 7 | CreateFile:write("&6Welcome to the Cuberite test server!\n&6https://cuberite.org/\n&6Type /help for all commands") 8 | CreateFile:close() 9 | end 10 | 11 | for Line in io.lines("motd.txt") do 12 | table.insert(MOTD, Line) 13 | end 14 | end 15 | 16 | function ShowMOTD(Player) 17 | for i = 1, #MOTD do 18 | Player:SendMessage(MOTD[i]) 19 | end 20 | end 21 | 22 | function OnPlayerJoined(Player) 23 | -- Send the MOTD to the player: 24 | ShowMOTD(Player) 25 | end 26 | 27 | function HandleMOTDCommand(Split, Player) 28 | ShowMOTD(Player) 29 | return true 30 | end 31 | -------------------------------------------------------------------------------- /core_worlds.lua: -------------------------------------------------------------------------------- 1 | -- General world-related core functionality, including difficulty, spawn protection and world limit 2 | -- TODO: Move difficulty and world limit (border) functionality to main server 3 | 4 | local WorldsSpawnProtect = {} 5 | local WorldsWorldDifficulty = {} 6 | local WorldsWorldLimit = {} 7 | 8 | function LoadWorldSettings(World) 9 | local WorldIni = cIniFile() 10 | WorldIni:ReadFile(World:GetIniFileName()) 11 | 12 | WorldsSpawnProtect[World:GetName()] = WorldIni:GetValueSetI("SpawnProtect", "ProtectRadius", 10) 13 | WorldsWorldDifficulty[World:GetName()] = WorldIni:GetValueSetI("Difficulty", "WorldDifficulty", 1) 14 | WorldsWorldLimit[World:GetName()] = WorldIni:GetValueSetI("WorldLimit", "LimitRadius", 0) 15 | 16 | WorldIni:WriteFile(World:GetIniFileName()) 17 | end 18 | 19 | 20 | -- Implements server difficulty for Cuberite 21 | 22 | local MobDamages = 23 | { 24 | ["cBlaze"] = { 4, 6, 9 }, 25 | ["cCaveSpider"] = { 2, 2, 3 }, 26 | ["cEnderDragon"] = { 6, 10, 15 }, 27 | ["cEnderman"] = { 4, 7, 10 }, 28 | ["cGiant"] = { 26, 50, 75 }, 29 | ["cGuardian"] = { 5, 6, 9 }, 30 | ["cIronGolem"] = { 4, 7, 10 }, 31 | ["cSkeleton"] = { 2, 2, 3 }, 32 | ["cSpider"] = { 2, 2, 3 }, 33 | ["cWitherSkeleton"] = { 5, 8, 12 }, 34 | ["cZombie"] = { 2, 3, 4 }, 35 | ["cZombiePigman"] = { 5, 9, 13 }, 36 | ["cZombieVillager"] = { 2, 3, 4 }, 37 | } 38 | 39 | function GetWorldDifficulty(World) 40 | local Difficulty = WorldsWorldDifficulty[World:GetName()] 41 | if not Difficulty then 42 | Difficulty = 1 43 | end 44 | 45 | return Clamp(Difficulty, 0, 3) 46 | end 47 | 48 | function SetWorldDifficulty(World, Difficulty) 49 | local Difficulty = Clamp(Difficulty, 0, 3) 50 | WorldsWorldDifficulty[World:GetName()] = Difficulty 51 | 52 | -- Update world.ini 53 | local WorldIni = cIniFile() 54 | WorldIni:ReadFile(World:GetIniFileName()) 55 | 56 | WorldIni:SetValueI("Difficulty", "WorldDifficulty", Difficulty) 57 | 58 | WorldIni:WriteFile(World:GetIniFileName()) 59 | end 60 | 61 | function OnTakeDamage(Receiver, TDI) 62 | local Attacker 63 | 64 | if TDI.Attacker then 65 | Attacker = TDI.Attacker 66 | local WorldDifficulty = GetWorldDifficulty(Attacker:GetWorld()) 67 | local Damages = MobDamages[Attacker:GetClass()] 68 | if Damages then 69 | TDI.FinalDamage = Damages[WorldDifficulty] 70 | end 71 | end 72 | 73 | -- Apply armor protection 74 | local ArmorCover = Receiver:GetArmorCoverAgainst(Attacker, TDI.DamageType, TDI.FinalDamage) 75 | local EnchantmentCover = Receiver:GetEnchantmentCoverAgainst(Attacker, TDI.DamageType, TDI.FinalDamage) 76 | TDI.FinalDamage = TDI.FinalDamage - ArmorCover - EnchantmentCover 77 | end 78 | 79 | function OnSpawningMonster(World, Monster) 80 | if GetWorldDifficulty(World) == 0 and Monster:GetMobFamily() == cMonster.mfHostile then 81 | -- Don't spawn hostile mobs in peaceful mode 82 | return true 83 | end 84 | return false 85 | end 86 | 87 | 88 | -- Implements spawn protection for Cuberite 89 | 90 | function GetSpawnProtectRadius(WorldName) 91 | return WorldsSpawnProtect[WorldName] 92 | end 93 | 94 | local function IsInSpawn(X, Y, Z, WorldName) 95 | local ProtectRadius = WorldsSpawnProtect[WorldName] 96 | 97 | if ProtectRadius > 0 then 98 | local World = cRoot:Get():GetWorld(WorldName) 99 | local SpawnArea = cBoundingBox(Vector3d(World:GetSpawnX() - ProtectRadius, -1000, World:GetSpawnZ() - ProtectRadius), Vector3d(World:GetSpawnX() + ProtectRadius, 1000, World:GetSpawnZ() + ProtectRadius)) 100 | local PlayerLocation = Vector3d(X, Y, Z) 101 | 102 | if SpawnArea:IsInside(PlayerLocation) then 103 | return true 104 | end 105 | end 106 | end 107 | 108 | local function CheckBlockModification(Player, BlockX, BlockY, BlockZ) 109 | if not Player:HasPermission("core.build") then 110 | SendMessageFailure(Player, "You do not have the \"core.build\" permission, thou cannot build") 111 | return true 112 | end 113 | 114 | if not Player:HasPermission("core.spawnprotect.bypass") and IsInSpawn(BlockX, BlockY, BlockZ, Player:GetWorld():GetName()) then 115 | SendMessageFailure(Player, "Go further from spawn to build") 116 | return true 117 | end 118 | end 119 | 120 | function OnBlockSpread(World, BlockX, BlockY, BlockZ, Source) 121 | if Source == ssFireSpread and IsInSpawn(BlockX, BlockY, BlockZ, World:GetName()) then 122 | return true 123 | end 124 | 125 | end 126 | 127 | function OnExploding(World, ExplosionSize, CanCauseFire, X, Y, Z, Source, SourceData) 128 | if IsInSpawn(X, Y, Z, World:GetName()) then 129 | return true 130 | end 131 | end 132 | 133 | function OnPlayerBreakingBlock(Player, BlockX, BlockY, BlockZ, BlockFace, Status, OldBlockType, OldBlockMeta) 134 | return CheckBlockModification(Player, BlockX, BlockY, BlockZ) 135 | end 136 | 137 | function OnPlayerPlacingBlock(Player, BlockX, BlockY, BlockZ, BlockFace, CursorX, CursorY, CursorZ, BlockType) 138 | return CheckBlockModification(Player, BlockX, BlockY, BlockZ) 139 | end 140 | 141 | function OnPlayerRightClick(Player, BlockX, BlockY, BlockZ, BlockFace, CursorX, CursorY, CursorZ) 142 | if not Player:HasPermission("core.spawnprotect.bypass") and IsInSpawn(BlockX, BlockY, BlockZ, Player:GetWorld():GetName()) then 143 | local Block = Player:GetWorld():GetBlock(Vector3i(BlockX, BlockY, BlockZ)) 144 | 145 | if Block == E_BLOCK_GRASS or Block == E_BLOCK_DIRT then 146 | if Player:GetEquippedItem():IsEmpty() then 147 | return false 148 | end 149 | end 150 | 151 | if Player:GetEquippedItem().m_ItemType ~= E_ITEM_FLINT_AND_STEEL and 152 | Player:GetEquippedItem().m_ItemType ~= E_ITEM_FIRE_CHARGE then 153 | return false 154 | end 155 | 156 | SendMessageFailure(Player, "Go further from spawn to build") 157 | return true 158 | end 159 | end 160 | 161 | 162 | -- Implements world limiter for Cuberite 163 | 164 | local WorldLimiter_Flag = false -- True when teleportation is about to occur, false otherwise 165 | local WorldLimiter_LastMessage = -100 -- The last time the player was sent a message about reaching the border 166 | function OnPlayerMoving(Player) 167 | if (WorldLimiter_Flag == true) then 168 | return 169 | end 170 | 171 | local LimitChunks = WorldsWorldLimit[Player:GetWorld():GetName()] 172 | 173 | -- The world probably was created by an external plugin. Let's load the settings. 174 | if not LimitChunks then 175 | LoadWorldSettings(Player:GetWorld()) 176 | LimitChunks = WorldsWorldLimit[Player:GetWorld():GetName()] 177 | end 178 | 179 | if (LimitChunks > 0) then 180 | local World = Player:GetWorld() 181 | local Limit = LimitChunks * 16 - 1 182 | local SpawnX = math.floor(World:GetSpawnX()) 183 | local SpawnZ = math.floor(World:GetSpawnZ()) 184 | local X = math.floor(Player:GetPosX()) 185 | local Z = math.floor(Player:GetPosZ()) 186 | local NewX = X 187 | local NewZ = Z 188 | 189 | if (X > SpawnX + Limit) then 190 | NewX = SpawnX + Limit 191 | elseif (X < SpawnX - Limit) then 192 | NewX = SpawnX - Limit 193 | end 194 | 195 | if (Z > SpawnZ + Limit) then 196 | NewZ = SpawnZ + Limit 197 | elseif (Z < SpawnZ - Limit) then 198 | NewZ = SpawnZ - Limit 199 | end 200 | 201 | if (X ~= NewX) or (Z ~= NewZ) then 202 | WorldLimiter_Flag = true 203 | 204 | local UpTime = cRoot:Get():GetServerUpTime() 205 | if UpTime - WorldLimiter_LastMessage > 30 then 206 | WorldLimiter_LastMessage = UpTime 207 | Player:SendMessageInfo("You have reached the world border") 208 | end 209 | 210 | local UUID = Player:GetUUID() 211 | World:ScheduleTask(3, function(World) 212 | World:DoWithPlayerByUUID(UUID, function(Player) 213 | Player:TeleportToCoords(NewX, Player:GetPosY(), NewZ) 214 | WorldLimiter_Flag = false 215 | end) 216 | end) 217 | end 218 | 219 | 220 | end 221 | end 222 | -------------------------------------------------------------------------------- /difficulties.lua: -------------------------------------------------------------------------------- 1 | function HandleDifficultyCommand ( Split, Player ) 2 | if (Split[2] == nil) then 3 | if (#Split == 1) then 4 | SendMessage(Player, "Current world difficulty: " .. GetWorldDifficulty(Player:GetWorld())) 5 | SendMessage(Player, "To change: " .. Split[1] .. " ") 6 | else 7 | SendMessage( Player, "Usage: " .. Split[1] .. " " ) 8 | end 9 | return true 10 | end 11 | 12 | if (Split[2] == "peaceful") or (Split[2] == "0") or (Split[2] == "p") then 13 | SetWorldDifficulty(Player:GetWorld(), 0) 14 | SendMessage( Player, "World difficulty set to peaceful" ) 15 | 16 | --Remove mobs which are not allowed in peaceful 17 | Player:GetWorld():ForEachEntity( 18 | function (a_Entity) 19 | if not(a_Entity:IsMob()) then 20 | return 21 | end 22 | if (a_Entity:GetMobFamily() == cMonster.mfHostile) then 23 | a_Entity:Destroy() 24 | end 25 | end 26 | ) 27 | elseif (Split[2] == "easy") or (Split[2] == "1") or (Split[2] == "e") then 28 | SetWorldDifficulty(Player:GetWorld(), 1) 29 | SendMessage( Player, "World difficulty set to easy" ) 30 | elseif (Split[2] == "normal") or (Split[2] == "2") or (Split[2] == "n") then 31 | SetWorldDifficulty(Player:GetWorld(), 2) 32 | SendMessage( Player, "World difficulty set to normal" ) 33 | elseif (Split[2] == "hard") or (Split[2] == "3") or (Split[2] == "h") then 34 | SetWorldDifficulty(Player:GetWorld(), 3) 35 | SendMessage( Player, "World difficulty set to hard" ) 36 | else 37 | SendMessage( Player, "Usage: " .. Split[1] .. " " ) 38 | end 39 | 40 | return true 41 | end 42 | -------------------------------------------------------------------------------- /do.lua: -------------------------------------------------------------------------------- 1 | function HandleDoCommand( Split, Player ) 2 | 3 | if #Split < 3 then 4 | SendMessage( Player, "Usage: " .. Split[1] .. " [arguments ...]" ) 5 | return true 6 | end 7 | 8 | -- Get the command and arguments. 9 | local newSplit = table.concat( Split, " ", 3 ) 10 | 11 | local FoundPlayerCallback = function( a_Player ) 12 | local pluginManager = cRoot:Get():GetPluginManager() 13 | if (pluginManager:ExecuteCommand( a_Player, newSplit )) then 14 | SendMessageSuccess( Player, "Command executed!" ) 15 | else 16 | SendMessageFailure( Player, "Bad command - execution failed" ) 17 | end 18 | return true 19 | end 20 | 21 | if not cRoot:Get():FindAndDoWithPlayer( Split[2], FoundPlayerCallback ) then 22 | SendMessageFailure( Player, "Could not find player" ) 23 | return true 24 | end 25 | 26 | return true 27 | end 28 | 29 | function HandleSudoCommand ( Split, Player ) 30 | 31 | if #Split < 3 then 32 | SendMessage( Player, "Usage: " .. Split[1] .. " [arguments ...]" ) 33 | return true 34 | end 35 | 36 | -- Get the command and arguments. 37 | local newSplit = table.concat( Split, " ", 3 ) 38 | 39 | local FoundPlayerCallback = function( a_Player ) 40 | local pluginManager = cRoot:Get():GetPluginManager() 41 | if (pluginManager:ForceExecuteCommand( a_Player, newSplit )) then 42 | SendMessageSuccess( Player, "Command executed!" ) 43 | else 44 | SendMessageFailure( Player, "Bad command - execution failed" ) 45 | end 46 | return true 47 | end 48 | 49 | if not cRoot:Get():FindAndDoWithPlayer( Split[2], FoundPlayerCallback ) then 50 | SendMessageFailure( Player, "Could not find player" ) 51 | return true 52 | end 53 | 54 | return true 55 | end 56 | -------------------------------------------------------------------------------- /gm.lua: -------------------------------------------------------------------------------- 1 | -- gm.lua 2 | 3 | -- Implements gamemode and gm commands and console commands 4 | 5 | 6 | -- Used to translate gamemodes into strings 7 | local GameModeNameTable = 8 | { 9 | [gmSurvival] = "survival", 10 | [gmCreative] = "creative", 11 | [gmAdventure] = "adventure", 12 | [gmSpectator] = "spectator", 13 | } 14 | 15 | 16 | -- Translate strings to their representative gamemodes 17 | -- All options from vanilla minecraft 18 | local GameModeTable = 19 | { 20 | ["0"] = gmSurvival, 21 | ["survival"] = gmSurvival, 22 | ["s"] = gmSurvival, 23 | ["1"] = gmCreative, 24 | ["creative"] = gmCreative, 25 | ["c"] = gmCreative, 26 | ["2"] = gmAdventure, 27 | ["adventure"] = gmAdventure, 28 | ["a"] = gmAdventure, 29 | ["3"] = gmSpectator, 30 | ["spectator"] = gmSpectator, 31 | ["sp"] = gmSpectator, 32 | } 33 | 34 | 35 | local MessageFailure = "Player not found" 36 | 37 | 38 | --- Changes the gamemode of the given player 39 | -- 40 | -- @param GameMode The gamemode to change to 41 | -- @param PlayerName The player name of the player to change the gamemode of 42 | -- 43 | -- @return true if player was found and gamemode successfully changed, false otherwise 44 | -- 45 | local function ChangeGameMode( GameMode, PlayerName ) 46 | 47 | local GMChanged = false 48 | local lcPlayerName = string.lower(PlayerName) 49 | 50 | -- Search through online players and if one matches 51 | -- the given PlayerName then change their gamemode 52 | cRoot:Get():FindAndDoWithPlayer(PlayerName, 53 | function(PlayerMatch) 54 | if string.lower(PlayerMatch:GetName()) == lcPlayerName then 55 | PlayerMatch:SetGameMode(GameMode) 56 | SendMessage(PlayerMatch, "Gamemode set to " .. GameModeNameTable[GameMode] ) 57 | GMChanged = true 58 | end 59 | return true 60 | end 61 | ) 62 | 63 | return GMChanged 64 | 65 | end 66 | 67 | --- Handles the `/gamemode [player]` in-game command 68 | -- 69 | function HandleChangeGMCommand(Split, Player) 70 | 71 | -- Check params, translate into gamemode and player name: 72 | local GameMode = GameModeTable[Split[2]] 73 | 74 | if not GameMode then 75 | SendMessage(Player, "Usage: " .. Split[1] .. " [player]" ) 76 | return true 77 | end 78 | 79 | local PlayerToChange = Split[3] or Player:GetName() 80 | 81 | -- Report success or failure: 82 | if ChangeGameMode( GameMode, PlayerToChange ) then 83 | 84 | local Message = "Gamemode of " .. PlayerToChange .. " set to " .. GameModeNameTable[GameMode] 85 | local MessageTail = " by: " .. Player:GetName() 86 | 87 | if PlayerToChange ~= Player:GetName() then 88 | SendMessageSuccess( Player, Message ) 89 | end 90 | 91 | LOG( Message .. MessageTail ) 92 | 93 | else 94 | SendMessageFailure(Player, MessageFailure ) 95 | end 96 | 97 | return true 98 | end 99 | 100 | 101 | --- Handles the `gamemode [player]` console command 102 | -- 103 | function HandleConsoleGamemode( a_Split ) 104 | 105 | -- Check params, translate into gamemode and player name: 106 | local GameMode = GameModeTable[a_Split[2]] 107 | local PlayerToChange = a_Split[3] 108 | 109 | if not PlayerToChange or not GameMode then 110 | return true, "Usage: " .. a_Split[1] .. " " 111 | end 112 | 113 | -- Report success or failure: 114 | if ChangeGameMode( GameMode, PlayerToChange ) then 115 | 116 | local Message = "Gamemode of " .. PlayerToChange .. " set to " .. GameModeNameTable[GameMode] 117 | local MessageTail = " by: " .. "console" 118 | 119 | LOG( Message .. MessageTail ) 120 | 121 | else 122 | LOG( MessageFailure ) 123 | end 124 | 125 | return true 126 | end 127 | -------------------------------------------------------------------------------- /help.lua: -------------------------------------------------------------------------------- 1 | 2 | -- help.lua 3 | 4 | -- Implements the /help in-game command 5 | 6 | 7 | 8 | 9 | 10 | --- How many commands to put into one help page 11 | local g_CommandsPerPage = 8 12 | 13 | 14 | 15 | 16 | --- Displays one page of help for commands beginning with the specified string 17 | -- If a_Beginning is not given, all commands are displayed 18 | local function handleHelpPage(a_Player, a_PageNumber, a_Beginning) 19 | -- Check params: 20 | assert(type(a_PageNumber) == "number") 21 | assert(tolua.type(a_Player) == "cPlayer") 22 | a_Beginning = a_Beginning or "" 23 | assert(type(a_Beginning) == "string") 24 | 25 | -- Collect all commands into a table: 26 | local output = {} 27 | local beginLen = a_Beginning:len() 28 | cPluginManager:Get():ForEachCommand( 29 | function(a_CBCommand, a_CBPermission, a_CBHelpString) 30 | if not (a_Player:HasPermission(a_CBPermission)) then 31 | -- Do not display commands for which the player has no permission 32 | return false 33 | end 34 | if (a_CBHelpString == "") then 35 | -- Do not display commands without a help string 36 | return false 37 | end 38 | -- Check that the command contains the wanted string: 39 | if (a_CBCommand:sub(1, beginLen) == a_Beginning) then 40 | table.insert(output, a_CBCommand .. " " .. a_CBHelpString) 41 | end 42 | end 43 | ) 44 | 45 | -- Check the page count: 46 | local numCommands = #output 47 | if (numCommands == 0) then 48 | a_Player:SendMessageFailure("No commands available") 49 | return true 50 | end 51 | local firstLine = (a_PageNumber - 1) * g_CommandsPerPage + 1 52 | local lastLine = firstLine + g_CommandsPerPage 53 | local maxPages = math.ceil(numCommands / g_CommandsPerPage) 54 | if (firstLine > numCommands or firstLine < 0) then 55 | a_Player:SendMessageFailure("The requested page is not available. Only pages 1 - " .. maxPages .. " are available.") 56 | return true 57 | end 58 | 59 | -- Display only the requested page: 60 | table.sort(output) 61 | a_Player:SendMessageInfo("Displaying page " .. a_PageNumber .. " of " .. maxPages .. ":") 62 | for idx, txt in ipairs(output) do 63 | if ((idx >= firstLine) and (idx < lastLine)) then 64 | a_Player:SendMessage(txt) 65 | end 66 | end 67 | return true 68 | end 69 | 70 | 71 | 72 | 73 | 74 | --- Decides what help to show based on the parameters, calls the appropriate worker function 75 | function HandleHelpCommand(a_Split, a_Player) 76 | -- Handles the "/help [] []" in-game command 77 | -- If there's no param, display the first page of help: 78 | local numSplit = #a_Split 79 | if (numSplit == 1) then 80 | return handleHelpPage(a_Player, 1) 81 | end 82 | 83 | -- If there is a number as the first param, show that page: 84 | local pageRequested = tonumber(a_Split[2]) 85 | local filterStart = 3 86 | if (pageRequested == nil) then 87 | filterStart = 2 88 | pageRequested = 1 89 | end 90 | local beginningWanted 91 | if (numSplit >= filterStart) then 92 | beginningWanted = "/" .. table.concat(a_Split, " ", filterStart) 93 | end 94 | return handleHelpPage(a_Player, pageRequested, beginningWanted) 95 | end 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /main.lua: -------------------------------------------------------------------------------- 1 | -- main.lua 2 | -- Implements the main plugin entrypoint 3 | 4 | function Initialize(Plugin) 5 | Plugin:SetName(g_PluginInfo.Name) 6 | 7 | -- Register for all hooks needed 8 | cPluginManager:AddHook(cPluginManager.HOOK_BLOCK_SPREAD, OnBlockSpread) 9 | cPluginManager:AddHook(cPluginManager.HOOK_CHAT, OnChat) 10 | cPluginManager:AddHook(cPluginManager.HOOK_CRAFTING_NO_RECIPE, OnCraftingNoRecipe) 11 | cPluginManager:AddHook(cPluginManager.HOOK_PLAYER_DESTROYED, OnDisconnect) 12 | cPluginManager:AddHook(cPluginManager.HOOK_EXPLODING, OnExploding) 13 | cPluginManager:AddHook(cPluginManager.HOOK_KILLING, OnKilling) 14 | cPluginManager:AddHook(cPluginManager.HOOK_PLAYER_BREAKING_BLOCK, OnPlayerBreakingBlock) 15 | cPluginManager:AddHook(cPluginManager.HOOK_PLAYER_JOINED, OnPlayerJoined) 16 | cPluginManager:AddHook(cPluginManager.HOOK_PLAYER_JOINED, OnPlayerJoined_WebChat) 17 | cPluginManager:AddHook(cPluginManager.HOOK_PLAYER_MOVING, OnPlayerMoving) 18 | cPluginManager:AddHook(cPluginManager.HOOK_PLAYER_PLACING_BLOCK, OnPlayerPlacingBlock) 19 | cPluginManager:AddHook(cPluginManager.HOOK_PLAYER_RIGHT_CLICK, OnPlayerRightClick) 20 | cPluginManager:AddHook(cPluginManager.HOOK_SPAWNING_MONSTER, OnSpawningMonster) 21 | cPluginManager:AddHook(cPluginManager.HOOK_TAKE_DAMAGE, OnTakeDamage) 22 | cPluginManager:AddHook(cPluginManager.HOOK_TICK, OnTick) 23 | cPluginManager:AddHook(cPluginManager.HOOK_WORLD_TICK, OnWorldTick) 24 | 25 | -- Bind ingame commands: 26 | dofile(cPluginManager:GetPluginsPath() .. "/InfoReg.lua") 27 | RegisterPluginInfoCommands() 28 | RegisterPluginInfoConsoleCommands() 29 | 30 | -- Load SpawnProtection and WorldLimit settings for individual worlds: 31 | cRoot:Get():ForEachWorld( 32 | function (World) 33 | LoadWorldSettings(World) 34 | end 35 | ) 36 | 37 | -- Initialize the banlist, load its DB, do whatever processing it needs on startup: 38 | InitializeBanlist() 39 | 40 | -- Initialize the whitelist, load its DB, do whatever processing it needs on startup: 41 | InitializeWhitelist() 42 | 43 | -- Initialize the Item Blacklist (the list of items that cannot be obtained using the give command): 44 | IntializeItemBlacklist(Plugin) 45 | 46 | -- Add webadmin tabs: 47 | cWebAdmin:AddWebTab("Manage Server", "manage-server", HandleRequest_ManageServer) 48 | cWebAdmin:AddWebTab("Server Settings", "server-settings", HandleRequest_ServerSettings) 49 | cWebAdmin:AddWebTab("Chat", "chat", HandleRequest_Chat) 50 | cWebAdmin:AddWebTab("Players", "players", HandleRequest_Players) 51 | cWebAdmin:AddWebTab("Whitelist", "whitelist", HandleRequest_WhiteList) 52 | cWebAdmin:AddWebTab("Permissions", "permissions", HandleRequest_Permissions) 53 | cWebAdmin:AddWebTab("Plugins", "plugins", HandleRequest_ManagePlugins) 54 | cWebAdmin:AddWebTab("Time & Weather", "time-weather", HandleRequest_Weather) 55 | cWebAdmin:AddWebTab("Ranks", "ranks", HandleRequest_Ranks) 56 | cWebAdmin:AddWebTab("Player Ranks", "player-ranks", HandleRequest_PlayerRanks) 57 | 58 | -- Load the message of the day from file, and cache it: 59 | LoadMOTD() 60 | 61 | WEBLOGINFO("Core is initialised") 62 | LOG("Initialised!") 63 | 64 | return true 65 | end 66 | 67 | function OnDisable() 68 | LOG("Disabling...") 69 | end 70 | -------------------------------------------------------------------------------- /plugins.lua: -------------------------------------------------------------------------------- 1 | 2 | function HandlePluginsCommand(a_Split, a_Player) 3 | -- Parse the parameters for plugin statuses to show: 4 | local IncludePluginStatus = {} -- map of status -> true for statuses to show 5 | local idx = 2 6 | while (a_Split[idx]) do 7 | local param = a_Split[idx] 8 | idx = idx + 1 9 | if (param == "all") then 10 | IncludePluginStatus[cPluginManager.psLoaded] = true 11 | IncludePluginStatus[cPluginManager.psUnloaded] = true 12 | IncludePluginStatus[cPluginManager.psError] = true 13 | IncludePluginStatus[cPluginManager.psNotFound] = true 14 | IncludePluginStatus[cPluginManager.psDisabled] = true 15 | elseif (param == "loaded") then 16 | IncludePluginStatus[cPluginManager.psLoaded] = true 17 | elseif (param == "unloaded") then 18 | IncludePluginStatus[cPluginManager.psUnloaded] = true 19 | elseif ((param == "errored") or (param == "error") or (param == "errorred")) then 20 | IncludePluginStatus[cPluginManager.psError] = true 21 | elseif (param == "notfound") then 22 | IncludePluginStatus[cPluginManager.psNotFound] = true 23 | elseif (param == "disabled") then 24 | IncludePluginStatus[cPluginManager.psDisabled] = true 25 | end 26 | end 27 | if not(a_Split[2]) then 28 | -- By default, show only psLoaded plugins 29 | IncludePluginStatus[cPluginManager.psLoaded] = true 30 | end 31 | 32 | -- Enumerate the plugins: 33 | local PluginTable = {} 34 | cPluginManager:Get():ForEachPlugin( 35 | function (a_CBPlugin) 36 | table.insert(PluginTable, 37 | { 38 | Name = a_CBPlugin:GetName(), 39 | Folder = a_CBPlugin:GetFolderName(), 40 | Status = a_CBPlugin:GetStatus(), 41 | LoadError = a_CBPlugin:GetLoadError() 42 | } 43 | ) 44 | end 45 | ) 46 | table.sort(PluginTable, 47 | function (a_Plugin1, a_Plugin2) 48 | return (string.lower(a_Plugin1.Folder) < string.lower(a_Plugin2.Folder)) 49 | end 50 | ) 51 | 52 | -- Prepare a translation table for the status: 53 | local StatusName = 54 | { 55 | [cPluginManager.psLoaded] = "Loaded ", 56 | [cPluginManager.psUnloaded] = "Unloaded", 57 | [cPluginManager.psError] = "Error ", 58 | [cPluginManager.psNotFound] = "NotFound", 59 | [cPluginManager.psDisabled] = "Disabled", 60 | } 61 | 62 | -- Generate the output: 63 | local Out = {} 64 | local HasAnyListed = false 65 | table.insert(Out, "There are ") 66 | table.insert(Out, #PluginTable) 67 | table.insert(Out, " plugins, ") 68 | table.insert(Out, cPluginManager:Get():GetNumLoadedPlugins()) 69 | table.insert(Out, " loaded.\n") 70 | for _, plg in ipairs(PluginTable) do 71 | if (IncludePluginStatus[plg.Status]) then 72 | table.insert(Out, " ") 73 | table.insert(Out, StatusName[plg.Status] or " ") 74 | table.insert(Out, " ") 75 | table.insert(Out, plg.Folder) 76 | if (plg.Name ~= plg.Folder) then 77 | table.insert(Out, " (API name ") 78 | table.insert(Out, plg.Name) 79 | table.insert(Out, ")") 80 | end 81 | if (plg.Status == cPluginManager.psError) then 82 | table.insert(Out, " ERROR: ") 83 | table.insert(Out, plg.LoadError or "") 84 | end 85 | table.insert(Out, "\n") 86 | HasAnyListed = true 87 | end 88 | end 89 | if not(HasAnyListed) then 90 | table.insert(Out, "No plugins match your search criteria") 91 | end 92 | 93 | -- Send output to player: 94 | SendMessage(a_Player, table.concat(Out, "")) 95 | return true 96 | end 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /regen.lua: -------------------------------------------------------------------------------- 1 | 2 | -- regen.lua 3 | 4 | -- Implements the in-game and console commands for chunk regeneration 5 | 6 | 7 | 8 | 9 | 10 | function HandleRegenCommand(a_Split, a_Player) 11 | -- Check the params: 12 | local numParams = #a_Split 13 | if (numParams == 2) or (numParams > 3) then 14 | SendMessage(a_Player, "Usage: '" .. a_Split[1] .. "' or '" .. a_Split[1] .. " '" ) 15 | return true 16 | end 17 | 18 | -- Get the coords of the chunk to regen: 19 | local chunkX = a_Player:GetChunkX() 20 | local chunkZ = a_Player:GetChunkZ() 21 | if (numParams == 3) then 22 | chunkX = tonumber(a_Split[2]) 23 | chunkZ = tonumber(a_Split[3]) 24 | if (chunkX == nil) then 25 | SendMessageFailure(a_Player, "Not a number: '" .. a_Split[2] .. "'") 26 | return true 27 | end 28 | if (chunkZ == nil) then 29 | SendMessageFailure(a_Player, "Not a number: '" .. a_Split[3] .. "'") 30 | return true 31 | end 32 | end 33 | 34 | -- Regenerate the chunk: 35 | SendMessageSuccess(a_Player, "Regenerating chunk [" .. chunkX .. ", " .. chunkZ .. "]...") 36 | a_Player:GetWorld():RegenerateChunk(chunkX, chunkZ) 37 | return true 38 | end 39 | 40 | 41 | 42 | 43 | 44 | function HandleConsoleRegen(a_Split) 45 | -- Check the params: 46 | local numParams = #a_Split 47 | if ((numParams ~= 3) and (numParams ~= 4)) then 48 | return true, "Usage: " .. a_Split[1] .. " [world]" 49 | end 50 | 51 | -- Get the coords of the chunk to regen: 52 | local chunkX = tonumber(a_Split[2]) 53 | if (chunkX == nil) then 54 | return true, "Not a number: '" .. a_Split[2] .. "'" 55 | end 56 | local chunkZ = tonumber(a_Split[3]) 57 | if (chunkZ == nil) then 58 | return true, "Not a number: '" .. a_Split[3] .. "'" 59 | end 60 | 61 | -- Get the world to regen: 62 | local world 63 | if (a_Split[4] == nil) then 64 | world = cRoot:Get():GetDefaultWorld() 65 | else 66 | world = cRoot:Get():GetWorld(a_Split[4]) 67 | if (world == nil) then 68 | return true, "There's no world named '" .. a_Split[4] .. "'." 69 | end 70 | end 71 | 72 | -- Regenerate the chunk: 73 | world:RegenerateChunk(chunkX, chunkZ) 74 | return true, "Regenerating chunk [" .. chunkX .. ", " .. chunkZ .. "] in world " .. world:GetName() 75 | end 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /sqlite.lua: -------------------------------------------------------------------------------- 1 | 2 | -- sqlite.lua 3 | 4 | -- Declares the cSQLite class representing a single SQLite database connection, with common operations 5 | 6 | 7 | 8 | 9 | 10 | local cSQLite = {} 11 | cSQLite.__index = cSQLite 12 | 13 | 14 | 15 | 16 | 17 | --- Creates the table of the specified name and columns[] 18 | -- If the table exists, any columns missing are added; existing data is kept 19 | function cSQLite:CreateDBTable(a_TableName, a_Columns) 20 | -- Check params: 21 | assert(self ~= nil) 22 | assert(a_TableName ~= nil) 23 | assert(a_Columns ~= nil) 24 | 25 | -- Try to create the table first 26 | local sql = "CREATE TABLE IF NOT EXISTS '" .. a_TableName .. "' (" 27 | sql = sql .. table.concat(a_Columns, ", ") .. ")" 28 | local ExecResult = self.DB:exec(sql) 29 | if (ExecResult ~= sqlite3.OK) then 30 | LOGWARNING(self.PluginPrefix .. "Cannot create DB Table " .. a_TableName .. ": " .. ExecResult) 31 | LOGWARNING(self.PluginPrefix .. "Command: \"" .. sql .. "\".") 32 | return false 33 | end 34 | -- SQLite doesn't inform us if it created the table or not, so we have to continue anyway 35 | 36 | -- Check each column whether it exists 37 | -- Remove all the existing columns from a_Columns: 38 | local RemoveExistingColumn = function(a_Values) 39 | if (a_Values.name ~= nil) then 40 | local ColumnName = a_Values.name:lower() 41 | -- Search the a_Columns if they have that column: 42 | for j = 1, #a_Columns do 43 | -- Cut away all column specifiers (after the first space), if any: 44 | local SpaceIdx = string.find(a_Columns[j], " ") 45 | if (SpaceIdx ~= nil) then 46 | SpaceIdx = SpaceIdx - 1 47 | end 48 | local ColumnTemplate = string.lower(string.sub(a_Columns[j], 1, SpaceIdx)) 49 | -- If it is a match, remove from a_Columns: 50 | if (ColumnTemplate == ColumnName) then 51 | table.remove(a_Columns, j) 52 | break -- for j 53 | end 54 | end -- for j - a_Columns[] 55 | end -- if (a_Values.name ~= nil) 56 | end 57 | if (not(self:ExecuteStatement("PRAGMA table_info(" .. a_TableName .. ")", {}, RemoveExistingColumn))) then 58 | LOGWARNING(self.PluginPrefix .. "Cannot query DB table structure") 59 | return false 60 | end 61 | 62 | -- Create the missing columns 63 | -- a_Columns now contains only those columns that are missing in the DB 64 | if (#a_Columns > 0) then 65 | LOGINFO(self.PluginPrefix .. "Database table \"" .. a_TableName .. "\" is missing " .. #a_Columns .. " columns, fixing now.") 66 | for idx, ColumnName in ipairs(a_Columns) do 67 | if (not(self:ExecuteStatement("ALTER TABLE '" .. a_TableName .. "' ADD COLUMN '" .. ColumnName .. "'", {}))) then 68 | LOGWARNING(self.PluginPrefix .. "Cannot add DB table \"" .. a_TableName .. "\" column \"" .. ColumnName .. "\"") 69 | return false 70 | end 71 | end 72 | LOGINFO(self.PluginPrefix .. "Database table \"" .. a_TableName .. "\" columns fixed.") 73 | end 74 | 75 | return true 76 | end 77 | 78 | 79 | 80 | 81 | 82 | --- Executes the SQL statement, substituting "?" in the SQL with the specified params 83 | -- Calls a_Callback for each row 84 | -- The callback receives a dictionary table containing the row values (stmt:nrows()) 85 | -- Returns false and error message on failure, or true on success 86 | function cSQLite:ExecuteStatement(a_SQL, a_Params, a_Callback, a_RowIDCallback) 87 | -- Check params: 88 | assert(self ~= nil) 89 | assert(a_SQL ~= nil) 90 | assert(a_Params ~= nil) 91 | assert(self.DB ~= nil) 92 | assert((a_Callback == nil) or (type(a_Callback) == "function")) 93 | assert((a_RowIDCallback == nil) or (type(a_RowIDCallback) == "function")) 94 | 95 | -- Prepare the statement (SQL-compile): 96 | local Stmt, ErrCode, ErrMsg = self.DB:prepare(a_SQL) 97 | if (Stmt == nil) then 98 | ErrMsg = (ErrCode or "") .. " (" .. (ErrMsg or "") .. ")" 99 | LOGWARNING(self.PluginPrefix .. "Cannot prepare SQL \"" .. a_SQL .. "\": " .. ErrMsg) 100 | LOGWARNING(self.PluginPrefix .. " Params = {" .. table.concat(a_Params, ", ") .. "}") 101 | return nil, ErrMsg 102 | end 103 | 104 | -- Bind the values into the statement: 105 | ErrCode = Stmt:bind_values(unpack(a_Params)) 106 | if ((ErrCode ~= sqlite3.OK) and (ErrCode ~= sqlite3.DONE)) then 107 | ErrMsg = (ErrCode or "") .. " (" .. (self.DB:errmsg() or "") .. ")" 108 | LOGWARNING(self.PluginPrefix .. "Cannot bind values to statement \"" .. a_SQL .. "\": " .. ErrMsg) 109 | Stmt:finalize() 110 | return nil, ErrMsg 111 | end 112 | 113 | -- Step the statement: 114 | if (a_Callback == nil) then 115 | ErrCode = Stmt:step() 116 | if ((ErrCode ~= sqlite3.ROW) and (ErrCode ~= sqlite3.DONE)) then 117 | ErrMsg = (ErrCode or "") .. " (" .. (self.DB:errmsg() or "") .. ")" 118 | LOGWARNING(self.PluginPrefix .. "Cannot step statement \"" .. a_SQL .. "\": " .. ErrMsg) 119 | Stmt:finalize() 120 | return nil, ErrMsg 121 | end 122 | if (a_RowIDCallback ~= nil) then 123 | a_RowIDCallback(self.DB:last_insert_rowid()) 124 | end 125 | else 126 | -- Iterate over all returned rows: 127 | for v in Stmt:nrows() do 128 | a_Callback(v) 129 | end 130 | 131 | if (a_RowIDCallback ~= nil) then 132 | a_RowIDCallback(self.DB:last_insert_rowid()) 133 | end 134 | end 135 | Stmt:finalize() 136 | return true 137 | end 138 | 139 | 140 | 141 | 142 | 143 | --- Returns a new cSQLite instance with the database connection open to the specified file 144 | -- Returns false and a reason string on failure 145 | function NewSQLiteDB(a_FileName) 146 | -- Create a new instance: 147 | local res = {} 148 | setmetatable(res, cSQLite) 149 | -- cSQLite.__index = cSQLite 150 | res.PluginPrefix = cRoot:Get():GetPluginManager():GetCurrentPlugin():GetName() .. ": " 151 | 152 | -- Open the DB file: 153 | local ErrMsg 154 | res.DB, ErrMsg = sqlite3.open(a_FileName) 155 | if not(res.DB) then 156 | return false, (ErrMsg or "") 157 | end 158 | 159 | return res 160 | end 161 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /teleport.lua: -------------------------------------------------------------------------------- 1 | function HandleTPCommand(a_Split, a_Player) 2 | 3 | if #a_Split == 2 or #a_Split == 3 then 4 | 5 | -- Teleport to player specified in a_Split[2], tell them unless a_Split[3] equals "-h": 6 | TeleportToPlayer( a_Player, a_Split[2], (a_Split[3] ~= "-h") ) 7 | return true 8 | 9 | elseif #a_Split == 4 then 10 | 11 | -- Teleport to XYZ coords specified in a_Split[2, 3, 4]: 12 | 13 | -- For relative coordinates 14 | local Function 15 | local X = tonumber(a_Split[2]) 16 | Function = loadstring(a_Split[2]:gsub("~", "return " .. a_Player:GetPosX() .. "+0")) 17 | if Function then 18 | -- Execute the function in a save environment, and get the second return value. 19 | -- The first return value is a boolean. 20 | X = select(2, pcall(setfenv(Function, {}))) 21 | end 22 | 23 | local Y = tonumber(a_Split[3]) 24 | Function = loadstring(a_Split[3]:gsub("~", "return " .. a_Player:GetPosY() .. "+0")) 25 | if Function then 26 | -- Execute the function in a save environment, and get the second return value. 27 | -- The first return value is a boolean. 28 | Y = select(2, pcall(setfenv(Function, {}))) 29 | end 30 | 31 | local Z = tonumber(a_Split[4]) 32 | Function = loadstring(a_Split[4]:gsub("~", "return " .. a_Player:GetPosZ() .. "+0")) 33 | if Function then 34 | -- Execute the function in a save environment, and get the second return value. 35 | -- The first return value is a boolean. 36 | Z = select(2, pcall(setfenv(Function, {}))) 37 | end 38 | 39 | -- Check the given coordinates for errors. 40 | if (type(X) ~= 'number') then 41 | SendMessageFailure(a_Player, "'" .. a_Split[2] .. "' is not a valid number") 42 | return true 43 | end 44 | 45 | if (type(Y) ~= 'number') then 46 | SendMessageFailure(a_Player, "'" .. a_Split[3] .. "' is not a valid number") 47 | return true 48 | end 49 | 50 | if (type(Z) ~= 'number') then 51 | SendMessageFailure(a_Player, "'" .. a_Split[4] .. "' is not a valid number") 52 | return true 53 | end 54 | 55 | a_Player:TeleportToCoords( X, Y, Z ) 56 | SendMessageSuccess( a_Player, "You teleported to [X:" .. X .. " Y:" .. Y .. " Z:" .. Z .. "]" ) 57 | return true 58 | 59 | else 60 | SendMessage( a_Player, "Usage: '" .. a_Split[1] .. " [-h]' or '" .. a_Split[1] .. " '" ) 61 | return true 62 | end 63 | 64 | end 65 | -------------------------------------------------------------------------------- /tests/FuzzCommands.lua: -------------------------------------------------------------------------------- 1 | -- FuzzCommands.lua 2 | 3 | -- Defines a scenario for fuzzing the commands 4 | 5 | 6 | 7 | 8 | 9 | scenario 10 | { 11 | name = "Fuzz all commands", 12 | world -- Create a world 13 | { 14 | name = "world", 15 | }, 16 | initializePlugin(), 17 | connectPlayer -- Simulate a player connection 18 | { 19 | name = "player1", 20 | worldName = "world", 21 | }, 22 | connectPlayer 23 | { 24 | name = "player2", 25 | worldName = "world", 26 | }, 27 | fuzzAllCommands -- fuzz all registered commands 28 | { 29 | playerName = "player1", 30 | choices = 31 | { 32 | "world", 33 | "first", 34 | "player1", 35 | "player2", 36 | "1", 37 | }, 38 | maxLen = 1, -- Max number of elements chosen from "choices" 39 | }, 40 | } 41 | -------------------------------------------------------------------------------- /time.lua: -------------------------------------------------------------------------------- 1 | -- Implements time related commands and console commands 2 | 3 | 4 | local PlayerTimeAddCommandUsage = "Usage: /time add [world]" 5 | local PlayerTimeSetCommandUsage = "Usage: /time set [world]" 6 | local ConsoleSetTimeCommandUsage = "Usage: time set [world]" 7 | local ConsoleAddTimeCommandUsage = "Usage: time add [world]" 8 | 9 | -- Times of day and night as defined in vanilla minecraft 10 | local SpecialTimesTable = { 11 | ["day"] = 1000, 12 | ["night"] = 1000 + 12000, 13 | } 14 | 15 | local TimeAnimationInProgress = false 16 | 17 | -- Used to animate the transition between previous and new times 18 | local function SetTime( World, TimeToSet ) 19 | 20 | local CurrentTime = World:GetTimeOfDay() 21 | local MaxTime = 24000 22 | 23 | -- Handle the cases where TimeToSet < 0 or > 24000 24 | TimeToSet = TimeToSet % MaxTime 25 | 26 | local AnimationSpeed = 480 27 | if CurrentTime > TimeToSet then 28 | AnimationSpeed = -AnimationSpeed 29 | end 30 | 31 | local function DoAnimation() 32 | if not TimeAnimationInProgress then 33 | return 34 | end 35 | local TimeOfDay = World:GetTimeOfDay() 36 | local AnimatedTime = TimeOfDay + AnimationSpeed 37 | local Animate = ( 38 | ((AnimationSpeed > 0) and (AnimatedTime < TimeToSet)) 39 | or ((AnimationSpeed < 0) and (AnimatedTime > TimeToSet)) 40 | ) 41 | if Animate then 42 | World:SetTimeOfDay(AnimatedTime) 43 | World:ScheduleTask(1, DoAnimation) 44 | else 45 | World:SetTimeOfDay(TimeToSet) 46 | TimeAnimationInProgress = false 47 | end 48 | end 49 | 50 | 51 | if TimeAnimationInProgress then 52 | TimeAnimationInProgress = false 53 | World:SetTimeOfDay(TimeToSet) 54 | else 55 | TimeAnimationInProgress = true 56 | World:ScheduleTask(1, DoAnimation) 57 | end 58 | World:BroadcastChatSuccess("Time was set to " .. TimeToSet) 59 | LOG("Time in world \"" .. World:GetName() .. "\" was set to " .. TimeToSet) -- Let the console know about time changes 60 | 61 | return true 62 | end 63 | 64 | 65 | -- Code common to console and in-game `time add` command 66 | local function CommonAddTime( World, Time ) 67 | 68 | -- Stop if an invalid world was given 69 | if not World then 70 | return true 71 | end 72 | 73 | local TimeToAdd = tonumber( Time ) 74 | 75 | if not TimeToAdd then 76 | return false 77 | end 78 | 79 | local TimeToSet = World:GetTimeOfDay() + TimeToAdd 80 | SetTime( World, TimeToSet ) 81 | 82 | return true 83 | end 84 | 85 | 86 | -- Handler for "/time add [world]" subcommand 87 | function HandleAddTimeCommand( Split, Player ) 88 | 89 | if not CommonAddTime( GetWorld( Split[4], Player ), Split[3] ) then 90 | SendMessage( Player, PlayerTimeAddCommandUsage ) 91 | end 92 | 93 | return true 94 | end 95 | 96 | 97 | -- Handler for console command: time add [WorldName] 98 | function HandleConsoleAddTime(a_Split) 99 | 100 | if not CommonAddTime( GetWorld( a_Split[4] ), a_Split[3] ) then 101 | LOG(ConsoleAddTimeCommandUsage) 102 | end 103 | 104 | return true 105 | end 106 | 107 | 108 | -- Code common to console and in-game `time set` command 109 | local function CommonSetTime( World, Time ) 110 | 111 | -- Stop if an invalid world was given 112 | if not World then 113 | return true 114 | end 115 | 116 | -- Handle the vanilla cases of /time set , for compatibility 117 | local TimeToSet = SpecialTimesTable[Time] or tonumber(Time) 118 | 119 | if not TimeToSet then 120 | return false 121 | else 122 | SetTime( World, TimeToSet ) 123 | end 124 | 125 | return true 126 | end 127 | 128 | 129 | -- Handler for "/time set [world]" subcommand 130 | function HandleSetTimeCommand( Split, Player ) 131 | 132 | if not CommonSetTime( GetWorld( Split[4], Player ), Split[3] ) then 133 | SendMessage( Player, PlayerTimeSetCommandUsage ) 134 | end 135 | 136 | return true 137 | 138 | end 139 | 140 | 141 | -- Handler for console command: time set [world] 142 | function HandleConsoleSetTime(a_Split) 143 | 144 | if not CommonSetTime( GetWorld( a_Split[4] ), a_Split[3] ) then 145 | LOG(ConsoleSetTimeCommandUsage) 146 | end 147 | 148 | return true 149 | end 150 | 151 | 152 | -- Code common to console and in-game time commands 153 | local function CommonSpecialTime( World, TimeName ) 154 | 155 | -- Stop if an invalid world was given 156 | if not World then 157 | return true 158 | end 159 | 160 | SetTime( World, SpecialTimesTable[TimeName] ) 161 | 162 | return true 163 | end 164 | 165 | 166 | -- Handler for /time [world] 167 | function HandleSpecialTimeCommand( Split, Player ) 168 | 169 | return CommonSpecialTime( GetWorld( Split[3], Player ), Split[2] ) 170 | 171 | end 172 | 173 | 174 | -- Handler for console command: time [world] 175 | function HandleConsoleSpecialTime(a_Split) 176 | 177 | return CommonSpecialTime( GetWorld( a_Split[3] ), a_Split[2] ) 178 | 179 | end 180 | 181 | 182 | -- Handler for /time query daytime [world] 183 | function HandleQueryDaytimeCommand( Split, Player ) 184 | 185 | local World = GetWorld( Split[4], Player ) 186 | 187 | -- Stop if an invalid world was given 188 | if not World then 189 | return true 190 | end 191 | 192 | SendMessage( Player, "The current time in World \"" .. World:GetName() .. "\" is " .. World:GetTimeOfDay() ) 193 | 194 | return true 195 | end 196 | 197 | 198 | -- Handler for console command: time query daytime [world] 199 | function HandleConsoleQueryDaytime(a_Split) 200 | 201 | local World = GetWorld( a_Split[4] ) 202 | 203 | -- Stop if an invalid world was given 204 | if not World then 205 | return true 206 | end 207 | 208 | LOG( "The current time in World \"" .. World:GetName() .. "\" is " .. World:GetTimeOfDay() ) 209 | 210 | return true 211 | end 212 | 213 | 214 | -- Handler for /time query gametime [world] 215 | function HandleQueryGametimeCommand( Split, Player ) 216 | 217 | local World = GetWorld( Split[4], Player ) 218 | 219 | -- Stop if an invalid world was given 220 | if not World then 221 | return true 222 | end 223 | 224 | SendMessage( Player, "The World \"" .. World:GetName() .. "\" has existed for " .. World:GetWorldAge() ) 225 | 226 | return true 227 | end 228 | 229 | 230 | -- Handler for console command: time query gametime [world] 231 | function HandleConsoleQueryGametime(a_Split) 232 | 233 | local World = GetWorld( a_Split[4] ) 234 | 235 | -- Stop if an invalid world was given 236 | if not World then 237 | return true 238 | end 239 | 240 | LOG( "The World \"" .. World:GetName() .. "\" has existed for " .. World:GetWorldAge() ) 241 | 242 | return true 243 | end 244 | -------------------------------------------------------------------------------- /web_chat.lua: -------------------------------------------------------------------------------- 1 | local CHAT_HISTORY = 50 2 | local LastMessageID = 0 3 | 4 | local JavaScript = [[ 5 | 97 | ]] 98 | -- Array of {PluginName, FunctionName} tables 99 | local OnWebChatCallbacks = {} 100 | local ChatLogMessages = {} 101 | local WebCommands = {} 102 | local ltNormal = 1 103 | local ltInfo = 2 104 | local ltWarning = 3 105 | local ltError = 4 106 | 107 | -- Adds Webchat callback, plugins can return true to 108 | -- prevent message from appearing / being processed 109 | -- by further callbacks 110 | -- OnWebChat(Username, Message) 111 | function AddWebChatCallback(PluginName, FunctionName) 112 | for k, v in pairs(OnWebChatCallbacks) do 113 | if v[1] == PluginName and v[2] == FunctionName then 114 | return false 115 | end 116 | end 117 | table.insert(OnWebChatCallbacks, {PluginName, FunctionName}) 118 | return true 119 | end 120 | 121 | -- Removes webchat callback 122 | function RemoveWebChatCallback(PluginName, FunctionName) 123 | for i = #OnWebChatCallbacks, 0, -1 do 124 | if OnWebChatCallbacks[i][1] == PluginName and OnWebChatCallbacks[i][2] == FunctionName then 125 | table.remove(OnWebChatCallbacks, i) 126 | return true 127 | end 128 | end 129 | return false 130 | end 131 | 132 | 133 | -- Checks the chatlogs to see if the size gets too big. 134 | function TrimWebChatIfNeeded() 135 | while( #ChatLogMessages > CHAT_HISTORY ) do 136 | table.remove( ChatLogMessages, 1 ) 137 | end 138 | end 139 | 140 | 141 | 142 | 143 | 144 | -- Adds a plain message to the chat log 145 | -- The a_WebUserName parameter can be either a string(name) to send it to a certain webuser or nil to send it to every webuser. 146 | function WEBLOG(a_Message, a_WebUserName) 147 | LastMessageID = LastMessageID + 1 148 | table.insert(ChatLogMessages, {timestamp = os.date("[%Y-%m-%d %H:%M:%S]", os.time()), webuser = a_WebUserName, message = a_Message, id = LastMessageID, logtype = ltNormal}) 149 | TrimWebChatIfNeeded() 150 | end 151 | 152 | 153 | 154 | 155 | 156 | -- Adds a yellow-ish message to the chat log. 157 | -- The a_WebUserName parameter can be either a string(name) to send it to a certain webuser or nil to send it to every webuser. 158 | function WEBLOGINFO(a_Message, a_WebUserName) 159 | LastMessageID = LastMessageID + 1 160 | table.insert(ChatLogMessages, {timestamp = os.date("[%Y-%m-%d %H:%M:%S]", os.time()), webuser = a_WebUserName, message = a_Message, id = LastMessageID, logtype = ltInfo}) 161 | TrimWebChatIfNeeded() 162 | end 163 | 164 | 165 | 166 | 167 | 168 | -- Adds a red message to the chat log 169 | -- The a_WebUserName parameter can be either a string(name) to send it to a certain webuser or nil to send it to every webuser. 170 | function WEBLOGWARN(a_Message, a_WebUserName) 171 | LastMessageID = LastMessageID + 1 172 | table.insert(ChatLogMessages, {timestamp = os.date("[%Y-%m-%d %H:%M:%S]", os.time()), webuser = a_WebUserName, message = a_Message, id = LastMessageID, logtype = ltWarning}) 173 | TrimWebChatIfNeeded() 174 | end 175 | 176 | 177 | 178 | 179 | 180 | -- Adds a message with a red background to the chat log 181 | -- The a_WebUserName parameter can be either a string(name) to send it to a certain webuser or nil to send it to every webuser. 182 | function WEBLOGERROR(a_Message, a_WebUserName) 183 | LastMessageID = LastMessageID + 1 184 | table.insert(ChatLogMessages, {timestamp = os.date("[%Y-%m-%d %H:%M:%S]", os.time()), webuser = a_WebUserName, message = a_Message, id = LastMessageID, logtype = ltError}) 185 | TrimWebChatIfNeeded() 186 | end 187 | 188 | 189 | 190 | 191 | 192 | -- This function allows other plugins to add new commands to the webadmin. 193 | -- a_CommandString is the the way you call the command ("/help") 194 | -- a_HelpString is the message that tells you what the command does ("Shows a list of all the possible commands") 195 | -- a_PluginName is the name of the plugin binding the command ("Core") 196 | -- a_CallbackName is the name if the function that will be called when the command is executed ("HandleWebHelpCommand") 197 | function BindWebCommand(a_CommandString, a_HelpString, a_PluginName, a_CallbackName) 198 | assert(type(a_CommandString) == 'string') 199 | assert(type(a_PluginName) == 'string') 200 | assert(type(a_CallbackName) == 'string') 201 | 202 | -- Check if the command is already bound. Return false with an error message if. 203 | for Idx, CommandInfo in ipairs(WebCommands) do 204 | if (CommandInfo.Command == a_CommandString) then 205 | return false, "That command is already bound to a plugin called \"" .. CommandInfo.PluginName .. "\"." 206 | end 207 | end 208 | 209 | -- Insert the command into the array and return true 210 | table.insert(WebCommands, {CommandString = a_CommandString, HelpString = a_HelpString, PluginName = a_PluginName, CallbackName = a_CallbackName}) 211 | return true 212 | end 213 | 214 | 215 | 216 | 217 | 218 | -- Used by the webadmin to use /help 219 | function HandleWebHelpCommand(a_User, a_Message) 220 | local Content = "Available Commands:" 221 | for Idx, CommandInfo in ipairs(WebCommands) do 222 | if (CommandInfo.HelpString ~= "") then 223 | Content = Content .. '
' .. CommandInfo.CommandString .. '  -  ' .. CommandInfo.HelpString 224 | end 225 | end 226 | 227 | WEBLOG(Content, a_User) 228 | return true 229 | end 230 | 231 | 232 | 233 | 234 | 235 | -- Used by the webadmin to reload the server 236 | function HandleWebReloadCommand(a_User, a_Message) 237 | cPluginManager:Get():ReloadPlugins() 238 | WEBLOG("Reloading Plugins", a_User) 239 | return true 240 | end 241 | 242 | 243 | 244 | 245 | 246 | -- Register some basic commands 247 | BindWebCommand("/help", "Shows a list of all the possible commands", "Core", "HandleWebHelpCommand") 248 | BindWebCommand("/reload", "Reloads all the plugins", "Core", "HandleWebReloadCommand") 249 | 250 | 251 | 252 | 253 | 254 | -- Add a chatmessage from a player to the chatlog 255 | function OnChat(a_Player, a_Message) 256 | WEBLOG("[" .. a_Player:GetName() .. "]: " .. a_Message) 257 | end 258 | 259 | 260 | 261 | 262 | 263 | function OnPlayerJoined_WebChat(Player) 264 | WEBLOGINFO(Player:GetName() .. " has joined the game") 265 | end 266 | 267 | 268 | 269 | 270 | 271 | function OnDisconnect(a_Player) 272 | WEBLOGINFO(a_Player:GetName() .. " has left the game") 273 | end 274 | 275 | 276 | 277 | 278 | 279 | --- Removes html tags 280 | --- Creates a tag when http(s) links are send. 281 | --- It does this by selecting all the characters between "http(s)://" and a space, and puts an anker tag around it. 282 | local function ParseMessage(a_Message) 283 | local function PlaceString(a_Url) 284 | return '' .. a_Url .. '' 285 | end 286 | 287 | a_Message = a_Message:gsub("<", "<"):gsub(">", ">"):gsub("=", "="):gsub('"', """):gsub("'", "'"):gsub("&", "&") 288 | a_Message = a_Message:gsub('http://[^%s]+', PlaceString):gsub('https://[^%s]+', PlaceString) 289 | return a_Message 290 | end 291 | 292 | 293 | 294 | 295 | 296 | function HandleRequest_Chat( Request ) 297 | -- The webadmin asks if there are new messages. 298 | if( Request.PostParams["JustChat"] ~= nil ) then 299 | local LastIdx = tonumber(Request.PostParams["LastMessageID"] or 0) or 0 300 | local Content = "" 301 | 302 | -- Go through each message to see if they are older then the last message, and add them to the content 303 | for key, MessageInfo in pairs(ChatLogMessages) do 304 | if( MessageInfo.id > LastIdx ) then 305 | if (not MessageInfo.webuser or (MessageInfo.webuser == Request.Username)) then 306 | local Message = MessageInfo.timestamp .. ' ' .. ParseMessage(MessageInfo.message) 307 | if (MessageInfo.logtype == ltNormal) then 308 | Content = Content .. Message .. "
" 309 | elseif (MessageInfo.logtype == ltInfo) then 310 | Content = Content .. '' .. Message .. '
' 311 | elseif (MessageInfo.logtype == ltWarning) then 312 | Content = Content .. '' .. Message .. '
' 313 | elseif (MessageInfo.logtype == ltError) then 314 | Content = Content .. '' .. Message .. '
' 315 | end 316 | end 317 | end 318 | end 319 | Content = Content .. "<>" .. LastMessageID .. "<>" .. LastIdx 320 | return Content 321 | end 322 | 323 | -- Check if the webuser send a chat message. 324 | if( Request.PostParams["ChatMessage"] ~= nil ) then 325 | local Split = StringSplit(Request.PostParams["ChatMessage"]) 326 | local CommandExecuted = false 327 | 328 | -- Check if the message was actualy a command 329 | for Idx, CommandInfo in ipairs(WebCommands) do 330 | if (CommandInfo.CommandString == Split[1]) then 331 | -- cPluginManager:CallPlugin doesn't support calling yourself, so we have to check if the command is from the Core. 332 | if (CommandInfo.PluginName == "Core") then 333 | if (not _G[CommandInfo.CallbackName](Request.Username, Request.PostParams["ChatMessage"])) then 334 | WEBLOG("Something went wrong while calling \"" .. CommandInfo.CallbackName .. "\" From \"" .. CommandInfo.PluginName .. "\".", Request.Username) 335 | end 336 | else 337 | if (not cPluginManager:CallPlugin(CommandInfo.PluginName, CommandInfo.CallbackName, Request.Username, Request.PostParams["ChatMessage"])) then 338 | WEBLOG("Something went wrong while calling \"" .. CommandInfo.CallbackName .. "\" From \"" .. CommandInfo.PluginName .. "\".", Request.Username) 339 | end 340 | end 341 | return "" 342 | end 343 | end 344 | 345 | -- If the message starts with a '/' then the message is a command, but since it wasn't executed a few lines above the command didn't exist 346 | if (Request.PostParams["ChatMessage"]:sub(1, 1) == "/") then 347 | WEBLOG('Unknown Command "' .. Request.PostParams["ChatMessage"] .. '"', nil) 348 | return "" 349 | end 350 | 351 | -- Broadcast the message to the server 352 | for k, v in pairs(OnWebChatCallbacks) do 353 | if cPluginManager:CallPlugin(v[1], v[2], Request.Username, Request.PostParams["ChatMessage"]) then 354 | return "" 355 | end 356 | end 357 | cRoot:Get():BroadcastChat(cCompositeChat("[WEB] <" .. Request.Username .. "> " .. Request.PostParams["ChatMessage"]):UnderlineUrls()) 358 | 359 | -- Add the message to the chatlog 360 | WEBLOG("[WEB] [" .. Request.Username .. "]: " .. Request.PostParams["ChatMessage"]) 361 | return "" 362 | end 363 | 364 | local Content = JavaScript 365 | Content = Content .. [[ 366 |
367 | 368 | ]] 369 | return Content 370 | end 371 | -------------------------------------------------------------------------------- /web_manageserver.lua: -------------------------------------------------------------------------------- 1 | function HandleRequest_ManageServer( Request ) 2 | local Content = "" 3 | if (Request.PostParams["RestartServer"] ~= nil) then 4 | cRoot:Get():QueueExecuteConsoleCommand("restart") 5 | elseif (Request.PostParams["ReloadServer"] ~= nil) then 6 | cRoot:Get():GetPluginManager():ReloadPlugins() 7 | elseif (Request.PostParams["StopServer"] ~= nil) then 8 | cRoot:Get():QueueExecuteConsoleCommand("stop") 9 | elseif (Request.PostParams["WorldSaveAllChunks"] ~= nil) then 10 | cRoot:Get():GetWorld(Request.PostParams["WorldSaveAllChunks"]):QueueSaveAllChunks() 11 | end 12 | Content = Content .. [[ 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 |
Manage Server
restart the server
reload the server
stop the server
21 |
22 | 23 | 24 | ]] 25 | local LoopWorlds = function( World ) 26 | Content = Content .. [[ 27 | 28 | 29 | ]] 30 | end 31 | cRoot:Get():ForEachWorld( LoopWorlds ) 32 | Content = Content .. "
Manage Worlds
Save all the chunks of world ]] .. World:GetName() .. [[
" 33 | 34 | return Content 35 | end 36 | -------------------------------------------------------------------------------- /web_permissions.lua: -------------------------------------------------------------------------------- 1 | 2 | -- web_permissions.lua 3 | 4 | -- Implements the Permissions tab in the webadmin 5 | 6 | 7 | 8 | 9 | 10 | --- Maximum number of permissions displayed within a group's row. 11 | -- If there are more permissions than this, a triple-dot is displayed at the end of the list 12 | local MAX_PERMISSIONS = 10 13 | 14 | local ins = table.insert 15 | local con = table.concat 16 | 17 | 18 | 19 | 20 | 21 | --- Returns the HTML for a single group's row in the listing table 22 | local function GetGroupRow(a_GroupName) 23 | -- Check params: 24 | assert(type(a_GroupName) == "string") 25 | 26 | -- First column: group name: 27 | local Row = {""} 28 | ins(Row, cWebAdmin:GetHTMLEscapedString(a_GroupName)) 29 | ins(Row, "") 30 | 31 | -- Second column: permissions: 32 | local Permissions = cRankManager:GetGroupPermissions(a_GroupName) 33 | table.sort(Permissions) 34 | local NumPermissions = #Permissions 35 | if (NumPermissions <= MAX_PERMISSIONS) then 36 | ins(Row, con(Permissions, "
")) 37 | else 38 | ins(Row, con(Permissions, "
", 1, MAX_PERMISSIONS)) 39 | ins(Row, "
...") 40 | end 41 | ins(Row, "") 42 | 43 | -- Third column: restrictions: 44 | local Restrictions = cRankManager:GetGroupRestrictions(a_GroupName) 45 | table.sort(Restrictions) 46 | local NumRestrictions = #Restrictions 47 | if (NumRestrictions <= MAX_PERMISSIONS) then 48 | ins(Row, con(Restrictions, "
")) 49 | else 50 | ins(Row, con(Restrictions, "
", 1, MAX_PERMISSIONS)) 51 | ins(Row, "
...") 52 | end 53 | ins(Row, "") 54 | 55 | -- Fourth column: operations: 56 | ins(Row, "") 57 | ins(Row, GetFormButton("edit", "Edit", {GroupName = a_GroupName})) 58 | ins(Row, "
") 59 | ins(Row, GetFormButton("confirmdelgroup", "Delete", {GroupName = a_GroupName})) 60 | ins(Row, "
") 61 | 62 | return con(Row) 63 | end 64 | 65 | 66 | 67 | 68 | 69 | --- Displays the main Permissions page, listing the permission groups and their permissions 70 | local function ShowMainPermissionsPage(a_Request) 71 | local Page = {"

Create a new group

"} 72 | 73 | -- Add the header for adding a new group: 74 | ins(Page, "
Group name:") 75 | ins(Page, GetFormButton("addgroup", "Create a new group", {})) 76 | ins(Page, "


") 77 | 78 | -- Display a table showing all groups currently known: 79 | ins(Page, "

Groups

") 80 | local AllGroups = cRankManager:GetAllGroups() 81 | table.sort(AllGroups) 82 | for _, group in ipairs(AllGroups) do 83 | ins(Page, GetGroupRow(group)) 84 | end 85 | ins(Page, "
Group namePermissionsRestrictionsActions
") 86 | 87 | return con(Page) 88 | end 89 | 90 | 91 | 92 | 93 | 94 | --- Handles the AddGroup form in the main page 95 | -- Adds the group and redirects the user back to the group list 96 | local function ShowAddGroupPage(a_Request) 97 | -- Check params: 98 | local GroupName = a_Request.PostParams["GroupName"] 99 | if (GroupName == nil) then 100 | return HTMLError("Bad request, missing parameters.") 101 | end 102 | 103 | -- Add the group: 104 | cRankManager:AddGroup(TrimString(GroupName)) 105 | 106 | -- Redirect the user: 107 | return "

Group created. Return to group list.

" 108 | end 109 | 110 | 111 | 112 | 113 | 114 | --- Handles the AddPermission form in the Edit group page 115 | -- Adds the permission to the group and redirects the user back to the permission list 116 | local function ShowAddPermissionPage(a_Request) 117 | -- Check params: 118 | local GroupName = a_Request.PostParams["GroupName"] 119 | local Permission = a_Request.PostParams["Permission"] 120 | if ((GroupName == nil) or (Permission == nil)) then 121 | return HTMLError("Bad request, missing parameters.") 122 | end 123 | 124 | -- Add the permission: 125 | cRankManager:AddPermissionToGroup(TrimString(Permission), GroupName) 126 | 127 | -- Redirect the user: 128 | return 129 | "

Permission added. Return to group list.

" 134 | end 135 | 136 | 137 | 138 | 139 | 140 | --- Handles the AddRestriction form in the Edit group page 141 | -- Adds the restriction to the group and redirects the user back to the permission list 142 | local function ShowAddRestrictionPage(a_Request) 143 | -- Check params: 144 | local GroupName = a_Request.PostParams["GroupName"] 145 | local Restriction = a_Request.PostParams["Restriction"] 146 | if ((GroupName == nil) or (Restriction == nil)) then 147 | return HTMLError("Bad request, missing parameters.") 148 | end 149 | 150 | -- Add the permission: 151 | cRankManager:AddRestrictionToGroup(TrimString(Restriction), GroupName) 152 | 153 | -- Redirect the user: 154 | return 155 | "

Restriction added. Return to group details.

" 160 | end 161 | 162 | 163 | 164 | 165 | 166 | --- Shows a confirmation page for deleting a group 167 | local function ShowConfirmDelGroupPage(a_Request) 168 | -- Check params: 169 | local GroupName = a_Request.PostParams["GroupName"] 170 | if (GroupName == nil) then 171 | return HTMLError("Bad request, missing parameters.") 172 | end 173 | 174 | -- Show the confirmation request: 175 | local Page = 176 | { 177 | "

Delete group

Are you sure you want to delete group ", 178 | GroupName, 179 | "? It will be removed from all the ranks that are using it.

", 180 | "
", 181 | GetFormButton("delgroup", "Delete the group", {GroupName = GroupName}), 182 | "
", 183 | GetFormButton("", "Do NOT delete", {}), 184 | "
" 185 | } 186 | return con(Page) 187 | end 188 | 189 | 190 | 191 | 192 | 193 | --- Handles the DelGroup button in the ConfirmDelGroup page 194 | -- Removes the group and redirects the user back to the group list 195 | local function ShowDelGroupPage(a_Request) 196 | -- Check params: 197 | local GroupName = a_Request.PostParams["GroupName"] 198 | if (GroupName == nil) then 199 | return HTMLError("Bad request, missing parameters.") 200 | end 201 | 202 | -- Remove the group: 203 | cRankManager:RemoveGroup(GroupName) 204 | 205 | -- Redirect the user: 206 | return 207 | "

Group removed. Return to group list.

" 210 | end 211 | 212 | 213 | 214 | 215 | 216 | -- Handles the DelPermission form in the Edit permissions page 217 | -- Removes the permission from the group and redirects the user back to the permission list 218 | local function ShowDelPermissionPage(a_Request) 219 | -- Check params: 220 | local GroupName = a_Request.PostParams["GroupName"] 221 | local Permission = a_Request.PostParams["Permission"] 222 | if ((GroupName == nil) or (Permission == nil)) then 223 | return HTMLError("Bad request, missing parameters.") 224 | end 225 | 226 | -- Add the permission: 227 | cRankManager:RemovePermissionFromGroup(Permission, GroupName) 228 | 229 | -- Redirect the user: 230 | return 231 | "

Permission removed. Return to group list.

" 236 | end 237 | 238 | 239 | 240 | 241 | 242 | -- Handles the DelRestriction form in the Edit group page 243 | -- Removes the restriction from the group and redirects the user back to the Edit group page 244 | local function ShowDelRestrictionPage(a_Request) 245 | -- Check params: 246 | local GroupName = a_Request.PostParams["GroupName"] 247 | local Restriction = a_Request.PostParams["Restriction"] 248 | if ((GroupName == nil) or (Restriction == nil)) then 249 | return HTMLError("Bad request, missing parameters.") 250 | end 251 | 252 | -- Add the permission: 253 | cRankManager:RemoveRestrictionFromGroup(Restriction, GroupName) 254 | 255 | -- Redirect the user: 256 | return 257 | "

Restriction removed. Return to group.

" 262 | end 263 | 264 | 265 | 266 | 267 | 268 | --- Displays the Edit Group page for a single group, allowing the admin to edit permissions and restrictions 269 | local function ShowEditGroupPage(a_Request) 270 | -- Check params: 271 | local GroupName = a_Request.PostParams["GroupName"] 272 | if (GroupName == nil) then 273 | return HTMLError("Bad request, missing parameters.") 274 | end 275 | 276 | -- Add the header for adding permissions: 277 | local Page = {[[ 278 |

Return to the group list.

279 |
280 |

Add a permission

281 |
Permission 282 | ]]} 283 | ins(Page, GetFormButton("addpermission", "Add permission", {GroupName = GroupName})) 284 | ins(Page, "


") 285 | 286 | -- Add the permission list: 287 | local Permissions = cRankManager:GetGroupPermissions(GroupName) 288 | table.sort(Permissions) 289 | ins(Page, "

Group permissions

") 290 | for _, permission in ipairs(Permissions) do 291 | ins(Page, "") 296 | end 297 | ins(Page, "
") 292 | ins(Page, cWebAdmin:GetHTMLEscapedString(permission)) 293 | ins(Page, "
") 294 | ins(Page, GetFormButton("delpermission", "Remove permission", {GroupName = GroupName, Permission = permission})) 295 | ins(Page, "
") 298 | 299 | -- Add the header for adding restrictions: 300 | ins(Page, [[ 301 |

Add a restriction

302 |
Restriction 303 | ]]) 304 | ins(Page, GetFormButton("addrestriction", "Add restriction", {GroupName = GroupName})) 305 | ins(Page, "


") 306 | 307 | -- Add the restriction list: 308 | local Restrictions = cRankManager:GetGroupRestrictions(GroupName) 309 | table.sort(Restrictions) 310 | ins(Page, "

Group restrictions

") 311 | for _, restriction in ipairs(Restrictions) do 312 | ins(Page, "") 317 | end 318 | ins(Page, "
") 313 | ins(Page, cWebAdmin:GetHTMLEscapedString(restriction)) 314 | ins(Page, "
") 315 | ins(Page, GetFormButton("delrestriction", "Remove restriction", {GroupName = GroupName, Restriction = restriction})) 316 | ins(Page, "
") 319 | return con(Page) 320 | end 321 | 322 | 323 | 324 | 325 | 326 | --- Handlers for the individual subpages in this tab 327 | -- Each item maps a subpage name to a handler function that receives a HTTPRequest object and returns the HTML to return 328 | local g_SubpageHandlers = 329 | { 330 | [""] = ShowMainPermissionsPage, 331 | ["addgroup"] = ShowAddGroupPage, 332 | ["addpermission"] = ShowAddPermissionPage, 333 | ["addrestriction"] = ShowAddRestrictionPage, 334 | ["confirmdelgroup"] = ShowConfirmDelGroupPage, 335 | ["delgroup"] = ShowDelGroupPage, 336 | ["delpermission"] = ShowDelPermissionPage, 337 | ["delrestriction"] = ShowDelRestrictionPage, 338 | ["edit"] = ShowEditGroupPage, 339 | } 340 | 341 | 342 | 343 | 344 | 345 | --- Handles the web request coming from MCS 346 | -- Returns the entire tab's HTML contents, based on the player's request 347 | function HandleRequest_Permissions(a_Request) 348 | local Subpage = (a_Request.PostParams["subpage"] or "") 349 | local Handler = g_SubpageHandlers[Subpage] 350 | if (Handler == nil) then 351 | return HTMLError("An internal error has occurred, no handler for subpage " .. Subpage .. ".") 352 | end 353 | 354 | local PageContent = Handler(a_Request) 355 | 356 | --[[ 357 | -- DEBUG: Save content to a file for debugging purposes: 358 | local f = io.open("permissions.html", "wb") 359 | if (f ~= nil) then 360 | f:write(PageContent) 361 | f:close() 362 | end 363 | --]] 364 | 365 | return PageContent 366 | end 367 | 368 | 369 | 370 | 371 | 372 | -------------------------------------------------------------------------------- /web_playerranks.lua: -------------------------------------------------------------------------------- 1 | 2 | -- web_playerranks.lua 3 | 4 | -- Implements the Player Ranks tab in the webadmin 5 | 6 | 7 | 8 | 9 | 10 | --- Maximum number of players displayed on a single page. 11 | local PLAYERS_PER_PAGE = 20 12 | 13 | local ins = table.insert 14 | local con = table.concat 15 | 16 | 17 | 18 | 19 | 20 | --- Updates the rank from the ingame player with this uuid 21 | local function UpdateIngamePlayer(a_PlayerUUID, a_Message) 22 | cRoot:Get():ForEachPlayer( 23 | function(a_Player) 24 | if (a_Player:GetUUID() == a_PlayerUUID) then 25 | if (a_Message ~= "") then 26 | a_Player:SendMessage(a_Message) 27 | end 28 | a_Player:LoadRank() 29 | end 30 | end 31 | ) 32 | end 33 | 34 | 35 | 36 | 37 | 38 | --- Returns the HTML contents of the rank list 39 | local function GetRankList(a_SelectedRank) 40 | local RankList = {} 41 | ins(RankList, "") 59 | 60 | return con(RankList) 61 | end 62 | 63 | 64 | 65 | 66 | 67 | --- Returns the HTML contents of a single row in the Players table 68 | local function GetPlayerRow(a_PlayerUUID) 69 | -- Get the player name/rank: 70 | local PlayerName = cRankManager:GetPlayerName(a_PlayerUUID) 71 | local PlayerRank = cRankManager:GetPlayerRankName(a_PlayerUUID) 72 | 73 | -- First row: player name: 74 | local Row = {""} 75 | ins(Row, cWebAdmin:GetHTMLEscapedString(PlayerName)) 76 | ins(Row, "") 77 | 78 | -- Rank: 79 | ins(Row, cWebAdmin:GetHTMLEscapedString(PlayerRank)) 80 | 81 | -- Display actions for this player: 82 | ins(Row, "
") 83 | ins(Row, GetFormButton("editplayer", "Edit", {PlayerUUID = a_PlayerUUID})) 84 | ins(Row, "
") 85 | ins(Row, GetFormButton("confirmdel", "Remove rank", {PlayerUUID = a_PlayerUUID, PlayerName = PlayerName})) 86 | 87 | -- Terminate the row and return the entire concatenated string: 88 | ins(Row, "
") 89 | return con(Row) 90 | end 91 | 92 | 93 | 94 | 95 | 96 | --- Returns the HTML contents of the main Rankplayers page 97 | local function ShowMainPlayersPage(a_Request) 98 | -- Read the page number: 99 | local PageNumber = tonumber(a_Request.Params["PageNumber"]) 100 | if (PageNumber == nil) then 101 | PageNumber = 1 102 | end 103 | local StartRow = (PageNumber - 1) * PLAYERS_PER_PAGE 104 | local EndRow = PageNumber * PLAYERS_PER_PAGE - 1 105 | 106 | -- Accumulator for the page data 107 | local PageText = {} 108 | ins(PageText, "

Add a new player, Clear players

") 109 | 110 | -- Add a table describing each player: 111 | ins(PageText, "\n") 112 | local AllPlayers = cRankManager:GetAllPlayerUUIDs() 113 | for i = StartRow, EndRow, 1 do 114 | local PlayerUUID = AllPlayers[i + 1] 115 | if (PlayerUUID ~= nil) then 116 | ins(PageText, GetPlayerRow(PlayerUUID)) 117 | end 118 | end 119 | ins(PageText, "
PlayernameRankAction
") 120 | 121 | -- Calculate the page num: 122 | local MaxPages = math.floor((#AllPlayers + PLAYERS_PER_PAGE - 1) / PLAYERS_PER_PAGE) 123 | 124 | -- Display the pages list: 125 | ins(PageText, "") 126 | if (PageNumber > 1) then 127 | ins(PageText, "") 128 | else 129 | ins(PageText, "") 130 | end 131 | ins(PageText, "") 132 | if (PageNumber < MaxPages) then 133 | ins(PageText, "") 134 | else 135 | ins(PageText, "") 136 | end 137 | ins(PageText, "
<<<<Page " .. PageNumber .. " of " .. MaxPages .. ">>>>
") 138 | 139 | -- Return the entire concatenated string: 140 | return con(PageText) 141 | end 142 | 143 | 144 | 145 | 146 | 147 | --- Returns the HTML contents of the player add page 148 | local function ShowAddPlayerPage(a_Request) 149 | return [[ 150 |

Add Player

151 |
152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 172 | 173 |
Player Name:
Player UUID (short): 161 | 162 | If you leave this empty, the server will generate the UUID automatically. 163 |
Rank]] .. GetRankList(cRankManager:GetDefaultRank()) .. [[
171 |
174 |
]] 175 | end 176 | 177 | 178 | 179 | 180 | 181 | --- Processes the AddPlayer page's input, creating a new player and redirecting to the player rank list 182 | local function ShowAddPlayerProcessPage(a_Request) 183 | -- Check the received values: 184 | local PlayerName = a_Request.PostParams["PlayerName"] 185 | local PlayerUUID = a_Request.PostParams["PlayerUUID"] 186 | local RankName = a_Request.PostParams["RankName"] 187 | if ((PlayerName == nil) or (PlayerUUID == nil) or (RankName == nil) or (RankName == "")) then 188 | return HTMLError("Invalid request received, missing values.") 189 | end 190 | 191 | -- Check if playername is given 192 | if (PlayerName == "") then 193 | return [[ 194 |

Add Player

195 |

Missing Playername or name is longer than 16 chars!

196 |

Go back

197 | ]] 198 | end 199 | 200 | -- Search the uuid (if uuid line is empty) 201 | if (PlayerUUID == "") then 202 | if (cRoot:Get():GetServer():ShouldAuthenticate()) then 203 | PlayerUUID = cMojangAPI:GetUUIDFromPlayerName(PlayerName, false) 204 | else 205 | PlayerUUID = cClientHandle:GenerateOfflineUUID(PlayerName) 206 | end 207 | end 208 | 209 | -- Check if the uuid is correct 210 | if ((PlayerUUID == "") or (string.len(PlayerUUID) ~= 32)) then 211 | if (a_Request.PostParams["PlayerUUID"] == "") then 212 | return [[ 213 |

Add Player

214 |

Bad uuid. Go back

215 | ]] 216 | else 217 | return [[ 218 |

Add Player

219 |

UUID for player ]] .. PlayerName .. [[ not found!
220 | Maybe the player doesn't exists?

221 |

Go back

222 | ]] 223 | end 224 | end 225 | 226 | -- Exists the player already? 227 | if (cRankManager:GetPlayerRankName(PlayerUUID) ~= "") then 228 | return [[ 229 |

Add Player

230 |

Can't create the player because a player with the same uuid already exists!

231 |

Go back

232 | ]] 233 | end 234 | 235 | -- Add the new player: 236 | cRankManager:SetPlayerRank(PlayerUUID, PlayerName, RankName) 237 | UpdateIngamePlayer(PlayerUUID, "You were assigned the rank " .. RankName .. " by webadmin.") 238 | return "

Player created. Return.

" 239 | end 240 | 241 | 242 | 243 | 244 | --- Deletes the specified player and redirects back to list 245 | local function ShowDelPlayerPage(a_Request) 246 | -- Check the input: 247 | local PlayerUUID = a_Request.PostParams["PlayerUUID"] 248 | if (PlayerUUID == nil) then 249 | return HTMLError("Bad request") 250 | end 251 | 252 | -- Delete the player: 253 | cRankManager:RemovePlayerRank(PlayerUUID) 254 | UpdateIngamePlayer(PlayerUUID, "You were assigned the rank " .. cRankManager:GetDefaultRank() .. " by webadmin.") 255 | 256 | -- Redirect back to list: 257 | return "

Rank deleted. Return to list." 258 | end 259 | 260 | 261 | 262 | 263 | 264 | --- Shows a confirmation page for deleting the specified player 265 | local function ShowConfirmDelPage(a_Request) 266 | -- Check the input: 267 | local PlayerUUID = a_Request.PostParams["PlayerUUID"] 268 | local PlayerName = a_Request.PostParams["PlayerName"] 269 | if ((PlayerUUID == nil) or (PlayerName == nil)) then 270 | return HTMLError("Bad request") 271 | end 272 | 273 | -- Show confirmation: 274 | return [[ 275 |

Delete player

276 |

Are you sure you want to delete player ]] .. PlayerName .. [[?
277 | UUID: ]] .. PlayerUUID .. [[

278 |

Delete

279 |

Cancel

280 | ]] 281 | end 282 | 283 | 284 | 285 | 286 | 287 | --- Returns the HTML contents of the playerrank edit page. 288 | local function ShowEditPlayerRankPage(a_Request) 289 | -- Check the input: 290 | local PlayerUUID = a_Request.PostParams["PlayerUUID"] 291 | if ((PlayerUUID == nil) or (string.len(PlayerUUID) ~= 32)) then 292 | return HTMLError("Bad request") 293 | end 294 | 295 | -- Get player name: 296 | local PlayerName = cRankManager:GetPlayerName(PlayerUUID) 297 | local PlayerRank = cRankManager:GetPlayerRankName(PlayerUUID) 298 | 299 | return [[ 300 |

Change rank from ]] .. PlayerName .. [[

301 |
302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 320 | 321 |
UUID]] .. PlayerUUID .. [[
Current Rank]] .. PlayerRank .. [[
New Rank]] .. GetRankList(PlayerRank) .. [[
319 |
322 |
323 | ]] 324 | end 325 | 326 | 327 | 328 | 329 | 330 | --- Processes the edit rank page's input, change the rank and redirecting to the player rank list 331 | local function ShowEditPlayerRankProcessPage(a_Request) 332 | -- Check the input: 333 | local PlayerUUID = a_Request.PostParams["PlayerUUID"] 334 | local NewRank = a_Request.PostParams["RankName"] 335 | if ((PlayerUUID == nil) or (NewRank == nil) or (string.len(PlayerUUID) ~= 32) or (NewRank == "")) then 336 | return HTMLError("Bad request") 337 | end 338 | 339 | -- Get the player name: 340 | local PlayerName = cRankManager:GetPlayerName(PlayerUUID) 341 | if (PlayerName == "") then 342 | return [[ 343 |

Can't change the rank because this user doesn't exists!

344 |

Go back

345 | ]] 346 | end 347 | 348 | -- Edit the rank: 349 | cRankManager:SetPlayerRank(PlayerUUID, PlayerName, NewRank) 350 | UpdateIngamePlayer(PlayerUUID, "You were assigned the rank " .. NewRank .. " by webadmin.") 351 | return "

The rank from player " .. PlayerName .. " was changed to " .. NewRank .. ". Return.

" 352 | end 353 | 354 | 355 | 356 | 357 | 358 | --- Processes the clear of all player ranks 359 | local function ShowClearPlayersPage(a_Request) 360 | cRankManager:ClearPlayerRanks() 361 | LOGINFO("WebAdmin: A user cleared all player ranks") 362 | 363 | -- Update ingame players: 364 | cRoot:Get():ForEachPlayer( 365 | function(a_Player) 366 | a_Player:LoadRank() 367 | end 368 | ) 369 | 370 | return "

Cleared all player ranks! Return.

" 371 | end 372 | 373 | 374 | 375 | 376 | 377 | --- Shows a confirmation page for deleting all players 378 | local function ShowConfirmClearPage(a_Request) 379 | -- Show confirmation: 380 | return [[ 381 |

Clear all player ranks

382 |

Are you sure you want to delete all players from the database?

383 |

Clear

384 |

Cancel

385 | ]] 386 | end 387 | 388 | 389 | 390 | 391 | 392 | --- Handlers for the individual subpages in this tab 393 | -- Each item maps a subpage name to a handler function that receives a HTTPRequest object and returns the HTML to return 394 | local g_SubpageHandlers = 395 | { 396 | [""] = ShowMainPlayersPage, 397 | ["addplayer"] = ShowAddPlayerPage, 398 | ["addplayerproc"] = ShowAddPlayerProcessPage, 399 | ["delplayer"] = ShowDelPlayerPage, 400 | ["confirmdel"] = ShowConfirmDelPage, 401 | ["editplayer"] = ShowEditPlayerRankPage, 402 | ["editplayerproc"] = ShowEditPlayerRankProcessPage, 403 | ["clear"] = ShowClearPlayersPage, 404 | ["confirmclear"] = ShowConfirmClearPage, 405 | } 406 | 407 | 408 | 409 | 410 | 411 | --- Handles the web request coming from MCS 412 | -- Returns the entire tab's HTML contents, based on the player's request 413 | function HandleRequest_PlayerRanks(a_Request) 414 | local Subpage = (a_Request.PostParams["subpage"] or "") 415 | local Handler = g_SubpageHandlers[Subpage] 416 | if (Handler == nil) then 417 | return HTMLError("An internal error has occurred, no handler for subpage " .. Subpage .. ".") 418 | end 419 | 420 | local PageContent = Handler(a_Request) 421 | return PageContent 422 | end 423 | -------------------------------------------------------------------------------- /web_players.lua: -------------------------------------------------------------------------------- 1 | 2 | -- web_players.lua 3 | 4 | -- Implements the Players tab in the webadmin 5 | 6 | 7 | 8 | 9 | 10 | local ins = table.insert 11 | local con = table.concat 12 | 13 | 14 | 15 | 16 | 17 | --- Enumerates all players currently connected to the server 18 | -- Returns an array-table in which each item has PlayerName, PlayerUUID and WorldName 19 | local function EnumAllPlayers() 20 | local res = {} 21 | 22 | -- Insert each player into the table: 23 | cRoot:Get():ForEachPlayer( 24 | function(a_Player) 25 | ins(res, { 26 | PlayerName = a_Player:GetName(), 27 | PlayerUUID = a_Player:GetUUID(), 28 | WorldName = a_Player:GetWorld():GetName(), 29 | EntityID = a_Player:GetUniqueID() 30 | }) 31 | end 32 | ) 33 | 34 | return res 35 | end 36 | 37 | 38 | 39 | 40 | 41 | --- Returns the HTML for a single table row describing the specified player 42 | -- a_Player is the player item, a table containing PlayerName, PlayerUUID, WorldName and EntityID, as 43 | -- returned by EnumAllPlayers 44 | local function GetPlayerRow(a_Player) 45 | -- Check params: 46 | assert(type(a_Player) == "table") 47 | assert(type(a_Player.PlayerName) == "string") 48 | assert(type(a_Player.PlayerUUID) == "string") 49 | assert(type(a_Player.WorldName) == "string") 50 | assert(type(a_Player.EntityID) == "number") 51 | 52 | local Row = {""} 53 | 54 | -- First column: player name: 55 | ins(Row, "") 56 | ins(Row, cWebAdmin:GetHTMLEscapedString(a_Player.PlayerName)) 57 | ins(Row, "") 58 | 59 | -- Second column: rank: 60 | local RankName = cWebAdmin:GetHTMLEscapedString(cRankManager:GetPlayerRankName(a_Player.PlayerUUID)) 61 | if (RankName == "") then 62 | RankName = cWebAdmin:GetHTMLEscapedString(cRankManager:GetDefaultRank()) 63 | end 64 | ins(Row, "") 65 | ins(Row, RankName) 66 | ins(Row, "") 67 | 68 | -- Third row: actions: 69 | local PlayerIdent = 70 | { 71 | WorldName = a_Player.WorldName, 72 | EntityID = a_Player.EntityID, 73 | PlayerName = a_Player.PlayerName, 74 | PlayerUUID = a_Player.PlayerUUID, 75 | } 76 | ins(Row, "
") 77 | ins(Row, GetFormButton("details", "View details", PlayerIdent)) 78 | ins(Row, "
") 79 | ins(Row, GetFormButton("sendpm", "Send PM", PlayerIdent)) 80 | ins(Row, "
") 81 | ins(Row, GetFormButton("kick", "Kick", PlayerIdent)) 82 | ins(Row, "
") 83 | 84 | -- Finish the row: 85 | ins(Row, "") 86 | return con(Row) 87 | end 88 | 89 | 90 | 91 | 92 | 93 | --- Displays the Position details table in the Player details page 94 | local function GetPositionDetails(a_PlayerIdent) 95 | -- Get the player info: 96 | local PlayerInfo = {} 97 | local World = cRoot:Get():GetWorld(a_PlayerIdent.WorldName) 98 | if (World == nil) then 99 | return HTMLError("Error querying player position details - no world.") 100 | end 101 | World:DoWithEntityByID(a_PlayerIdent.EntityID, 102 | function(a_Entity) 103 | if not(a_Entity:IsPlayer()) then 104 | return 105 | end 106 | local Player = tolua.cast(a_Entity, "cPlayer") 107 | PlayerInfo.Pos = Player:GetPosition() 108 | PlayerInfo.LastBedPos = Player:GetLastBedPos() 109 | PlayerInfo.Found = true 110 | -- TODO: Other info? 111 | end 112 | ) 113 | 114 | -- If the player is not present (disconnected in the meantime), display no info: 115 | if not(PlayerInfo.Found) then 116 | return "" 117 | end 118 | 119 | -- Display the current world and coords: 120 | local Page = 121 | { 122 | "
Current world", 123 | cWebAdmin:GetHTMLEscapedString(a_PlayerIdent.WorldName), 124 | "
PositionX: ", 125 | tostring(math.floor(PlayerInfo.Pos.x * 1000) / 1000), 126 | "
Y: ", 127 | tostring(math.floor(PlayerInfo.Pos.y * 1000) / 1000), 128 | "
Z: ", 129 | tostring(math.floor(PlayerInfo.Pos.z * 1000) / 1000), 130 | "
Last bed positionX: ", 131 | tostring(PlayerInfo.LastBedPos.x), 132 | "
Y: ", 133 | tostring(PlayerInfo.LastBedPos.y), 134 | "
Z: ", 135 | tostring(PlayerInfo.LastBedPos.z), 136 | "
" 137 | 138 | --[[ 139 | -- TODO 140 | -- Add teleport control page: 141 | "

Teleport control

" 61 | content = content.."" 62 | end 63 | 64 | content = content.."
Spawn
", 142 | GetFormButton("teleportcoord", "Teleport to spawn", a_PlayerIdent) 143 | --]] 144 | } 145 | 146 | return con(Page) 147 | end 148 | 149 | 150 | 151 | 152 | 153 | --- Displays the Rank details table in the Player details page 154 | local function GetRankDetails(a_PlayerIdent) 155 | -- Display the current rank and its permissions: 156 | local RankName = cWebAdmin:GetHTMLEscapedString(cRankManager:GetPlayerRankName(a_PlayerIdent.PlayerUUID)) 157 | if (RankName == "") then 158 | RankName = cWebAdmin:GetHTMLEscapedString(cRankManager:GetDefaultRank()) 159 | end 160 | local Permissions = cRankManager:GetPlayerPermissions(a_PlayerIdent.PlayerUUID) 161 | table.sort(Permissions) 162 | local Page = 163 | { 164 | "

Rank

", 169 | } 170 | 171 | -- Let the admin change the rank using a combobox: 172 | ins(Page, "
Current rank", 165 | RankName, 166 | "
Permissions", 167 | con(Permissions, "
"), 168 | "
Change rank") 188 | ins(Page, GetFormButton("setrank", "Change rank", a_PlayerIdent)) 189 | ins(Page, "
") 190 | 191 | return con(Page) 192 | end 193 | 194 | 195 | 196 | 197 | 198 | --- Displays the main Players page 199 | -- Contains a per-world tables of all the players connected to the server 200 | local function ShowMainPlayersPage(a_Request) 201 | -- Get all players: 202 | local AllPlayers = EnumAllPlayers() 203 | 204 | -- Get all worlds: 205 | local PerWorldPlayers = {} -- Contains a map: WorldName -> {Players} 206 | local WorldNames = {} -- Contains an array of world names 207 | cRoot:Get():ForEachWorld( 208 | function(a_World) 209 | local WorldName = a_World:GetName() 210 | PerWorldPlayers[WorldName] = {} 211 | ins(WorldNames, WorldName) 212 | end 213 | ) 214 | table.sort(WorldNames) 215 | 216 | -- Translate the list into a per-world list: 217 | for _, player in ipairs(AllPlayers) do 218 | local PerWorld = PerWorldPlayers[player.WorldName] 219 | ins(PerWorld, player) 220 | end 221 | 222 | -- For each world, display a table of players: 223 | local Page = {} 224 | for _, worldname in ipairs(WorldNames) do 225 | ins(Page, "

") 226 | ins(Page, worldname) 227 | ins(Page, "

") 228 | table.sort(PerWorldPlayers[worldname], 229 | function (a_Player1, a_Player2) 230 | return (a_Player1.PlayerName < a_Player2.PlayerName) 231 | end 232 | ) 233 | for _, player in ipairs(PerWorldPlayers[worldname]) do 234 | ins(Page, GetPlayerRow(player)) 235 | end 236 | ins(Page, "
PlayerRankActions

Total players in world: ") 237 | ins(Page, tostring(#PerWorldPlayers[worldname])) 238 | ins(Page, "

") 239 | end 240 | 241 | return con(Page) 242 | end 243 | 244 | 245 | 246 | 247 | 248 | --- Displays the player details page 249 | local function ShowDetailsPage(a_Request) 250 | -- Check params: 251 | local WorldName = a_Request.PostParams["WorldName"] 252 | local EntityID = a_Request.PostParams["EntityID"] 253 | local PlayerName = a_Request.PostParams["PlayerName"] 254 | local PlayerUUID = a_Request.PostParams["PlayerUUID"] 255 | if ((WorldName == nil) or (EntityID == nil) or (PlayerName == nil) or (PlayerUUID == nil)) then 256 | return HTMLError("Bad request, missing parameters.") 257 | end 258 | 259 | -- Stuff the parameters into a table: 260 | local PlayerIdent = 261 | { 262 | PlayerName = PlayerName, 263 | WorldName = WorldName, 264 | EntityID = EntityID, 265 | PlayerUUID = PlayerUUID, 266 | } 267 | 268 | -- Add the header: 269 | local Page = 270 | { 271 | "

Back to player list.

", 274 | } 275 | 276 | -- Display the position details: 277 | ins(Page, GetPositionDetails(PlayerIdent)) 278 | 279 | -- Display the rank details: 280 | ins(Page, GetRankDetails(PlayerIdent)) 281 | 282 | return con(Page) 283 | end 284 | 285 | 286 | 287 | 288 | 289 | --- Handles the KickPlayer button in the main page 290 | -- Kicks the player and redirects the admin back to the player list 291 | local function ShowKickPlayerPage(a_Request) 292 | -- Check params: 293 | local WorldName = a_Request.PostParams["WorldName"] 294 | local EntityID = a_Request.PostParams["EntityID"] 295 | local PlayerName = a_Request.PostParams["PlayerName"] 296 | if ((WorldName == nil) or (EntityID == nil) or (PlayerName == nil)) then 297 | return HTMLError("Bad request, missing parameters.") 298 | end 299 | 300 | -- Get the world: 301 | local World = cRoot:Get():GetWorld(WorldName) 302 | if (World == nil) then 303 | return HTMLError("Bad request, no such world.") 304 | end 305 | 306 | -- Kick the player: 307 | World:DoWithEntityByID(EntityID, 308 | function(a_Entity) 309 | if (a_Entity:IsPlayer()) then 310 | local Client = tolua.cast(a_Entity, "cPlayer"):GetClientHandle() 311 | if (Client ~= nil) then 312 | Client:Kick(a_Request.PostParams["Reason"] or "Kicked from webadmin") 313 | else 314 | LOG("Client is nil") 315 | end 316 | end 317 | end 318 | ) 319 | 320 | -- Redirect the admin back to the player list: 321 | return "

Player kicked. Return to player list.

" 322 | end 323 | 324 | 325 | 326 | 327 | 328 | --- Displays the SendPM subpage allowing the admin to send a PM to the player 329 | local function ShowSendPMPage(a_Request) 330 | -- Check params: 331 | local WorldName = a_Request.PostParams["WorldName"] 332 | local EntityID = a_Request.PostParams["EntityID"] 333 | local PlayerName = a_Request.PostParams["PlayerName"] 334 | if ((WorldName == nil) or (EntityID == nil) or (PlayerName == nil)) then 335 | return HTMLError("Bad request, missing parameters.") 336 | end 337 | 338 | -- Show the form for entering the message: 339 | local PlayerIdent = 340 | { 341 | PlayerName = PlayerName, 342 | WorldName = WorldName, 343 | EntityID = EntityID, 344 | } 345 | return table.concat({ 346 | "

Send a message

Player", 347 | cWebAdmin:GetHTMLEscapedString(PlayerName), 348 | "
Message
", 349 | "
", 350 | GetFormButton("sendpmproc", "Send message", PlayerIdent), 351 | "
" 352 | }) 353 | end 354 | 355 | 356 | 357 | 358 | 359 | --- Handles the message form from the SendPM page 360 | -- Sends the PM, redirects the admin back to the player list 361 | local function ShowSendPMProcPage(a_Request) 362 | -- Check params: 363 | local WorldName = a_Request.PostParams["WorldName"] 364 | local EntityID = a_Request.PostParams["EntityID"] 365 | local PlayerName = a_Request.PostParams["PlayerName"] 366 | local Msg = a_Request.PostParams["Msg"] 367 | if ((WorldName == nil) or (EntityID == nil) or (PlayerName == nil) or (Msg == nil)) then 368 | return HTMLError("Bad request, missing parameters.") 369 | end 370 | 371 | -- Send the PM: 372 | local World = cRoot:Get():GetWorld(WorldName) 373 | if (World ~= nil) then 374 | World:DoWithEntityByID(EntityID, 375 | function(a_Entity) 376 | if (a_Entity:IsPlayer()) then 377 | SendMessage(tolua.cast(a_Entity, "cPlayer"), Msg) 378 | end 379 | end 380 | ) 381 | end 382 | 383 | -- Redirect the admin back to the player list: 384 | return "

Message sent. Return to player list.

" 385 | end 386 | 387 | 388 | 389 | 390 | 391 | --- Processes the SetRank form in the player details page 392 | -- Sets the player's rank and redirects the admin back to the player details page 393 | local function ShowSetRankPage(a_Request) 394 | -- Check params: 395 | local WorldName = a_Request.PostParams["WorldName"] 396 | local EntityID = a_Request.PostParams["EntityID"] 397 | local PlayerName = a_Request.PostParams["PlayerName"] 398 | local PlayerUUID = a_Request.PostParams["PlayerUUID"] 399 | local RankName = a_Request.PostParams["RankName"] 400 | if ((WorldName == nil) or (EntityID == nil) or (PlayerName == nil) or (PlayerUUID == nil) or (RankName == nil)) then 401 | return HTMLError("Bad request, missing parameters.") 402 | end 403 | 404 | -- Change the player's rank: 405 | cRankManager:SetPlayerRank(PlayerUUID, PlayerName, RankName) 406 | 407 | -- Update each in-game player: 408 | cRoot:Get():ForEachPlayer( 409 | function(a_CBPlayer) 410 | if (a_CBPlayer:GetName() == PlayerName) then 411 | a_CBPlayer:SendMessage("You were assigned the rank " .. RankName .. " by webadmin.") 412 | a_CBPlayer:LoadRank() 413 | end 414 | end 415 | ) 416 | 417 | -- Redirect the admin back to the player list: 418 | return con({ 419 | "

Rank changed. Return to player details.

" 430 | }) 431 | end 432 | 433 | 434 | 435 | 436 | 437 | --- Handlers for the individual subpages in this tab 438 | -- Each item maps a subpage name to a handler function that receives a HTTPRequest object and returns the HTML to return 439 | local g_SubpageHandlers = 440 | { 441 | [""] = ShowMainPlayersPage, 442 | ["details"] = ShowDetailsPage, 443 | ["kick"] = ShowKickPlayerPage, 444 | ["sendpm"] = ShowSendPMPage, 445 | ["sendpmproc"] = ShowSendPMProcPage, 446 | ["setrank"] = ShowSetRankPage, 447 | } 448 | 449 | 450 | 451 | 452 | 453 | --- Handles the web request coming from MCS 454 | -- Returns the entire tab's HTML contents, based on the player's request 455 | function HandleRequest_Players(a_Request) 456 | local Subpage = (a_Request.PostParams["subpage"] or "") 457 | local Handler = g_SubpageHandlers[Subpage] 458 | if (Handler == nil) then 459 | return HTMLError("An internal error has occurred, no handler for subpage " .. Subpage .. ".") 460 | end 461 | 462 | local PageContent = Handler(a_Request) 463 | 464 | --[[ 465 | -- DEBUG: Save content to a file for debugging purposes: 466 | local f = io.open("players.html", "wb") 467 | if (f ~= nil) then 468 | f:write(PageContent) 469 | f:close() 470 | end 471 | --]] 472 | 473 | return PageContent 474 | end 475 | 476 | 477 | 478 | 479 | -------------------------------------------------------------------------------- /web_plugins.lua: -------------------------------------------------------------------------------- 1 | 2 | -- web_plugins.lua 3 | 4 | -- Implements the Plugins web tab used to manage plugins on the server 5 | 6 | --[[ 7 | General info: The web handler loads the settings.ini file in its start, and reads the list of enabled 8 | plugins out of it. Then it processes any changes requested by the user through the buttons; it carries out 9 | those changes on the list of enabled plugins itself. Then it saves that list back to the settings.ini. The 10 | changes aren't applied until the user explicitly clicks on "reload", since some changes require more than 11 | single reloads of the page (such as enabling a plugin and moving it into place using the up / down buttons). 12 | --]] 13 | 14 | 15 | 16 | 17 | 18 | -- Stores whether the plugin list has changed and thus the server needs to reload plugins 19 | -- Has to be defined outside so that it keeps its value across multiple calls to the handler. 20 | local g_NeedsReload = false 21 | 22 | 23 | 24 | 25 | 26 | --- Returns an array of plugin names that are enabled, in their load order 27 | local function LoadEnabledPlugins(SettingsIni) 28 | local res = {} 29 | local IniKeyPlugins = SettingsIni:FindKey("Plugins") 30 | if (IniKeyPlugins == cIniFile.noID) then 31 | -- No [Plugins] key in the INI file 32 | return {} 33 | end 34 | 35 | -- Scan each value, remember each that is named "plugin" 36 | for idx = 0, SettingsIni:GetNumValues(IniKeyPlugins) - 1 do 37 | if (string.lower(SettingsIni:GetValue(IniKeyPlugins, idx)) == "1") then 38 | table.insert(res, SettingsIni:GetValueName(IniKeyPlugins, idx)) 39 | end 40 | end 41 | return res 42 | end 43 | 44 | 45 | 46 | 47 | 48 | --- Saves the list of enabled plugins into the ini file 49 | -- Keeps all the other values in the ini file intact 50 | local function SaveEnabledPlugins(SettingsIni, EnabledPlugins) 51 | -- First remove all values named "plugin": 52 | local IniKeyPlugins = SettingsIni:FindKey("Plugins") 53 | if (IniKeyPlugins ~= cIniFile.noID) then 54 | for idx = SettingsIni:GetNumValues(IniKeyPlugins) - 1, 0, -1 do 55 | SettingsIni:DeleteValueByID(IniKeyPlugins, idx) 56 | end 57 | end 58 | 59 | -- Now add back the entire list of enabled plugins, in our order: 60 | for idx, name in ipairs(EnabledPlugins) do 61 | SettingsIni:AddValue("Plugins", name, "1") 62 | end 63 | 64 | -- Save to file: 65 | SettingsIni:WriteFile("settings.ini") 66 | 67 | -- Mark the settings as changed: 68 | g_NeedsReload = true 69 | end 70 | 71 | 72 | 73 | 74 | 75 | --- Returns the lists of Enabled and Disabled plugins 76 | -- Each list's item is a table describing the plugin - has Name, Folder, Status and LoadError 77 | -- a_EnabledPluginFolders is an array of strings read from settings.ini listing the enabled plugins in their load order 78 | local function GetPluginLists(a_EnabledPluginFolders) 79 | -- Convert a_EnabledPluginFolders into a map {Folder -> true}: 80 | local EnabledPluginFolderMap = {} 81 | for _, folder in ipairs(a_EnabledPluginFolders) do 82 | EnabledPluginFolderMap[folder] = true 83 | end 84 | 85 | -- Retrieve a map of all known plugins: 86 | local PM = cPluginManager:Get() 87 | PM:RefreshPluginList() 88 | local Plugins = {} -- map {PluginFolder -> plugin} 89 | PM:ForEachPlugin( 90 | function (a_CBPlugin) 91 | local plugin = 92 | { 93 | Name = a_CBPlugin:GetName(), 94 | Folder = a_CBPlugin:GetFolderName(), 95 | Status = a_CBPlugin:GetStatus(), 96 | LoadError = a_CBPlugin:GetLoadError() 97 | } 98 | Plugins[plugin.Folder] = plugin 99 | end 100 | ) 101 | 102 | -- Process the information about enabled plugins: 103 | local EnabledPlugins = {} 104 | for _, plgFolder in ipairs(a_EnabledPluginFolders) do 105 | table.insert(EnabledPlugins, Plugins[plgFolder]) 106 | end 107 | 108 | -- Pick up all the disabled plugins: 109 | local DisabledPlugins = {} 110 | for folder, plugin in pairs(Plugins) do 111 | if not(EnabledPluginFolderMap[folder]) then 112 | table.insert(DisabledPlugins, plugin) 113 | end 114 | end 115 | 116 | -- Sort the disabled plugin array: 117 | table.sort(DisabledPlugins, 118 | function (a_Plugin1, a_Plugin2) 119 | return (string.lower(a_Plugin1.Folder) < string.lower(a_Plugin2.Folder)) 120 | end 121 | ) 122 | -- Do NOT sort EnabledPlugins - we want them listed in their load order instead! 123 | 124 | return EnabledPlugins, DisabledPlugins 125 | end 126 | 127 | 128 | 129 | 130 | 131 | --- Builds an HTML table containing the list of plugins 132 | -- First the enabled plugins are listed in their load order. If any is manually unloaded or errored, it is marked as such. 133 | -- Then an alpha-sorted list of the disabled plugins 134 | local function ListCurrentPlugins(a_EnabledPluginFolders) 135 | local EnabledPlugins, DisabledPlugins = GetPluginLists(a_EnabledPluginFolders) 136 | 137 | -- Output the EnabledPlugins table: 138 | local res = {} 139 | local ins = table.insert 140 | if (#EnabledPlugins > 0) then 141 | ins(res, [[ 142 |

Enabled plugins

143 |

These plugins are enabled in the server settings:

144 | 145 | ]] 146 | ) 147 | local Num = #EnabledPlugins 148 | for idx, plugin in pairs(EnabledPlugins) do 149 | -- Move and Disable buttons: 150 | ins(res, "]]) 153 | else 154 | ins(res, '') 157 | end 158 | ins(res, [[') 161 | else 162 | ins(res, '') 165 | end 166 | ins(res, '') 169 | 170 | -- Plugin name and, if different, folder: 171 | ins(res, "") 195 | end 196 | ins(res, "
") 151 | if (idx == 1) then 152 | ins(res, [[ ]]) 159 | if (idx == Num) then 160 | ins(res, '
") 172 | ins(res, plugin.Folder) 173 | if (plugin.Folder ~= plugin.Name) then 174 | ins(res, " (API name ") 175 | ins(res, plugin.Name) 176 | ins(res, ")") 177 | end 178 | 179 | -- Plugin status, if not psLoaded: 180 | ins(res, "") 181 | if (plugin.Status == cPluginManager.psUnloaded) then 182 | ins(res, "(currently unloaded)") 183 | elseif (plugin.Status == cPluginManager.psNotFound) then 184 | ins(res, "(files missing on disk)") 185 | elseif (plugin.Status == cPluginManager.psError) then 186 | ins(res, "") 187 | if ((plugin.LoadError == nil) or (plugin.LoadError == "")) then 188 | ins(res, "Unknown load error") 189 | else 190 | ins(res, plugin.LoadError) 191 | end 192 | ins(res, "") 193 | end 194 | ins(res, "

") 197 | end 198 | 199 | -- Output DisabledPlugins table: 200 | if (#DisabledPlugins > 0) then 201 | ins(res, [[

Disabled plugins

202 |

These plugins are installed, but are disabled in the configuration.

203 | ]] 204 | ) 205 | for idx, plugin in ipairs(DisabledPlugins) do 206 | ins(res, '") 211 | end 212 | ins(res, "
') 209 | ins(res, plugin.Name) 210 | ins(res, "

") 213 | end 214 | 215 | return table.concat(res, "") 216 | end 217 | 218 | 219 | 220 | 221 | 222 | --- Disables the specified plugin 223 | -- Saves the new set of enabled plugins into a_SettingsIni 224 | -- Returns true if the plugin was disabled 225 | local function DisablePlugin(a_SettingsIni, a_PluginFolder, a_EnabledPlugins) 226 | for idx, name in ipairs(a_EnabledPlugins) do 227 | if (name == a_PluginFolder) then 228 | table.remove(a_EnabledPlugins, idx) 229 | SaveEnabledPlugins(a_SettingsIni, a_EnabledPlugins) 230 | return true 231 | end 232 | end 233 | return false 234 | end 235 | 236 | 237 | 238 | 239 | 240 | --- Enables the specified plugin 241 | -- Saves the new set of enabled plugins into SettingsIni 242 | -- Returns true if the plugin was enabled (false if it was already enabled before) 243 | local function EnablePlugin(SettingsIni, PluginName, EnabledPlugins) 244 | for idx, name in ipairs(EnabledPlugins) do 245 | if (name == PluginName) then 246 | -- Plugin already enabled, ignore this call 247 | return false 248 | end 249 | end 250 | -- Add the plugin to the end of the list, save: 251 | table.insert(EnabledPlugins, PluginName) 252 | SaveEnabledPlugins(SettingsIni, EnabledPlugins) 253 | return true 254 | end 255 | 256 | 257 | 258 | 259 | 260 | --- Moves the specified plugin up or down by the specified delta 261 | -- Saves the new order into SettingsIni 262 | -- Returns true if the plugin was moved, false if not (bad delta / not found) 263 | local function MovePlugin(SettingsIni, PluginName, IndexDelta, EnabledPlugins) 264 | for idx, name in ipairs(EnabledPlugins) do 265 | if (name == PluginName) then 266 | local DstIdx = idx + IndexDelta 267 | if ((DstIdx < 1) or (DstIdx > #EnabledPlugins)) then 268 | LOGWARNING("WebAdmin: Requesting moving the plugin " .. PluginName .. " to invalid index " .. DstIdx .. " (max idx " .. #EnabledPlugins .. "); ignoring.") 269 | return false 270 | end 271 | EnabledPlugins[idx], EnabledPlugins[DstIdx] = EnabledPlugins[DstIdx], EnabledPlugins[idx] -- swap the two - we're expecting ony +1 / -1 moves 272 | SaveEnabledPlugins(SettingsIni, EnabledPlugins) 273 | return true 274 | end 275 | end 276 | 277 | -- Plugin not found: 278 | return false 279 | end 280 | 281 | 282 | 283 | 284 | 285 | --- Processes the actions specified by the request parameters 286 | -- Modifies EnabledPlugins directly to reflect the action 287 | -- Returns the notification text to be displayed at the top of the page 288 | local function ProcessRequestActions(SettingsIni, Request, EnabledPlugins) 289 | local PluginFolder = Request.PostParams["PluginFolder"] 290 | if (PluginFolder == nil) then 291 | -- PluginFolder was not provided, so there's no action to perform 292 | return 293 | end 294 | 295 | if (Request.PostParams["DisablePlugin"] ~= nil) then 296 | if (DisablePlugin(SettingsIni, PluginFolder, EnabledPlugins)) then 297 | return '

You disabled plugin: "' .. PluginFolder .. '"

' 298 | end 299 | elseif (Request.PostParams["EnablePlugin"] ~= nil) then 300 | if (EnablePlugin(SettingsIni, PluginFolder, EnabledPlugins)) then 301 | return '

You enabled plugin: "' .. PluginFolder .. '"

' 302 | end 303 | elseif (Request.PostParams["MoveUp"] ~= nil) then 304 | MovePlugin(SettingsIni, PluginFolder, -1, EnabledPlugins) 305 | elseif (Request.PostParams["MoveDown"] ~= nil) then 306 | MovePlugin(SettingsIni, PluginFolder, 1, EnabledPlugins) 307 | end 308 | end 309 | 310 | 311 | 312 | 313 | 314 | function HandleRequest_ManagePlugins(Request) 315 | local Content = "" 316 | 317 | if (Request.PostParams["reload"] ~= nil) then 318 | Content = Content .. "" 319 | Content = Content .. "

Reloading plugins... This can take a while depending on the plugins you're using.

" 320 | cRoot:Get():GetPluginManager():ReloadPlugins() 321 | return Content 322 | end 323 | 324 | local SettingsIni = cIniFile() 325 | SettingsIni:ReadFile("settings.ini") 326 | 327 | local EnabledPlugins = LoadEnabledPlugins(SettingsIni) 328 | 329 | local NotificationText = ProcessRequestActions(SettingsIni, Request, EnabledPlugins) 330 | Content = Content .. (NotificationText or "") 331 | 332 | if (g_NeedsReload) then 333 | Content = Content .. [[ 334 |
335 |

336 | You need to reload the plugins in order for the changes to take effect. 337 |   338 |

339 | ]] 340 | end 341 | 342 | Content = Content .. ListCurrentPlugins(EnabledPlugins) 343 | 344 | Content = Content .. [[
345 |

Reload

346 |
347 |

Click the reload button to reload all plugins. 348 |

349 |
]] 350 | return Content 351 | end 352 | -------------------------------------------------------------------------------- /web_utils.lua: -------------------------------------------------------------------------------- 1 | 2 | -- web_utils.lua 3 | 4 | -- Implements various utility functions related to the webadmin pages 5 | 6 | 7 | 8 | 9 | 10 | local ins = table.insert 11 | local con = table.concat 12 | 13 | 14 | 15 | 16 | 17 | --- Returns the HTML-formatted error message with the specified reason 18 | function HTMLError(a_Reason) 19 | return "" .. a_Reason .. "" 20 | end 21 | 22 | 23 | 24 | 25 | 26 | --- Returns a HTML string representing a form's Submit button 27 | -- The form has the subpage hidden field added, and any hidden values in the a_HiddenValues map 28 | -- All keys are left as-is, all values are HTML-escaped 29 | function GetFormButton(a_SubpageName, a_ButtonText, a_HiddenValues) 30 | -- Check params: 31 | assert(type(a_SubpageName) == "string") 32 | assert(type(a_ButtonText) == "string") 33 | assert(type(a_HiddenValues or {}) == "table") 34 | 35 | local res = {"") 40 | for k, v in pairs(a_HiddenValues) do 41 | ins(res, "") 46 | end 47 | 48 | return con(res) 49 | end 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /web_weather.lua: -------------------------------------------------------------------------------- 1 | local function AddWorldButtons(inName) 2 | result = "
" 3 | result = result .. "" 4 | result = result .. "" 5 | result = result .. "" 6 | result = result .. "" 7 | result = result .. "" 8 | result = result .. "" 9 | result = result .. "" 10 | result = result .. "
" 11 | return result 12 | end 13 | 14 | function HandleRequest_Weather(Request) 15 | if (Request.PostParams["WorldName"] ~= nil) then -- World is selected! 16 | workWorldName = Request.PostParams["WorldName"] 17 | workWorld = cRoot:Get():GetWorld(workWorldName) 18 | if( Request.PostParams["SetTime"] ~= nil ) then 19 | -- Times used replicate vanilla: http://minecraft.gamepedia.com/Day-night_cycle#Commands 20 | if (Request.PostParams["SetTime"] == "Dawn") then 21 | workWorld:SetTimeOfDay(0) 22 | LOG("Time set to dawn in " .. workWorldName) 23 | elseif (Request.PostParams["SetTime"] == "Day") then 24 | workWorld:SetTimeOfDay(1000) 25 | LOG("Time set to day in " .. workWorldName) 26 | elseif (Request.PostParams["SetTime"] == "Dusk") then 27 | workWorld:SetTimeOfDay(12000) 28 | LOG("Time set to dusk in " .. workWorldName) 29 | elseif (Request.PostParams["SetTime"] == "Night") then 30 | workWorld:SetTimeOfDay(14000) 31 | LOG("Time set to night in " .. workWorldName) 32 | elseif (Request.PostParams["SetTime"] == "Midnight") then 33 | workWorld:SetTimeOfDay(18000) 34 | LOG("Time set to midnight in " .. workWorldName) 35 | end 36 | end 37 | 38 | if (Request.PostParams["SetWeather"] ~= nil) then 39 | if (Request.PostParams["SetWeather"] == "Sun") then 40 | workWorld:SetWeather(wSunny) 41 | LOG("Weather changed to sun in " .. workWorldName) 42 | elseif (Request.PostParams["SetWeather"] == "Rain") then 43 | workWorld:SetWeather(wRain) 44 | LOG("Weather changed to rain in " .. workWorldName) 45 | elseif (Request.PostParams["SetWeather"] == "Storm") then 46 | workWorld:SetWeather(wStorm) 47 | LOG("Weather changed to storm in " .. workWorldName) 48 | end 49 | end 50 | end 51 | 52 | return GenerateContent() 53 | end 54 | 55 | function GenerateContent() 56 | local content = "

Operations:


" 57 | local worldCount = 0 58 | local AddWorldToTable = function( inWorld ) 59 | worldCount = worldCount + 1 60 | content = content.."
"..worldCount.."."..inWorld:GetName()..""..AddWorldButtons( inWorld:GetName() ).."
" 65 | cRoot:Get():ForEachWorld( AddWorldToTable ) 66 | if( worldCount == 0 ) then 67 | content = content.."" 68 | end 69 | content = content.."
No worlds! O_O
" 70 | 71 | return content 72 | end 73 | -------------------------------------------------------------------------------- /web_whitelist.lua: -------------------------------------------------------------------------------- 1 | 2 | -- web_whitelist.lua 3 | 4 | -- Implements the webadmin page for handling whitelist 5 | 6 | 7 | 8 | 9 | 10 | local ins = table.insert 11 | local con = table.concat 12 | 13 | 14 | 15 | 16 | 17 | --- Returns the HTML code for an action button specific for the specified player 18 | -- a_Player should be the player's description, as returned by ListWhitelistedPlayers() 19 | local function getPlayerActionButton(a_Action, a_Caption, a_Player) 20 | -- Check params: 21 | assert(type(a_Action) == "string") 22 | assert(type(a_Caption) == "string") 23 | assert(type(a_Player) == "table") 24 | 25 | -- Put together the code for the form: 26 | local res = { "
") 33 | return con(res) 34 | end 35 | 36 | 37 | 38 | 39 | 40 | --- Returns the table row for a single player 41 | -- a_Player should be the player's description, as returned by ListWhitelistedPlayers() 42 | local function getPlayerRow(a_Player) 43 | -- Check the params: 44 | assert(type(a_Player) == "table") 45 | 46 | -- Put together the code for the entire row: 47 | local res = { "" } 48 | ins(res, cWebAdmin:GetHTMLEscapedString(a_Player.Name)) 49 | ins(res, "") 50 | ins(res, os.date("%Y-%m-%d %H:%M:%S", a_Player.Timestamp or 0)) 51 | ins(res, "") 52 | ins(res, cWebAdmin:GetHTMLEscapedString(a_Player.WhitelistedBy or "")) 53 | ins(res, "") 54 | ins(res, getPlayerActionButton("delplayer", "Remove", a_Player)) 55 | ins(res, "") 56 | return con(res) 57 | end 58 | 59 | 60 | 61 | 62 | 63 | --- Returns the list of whitelisted players 64 | local function showList(a_Request) 65 | -- Show the whitelist status - enabled or disabled: 66 | local res = { "") 73 | 74 | -- Add the form to whitelist players: 75 | ins(res, "") 78 | 79 | -- Show the whitelisted players: 80 | local players = ListWhitelistedPlayers() 81 | if (players[1] == nil) then 82 | ins(res, "") 83 | else 84 | ins(res, "") 85 | for _, player in ipairs(players) do 86 | ins(res, getPlayerRow(player)) 87 | end 88 | end 89 | ins(res, "
" } 67 | if (IsWhitelistEnabled()) then 68 | ins(res, "Whitelist is ENABLED
") 69 | else 70 | ins(res, "Whitelist is DISABLED
") 71 | end 72 | ins(res, "


Add player to whitelist: ") 76 | ins(res, "
") 77 | ins(res, "


There are no players in the whitelist.
NameDate whitelistedWhitelisted byAction
") 90 | 91 | return con(res) 92 | end 93 | 94 | 95 | 96 | 97 | 98 | --- Processes the "addplayer" action, whitelisting the specified player and returning the player list 99 | local function showAddPlayer(a_Request) 100 | -- Check HTML params: 101 | local playerName = a_Request.PostParams["playername"] or "" 102 | if (playerName == "") then 103 | return HTMLError("Cannot add player, bad name") .. showList(a_Request) 104 | end 105 | 106 | -- Whitelist the player: 107 | AddPlayerToWhitelist(playerName, "") 108 | 109 | -- Redirect back to the whitelist: 110 | return showList(a_Request) 111 | end 112 | 113 | 114 | 115 | 116 | 117 | --- Processes the "delplayer" action, unwhitelisting the specified player and returning the player list 118 | local function showDelPlayer(a_Request) 119 | -- Check HTML params: 120 | local playerName = a_Request.PostParams["playername"] or "" 121 | if (playerName == "") then 122 | return HTMLError("Cannot remove player, bad name") .. showList(a_Request) 123 | end 124 | 125 | -- Whitelist the player: 126 | RemovePlayerFromWhitelist(playerName) 127 | 128 | -- Redirect back to the whitelist: 129 | return showList(a_Request) 130 | end 131 | 132 | 133 | 134 | 135 | 136 | --- Processes the "disable" action, disabling the whitelist and returning the player list 137 | local function showDisableWhitelist(a_Request) 138 | WhitelistDisable() 139 | return showList(a_Request) 140 | end 141 | 142 | 143 | 144 | 145 | 146 | --- Processes the "disable" action, disabling the whitelist and returning the player list 147 | local function showEnableWhitelist(a_Request) 148 | WhitelistEnable() 149 | return showList(a_Request) 150 | end 151 | 152 | 153 | 154 | 155 | 156 | --- The table of all actions supported by this web tab: 157 | local g_ActionHandlers = 158 | { 159 | [""] = showList, 160 | ["addplayer"] = showAddPlayer, 161 | ["delplayer"] = showDelPlayer, 162 | ["disable"] = showDisableWhitelist, 163 | ["enable"] = showEnableWhitelist, 164 | } 165 | 166 | 167 | 168 | 169 | 170 | function HandleRequest_WhiteList(a_Request) 171 | local action = a_Request.PostParams["action"] or "" 172 | local handler = g_ActionHandlers[action] 173 | if (handler == nil) then 174 | return HTMLError("Error in whitelist processing: no action handler found for action \"" .. action .. "\"") 175 | end 176 | return handler(a_Request) 177 | end 178 | 179 | 180 | 181 | 182 | -------------------------------------------------------------------------------- /whitelist.lua: -------------------------------------------------------------------------------- 1 | 2 | -- whitelist.lua 3 | 4 | -- Implements the whitelist-related commands, console commands, API and storage 5 | 6 | 7 | 8 | 9 | 10 | --- The SQLite handle to the whitelist database: 11 | local WhitelistDB 12 | 13 | --- Global flag whether whitelist is enabled: 14 | local g_IsWhitelistEnabled = false 15 | 16 | 17 | 18 | 19 | 20 | --- Loads the config from the DB 21 | -- If any value cannot be read, it is kept unchanged 22 | local function LoadConfig() 23 | -- Read the g_IsWhitelistEnabled value: 24 | WhitelistDB:ExecuteStatement( 25 | "SELECT Value FROM WhitelistConfig WHERE Name='isEnabled'", 26 | {}, 27 | function (a_Val) 28 | g_IsWhitelistEnabled = (a_Val["Value"] == "true") 29 | end 30 | ) 31 | end 32 | 33 | 34 | 35 | 36 | 37 | --- Saves the current config into the DB 38 | local function SaveConfig() 39 | -- Remove the value, if it exists: 40 | WhitelistDB:ExecuteStatement( 41 | "DELETE FROM WhitelistConfig WHERE Name='isEnabled'", {} 42 | ) 43 | 44 | -- Insert the current value: 45 | WhitelistDB:ExecuteStatement( 46 | "INSERT INTO WhitelistConfig(Name, Value) VALUES ('isEnabled', ?)", 47 | { tostring(g_IsWhitelistEnabled) } 48 | ) 49 | end 50 | 51 | 52 | 53 | 54 | 55 | --- API: Adds the specified player to the whitelist 56 | -- Resolves the player UUID, if needed, but only through cache, not to block 57 | -- Returns true on success, false and optional error message on failure 58 | function AddPlayerToWhitelist(a_PlayerName, a_WhitelistedBy) 59 | -- Check params: 60 | assert(type(a_PlayerName) == "string") 61 | assert(type(a_WhitelistedBy) == "string") 62 | 63 | -- Resolve the player name to OfflineUUID and possibly OnlineUUID (if server is in online mode): 64 | local UUID = "" 65 | if (cRoot:Get():GetServer():ShouldAuthenticate()) then 66 | UUID = cMojangAPI:GetUUIDFromPlayerName(a_PlayerName, true) 67 | -- If the UUID cannot be resolved, leave it as an empty string, it will be resolved on next startup / eventually 68 | end 69 | local OfflineUUID = cClientHandle:GenerateOfflineUUID(a_PlayerName) 70 | 71 | -- Insert into DB: 72 | return WhitelistDB:ExecuteStatement( 73 | "INSERT INTO WhitelistNames (Name, UUID, OfflineUUID, Timestamp, WhitelistedBy) VALUES (?, ?, ?, ?, ?)", 74 | { 75 | a_PlayerName, UUID, OfflineUUID, 76 | os.time(), a_WhitelistedBy, 77 | } 78 | ) 79 | end 80 | 81 | 82 | 83 | 84 | 85 | --- API: Checks if the player is whitelisted 86 | -- Returns true if whitelisted, false if not 87 | -- Uses UUID for the check, and the playername with an empty UUID for a secondary check 88 | function IsPlayerWhitelisted(a_PlayerUUID, a_PlayerName) 89 | -- Check params: 90 | assert(type(a_PlayerUUID) == "string") 91 | assert(type(a_PlayerName) == "string") 92 | local UUID = a_PlayerUUID 93 | if (UUID == "") then 94 | -- There is no UUID supplied for the player, do not search by the UUID by using a dummy impossible value: 95 | UUID = "DummyImpossibleValue" 96 | end 97 | 98 | -- Query the DB: 99 | local offlineUUID = cClientHandle:GenerateOfflineUUID(a_PlayerName) 100 | local isWhitelisted 101 | assert(WhitelistDB:ExecuteStatement( 102 | [[ 103 | SELECT Name FROM WhitelistNames WHERE 104 | (UUID = ?) OR 105 | (OfflineUUID = ?) OR 106 | ((UUID = '') AND (Name = ?)) 107 | ]], 108 | { UUID, offlineUUID, a_PlayerName }, 109 | function (a_Row) 110 | isWhitelisted = true 111 | end 112 | )) 113 | return isWhitelisted 114 | end 115 | 116 | 117 | 118 | 119 | 120 | --- API: Returns true if whitelist is enabled 121 | function IsWhitelistEnabled() 122 | return g_IsWhitelistEnabled 123 | end 124 | 125 | 126 | 127 | 128 | 129 | --- Returns a sorted array-table of all whitelisted players' names 130 | function ListWhitelistedPlayerNames() 131 | local res = {} 132 | WhitelistDB:ExecuteStatement( 133 | "SELECT Name FROM WhitelistNames ORDER BY Name", {}, 134 | function (a_Columns) 135 | table.insert(res, a_Columns["Name"]) 136 | end 137 | ) 138 | return res 139 | end 140 | 141 | 142 | 143 | 144 | 145 | --- Returns an array-table of all whitelisted players, sorted by player name 146 | -- Each item is a table with the Name, OnlineUUID, OfflineUUID, Date and WhitelistedBy values 147 | function ListWhitelistedPlayers() 148 | local res = {} 149 | WhitelistDB:ExecuteStatement( 150 | "SELECT * FROM WhitelistNames ORDER BY Name", {}, 151 | function (a_Columns) 152 | table.insert(res, a_Columns) 153 | end 154 | ) 155 | return res 156 | end 157 | 158 | 159 | 160 | 161 | 162 | --- API: Removes the specified player from the whitelist 163 | -- No action if the player is not whitelisted 164 | -- Returns true on success, false and optional error message on failure 165 | function RemovePlayerFromWhitelist(a_PlayerName) 166 | -- Check params: 167 | assert(type(a_PlayerName) == "string") 168 | 169 | -- Remove from the DB: 170 | return WhitelistDB:ExecuteStatement( 171 | "DELETE FROM WhitelistNames WHERE Name = ?", 172 | { a_PlayerName } 173 | ) 174 | end 175 | 176 | 177 | 178 | 179 | 180 | --- API: Disables the whitelist 181 | -- After this call, any player can connect to the server 182 | function WhitelistDisable() 183 | g_IsWhitelistEnabled = false 184 | SaveConfig() 185 | end 186 | 187 | 188 | 189 | 190 | 191 | --- API: Enables the whitelist 192 | -- After this call, only whitelisted players can connect to the server 193 | function WhitelistEnable() 194 | g_IsWhitelistEnabled = true 195 | SaveConfig() 196 | end 197 | 198 | 199 | 200 | 201 | 202 | --- Resolves the UUIDs for players that don't have their UUIDs in the DB 203 | -- This may happen when whitelisting a player who never connected to the server and thus is not yet cached in the UUID lookup 204 | local function ResolveUUIDs() 205 | -- If the server is offline, bail out: 206 | if not(cRoot:Get():GetServer():ShouldAuthenticate()) then 207 | return 208 | end 209 | 210 | -- Collect the names of players without their UUIDs: 211 | local NamesToResolve = {} 212 | WhitelistDB:ExecuteStatement( 213 | "SELECT Name From WhitelistNames WHERE UUID = ''", {}, 214 | function (a_Columns) 215 | table.insert(NamesToResolve, a_Columns["PlayerName"]) 216 | end 217 | ) 218 | if (#NamesToResolve == 0) then 219 | return 220 | end 221 | 222 | -- Resolve the names: 223 | LOGINFO("Resolving player UUIDs in the whitelist from Mojang servers. This may take a while...") 224 | local ResolvedNames = cMojangAPI:GetUUIDsFromPlayerNames(NamesToResolve) 225 | LOGINFO("Resolving finished.") 226 | 227 | -- Update the names in the DB: 228 | for name, uuid in pairs(ResolvedNames) do 229 | WhitelistDB:ExecuteStatement( 230 | "UPDATE WhitelistNames SET UUID = ? WHERE PlayerName = ?", 231 | { uuid, name } 232 | ) 233 | end 234 | end 235 | 236 | 237 | 238 | 239 | 240 | --- If whitelist is disabled, sends a message to the specified player (or console if nil) 241 | local function NotifyWhitelistStatus(a_Player) 242 | -- Nothing to notify if the whitelist is enabled: 243 | if (g_IsWhitelistEnabled) then 244 | return 245 | end 246 | 247 | -- Send the notification msg to player / console: 248 | if (a_Player == nil) then 249 | LOG("Note: Whitelist is disabled. Use the \"whitelist on\" command to enable.") 250 | else 251 | a_Player:SendMessageInfo("Note: Whitelist is disabled. Use the \"/whitelist on\" command to enable.") 252 | end 253 | end 254 | 255 | 256 | 257 | 258 | 259 | --- If whitelist is empty, sends a notification to the specified player (or console if nil) 260 | -- Assumes that the whitelist is enabled 261 | local function NotifyWhitelistEmpty(a_Player) 262 | -- Check if whitelist is empty: 263 | local numWhitelisted 264 | local isSuccess, msg = WhitelistDB:ExecuteStatement( 265 | "SELECT COUNT(*) AS c FROM WhitelistNames", 266 | {}, 267 | function (a_Values) 268 | numWhitelisted = a_Values["c"] 269 | end 270 | ) 271 | if (not (isSuccess) or (type(numWhitelisted) ~= "number") or (numWhitelisted > 0)) then 272 | return 273 | end 274 | 275 | -- Send the notification msg to player / console: 276 | if (a_Player == nil) then 277 | LOGINFO("Note: Whitelist is empty. No player can connect to the server now. Use the \"whitelist add\" command to add players to whitelist.") 278 | else 279 | a_Player:SendMessageInfo("Note: Whitelist is empty. No player can connect to the server now. Use the \"/whitelist add\" command to add players to whitelist.") 280 | end 281 | end 282 | 283 | 284 | 285 | 286 | 287 | function HandleWhitelistAddCommand(a_Split, a_Player) 288 | -- Check params: 289 | if (a_Split[3] == nil) then 290 | SendMessage(a_Player, "Usage: " .. a_Split[1] .. " add ") 291 | return true 292 | end 293 | local playerName = a_Split[3] 294 | 295 | -- Add the player to the whitelist: 296 | local isSuccess, msg = AddPlayerToWhitelist(playerName, a_Player:GetName()) 297 | if not(isSuccess) then 298 | SendMessageFailure(a_Player, "Cannot whitelist " .. playerName .. ": " .. (msg or "")) 299 | return true 300 | end 301 | 302 | -- Notify success: 303 | LOGINFO(a_Player:GetName() .. " added " .. playerName .. " to whitelist.") 304 | SendMessageSuccess(a_Player, "Successfully added " .. playerName .. " to whitelist.") 305 | NotifyWhitelistStatus(a_Player) 306 | return true 307 | end 308 | 309 | 310 | 311 | 312 | 313 | function HandleWhitelistListCommand(a_Split, a_Player) 314 | if (IsWhitelistEnabled()) then 315 | a_Player:SendMessageSuccess("Whitelist is enabled") 316 | else 317 | a_Player:SendMessageSuccess("Whitelist is disabled") 318 | end 319 | local players = ListWhitelistedPlayerNames() 320 | table.sort(players) 321 | a_Player:SendMessageSuccess(table.concat(players, ", ")) 322 | return true 323 | end 324 | 325 | 326 | 327 | 328 | 329 | function HandleWhitelistOffCommand(a_Split, a_Player) 330 | g_IsWhitelistEnabled = true 331 | SaveConfig() 332 | a_Player:SendMessageSuccess("Whitelist is disabled.") 333 | return true 334 | end 335 | 336 | 337 | 338 | 339 | 340 | function HandleWhitelistOnCommand(a_Split, a_Player) 341 | g_IsWhitelistEnabled = true 342 | SaveConfig() 343 | a_Player:SendMessageSuccess("Whitelist is enabled.") 344 | NotifyWhitelistEmpty(a_Player) 345 | return true 346 | end 347 | 348 | 349 | 350 | 351 | 352 | function HandleWhitelistRemoveCommand(a_Split, a_Player) 353 | -- Check params: 354 | if ((a_Split[3] == nil) or (a_Split[4] ~= nil)) then 355 | SendMessage(a_Player, "Usage: " .. a_Split[1] .. " remove ") 356 | return true 357 | end 358 | local playerName = a_Split[3] 359 | 360 | -- Remove the player from the whitelist: 361 | local isSuccess, msg = RemovePlayerFromWhitelist(playerName) 362 | if not(isSuccess) then 363 | SendMessageFailure(a_Player, "Cannot unwhitelist " .. playerName .. ": " .. (msg or "")) 364 | return true 365 | end 366 | 367 | -- Notify success: 368 | LOGINFO(a_Player:GetName() .. " removed " .. playerName .. " from whitelist.") 369 | SendMessageSuccess(a_Player, "Removed " .. playerName .. " from whitelist.") 370 | NotifyWhitelistStatus(a_Player) 371 | return true 372 | end 373 | 374 | 375 | 376 | 377 | 378 | function HandleConsoleWhitelistAdd(a_Split) 379 | -- Check params: 380 | if (a_Split[3] == nil) then 381 | return true, "Usage: " .. a_Split[1] .. " add " 382 | end 383 | local playerName = a_Split[3] 384 | 385 | -- Whitelist the player: 386 | local isSuccess, msg = AddPlayerToWhitelist(playerName, "") 387 | if not(isSuccess) then 388 | return true, "Cannot whitelist " .. playerName .. ": " .. (msg or "") 389 | end 390 | 391 | -- Notify success: 392 | NotifyWhitelistStatus() 393 | return true, "You added " .. playerName .. " to whitelist." 394 | end 395 | 396 | 397 | 398 | 399 | 400 | function HandleConsoleWhitelistList(a_Split) 401 | local status 402 | if (g_IsWhitelistEnabled) then 403 | status = "Whitelist is ENABLED.\n" 404 | else 405 | status = "Whitelist is DISABLED.\n" 406 | end 407 | local players = ListWhitelistedPlayerNames() 408 | if (players[1] == nil) then 409 | return true, status .. "The whitelist is empty." 410 | else 411 | return true, status .. "Whitelisted players: " .. table.concat(players, ", ") 412 | end 413 | end 414 | 415 | 416 | 417 | 418 | 419 | function HandleConsoleWhitelistOff(a_Split) 420 | WhitelistDisable() 421 | return true, "Whitelist is disabled" 422 | end 423 | 424 | 425 | 426 | 427 | 428 | function HandleConsoleWhitelistOn(a_Split) 429 | WhitelistEnable() 430 | NotifyWhitelistEmpty() 431 | return true, "Whitelist is enabled" 432 | end 433 | 434 | 435 | 436 | 437 | 438 | function HandleConsoleWhitelistRemove(a_Split) 439 | -- Check params: 440 | if ((a_Split[3] == nil) or (a_Split[4] ~= nil)) then 441 | return true, "Usage: " .. a_Split[1] .. " remove " 442 | end 443 | local playerName = a_Split[3] 444 | 445 | -- Unwhitelist the player: 446 | local isSuccess, msg = RemovePlayerFromWhitelist(playerName) 447 | if not(isSuccess) then 448 | return true, "Cannot unwhitelist " .. playerName .. ": " .. (msg or "") 449 | end 450 | 451 | -- Notify success: 452 | NotifyWhitelistStatus() 453 | return true, "You removed " .. playerName .. " from whitelist." 454 | end 455 | 456 | 457 | 458 | 459 | 460 | --- Opens the whitelist DB and checks that all the tables have the needed structure 461 | local function InitializeDB() 462 | -- Open the DB: 463 | local ErrMsg 464 | WhitelistDB, ErrMsg = NewSQLiteDB("whitelist.sqlite") 465 | if not(WhitelistDB) then 466 | LOGWARNING("Cannot open the whitelist database, whitelist not available. SQLite: " .. (ErrMsg or "")) 467 | error(ErrMsg) 468 | end 469 | 470 | -- Define the needed structure: 471 | local nameListColumns = 472 | { 473 | "Name", 474 | "UUID", 475 | "OfflineUUID", 476 | "Timestamp", 477 | "WhitelistedBy", 478 | } 479 | local configColumns = 480 | { 481 | "Name TEXT PRIMARY KEY", 482 | "Value" 483 | } 484 | 485 | -- Check structure: 486 | if ( 487 | not(WhitelistDB:CreateDBTable("WhitelistNames", nameListColumns)) or 488 | not(WhitelistDB:CreateDBTable("WhitelistConfig", configColumns)) 489 | ) then 490 | LOGWARNING("Cannot initialize the whitelist database, whitelist not available.") 491 | error("Whitelist DB failure") 492 | end 493 | 494 | -- Load the config: 495 | LoadConfig() 496 | end 497 | 498 | 499 | 500 | 501 | 502 | 503 | --- Callback for the HOOK_PLAYER_JOINED hook 504 | -- Kicks the player if they are whitelisted by UUID or Name 505 | -- Also sets the UUID for the player in the DB, if not present 506 | local function OnPlayerJoined(a_Player) 507 | local UUID = a_Player:GetUUID() 508 | local Name = a_Player:GetName() 509 | 510 | -- Update the UUID in the DB, if empty: 511 | assert(WhitelistDB:ExecuteStatement( 512 | "UPDATE WhitelistNames SET UUID = ? WHERE ((UUID = '') AND (Name = ?))", 513 | { UUID, Name } 514 | )) 515 | 516 | -- If whitelist is not enabled, bail out: 517 | if not(g_IsWhitelistEnabled) then 518 | return false 519 | end 520 | 521 | -- Kick if not whitelisted: 522 | local isWhitelisted = IsPlayerWhitelisted(UUID, Name) 523 | if not(isWhitelisted) then 524 | a_Player:GetClientHandle():Kick("You are not on the whitelist") 525 | return true 526 | end 527 | end 528 | 529 | 530 | 531 | 532 | 533 | --- Init function to be called upon plugin startup 534 | -- Opens the whitelist DB and refreshes the player names stored within 535 | function InitializeWhitelist() 536 | -- Initialize the Whitelist DB: 537 | InitializeDB() 538 | ResolveUUIDs() 539 | 540 | -- Make a note in the console if the whitelist is enabled and empty: 541 | if (g_IsWhitelistEnabled) then 542 | NotifyWhitelistEmpty() 543 | end 544 | 545 | -- Add a hook to filter out non-whitelisted players: 546 | cPluginManager:AddHook(cPluginManager.HOOK_PLAYER_JOINED, OnPlayerJoined) 547 | end 548 | --------------------------------------------------------------------------------