Commit 23d89d1311fb747bc78ebbad220bc273a64b3262
1 parent
8dce6908
冒险 结构
Showing
7 changed files
with
377 additions
and
10 deletions
Show diff stats
src/GlobalVar.lua
| ... | ... | @@ -55,4 +55,24 @@ ItemId = { |
| 55 | 55 | Diamond = 3, -- 钻石 |
| 56 | 56 | BreakCost = 4, -- 突破材料 |
| 57 | 57 | HeroFC = {700, 701, 702, 703}, -- 通用角色碎片 |
| 58 | +} | |
| 59 | + | |
| 60 | +--客户端不需要知道这个 | |
| 61 | +AdvSpecialStage = { | |
| 62 | + [1]= "In", | |
| 63 | + [2] = "Out", | |
| 64 | + [3] = "BOSS" | |
| 65 | +} | |
| 66 | +--客户端需要知道这个 | |
| 67 | +AdvEventType = { | |
| 68 | + -- 特殊事件(地块决定) | |
| 69 | + In = -1, --入口 | |
| 70 | + Out = -2, --出口 | |
| 71 | + BOSS = -3, -- boss | |
| 72 | + -- 普通事件(随机) | |
| 73 | + Choose = 1, --选择点 | |
| 74 | + Drop = 2, --物品掉落点 | |
| 75 | + Monster = 3, -- 普通怪 | |
| 76 | + Trader = 4, --商人 | |
| 77 | + Build = 5, --建筑物 | |
| 58 | 78 | } |
| 59 | 79 | \ No newline at end of file | ... | ... |
src/ProtocolCode.lua
| ... | ... | @@ -20,6 +20,9 @@ actionCodes = { |
| 20 | 20 | Role_updateProperties = 106, |
| 21 | 21 | Role_updateItems = 107, |
| 22 | 22 | Role_changeUpdate = 108, |
| 23 | + Role_pipelining = 109, | |
| 24 | + | |
| 25 | + Adv_startAdvRpc = 151, | |
| 23 | 26 | |
| 24 | 27 | Hero_loadInfos = 201, |
| 25 | 28 | Hero_updateProperty = 202, |
| ... | ... | @@ -35,6 +38,7 @@ actionCodes = { |
| 35 | 38 | Hero_loveItemRpc = 212, |
| 36 | 39 | Hero_loveTaskRpc = 213, |
| 37 | 40 | Hero_changeSkinRpc = 214, |
| 41 | + | |
| 38 | 42 | } |
| 39 | 43 | |
| 40 | 44 | rpcResponseBegin = 10000 | ... | ... |
| ... | ... | @@ -0,0 +1,35 @@ |
| 1 | +local ipairs = ipairs | |
| 2 | +local table = table | |
| 3 | +local math = math | |
| 4 | +local next = next | |
| 5 | +local string = string | |
| 6 | +local redisproxy = redisproxy | |
| 7 | +local MsgPack = MsgPack | |
| 8 | +local getRandomName = getRandomName | |
| 9 | +local mcast_util = mcast_util | |
| 10 | +local string_format = string.format | |
| 11 | +local tonumber = tonumber | |
| 12 | +local require = require | |
| 13 | +local table_insert = table.insert | |
| 14 | +local tconcat = table.concat | |
| 15 | +local table_unpack = table.unpack | |
| 16 | + | |
| 17 | +local _M = {} | |
| 18 | + | |
| 19 | +function _M.startAdvRpc( agent, data ) | |
| 20 | + local role = agent.role | |
| 21 | + local msg = MsgPack.unpack(data) | |
| 22 | + | |
| 23 | + local advInfo = role:getProperty("advInfo") | |
| 24 | + | |
| 25 | + role:randomAdvMap(10101, 1) --测试 | |
| 26 | + | |
| 27 | + SendPacket(actionCodes.Adv_startAdvRpc, '') | |
| 28 | + return true | |
| 29 | +end | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | +return _M | |
| 0 | 36 | \ No newline at end of file | ... | ... |
src/agent.lua
| ... | ... | @@ -46,17 +46,41 @@ function cancel_agent_timer() |
| 46 | 46 | end |
| 47 | 47 | ---- 定时器相关 }}} |
| 48 | 48 | |
| 49 | +local _pipelinings = {} --可同时多个序列 栈的形式保证嵌套不出错 | |
| 49 | 50 | function SendPacket(actionCode, bin, client_fd) |
| 50 | - if #bin > 0 then bin = xxtea.encrypt(bin, XXTEA_KEY) end | |
| 51 | - | |
| 51 | + client_fd = client_fd or agentInfo.client_fd | |
| 52 | + | |
| 52 | 53 | local handlerName = actionHandlers[actionCode] |
| 53 | 54 | if string.sub(handlerName, -3, -1) == "Rpc" then |
| 54 | 55 | actionCode = actionCode + rpcResponseBegin |
| 55 | 56 | end |
| 57 | + -- 查看是否是在 流水线操作中 | |
| 58 | + if #_pipelinings > 0 then | |
| 59 | + local _curPipelining = _pipelinings[#_pipelinings] | |
| 60 | + _curPipelining[client_fd] = _curPipelining[client_fd] or {} --区分不同客户端 | |
| 61 | + table.insert(_curPipelining[client_fd], {actionCode, bin}) | |
| 62 | + else | |
| 63 | + if #bin > 0 then bin = xxtea.encrypt(bin, XXTEA_KEY) end | |
| 64 | + local head = string.pack("H", actionCode) | |
| 65 | + return socket.write(client_fd, netpack.pack(head .. bin)) | |
| 66 | + end | |
| 67 | +end | |
| 56 | 68 | |
| 57 | - local client_fd = client_fd or agentInfo.client_fd | |
| 58 | - local head = string.pack("H", actionCode) | |
| 59 | - return socket.write(client_fd, netpack.pack(head .. bin)) | |
| 69 | +function SendPipelining(callback) | |
| 70 | + if type(callback) ~= "function" then return end | |
| 71 | + --push 当前队列 | |
| 72 | + table.insert(_pipelinings, {}) | |
| 73 | + -- 执行代码块 输出错误信息 | |
| 74 | + local ok, err = pcall(callback) | |
| 75 | + --弹出当前队列 | |
| 76 | + local curPipelining = table.remove(_pipelinings) | |
| 77 | + -- 查看是否有消息 | |
| 78 | + if next(curPipelining) then | |
| 79 | + for client_fd, msg in pairs(curPipelining) do | |
| 80 | + SendPacket(actionCodes.Role_pipelining, MsgPack.pack(msg), client_fd) | |
| 81 | + end | |
| 82 | + end | |
| 83 | + if not ok then error(err) end | |
| 60 | 84 | end |
| 61 | 85 | |
| 62 | 86 | function rpcAgent(roleId, funcName, ...) | ... | ... |
src/models/Role.lua
| ... | ... | @@ -2,10 +2,12 @@ local Role = class("Role", require("shared.ModelBase")) |
| 2 | 2 | |
| 3 | 3 | local RolePlugin = import(".RolePlugin") |
| 4 | 4 | local RoleTask = import(".RoleTask") |
| 5 | +local RoleAdv = import(".RoleAdv") | |
| 5 | 6 | local RoleActivity = import(".RoleActivity") |
| 6 | 7 | local RoleChangeStruct = import(".RoleChangeStruct") |
| 7 | 8 | RolePlugin.bind(Role) |
| 8 | 9 | RoleTask.bind(Role) |
| 10 | +RoleAdv.bind(Role) | |
| 9 | 11 | RoleActivity.bind(Role) |
| 10 | 12 | RoleChangeStruct.bind(Role) |
| 11 | 13 | |
| ... | ... | @@ -35,8 +37,9 @@ Role.schema = { |
| 35 | 37 | items = {"string", ""}, |
| 36 | 38 | loveStatus = {"string", ""}, --统计角色的最高 好感度等级 类型相关 -- type=loveL type=loveL |
| 37 | 39 | |
| 38 | - advPass = {"string", ""}, -- 冒险 通关记录 | |
| 39 | - advInfo = {"table", {}}, -- 冒险 相关信息 | |
| 40 | + --冒险相关 | |
| 41 | + advPass = {"string", ""}, -- 通关记录 | |
| 42 | + advInfo = {"table", {}}, -- 其他信息 | |
| 40 | 43 | |
| 41 | 44 | } |
| 42 | 45 | |
| ... | ... | @@ -56,7 +59,7 @@ end |
| 56 | 59 | |
| 57 | 60 | function Role:updateProperty(params) |
| 58 | 61 | params = params or {} |
| 59 | - if not self.fields[params.field] then return end | |
| 62 | + if not self.schema[params.field] then return end | |
| 60 | 63 | local oldValue = self:getProperty(params.field) |
| 61 | 64 | local ret = {key = params.field, oldValue = oldValue} |
| 62 | 65 | if params.value then |
| ... | ... | @@ -86,13 +89,36 @@ function Role:notifyUpdateProperties(params) |
| 86 | 89 | SendPacket(actionCodes.Role_updateProperties, MsgPack.pack(params)) |
| 87 | 90 | end |
| 88 | 91 | |
| 89 | --- 某些字段更新改变量 改变量的定义由字段自身决定 {{type = ""}, } | |
| 92 | +-- 某些字段 更新改变量 改变量的定义由字段自身决定 {{type = ""}, } | |
| 90 | 93 | function Role:changeUpdates(params, notNotify) |
| 91 | 94 | local changeUpdateFunc = { |
| 92 | 95 | ["loveStatus"] = function(info) |
| 93 | 96 | self:setProperty("loveStatus", self:getProperty("loveStatus"):setv(info["field"], info["value"])) |
| 94 | 97 | return {type = "loveStatus", field = info["field"], value = info["value"]} |
| 95 | 98 | end, |
| 99 | + --table 类型通用更新 | |
| 100 | + ["tableCommon"] = function(fieldType, info) | |
| 101 | + if self.class.schema[fieldType][1] ~= "table" then | |
| 102 | + error("[ERROR:] need handler for changeUpdate, field : " .. fieldType) | |
| 103 | + return | |
| 104 | + end | |
| 105 | + --支持多深度单字段 | |
| 106 | + local curValue = self:getProperty(fieldType) | |
| 107 | + if type(info["field"]) == "table" then | |
| 108 | + for _idx, _field in ipairs(info["field"]) do | |
| 109 | + if _idx < #info["field"] then | |
| 110 | + curValue[_field] = curValue[_field] or {} | |
| 111 | + curValue = curValue[_field] | |
| 112 | + else | |
| 113 | + curValue[_field] = info["value"] | |
| 114 | + end | |
| 115 | + end | |
| 116 | + else | |
| 117 | + curValue[info["field"]] = info["value"] | |
| 118 | + end | |
| 119 | + self:setProperty(fieldType) | |
| 120 | + return {type = fieldType, field = info["field"], value = info["value"]} | |
| 121 | + end, | |
| 96 | 122 | } |
| 97 | 123 | |
| 98 | 124 | local updates = {} |
| ... | ... | @@ -100,7 +126,7 @@ function Role:changeUpdates(params, notNotify) |
| 100 | 126 | if changeUpdateFunc[one["type"]] then |
| 101 | 127 | table.insert(updates, changeUpdateFunc[one["type"]](one)) |
| 102 | 128 | else |
| 103 | - print("need handler for changeUpdate type : " .. params["type"]) | |
| 129 | + table.insert(updates, changeUpdateFunc["tableCommon"](one["type"], one)) | |
| 104 | 130 | end |
| 105 | 131 | end |
| 106 | 132 | if not notNotify and next(updates) then |
| ... | ... | @@ -117,6 +143,8 @@ function Role:data() |
| 117 | 143 | reDiamond = self:getProperty("reDiamond"), |
| 118 | 144 | items = self:getProperty("items"):toNumMap(), |
| 119 | 145 | loveStatus = self:getProperty("loveStatus"):toNumMap(), |
| 146 | + advPass = self:getProperty("advPass"), | |
| 147 | + advInfo = self:getProperty("advInfo"), | |
| 120 | 148 | } |
| 121 | 149 | end |
| 122 | 150 | ... | ... |
| ... | ... | @@ -0,0 +1,250 @@ |
| 1 | + | |
| 2 | +local RoleAdv = {} | |
| 3 | + | |
| 4 | +function RoleAdv.bind(Role) | |
| 5 | + | |
| 6 | + | |
| 7 | + local function getIdByCr(c, r) | |
| 8 | + local crId = math.abs(r) + math.abs(c) * 100 | |
| 9 | + if c < 0 then | |
| 10 | + crId = crId + 10000 | |
| 11 | + end | |
| 12 | + if r < 0 then | |
| 13 | + crId = crId + 20000 | |
| 14 | + end | |
| 15 | + return crId | |
| 16 | + end | |
| 17 | + local function getCrById(crId) | |
| 18 | + local c = math.floor(crId % 10000 / 100) | |
| 19 | + local r = crId % 100 | |
| 20 | + local last = math.floor(crId / 10000) | |
| 21 | + if last == 3 then | |
| 22 | + c, r = -c, -r | |
| 23 | + elseif last == 1 then | |
| 24 | + c = -c | |
| 25 | + elseif last == 2 then | |
| 26 | + r = -r | |
| 27 | + end | |
| 28 | + return c, r | |
| 29 | + end | |
| 30 | + | |
| 31 | + --检查 是否满足层数限制条件 | |
| 32 | + local function checkIsIn(checkValue, checkType, checkRange) | |
| 33 | + if not checkValue then return end | |
| 34 | + if checkType == 1 then | |
| 35 | + local limits = checkRange:toNumMap() | |
| 36 | + for min, max in pairs(limits) do | |
| 37 | + if checkValue >= min and checkValue <= max then | |
| 38 | + return true | |
| 39 | + end | |
| 40 | + end | |
| 41 | + else | |
| 42 | + local limit = checkRange:toArray(true, "=") | |
| 43 | + for _, _l in ipairs(limit) do | |
| 44 | + if _l == checkValue then | |
| 45 | + return true | |
| 46 | + end | |
| 47 | + end | |
| 48 | + end | |
| 49 | + end | |
| 50 | + | |
| 51 | + --关卡事件库 | |
| 52 | + local function getEventLib(chapterId, level) | |
| 53 | + local chapter = math.floor(chapterId / 100) % 100 | |
| 54 | + | |
| 55 | + local libsToType = { | |
| 56 | + ["event_monsterCsv"] = {AdvEventType.Monster, AdvEventType.BOSS}, | |
| 57 | + ["event_chooseCsv"] = AdvEventType.Choose, | |
| 58 | + ["event_dropCsv"] = AdvEventType.Drop, | |
| 59 | + ["event_buildingCsv"] = AdvEventType.Build, | |
| 60 | + ["event_traderCsv"] = AdvEventType.Trader, | |
| 61 | + | |
| 62 | + } | |
| 63 | + local eventLib = {} | |
| 64 | + for lib, eventType in pairs(libsToType) do | |
| 65 | + if type(eventType) == "table" then | |
| 66 | + for _, temp in ipairs(eventType) do | |
| 67 | + eventLib[temp] = {} | |
| 68 | + end | |
| 69 | + else | |
| 70 | + eventLib[eventType] = {} | |
| 71 | + end | |
| 72 | + for id, data in pairs(csvdb[lib]) do | |
| 73 | + if data.levelchapter == chapter then | |
| 74 | + if checkIsIn(level, data.leveltype, data.levellimit) then | |
| 75 | + if type(eventType) == "table" then | |
| 76 | + eventLib[eventType[data.type]][id] = data | |
| 77 | + else | |
| 78 | + eventLib[eventType][id] = data | |
| 79 | + end | |
| 80 | + end | |
| 81 | + end | |
| 82 | + end | |
| 83 | + end | |
| 84 | + return eventLib | |
| 85 | + end | |
| 86 | + | |
| 87 | + -- 生成地图 是否可以生成地图上层判断 | |
| 88 | + function Role:randomAdvMap(chapterId, level) | |
| 89 | + local chapterData = csvdb["adv_chapterCsv"][chapterId] | |
| 90 | + if not chapterData then return end | |
| 91 | + if level > chapterData.limitlevel then return end | |
| 92 | + --随出地图 | |
| 93 | + local raw_pool = chapterData.mapid:toArray(true, "=") | |
| 94 | + local advInfo = self:getProperty("advInfo") | |
| 95 | + local lastMapId = advInfo.mapId --非同一层不连续随出同一张类似的地图 | |
| 96 | + local lastChapterId = advInfo.chapter | |
| 97 | + local pool = {} | |
| 98 | + for _, mapId in ipairs(raw_pool) do | |
| 99 | + local temp = csvdb["mapCsv"][mapId] | |
| 100 | + if temp and (lastChapterId == chapterId or lastMapId ~= mapId) then --非同一层不连续随出同一张类似的地图 | |
| 101 | + if checkIsIn(level, temp.leveltype, temp.levellimit) then | |
| 102 | + table.insert(pool, mapId) | |
| 103 | + end | |
| 104 | + end | |
| 105 | + end | |
| 106 | + if not next(pool) then return end | |
| 107 | + local mapId = pool[math.randomInt(1, #pool)] | |
| 108 | + --随出事件 | |
| 109 | + local mapData = csvdb["map_" .. csvdb["mapCsv"][mapId]["path"] .. "Csv"] | |
| 110 | + if not mapData then return end | |
| 111 | + | |
| 112 | + table.clear(advInfo) | |
| 113 | + advInfo.chapter = chapterId | |
| 114 | + advInfo.level = level | |
| 115 | + advInfo.mapId = mapId | |
| 116 | + advInfo.rooms = {} -- {[roomId] = {event = {}, open = {}, isPath = nil},} -- event 事件信息(具体信息查看randomEvent), open 是否解锁 isPath 是否是路径 | |
| 117 | + | |
| 118 | + --事件随机 | |
| 119 | + local eventLib = getEventLib(chapterId, level) | |
| 120 | + local monsterEvents = {} --处理钥匙掉落 | |
| 121 | + local haveBoss = false | |
| 122 | + local function randomEvent(roomId, blockId, eventType) | |
| 123 | + if advInfo.rooms[roomId]["event"][blockId] then return end --已经有事件了 不覆盖 | |
| 124 | + local event = {etype = eventType} | |
| 125 | + local randomFunc = { | |
| 126 | + [AdvEventType.In] = function() | |
| 127 | + advInfo.rooms[roomId]["open"][blockId] = 1 | |
| 128 | + end, | |
| 129 | + [AdvEventType.Out] = function() | |
| 130 | + end, | |
| 131 | + [AdvEventType.BOSS] = function() | |
| 132 | + if not next(eventLib[eventType]) or haveBoss then return false end | |
| 133 | + haveBoss = true | |
| 134 | + event.id = math.randWeight(eventLib[eventType], "showup") | |
| 135 | + end, | |
| 136 | + [AdvEventType.Choose] = function() | |
| 137 | + if not next(eventLib[eventType]) then return false end | |
| 138 | + event.id = math.randWeight(eventLib[eventType], "showup") | |
| 139 | + end, | |
| 140 | + [AdvEventType.Drop] = function() | |
| 141 | + if not next(eventLib[eventType]) then return false end | |
| 142 | + event.item = eventLib[eventType][math.randWeight(eventLib[eventType], "showup")]["range"]:randWeight(true) | |
| 143 | + end, | |
| 144 | + [AdvEventType.Monster] = function() | |
| 145 | + if not next(eventLib[eventType]) then return false end | |
| 146 | + event.id = math.randWeight(eventLib[eventType], "showup") | |
| 147 | + table.insert(monsterEvents, event) | |
| 148 | + end, | |
| 149 | + [AdvEventType.Trader] = function() | |
| 150 | + if not next(eventLib[eventType]) then return false end | |
| 151 | + event.id = math.randWeight(eventLib[eventType], "showup") | |
| 152 | + end, | |
| 153 | + [AdvEventType.Build] = function() | |
| 154 | + if not next(eventLib[eventType]) then return false end | |
| 155 | + event.id = math.randWeight(eventLib[eventType], "showup") | |
| 156 | + end, | |
| 157 | + } | |
| 158 | + | |
| 159 | + if randomFunc[eventType] then | |
| 160 | + if randomFunc[eventType]() ~= false then | |
| 161 | + advInfo.rooms[roomId]["event"][blockId] = event | |
| 162 | + end | |
| 163 | + end | |
| 164 | + end | |
| 165 | + | |
| 166 | + stagePool = {["global"] = {}} | |
| 167 | + for roomId, roomName in pairs(mapData["rooms"]) do | |
| 168 | + stagePool[roomId] = {} | |
| 169 | + advInfo.rooms[roomId] = {event = {}, open = {}} -- 事件, open | |
| 170 | + local roomData | |
| 171 | + if roomName == "path" then | |
| 172 | + advInfo.rooms[roomId].isPath = true | |
| 173 | + roomData = mapData["path"] | |
| 174 | + else | |
| 175 | + roomName = roomName:gsub("/", "_") | |
| 176 | + roomData = csvdb["room_" .. roomName .. "Csv"] | |
| 177 | + end | |
| 178 | + for blockId, stageType in pairs(roomData["blocks"]) do | |
| 179 | + if AdvSpecialStage[stageType] then | |
| 180 | + eventType = AdvEventType[AdvSpecialStage[stageType]] | |
| 181 | + randomEvent(roomId, blockId, eventType) | |
| 182 | + else | |
| 183 | + stagePool["global"][stageType] = stagePool["global"][stageType] or {} | |
| 184 | + stagePool[roomId][stageType] = stagePool[roomId][stageType] or {} | |
| 185 | + table.insert(stagePool["global"][stageType], {room = roomId, block = blockId}) | |
| 186 | + stagePool[roomId][stageType][blockId] = 1 | |
| 187 | + end | |
| 188 | + end | |
| 189 | + end | |
| 190 | + -- 全地图事件 优先级高 | |
| 191 | + for stageType, events in pairs(mapData["events"]) do | |
| 192 | + for _, event in ipairs(events) do | |
| 193 | + local lastCount = #stagePool["global"][stageType] | |
| 194 | + if lastCount <= 0 then break end | |
| 195 | + if math.randomFloat(0, 1) <= (event["rate"] or 1) then | |
| 196 | + local count = math.randomInt(math.min(lastCount, event["minc"]), math.min(lastCount, event["maxc"])) | |
| 197 | + for i = 1, count do | |
| 198 | + local idx = math.randomInt(1, lastCount) | |
| 199 | + local cur = stagePool["global"][stageType][idx] | |
| 200 | + randomEvent(cur["room"], cur["block"], event["event"]) | |
| 201 | + table.remove(stagePool["global"][stageType], idx) | |
| 202 | + lastCount = lastCount - 1 | |
| 203 | + stagePool[cur["room"]][stageType][cur["block"]] = nil | |
| 204 | + end | |
| 205 | + end | |
| 206 | + end | |
| 207 | + end | |
| 208 | + -- 随机单个房间的事件 | |
| 209 | + for roomId, roomName in pairs(mapData["rooms"]) do | |
| 210 | + local roomData | |
| 211 | + if roomName == "path" then | |
| 212 | + roomData = mapData["path"] | |
| 213 | + else | |
| 214 | + roomName = roomName:gsub("/", "_") | |
| 215 | + roomData = csvdb["room_" .. roomName .. "Csv"] | |
| 216 | + end | |
| 217 | + for stageType, events in pairs(roomData["events"]) do | |
| 218 | + local bpool = {} | |
| 219 | + if stagePool[roomId][stageType] then | |
| 220 | + for block, _ in pairs(stagePool[roomId][stageType]) do | |
| 221 | + table.insert(bpool, block) | |
| 222 | + end | |
| 223 | + end | |
| 224 | + for _, event in ipairs(events) do | |
| 225 | + if #bpool <= 0 then break end | |
| 226 | + if math.randomFloat(0, 1) <= (event["rate"] or 1) then | |
| 227 | + local count = math.randomInt(math.min(#bpool, event["minc"]), math.min(#bpool, event["maxc"])) | |
| 228 | + for i = 1, count do | |
| 229 | + local idx = math.randomInt(1, #bpool) | |
| 230 | + randomEvent(roomId, bpool[idx], event["event"]) | |
| 231 | + table.remove(bpool, idx) | |
| 232 | + end | |
| 233 | + end | |
| 234 | + end | |
| 235 | + end | |
| 236 | + end | |
| 237 | + if not haveBoss then | |
| 238 | + if not next(monsterEvents) then | |
| 239 | + print("这个地图没有钥匙!!! mapId : " .. mapId) | |
| 240 | + else | |
| 241 | + local event = monsterEvents[math.randomInt(1, #monsterEvents)] | |
| 242 | + event.item = {1, 1} --掉落钥匙 | |
| 243 | + end | |
| 244 | + end | |
| 245 | + self:updateProperty({field = "advInfo", value = advInfo}) | |
| 246 | + end | |
| 247 | + | |
| 248 | +end | |
| 249 | + | |
| 250 | +return RoleAdv | |
| 0 | 251 | \ No newline at end of file | ... | ... |
src/utils/TableUtil.lua