-- ==========================================
-- 区域复制 V2.2 by幻邃
-- 功能说明:支持多模式区域选择、旋转/镜像粘贴、方块过滤、撤销回溯、模板管理、权限管控
-- 适配环境:开发者2.0
-- ==========================================
-- 全局命名空间隔离,避免全局变量污染
local AreaCopy = {
version = "2.2.0",
-- 常量枚举定义,消除魔法数字,提升可维护性
ENUM = {
-- 区域记录模式
RECORD_MODE = {
CHAT_CMD = 1,
BLOCK_PLACE = 2,
ITEM_CLICK = 3
},
-- 方块过滤模式
FILTER_MODE = {
ALL = 0,
WHITELIST = 1,
BLACKLIST = 2
},
-- 旋转角度枚举
ROTATION = {
ANGLE_0 = 0,
ANGLE_90 = 90,
ANGLE_180 = 180,
ANGLE_270 = 270
},
-- 变量库类型枚举(匹配官方API定义)
VARTYPE = {
POSITION = 1,
AREA = 2,
NUMBER = 3,
STRING = 4,
BOOLEAN = 5,
NUMBER_GROUP = 17,
STRING_GROUP = 18
},
-- 命令权限等级
CMD_PERMISSION = {
GUEST = 0,
ADMIN = 1
},
-- 游戏对象类型枚举(匹配官方API定义)
OBJ_TYPE = {
PLAYER = 1,
CREATURE = 2,
DROP_ITEM = 3,
PROJECTILE = 4
},
-- 方块朝向枚举(匹配官方API定义)
FACE_DIR = {
WEST = 0,
EAST = 1,
SOUTH = 2,
NORTH = 3,
DOWN = 4,
UP = 5
}
},
-- 模块容器
Modules = {},
-- 运行时数据
Runtime = {
isInited = false,
playerData = {}, -- 玩家维度全量数据统一管理
copyTaskLocks = {}, -- 复制任务并发锁
config = {} -- 运行时配置
}
}
-- ==========================================
-- 配置管理模块
-- 职责:配置的初始化、读写、合并、持久化、深拷贝
-- ==========================================
AreaCopy.Modules.Config = {
-- 默认配置规约
defaultConfig = {
record = {
mode = AreaCopy.ENUM.RECORD_MODE.CHAT_CMD,
blockId = 101,
itemId = 1001
},
copy = {
enableRotate = true,
enableMirror = true,
defaultRotation = AreaCopy.ENUM.ROTATION.ANGLE_0,
defaultMirror = false,
copyDelay = 100, -- 单位:ms,适配引擎最小等待粒度
batchSize = 64 -- 单批次方块放置数量,平衡性能与帧率
},
filter = {
mode = AreaCopy.ENUM.FILTER_MODE.ALL,
whitelist = {},
blacklist = {},
copyAir = false
},
permission = {
enableCheck = true,
ownerIsAdmin = true,
admins = {},
blacklist = {}
},
special = {
enableUndo = true,
undoMaxSteps = 10,
enablePreview = true,
previewBlockId = 95,
enableTemplate = true,
maxTemplates = 20
},
others = {
showTips = true
}
},
configKey = "AreaCopy_Config", -- 存档配置存储键名
isInited = false
}
-- 深拷贝工具函数,避免引用类型数据污染
function AreaCopy.Modules.Config:DeepCopy(src)
local copy = {}
if type(src) ~= "table" then
return src
end
for k, v in pairs(src) do
copy[k] = self:DeepCopy(v)
end
return copy
end
-- 配置递归合并,严格校验类型匹配,避免配置结构损坏
function AreaCopy.Modules.Config:MergeConfig(defaultTbl, customTbl)
if type(defaultTbl) ~= "table" or type(customTbl) ~= "table" then
return
end
for k, v in pairs(customTbl) do
local defaultVal = defaultTbl[k]
if defaultVal == nil then
goto continue
end
if type(defaultVal) == "table" and type(v) == "table" then
self:MergeConfig(defaultVal, v)
elseif type(defaultVal) == type(v) then
defaultTbl[k] = v
end
::continue::
end
end
-- 模块初始化
function AreaCopy.Modules.Config:Init()
if self.isInited then return end
-- 加载默认配置
AreaCopy.Runtime.config = self:DeepCopy(self.defaultConfig)
-- 从存档读取持久化配置
self:LoadFromArchive()
self.isInited = true
print(string.format("[AreaCopy] 配置模块初始化完成,配置版本:%s", AreaCopy.version))
end
-- 从存档加载配置
function AreaCopy.Modules.Config:LoadFromArchive()
local result, saveData = VarLib2:getGlobalVarByName(AreaCopy.ENUM.VARTYPE.STRING, self.configKey)
if result ~= 0 or not saveData then
print("[AreaCopy] 无存档配置,使用默认配置")
return
end
-- JSON解码异常捕获
local decodeOk, loadData = pcall(JSON.decode, JSON, saveData)
if not decodeOk or type(loadData) ~= "table" then
print("[AreaCopy] 存档配置解析失败,已重置为默认配置")
return
end
-- 合并配置
self:MergeConfig(AreaCopy.Runtime.config, loadData)
print("[AreaCopy] 存档配置加载成功")
end
-- 保存配置到存档
function AreaCopy.Modules.Config:SaveToArchive()
local encodeOk, jsonData = pcall(JSON.encode, JSON, AreaCopy.Runtime.config)
if not encodeOk then
print("[AreaCopy] 配置序列化失败")
return false
end
local result = VarLib2:setGlobalVarByName(AreaCopy.ENUM.VARTYPE.STRING, self.configKey, jsonData)
return result == 0
end
-- 重置配置为默认值
function AreaCopy.Modules.Config:Reset()
AreaCopy.Runtime.config = self:DeepCopy(self.defaultConfig)
return self:SaveToArchive()
end
-- 获取配置项
function AreaCopy.Modules.Config:Get(module, key)
local moduleTbl = AreaCopy.Runtime.config[module]
if not moduleTbl then return nil end
return moduleTbl[key]
end
-- 设置配置项
function AreaCopy.Modules.Config:Set(module, key, value)
local moduleTbl = AreaCopy.Runtime.config[module]
if not moduleTbl then
AreaCopy.Runtime.config[module] = {}
moduleTbl = AreaCopy.Runtime.config[module]
end
moduleTbl[key] = value
end
-- ==========================================
-- 权限管理模块
-- 职责:管理员/黑名单校验、权限操作、权限生命周期管理
-- ==========================================
AreaCopy.Modules.Permission = {
isInited = false
}
function AreaCopy.Modules.Permission:Init()
if self.isInited then return end
self.isInited = true
print("[AreaCopy] 权限模块初始化完成")
end
-- 玩家黑名单校验(优先级最高,无视权限开关)
function AreaCopy.Modules.Permission:IsInBlacklist(playerUin)
playerUin = tonumber(playerUin)
if not playerUin then return false end
local blacklist = AreaCopy.Modules.Config:Get("permission", "blacklist") or {}
for _, uin in ipairs(blacklist) do
if tonumber(uin) == playerUin then
return true
end
end
return false
end
-- 玩家管理员身份校验
function AreaCopy.Modules.Permission:IsAdmin(playerUin)
playerUin = tonumber(playerUin)
if not playerUin then return false end
-- 房主自动管理员校验
if AreaCopy.Modules.Config:Get("permission", "ownerIsAdmin") then
local result, hostUin = Player:getHostUin()
if result == 0 and playerUin == tonumber(hostUin) then
return true
end
end
-- 管理员列表校验
local adminList = AreaCopy.Modules.Config:Get("permission", "admins") or {}
for _, uin in ipairs(adminList) do
if tonumber(uin) == playerUin then
return true
end
end
return false
end
-- 命令执行权限校验
function AreaCopy.Modules.Permission:CheckCommandPermission(playerUin, requireAdmin)
-- 黑名单拦截
if self:IsInBlacklist(playerUin) then
return false, "你已被加入黑名单,无法使用该命令"
end
-- 无需管理员权限,直接放行
if not requireAdmin then
return true
end
-- 权限开关校验
if not AreaCopy.Modules.Config:Get("permission", "enableCheck") then
return true
end
-- 管理员身份校验
if self:IsAdmin(playerUin) then
return true
end
return false, "你没有权限执行该命令"
end
-- 添加管理员
function AreaCopy.Modules.Permission:AddAdmin(playerUin)
playerUin = tonumber(playerUin)
if not playerUin or self:IsAdmin(playerUin) then
return false
end
local adminList = AreaCopy.Modules.Config:Get("permission", "admins") or {}
table.insert(adminList, playerUin)
AreaCopy.Modules.Config:Set("permission", "admins", adminList)
return AreaCopy.Modules.Config:SaveToArchive()
end
-- 移除管理员
function AreaCopy.Modules.Permission:RemoveAdmin(playerUin)
playerUin = tonumber(playerUin)
if not playerUin then return false end
-- 禁止移除房主管理员权限
local result, hostUin = Player:getHostUin()
if result == 0 and playerUin == tonumber(hostUin) then
return false
end
local adminList = AreaCopy.Modules.Config:Get("permission", "admins") or {}
local newAdminList = {}
local isRemoved = false
for _, uin in ipairs(adminList) do
if tonumber(uin) ~= playerUin then
table.insert(newAdminList, uin)
else
isRemoved = true
end
end
if not isRemoved then return false end
AreaCopy.Modules.Config:Set("permission", "admins", newAdminList)
return AreaCopy.Modules.Config:SaveToArchive()
end
-- 获取管理员列表
function AreaCopy.Modules.Permission:GetAdminList()
return AreaCopy.Modules.Config:Get("permission", "admins") or {}
end
-- 添加玩家到黑名单
function AreaCopy.Modules.Permission:AddBlacklist(playerUin)
playerUin = tonumber(playerUin)
if not playerUin or self:IsInBlacklist(playerUin) then
return false
end
-- 禁止拉黑房主
local result, hostUin = Player:getHostUin()
if result == 0 and playerUin == tonumber(hostUin) then
return false
end
local blacklist = AreaCopy.Modules.Config:Get("permission", "blacklist") or {}
table.insert(blacklist, playerUin)
AreaCopy.Modules.Config:Set("permission", "blacklist", blacklist)
return AreaCopy.Modules.Config:SaveToArchive()
end
-- 从黑名单移除玩家
function AreaCopy.Modules.Permission:RemoveBlacklist(playerUin)
playerUin = tonumber(playerUin)
if not playerUin then return false end
local blacklist = AreaCopy.Modules.Config:Get("permission", "blacklist") or {}
local newBlacklist = {}
local isRemoved = false
for _, uin in ipairs(blacklist) do
if tonumber(uin) ~= playerUin then
table.insert(newBlacklist, uin)
else
isRemoved = true
end
end
if not isRemoved then return false end
AreaCopy.Modules.Config:Set("permission", "blacklist", newBlacklist)
return AreaCopy.Modules.Config:SaveToArchive()
end
-- 获取黑名单列表
function AreaCopy.Modules.Permission:GetBlacklist()
return AreaCopy.Modules.Config:Get("permission", "blacklist") or {}
end
-- ==========================================
-- 区域记录模块
-- 职责:多模式区域坐标记录、区域数据计算、预览渲染、玩家数据生命周期管理
-- ==========================================
AreaCopy.Modules.Record = {
isInited = false
}
-- 玩家数据初始化
function AreaCopy.Modules.Record:InitPlayerData(playerUin)
if not AreaCopy.Runtime.playerData[playerUin] then
AreaCopy.Runtime.playerData[playerUin] = {
selection = {}, -- 区域选择坐标
previewBlocks = {}, -- 预览方块备份数据
undoQueue = {}, -- 撤销队列
copyCache = {}, -- 复制缓存数据
templates = {} -- 玩家模板库
}
end
end
-- 清理玩家全量数据(玩家离开时调用)
function AreaCopy.Modules.Record:ClearPlayerData(playerUin)
if not AreaCopy.Runtime.playerData[playerUin] then return end
-- 清除预览方块
self:ClearPreview(playerUin)
-- 释放内存
AreaCopy.Runtime.playerData[playerUin] = nil
AreaCopy.Runtime.copyTaskLocks[playerUin] = nil
print(string.format("[AreaCopy] 玩家%d数据已清理", playerUin))
end
-- 模块初始化
function AreaCopy.Modules.Record:Init()
if self.isInited then return end
-- 注册玩家放置方块事件(修复原代码事件名错误)
ScriptSupportEvent:registerEvent([=[Block.PlaceBy]=], function(e)
self:OnBlockPlaceEvent(e)
end)
-- 注册玩家道具使用事件
ScriptSupportEvent:registerEvent([=[Player.UseItem]=], function(e)
self:OnItemUseEvent(e)
end)
-- 注册玩家离开游戏事件(统一数据清理入口)
ScriptSupportEvent:registerEvent([=[Game.AnyPlayer.LeaveGame]=], function(e)
self:ClearPlayerData(e.eventobjid)
end)
self.isInited = true
print("[AreaCopy] 区域记录模块初始化完成")
end
-- 设置记录模式
function AreaCopy.Modules.Record:SetMode(mode)
mode = tonumber(mode)
if not mode or not AreaCopy.ENUM.RECORD_MODE[mode] then
return false
end
AreaCopy.Modules.Config:Set("record", "mode", mode)
return AreaCopy.Modules.Config:SaveToArchive()
end
-- 获取当前记录模式
function AreaCopy.Modules.Record:GetMode()
return AreaCopy.Modules.Config:Get("record", "mode") or AreaCopy.ENUM.RECORD_MODE.CHAT_CMD
end
-- 记录区域坐标点
function AreaCopy.Modules.Record:RecordPosition(playerUin, posIndex, x, y, z)
self:InitPlayerData(playerUin)
-- 坐标自动补全:未传入坐标则使用玩家当前位置
if not x or not y or not z then
local result, px, py, pz = Player:getPosition(playerUin)
if result ~= 0 then
return false, "无法获取玩家当前位置,请手动输入坐标"
end
x, y, z = math.floor(px), math.floor(py), math.floor(pz)
else
x, y, z = math.floor(x), math.floor(y), math.floor(z)
end
-- 坐标合法性校验
if y < 0 or y > 256 then
return false, "高度坐标超出合法范围(0-256)"
end
-- 记录坐标点
local selection = AreaCopy.Runtime.playerData[playerUin].selection
if posIndex == 1 then
selection.pos1 = {x = x, y = y, z = z}
else
selection.pos2 = {x = x, y = y, z = z}
end
-- 双点记录完成,计算区域信息
if selection.pos1 and selection.pos2 then
local areaInfo = self:CalcPlayerArea(playerUin)
local volume = areaInfo.sizeX * areaInfo.sizeY * areaInfo.sizeZ
return true, string.format("区域选择完成,尺寸:%d×%d×%d,总计%d个方块",
areaInfo.sizeX, areaInfo.sizeY, areaInfo.sizeZ, volume)
end
return true, string.format("已记录%d号坐标点:(%d, %d, %d)", posIndex, x, y, z)
end
-- 方块放置事件处理
function AreaCopy.Modules.Record:OnBlockPlaceEvent(e)
local playerUin = e.eventobjid
local currentMode = self:GetMode()
-- 非方块放置模式直接拦截
if currentMode ~= AreaCopy.ENUM.RECORD_MODE.BLOCK_PLACE then
return
end
-- 校验记录方块ID
local recordBlockId = AreaCopy.Modules.Config:Get("record", "blockId")
if e.blockid ~= recordBlockId then
return
end
-- 记录坐标
local selection = AreaCopy.Runtime.playerData[playerUin] and AreaCopy.Runtime.playerData[playerUin].selection
local posIndex = (not selection or not selection.pos1) and 1 or 2
local success, msg = self:RecordPosition(playerUin, posIndex, e.x, e.y, e.z)
if msg then
Chat:sendSystemMsg(msg, playerUin)
end
-- 销毁记录用方块,不生成掉落物
Block:destroyBlock(e.x, e.y, e.z, false)
end
-- 道具使用事件处理
function AreaCopy.Modules.Record:OnItemUseEvent(e)
local playerUin = e.eventobjid
local currentMode = self:GetMode()
-- 非道具点击模式直接拦截
if currentMode ~= AreaCopy.ENUM.RECORD_MODE.ITEM_CLICK then
return
end
-- 校验记录道具ID
local recordItemId = AreaCopy.Modules.Config:Get("record", "itemId")
if e.itemid ~= recordItemId then
return
end
-- 获取玩家准星指向坐标
local result, x, y, z = Player:getAimPos(playerUin)
if result ~= 0 then
Chat:sendSystemMsg("无法获取准星指向坐标", playerUin)
return
end
-- 记录坐标
local selection = AreaCopy.Runtime.playerData[playerUin] and AreaCopy.Runtime.playerData[playerUin].selection
local posIndex = (not selection or not selection.pos1) and 1 or 2
local success, msg = self:RecordPosition(playerUin, posIndex, x, y, z)
if msg then
Chat:sendSystemMsg(msg, playerUin)
end
end
-- 计算玩家选择的区域边界数据
function AreaCopy.Modules.Record:CalcPlayerArea(playerUin)
local selection = AreaCopy.Runtime.playerData[playerUin] and AreaCopy.Runtime.playerData[playerUin].selection
if not selection or not selection.pos1 or not selection.pos2 then
return nil
end
-- 计算区域最小/最大边界
local minX, maxX = math.min(selection.pos1.x, selection.pos2.x), math.max(selection.pos1.x, selection.pos2.x)
local minY, maxY = math.min(selection.pos1.y, selection.pos2.y), math.max(selection.pos1.y, selection.pos2.y)
local minZ, maxZ = math.min(selection.pos1.z, selection.pos2.z), math.max(selection.pos1.z, selection.pos2.z)
-- 返回标准化区域数据
return {
minX = minX, maxX = maxX,
minY = minY, maxY = maxY,
minZ = minZ, maxZ = maxZ,
sizeX = maxX - minX + 1,
sizeY = maxY - minY + 1,
sizeZ = maxZ - minZ + 1,
centerX = (minX + maxX) / 2,
centerZ = (minZ + maxZ) / 2
}
end
-- 清除玩家区域选择数据
function AreaCopy.Modules.Record:ClearSelection(playerUin)
if not AreaCopy.Runtime.playerData[playerUin] then return false end
-- 先清除预览
self:ClearPreview(playerUin)
-- 重置选择数据
AreaCopy.Runtime.playerData[playerUin].selection = {}
return true
end
-- 绘制区域预览边框(仅空气位置渲染,避免覆盖原有方块)
function AreaCopy.Modules.Record:DrawPreview(playerUin)
if not AreaCopy.Modules.Config:Get("special", "enablePreview") then
return false, "预览功能已禁用"
end
local areaInfo = self:CalcPlayerArea(playerUin)
if not areaInfo then
return false, "请先完成区域双点选择"
end
-- 清除旧预览
self:ClearPreview(playerUin)
local previewBlockId = AreaCopy.Modules.Config:Get("special", "previewBlockId")
local playerData = AreaCopy.Runtime.playerData[playerUin]
playerData.previewBlocks = {}
-- 边框线段绘制函数
local function drawLine(x1, y1, z1, x2, y2, z2)
local dx = x2 - x1
local dy = y2 - y1
local dz = z2 - z1
local steps = math.max(math.abs(dx), math.abs(dy), math.abs(dz))
for i = 0, steps do
local x = math.floor(x1 + dx * i / steps)
local y = math.floor(y1 + dy * i / steps)
local z = math.floor(z1 + dz * i / steps)
-- 坐标合法性校验
if y < 0 or y > 256 then
goto continue
end
-- 仅在空气位置渲染预览方块,避免覆盖原有方块
local result, blockId = Block:getBlockID(x, y, z)
if result ~= 0 or blockId ~= 0 then
goto continue
end
-- 记录原方块数据
table.insert(playerData.previewBlocks, {
x = x, y = y, z = z,
oldId = blockId, oldData = 0
})
-- 放置预览方块
Block:setBlockAll(x, y, z, previewBlockId, 0)
::continue::
end
end
-- 绘制区域12条边框线
drawLine(areaInfo.minX, areaInfo.minY, areaInfo.minZ, areaInfo.maxX, areaInfo.minY, areaInfo.minZ)
drawLine(areaInfo.minX, areaInfo.maxY, areaInfo.minZ, areaInfo.maxX, areaInfo.maxY, areaInfo.minZ)
drawLine(areaInfo.minX, areaInfo.minY, areaInfo.maxZ, areaInfo.maxX, areaInfo.minY, areaInfo.maxZ)
drawLine(areaInfo.minX, areaInfo.maxY, areaInfo.maxZ, areaInfo.maxX, areaInfo.maxY, areaInfo.maxZ)
drawLine(areaInfo.minX, areaInfo.minY, areaInfo.minZ, areaInfo.minX, areaInfo.maxY, areaInfo.minZ)
drawLine(areaInfo.maxX, areaInfo.minY, areaInfo.minZ, areaInfo.maxX, areaInfo.maxY, areaInfo.minZ)
drawLine(areaInfo.minX, areaInfo.minY, areaInfo.maxZ, areaInfo.minX, areaInfo.maxY, areaInfo.maxZ)
drawLine(areaInfo.maxX, areaInfo.minY, areaInfo.maxZ, areaInfo.maxX, areaInfo.maxY, areaInfo.maxZ)
drawLine(areaInfo.minX, areaInfo.minY, areaInfo.minZ, areaInfo.minX, areaInfo.minY, areaInfo.maxZ)
drawLine(areaInfo.maxX, areaInfo.minY, areaInfo.minZ, areaInfo.maxX, areaInfo.minY, areaInfo.maxZ)
drawLine(areaInfo.minX, areaInfo.maxY, areaInfo.minZ, areaInfo.minX, areaInfo.maxY, areaInfo.maxZ)
drawLine(areaInfo.maxX, areaInfo.maxY, areaInfo.minZ, areaInfo.maxX, areaInfo.maxY, areaInfo.maxZ)
return true, "区域预览边框已渲染"
end
-- 清除玩家预览方块,还原场景
function AreaCopy.Modules.Record:ClearPreview(playerUin)
local playerData = AreaCopy.Runtime.playerData[playerUin]
if not playerData or not playerData.previewBlocks or #playerData.previewBlocks == 0 then
return false
end
-- 还原原方块数据
for _, block in ipairs(playerData.previewBlocks) do
Block:setBlockAll(block.x, block.y, block.z, block.oldId, block.oldData)
end
-- 清空预览数据
playerData.previewBlocks = {}
return true
end
-- ==========================================
-- 方块过滤模块
-- 职责:方块过滤规则管理、过滤校验、预设管理
-- ==========================================
AreaCopy.Modules.Filter = {
-- 常用方块预设组
presetGroups = {
建筑 = {1, 2, 3, 4, 5, 7, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 46, 47, 101},
矿石 = {10, 11, 14, 15, 16, 41, 42, 43, 44, 45},
电路 = {58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80},
自然 = {2, 3, 4, 5, 6, 8, 9, 12, 13, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40},
功能 = {53, 54, 55, 56, 57, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100}
},
-- 方块ID-名称映射表
blockNameMap = {
[0] = "空气", [1] = "基岩", [2] = "草方块", [3] = "泥土", [4] = "圆石",
[5] = "木板", [10] = "煤矿石", [11] = "铁矿石", [12] = "沙子", [13] = "沙砾",
[14] = "金矿石", [15] = "钻石矿石", [16] = "青金石矿石", [41] = "金块",
[42] = "铁块", [43] = "钻石块", [45] = "青金石块", [46] = "TNT", [47] = "蜘蛛网", [101] = "土块"
},
isInited = false
}
function AreaCopy.Modules.Filter:Init()
if self.isInited then return end
self.isInited = true
print("[AreaCopy] 过滤模块初始化完成")
end
-- 设置过滤模式
function AreaCopy.Modules.Filter:SetMode(mode)
mode = tonumber(mode)
if not mode or not AreaCopy.ENUM.FILTER_MODE[mode] then
return false
end
AreaCopy.Modules.Config:Set("filter", "mode", mode)
return AreaCopy.Modules.Config:SaveToArchive()
end
-- 获取当前过滤模式
function AreaCopy.Modules.Filter:GetMode()
return AreaCopy.Modules.Config:Get("filter", "mode") or AreaCopy.ENUM.FILTER_MODE.ALL
end
-- 设置是否复制空气方块
function AreaCopy.Modules.Filter:SetCopyAir(enable)
AreaCopy.Modules.Config:Set("filter", "copyAir", enable)
return AreaCopy.Modules.Config:SaveToArchive()
end
-- 获取是否复制空气方块
function AreaCopy.Modules.Filter:GetCopyAir()
return AreaCopy.Modules.Config:Get("filter", "copyAir") or false
end
-- 方块过滤校验核心函数
function AreaCopy.Modules.Filter:CheckBlockValid(blockId)
local filterMode = self:GetMode()
local copyAir = self:GetCopyAir()
-- 空气方块特殊处理
if blockId == 0 then
return copyAir
end
-- 全量复制模式
if filterMode == AreaCopy.ENUM.FILTER_MODE.ALL then
return true
end
-- 白名单模式
if filterMode == AreaCopy.ENUM.FILTER_MODE.WHITELIST then
local whitelist = AreaCopy.Modules.Config:Get("filter", "whitelist") or {}
for _, id in ipairs(whitelist) do
if id == blockId then
return true
end
end
return false
end
-- 黑名单模式
if filterMode == AreaCopy.ENUM.FILTER_MODE.BLACKLIST then
local blacklist = AreaCopy.Modules.Config:Get("filter", "blacklist") or {}
for _, id in ipairs(blacklist) do
if id == blockId then
return false
end
end
return true
end
return true
end
-- 添加方块到白名单
function AreaCopy.Modules.Filter:AddToWhitelist(blockId)
blockId = math.floor(tonumber(blockId) or -1)
if blockId < 0 then return false end
local whitelist = AreaCopy.Modules.Config:Get("filter", "whitelist") or {}
-- 去重校验
for _, id in ipairs(whitelist) do
if id == blockId then
return false
end
end
table.insert(whitelist, blockId)
AreaCopy.Modules.Config:Set("filter", "whitelist", whitelist)
return AreaCopy.Modules.Config:SaveToArchive()
end
-- 从白名单移除方块
function AreaCopy.Modules.Filter:RemoveFromWhitelist(blockId)
blockId = math.floor(tonumber(blockId) or -1)
if blockId < 0 then return false end
local whitelist = AreaCopy.Modules.Config:Get("filter", "whitelist") or {}
local newWhitelist = {}
local isRemoved = false
for _, id in ipairs(whitelist) do
if id ~= blockId then
table.insert(newWhitelist, id)
else
isRemoved = true
end
end
if not isRemoved then return false end
AreaCopy.Modules.Config:Set("filter", "whitelist", newWhitelist)
return AreaCopy.Modules.Config:SaveToArchive()
end
-- 添加方块到黑名单
function AreaCopy.Modules.Filter:AddToBlacklist(blockId)
blockId = math.floor(tonumber(blockId) or -1)
if blockId < 0 then return false end
local blacklist = AreaCopy.Modules.Config:Get("filter", "blacklist") or {}
-- 去重校验
for _, id in ipairs(blacklist) do
if id == blockId then
return false
end
end
table.insert(blacklist, blockId)
AreaCopy.Modules.Config:Set("filter", "blacklist", blacklist)
return AreaCopy.Modules.Config:SaveToArchive()
end
-- 从黑名单移除方块
function AreaCopy.Modules.Filter:RemoveFromBlacklist(blockId)
blockId = math.floor(tonumber(blockId) or -1)
if blockId < 0 then return false end
local blacklist = AreaCopy.Modules.Config:Get("filter", "blacklist") or {}
local newBlacklist = {}
local isRemoved = false
for _, id in ipairs(blacklist) do
if id ~= blockId then
table.insert(newBlacklist, id)
else
isRemoved = true
end
end
if not isRemoved then return false end
AreaCopy.Modules.Config:Set("filter", "blacklist", newBlacklist)
return AreaCopy.Modules.Config:SaveToArchive()
end
-- 添加预设方块组到过滤列表
function AreaCopy.Modules.Filter:AddPresetGroup(presetName, isWhitelist)
local preset = self.presetGroups[presetName]
if not preset then return 0 end
local addCount = 0
if isWhitelist then
for _, blockId in ipairs(preset) do
if self:AddToWhitelist(blockId) then
addCount = addCount + 1
end
end
else
for _, blockId in ipairs(preset) do
if self:AddToBlacklist(blockId) then
addCount = addCount + 1
end
end
end
return addCount
end
-- 清空过滤列表
function AreaCopy.Modules.Filter:ClearList(isWhitelist)
if isWhitelist then
AreaCopy.Modules.Config:Set("filter", "whitelist", {})
else
AreaCopy.Modules.Config:Set("filter", "blacklist", {})
end
return AreaCopy.Modules.Config:SaveToArchive()
end
-- 获取方块名称
function AreaCopy.Modules.Filter:GetBlockName(blockId)
return self.blockNameMap[blockId] or string.format("方块ID:%d", blockId)
end
-- 获取过滤状态详情
function AreaCopy.Modules.Filter:GetStatusInfo()
local mode = self:GetMode()
local modeNameMap = {[0] = "全量复制", [1] = "白名单模式", [2] = "黑名单模式"}
local whitelist = AreaCopy.Modules.Config:Get("filter", "whitelist") or {}
local blacklist = AreaCopy.Modules.Config:Get("filter", "blacklist") or {}
return {
modeName = modeNameMap[mode] or "未知模式",
copyAir = self:GetCopyAir() and "开启" or "关闭",
whitelistCount = #whitelist,
blacklistCount = #blacklist
}
end
-- ==========================================
-- 复制核心模块
-- 职责:区域方块读取、旋转/镜像计算、批量粘贴、撤销管理、模板管理
-- ==========================================
AreaCopy.Modules.Copy = {
isInited = false
}
function AreaCopy.Modules.Copy:Init()
if self.isInited then return end
self.isInited = true
print("[AreaCopy] 复制核心模块初始化完成")
end
-- 坐标旋转计算(基于区域中心的旋转矩阵)
function AreaCopy.Modules.Copy:RotateCoord(relX, relZ, rotation)
local newX, newZ = relX, relZ
if rotation == AreaCopy.ENUM.ROTATION.ANGLE_90 then
newX = -relZ
newZ = relX
elseif rotation == AreaCopy.ENUM.ROTATION.ANGLE_180 then
newX = -relX
newZ = -relZ
elseif rotation == AreaCopy.ENUM.ROTATION.ANGLE_270 then
newX = relZ
newZ = -relX
end
return newX, newZ
end
-- 坐标镜像计算
function AreaCopy.Modules.Copy:MirrorCoord(relX, enableMirror)
if not enableMirror then
return relX
end
return -relX
end
-- 读取区域方块数据(预过滤,优化内存占用)
function AreaCopy.Modules.Copy:ReadAreaData(areaInfo)
local blockData = {}
local minX, maxX = areaInfo.minX, areaInfo.maxX
local minY, maxY = areaInfo.minY, areaInfo.maxY
local minZ, maxZ = areaInfo.minZ, areaInfo.maxZ
-- 优化遍历顺序:Y轴优先,减少区块加载切换开销
for y = minY, maxY do
for x = minX, maxX do
for z = minZ, maxZ do
local result, blockId = Block:getBlockID(x, y, z)
local result2, blockDataVal = Block:getBlockData(x, y, z)
-- 过滤无效方块
if result ~= 0 or result2 ~= 0 then
goto continue
end
-- 应用过滤规则
if not AreaCopy.Modules.Filter:CheckBlockValid(blockId) then
goto continue
end
-- 存储相对坐标,减少后续计算量
table.insert(blockData, {
relX = x - minX,
relY = y - minY,
relZ = z - minZ,
blockId = blockId,
blockData = blockDataVal
})
::continue::
end
end
end
return blockData
end
-- 执行区域复制粘贴
function AreaCopy.Modules.Copy:ExecuteCopy(playerUin, targetX, targetY, targetZ, rotation, mirror)
-- 并发锁校验,防止重复执行
if AreaCopy.Runtime.copyTaskLocks[playerUin] then
return false, "正在执行复制操作,请等待当前任务完成"
end
-- 初始化玩家数据
AreaCopy.Modules.Record:InitPlayerData(playerUin)
-- 获取区域信息
local areaInfo = AreaCopy.Modules.Record:CalcPlayerArea(playerUin)
if not areaInfo then
return false, "请先完成区域双点选择"
end
-- 参数标准化
targetX, targetY, targetZ = math.floor(targetX), math.floor(targetY), math.floor(targetZ)
rotation = tonumber(rotation) or AreaCopy.Modules.Config:Get("copy", "defaultRotation")
mirror = mirror ~= nil and mirror or AreaCopy.Modules.Config:Get("copy", "defaultMirror")
-- 旋转功能校验
if not AreaCopy.Modules.Config:Get("copy", "enableRotate") and rotation ~= 0 then
return false, "旋转功能已禁用"
end
-- 镜像功能校验
if not AreaCopy.Modules.Config:Get("copy", "enableMirror") and mirror then
return false, "镜像功能已禁用"
end
-- 读取源区域方块数据
local blockData = self:ReadAreaData(areaInfo)
if #blockData == 0 then
return false, "所选区域内无有效方块"
end
-- 缓存复制数据
AreaCopy.Runtime.playerData[playerUin].copyCache = {
blockData = blockData,
sourceArea = areaInfo,
rotation = rotation,
mirror = mirror
}
-- 加锁
AreaCopy.Runtime.copyTaskLocks[playerUin] = true
-- 批量放置配置
local batchSize = AreaCopy.Modules.Config:Get("copy", "batchSize")
local copyDelay = AreaCopy.Modules.Config:Get("copy", "copyDelay") / 1000
local totalCount = #blockData
local placedCount = 0
local batchIndex = 0
local undoData = {} -- 撤销数据记录
-- 区域尺寸计算
local sizeX = areaInfo.sizeX
local sizeZ = areaInfo.sizeZ
local centerOffsetX = (sizeX - 1) / 2
local centerOffsetZ = (sizeZ - 1) / 2
-- 分批放置协程函数
local function batchPlaceTask()
local endIndex = math.min((batchIndex + 1) * batchSize, totalCount)
for i = batchIndex * batchSize + 1, endIndex do
local data = blockData[i]
-- 计算旋转后的相对坐标
local relX = data.relX - centerOffsetX
local relZ = data.relZ - centerOffsetZ
local rotX, rotZ = self:RotateCoord(relX, relZ, rotation)
-- 镜像处理
rotX = self:MirrorCoord(rotX, mirror)
-- 计算最终绝对坐标
local finalX = math.floor(targetX + rotX + centerOffsetX)
local finalY = math.floor(targetY + data.relY)
local finalZ = math.floor(targetZ + rotZ + centerOffsetZ)
-- 坐标合法性校验
if finalY < 0 or finalY > 256 then
goto continue
end
-- 记录原方块数据,用于撤销
local result, oldBlockId = Block:getBlockID(finalX, finalY, finalZ)
local result2, oldData = Block:getBlockData(finalX, finalY, finalZ)
if result == 0 and result2 == 0 then
table.insert(undoData, {
x = finalX, y = finalY, z = finalZ,
oldId = oldBlockId, oldData = oldData,
newId = data.blockId, newData = data.blockData
})
end
-- 放置方块
Block:setBlockAll(finalX, finalY, finalZ, data.blockId, data.blockData)
placedCount = placedCount + 1
::continue::
end
batchIndex = batchIndex + 1
-- 未完成则继续下一批次
if batchIndex * batchSize < totalCount then
threadpool:wait(copyDelay)
batchPlaceTask()
else
-- 复制完成,释放锁
AreaCopy.Runtime.copyTaskLocks[playerUin] = false
-- 记录撤销数据
self:AddUndoRecord(playerUin, undoData)
Chat:sendSystemMsg(string.format("复制完成!成功放置%d个方块", placedCount), playerUin)
end
end
-- 启动协程执行批量放置
threadpool:new(batchPlaceTask)
return true, string.format("开始复制区域,总计%d个有效方块,正在执行...", totalCount)
end
-- 添加撤销记录
function AreaCopy.Modules.Copy:AddUndoRecord(playerUin, undoData)
if not AreaCopy.Modules.Config:Get("special", "enableUndo") then
return
end
local playerData = AreaCopy.Runtime.playerData[playerUin]
if not playerData then return end
local maxSteps = AreaCopy.Modules.Config:Get("special", "undoMaxSteps")
-- 超出最大步数,移除最早的记录
if #playerData.undoQueue >= maxSteps then
table.remove(playerData.undoQueue, 1)
end
table.insert(playerData.undoQueue, undoData)
end
-- 执行撤销操作
function AreaCopy.Modules.Copy:ExecuteUndo(playerUin)
if not AreaCopy.Modules.Config:Get("special", "enableUndo") then
return false, "撤销功能已禁用"
end
local playerData = AreaCopy.Runtime.playerData[playerUin]
if not playerData or #playerData.undoQueue == 0 then
return false, "无可用的撤销记录"
end
-- 取出最近一次操作记录
local undoData = table.remove(playerData.undoQueue)
local restoreCount = 0
-- 还原方块数据
for _, block in ipairs(undoData) do
Block:setBlockAll(block.x, block.y, block.z, block.oldId, block.oldData)
restoreCount = restoreCount + 1
end
return true, string.format("撤销成功!已还原%d个方块", restoreCount)
end
-- 保存区域为模板
function AreaCopy.Modules.Copy:SaveTemplate(playerUin, templateName)
if not AreaCopy.Modules.Config:Get("special", "enableTemplate") then
return false, "模板功能已禁用"
end
local areaInfo = AreaCopy.Modules.Record:CalcPlayerArea(playerUin)
if not areaInfo then
return false, "请先完成区域双点选择"
end
local blockData = self:ReadAreaData(areaInfo)
if #blockData == 0 then
return false, "所选区域内无有效方块"
end
local playerData = AreaCopy.Runtime.playerData[playerUin]
local maxTemplates = AreaCopy.Modules.Config:Get("special", "maxTemplates")
if #playerData.templates >= maxTemplates then
return false, string.format("模板数量已达上限%d个,请先删除旧模板", maxTemplates)
end
-- 保存模板
table.insert(playerData.templates, {
name = templateName,
blockData = blockData,
sourceArea = areaInfo,
createTime = os.time()
})
return true, string.format("模板「%s」保存成功!当前模板数量:%d", templateName, #playerData.templates)
end
-- 获取玩家模板列表
function AreaCopy.Modules.Copy:GetTemplateList(playerUin)
local playerData = AreaCopy.Runtime.playerData[playerUin]
if not playerData or #playerData.templates == 0 then
return nil
end
return playerData.templates
end
-- 使用模板粘贴
function AreaCopy.Modules.Copy:UseTemplate(playerUin, templateIndex, targetX, targetY, targetZ, rotation, mirror)
if AreaCopy.Runtime.copyTaskLocks[playerUin] then
return false, "正在执行复制操作,请等待当前任务完成"
end
local templates = self:GetTemplateList(playerUin)
if not templates then
return false, "你还没有保存任何模板"
end
templateIndex = tonumber(templateIndex)
if not templateIndex or templateIndex < 1 or templateIndex > #templates then
return false, "无效的模板索引"
end
-- 参数标准化
targetX, targetY, targetZ = math.floor(targetX), math.floor(targetY), math.floor(targetZ)
rotation = tonumber(rotation) or AreaCopy.Modules.Config:Get("copy", "defaultRotation")
mirror = mirror ~= nil and mirror or AreaCopy.Modules.Config:Get("copy", "defaultMirror")
-- 旋转/镜像功能校验
if not AreaCopy.Modules.Config:Get("copy", "enableRotate") and rotation ~= 0 then
return false, "旋转功能已禁用"
end
if not AreaCopy.Modules.Config:Get("copy", "enableMirror") and mirror then
return false, "镜像功能已禁用"
end
-- 获取模板数据
local template = templates[templateIndex]
local blockData = template.blockData
local areaInfo = template.sourceArea
-- 加锁
AreaCopy.Runtime.copyTaskLocks[playerUin] = true
-- 批量放置配置
local batchSize = AreaCopy.Modules.Config:Get("copy", "batchSize")
local copyDelay = AreaCopy.Modules.Config:Get("copy", "copyDelay") / 1000
local totalCount = #blockData
local placedCount = 0
local batchIndex = 0
local undoData = {}
-- 区域尺寸计算
local sizeX = areaInfo.sizeX
local sizeZ = areaInfo.sizeZ
local centerOffsetX = (sizeX - 1) / 2
local centerOffsetZ = (sizeZ - 1) / 2
-- 分批放置协程函数
local function templatePlaceTask()
local endIndex = math.min((batchIndex + 1) * batchSize, totalCount)
for i = batchIndex * batchSize + 1, endIndex do
local data = blockData[i]
-- 坐标变换计算
local relX = data.relX - centerOffsetX
local relZ = data.relZ - centerOffsetZ
local rotX, rotZ = self:RotateCoord(relX, relZ, rotation)
rotX = self:MirrorCoord(rotX, mirror)
-- 最终坐标
local finalX = math.floor(targetX + rotX + centerOffsetX)
local finalY = math.floor(targetY + data.relY)
local finalZ = math.floor(targetZ + rotZ + centerOffsetZ)
if finalY < 0 or finalY > 256 then
goto continue
end
-- 记录撤销数据
local result, oldBlockId = Block:getBlockID(finalX, finalY, finalZ)
local result2, oldData = Block:getBlockData(finalX, finalY, finalZ)
if result == 0 and result2 == 0 then
table.insert(undoData, {
x = finalX, y = finalY, z = finalZ,
oldId = oldBlockId, oldData = oldData,
newId = data.blockId, newData = data.blockData
})
end
-- 放置方块
Block:setBlockAll(finalX, finalY, finalZ, data.blockId, data.blockData)
placedCount = placedCount + 1
::continue::
end
batchIndex = batchIndex + 1
if batchIndex * batchSize < totalCount then
threadpool:wait(copyDelay)
templatePlaceTask()
else
AreaCopy.Runtime.copyTaskLocks[playerUin] = false
self:AddUndoRecord(playerUin, undoData)
Chat:sendSystemMsg(string.format("模板「%s」使用完成!成功放置%d个方块", template.name, placedCount), playerUin)
end
end
-- 启动协程
threadpool:new(templatePlaceTask)
return true, string.format("开始使用模板「%s」,总计%d个有效方块,正在执行...", template.name, totalCount)
end
-- 删除模板
function AreaCopy.Modules.Copy:DeleteTemplate(playerUin, templateIndex)
local templates = self:GetTemplateList(playerUin)
if not templates then
return false, "你还没有保存任何模板"
end
templateIndex = tonumber(templateIndex)
if not templateIndex or templateIndex < 1 or templateIndex > #templates then
return false, "无效的模板索引"
end
local templateName = templates[templateIndex].name
table.remove(templates, templateIndex)
return true, string.format("模板「%s」已删除", templateName)
end
-- ==========================================
-- 命令管理模块
-- 职责:命令注册、消息解析、权限校验、命令执行
-- ==========================================
AreaCopy.Modules.Command = {
cmdPrefix = "/copy",
cmdHandlers = {},
cmdAliases = {},
isInited = false
}
-- 注册命令
function AreaCopy.Modules.Command:RegisterCmd(name, handler, requireAdmin)
self.cmdHandlers[name] = {
handler = handler,
requireAdmin = requireAdmin or false
}
end
-- 注册命令别名
function AreaCopy.Modules.Command:RegisterAlias(alias, targetCmd)
self.cmdAliases[alias] = targetCmd
end
-- 模块初始化
function AreaCopy.Modules.Command:Init()
if self.isInited then return end
-- 注册基础命令
self:RegisterBaseCmds()
-- 注册聊天消息监听事件
ScriptSupportEvent:registerEvent([=[Player.NewInputContent]=], function(e)
self:OnChatMessageEvent(e)
end)
self.isInited = true
print("[AreaCopy] 命令管理模块初始化完成")
end
-- 注册所有基础命令
function AreaCopy.Modules.Command:RegisterBaseCmds()
-- 帮助命令
self:RegisterCmd("help", function(playerUin, args)
self:ShowHelpInfo(playerUin)
end, false)
self:RegisterAlias("?", "help")
-- 记录模式切换
self:RegisterCmd("mode", function(playerUin, args)
local mode = tonumber(args[1])
if not mode or not AreaCopy.ENUM.RECORD_MODE[mode] then
Chat:sendSystemMsg("用法:/copy mode <1|2|3> (1=聊天命令 2=方块放置 3=道具点击)", playerUin)
return
end
if AreaCopy.Modules.Record:SetMode(mode) then
local modeNameMap = {[1] = "聊天命令", [2] = "方块放置", [3] = "道具点击"}
Chat:sendSystemMsg(string.format("已切换为%s记录模式", modeNameMap[mode]), playerUin)
else
Chat:sendSystemMsg("模式切换失败", playerUin)
end
end, true)
-- 坐标点记录
self:RegisterCmd("pos1", function(playerUin, args)
local x, y, z = tonumber(args[1]), tonumber(args[2]), tonumber(args[3])
local success, msg = AreaCopy.Modules.Record:RecordPosition(playerUin, 1, x, y, z)
if msg then
Chat:sendSystemMsg(msg, playerUin)
end
end, false)
self:RegisterCmd("pos2", function(playerUin, args)
local x, y, z = tonumber(args[1]), tonumber(args[2]), tonumber(args[3])
local success, msg = AreaCopy.Modules.Record:RecordPosition(playerUin, 2, x, y, z)
if msg then
Chat:sendSystemMsg(msg, playerUin)
end
end, false)
-- 清除选择
self:RegisterCmd("clear", function(playerUin, args)
if AreaCopy.Modules.Record:ClearSelection(playerUin) then
Chat:sendSystemMsg("已清除当前区域选择数据", playerUin)
end
end, false)
-- 粘贴命令
self:RegisterCmd("paste", function(playerUin, args)
local x, y, z = tonumber(args[1]), tonumber(args[2]), tonumber(args[3])
-- 未传入坐标则使用玩家当前位置
if not x or not y or not z then
local result, px, py, pz = Player:getPosition(playerUin)
if result ~= 0 then
Chat:sendSystemMsg("无法获取当前位置,请手动输入目标坐标", playerUin)
return
end
x, y, z = math.floor(px), math.floor(py - 1), math.floor(pz)
end
-- 获取旋转/镜像配置
local rotation = AreaCopy.Modules.Config:Get("copy", "defaultRotation")
local mirror = AreaCopy.Modules.Config:Get("copy", "defaultMirror")
local success, msg = AreaCopy.Modules.Copy:ExecuteCopy(playerUin, x, y, z, rotation, mirror)
if msg then
Chat:sendSystemMsg(msg, playerUin)
end
end, false)
-- 旋转设置
self:RegisterCmd("rotate", function(playerUin, args)
local rotation = tonumber(args[1])
local validRotations = {0, 90, 180, 270}
local isValid = false
for _, v in ipairs(validRotations) do
if rotation == v then
isValid = true
break
end
end
if not isValid then
Chat:sendSystemMsg("用法:/copy rotate <0|90|180|270>", playerUin)
return
end
AreaCopy.Modules.Config:Set("copy", "defaultRotation", rotation)
AreaCopy.Modules.Config:SaveToArchive()
Chat:sendSystemMsg(string.format("默认旋转角度已设置为%d°", rotation), playerUin)
end, false)
-- 镜像设置
self:RegisterCmd("mirror", function(playerUin, args)
local option = args[1]
if not option or (option ~= "on" and option ~= "off") then
Chat:sendSystemMsg("用法:/copy mirror <on|off>", playerUin)
return
end
local mirror = (option == "on")
AreaCopy.Modules.Config:Set("copy", "defaultMirror", mirror)
AreaCopy.Modules.Config:SaveToArchive()
Chat:sendSystemMsg(string.format("镜像模式已%s", mirror and "开启" or "关闭"), playerUin)
end, false)
-- 过滤模式设置
self:RegisterCmd("filter", function(playerUin, args)
local mode = args[1]
local modeMap = {all = 0, white = 1, black = 2}
if not mode or modeMap[mode] == nil then
Chat:sendSystemMsg("用法:/copy filter <all|white|black>", playerUin)
return
end
AreaCopy.Modules.Filter:SetMode(modeMap[mode])
local modeNameMap = {all = "全量复制", white = "白名单", black = "黑名单"}
Chat:sendSystemMsg(string.format("过滤模式已设置为%s", modeNameMap[mode]), playerUin)
end, true)
-- 添加方块到过滤列表
self:RegisterCmd("addblock", function(playerUin, args)
local blockId = tonumber(args[1])
if not blockId then
Chat:sendSystemMsg("用法:/copy addblock <方块ID>", playerUin)
return
end
local filterMode = AreaCopy.Modules.Filter:GetMode()
if filterMode == AreaCopy.ENUM.FILTER_MODE.ALL then
Chat:sendSystemMsg("当前为全量复制模式,无需设置过滤规则", playerUin)
return
end
local success = false
local blockName = AreaCopy.Modules.Filter:GetBlockName(blockId)
if filterMode == AreaCopy.ENUM.FILTER_MODE.WHITELIST then
success = AreaCopy.Modules.Filter:AddToWhitelist(blockId)
if success then
Chat:sendSystemMsg(string.format("已将%s(ID:%d)添加到白名单", blockName, blockId), playerUin)
else
Chat:sendSystemMsg("该方块已在白名单中", playerUin)
end
else
success = AreaCopy.Modules.Filter:AddToBlacklist(blockId)
if success then
Chat:sendSystemMsg(string.format("已将%s(ID:%d)添加到黑名单", blockName, blockId), playerUin)
else
Chat:sendSystemMsg("该方块已在黑名单中", playerUin)
end
end
end, true)
-- 从过滤列表移除方块
self:RegisterCmd("removeblock", function(playerUin, args)
local blockId = tonumber(args[1])
if not blockId then
Chat:sendSystemMsg("用法:/copy removeblock <方块ID>", playerUin)
return
end
local filterMode = AreaCopy.Modules.Filter:GetMode()
if filterMode == AreaCopy.ENUM.FILTER_MODE.ALL then
Chat:sendSystemMsg("当前为全量复制模式,无需设置过滤规则", playerUin)
return
end
local success = false
if filterMode == AreaCopy.ENUM.FILTER_MODE.WHITELIST then
success = AreaCopy.Modules.Filter:RemoveFromWhitelist(blockId)
if success then
Chat:sendSystemMsg(string.format("已从白名单中移除方块ID:%d", blockId), playerUin)
else
Chat:sendSystemMsg("该方块不在白名单中", playerUin)
end
else
success = AreaCopy.Modules.Filter:RemoveFromBlacklist(blockId)
if success then
Chat:sendSystemMsg(string.format("已从黑名单中移除方块ID:%d", blockId), playerUin)
else
Chat:sendSystemMsg("该方块不在黑名单中", playerUin)
end
end
end, true)
-- 空气复制设置
self:RegisterCmd("air", function(playerUin, args)
local option = args[1]
if not option or (option ~= "on" and option ~= "off") then
Chat:sendSystemMsg("用法:/copy air <on|off>", playerUin)
return
end
local copyAir = (option == "on")
AreaCopy.Modules.Filter:SetCopyAir(copyAir)
Chat:sendSystemMsg(string.format("空气方块复制已%s", copyAir and "开启" or "关闭"), playerUin)
end, true)
-- 管理员管理
self:RegisterCmd("admin", function(playerUin, args)
local action = args[1]
local targetUin = tonumber(args[2])
if not action then
Chat:sendSystemMsg("用法:/copy admin <add|remove|list> [玩家迷你号]", playerUin)
return
end
if action == "add" then
if not targetUin then
Chat:sendSystemMsg("请输入有效的玩家迷你号", playerUin)
return
end
if AreaCopy.Modules.Permission:AddAdmin(targetUin) then
Chat:sendSystemMsg(string.format("已将玩家%d添加为管理员", targetUin), playerUin)
else
Chat:sendSystemMsg("添加失败,该玩家已是管理员", playerUin)
end
elseif action == "remove" then
if not targetUin then
Chat:sendSystemMsg("请输入有效的玩家迷你号", playerUin)
return
end
if AreaCopy.Modules.Permission:RemoveAdmin(targetUin) then
Chat:sendSystemMsg(string.format("已移除管理员%d", targetUin), playerUin)
else
Chat:sendSystemMsg("移除失败,该玩家不是管理员或无法移除房主", playerUin)
end
elseif action == "list" then
local adminList = AreaCopy.Modules.Permission:GetAdminList()
Chat:sendSystemMsg("===== 管理员列表 =====", playerUin)
for i, uin in ipairs(adminList) do
local result, name = Player:getNickname(uin)
name = name or tostring(uin)
Chat:sendSystemMsg(string.format("%d. %s (%d)", i, name, uin), playerUin)
end
else
Chat:sendSystemMsg("未知操作:" .. action, playerUin)
end
end, true)
-- 黑名单管理
self:RegisterCmd("blacklist", function(playerUin, args)
local action = args[1]
local targetUin = tonumber(args[2])
if not action then
Chat:sendSystemMsg("用法:/copy blacklist <add|remove|list> [玩家迷你号]", playerUin)
return
end
if action == "add" then
if not targetUin then
Chat:sendSystemMsg("请输入有效的玩家迷你号", playerUin)
return
end
if AreaCopy.Modules.Permission:AddBlacklist(targetUin) then
Chat:sendSystemMsg(string.format("已将玩家%d添加到黑名单", targetUin), playerUin)
else
Chat:sendSystemMsg("添加失败,该玩家已在黑名单中", playerUin)
end
elseif action == "remove" then
if not targetUin then
Chat:sendSystemMsg("请输入有效的玩家迷你号", playerUin)
return
end
if AreaCopy.Modules.Permission:RemoveBlacklist(targetUin) then
Chat:sendSystemMsg(string.format("已从黑名单中移除玩家%d", targetUin), playerUin)
else
Chat:sendSystemMsg("移除失败,该玩家不在黑名单中", playerUin)
end
elseif action == "list" then
local blacklist = AreaCopy.Modules.Permission:GetBlacklist()
Chat:sendSystemMsg("===== 黑名单列表 =====", playerUin)
if #blacklist == 0 then
Chat:sendSystemMsg("黑名单为空", playerUin)
else
for i, uin in ipairs(blacklist) do
local result, name = Player:getNickname(uin)
name = name or tostring(uin)
Chat:sendSystemMsg(string.format("%d. %s (%d)", i, name, uin), playerUin)
end
end
else
Chat:sendSystemMsg("未知操作:" .. action, playerUin)
end
end, true)
-- 配置管理
self:RegisterCmd("config", function(playerUin, args)
local action = args[1]
if not action then
Chat:sendSystemMsg("用法:/copy config <show|save|reset>", playerUin)
return
end
if action == "show" then
self:ShowConfigInfo(playerUin)
elseif action == "save" then
if AreaCopy.Modules.Config:SaveToArchive() then
Chat:sendSystemMsg("当前配置已持久化到存档", playerUin)
else
Chat:sendSystemMsg("配置保存失败", playerUin)
end
elseif action == "reset" then
if AreaCopy.Modules.Config:Reset() then
Chat:sendSystemMsg("配置已重置为默认值", playerUin)
else
Chat:sendSystemMsg("配置重置失败", playerUin)
end
else
Chat:sendSystemMsg("未知操作:" .. action, playerUin)
end
end, true)
-- 撤销操作
self:RegisterCmd("undo", function(playerUin, args)
local success, msg = AreaCopy.Modules.Copy:ExecuteUndo(playerUin)
if msg then
Chat:sendSystemMsg(msg, playerUin)
end
end, false)
-- 预览管理
self:RegisterCmd("preview", function(playerUin, args)
local success, msg = AreaCopy.Modules.Record:DrawPreview(playerUin)
if msg then
Chat:sendSystemMsg(msg, playerUin)
end
end, false)
self:RegisterCmd("nopreview", function(playerUin, args)
if AreaCopy.Modules.Record:ClearPreview(playerUin) then
Chat:sendSystemMsg("已清除区域预览", playerUin)
end
end, false)
-- 模板管理
self:RegisterCmd("template", function(playerUin, args)
if not AreaCopy.Modules.Config:Get("special", "enableTemplate") then
Chat:sendSystemMsg("模板功能已禁用", playerUin)
return
end
local action = args[1]
if not action then
Chat:sendSystemMsg("用法:/copy template <save|list|use|delete> [参数]", playerUin)
return
end
if action == "save" then
local templateName = args[2]
if not templateName then
Chat:sendSystemMsg("用法:/copy template save <模板名称>", playerUin)
return
end
local success, msg = AreaCopy.Modules.Copy:SaveTemplate(playerUin, templateName)
if msg then
Chat:sendSystemMsg(msg, playerUin)
end
elseif action == "list" then
local templates = AreaCopy.Modules.Copy:GetTemplateList(playerUin)
if not templates then
Chat:sendSystemMsg("你还没有保存任何模板", playerUin)
return
end
Chat:sendSystemMsg("===== 我的模板列表 =====", playerUin)
for i, template in ipairs(templates) do
local size = string.format("%d×%d×%d", template.sourceArea.sizeX, template.sourceArea.sizeY, template.sourceArea.sizeZ)
Chat:sendSystemMsg(string.format("%d. %s (%s)", i, template.name, size), playerUin)
end
elseif action == "use" then
local templateIndex = tonumber(args[2])
if not templateIndex then
Chat:sendSystemMsg("用法:/copy template use <模板索引> [x y z]", playerUin)
return
end
local x, y, z = tonumber(args[3]), tonumber(args[4]), tonumber(args[5])
-- 未传入坐标则使用玩家当前位置
if not x or not y or not z then
local result, px, py, pz = Player:getPosition(playerUin)
if result ~= 0 then
Chat:sendSystemMsg("无法获取当前位置,请手动输入目标坐标", playerUin)
return
end
x, y, z = math.floor(px), math.floor(py - 1), math.floor(pz)
end
local rotation = AreaCopy.Modules.Config:Get("copy", "defaultRotation")
local mirror = AreaCopy.Modules.Config:Get("copy", "defaultMirror")
local success, msg = AreaCopy.Modules.Copy:UseTemplate(playerUin, templateIndex, x, y, z, rotation, mirror)
if msg then
Chat:sendSystemMsg(msg, playerUin)
end
elseif action == "delete" then
local templateIndex = tonumber(args[2])
if not templateIndex then
Chat:sendSystemMsg("用法:/copy template delete <模板索引>", playerUin)
return
end
local success, msg = AreaCopy.Modules.Copy:DeleteTemplate(playerUin, templateIndex)
if msg then
Chat:sendSystemMsg(msg, playerUin)
end
else
Chat:sendSystemMsg("未知操作:" .. action, playerUin)
end
end, false)
end
-- 聊天消息事件处理
function AreaCopy.Modules.Command:OnChatMessageEvent(e)
local message = e.content
local playerUin = e.eventobjid
-- 匹配命令前缀
if not string.find(message, "^" .. self.cmdPrefix) then
return
end
-- 解析命令参数
local args = {}
for part in string.gmatch(message, "%S+") do
table.insert(args, part)
end
-- 提取命令名
local cmdName = args[2] or "help"
-- 别名解析
if self.cmdAliases[cmdName] then
cmdName = self.cmdAliases[cmdName]
end
-- 校验命令是否存在
local cmdInfo = self.cmdHandlers[cmdName]
if not cmdInfo then
Chat:sendSystemMsg(string.format("未知命令:%s,输入 /copy help 查看帮助文档", cmdName), playerUin)
return
end
-- 权限校验
local hasPermission, errMsg = AreaCopy.Modules.Permission:CheckCommandPermission(playerUin, cmdInfo.requireAdmin)
if not hasPermission then
Chat:sendSystemMsg(errMsg, playerUin)
return
end
-- 提取命令参数
local cmdArgs = {}
for i = 3, #args do
table.insert(cmdArgs, args[i])
end
-- 执行命令处理函数
cmdInfo.handler(playerUin, cmdArgs)
end
-- 显示帮助信息
function AreaCopy.Modules.Command:ShowHelpInfo(playerUin)
Chat:sendSystemMsg("===== 区域复制脚本 V2.2 帮助 =====", playerUin)
Chat:sendSystemMsg("【基础命令】", playerUin)
Chat:sendSystemMsg("/copy help - 显示帮助文档", playerUin)
Chat:sendSystemMsg("/copy mode <1|2|3> - 切换区域记录模式", playerUin)
Chat:sendSystemMsg("/copy pos1 [x y z] - 记录区域起点坐标", playerUin)
Chat:sendSystemMsg("/copy pos2 [x y z] - 记录区域终点坐标", playerUin)
Chat:sendSystemMsg("/copy clear - 清除当前区域选择", playerUin)
Chat:sendSystemMsg("/copy paste [x y z] - 粘贴区域到指定位置", playerUin)
Chat:sendSystemMsg("【高级配置】", playerUin)
Chat:sendSystemMsg("/copy rotate <0|90|180|270> - 设置默认旋转角度", playerUin)
Chat:sendSystemMsg("/copy mirror <on|off> - 开启/关闭镜像粘贴", playerUin)
Chat:sendSystemMsg("/copy filter <all|white|black> - 设置方块过滤模式", playerUin)
Chat:sendSystemMsg("/copy addblock <方块ID> - 添加方块到过滤列表", playerUin)
Chat:sendSystemMsg("/copy removeblock <方块ID> - 从过滤列表移除方块", playerUin)
Chat:sendSystemMsg("/copy air <on|off> - 开启/关闭空气方块复制", playerUin)
-- 仅管理员显示管理命令
if AreaCopy.Modules.Permission:IsAdmin(playerUin) then
Chat:sendSystemMsg("【管理命令】", playerUin)
Chat:sendSystemMsg("/copy admin <add|remove|list> - 管理员管理", playerUin)
Chat:sendSystemMsg("/copy blacklist <add|remove|list> - 黑名单管理", playerUin)
Chat:sendSystemMsg("/copy config <show|save|reset> - 配置管理", playerUin)
end
Chat:sendSystemMsg("【功能扩展】", playerUin)
Chat:sendSystemMsg("/copy undo - 撤销上一次粘贴操作", playerUin)
Chat:sendSystemMsg("/copy preview - 渲染区域预览边框", playerUin)
Chat:sendSystemMsg("/copy nopreview - 清除区域预览", playerUin)
Chat:sendSystemMsg("/copy template <save|list|use|delete> - 模板管理", playerUin)
end
-- 显示当前配置信息
function AreaCopy.Modules.Command:ShowConfigInfo(playerUin)
local config = AreaCopy.Runtime.config
local filterStatus = AreaCopy.Modules.Filter:GetStatusInfo()
Chat:sendSystemMsg("===== 当前运行配置 =====", playerUin)
Chat:sendSystemMsg(string.format("记录模式:%d", config.record.mode), playerUin)
Chat:sendSystemMsg(string.format("默认旋转:%d°", config.copy.defaultRotation), playerUin)
Chat:sendSystemMsg(string.format("默认镜像:%s", config.copy.defaultMirror and "开启" or "关闭"), playerUin)
Chat:sendSystemMsg(string.format("过滤模式:%s", filterStatus.modeName), playerUin)
Chat:sendSystemMsg(string.format("空气复制:%s", filterStatus.copyAir), playerUin)
Chat:sendSystemMsg(string.format("白名单数量:%d", filterStatus.whitelistCount), playerUin)
Chat:sendSystemMsg(string.format("黑名单数量:%d", filterStatus.blacklistCount), playerUin)
Chat:sendSystemMsg(string.format("权限校验:%s", config.permission.enableCheck and "开启" or "关闭"), playerUin)
Chat:sendSystemMsg(string.format("撤销功能:%s", config.special.enableUndo and "开启" or "关闭"), playerUin)
Chat:sendSystemMsg(string.format("预览功能:%s", config.special.enablePreview and "开启" or "关闭"), playerUin)
Chat:sendSystemMsg(string.format("模板功能:%s", config.special.enableTemplate and "开启" or "关闭"), playerUin)
end
-- ==========================================
-- 主程序生命周期管理
-- ==========================================
-- 脚本初始化入口
function AreaCopy:Init()
if self.Runtime.isInited then return end
print(string.format("[AreaCopy] 区域复制脚本 V%s 开始初始化", self.version))
-- 按依赖顺序初始化各模块
self.Modules.Config:Init()
self.Modules.Permission:Init()
self.Modules.Filter:Init()
self.Modules.Record:Init()
self.Modules.Copy:Init()
self.Modules.Command:Init()
-- 注册游戏启动事件
ScriptSupportEvent:registerEvent([=[Game.Start]=], function(e)
self:OnGameStartEvent(e)
end)
-- 注册玩家进入事件
ScriptSupportEvent:registerEvent([=[Game.AnyPlayer.EnterGame]=], function(e)
self:OnPlayerEnterEvent(e)
end)
self.Runtime.isInited = true
print(string.format("[AreaCopy] 脚本初始化完成,版本:%s", self.version))
end
-- 游戏启动事件处理
function AreaCopy:OnGameStartEvent(e)
local showTips = self.Modules.Config:Get("others", "showTips")
if showTips then
Chat:sendSystemMsg(string.format("区域复制脚本 V%s 加载完成,输入 /copy help 查看帮助", self.version))
end
-- 房主自动添加为管理员
local result, hostUin = Player:getHostUin()
if result == 0 and self.Modules.Config:Get("permission", "ownerIsAdmin") then
self.Modules.Permission:AddAdmin(hostUin)
end
end
-- 玩家进入游戏事件处理
function AreaCopy:OnPlayerEnterEvent(e)
local playerUin = e.eventobjid
-- 初始化玩家数据
self.Modules.Record:InitPlayerData(playerUin)
-- 欢迎提示
local showTips = self.Modules.Config:Get("others", "showTips")
if showTips then
Chat:sendSystemMsg("欢迎使用区域复制脚本,输入 /copy help 查看帮助文档", playerUin)
end
-- 房主权限校验
local result, hostUin = Player:getHostUin()
if playerUin == hostUin and self.Modules.Config:Get("permission", "ownerIsAdmin") then
self.Modules.Permission:AddAdmin(playerUin)
end
end
-- 启动脚本
AreaCopy:Init()
-- 全局导出
_G.AreaCopy = AreaCopy
一、快速上手流程
1. 基础操作三步法
- 第一步:记录区域
选择以下任意一种方式记录区域的两个对角点:
– 聊天命令模式(默认):输入 /copy pos1 记录起点(默认当前位置),输入 /copy pos2 记录终点(可手动添加坐标,格式: /copy pos2 x y z )
– 方块放置模式:输入 /copy mode 2 切换模式,放置指定记录方块(默认ID:101)记录两点,方块自动销毁
– 道具点击模式:输入 /copy mode 3 切换模式,用指定道具(默认ID:1001)点击方块表面记录两点
- 第二步:配置复制参数(可选)
– 旋转设置: /copy rotate 90 (支持0/90/180/270度,默认0度)
– 镜像设置: /copy mirror on (开启镜像, off 关闭,默认关闭)
– 过滤设置: /copy filter white (仅复制白名单方块,可选all/white/black)
- 第三步:执行粘贴
输入 /copy paste 粘贴到当前位置(或手动指定坐标: /copy paste x y z ),脚本会自动批量放置方块并显示进度
2. 常用功能速查
- 功能需求 执行命令 备注
- 查看帮助 /copy help 显示完整命令列表
- 清除选择 /copy clear 重置已记录的区域坐标
- 显示预览 /copy preview 渲染区域边框,避免粘贴偏差
- 撤销操作 /copy undo 恢复上一次粘贴前的状态
- 保存模板 /copy template save 模板名 保存当前区域为模板
- 使用模板 /copy template use 索引 粘贴已保存的模板(索引通过列表查看)
- 查看模板 /copy template list 显示所有已保存的模板及尺寸
二、高级功能详解
1. 方块过滤精细化配置
基础设置
- – 切换过滤模式: /copy filter black (黑名单模式,排除指定方块)
- - 添加方块到列表: /copy addblock 101 (将土块ID101添加到当前过滤列表)
- - 移除方块: /copy removeblock 101 (从过滤列表移除指定方块)
- - 空气复制开关: /copy air on (开启后复制空气方块,默认关闭)
预设组快速配置
脚本内置常用方块预设组,可快速添加过滤规则:
- – 建筑类方块:自动包含木板、石头、砖块等基础建材
- - 矿石类方块:包含各类矿石及矿石块
- - 电路类方块:包含导线、开关、发射器等
- – 自然类方块:包含草方块、沙子、树叶等自然方块
- - 功能类方块:包含箱子、熔炉、工作台等交互方块
2. 模板管理高效复用
模板操作流程
- 1. 记录目标区域(pos1+pos2)
- 2. 保存模板: /copy template save 城墙组件 (命名建议简洁明了)
- 3. 查看模板: /copy template list (获取模板索引)
- 4. 快速粘贴: /copy template use 1 x y z (使用索引1的模板,指定粘贴坐标)
- 5. 删除模板: /copy template delete 1 (删除无需保留的模板)
模板使用技巧
- – 模板支持旋转/镜像粘贴,粘贴前可配置参数
- - 建议将常用组件(如窗户、门框、装饰花纹)保存为模板
- - 模板数量上限可通过配置调整(默认20个)
3. 权限管控多人协作
管理员操作
- – 添加管理员: /copy admin add 玩家迷你号 (赋予其他玩家管理权限)
- – 移除管理员: /copy admin remove 玩家迷你号
- – 查看管理员列表: /copy admin list
黑名单管理
- – 添加黑名单: /copy blacklist add 玩家迷你号 (禁止该玩家使用脚本)
- - 移除黑名单: /copy blacklist remove 玩家迷你号
- - 查看黑名单: /copy blacklist list
权限说明
- – 房主默认拥有管理员权限,可配置是否自动继承
- - 普通玩家仅可使用基础复制粘贴功能,无法修改配置
- - 黑名单玩家无法使用脚本任何功能
4. 配置自定义优化
查看当前配置
- 输入 /copy config show 查看所有运行参数,包括记录模式、过滤规则、功能开关等
常用配置调整
- – 保存当前配置: /copy config save (持久化到存档,下次启动自动加载)
- - 重置默认配置: /copy config reset (恢复初始设置)
- - 调整批量大小:需修改脚本内 batchSize 参数(默认64,设备性能差可改小)
- - 预览方块修改:修改 previewBlockId 参数(默认95,可替换为其他方块ID)
三、常见问题排查
1. 区域记录失败
- – 检查坐标是否超出合法范围(Y轴0-256)
- - 确认记录模式与操作方式匹配(如方块模式需使用指定方块)
- - 检查是否已记录两个完整坐标点(pos1和pos2均需记录)
2. 复制粘贴卡顿
- – 减少单次复制区域大小(建议单区域不超过10000个方块)
- - 关闭空气复制功能( /copy air off )
- - 降低批量放置大小(修改脚本 batchSize 为32)
3. 权限不足提示
- – 确认是否为管理员或房主身份
- – 联系管理员添加权限( /copy admin add 你的迷你号 )
- – 检查是否被加入黑名单
4. 撤销功能无效
- – 确认撤销功能已开启( config.special.enableUndo=true )
- - 撤销步数有上限(默认10步,可通过配置调整)
- - 仅粘贴操作支持撤销,记录和模板操作无法撤销
四、使用技巧进阶
- 1. 精准粘贴:粘贴前使用 /copy preview 查看区域边框,调整坐标后再执行粘贴
- 2. 组件组合:将复杂建筑拆分为多个小模板,按需组合粘贴,提升灵活性
- 3. 批量部署:配合旋转/镜像功能,可快速生成对称建筑(如宫殿、城堡)
- 4. 备份重要操作:关键建筑复制前建议保存模板,避免误操作无法恢复
- 5. 性能优化:复制大型区域时关闭游戏内特效和光影,减少资源占用



没有回复内容