RolePvp.lua 9.37 KB

local RolePvp = {}

RolePvp.bind = function (Role)

local PVP_RANK_TIME_SORT_STD = 1924876800 -- 2030-12-31 00:00:00
local PVP_RANK_TIME_SORT_PLACE = 1000000 -- 时间戳占据 6位数
local PVP_RANK_TIME_SORT_PRECISION = 360 -- 时间精度 每6分钟忽略差异
local PVP_RANK_ROBOT_SCORE = globalCsv.pvp_base_score -- 机器人积分


function Role:unpackPvpScore(score)
	if not score then return globalCsv.pvp_base_score end
	score = tonumber(score)
	return math.floor(score / PVP_RANK_TIME_SORT_PLACE)
end

function Role:packPvpScore(score, now)
	now = now or skynet.timex()
	return math.floor(score * PVP_RANK_TIME_SORT_PLACE + (PVP_RANK_TIME_SORT_STD - now) / PVP_RANK_TIME_SORT_PRECISION)
end


function Role:changePvpScore(rankKey, changeScoreCallback, matchId)
	local dbKey = self:getPvpDBKey(rankKey)
	local roleId = self:getProperty("id")
	local isPlayer = matchId ~= -1
	local redret = redisproxy:pipelining(function(red)
		red:zscore(dbKey, roleId)
		red:zrevrank(dbKey, roleId)
		if isPlayer then
			red:zscore(dbKey, matchId)
		end
	end)
	local myScore = self:unpackPvpScore(redret[1])
	local oldMyRank = tonumber(redret[2] or -2) + 1
	local matchScore = PVP_RANK_ROBOT_SCORE
	if isPlayer then
		matchScore = self:unpackPvpScore(redret[3])
	end
	local oldmyScore, oldMatchScore = myScore, matchScore

	myScore, matchScore = changeScoreCallback(myScore, matchScore)
	myScore = math.max(myScore, 0)
	matchScore = math.max(matchScore, 0)

	local now = skynet.timex()
	redret = redisproxy:pipelining(function(red)
		red:zadd(dbKey, self:packPvpScore(myScore, now), roleId)
		if isPlayer then
			red:zadd(dbKey, self:packPvpScore(matchScore, now), matchId)
		end
		red:zrevrank(dbKey, roleId)
	end)

	local myRank
	if isPlayer then
		myRank = tonumber(redret[3] or -2) + 1
	else
		myRank = tonumber(redret[2] or -2) + 1
	end

	return {myScore, matchScore, oldmyScore, oldMatchScore, myRank, oldMyRank}
end

function Role:changePvpScoreCommon(matchId, isWin)
	local function changeScoreCallback(myScore, matchScore)
		if isWin then
			local scoreChange = math.ceil(60 / (1 + 10 ^ ((myScore - matchScore) / 400)))
			myScore = myScore + scoreChange
			matchScore = matchScore - scoreChange
		else
			local scoreChange = math.ceil(60 / (1 + 10 ^ ((matchScore - myScore) / 400)))
			myScore = myScore - scoreChange
			matchScore = matchScore + scoreChange
		end
		return myScore, matchScore
	end

	local result = self:changePvpScore(RANK_PVP_COMMON, changeScoreCallback, matchId)
	self:refreshPvpMatchC(result[1])
	return table.unpack(result)
end

function Role:calculatePvpHGift(division)
	local now = skynet.timex()
	local oldTime = self:getProperty("pvpHGTime")
	local oldReward = self:getProperty("pvpHGift")

	local newTime = oldTime
	local newReward = clone(oldReward)

	local divisionData = csvdb["pvp_group_divisionCsv"][division]
	if oldTime == 0 then
		newTime = now
	else
		local times = math.floor((now - oldTime) / globalCsv.pvp_high_reward_add_pre)
		newTime = oldTime + times * globalCsv.pvp_high_reward_add_pre

		for itemId, count in pairs(divisionData.reward:toNumMap()) do
			newReward[itemId] = math.min((newReward[itemId] or 0) + count, divisionData.limit)
		end
	end
	return newTime, newReward
end

function Role:changePvpScoreHigh(matchId, isWin)
	local oldMyDivision
	local function changeScoreCallback(myScore, matchScore)
		local myMinId = 0
		local matchMinId = 0
		for _id, division in ipairs(csvdb["pvp_group_divisionCsv"]) do
			if myScore >= division.division then
				myMinId = _id
			end
			if matchScore >= division.division then
				matchMinId = _id 
			end
		end
		oldMyDivision = myMinId
		if isWin then
			local scoreChange = math.ceil(50 / (1 + 10 ^ ((myScore - matchScore) / 1000)))
			myScore = myScore + scoreChange
			matchScore = matchScore - scoreChange
		else
			local scoreChange = math.ceil(50 / (1 + 10 ^ ((matchScore - myScore) / 1000)))
			myScore = myScore - scoreChange
			matchScore = matchScore + scoreChange
		end
		return math.max(myScore, csvdb["pvp_group_divisionCsv"][myMinId].division), math.max(matchScore, csvdb["pvp_group_divisionCsv"][matchMinId].division)
	end

	local result = self:changePvpScore(RANK_PVP_HIGHT, changeScoreCallback, matchId)
	--{myScore, matchScore, oldmyScore, oldMatchScore, myRank, oldMyRank}
	local newDivision = nil
	for _id, division in ipairs(csvdb["pvp_group_divisionCsv"]) do
		if result[1] >= division.division then
			newDivision = _id
		else
			break
		end
	end
	if newDivision ~= oldMyDivision then
		--刷新段位奖励
		local newTime, newReward = self:calculatePvpHGift(oldMyDivision)
		self:updateProperties({
			pvpHGTime = newTime,
			pvpHGift = newReward,
		})
	end

	self:refreshPvpMatchH(result[1])
	return table.unpack(result)
end

function Role:refreshPvpMatch(score, rankKey)

	local Fields = {
		[RANK_PVP_COMMON] = "pvpMC",
		[RANK_PVP_HIGHT] = "pvpMH",
	}
	local RobotCsvs = {
		[RANK_PVP_COMMON] = "pvp_robotCsv",
		[RANK_PVP_HIGHT] = "pvp_robotCsv",
	}
	local mField = Fields[rankKey]
	local robotCsv = RobotCsvs[rankKey]
	local dbKey = self:getPvpDBKey(rankKey)

	local roleId = self:getProperty("id")
	score =  score or self:unpackPvpScore(redisproxy:zscore(dbKey, roleId))

	local function getPlayers(levels)
		local redret = redisproxy:pipelining(function(red)
			for _, level in ipairs(levels) do
				local low, high = table.unpack(level)
				red:zadd(dbKey, low * PVP_RANK_TIME_SORT_PLACE, "std_temp1")
				red:zadd(dbKey, high * PVP_RANK_TIME_SORT_PLACE + PVP_RANK_TIME_SORT_PLACE - 1, "std_temp2")
				red:ZREVRANK(dbKey, "std_temp1")
				red:ZREVRANK(dbKey, "std_temp2")
			end
			red:zrem(dbKey, "std_temp1", "std_temp2")
		end)

		local PreGetCount = 7 
		local redret = redisproxy:pipelining(function(red)
			for idx, level in ipairs(levels) do
				local rank1 = tonumber(redret[(idx - 1) * 4 + 3])
				local rank2 = tonumber(redret[(idx - 1) * 4 + 4])
				if rank1 - rank2 > PreGetCount then
					rank2 = math.randomInt(rank2, rank1 - PreGetCount + 1)
					rank1 = rank2 + PreGetCount - 1
				end
				red:ZREVRANGE(dbKey, rank2, rank1)
			end
		end)
		return redret
	end

	local findIdx = #globalCsv.pvp_division
	for idx, limit in ipairs(globalCsv.pvp_division) do
		if score < limit then
			findIdx = idx - 1
			break
		end
	end
	local levels = {
		{}, {}, {}
	}
	if globalCsv.pvp_division[findIdx + 1] then
		levels[1] = {globalCsv.pvp_division[findIdx + 1], (globalCsv.pvp_division[findIdx + 2] or 100000000) - 1}
	end
	levels[2] =  {globalCsv.pvp_division[findIdx], (globalCsv.pvp_division[findIdx + 1] or 100000000) - 1}
	if globalCsv.pvp_division[findIdx - 1] then
		levels[3] =  {globalCsv.pvp_division[findIdx - 1], globalCsv.pvp_division[findIdx] - 1}
	end
	local redirect = {}
	for i = #levels , 1, -1 do
		if not next(levels[i]) then
			table.remove(levels, i)
			redirect[i] = -1
			for _, v in pairs(redirect) do
				redirect[_] = v - 1
			end
		else
			redirect[i] = i
		end
	end

	local result = getPlayers(levels)
	local match = self:getProperty(mField)
	local hadPlayer = {[roleId] = 1}
	local hadRobot = {}
	for _, one in pairs(match) do
		if one.t == 1 then
			hadPlayer[one.id] = 1
		elseif one.t == 2 then
			hadRobot[one.id] = 1
		end
	end

	for _, temp in pairs(result) do
		for i = #temp, 1, -1 do
			local id = tonumber(temp[i])
			if hadPlayer[id] then
				table.remove(temp, i)
			else
				temp[i] = id
				hadPlayer[id] = 1
			end
		end
	end
	-- 增加第几个
	local function getPlayer(idx)
		for i = idx, 3 do
			if redirect[i] ~= -1 then
				local curR = result[redirect[i]] or {}
				if next(curR) then
					local curIdx = math.randomInt(1, #curR)
					local objId = curR[curIdx]
					table.remove(curR, curIdx)
					return objId
				end
			end
		end
	end

	local tempMatch = {}
	local curCount = 0
	for i = 1, 3 do
		local objId = getPlayer(i)
		if objId then
			tempMatch[i] = {t = 1, id = objId}
			curCount = curCount + 1
		end
	end
	
	-- 正常的玩家不够了 低一档继续
	if curCount < 3 then
		local level = nil
		if globalCsv.pvp_division[findIdx - 2] then
			level =  {globalCsv.pvp_division[findIdx - 2], globalCsv.pvp_division[findIdx - 1] - 1}
		end
		if level then
			local result = getPlayers({level})[1] or {}
			for i = #result, 1, -1 do
				local id = tonumber(result[i])
				if hadPlayer[id] then
					table.remove(result, i)
				else
					result[i] = id
					hadPlayer[id] = 1
				end
			end

			if next(result) then
				for i = curCount + 1, 3 do
					local curIdx = math.randomInt(1, #result)
					local objId = result[curIdx]
					table.remove(result, curIdx)
					tempMatch[i] = {t = 1, id = objId}
					curCount = curCount + 1
					if not next(result) then
						break
					end
				end
			end
		end
	end

	-- 增加机器人
	if curCount < 3 then
		for i = curCount + 1, 3 do
			while true do
				local id = math.randomInt(1, #csvdb[robotCsv])
				if not hadRobot[id] then
					hadRobot[id] = 1
					tempMatch[i] = {t = 2, id = id}
					break
				end
			end
		end
	end
	self:setProperty(mField, tempMatch)
end

function Role:refreshPvpMatchC(score)
	self:refreshPvpMatch(score, RANK_PVP_COMMON)
end

function Role:refreshPvpMatchH(score)
	self:refreshPvpMatch(score, RANK_PVP_HIGHT)
end

function Role:getPvpDBKey(ptype)
	local dbList
	local round
	if ptype == RANK_PVP_COMMON then
		dbList = RANK_PVP_COMMON_KEY
		round = self:getTimeResetRound(TimeReset.PvpRank)
	elseif ptype == RANK_PVP_HIGHT then
		dbList = RANK_PVP_HIGHT_KEY
		round = self:getTimeResetRound(TimeReset.PvpHight)
	end
	local idx = 1
	if round % 2 == 1 then
		idx = 2 
	end
	return dbList[idx]
end




end


return RolePvp