Moved ProtectionAreas into a separate repository
This commit is contained in:
parent
8326aff5b4
commit
0074432ccd
@ -3,4 +3,5 @@
|
|||||||
|
|
||||||
:: Clones the base plugins from their respective repos into proper folders (./MCServer/Plugins
|
:: Clones the base plugins from their respective repos into proper folders (./MCServer/Plugins
|
||||||
|
|
||||||
git clone https://github.com/mc-server/Core.git ./MCServer/Plugins/Core
|
git clone https://github.com/mc-server/Core.git ./MCServer/Plugins/Core
|
||||||
|
git clone https://github.com/mc-server/ProtectionAreas.git ./MCServer/Plugins/ProtectionAreas
|
3
MCServer/Plugins/.gitignore
vendored
3
MCServer/Plugins/.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
Core
|
Core
|
||||||
|
ProtectionAreas
|
@ -1,322 +0,0 @@
|
|||||||
|
|
||||||
-- CommandHandlers.lua
|
|
||||||
-- Defines the individual command handlers
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function InitializeCommandHandlers()
|
|
||||||
local PlgMgr = cRoot:Get():GetPluginManager();
|
|
||||||
for idx, Cmd in ipairs(CommandReg()) do
|
|
||||||
PlgMgr:BindCommand(Cmd[2], Cmd[3], Cmd[1], Cmd[4]);
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- Handles the ProtAdd command
|
|
||||||
function HandleAddArea(a_Split, a_Player)
|
|
||||||
-- Command syntax: ProtAdd username1 [username2] [username3] ...
|
|
||||||
if (#a_Split < 2) then
|
|
||||||
a_Player:SendMessage(g_Msgs.ErrExpectedListOfUsernames);
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Get the cuboid that the player had selected
|
|
||||||
local CmdState = GetCommandStateForPlayer(a_Player);
|
|
||||||
if (CmdState == nil) then
|
|
||||||
a_Player:SendMessage(g_Msgs.ErrCmdStateNilAddArea);
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
local Cuboid = CmdState:GetCurrentCuboid();
|
|
||||||
if (Cuboid == nil) then
|
|
||||||
a_Player:SendMessage(g_Msgs.ErrNoAreaWanded);
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Put all allowed players into a table:
|
|
||||||
AllowedNames = {};
|
|
||||||
for i = 2, #a_Split do
|
|
||||||
table.insert(AllowedNames, a_Split[i]);
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Add the area to the storage
|
|
||||||
local AreaID = g_Storage:AddArea(Cuboid, a_Player:GetWorld():GetName(), a_Player:GetName(), AllowedNames);
|
|
||||||
a_Player:SendMessage(string.format(g_Msgs.AreaAdded, AreaID));
|
|
||||||
|
|
||||||
-- Reload all currently logged in players
|
|
||||||
ReloadAllPlayersInWorld(a_Player:GetWorld():GetName());
|
|
||||||
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function HandleAddAreaCoords(a_Split, a_Player)
|
|
||||||
-- Command syntax: ProtAddCoords x1 z1 x2 z2 username1 [username2] [username3] ...
|
|
||||||
if (#a_Split < 6) then
|
|
||||||
a_Player:SendMessage(g_Msgs.ErrExpectedCoordsUsernames);
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Convert the coords to a cCuboid
|
|
||||||
local x1, z1 = tonumber(a_Split[2]), tonumber(a_Split[3]);
|
|
||||||
local x2, z2 = tonumber(a_Split[4]), tonumber(a_Split[5]);
|
|
||||||
if ((x1 == nil) or (z1 == nil) or (x2 == nil) or (z2 == nil)) then
|
|
||||||
a_Player:SendMessage(g_Msgs.ErrParseCoords);
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
local Cuboid = cCuboid(x1, 0, z1, x2, 255, z1);
|
|
||||||
Cuboid:Sort();
|
|
||||||
|
|
||||||
-- Put all allowed players into a table:
|
|
||||||
AllowedNames = {};
|
|
||||||
for i = 6, #a_Split do
|
|
||||||
table.insert(AllowedNames, a_Split[i]);
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Add the area to the storage
|
|
||||||
local AreaID = g_Storage:AddArea(Cuboid, a_Player:GetWorld():GetName(), a_Player:GetName(), AllowedNames);
|
|
||||||
a_Player:SendMessage(string.format(g_Msgs.AreaAdded, AreaID));
|
|
||||||
|
|
||||||
-- Reload all currently logged in players
|
|
||||||
ReloadAllPlayersInWorld(a_Player:GetWorld():GetName());
|
|
||||||
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function HandleAddAreaUser(a_Split, a_Player)
|
|
||||||
-- Command syntax: ProtAddUser AreaID username1 [username2] [username3] ...
|
|
||||||
if (#a_Split < 3) then
|
|
||||||
a_Player:SendMessage(g_Msgs.ErrExpectedAreaIDUsernames);
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Put all allowed players into a table:
|
|
||||||
AllowedNames = {};
|
|
||||||
for i = 3, #a_Split do
|
|
||||||
table.insert(AllowedNames, a_Split[i]);
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Add the area to the storage
|
|
||||||
if (not(g_Storage:AddAreaUsers(
|
|
||||||
tonumber(a_Split[2]), a_Player:GetWorld():GetName(), a_Player:GetName(), AllowedNames))
|
|
||||||
) then
|
|
||||||
LOGWARNING("g_Storage:AddAreaUsers failed");
|
|
||||||
a_Player:SendMessage(g_Msgs.ErrDBFailAddUsers);
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
if (#AllowedNames == 0) then
|
|
||||||
a_Player:SendMessage(g_Msgs.AllUsersAlreadyAllowed);
|
|
||||||
else
|
|
||||||
a_Player:SendMessage(string.format(g_Msgs.UsersAdded, table.concat(AllowedNames, ", ")));
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Reload all currently logged in players
|
|
||||||
ReloadAllPlayersInWorld(a_Player:GetWorld():GetName());
|
|
||||||
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function HandleDelArea(a_Split, a_Player)
|
|
||||||
-- Command syntax: ProtDelArea AreaID
|
|
||||||
if (#a_Split ~= 2) then
|
|
||||||
a_Player:SendMessage(g_Msgs.ErrExpectedAreaID);
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Parse the AreaID
|
|
||||||
local AreaID = tonumber(a_Split[2]);
|
|
||||||
if (AreaID == nil) then
|
|
||||||
a_Player:SendMessage(g_Msgs.ErrParseAreaID);
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Delete the area
|
|
||||||
g_Storage:DelArea(a_Player:GetWorld():GetName(), AreaID);
|
|
||||||
|
|
||||||
a_Player:SendMessage(string.format(g_Msgs.AreaDeleted, AreaID));
|
|
||||||
-- Reload all currently logged in players
|
|
||||||
ReloadAllPlayersInWorld(a_Player:GetWorld():GetName());
|
|
||||||
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function HandleGiveWand(a_Split, a_Player)
|
|
||||||
local NumGiven = a_Player:GetInventory():AddItem(cConfig:GetWandItem());
|
|
||||||
if (NumGiven == 1) then
|
|
||||||
a_Player:SendMessage(g_Msgs.WandGiven);
|
|
||||||
else
|
|
||||||
a_Player:SendMessage(g_Msgs.ErrNoSpaceForWand);
|
|
||||||
end
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function HandleListAreas(a_Split, a_Player)
|
|
||||||
-- Command syntax: ProtListAreas [x, z]
|
|
||||||
|
|
||||||
local x, z;
|
|
||||||
if (#a_Split == 1) then
|
|
||||||
-- Get the last "wanded" coord
|
|
||||||
local CmdState = GetCommandStateForPlayer(a_Player);
|
|
||||||
if (CmdState == nil) then
|
|
||||||
a_Player:SendMessage(g_Msgs.ErrCmdStateNilListAreas);
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
x, z = CmdState:GetLastCoords();
|
|
||||||
if ((x == nil) or (z == nil)) then
|
|
||||||
a_Player:SendMessage(g_Msgs.ErrListNotWanded);
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
elseif (#a_Split == 3) then
|
|
||||||
-- Parse the coords from the command params
|
|
||||||
x = tonumber(a_Split[2]);
|
|
||||||
z = tonumber(a_Split[3]);
|
|
||||||
if ((x == nil) or (z == nil)) then
|
|
||||||
a_Player:SendMessage(g_Msgs.ErrParseCoordsListAreas);
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
else
|
|
||||||
-- Wrong number of params, report back to the user
|
|
||||||
a_Player:SendMessage(g_Msgs.ErrSyntaxErrorListAreas);
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
|
|
||||||
a_Player:SendMessage(string.format(g_Msgs.ListAreasHeader, x, z));
|
|
||||||
|
|
||||||
-- List areas intersecting the coords
|
|
||||||
local PlayerName = a_Player:GetName();
|
|
||||||
local WorldName = a_Player:GetWorld():GetName();
|
|
||||||
g_Storage:ForEachArea(x, z, WorldName,
|
|
||||||
function(AreaID, MinX, MinZ, MaxX, MaxZ, CreatorName)
|
|
||||||
local Coords = string.format("%s: {%d, %d} - {%d, %d} ", AreaID, MinX, MinZ, MaxX, MaxZ);
|
|
||||||
local Allowance;
|
|
||||||
if (g_Storage:IsAreaAllowed(AreaID, PlayerName, WorldName)) then
|
|
||||||
Allowance = g_Msgs.AreaAllowed;
|
|
||||||
else
|
|
||||||
Allowance = g_Msgs.AreaNotAllowed;
|
|
||||||
end
|
|
||||||
a_Player:SendMessage(string.format(g_Msgs.ListAreasRow, Coords, Allowance, CreatorName));
|
|
||||||
end
|
|
||||||
);
|
|
||||||
|
|
||||||
a_Player:SendMessage(g_Msgs.ListAreasFooter);
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- Lists all allowed users for a particular area
|
|
||||||
function HandleListUsers(a_Split, a_Player)
|
|
||||||
-- Command syntax: ProtListUsers AreaID
|
|
||||||
if (#a_Split ~= 2) then
|
|
||||||
a_Player:SendMessage(g_Msgs.ErrExpectedAreaID);
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Get the general info about the area
|
|
||||||
local AreaID = a_Split[2];
|
|
||||||
local WorldName = a_Player:GetWorld():GetName();
|
|
||||||
local MinX, MinZ, MaxX, MaxZ, CreatorName = g_Storage:GetArea(AreaID, WorldName);
|
|
||||||
if (MinX == nil) then
|
|
||||||
a_Player:SendMessage(string.format(g_Msgs.ErrNoSuchArea, AreaID));
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Send the header
|
|
||||||
a_Player:SendMessage(string.format(g_Msgs.ListUsersHeader, AreaID, MinX, MinZ, MaxX, MaxZ, CreatorName));
|
|
||||||
|
|
||||||
-- List and count the allowed users
|
|
||||||
local NumUsers = 0;
|
|
||||||
g_Storage:ForEachUserInArea(AreaID, WorldName,
|
|
||||||
function(UserName)
|
|
||||||
a_Player:SendMessage(string.format(g_Msgs.ListUsersRow, UserName));
|
|
||||||
NumUsers = NumUsers + 1;
|
|
||||||
end
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Send the footer
|
|
||||||
a_Player:SendMessage(string.format(g_Msgs.ListUsersFooter, AreaID, NumUsers));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function HandleRemoveUser(a_Split, a_Player)
|
|
||||||
-- Command syntax: ProtRemUser AreaID UserName
|
|
||||||
if (#a_Split ~= 3) then
|
|
||||||
a_Player:SendMessage(g_Msgs.ErrExpectedAreaIDUserName);
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Parse the AreaID
|
|
||||||
local AreaID = tonumber(a_Split[2]);
|
|
||||||
if (AreaID == nil) then
|
|
||||||
a_Player:SendMessage(g_Msgs.ErrParseAreaID);
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Remove the user from the DB
|
|
||||||
local UserName = a_Split[3];
|
|
||||||
g_Storage:RemoveUser(AreaID, UserName, a_Player:GetWorld():GetName());
|
|
||||||
|
|
||||||
-- Send confirmation
|
|
||||||
a_Player:SendMessage(string.format(g_Msgs.RemovedUser, UserName, AreaID));
|
|
||||||
|
|
||||||
-- Reload all currently logged in players
|
|
||||||
ReloadAllPlayersInWorld(a_Player:GetWorld():GetName());
|
|
||||||
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function HandleRemoveUserAll(a_Split, a_Player)
|
|
||||||
-- Command syntax: ProtRemUserAll UserName
|
|
||||||
if (#a_Split ~= 2) then
|
|
||||||
a_Player:SendMessage(g_Msgs.ErrExpectedUserName);
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Remove the user from the DB
|
|
||||||
g_Storage:RemoveUserAll(a_Split[2], a_Player:GetWorld():GetName());
|
|
||||||
|
|
||||||
-- Send confirmation
|
|
||||||
a_Player:SendMessage(string.format(g_Msgs.RemovedUserAll, UserName));
|
|
||||||
|
|
||||||
-- Reload all currently logged in players
|
|
||||||
ReloadAllPlayersInWorld(a_Player:GetWorld():GetName());
|
|
||||||
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,121 +0,0 @@
|
|||||||
|
|
||||||
-- CommandState.lua
|
|
||||||
|
|
||||||
-- Implements the cCommandState class representing a command state for each VIP player
|
|
||||||
|
|
||||||
--[[
|
|
||||||
The command state holds internal info, such as the coords they selected using the wand
|
|
||||||
The command state needs to be held in a per-entity manner, so that we can support multiple logins
|
|
||||||
from the same account (just for the fun of it)
|
|
||||||
The OOP class implementation follows the PiL 16.1
|
|
||||||
|
|
||||||
Also, a global table g_CommandStates is the map of PlayerEntityID -> cCommandState
|
|
||||||
--]]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
cCommandState = {
|
|
||||||
-- Default coords
|
|
||||||
m_Coords1 = {x = 0, z = 0}; -- lclk coords
|
|
||||||
m_Coords2 = {x = 0, z = 0}; -- rclk coords
|
|
||||||
m_LastCoords = 0; -- When Coords1 or Coords2 is set, this gets set to 1 or 2, signifying the last changed set of coords
|
|
||||||
m_HasCoords1 = false; -- Set to true when m_Coords1 has been set by the user
|
|
||||||
m_HasCoords2 = false; -- Set to true when m_Coords2 has been set by the user
|
|
||||||
};
|
|
||||||
|
|
||||||
g_CommandStates = {};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function cCommandState:new(obj)
|
|
||||||
obj = obj or {};
|
|
||||||
setmetatable(obj, self);
|
|
||||||
self.__index = self;
|
|
||||||
return obj;
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- Returns the current coord pair as a cCuboid object
|
|
||||||
function cCommandState:GetCurrentCuboid()
|
|
||||||
if (not(self.m_HasCoords1) or not(self.m_HasCoords2)) then
|
|
||||||
-- Some of the coords haven't been set yet
|
|
||||||
return nil;
|
|
||||||
end
|
|
||||||
|
|
||||||
local res = cCuboid(
|
|
||||||
self.m_Coords1.x, 0, self.m_Coords1.z,
|
|
||||||
self.m_Coords2.x, 255, self.m_Coords2.z
|
|
||||||
);
|
|
||||||
res:Sort();
|
|
||||||
return res;
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- Returns the x, z coords that were the set last,
|
|
||||||
-- That is, either m_Coords1 or m_Coords2, based on m_LastCoords member
|
|
||||||
-- Returns nothing if no coords were set yet
|
|
||||||
function cCommandState:GetLastCoords()
|
|
||||||
if (self.m_LastCoords == 0) then
|
|
||||||
-- No coords have been set yet
|
|
||||||
return;
|
|
||||||
elseif (self.m_LastCoords == 1) then
|
|
||||||
return self.m_Coords1.x, self.m_Coords1.z;
|
|
||||||
elseif (self.m_LastCoords == 2) then
|
|
||||||
return self.m_Coords2.x, self.m_Coords2.z;
|
|
||||||
else
|
|
||||||
LOGWARNING(PluginPrefix .. "cCommandState is in an unexpected state, m_LastCoords == " .. self.m_LastCoords);
|
|
||||||
return;
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- Sets the first set of coords (upon rclk with a wand)
|
|
||||||
function cCommandState:SetCoords1(a_BlockX, a_BlockZ)
|
|
||||||
self.m_Coords1.x = a_BlockX;
|
|
||||||
self.m_Coords1.z = a_BlockZ;
|
|
||||||
self.m_LastCoords = 1;
|
|
||||||
self.m_HasCoords1 = true;
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- Sets the second set of coords (upon lclk with a wand)
|
|
||||||
function cCommandState:SetCoords2(a_BlockX, a_BlockZ)
|
|
||||||
self.m_Coords2.x = a_BlockX;
|
|
||||||
self.m_Coords2.z = a_BlockZ;
|
|
||||||
self.m_LastCoords = 2;
|
|
||||||
self.m_HasCoords2 = true;
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- Returns the cCommandState for the specified player; creates one if not existant
|
|
||||||
function GetCommandStateForPlayer(a_Player)
|
|
||||||
local res = g_CommandStates[a_Player:GetUniqueID()];
|
|
||||||
if (res == nil) then
|
|
||||||
res = cCommandState:new();
|
|
||||||
g_CommandStates[a_Player:GetUniqueID()] = res;
|
|
||||||
end
|
|
||||||
return res;
|
|
||||||
end;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
|||||||
|
|
||||||
-- Config.lua
|
|
||||||
|
|
||||||
-- Implements the cConfig class that holds the general plugin configuration
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
cConfig = {
|
|
||||||
m_Wand = cItem(E_ITEM_STICK, 1, 1); -- The item to be used as the selection wand
|
|
||||||
m_AllowInteractNoArea = true; -- If there's no area, is a player allowed to build / dig?
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- Initializes the cConfig object, loads the configuration from an INI file
|
|
||||||
function InitializeConfig()
|
|
||||||
local ini = cIniFile("ProtectionAreas.ini");
|
|
||||||
if (not(ini:ReadFile())) then
|
|
||||||
LOGINFO(PluginPrefix .. "Cannot read ProtectionAreas.ini, all plugin configuration is set to defaults");
|
|
||||||
end
|
|
||||||
local WandItem = cItem();
|
|
||||||
if (
|
|
||||||
StringToItem(ini:GetValueSet("ProtectionAreas", "WandItem", ItemToString(cConfig.m_Wand)), WandItem) and
|
|
||||||
IsValidItem(WandItem.m_ItemType)
|
|
||||||
) then
|
|
||||||
cConfig.m_Wand = WandItem;
|
|
||||||
end
|
|
||||||
cConfig.m_AllowInteractNoArea = ini:GetValueSetB("ProtectionAreas", "AllowInteractNoArea", cConfig.m_AllowInteractNoArea);
|
|
||||||
ini:WriteFile();
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- Returns true if a_Item is the wand tool item
|
|
||||||
function cConfig:IsWand(a_Item)
|
|
||||||
return (
|
|
||||||
(a_Item.m_ItemType == self.m_Wand.m_ItemType) and
|
|
||||||
(a_Item.m_ItemDamage == self.m_Wand.m_ItemDamage)
|
|
||||||
);
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- Returns the wand tool item as a cItem object
|
|
||||||
function cConfig:GetWandItem()
|
|
||||||
return self.m_Wand;
|
|
||||||
end
|
|
@ -1,76 +0,0 @@
|
|||||||
|
|
||||||
-- CurrentLng.lua
|
|
||||||
-- This file provides all the translatable strings
|
|
||||||
-- The expectation is that the translators will create copies of this file, translate the texts and then the users will overwrite this file with a specific language version
|
|
||||||
-- Note that the individual languages must not have ".lua" extension, otherwise MCServer will load them and the plugin won't work!
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- Individual commands, and their help strings. Don't touch the first symbol on each line!
|
|
||||||
-- This needs to be implemented as a function, because it references other functions which might not yet be loaded while Lua is processing the globals
|
|
||||||
|
|
||||||
function CommandReg()
|
|
||||||
return {
|
|
||||||
-- Handler function | Command | Permission | Help text
|
|
||||||
{HandleAddArea, "/ProtAdd", "Prot.Add", "<UserNames> - Adds a new protected area"},
|
|
||||||
{HandleAddAreaCoords, "/ProtAddCoords", "Prot.Add", "<x1> <z1> <x2> <z2> <UserNames> - Adds a new protected area by coords"},
|
|
||||||
{HandleAddAreaUser, "/ProtAddUser", "Prot.AddUser", "<AreaID> <UserNames> - Adds new users to an existing protected area"},
|
|
||||||
{HandleDelArea, "/ProtDelID", "Prot.Del", "<AreaID> - Deletes a protected area by ID"},
|
|
||||||
{HandleGiveWand, "/ProtWand", "Prot.Wand", " - Gives you the wand used for protection"},
|
|
||||||
{HandleListAreas, "/ProtList", "Prot.List", "[<x> <z>] - Lists all areas for the marked block or given coords"},
|
|
||||||
{HandleListUsers, "/ProtUsers", "Prot.List", "<AreaID> - Lists all allowed users for a given area ID"},
|
|
||||||
{HandleRemoveUser, "/ProtRemUser", "Prot.RemUser", "<AreaID> <UserName> - Removes a user from the protected area"},
|
|
||||||
{HandleRemoveUserAll, "/ProtRemUserAll", "Prot.RemUser", "<UserName> - Removes a user from all protected areas"},
|
|
||||||
};
|
|
||||||
end;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- Messages sent to players
|
|
||||||
g_Msgs =
|
|
||||||
{
|
|
||||||
AllUsersAlreadyAllowed = "All the specified users were already allowed.";
|
|
||||||
AreaAdded = "Area added, ID %s";
|
|
||||||
AreaAllowed = "Allowed";
|
|
||||||
AreaDeleted = "Area ID %s deleted";
|
|
||||||
AreaNotAllowed = "NOT allowed";
|
|
||||||
Coords1Set = "Coords1 set as {%d, %d}";
|
|
||||||
Coords2Set = "Coords2 set as {%d, %d}";
|
|
||||||
ErrCmdStateNilAddArea = "Cannot add area, internal plugin error (CmdState == nil)";
|
|
||||||
ErrCmdStateNilListAreas = "Cannot list areas, internal plugin error (CmdState == nil)";
|
|
||||||
ErrDBFailAddUsers = "Cannot add users, DB failure";
|
|
||||||
ErrExpectedAreaID = "Parameter mismatch. Expected <AreaID>.";
|
|
||||||
ErrExpectedAreaIDUserName = "Parameter mismatch. Expected <AreaID> <UserName>.";
|
|
||||||
ErrExpectedAreaIDUsernames = "Not enough parameters. Expected <AreaID> and a list of usernames.";
|
|
||||||
ErrExpectedCoordsUsernames = "Not enough parameters. Expected <x1> <z1> <x2> <z2> coords and a list of usernames.";
|
|
||||||
ErrExpectedListOfUsernames = "Not enough parameters. Expected a list of usernames.";
|
|
||||||
ErrExpectedUserName = "Parameter mismatch. Expected <UserName>.";
|
|
||||||
ErrListNotWanded = "Cannot list areas, no query point has been selected. Use a ProtWand lclk / rclk to select a point first";
|
|
||||||
ErrNoAreaWanded = "Cannot add area, no area has been selected. Use a ProtWand lclk / rclk to select area first";
|
|
||||||
ErrNoSpaceForWand = "Cannot give wand, no space in your inventory";
|
|
||||||
ErrNoSuchArea = "No such area: %s";
|
|
||||||
ErrParseAreaID = "Cannot parse <AreaID>.";
|
|
||||||
ErrParseCoords = "Cannot parse coords.";
|
|
||||||
ErrParseCoordsListAreas = "Cannot list areas, cannot parse coords in params";
|
|
||||||
ErrSyntaxErrorListAreas = "Cannot list areas, syntax error. Expected either no params or <x> <z>.";
|
|
||||||
ListAreasFooter = "Area list finished";
|
|
||||||
ListAreasHeader = "Listing protection areas intersecting block column {%d, %d}:";
|
|
||||||
ListAreasRow = " %s, %s, created by %s";
|
|
||||||
ListUsersFooter = "End of area %s user list, total %d users";
|
|
||||||
ListUsersHeader = "Area ID %s: {%d, %d} - {%d, %d}, created by %s; allowed users:";
|
|
||||||
ListUsersRow = " %s";
|
|
||||||
NotAllowedToBuild = "You are not allowed to build here!";
|
|
||||||
NotAllowedToDig = "You are not allowed to dig here!";
|
|
||||||
RemovedUser = "Removed %s from area %d";
|
|
||||||
RemovedUserAll = "Removed %s from all areas";
|
|
||||||
UsersAdded = "Users added: %s";
|
|
||||||
WandGiven = "Wand given";
|
|
||||||
} ;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,139 +0,0 @@
|
|||||||
|
|
||||||
-- HookHandlers.lua
|
|
||||||
-- Implements the handlers for individual hooks
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- Registers all the hooks that the plugin needs to know about
|
|
||||||
function InitializeHooks(a_Plugin)
|
|
||||||
local PlgMgr = cRoot:Get():GetPluginManager();
|
|
||||||
PlgMgr:AddHook(a_Plugin, cPluginManager.HOOK_DISCONNECT);
|
|
||||||
PlgMgr:AddHook(a_Plugin, cPluginManager.HOOK_PLAYER_LEFT_CLICK);
|
|
||||||
PlgMgr:AddHook(a_Plugin, cPluginManager.HOOK_PLAYER_MOVING);
|
|
||||||
PlgMgr:AddHook(a_Plugin, cPluginManager.HOOK_PLAYER_RIGHT_CLICK);
|
|
||||||
PlgMgr:AddHook(a_Plugin, cPluginManager.HOOK_PLAYER_SPAWNED);
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- Called by MCS when a player's connectino is lost - either they disconnected or timed out
|
|
||||||
function OnDisconnect(a_Player, a_Reason)
|
|
||||||
-- Remove the player's cProtectionArea object
|
|
||||||
g_PlayerAreas[a_Player:GetUniqueID()] = nil;
|
|
||||||
|
|
||||||
-- If the player is a VIP, they had a command state, remove that as well
|
|
||||||
g_CommandStates[a_Player:GetUniqueID()] = nil;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
end;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- Called by MCS whenever a player enters a world (is spawned)
|
|
||||||
function OnPlayerSpawned(a_Player)
|
|
||||||
-- Create a new cPlayerAreas object for this player
|
|
||||||
if (g_PlayerAreas[a_Player:GetUniqueID()] == nil) then
|
|
||||||
LoadPlayerAreas(a_Player);
|
|
||||||
end;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- Called by MCS whenever a player is moving (at most once every tick)
|
|
||||||
function OnPlayerMoving(a_Player)
|
|
||||||
local PlayerID = a_Player:GetUniqueID();
|
|
||||||
|
|
||||||
-- If for some reason we don't have a cPlayerAreas object for this player, load it up
|
|
||||||
local PlayerAreas = g_PlayerAreas[PlayerID];
|
|
||||||
if (PlayerAreas == nil) then
|
|
||||||
LoadPlayerAreas(a_Player);
|
|
||||||
return false;
|
|
||||||
end;
|
|
||||||
|
|
||||||
-- If the player is outside their areas' safe space, reload
|
|
||||||
if (not(PlayerAreas:IsInSafe(a_Player:GetPosX(), a_Player:GetPosZ()))) then
|
|
||||||
LoadPlayerAreas(a_Player);
|
|
||||||
end
|
|
||||||
return false;
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- Called by MCS when a player left-clicks
|
|
||||||
function OnPlayerLeftClick(a_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_Status)
|
|
||||||
-- If the player has lclked with the wand; regardless of their permissions, let's set the coords:
|
|
||||||
if (cConfig:IsWand(a_Player:GetEquippedItem())) then
|
|
||||||
-- BlockFace < 0 means "use item", for which the coords are not given by the client
|
|
||||||
if (a_BlockFace < 0) then
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Convert the clicked coords into the block space
|
|
||||||
a_BlockX, a_BlockY, a_BlockZ = AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace);
|
|
||||||
|
|
||||||
-- Set the coords in the CommandState
|
|
||||||
GetCommandStateForPlayer(a_Player):SetCoords1(a_BlockX, a_BlockZ);
|
|
||||||
a_Player:SendMessage(string.format(g_Msgs.Coords1Set, a_BlockX, a_BlockZ));
|
|
||||||
return true;
|
|
||||||
end;
|
|
||||||
|
|
||||||
-- Check the player areas to see whether to disable this action
|
|
||||||
local Areas = g_PlayerAreas[a_Player:GetUniqueID()];
|
|
||||||
if not(Areas:CanInteractWithBlock(a_BlockX, a_BlockZ)) then
|
|
||||||
a_Player:SendMessage(g_Msgs.NotAllowedToDig);
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Allow interaction
|
|
||||||
return false;
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- Called by MCS when a player right-clicks
|
|
||||||
function OnPlayerRightClick(a_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ, a_Status)
|
|
||||||
|
|
||||||
-- BlockFace < 0 means "use item", for which the coords are not given by the client
|
|
||||||
if (a_BlockFace < 0) then
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Convert the clicked coords into the block space
|
|
||||||
a_BlockX, a_BlockY, a_BlockZ = AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace);
|
|
||||||
|
|
||||||
-- If the player has rclked with the wand; regardless of their permissions, let's set the coords
|
|
||||||
if (cConfig:IsWand(a_Player:GetEquippedItem())) then
|
|
||||||
-- Set the coords in the CommandState
|
|
||||||
GetCommandStateForPlayer(a_Player):SetCoords2(a_BlockX, a_BlockZ);
|
|
||||||
a_Player:SendMessage(string.format(g_Msgs.Coords2Set, a_BlockX, a_BlockZ));
|
|
||||||
return true;
|
|
||||||
end;
|
|
||||||
|
|
||||||
-- Check the player areas to see whether to disable this action
|
|
||||||
local Areas = g_PlayerAreas[a_Player:GetUniqueID()];
|
|
||||||
if not(Areas:CanInteractWithBlock(a_BlockX, a_BlockZ)) then
|
|
||||||
a_Player:SendMessage(g_Msgs.NotAllowedToBuild);
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Allow interaction
|
|
||||||
return false;
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
|||||||
|
|
||||||
ProtectionAreas license
|
|
||||||
=======================
|
|
||||||
|
|
||||||
The ProtectionAreas plugin is written by _Xoft(o) / Mattes and is hereby released as public domain.
|
|
||||||
|
|
||||||
If you like it, I'd really appreciate a postcard, or something tiny typical from your country :) The current snailmail address is at my personal web, http://xoft.cz .
|
|
@ -1,109 +0,0 @@
|
|||||||
|
|
||||||
-- PlayerAreas.lua
|
|
||||||
-- Implements the cPlayerAreas class representing the per-player area storage object
|
|
||||||
|
|
||||||
--[[
|
|
||||||
Each player instance is expected to have a separate object of type cPlayerAreas.
|
|
||||||
Each object has an array of {cuboid, IsAllowed} tables, one for each area that is "within reach"
|
|
||||||
The code can then ask each object, whether the player can interact with a certain block or not.
|
|
||||||
A player can interact with a block if either one of these is true:
|
|
||||||
1, There are no areas covering the block
|
|
||||||
2, There is at least one area covering the block with IsAllowed set to true
|
|
||||||
The object also has a m_SafeCuboid object that specified the area within which the player may move
|
|
||||||
without the PlayerAreas needing a re-query.
|
|
||||||
|
|
||||||
Also, a global table g_PlayerAreas is the actual map of PlayerID -> cPlayerAreas
|
|
||||||
--]]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
cPlayerAreas = {};
|
|
||||||
|
|
||||||
g_PlayerAreas = {};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function cPlayerAreas:new(a_SafeMinX, a_SafeMinZ, a_SafeMaxX, a_SafeMaxZ)
|
|
||||||
assert(a_SafeMinX);
|
|
||||||
assert(a_SafeMinZ);
|
|
||||||
assert(a_SafeMaxX);
|
|
||||||
assert(a_SafeMaxZ);
|
|
||||||
|
|
||||||
local obj = {};
|
|
||||||
setmetatable(obj, self);
|
|
||||||
self.__index = self;
|
|
||||||
self.m_SafeCuboid = cCuboid(a_SafeMinX, 0, a_SafeMinZ, a_SafeMaxX, 255, a_SafeMaxZ);
|
|
||||||
return obj;
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- Adds a new cuboid to the area list, where the player is either allowed or not, depending on the IsAllowed param
|
|
||||||
function cPlayerAreas:AddArea(a_Cuboid, a_IsAllowed)
|
|
||||||
table.insert(self, {m_Cuboid = a_Cuboid, m_IsAllowed = a_IsAllowed});
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- returns true if the player owning this object can interact with the specified block
|
|
||||||
function cPlayerAreas:CanInteractWithBlock(a_BlockX, a_BlockZ)
|
|
||||||
assert(self);
|
|
||||||
|
|
||||||
-- iterate through all the stored areas:
|
|
||||||
local IsInsideAnyArea = false;
|
|
||||||
for idx, Area in ipairs(self) do
|
|
||||||
if (Area.m_Cuboid:IsInside(a_BlockX, 1, a_BlockZ)) then -- We don't care about Y coords, so use a dummy value
|
|
||||||
if (Area.m_IsAllowed) then
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
-- The coords are inside a cuboid for which the player doesn't have access, take a note of it
|
|
||||||
IsInsideAnyArea = true;
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if (IsInsideAnyArea) then
|
|
||||||
-- The specified coords are inside at least one area, but none of them allow the player to interact
|
|
||||||
return false;
|
|
||||||
end
|
|
||||||
|
|
||||||
-- The coords are not inside any area
|
|
||||||
return cConfig.m_AllowInteractNoArea;
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- Calls the specified callback for each area contained within
|
|
||||||
-- a_Callback has a signature: function(a_Cuboid, a_IsAllowed)
|
|
||||||
-- Returns true if all areas have been enumerated, false if the callback has aborted by returning true
|
|
||||||
function cPlayerAreas:ForEachArea(a_Callback)
|
|
||||||
assert(self);
|
|
||||||
|
|
||||||
for idx, Area in ipairs(self) do
|
|
||||||
if (a_Callback(Area.m_Cuboid, Area.m_IsAllowed)) then
|
|
||||||
return false;
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- Returns true if the player is withing the safe cuboid (no need to re-query the areas)
|
|
||||||
function cPlayerAreas:IsInSafe(a_BlockX, a_BlockZ)
|
|
||||||
assert(self);
|
|
||||||
return self.m_SafeCuboid:IsInside(a_BlockX, 0, a_BlockZ);
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<project>
|
|
||||||
<file>
|
|
||||||
<filename>CommandHandlers.lua</filename>
|
|
||||||
</file>
|
|
||||||
<file>
|
|
||||||
<filename>CommandState.lua</filename>
|
|
||||||
</file>
|
|
||||||
<file>
|
|
||||||
<filename>Config.lua</filename>
|
|
||||||
</file>
|
|
||||||
<file>
|
|
||||||
<filename>CurrentLng.lua</filename>
|
|
||||||
</file>
|
|
||||||
<file>
|
|
||||||
<filename>HookHandlers.lua</filename>
|
|
||||||
</file>
|
|
||||||
<file>
|
|
||||||
<filename>PlayerAreas.lua</filename>
|
|
||||||
</file>
|
|
||||||
<file>
|
|
||||||
<filename>ProtectionAreas.lua</filename>
|
|
||||||
</file>
|
|
||||||
<file>
|
|
||||||
<filename>Storage.lua</filename>
|
|
||||||
</file>
|
|
||||||
</project>
|
|
@ -1,71 +0,0 @@
|
|||||||
|
|
||||||
-- ProtectionAreas.lua
|
|
||||||
-- Defines the main plugin entrypoint, as well as some utility functions
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- Prefix for all messages logged to the server console
|
|
||||||
PluginPrefix = "ProtectionAreas: ";
|
|
||||||
|
|
||||||
--- Bounds for the area loading. Areas less this far in any direction from the player will be loaded into cPlayerAreas
|
|
||||||
g_AreaBounds = 48;
|
|
||||||
|
|
||||||
--- If a player moves this close to the PlayerAreas bounds, the PlayerAreas will be re-queried
|
|
||||||
g_AreaSafeEdge = 12;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- Called by MCS when the plugin loads
|
|
||||||
-- Returns true if initialization successful, false otherwise
|
|
||||||
function Initialize(a_Plugin)
|
|
||||||
a_Plugin:SetName("ProtectionAreas");
|
|
||||||
a_Plugin:SetVersion(1);
|
|
||||||
|
|
||||||
InitializeConfig();
|
|
||||||
if (not(InitializeStorage())) then
|
|
||||||
LOGWARNING(PluginPrefix .. "failed to initialize Storage, plugin is disabled");
|
|
||||||
return false;
|
|
||||||
end
|
|
||||||
InitializeHooks(a_Plugin);
|
|
||||||
InitializeCommandHandlers();
|
|
||||||
|
|
||||||
-- We might be reloading, so there may be players already present in the server; reload all of them
|
|
||||||
cRoot:Get():ForEachWorld(
|
|
||||||
function(a_World)
|
|
||||||
ReloadAllPlayersInWorld(a_World:GetName());
|
|
||||||
end
|
|
||||||
);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- Loads a cPlayerAreas object from the DB for the player, and assigns it to the player map
|
|
||||||
function LoadPlayerAreas(a_Player)
|
|
||||||
local PlayerID = a_Player:GetUniqueID();
|
|
||||||
local PlayerX = math.floor(a_Player:GetPosX());
|
|
||||||
local PlayerZ = math.floor(a_Player:GetPosZ());
|
|
||||||
local WorldName = a_Player:GetWorld():GetName();
|
|
||||||
g_PlayerAreas[PlayerID] = g_Storage:LoadPlayerAreas(a_Player:GetName(), PlayerX, PlayerZ, WorldName);
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function ReloadAllPlayersInWorld(a_WorldName)
|
|
||||||
local World = cRoot:Get():GetWorld(a_WorldName);
|
|
||||||
World:ForEachPlayer(LoadPlayerAreas);
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,518 +0,0 @@
|
|||||||
|
|
||||||
-- Storage.lua
|
|
||||||
-- Implements the storage access object, shielding the rest of the code away from the DB
|
|
||||||
|
|
||||||
--[[
|
|
||||||
The cStorage class is the interface to the underlying storage, the SQLite database.
|
|
||||||
This class knows how to load player areas from the DB, how to add or remove areas in the DB
|
|
||||||
and other such operations.
|
|
||||||
|
|
||||||
Also, a g_Storage global variable is declared, it holds the single instance of the storage.
|
|
||||||
--]]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
cStorage = {};
|
|
||||||
|
|
||||||
g_Storage = {};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- Initializes the storage subsystem, creates the g_Storage object
|
|
||||||
-- Returns true if successful, false if not
|
|
||||||
function InitializeStorage()
|
|
||||||
g_Storage = cStorage:new();
|
|
||||||
if (not(g_Storage:OpenDB())) then
|
|
||||||
return false;
|
|
||||||
end
|
|
||||||
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function cStorage:new(obj)
|
|
||||||
obj = obj or {};
|
|
||||||
setmetatable(obj, self);
|
|
||||||
self.__index = self;
|
|
||||||
return obj;
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- Opens the DB and makes sure it has all the columns needed
|
|
||||||
-- Returns true if successful, false otherwise
|
|
||||||
function cStorage:OpenDB()
|
|
||||||
local ErrCode, ErrMsg;
|
|
||||||
self.DB, ErrCode, ErrMsg = sqlite3.open("ProtectionAreas.sqlite");
|
|
||||||
if (self.DB == nil) then
|
|
||||||
LOGWARNING(PluginPrefix .. "Cannot open ProtectionAreas.sqlite, error " .. ErrCode .. " (" .. ErrMsg ..")");
|
|
||||||
return false;
|
|
||||||
end
|
|
||||||
|
|
||||||
if (
|
|
||||||
not(self:CreateTable("Areas", {"ID INTEGER PRIMARY KEY AUTOINCREMENT", "MinX", "MaxX", "MinZ", "MaxZ", "WorldName", "CreatorUserName"})) or
|
|
||||||
not(self:CreateTable("AllowedUsers", {"AreaID", "UserName"}))
|
|
||||||
) then
|
|
||||||
LOGWARNING(PluginPrefix .. "Cannot create DB tables!");
|
|
||||||
return false;
|
|
||||||
end
|
|
||||||
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- Executes the SQL command given, calling the a_Callback for each result
|
|
||||||
-- If the SQL command fails, prints it out on the server console and returns false
|
|
||||||
-- Returns true on success
|
|
||||||
function cStorage:DBExec(a_SQL, a_Callback, a_CallbackParam)
|
|
||||||
local ErrCode = self.DB:exec(a_SQL, a_Callback, a_CallbackParam);
|
|
||||||
if (ErrCode ~= sqlite3.OK) then
|
|
||||||
LOGWARNING(PluginPrefix .. "Error " .. ErrCode .. " (" .. self.DB:errmsg() ..
|
|
||||||
") while processing SQL command >>" .. a_SQL .. "<<"
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
end
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- Creates the table of the specified name and columns[]
|
|
||||||
-- If the table exists, any columns missing are added; existing data is kept
|
|
||||||
function cStorage:CreateTable(a_TableName, a_Columns)
|
|
||||||
-- Try to create the table first
|
|
||||||
local sql = "CREATE TABLE IF NOT EXISTS '" .. a_TableName .. "' (";
|
|
||||||
sql = sql .. table.concat(a_Columns, ", ");
|
|
||||||
sql = sql .. ")";
|
|
||||||
if (not(self:DBExec(sql))) then
|
|
||||||
LOGWARNING(PluginPrefix .. "Cannot create DB Table " .. a_TableName);
|
|
||||||
return false;
|
|
||||||
end
|
|
||||||
-- SQLite doesn't inform us if it created the table or not, so we have to continue anyway
|
|
||||||
|
|
||||||
-- Check each column whether it exists
|
|
||||||
-- Remove all the existing columns from a_Columns:
|
|
||||||
local RemoveExistingColumn = function(UserData, NumCols, Values, Names)
|
|
||||||
-- Remove the received column from a_Columns. Search for column name in the Names[] / Values[] pairs
|
|
||||||
for i = 1, NumCols do
|
|
||||||
if (Names[i] == "name") then
|
|
||||||
local ColumnName = Values[i]:lower();
|
|
||||||
-- Search the a_Columns if they have that column:
|
|
||||||
for j = 1, #a_Columns do
|
|
||||||
-- Cut away all column specifiers (after the first space), if any:
|
|
||||||
local SpaceIdx = string.find(a_Columns[j], " ");
|
|
||||||
if (SpaceIdx ~= nil) then
|
|
||||||
SpaceIdx = SpaceIdx - 1;
|
|
||||||
end
|
|
||||||
local ColumnTemplate = string.lower(string.sub(a_Columns[j], 1, SpaceIdx));
|
|
||||||
-- If it is a match, remove from a_Columns:
|
|
||||||
if (ColumnTemplate == ColumnName) then
|
|
||||||
table.remove(a_Columns, j);
|
|
||||||
break; -- for j
|
|
||||||
end
|
|
||||||
end -- for j - a_Columns[]
|
|
||||||
end
|
|
||||||
end -- for i - Names[] / Values[]
|
|
||||||
return 0;
|
|
||||||
end
|
|
||||||
if (not(self:DBExec("PRAGMA table_info(" .. a_TableName .. ")", RemoveExistingColumn))) then
|
|
||||||
LOGWARNING(PluginPrefix .. "Cannot query DB table structure");
|
|
||||||
return false;
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Create the missing columns
|
|
||||||
-- a_Columns now contains only those columns that are missing in the DB
|
|
||||||
if (#a_Columns > 0) then
|
|
||||||
LOGINFO(PluginPrefix .. "Database table \"" .. a_TableName .. "\" is missing " .. #a_Columns .. " columns, fixing now.");
|
|
||||||
for idx, ColumnName in ipairs(a_Columns) do
|
|
||||||
if (not(self:DBExec("ALTER TABLE '" .. a_TableName .. "' ADD COLUMN " .. ColumnName))) then
|
|
||||||
LOGWARNING(PluginPrefix .. "Cannot add DB table \"" .. a_TableName .. "\" column \"" .. ColumnName .. "\"");
|
|
||||||
return false;
|
|
||||||
end
|
|
||||||
end
|
|
||||||
LOGINFO(PluginPrefix .. "Database table \"" .. a_TableName .. "\" columns fixed.");
|
|
||||||
end
|
|
||||||
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- Returns true if the specified area is allowed for the specified player
|
|
||||||
function cStorage:IsAreaAllowed(a_AreaID, a_PlayerName, a_WorldName)
|
|
||||||
assert(a_AreaID);
|
|
||||||
assert(a_PlayerName);
|
|
||||||
assert(a_WorldName);
|
|
||||||
assert(self);
|
|
||||||
|
|
||||||
local lcPlayerName = string.lower(a_PlayerName);
|
|
||||||
local res = false;
|
|
||||||
local sql = "SELECT COUNT(*) FROM AllowedUsers WHERE (AreaID = " .. a_AreaID ..
|
|
||||||
") AND (UserName ='" .. lcPlayerName .. "')";
|
|
||||||
local function SetResTrue(UserData, NumValues, Values, Names)
|
|
||||||
res = (tonumber(Values[1]) > 0);
|
|
||||||
return 0;
|
|
||||||
end
|
|
||||||
if (not(self:DBExec(sql, SetResTrue))) then
|
|
||||||
LOGWARNING("SQL error while determining area allowance");
|
|
||||||
return false;
|
|
||||||
end
|
|
||||||
return res;
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- Loads cPlayerAreas for the specified player from the DB. Returns a cPlayerAreas object
|
|
||||||
function cStorage:LoadPlayerAreas(a_PlayerName, a_PlayerX, a_PlayerZ, a_WorldName)
|
|
||||||
assert(a_PlayerName);
|
|
||||||
assert(a_PlayerX);
|
|
||||||
assert(a_PlayerZ);
|
|
||||||
assert(a_WorldName);
|
|
||||||
assert(self);
|
|
||||||
|
|
||||||
-- Bounds for which the areas are loaded
|
|
||||||
local BoundsMinX = a_PlayerX - g_AreaBounds;
|
|
||||||
local BoundsMaxX = a_PlayerX + g_AreaBounds;
|
|
||||||
local BoundsMinZ = a_PlayerZ - g_AreaBounds;
|
|
||||||
local BoundsMaxZ = a_PlayerZ + g_AreaBounds;
|
|
||||||
|
|
||||||
local res = cPlayerAreas:new(
|
|
||||||
BoundsMinX + g_AreaSafeEdge, BoundsMinZ + g_AreaSafeEdge,
|
|
||||||
BoundsMaxX - g_AreaSafeEdge, BoundsMaxZ - g_AreaSafeEdge
|
|
||||||
);
|
|
||||||
|
|
||||||
--[[
|
|
||||||
LOG("Loading protection areas for player " .. a_PlayerName .. " centered around {" .. a_PlayerX .. ", " .. a_PlayerZ ..
|
|
||||||
"}, bounds are {" .. BoundsMinX .. ", " .. BoundsMinZ .. "} - {" ..
|
|
||||||
BoundsMaxX .. ", " .. BoundsMaxZ .. "}"
|
|
||||||
);
|
|
||||||
--]]
|
|
||||||
|
|
||||||
-- Load the areas from the DB, based on the player's location
|
|
||||||
local lcWorldName = string.lower(a_WorldName);
|
|
||||||
local sql =
|
|
||||||
"SELECT ID, MinX, MaxX, MinZ, MaxZ FROM Areas WHERE " ..
|
|
||||||
"MinX < " .. BoundsMaxX .. " AND MaxX > " .. BoundsMinX .. " AND " ..
|
|
||||||
"MinZ < " .. BoundsMaxZ .. " AND MaxZ > " .. BoundsMinZ .. " AND " ..
|
|
||||||
"WorldName = '" .. lcWorldName .."'";
|
|
||||||
|
|
||||||
local function AddAreas(UserData, NumValues, Values, Names)
|
|
||||||
if ((NumValues < 5) or ((Values[1] and Values[2] and Values[3] and Values[4] and Values[5]) == nil)) then
|
|
||||||
LOGWARNING("SQL query didn't return all data");
|
|
||||||
return 0;
|
|
||||||
end
|
|
||||||
res:AddArea(cCuboid(Values[2], 0, Values[4], Values[3], 255, Values[5]), self:IsAreaAllowed(Values[1], a_PlayerName, a_WorldName));
|
|
||||||
return 0;
|
|
||||||
end
|
|
||||||
|
|
||||||
if (not(self:DBExec(sql, AddAreas))) then
|
|
||||||
LOGWARNING("SQL error while querying areas");
|
|
||||||
return res;
|
|
||||||
end
|
|
||||||
|
|
||||||
return res;
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- Adds a new area into the DB. a_AllowedNames is a table listing all the players that are allowed in the area
|
|
||||||
-- Returns the ID of the new area, or -1 on failure
|
|
||||||
function cStorage:AddArea(a_Cuboid, a_WorldName, a_CreatorName, a_AllowedNames)
|
|
||||||
assert(a_Cuboid);
|
|
||||||
assert(a_WorldName);
|
|
||||||
assert(a_CreatorName);
|
|
||||||
assert(a_AllowedNames);
|
|
||||||
assert(self);
|
|
||||||
|
|
||||||
-- Store the area in the DB
|
|
||||||
local ID = -1;
|
|
||||||
local function RememberID(UserData, NumCols, Values, Names)
|
|
||||||
for i = 1, NumCols do
|
|
||||||
if (Names[i] == "ID") then
|
|
||||||
ID = Values[i];
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return 0;
|
|
||||||
end
|
|
||||||
local lcWorldName = string.lower(a_WorldName);
|
|
||||||
local lcCreatorName = string.lower(a_CreatorName);
|
|
||||||
local sql =
|
|
||||||
"INSERT INTO Areas (ID, MinX, MaxX, MinZ, MaxZ, WorldName, CreatorUserName) VALUES (NULL, " ..
|
|
||||||
a_Cuboid.p1.x .. ", " .. a_Cuboid.p2.x .. ", " .. a_Cuboid.p1.z .. ", " .. a_Cuboid.p2.z ..
|
|
||||||
", '" .. lcWorldName .. "', '" .. lcCreatorName ..
|
|
||||||
"'); SELECT last_insert_rowid() AS ID";
|
|
||||||
if (not(self:DBExec(sql, RememberID))) then
|
|
||||||
LOGWARNING(PluginPrefix .. "SQL Error while inserting new area");
|
|
||||||
return -1;
|
|
||||||
end
|
|
||||||
if (ID == -1) then
|
|
||||||
LOGWARNING(PluginPrefix .. "SQL Error while retrieving INSERTion ID");
|
|
||||||
return -1;
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Store each allowed player in the DB
|
|
||||||
for idx, Name in ipairs(a_AllowedNames) do
|
|
||||||
local lcName = string.lower(Name);
|
|
||||||
local sql = "INSERT INTO AllowedUsers (AreaID, UserName) VALUES (" .. ID .. ", '" .. lcName .. "')";
|
|
||||||
if (not(self:DBExec(sql))) then
|
|
||||||
LOGWARNING(PluginPrefix .. "SQL Error while inserting new area's allowed player " .. Name);
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return ID;
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function cStorage:DelArea(a_WorldName, a_AreaID)
|
|
||||||
assert(a_WorldName);
|
|
||||||
assert(a_AreaID);
|
|
||||||
assert(self);
|
|
||||||
|
|
||||||
-- Since all areas are stored in a single DB (for now), the worldname parameter isn't used at all
|
|
||||||
-- Later if we change to a per-world DB, we'll need the world name
|
|
||||||
|
|
||||||
-- Delete from both tables simultaneously
|
|
||||||
local sql =
|
|
||||||
"DELETE FROM Areas WHERE ID = " .. a_AreaID .. ";" ..
|
|
||||||
"DELETE FROM AllowedUsers WHERE AreaID = " .. a_AreaID;
|
|
||||||
if (not(self:DBExec(sql))) then
|
|
||||||
LOGWARNING(PluginPrefix .. "SQL error while deleting area " .. a_AreaID .. " from world \"" .. a_WorldName .. "\"");
|
|
||||||
return false;
|
|
||||||
end
|
|
||||||
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- Removes the user from the specified area
|
|
||||||
function cStorage:RemoveUser(a_AreaID, a_UserName, a_WorldName)
|
|
||||||
assert(a_AreaID);
|
|
||||||
assert(a_UserName);
|
|
||||||
assert(a_WorldName);
|
|
||||||
assert(self);
|
|
||||||
|
|
||||||
-- WorldName is not used yet, because all the worlds share the same DB in this version
|
|
||||||
|
|
||||||
local lcUserName = string.lower(a_UserName);
|
|
||||||
local sql = "DELETE FROM AllowedUsers WHERE " ..
|
|
||||||
"AreaID = " .. a_AreaID .. " AND UserName = '" .. lcUserName .. "'";
|
|
||||||
if (not(self:DBExec(sql))) then
|
|
||||||
LOGWARNING("SQL error while removing user " .. a_UserName .. " from area ID " .. a_AreaID);
|
|
||||||
return false;
|
|
||||||
end
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- Removes the user from all areas in the specified world
|
|
||||||
function cStorage:RemoveUserAll(a_UserName, a_WorldName)
|
|
||||||
assert(a_UserName);
|
|
||||||
assert(a_WorldName);
|
|
||||||
assert(self);
|
|
||||||
|
|
||||||
local lcUserName = string.lower(a_UserName);
|
|
||||||
local sql = "DELETE FROM AllowedUsers WHERE UserName = '" .. lcUserName .."'";
|
|
||||||
if (not(self:DBExec(sql))) then
|
|
||||||
LOGWARNING("SQL error while removing user " .. a_UserName .. " from all areas");
|
|
||||||
return false;
|
|
||||||
end
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- Calls the callback for each area intersecting the specified coords
|
|
||||||
-- Callback signature: function(ID, MinX, MinZ, MaxX, MaxZ, CreatorName)
|
|
||||||
function cStorage:ForEachArea(a_BlockX, a_BlockZ, a_WorldName, a_Callback)
|
|
||||||
assert(a_BlockX);
|
|
||||||
assert(a_BlockZ);
|
|
||||||
assert(a_WorldName);
|
|
||||||
assert(a_Callback);
|
|
||||||
assert(self);
|
|
||||||
|
|
||||||
-- SQL callback that parses the values and calls our callback
|
|
||||||
function CallCallback(UserData, NumValues, Values, Names)
|
|
||||||
if (NumValues ~= 6) then
|
|
||||||
-- Not enough values returned, skip this row
|
|
||||||
return 0;
|
|
||||||
end
|
|
||||||
local ID = Values[1];
|
|
||||||
local MinX = Values[2];
|
|
||||||
local MinZ = Values[3];
|
|
||||||
local MaxX = Values[4];
|
|
||||||
local MaxZ = Values[5];
|
|
||||||
local CreatorName = Values[6];
|
|
||||||
a_Callback(ID, MinX, MinZ, MaxX, MaxZ, CreatorName);
|
|
||||||
return 0;
|
|
||||||
end
|
|
||||||
|
|
||||||
local lcWorldName = string.lower(a_WorldName);
|
|
||||||
local sql = "SELECT ID, MinX, MinZ, MaxX, MaxZ, CreatorUserName FROM Areas WHERE " ..
|
|
||||||
"MinX <= " .. a_BlockX .. " AND MaxX >= " .. a_BlockX .. " AND " ..
|
|
||||||
"MinZ <= " .. a_BlockZ .. " AND MaxZ >= " .. a_BlockZ .. " AND " ..
|
|
||||||
"WorldName = '" .. lcWorldName .. "'";
|
|
||||||
if (not(self:DBExec(sql, CallCallback))) then
|
|
||||||
LOGWARNING("SQL Error while iterating through areas (cStorage:ForEachArea())");
|
|
||||||
return false;
|
|
||||||
end
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- Returns the info on the specified area
|
|
||||||
-- Returns MinX, MinZ, MaxX, MaxZ, CreatorName on success, or nothing on failure
|
|
||||||
function cStorage:GetArea(a_AreaID, a_WorldName)
|
|
||||||
assert(a_AreaID);
|
|
||||||
assert(a_WorldName);
|
|
||||||
assert(self);
|
|
||||||
|
|
||||||
local MinX, MinZ, MaxX, MaxZ, CreatorName;
|
|
||||||
local HasValues = false;
|
|
||||||
|
|
||||||
-- SQL callback that parses the values and remembers them in variables
|
|
||||||
function RememberValues(UserData, NumValues, Values, Names)
|
|
||||||
if (NumValues ~= 5) then
|
|
||||||
-- Not enough values returned, skip this row
|
|
||||||
return 0;
|
|
||||||
end
|
|
||||||
MinX = Values[1];
|
|
||||||
MinZ = Values[2];
|
|
||||||
MaxX = Values[3];
|
|
||||||
MaxZ = Values[4];
|
|
||||||
CreatorName = Values[5];
|
|
||||||
HasValues = true;
|
|
||||||
return 0;
|
|
||||||
end
|
|
||||||
|
|
||||||
local lcWorldName = string.lower(a_WorldName);
|
|
||||||
local sql = "SELECT MinX, MinZ, MaxX, MaxZ, CreatorUserName FROM Areas WHERE " ..
|
|
||||||
"ID = " .. a_AreaID .. " AND WorldName = '" .. lcWorldName .. "'";
|
|
||||||
if (not(self:DBExec(sql, RememberValues))) then
|
|
||||||
LOGWARNING("SQL Error while getting area info (cStorage:ForEachArea())");
|
|
||||||
return;
|
|
||||||
end
|
|
||||||
|
|
||||||
-- If no data has been retrieved, return nothing
|
|
||||||
if (not(HasValues)) then
|
|
||||||
return;
|
|
||||||
end
|
|
||||||
|
|
||||||
return MinX, MinZ, MaxX, MaxZ, CreatorName;
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- Calls the callback for each allowed user for the specified area
|
|
||||||
-- Callback signature: function(UserName)
|
|
||||||
function cStorage:ForEachUserInArea(a_AreaID, a_WorldName, a_Callback)
|
|
||||||
assert(a_AreaID);
|
|
||||||
assert(a_WorldName);
|
|
||||||
assert(a_Callback);
|
|
||||||
assert(self);
|
|
||||||
|
|
||||||
-- Since in this version all the worlds share a single DB, the a_WorldName parameter is not actually used
|
|
||||||
-- But this may change in the future, when we have a per-world DB
|
|
||||||
|
|
||||||
local function CallCallback(UserData, NumValues, Values)
|
|
||||||
if (NumValues ~= 1) then
|
|
||||||
return 0;
|
|
||||||
end
|
|
||||||
a_Callback(Values[1]);
|
|
||||||
return 0;
|
|
||||||
end
|
|
||||||
local sql = "SELECT UserName FROM AllowedUsers WHERE AreaID = " .. a_AreaID;
|
|
||||||
if (not(self:DBExec(sql, CallCallback))) then
|
|
||||||
LOGWARNING("SQL error while iterating area users for AreaID" .. a_AreaID);
|
|
||||||
return false;
|
|
||||||
end
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- Adds the specified usernames to the specified area, if not already present
|
|
||||||
-- a_Users is an array table of usernames to add
|
|
||||||
function cStorage:AddAreaUsers(a_AreaID, a_WorldName, a_AddedBy, a_Users)
|
|
||||||
assert(a_AreaID);
|
|
||||||
assert(a_WorldName);
|
|
||||||
assert(a_Users);
|
|
||||||
assert(self);
|
|
||||||
|
|
||||||
-- Convert all usernames to lowercase
|
|
||||||
for idx, Name in ipairs(a_Users) do
|
|
||||||
a_Users[idx] = string.lower(Name);
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Remove from a_Users the usernames already present in the area
|
|
||||||
local sql = "SELECT UserName FROM AllowedUsers WHERE AreaID = " .. a_AreaID;
|
|
||||||
local function RemovePresent(UserData, NumValues, Values, Names)
|
|
||||||
if (NumValues ~= 1) then
|
|
||||||
-- Invalid response format
|
|
||||||
return 0;
|
|
||||||
end
|
|
||||||
local DBName = Values[1];
|
|
||||||
-- Remove the name from a_Users, if exists
|
|
||||||
for idx, Name in ipairs(a_Users) do
|
|
||||||
if (Name == DBName) then
|
|
||||||
table.remove(a_Users, idx);
|
|
||||||
return 0;
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return 0;
|
|
||||||
end
|
|
||||||
if (not(self:DBExec(sql, RemovePresent))) then
|
|
||||||
LOGWARNING("SQL error while iterating through users");
|
|
||||||
return false;
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Add the users
|
|
||||||
for idx, Name in ipairs(a_Users) do
|
|
||||||
local sql = "INSERT INTO AllowedUsers (AreaID, UserName) VALUES (" .. a_AreaID .. ", '" .. Name .. "')";
|
|
||||||
if (not(self:DBExec(sql))) then
|
|
||||||
LOGWARNING("SQL error while adding user " .. Name .. " to area " .. a_AreaID);
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user