---------------------------------- -- 角色相关逻辑 ---------------------------------- local Config = require("Config") local Util = require("common.Util") local Log = require("common.Log") local Lang = require("common.Lang") local LogDefine = require("common.LogDefine") local CommonDefine = require("common.CommonDefine") local RoleExcel = require("excel.role") local Msg = require("core.Msg") local Obj = require("core.Obj") local Timer = require("core.Timer") local RoleAttr = require("role.RoleAttr") local RoleDBLogic = require("role.RoleDBLogic") local RoleDefine = require("role.RoleDefine") local Broadcast = require("broadcast.Broadcast") local BagLogic = require("bag.BagLogic") local HeroLogic = require("hero.HeroLogic") local VipLogic = require("vip.VipLogic") local RoleSystemLogic = require("roleSystem.RoleSystemLogic") local BarTaskLogic = require("bar.BarTaskLogic") local CombatPosLogic = require("combat.CombatPosLogic") local ChengjiuLogic = require("chengjiu.ChengjiuLogic") local ChengjiuDefine = require("chengjiu.ChengjiuDefine") local OnlineRewardLogic = require("onlineReward.OnlineRewardLogic") local SettingLogic = require("setting.SettingLogic") local ChatHandler = require("chat.Handler") local FriendLogic = require("friend.FriendLogic") local UnionDBLogic = require("union.UnionDBLogic") local BuyLogic = require("topup.BuyLogic") local ReportManager = require("platform.ReportManager") local BattleLogic = require("battle.BattleLogic") local ChatPaoMaLogic = require("chat.ChatPaoMaLogic") local RoleLogic = require("role.RoleLogic") local FuwenLogic = require("fuwen.FuwenLogic") local EquipLogic = require("equip.EquipLogic") local TequanLogic = require("qqTequan.TequanLogic") local ShopLogic = require("shop.ShopLogic") local MailLogic = require("mail.MailLogic") local LianyuLogic = require("lianyu.LianyuLogic") local MiddleLogic = require("middle.MiddleLogic") local MiddleOption = require("middle.MiddleOption") local GuideLogic = require("guide.GuideLogic") local ItemDefine = require("bag.ItemDefine") local DrillLogic = require("drill.DrillLogic") local BRoleLogic = require("billboard.BRoleLogic") local BillboardDefine = require("billboard.BillboardDefine") local FcmLogic = require("fcm.FcmLogic") local CombatLogic = require("combat.CombatLogic") local SceneHandler = require("scene.Handler") local TuiSongLiBao = require("present.TuiSongLiBao") local CopyLogic = require("copy.CopyLogic") local HuanJingTowerLogic = require("huanjingTower.HuanjingTowerLogic") local UnionLivenessLogic = require("union.UnionLivenessLogic") local UnionDonateLogic = require("union.UnionDonateLogic") local RoleStrongLogic = require("role.RoleStrongLogic") local DailyTaskLogic = require("dailyTask.DailyTaskLogic") local LiLianLogic = require("dailyTask.LiLianLogic") local FirstChargeLogic = require("present.FirstChargeLogic") local JjcLadderLogic = require("jjcLadder.JjcLadderLogic") local RelationGiftLogic = require("present.RelationGiftLogic") local WelfareGiftLogic = require("present.WelfareGiftLogic") local GlobalWorld = require("core.GlobalWorld") local ChatRecord = require("chat.ChatRecord") local WarReportLogic = require("warReport.WarReportLogic") local ZhuanpanLogic = require("zhuanpan.ZhuanpanLogic") local UnionRedBagLogic = require("union.UnionRedBagLogic") local JjcGodWarLogic = require("jjcGodWar.JjcGodWarLogic") local JibanLogic = require("combat.JibanLogic") local YunYingLogic = require("yunying.YunYingLogic") local KingWorldLogic = require("present.KingWorldLogic") local CommonDB = require("common.CommonDB") local ZhanbuLogic = require("zhanbu.ZhanbuLogic") local TopupLogic = require("topup.TopupLogic") local HeroLogLogic = require("absAct.HeroLogLogic") local FanliLogic = require("platform.FanliLogic") local HeroGrowUp = require("absAct.HeroGrowUp") local MailManager = require("mail.MailManager") local MailExcel = require("excel.mail") local PfLogic = require("platform.PfLogic") local MoshouLogic = require("moshou.MoshouLogic") local RoleSystemDefine = require("roleSystem.RoleSystemDefine") local LostTempleLogic = require("lostTemple.lostTempleLogic") local DrawCardV2Excel = require("excel.drawCard").drawCardV2 local Json = require("common.Json") fds = fds or {} -- fd->obj_id,包括创角中+游戏中 onlineAccount = onlineAccount or {} -- account->human,包括创角中+游戏中 onlineHuman = onlineHuman or {} -- name->human,仅包括游戏中 onlineUuid = onlineUuid or {} -- uuid->human,仅包括游戏中 function create(fd, account, db) local human = {} Obj.create(human, Obj.TYPE_HUMAN) fds[fd] = human.id human.fd = fd human.db = db or RoleDBLogic.loadRole(account) if human.db ~= nil then initHuman(human) else human.account = account end return human end function initHuman(human) local now = os.time() human.preLoginTime = human.db.lastLoginTime -- 上一次登录时间 human.db.lastLoginTime = now -- 记录最近一次登录时间 human.saveDBTime = Timer.now -- 人物数据库保存时间 human.firstEnter = true -- 首次进入场景 -- 角色信息初始化 onlineAccount[human.db.account] = human onlineHuman[human.db.name] = human onlineUuid[human.db._id] = human end function destroy(human) local account = human.account or human.db.account if human.fd then fds[human.fd] = nil end if human.db then -- 已创角 onlineAccount[account] = nil onlineHuman[human.db.name] = nil onlineUuid[human.db._id] = nil end Obj.destroy(human) end function doDisconnect(human, code) print("doDisconnect account:", human.account or human.db.account, " code:", code) if human.db then local now = os.time() if human.db.changeNameCnt ~= nil then Log.write(Log.LOGID_OSS_LOGOUT, human.db._id, human.db.account, human.db.name, human.db.lv, human.db.ip, code, now - human.db.lastLoginTime) end if human.fd then onLogout(human) end end if code ~= CommonDefine.DISCONNECT_NORMAL and code ~= CommonDefine.DISCONNECT_NORMAL_AFTER then if _G.is_middle ~= true then local mm = Msg.gc.GC_DISCONNECT mm.code = code mm.msg = CommonDefine.DISCONNECT_MSG[code] or "" Msg.send(mm, human.fd) end end if human.db then if code ~= CommonDefine.DISCONNECT_NORMAL then -- 正常断线先不销毁 if code ~= CommonDefine.DISCONNECT_DUPLICATE then CombatLogic.forceFinish(human) end ReportManager.quit(human,code) destroy(human) if code == CommonDefine.DISCONNECT_DUPLICATE then else if _G.is_middle == true then -- 数据销毁 要把db数据发回给正常服 MiddleLogic.sendWL_DB_BACK(human) else local ret, err = pcall(save, human) if not ret then Log.write(Log.LOGID_ERR_PCALL, "destroy account=" .. human.db.account, err) end end end return end else destroy(human) -- 未创角,立刻销毁对象 return end if not human.fd then assert() end fds[human.fd] = nil human.fd = nil human.f5 = Timer.now end -- 下线回调 function onLogout(human) local nowTime = os.time() human.db.lastLogoutTime = os.time() -- 记录最近登出时间 local time = math.max(nowTime - human.db.lastLoginTime, 0) human.db.onlineTime = (human.db.onlineTime or 0) + time -- 记录在线时长 local time2 = nowTime - math.max(human.db.lastLoginTime, Util.getDayStartTime(nowTime)) human.db.onlineTimeDay = (human.db.onlineTimeDay or 0) + math.max(time2, 0) -- 记录本日在线时长 LianyuLogic.onLogout(human) OnlineRewardLogic.onLogout(human, time) YunYingLogic.onCallBack(human, "onLogout") end function saveCharDB() local now = Timer.now local minn local ans for _, human in pairs(onlineUuid) do if human.fd then local t = human.saveDBTime if 300000 <= now - t and (not minn or t < minn) then minn = t ans = human end end end if not ans then return end ans.saveDBTime = now local ret, err = pcall(save, ans) if not ret then Log.write(Log.LOGID_ERR_PCALL, "saveCharDB rolename="..ans.db.name, err) end end function humanOfflineCheck() if _G.is_middle == true then local now = os.time() for account, human in pairs(onlineAccount) do if human.fd == nil then if human.f5 == nil or Timer.now - human.f5 > 30000 then doDisconnect(human, CommonDefine.DISCONNECT_NORMAL_AFTER, "") end end end for account, humanTempInfo in pairs(MiddleLogic.TRY_LOGIN_HUMAN) do if now - humanTempInfo.tsForMiddle > 30 then MiddleLogic.TRY_LOGIN_HUMAN[account] = nil end end else for uuid, human in pairs(onlineUuid) do -- 暂时不检测 不主动结束战斗 方便客户端调试 dxzeng debug if CombatLogic.COMBAT_CACHE[uuid] and not Config.IS_DEBUG then -- reyes test CombatLogic.checkFinish(human) else if human.fd == nil then if human.f5 == nil or Timer.now - human.f5 > 32000 then doDisconnect(human, CommonDefine.DISCONNECT_NORMAL_AFTER) end end end end end end function save(human) RoleDBLogic.saveRole(human.db) end -- 获得在线人数(包含停留在创角页) function getOnlineCnt() local onlineCnt = 0 for _ in pairs(onlineAccount) do onlineCnt = onlineCnt + 1 end return onlineCnt end function onZhandouliUpdate(human) -- 更新排行榜 LiLianLogic.onCallback(human,LiLianLogic.LILIAN_OUTID22,human.db.zhandouli,nil,nil,true) YunYingLogic.onCallBack(human, "onZhandouli", human.db.zhandouli) RoleSystemLogic.onDot(human, RoleSystemDefine.ROLE_SYS_ID_203) end local old_attr = {} function doCalc(human) for k in pairs(old_attr) do old_attr[k] = nil end if human.attr then for k, v in pairs(human.attr) do old_attr[k] = v end end RoleAttr.doCalc(human) onZhandouliUpdate(human) local mm = Msg.gc.GC_ROLE_ATTR local len = 0 local left = mm.attrs for k,v in pairs(human.attr) do if v ~= old_attr[k] then len = len + 1 left[len].key = k left[len].value = v end end if 0 < len then left[0] = len Msg.send(mm, human.fd) --Msg.trace(mm) end end function sendAttr(human,key) if human.db.middleFlag and _G.is_middle ~= true then -- 这个时候的属性改变由跨服服计算 return end if not human.attr[key] then return end local msgRet = Msg.gc.GC_ROLE_ATTR msgRet.attrs[0] = 1 local attr = msgRet.attrs[1] attr.key = key attr.value = human.attr[key] Msg.send(msgRet, human.fd) --Msg.trace(msgRet) end function doCalcHero(human, bagIndex, heroAttrs) RoleAttr.doCalcHero(human, bagIndex,heroAttrs) doCalc(human) end function getHeroAttrs(human, bagIndex) if not human.heroAttrs or not human.heroAttrs[bagIndex] then RoleAttr.doCalcHero(human, bagIndex) end return human.heroAttrs[bagIndex] end function checkLevelUp(human) if human.db.lv > #RoleExcel.exp then return 0 end local levelUp = 0 while human.db.exp > 0 do local needExp = 0 if human.db.lv < 200 then needExp = RoleExcel.exp[human.db.lv + 1].exp end if needExp <= human.db.exp then if human.db.lv >= #RoleExcel.exp then if human.db.exp > needExp then human.db.exp = needExp end break else human.db.exp = human.db.exp - needExp human.db.lv = human.db.lv + 1 levelUp = 1 JibanLogic.onCallback(human,4,human.db.lv) end else break end end return levelUp end function onLvUpCB(human, oldLv, newLv) doCalc(human) --升级判断钻石基金是否返钻 --FundLogic.onLvUp(human, oldLv, newLv) GuideLogic.onLvUpCB(human, oldLv, newLv) RoleSystemLogic.onLvUp(human) YunYingLogic.onLevelUp(human, oldLv, newLv) TuiSongLiBao.tuiSongLiBaoOnTask(human,TuiSongLiBao.TUISONGLIBAOTASK_LV, newLv, oldLv) CombatPosLogic.onLvUp(human, oldLv, newLv) ChengjiuLogic.onLvUp(human) -- 更新等级排名数据 if BRoleLogic.updateData(BillboardDefine.TYPE_LV, human.db) then -- 计算世界等级 GlobalWorld.doCalcWorldLv() end -- 开服邮件 local subDay = CommonDB.getServerOpenDay() human.db.sendPfEmail = human.db.sendPfEmail or {} for k, config in ipairs(MailExcel.openMail) do if subDay and subDay >= config.openDay[1] and subDay <= config.openDay[2] and PfLogic.isServerIndex(config.svrIndex) then if newLv >= config.lv and human.db.sendPfEmail[k] == nil then human.db.sendPfEmail[k] = 1 local title = MailExcel.mail[config.mailID].title local content = MailExcel.mail[config.mailID].content local senderName = MailExcel.mail[config.mailID].senderName if #config.items > 0 then MailManager.add(MailManager.SYSTEM, human.db._id, title, content, config.items, senderName) else MailManager.add(MailManager.SYSTEM, human.db._id, title, content, nil, senderName) end end end end --升级日志 Log.write(Log.LOGID_OSS_LEVELUP, human.db._id, human.db.account, human.db.name, oldLv, newLv) end function addExp(human, addExp) if human.db.lv > #RoleExcel.exp then return end if not addExp then return end addExp = math.floor(addExp) if addExp == 0 then return end local total = human.db.exp + addExp total = math.max(0, total) total = math.floor(total) if total == human.db.exp then return end local oldLv = human.db.lv human.db.exp = total local levelUp = checkLevelUp(human) if levelUp == 1 then onLvUpCB(human, oldLv, human.db.lv) else human.attr[RoleDefine.EXP] = human.db.exp sendAttr(human,RoleDefine.EXP) end end function canAddJinbi(human, d) if human.db.jinbi + d > ItemDefine.BAG_ITEM_MAX_JINBI then return Broadcast.sendErr(human, Lang.COMMON_ADD_JINBI_LIMIT) else return true end end function updateJinbi(human, d, logType,item_id, num) if not LogDefine.DEFINE[logType] or not LogDefine.TYPE["jinbi"] then assert() end d = math.floor(d) if d == 0 then return end local val = human.db.jinbi + d if val < 0 then assert() end if val > ItemDefine.BAG_ITEM_MAX_JINBI then val = ItemDefine.BAG_ITEM_MAX_JINBI Broadcast.sendErr(human, Lang.COMMON_ADD_JINBI_LIMIT) end --金币使用日志 if item_id == nil then item_id = 0 end if num == nil then num = 0 end Log.write(Log.LOGID_OSS_JINBI, human.db._id, human.db.account, human.db.name, human.db.lv, d, LogDefine.DEFINE[logType] + LogDefine.TYPE["jinbi"], item_id or 0, num or 0, val) human.db.jinbi = val if human.fd then human.attr[RoleDefine.JINBI] = human.db.jinbi sendAttr(human,RoleDefine.JINBI) end MiddleOption.updateYinliang(human, d, logType) if d < 0 then HeroGrowUp.onCallback(human, HeroGrowUp.TASKTYPE4, -d) end return true end function addZuanshi(human, d, logType, item_id, num) d = math.floor(d) if d < 0 then assert(nil) end if d == 0 then return end if not LogDefine.DEFINE[logType] or not LogDefine.TYPE["zuanshi"] then assert() end local val = human.db.zuanshi + d --钻石使用日志 Log.write(Log.LOGID_OSS_GOLD, human.db._id, human.db.account, human.db.name, human.db.lv, d, LogDefine.DEFINE[logType] + LogDefine.TYPE["zuanshi"] , item_id or 0, num or 0, val) human.db.zuanshi = val if human.fd then human.attr[RoleDefine.ZUANSHI] = human.db.zuanshi sendAttr(human, RoleDefine.ZUANSHI) end MiddleOption.updateYuanbao(human, d, logType) return true end function decZuanshi(human, d, logType, item_id, num, noSend) d = math.floor(d) if d > 0 then assert(nil) end if d == 0 then return end if not LogDefine.DEFINE[logType] or not LogDefine.TYPE["zuanshi"] then assert() end local val = human.db.zuanshi + d if val < 0 then assert() end human.db.zuanshi = val Log.write(Log.LOGID_OSS_GOLD, human.db._id, human.db.account, human.db.name, human.db.lv, d, LogDefine.DEFINE[logType] + LogDefine.TYPE["zuanshi"] , item_id or 0, num or 0, val) if human.fd then human.attr[RoleDefine.ZUANSHI] = human.db.zuanshi sendAttr(human, RoleDefine.ZUANSHI) end HeroLogLogic.finishTaskCB(human,HeroLogLogic.HERO_LOG_TYPE_6,-d) MiddleOption.updateYuanbao(human, d, logType) HeroGrowUp.onCallback(human, HeroGrowUp.TASKTYPE5, -d) YunYingLogic.onCallBack(human, "onDecZuanshi",-d) return true end -- 钻石不足时候的处理 function checkRMB(human, need) local sum = human.db.zuanshi if sum < need then if _G.is_middle == true then Broadcast.sendErr(human, Lang.COMMON_NO_ZUANSHI) else if not SceneHandler.canCharge(human) then Broadcast.sendErr(human, Lang.COMMON_NO_ZUANSHI) else local msgRet = Msg.gc.GC_BAG_NORMB Msg.send(msgRet,human.fd) end end return false else return true end end function updateFriendHeart(human, d, logType,item_id, num) if not LogDefine.DEFINE[logType] or not LogDefine.TYPE["friendheart"] then assert() end d = math.floor(d) if d == 0 then return end local val = human.db.friendHeart + d if val < 0 then assert() end --好友爱心使用日志 if item_id == nil then item_id = 0 end if num == nil then num = 0 end Log.write(Log.LOGID_OSS_FRIENDHEART, human.db._id, human.db.account, human.db.name, human.db.lv, d, LogDefine.DEFINE[logType] + LogDefine.TYPE["friendheart"], item_id or 0, num or 0, val) human.db.friendHeart = val if human.fd then human.attr[RoleDefine.FRIENDHEART] = human.db.friendHeart sendAttr(human,RoleDefine.FRIENDHEART) end return true end function sendHumanInfo(human,isNew) -- 处理称号被销毁的 if human.db.chenghao then if not human.db.chenghaoList[human.db.chenghao] then human.db.chenghao = nil end end local subDay = CommonDB.getServerOpenDay() local mm = Msg.gc.GC_ZZ_HUMAN_INFO RoleLogic.getRoleBase(human, mm.roleBase) local union = UnionDBLogic.getUnion(human.db.unionUuid) mm.unionName = union and union.name or "" mm.unionIdentity = union and union.id or "" mm.bannerID = union and union.bannerID or 0 mm.unionFrame = union and union.headFrame or 0 mm.identity = human.db.identity mm.vipLv = VipLogic.getVipLv(human) mm.animation = human.db.animation or 0 mm.background = human.db.background or 0 mm.debug = Config.IS_USE_GM_CMD and 1 or 0 mm.worldLv = GlobalWorld.getWorldLv() mm.openDay = subDay or 0 mm.guideState= GuideLogic.checkAllFinish(human) mm.isFrist = isNew and 1 or 0 local drawCardRateList = {} local drawCardV2Confs = DrawCardV2Excel local num = drawCardV2Confs and (#drawCardV2Confs) or 0 for i = 0 , num do local drawCardV2Conf = drawCardV2Confs[i] drawCardRateList[i] = drawCardRateList[i] or {} for weightIndex = 1 , #drawCardV2Conf.weight do drawCardRateList[i][weightIndex] = drawCardRateList[i][weightIndex] or {} drawCardRateList[i][weightIndex] = drawCardV2Conf.weight[weightIndex][2] end end mm.drawCardRateList = Json.Encode(drawCardRateList) Msg.send(mm, human.fd) end function enterCity(human) Msg.send(Msg.gc.GC_ENTER_CITY, human.fd) end -- 上线回调 function onLogin(human, isNew) human.db.ip = human.ip human.db.phpChanelID = human.phpChanelID if human.db.changeNameCnt ~= nil then Log.write(Log.LOGID_OSS_LOGIN, human.db._id, human.db.account, human.db.name, human.db.lv, human.db.ip) end updateDaily(human) if not FcmLogic.checkFcm(human) then return -- 防沉迷 踢下线 end TequanLogic.jihuopt(human) TequanLogic.setHumanTequan(human,human.pf_info) doCalc(human) sendHumanInfo(human,isNew) BagLogic.sendBagList(human) FuwenLogic.sendFuwenBagList(human) EquipLogic.sendEquipBagList(human) HeroLogic.sendHeroBagCap(human) HeroLogic.sendHeroBagList(human) CombatPosLogic.onLogin(human) VipLogic.onLogin(human) FriendLogic.onLogin(human) DailyTaskLogic.onLogin(human) OnlineRewardLogic.onLogin(human) SettingLogic.query(human) ChatHandler.onLogin(human) BattleLogic.onLogin(human) ChatPaoMaLogic.onLogin(human) MailLogic.cleanNilContent(human) YunYingLogic.onLogin(human) RoleStrongLogic.onLogin(human) RelationGiftLogic.onLogin(human) ChatRecord.sendAllNotRead(human) JjcGodWarLogic.onLogin(human) HeroGrowUp.onCallback(human, HeroGrowUp.TASKTYPE1, 1) if not isNew then ReportManager.login(human) end LostTempleLogic.onLogin(human) SettingLogic.onLogin(human) GuideLogic.onLogin(human) CombatLogic.onLogin(human) -- reyes test LianyuLogic.onLogin(human) MoshouLogic.onLogin(human) TopupLogic.checkKf(human) -- 最后执行离线发货 BuyLogic.onLogin(human) HeroLogLogic.finishTaskCB(human,HeroLogLogic.HERO_LOG_TYPE_1,1) -- 红点 检测 需要放在通用的onLogin 后面 保持数据的正确性 再检测红点 local pcallRet, pcallErr = pcall(RoleSystemLogic.onLogin, human) if not pcallRet then Log.write(Log.LOGID_ERR_PCALL, "RoleSystemLogic.onLogin err=" .. human.db.account, pcallErr) end enterCity(human) local day = Util.diffDay(human.db.createTime) if day == 2 or day == 7 then local msgRet = Msg.gc.GC_NOTICE_DADIAN msgRet.type = 2 msgRet.param = day msgRet.param2 = 0 Msg.send(msgRet,human.fd) end -- 注意 这个函数写在doLogin的最后 MiddleLogic.onLogin(human) end -- 这里有个潜规则,凡是使用每天都需更新的数据时,都必须先调用这个方法。by cc function updateDaily(human, isGm) if not isGm then if human.db.update_daily_time and Util.isSameDay(human.db.update_daily_time) then return true end end -- 跨月 if human.db.update_daily_time and Util.isSameMonth(human.db.update_daily_time) ~= true then human.db.topupAccountMonth = nil end YunYingLogic.updateDaily(human) BattleLogic.updateDaily(human) BarTaskLogic.refreshDailyTask(human) FriendLogic.refreshDailyTask(human) TequanLogic.cleanDaily(human) CopyLogic.updateDaily(human) HuanJingTowerLogic.updateDaily(human) VipLogic.updateDaily(human) WelfareGiftLogic.updateDaily(human) WarReportLogic.updateDaily(human) ZhanbuLogic.updateDaily(human) ZhuanpanLogic.updateDaily(human) LostTempleLogic.updateDaily(human) human.db.update_daily_time = os.time() human.db.dailyTask = nil human.db.dailyShareTask = nil human.db.jjcDailyFight = nil human.db.jjcWorship = nil human.db.personMail = nil human.db.topupAcountDaily = nil human.db.dailyLeiChong = nil human.db.onlineTimeDay = nil human.db.onlineTimeDayReport = nil human.db.heroResetCnt = nil human.db.dailyLibao = nil human.db.ectypeLike = nil human.db.ectypHurt = nil human.db.ectypeCnt = nil human.db.ectypLikeUuid = nil human.db.adRewardCnt = nil ShopLogic.updateDaily(human) LianyuLogic.updateDaily(human) DrillLogic.updateDaily(human) UnionLivenessLogic.updateDaily(human) UnionDonateLogic.updateDaily(human) FirstChargeLogic.updateDaily(human) JjcLadderLogic.updateDaily(human) UnionRedBagLogic.updateDaily(human) JjcGodWarLogic.updateDaily(human) -- 红点 发送 理论放最后 RoleSystemLogic.onLogin(human) end -- 角色心跳 function refresh() for _, human in pairs(onlineHuman) do --活动开启邮件提示 RoleSystemLogic.checkMailTips(human) end end