local skynet = require "skynet" local oo = require "Class" local log = require "Log" local pb = require "pb" local dataType = require "DataType" local errorInfo = require "ErrorInfo" local activity = require "Activity" local sqlUrl = require "SqlUrl" local db = require "DB" local json =require "json" local redisKeyUrl = require "RedisKeyUrl" local personal = require "Personal" local playerLand = require "PlayerLand" local serverId = tonumber(skynet.getenv "serverId") local RankDecoWeek = oo.class() RankDecoWeek.ActivityType = dataType.ActivityType_DecoWeek RankDecoWeek.ActivityStatus_No = 0 --无状态 RankDecoWeek.ActivityStatus_Join = 1 --参赛阶段 RankDecoWeek.ActivityStatus_Vote = 2 --评选阶段 RankDecoWeek.ActivityStatus_Balance = 3 --结算阶段 --[[ 参赛阶段:每周五晚18:00:00 至 每周日晚23:59:59 评选阶段:每周一0:00:00 至 每周五下午15:59:59 结算阶段:每周五下午16:00:00 至 17:59:59 • 保底刷新机制: ◦ 需要确保所有投稿作品刷新至任意用户的次数平均 ◦ 没有达到平均次数的作品会优先被推荐给参与投票用户,NPC作品刷新次数不计入 ◦ 到结算时没有达到平均推荐次数的作品则按“平均推荐次数-当前推荐次数”取整的值,在当前热度值基础上补偿对应的数额 ]] function RankDecoWeek:Init() self.curActivityStatus = self.ActivityStatus_No --当前服的状态 end --5秒Timer function RankDecoWeek:On5SecTimer() local activityThemeId , activityStatus , startTime , endTime = self:RefreshActivityInfo() log.info(string.format("装修周赛 获取最新活动详情 期数 %d 活动状态 %d 开始时间 %s 结束时间 %s" , activityThemeId , activityStatus , skynet.server.common:GetStrTime(startTime) , skynet.server.common:GetStrTime(endTime))) end --重置活动数据 function RankDecoWeek:ResetActivityData( activityThemeId ) local redisKey = string.format( redisKeyUrl.GameServerActivityDecoWeekInfoHash ) local returnValue = skynet.server.redis:hsetnx( redisKey , "isResetActivityData" , true ) if 0 == returnValue then --返回0,表示当期已经处理过,就不再往下走 return end --清除所有曝光数据 redisKey = string.format( redisKeyUrl.GameServerActivityDecoWeekInfoHash ) skynet.server.redis:hset( redisKey , "allExposureCount" , 0 ) skynet.server.redis:hdel( redisKey , "isGiveRankReward" ) --新的一期开始,清除之前的发奖的标记 skynet.server.redis:hdel( redisKey , "isCheckExtraExposure") --新的一期开始,清除之前的增加曝光次数的标记 skynet.server.redis:hset( redisKey , "allVoteScore" , 0 ) --清除投票介入分数 redisKey = redisKeyUrl.GameServerActivityDecoWeekHumanVoteAdjustScoreZSet skynet.server.redis:del(redisKey) --清除需要曝光次数 redisKey = redisKeyUrl.GameServerActivityDecoWeekNeedExpouseCountZSet skynet.server.redis:del(redisKey) --清除平均曝光次数 redisKey = redisKeyUrl.GameServerActivityDecoWeekAveExpouseCountZSet skynet.server.redis:del(redisKey) --初始化NPC的分数和设计时间 redisKey = redisKeyUrl.GameServerActivityDecoWeekNpcVoteScoreZSet local cfgAllNPCName = skynet.server.gameConfig:GetAllCfg( "NPCName") for k, v in pairs( cfgAllNPCName ) do local robotPartnerId = string.format( "Npc_%d" , v.id ) skynet.server.redis:zadd( redisKey , 0 , robotPartnerId ) skynet.server.personal:SetDetail( robotPartnerId ,"decoWeekDesignTime", 0 ) end --self:TestNpc() end --检测额外的曝光次数 function RankDecoWeek:CheckExtraExposure( activityThemeId ) local redisKey = string.format( redisKeyUrl.GameServerActivityDecoWeekInfoHash ) local returnValue = skynet.server.redis:hsetnx( redisKey , "isCheckExtraExposure" , true ) if 0 == returnValue then --返回0,表示当期已经处理过,就不再往下走 return end local redisVoteKey = redisKeyUrl.GameServerActivityDecoWeekNeedExpouseCountZSet local queryHumanData = skynet.server.redis:zrevrange( redisVoteKey , 0 , -1 ) local count = 0 local curTime = skynet.GetTime() log.info("周赛检测额外的曝光 开始处理" , #queryHumanData , activityThemeId) --遍历所有玩家 for k, partnerId in pairs( queryHumanData ) do function Do() local myDetail = skynet.server.personal:GetDetail( partnerId ) if myDetail and myDetail.rechargeAmount and myDetail.lastWeekGameTime and myDetail.decoWeekOpenShowCount then local lastWeekGameTime = math.floor( myDetail.lastWeekGameTime / 60 ) --上一周游戏时长(分钟) local addCount = 0 --额外的曝光次数 --玩家在进入投票阶段前的充值累积金额≥30元时,则获得额外20次的曝光机会 if myDetail.rechargeAmount >= 30 then addCount = addCount + 20 log.info(string.format("装修周赛 检测额外的曝光 玩家 %s 满足充值条件 充值金额 %d 增加次数 %d " , partnerId , myDetail.rechargeAmount or 0, 20)) end --玩家在投票阶段上一周的累积游戏时长≥400分钟时,则获得额外10次曝光机会 if lastWeekGameTime >= 400 then addCount = addCount + 10 log.info(string.format("装修周赛 检测额外的曝光 玩家 %s 满足在线时长条件 游戏时长 %d 增加次数 %d " , partnerId , lastWeekGameTime or 0 , 10)) end --玩家在本次投稿阶段累积完成交互≥35次时,则获得额外15次曝光机会 if myDetail.decoWeekOpenShowCount >= 35 then addCount = addCount + 15 log.info(string.format("装修周赛 检测额外的曝光 玩家 %s 满足交互条件 次数 %d 增加次数 %d " , partnerId , myDetail.decoWeekOpenShowCount or 0 , 15)) end local redisKey = redisKeyUrl.GameServerActivityDecoWeekNeedExpouseCountZSet skynet.server.redis:zincrby( redisKey , addCount , partnerId ) end end local isDo,callData = pcall( Do ) local sendMsg,sendLen = 0,0 if not isDo then local errorText = "装修周赛 检测额外的曝光次数 结算报错" if partnerId then log.warning(errorText , partnerId , callData ) else log.warning( errorText , callData) end end count = count + 1 if count >= 100 then --处理100个玩家睡眠1毫秒 log.info("周赛检测额外的曝光 100玩家时间花费" , skynet.GetTime() - curTime) skynet.sleep(1) count = 0 curTime = skynet.GetTime() end end log.info("周赛检测额外的曝光 结束处理" , #queryHumanData , activityThemeId) end --计算排名 function RankDecoWeek:CalcRank( activityThemeId ) local redisWeekInfoKey = string.format( redisKeyUrl.GameServerActivityDecoWeekInfoHash ) local returnValue = skynet.server.redis:hsetnx( redisWeekInfoKey , "isGiveRankReward" , true ) if 0 == returnValue then --返回0,表示有其它服务器发了奖励,就不再往下走 return end local curTime = skynet.GetTime() --这里可以打开重置活动数据的标记 skynet.server.redis:hdel( redisWeekInfoKey , "isResetActivityData" ) ------------------------------------------------------------------------第一步 获取所有排行榜数据------------------------------------------------------------------------ --获取玩家分数 local redisVoteKey = string.format( redisKeyUrl.GameServerActivityDecoWeekHumanVoteScoreZSet , activityThemeId ) local queryHumanData = skynet.server.redis:zrevrangebyscore( redisVoteKey , "+inf" , "-inf" , "withscores" ) queryHumanData = redisKeyUrl:CovertOrderTable( queryHumanData ) log.info("周赛结算步骤1 时间花费" , skynet.GetTime() - curTime) --获取NPC玩家分数 redisVoteKey = redisKeyUrl.GameServerActivityDecoWeekNpcVoteScoreZSet local queryNpcData = skynet.server.redis:zrevrangebyscore( redisVoteKey , "+inf" , "-inf" , "withscores" ) queryNpcData = redisKeyUrl:CovertOrderTable( queryNpcData ) log.info("周赛结算步骤2 时间花费" , skynet.GetTime() - curTime) local queryData = {} local maxDesignCount = 0 --所有参加设计玩家数量 local cfgSValue = skynet.server.gameConfig:GetAllCfg( "SValue") for k, v in pairs(queryHumanData) do maxDesignCount = maxDesignCount + 1 v.value = v.value * cfgSValue.weeklyContestHeatValue --玩家分数乘以热度系数 table.insert( queryData , v ) end log.info("周赛结算步骤3 时间花费" , skynet.GetTime() - curTime) for k, v in pairs(queryNpcData) do maxDesignCount = maxDesignCount + 1 v.value = v.value * cfgSValue.weeklyContestHeatValue --玩家分数乘以热度系数 table.insert( queryData , v ) end log.info("周赛结算步骤4 时间花费" , skynet.GetTime() - curTime) local rankId = 0 --排名ID local rankRatio = 0 --排名百分比(保留两位小数) local allExposureCount = skynet.server.redis:hget( redisWeekInfoKey , "allExposureCount" ) or 0 --所有曝光次数 local aveExposureCount = math.floor( allExposureCount / maxDesignCount ) --平均曝光次数 log.info("周赛结算步骤5 时间花费" , skynet.GetTime() - curTime) ------------------------------------------------------------------------第二步 根据曝光 补偿分数------------------------------------------------------------------------ --获取需要曝光补偿的玩家数据(补偿范围分数1~+inf) local redisNeedExpouseCountKey = redisKeyUrl.GameServerActivityDecoWeekNeedExpouseCountZSet local queryNeedExpouseData = skynet.server.redis:zrangebyscore( redisNeedExpouseCountKey , 1 , "+inf" , "withscores" ) queryNeedExpouseData = redisKeyUrl:CovertOrderTable( queryNeedExpouseData ) local needExpouseCount = #queryNeedExpouseData log.info(string.format("装修周赛 需要曝光补偿的玩家数量 %d " , needExpouseCount)) if needExpouseCount > 0 then --获取需要曝光次数 local function GetNeedExpouseCount( partnerId ) if string.find( partnerId , "Npc_") then return 0 end for k, v in pairs( queryNeedExpouseData ) do if partnerId == v.key then return v.value end end return 0 end local redisHumanVoteKey = string.format( redisKeyUrl.GameServerActivityDecoWeekHumanVoteScoreZSet , activityThemeId ) for k , v in pairs( queryData ) do function Do() local partnerId = v.key local subsidyCount = GetNeedExpouseCount( partnerId ) if subsidyCount >0 then --没有达到平均推荐次数的作品则按((“平均推荐次数-当前推荐次数”) / 10 + 0.5 (为了四舍五入))取整的值,在当前热度值基础上补偿对应的数额 --subsidyCount = math.floor((((aveExposureCount - curDetail.decoWeekExposureCount) / 10 ) + 0.5) ) queryData[ k ].value = queryData[ k ].value + ( subsidyCount * cfgSValue.weeklyContestHeatValue ) skynet.server.redis:zincrby( redisHumanVoteKey , subsidyCount , partnerId ) log.info(string.format("装修周赛 玩家 %s 原分数 %d 补偿分数 %d 最终分数 %d", partnerId , (queryData[ k ].value / (cfgSValue.weeklyContestHeatValue or 1)) - subsidyCount , subsidyCount , queryData[ k ].value)) skynet.sleep(1) end end local isDo,callData = pcall( Do ) local sendMsg,sendLen = 0,0 if not isDo then local errorText = "装修周赛 补偿分数 结算报错" if v then log.warning(errorText , v.key , v.value , callData ) else log.warning( errorText , callData) end end end end log.info("周赛结算步骤6 时间花费" , skynet.GetTime() - curTime) --对数据进行一个降序排列 table.sort(queryData , function (a,b) return a.value >b.value end) log.info("周赛结算步骤7 时间花费" , skynet.GetTime() - curTime) ------------------------------------------------------------------------第三步 计算所有玩家排名信息,并找出最大排名------------------------------------------------------------------ local lastUserScore = -1 --上一个玩家的分数 for k , v in pairs( queryData ) do function Do() local partnerId = v.key local score = v.value if -1 == lastUserScore or score < lastUserScore then --第一个玩家直接名次+1或者分数大于上一个玩家名次+1 rankId = rankId + 1 end lastUserScore = score queryData[ k ].rankId = rankId end local isDo,callData = pcall( Do ) local sendMsg,sendLen = 0,0 if not isDo then local errorText = "装修周赛 计算名次 结算报错" if v then log.warning(errorText , v.key , v.value , callData ) else log.warning( errorText , callData) end end end log.info("周赛结算步骤8 时间花费" , skynet.GetTime() - curTime) --保存下最大设计数量,平均曝光数量和最大排名ID skynet.server.redis:hmset( redisWeekInfoKey , "lastActivityId" , activityThemeId ,"maxDesignCount" , maxDesignCount , "aveExposureCount" , aveExposureCount , "maxRankId" , rankId ) log.info(string.format("装修周赛 计算排名 所有设计人数 %d 所有曝光次数 %d 平均曝光次数 %d 最大名次 %d " , maxDesignCount , allExposureCount , aveExposureCount , rankId)) ------------------------------------------------------------------------第四步 给玩家发放离线消息------------------------------------------------------------------ local isSaveTopRank = false for k , v in pairs( queryData ) do function Do() local partnerId = v.key local myRankId = v.rankId local score = v.value local rankRatio , rankRange, rankRewardId = self:GetRewardInfo( activityThemeId , myRankId , rankId) if rankRewardId > 0 then --保存下玩家的排名 local msgData = {} msgData.rankRange = rankRange msgData.rankRewardId = rankRewardId msgData.activityThemeId = activityThemeId --获取7天后18点时间戳 local sendMailTime = skynet.server.common:GetAfterSomeDayTime( 7 ,18) skynet.server.personal:AddOfflineMsg( partnerId , dataType.OfflineMsgType_ActivityDecoRankId , msgData , 45 ,sendMailTime) skynet.server.personal:SetDetail( partnerId , "decoWeekLastThemeId" , activityThemeId , "decoWeekIsLastSubmitDesign" , true , "decoWeekRankRange" , rankRange , "decoWeekRewardId" , rankRewardId , "decoWeekRewardStatus" , dataType.DecoWeekRewardStatus_AddOfflineMsg, "decoWeekLastRankRange" , rankRange , "decoWeekLastRewardId" , rankRewardId, "decoWeekLastRewardCanGet" , true) if 1 == myRankId and not isSaveTopRank then isSaveTopRank = true self:SaveTopDesign( activityThemeId , partnerId , score ) end log.info(string.format("装修周赛 玩家 %s 当前排名 %d 分数 %d 当前排名因子 %f 当前排名范围最大因子 %f 奖励ID %d", partnerId , myRankId , score , rankRatio , rankRange , rankRewardId)) skynet.sleep(1) end end local isDo,callData = pcall( Do ) local sendMsg,sendLen = 0,0 if not isDo then local errorText = "装修周赛 发送离线邮件 结算报错" if v then log.warning(errorText , v.key , v.value , callData ) else log.warning( errorText , callData) end end end log.info("周赛结算步骤9 时间花费" , skynet.GetTime() - curTime) end --改变活动状态 function RankDecoWeek:ChangeStatus( activityThemeId , newActivityStatus ) self.curActivityStatus = newActivityStatus if self.ActivityStatus_Join == newActivityStatus then --重置活动数据 self:ResetActivityData( activityThemeId ) elseif self.ActivityStatus_Vote == newActivityStatus then --被投票的玩家是不是增加额外的曝光次数 self:CheckExtraExposure( activityThemeId ) elseif self.ActivityStatus_Balance == newActivityStatus then --计算排名 self:CalcRank( activityThemeId ) end end --获取当前活动信息 function RankDecoWeek:RefreshActivityInfo() local newActivityStatus = self.ActivityStatus_No local curTime = skynet.GetTime() local cfgAllWeeklyContest = skynet.server.gameConfig:GetAllCfg( "WeeklyContest" ) local startTime,endTime = 0 , 0 for k, v in pairs( cfgAllWeeklyContest ) do local postBeginTime = skynet.server.common:GetTime( v.postBeginTime ) local postEndTime = skynet.server.common:GetTime( v.postEndTime ) local voteBeginTime = skynet.server.common:GetTime( v.voteBeginTime ) local voteEndTime = skynet.server.common:GetTime( v.voteEndTime ) local settleBeginTime = skynet.server.common:GetTime( v.settleBeginTime ) local settleEndTime = skynet.server.common:GetTime( v.settleEndTime ) --根据配置时间获取当前阶段 if curTime >= postBeginTime and curTime <= postEndTime then newActivityStatus = self.ActivityStatus_Join startTime = postBeginTime endTime = postEndTime elseif curTime >= voteBeginTime and curTime <= voteEndTime then newActivityStatus = self.ActivityStatus_Vote startTime = voteBeginTime endTime = voteEndTime elseif curTime >= settleBeginTime and curTime <= settleEndTime then newActivityStatus = self.ActivityStatus_Balance startTime = settleBeginTime endTime = settleEndTime end --修改最新的活动ID和状态,切状态时同步下redis上的数据 if self.ActivityStatus_No ~= newActivityStatus then --newActivityStatus = 2 local redisKey = string.format( redisKeyUrl.GameServerActivityDecoWeekInfoHash ) skynet.server.redis:hmset( redisKey , "activityThemeId" , v.id , "activityStatus" , newActivityStatus , "stageStartTime" , startTime , "stageEndTime" , endTime ) if self.curActivityStatus ~= newActivityStatus then log.info(string.format("装修周赛 设置最新活动详情 期数 %d 当前状态 %d 最新状态 %d 开始时间 %s 结束时间 %s" , v.id , self.curActivityStatus , newActivityStatus , skynet.server.common:GetStrTime(startTime) , skynet.server.common:GetStrTime(endTime))) self:ChangeStatus( v.id , newActivityStatus ) end return v.id , newActivityStatus , startTime , endTime end end self.curActivityStatus = self.ActivityStatus_No return 0 , 0 , 0 , 0 end --保存分数最高的玩家 function RankDecoWeek:SaveTopDesign( activityThemeId , partnerId , score ) --将第一名的设计只在下来 local redisKey = redisKeyUrl.GameServerActivityDecoWeekTopDesignList local curDetail = skynet.server.personal:GetDetail( partnerId ) --获取个人详细信息 if curDetail then local topDesign = {} topDesign.themeId = activityThemeId topDesign.partnerId = partnerId topDesign.nickName = curDetail.nickName topDesign.score = score --* cfgSValue.weeklyContestHeatValue if skynet.server.gameConfig:IsOnline() then topDesign.designUrl = curDetail.decoWeekDesignOssUrl else topDesign.designUrl = curDetail.decoWeekDesignUrl end topDesign.designTime = curDetail.decoWeekDesignTime skynet.server.redis:lpush( redisKey , json:encode(topDesign)) else log.info(string.format("装修周赛 NPC %s 为第一名 没有个人信息 不保存到热门设计中", partnerId )) end end --获取奖励信息 function RankDecoWeek:GetRewardInfo( activityThemeId , myRankId , maxRankId ) local cfgOneWeekly = skynet.server.gameConfig:GetCurCfg( "WeeklyContest" , activityThemeId ) local rankRange = cfgOneWeekly.rankRange --排名范围 local rankReward = cfgOneWeekly.rankReward --排名范围奖励 local rankRatio = tonumber(string.format("%.2f", myRankId / maxRankId )) for i = 1, #rankRange, 1 do if rankRatio <= rankRange[i] then return rankRatio , rankRange[i] , rankReward[i] end end return 0.0 , 0.0 , 0 end --测试NPC function RankDecoWeek:TestNpc() --初始化NPC的分数和设计时间 local redisKey = redisKeyUrl.GameServerActivityDecoWeekNpcVoteScoreZSet local cfgAllNPCName = skynet.server.gameConfig:GetAllCfg( "NPCName") local startPos = 0 for k, v in pairs( cfgAllNPCName ) do local robotPartnerId = string.format( "Npc_%d" ,startPos + v.id ) skynet.server.redis:zadd( redisKey , math.random(10, 20) , robotPartnerId ) skynet.server.personal:SetDetail( robotPartnerId ,"decoWeekDesignTime", 0 ) end startPos = 200 for k, v in pairs( cfgAllNPCName ) do local robotPartnerId = string.format( "Npc_%d" ,startPos + v.id ) --skynet.server.redis:zadd( redisKey , 0 , robotPartnerId ) 测试要删 skynet.server.redis:zadd( redisKey , math.random(20, 40) , robotPartnerId ) skynet.server.personal:SetDetail( robotPartnerId ,"decoWeekDesignTime", 0 ) end startPos = 400 for k, v in pairs( cfgAllNPCName ) do local robotPartnerId = string.format( "Npc_%d" , startPos +v.id ) --skynet.server.redis:zadd( redisKey , 0 , robotPartnerId ) 测试要删 skynet.server.redis:zadd( redisKey , math.random(40, 80) , robotPartnerId ) skynet.server.personal:SetDetail( robotPartnerId ,"decoWeekDesignTime", 0 ) end end skynet.server.rankDecoWeek = RankDecoWeek return RankDecoWeek