--- 离线挂机 local this = {} onHook = {} --- 获取离线挂机信息 ---@param actor 玩家对象 function onHook.getOffLineOnHookInfo(actor) local onHookInfo = getonhookinfo(actor) local duration = tonumber(onHookInfo["duration"]) local fightExp = getplaydef(actor, PlayerDefKey.offline.FIGHT_EXP) if string.isNullOrEmpty(fightExp) then fightExp = 0 end local offlineStartTime = getplaydef(actor, PlayerDefKey.offline.START_TIME) local offlineEndTime = getplaydef(actor, PlayerDefKey.offline.END_TIME) if not string.isNullOrEmpty(offlineEndTime) then duration = math.round((offlineEndTime - offlineStartTime) / 1000) end local offlineTimeout = ConfigDataManager.getTableValue("cfg_global", "value", "id", GlobalConfigId.OFFLINE_ON_HOOK_TIMEOUT) local timeout = duration > offlineTimeout * 60 duration = timeout and offlineTimeout * 60 or duration local secondDiff = this.calcBubblePointTime(actor, offlineTimeout) -- 泡点经验 local bubblePointExp = getplaydef(actor, PlayerDefKey.offline.BUBBLE_POINT_EXP) if string.isNullOrEmpty(bubblePointExp) then bubblePointExp = 0 end onHookInfo["freeExp"] = tostring(bubblePointExp) -- 泡点时长与总离线挂机时长 onHookInfo["freeDuration"] = math.round(secondDiff) onHookInfo["duration"] = math.round(duration) onHookInfo["fightDuration"] = math.round(duration - secondDiff) onHookInfo["fightExp"] = tostring(fightExp) local isReceive = getplaydef(actor, PlayerDefKey.offline.IS_RECEIVE) if string.isNullOrEmpty(isReceive) then isReceive = false end onHookInfo["receiveExp"] = isReceive local totalExp = getplaydef(actor, PlayerDefKey.offline.TOTAL_EXP) if string.isNullOrEmpty(totalExp) then totalExp = 0 end local expLimit = ConfigDataManager.getTableValue("cfg_global", "value", "id", GlobalConfigId.OFFLINE_ON_HOOK_EXP_LIMIT) local total = totalExp + bubblePointExp + fightExp if expLimit and tonumber(expLimit) < total then total = tonumber(expLimit) end onHookInfo["totalExp"] = tostring(total) local openBubbleTime = getplaydef(actor, PlayerDefKey.offline.BUBBLE_POINT_START) local mapId = getplaydef(actor, PlayerDefKey.offline.BUBBLE_POINT_MAP) if not string.isNullOrEmpty(openBubbleTime) and tonumber(openBubbleTime) >= tonumber(offlineStartTime) then local temp = {} temp[openBubbleTime] = mapId onHookInfo["openBubbleMap"] = temp end -- 如果后端服务重启,则需要重新计算挂机超时时间点 if timeout then onHookInfo["timeout"] = offlineStartTime + offlineTimeout * TimeUnit.MINUTE * TimeUnit.MILLISECOND end sendluamsg(actor, LuaMessageIdToClient.RES_OFFLINE_ON_HOOK_INFO, onHookInfo) end --- 请求领取离线挂机经验 ---@param actor 玩家对象 function onHook.reqReceiveOfflineOnHookExp(actor) -- 领取过离线挂机经验直接返回 if getplaydef(actor, PlayerDefKey.offline.IS_RECEIVE) then return end -- 计算角色泡点经验 local bubblePointExp = getplaydef(actor, PlayerDefKey.offline.BUBBLE_POINT_EXP) if string.isNullOrEmpty(bubblePointExp) then bubblePointExp = 0 end local fightExp = getplaydef(actor, PlayerDefKey.offline.FIGHT_EXP) if string.isNullOrEmpty(fightExp) then fightExp = 0 end local totalExp = getplaydef(actor, PlayerDefKey.offline.TOTAL_EXP) if string.isNullOrEmpty(totalExp) then totalExp = 0 end local exp = bubblePointExp + fightExp + totalExp local expLimit = ConfigDataManager.getTableValue("cfg_global", "value", "id", GlobalConfigId.OFFLINE_ON_HOOK_EXP_LIMIT) exp = exp > tonumber(expLimit) and tonumber(expLimit) or exp additemtobag(actor, ItemConfigId.EXP, exp, 0, 9999, '离线挂机') setplaydef(actor, PlayerDefKey.offline.IS_RECEIVE, true) end --- 角色登录存储泡点结束时间与离线挂机结束时间 ---@param actor 玩家对象 function onHook.login(actor) onHook.setOfflineState(actor, 0) local offlineTimeout = ConfigDataManager.getTableValue("cfg_global", "value", "id", GlobalConfigId.OFFLINE_ON_HOOK_TIMEOUT) local offlineStartTime = getplaydef(actor, PlayerDefKey.offline.START_TIME) local offlineBubblePointStart = getplaydef(actor, PlayerDefKey.offline.BUBBLE_POINT_START) local serverStart = getsysvar(SystemVarConst.SERVER_START) -- 角色离线挂机开始时间小于服务器最新启动时间则表示玩家离线挂机期间服务器重启过 local now = getbaseinfo(actor, "now") local fightExp = getplaydef(actor, PlayerDefKey.offline.FIGHT_EXP) local isFirstLogin = getplaydef(actor, "T$_isResetReceiveFlag") if offlineStartTime and serverStart and tonumber(serverStart) > tonumber(offlineStartTime) and not isFirstLogin then -- 设置已经重置过领取经验标识 setplaydef(actor, "T$_isResetReceiveFlag", 1) -- 设置领取经验为false,防止服务器重启领取经验标识为true,导致玩家无法领取离线挂机经验 setplaydef(actor, PlayerDefKey.offline.IS_RECEIVE, false) -- 判断当前角色是否在安全区内,如果不在安全区,根据配表计算战斗经验 if string.isNullOrEmpty(offlineBubblePointStart) then local lastTime = getplaydef(actor, PlayerDefKey.offline.LAST_TIME) local fightTime = tonumber(now) - tonumber(lastTime) local timeoutMillisecond = tonumber(offlineTimeout * TimeUnit.MINUTE * TimeUnit.MILLISECOND) fightTime = fightTime > timeoutMillisecond and timeoutMillisecond or fightTime local mapId = getbaseinfo(actor, "map") local patrolState = getplaydef(actor, PlayerDefKey.offline.PATROL_STATE) local monsterState = patrolState and 2 or 1 local expConfig = ConfigDataManager.getTableValue("cfg_hangupReboot", "monstergroup", "mapid", mapId, "monstername", monsterState) if not string.isNullOrEmpty(expConfig) then local second = string.split(expConfig, "#")[1] local exp = string.split(expConfig, "#")[2] local step = math.round((fightTime / 1000) / second) if step > 0 then local baseExp = fightExp and tonumber(fightExp) or 0 setplaydef(actor, PlayerDefKey.offline.FIGHT_EXP, baseExp + (tonumber(exp) * step)) end end end end if offlineBubblePointStart then local offlineBubblePointEnd = getplaydef(actor, PlayerDefKey.offline.BUBBLE_POINT_END) if string.isNullOrEmpty(offlineBubblePointEnd) and this.checkBubblePointArea(actor) then setplaydef(actor, PlayerDefKey.offline.BUBBLE_POINT_END, now) -- 将离线泡点经验持久化,如果上次离线的泡点经验没有领取则累加 local onHookInfo = getonhookinfo(actor) if table.isNullOrEmpty(onHookInfo) then return end local bubblePointExp = getplaydef(actor, PlayerDefKey.offline.BUBBLE_POINT_EXP) local exp = this.calcBubblePointExp(actor, this.calcBubblePointTime(actor, offlineTimeout)) if string.isNullOrEmpty(bubblePointExp) then setplaydef(actor, PlayerDefKey.offline.BUBBLE_POINT_EXP, exp) else local bubbleExp = tonumber(bubblePointExp) + exp if this.checkExpLimit(actor, fightExp, bubbleExp) then local expLimit = ConfigDataManager.getTableValue("cfg_global", "value", "id", GlobalConfigId.OFFLINE_ON_HOOK_EXP_LIMIT) local totalExp = getplaydef(actor, PlayerDefKey.offline.TOTAL_EXP) bubbleExp = tonumber(expLimit) - fightExp - totalExp end setplaydef(actor, PlayerDefKey.offline.BUBBLE_POINT_EXP, bubbleExp) end end end if offlineStartTime then local offlineEndTime = getplaydef(actor, PlayerDefKey.offline.END_TIME) if string.isNullOrEmpty(offlineEndTime) then setplaydef(actor, PlayerDefKey.offline.END_TIME, now) end end end --- 角色退出清空泡点相关信息 ---@param actor 玩家对象 function onHook.logout(actor) -- 角色重置过领取经验标识重置 setplaydef(actor, "T$_isResetReceiveFlag", nil) -- 重置结束时间 setplaydef(actor, PlayerDefKey.offline.BUBBLE_POINT_END, nil) setplaydef(actor, PlayerDefKey.offline.END_TIME, nil) -- 领取过经验后重置存储的经验信息 if getplaydef(actor, PlayerDefKey.offline.IS_RECEIVE) then setplaydef(actor, PlayerDefKey.offline.IS_RECEIVE, false) setplaydef(actor, PlayerDefKey.offline.BUBBLE_POINT_EXP, nil) setplaydef(actor, PlayerDefKey.offline.FIGHT_EXP, nil) setplaydef(actor, PlayerDefKey.offline.TOTAL_EXP, nil) else local totalExp = getplaydef(actor, PlayerDefKey.offline.TOTAL_EXP) if string.isNullOrEmpty(totalExp) then totalExp = 0 end local bubblePointExp = getplaydef(actor, PlayerDefKey.offline.BUBBLE_POINT_EXP) if not string.isNullOrEmpty(bubblePointExp) then totalExp = totalExp + bubblePointExp end local fightExp = getplaydef(actor, PlayerDefKey.offline.FIGHT_EXP) if not string.isNullOrEmpty(fightExp) then totalExp = totalExp + fightExp end local expLimit = ConfigDataManager.getTableValue("cfg_global", "value", "id", GlobalConfigId.OFFLINE_ON_HOOK_EXP_LIMIT) if expLimit and totalExp > tonumber(expLimit) then totalExp = tonumber(expLimit) end setplaydef(actor, PlayerDefKey.offline.TOTAL_EXP, totalExp) setplaydef(actor, PlayerDefKey.offline.BUBBLE_POINT_EXP, nil) setplaydef(actor, PlayerDefKey.offline.FIGHT_EXP, nil) end local level = tonumber(getbaseinfo(actor, "level")) -- 是否达到开启离线挂机的等级 local offlineOnHookLevel = ConfigDataManager.getTableValue("cfg_global", "value", "id", GlobalConfigId.OFFLINE_ON_HOOK_LEVEL) if level >= tonumber(offlineOnHookLevel) then -- 记录离线挂机开启时间 setplaydef(actor, PlayerDefKey.offline.START_TIME, getbaseinfo(actor, "now")) if this.checkBubblePointArea(actor) then setplaydef(actor, PlayerDefKey.offline.BUBBLE_POINT_MAP, getbaseinfo(actor, "map")) setplaydef(actor, PlayerDefKey.offline.BUBBLE_POINT_START, getbaseinfo(actor, "now")) else setplaydef(actor, PlayerDefKey.offline.BUBBLE_POINT_MAP, nil) setplaydef(actor, PlayerDefKey.offline.BUBBLE_POINT_START, nil) end end end --- 玩家传送后判断是否进入泡点区域 ---@param actor 玩家对象 function onHook.afterTransmit(actor) -- 非离线挂机玩家不处理 if not isofflineplay(actor) then return end -- 是否达到开启离线挂机的等级,离线挂机传送只可能是免费复活后传送,所以此处不需要判断区域是否为泡点区域 local offlineOnHookLevel = ConfigDataManager.getTableValue("cfg_global", "value", "id", GlobalConfigId.OFFLINE_ON_HOOK_LEVEL) if tonumber(getbaseinfo(actor, "level")) >= tonumber(offlineOnHookLevel) then setplaydef(actor, PlayerDefKey.offline.BUBBLE_POINT_MAP, getbaseinfo(actor, "map")) setplaydef(actor, PlayerDefKey.offline.BUBBLE_POINT_START, getbaseinfo(actor, "now")) end end --- 检查区域是否是泡点区域 ---@param actor 玩家对象 ---@return boolean 是否在泡点区域 function this.checkBubblePointArea(actor) local level = tonumber(getbaseinfo(actor, "level")) local mapId = getbaseinfo(actor, "map") local safeArea = getbaseinfo(actor, "safearea") local configString = ConfigDataManager.getTableValue("cfg_bubble_point", "expMap", "id", level) local configMap = string.toStringStringMap(configString, "#", "|") local checkArea = false for key, value in pairs(configMap) do mapId = tonumber(mapId) key = tonumber(key) value = tonumber(value) if mapId == key then if value == 3 then checkArea = true elseif value == 2 and safeArea == false then checkArea = true elseif value == 1 and safeArea == true then checkArea = true end end end return checkArea end --- 计算泡点时长 ---@param actor 玩家对象 ---@param duration 离线挂机战斗时长 ---@return number 泡点时长(s) function this.calcBubblePointTime(actor, offlineTimeout) local offlineBubblePointStart = getplaydef(actor, PlayerDefKey.offline.BUBBLE_POINT_START) local offlineBubblePointEnd = getplaydef(actor, PlayerDefKey.offline.BUBBLE_POINT_END) if not string.isNullOrEmpty(offlineBubblePointStart) and not string.isNullOrEmpty(offlineBubblePointEnd) and tonumber(offlineBubblePointEnd) - tonumber(offlineBubblePointStart) > 0 then local secondDiff = math.round((tonumber(offlineBubblePointEnd) - tonumber(offlineBubblePointStart)) / 1000) -- 泡点时长处理 if secondDiff > tonumber(offlineTimeout) * 60 then secondDiff = tonumber(offlineTimeout) * 60 local offlineStartTime = getplaydef(actor, PlayerDefKey.offline.START_TIME) local diff = (offlineBubblePointStart - offlineStartTime) / 1000 if offlineBubblePointStart > offlineStartTime then if diff > offlineTimeout * 60 then secondDiff = 0 else secondDiff = offlineTimeout * 60 - diff end end end return secondDiff end return 0 end --- 根据泡点时长获取泡点经验 ---@param actor 玩家对象 ---@param secondDiff 泡点时长(s) ---@return number 泡点经验 function this.calcBubblePointExp(actor, secondDiff) if secondDiff == 0 then return 0 end local level = tonumber(getbaseinfo(actor, "level")) local expInterval = ConfigDataManager.getTableValue("cfg_bubble_point", "expInterval", "id", level) if string.isNullOrEmpty(expInterval) then gameDebug.print("找不到cfg_bubble_point配置,id:" .. level) return 0, 0 end local times = math.round(secondDiff / expInterval) local expConfig = ConfigDataManager.getTableValue("cfg_bubble_point", "exp", "id", level) local exp = string.split(expConfig, "#")[2] return tonumber(exp) * tonumber(times) end --- 检查离线挂机经验是否超限 ---@param fightExp number 战斗经验 ---@param bubblePointExp number 泡点经验 function this.checkExpLimit(actor, fightExp, bubblePointExp) local expLimit = ConfigDataManager.getTableValue("cfg_global", "value", "id", GlobalConfigId.OFFLINE_ON_HOOK_EXP_LIMIT) if not string.isNullOrEmpty(expLimit) then local totalExp = getplaydef(actor, PlayerDefKey.offline.TOTAL_EXP) totalExp = totalExp or 0 fightExp = fightExp or 0 bubblePointExp = bubblePointExp or 0 return fightExp + bubblePointExp + totalExp > tonumber(expLimit) end return false end --- 保存离线挂机战斗经验 ---@param actor 玩家对象 ---@param exp 战斗经验 function onHook.saveOfflineFightExp(actor, exp) -- 是否超时判断 local start = getplaydef(actor, PlayerDefKey.offline.START_TIME) local offlineTimeout = ConfigDataManager.getTableValue("cfg_global", "value", "id", GlobalConfigId.OFFLINE_ON_HOOK_TIMEOUT) local now = getbaseinfo(actor, "now") if now - start > tonumber(offlineTimeout) * TimeUnit.MINUTE * TimeUnit.MILLISECOND then return end local offlineFightExp = getplaydef(actor, PlayerDefKey.offline.FIGHT_EXP) if string.isNullOrEmpty(offlineFightExp) then setplaydef(actor, PlayerDefKey.offline.FIGHT_EXP, tonumber(exp)) else if not this.checkExpLimit(actor, offlineFightExp, 0) then setplaydef(actor, PlayerDefKey.offline.FIGHT_EXP, tonumber(offlineFightExp) + tonumber(exp)) end end end --- 设置离线挂机巡逻状态 ---@param actor table 玩家对象 ---@param state number 状态 巡逻状态:1 战斗状态:2 无状态:0 function onHook.setOfflineState(actor, state) setofflinepatrolstate(actor, state) setplaydef(actor, PlayerDefKey.offline.PATROL_STATE, state) end --- 记录玩家最后一次离线挂机时间 ---@param actor table 玩家对象 function onHook.recordLastTime(actor) setplaydef(actor, PlayerDefKey.offline.LAST_TIME, getbaseinfo(actor, "now")) end --- 记录服务器启动时间 function onHook.recordStartTime() setsysvar(SystemVarConst.SERVER_START, getbaseinfo("now")) end --- 进入泡点地图设置离线挂机状态 function onHook.enterMap(actor) if this.checkBubblePointArea(actor) then setofflinepatrolstate(actor, 0) else -- 不是泡点地图设置为战斗状态 setofflinepatrolstate(actor, 2) end end