HomeServer/Server/AllServer/RankServer/RankDecoWeek.lua
2024-11-20 15:41:37 +08:00

485 lines
23 KiB
Lua
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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