1
0

Moved ProtectionAreas into a separate repository

This commit is contained in:
madmaxoft 2013-08-16 09:38:57 +02:00
parent 8326aff5b4
commit 0074432ccd
12 changed files with 4 additions and 1447 deletions

View File

@ -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

View File

@ -1 +1,2 @@
Core Core
ProtectionAreas

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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";
} ;

View File

@ -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

View File

@ -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 .

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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