从自走棋代码分析游戏机制--棋池、回蓝、目标判断、掉落概率与新英雄

前几天接触了一下自走棋,发现这游戏有毒= =。 虽然作为新手菜鸡被虐的不要不要的,但是依然乐此不疲....

可惜的是虽然大概的机制玩过Dota 2的同学都懂,但是某些游戏机制是巨鸟多多独创的,而且并未说明过,除了知道有8位玩家共享棋池,攻击/被攻击都能回蓝,每轮会抽牌,每5轮野怪,大家觉得这似乎是一个纯随机游戏,技能释放完全看脸,回蓝也完全看脸.....

作为非酋的我实在不能忍.....这真的是一个概率游戏?正好逛github的时候发现有人上传了代码,于是去瞅了瞅。

自走棋部分代码(请自己注意日期)

虽然我Lua语法不太熟,但是作者写的注释很全而且意外的好懂.......以至于我这种弱鸡也能从代码发现一点东西,拿来和大家分享一下~

棋池大小与棋子总数

首先,需要明确的是自走棋游戏规则中,每轮抽牌是先根据当前人口的概率分布,先随机选择棋子的稀有度,再从这个稀有度的所有棋子中随机抽出一个棋子。

举个例子,比如在一轮抽牌中我抽到了1个2块钱的月骑,那么实际的过程是:

  1. 我抽到了一个2块钱的棋子
  2. 我从所有2块钱棋子组成的棋池里抽到了月骑


彩蛋环节

第一步中决定概率的是你的人口(科技等级),不过在这之前,有两个彩蛋,作者称之以为SSR卡........就是ssr_ck和ssr_nec,似乎是特殊版的混沌骑士和特殊版的死灵法师。

function RandomDrawChessNew(team_id)
	local h = TeamId2Hero(team_id)
	local this_chess = nil
	local ran = RandomInt(1,100)
	local chess_level = 1
	local curr_per = 0
	local hero_level = h:GetLevel()

	local table_11chess = {}
	for _,chess in pairs(GameRules:GetGameModeEntity().mychess[team_id]) do
		if string.find(chess.chess,'11') then
			table.insert(table_11chess,string.sub(chess.chess,1,-3))
		end
	end
	local ran1 = RandomInt(1,10000) %随机一个1~10000的数1
	local ran2 = RandomInt(1,10000) %随机一个1~100000的数2
	if h:GetLevel() >= 7 and ran1 <= 1 and ran2 <= 1 then %如果人口在7以上,且两个随机数都是1,随机抽SSR卡池中的卡
		this_chess = GameRules:GetGameModeEntity().chess_list_ssr[RandomInt(1,table.maxn(GameRules:GetGameModeEntity().chess_list_ssr))]
	else
		if GameRules:GetGameModeEntity().chess_gailv[hero_level] ~= nil then
			for per,lv in pairs(GameRules:GetGameModeEntity().chess_gailv[hero_level]) do
				if ran>per and curr_per<=per then
					curr_per = per
					chess_level = lv
				end
			end
		end
		-- this_chess = GameRules:GetGameModeEntity().chess_list_by_mana[chess_level][RandomInt(1,table.maxn(GameRules:GetGameModeEntity().chess_list_by_mana[chess_level]))]

		this_chess = DrawAChessFromChessPool(chess_level, table_11chess)
	end
	return this_chess
end

我在上述代码中加粗并加了注释来说明这部分,在这里解释一下。

所有的抽卡开始之前,有两个判断逻辑:如果你的人口大于7,且两个1~10000的随机数都是1,那么从SSR棋池中抽卡,这里抽出来的卡目前只有两种,就是上述说的ssr_ck和ssr_nec,两个随机数都是1的概率大概是 \frac{1}{10000} \times \frac{1}{10000} = 10^{-8}

当前的SSR棋池是这个:

GameRules:GetGameModeEntity().chess_list_ssr = {'chess_nec_ssr','chess_ck_ssr'}

所以这个概率下能抽出来SSR卡的是真·欧洲人,所以想验证自己的rp的各位,请7人口以后疯狂抽卡,8人口提升不了SSR的概率。

PS:这一步会在每轮抽卡的时候进行5次,所以....概率还是有的。


第一步判断棋子等级

在作为RP过滤器的彩蛋环节后,非洲人们就进入了正常抽卡环节。如果我没有理解错的话,这个根据当前人口判断抽卡等级的概率来自这里:

for per,lv in pairs(GameRules:GetGameModeEntity().chess_gailv[hero_level]) do
				if ran>per and curr_per<=per then
					curr_per = per
					chess_level = lv
				end
			end

per,lv两个变量储存在下边这个map里(我也不知道lua里这个数据类型应该叫啥),且随着每个等级不同。

GameRules:GetGameModeEntity().chess_gailv = {
	[1] = { [101] = 2 },
	[2] = { [70] = 2 },
	[3] = { [60] = 2, [95] = 3 },
	[4] = { [50] = 2, [85] = 3 },
	[5] = { [40] = 2, [75] = 3, [98] = 4 },
	[6] = { [33] = 2, [63] = 3, [93] = 4 },
	[7] = { [30] = 2, [60] = 3, [90] = 4 },
	[8] = { [24] = 2, [54] = 3, [84] = 4, [99] = 5 },
	[9] = { [22] = 2, [52] = 3, [77] = 4, [97] = 5 },
	[10] = { [19] = 2, [44] = 3, [69] = 4, [94] = 5 },
	}

具体的判断逻辑是这样的:先随机出一个1~100的数,然后和上述map中的数字比大小,比如1级的时候,随机数是无论如何不会大于101的,所以一定是一个1费棋子;2级的时候,有30%可能大于70,获得2费棋子;3级的时候,有40%可能大于60,获得2费棋子,然后重点来了,有5%的可能不仅大于60而且大于95,获得3费棋子,因此3级的概率是 5%的 3费棋子, 35%的2费棋子, 60%的1费棋子。

以此类推得到下图。

db004c44a3c3bd01ce4bdeaca0ec5b32.jpeg


棋池有多大

具体抽卡和放回的过程我就不详细描述了,不然太长了。只谈谈大家关心的部分,总体棋池有多大。


文件里负责棋池大小的代码有三部分。

默认棋池参数:

--默认卡池参数
	GameRules:GetGameModeEntity().CHESS_POOL_SIZE = 5
	GameRules:GetGameModeEntity().CHESS_INIT_COUNT = {
		[1] = 9,
		[2] = 6,
		[3] = 5,
		[4] = 3,
		[5] = 2,
	}

从服务器端获取的棋池参数:

if t.chess_pool ~= nil  then
	if t.chess_pool.pool_size ~= nil then
		GameRules:GetGameModeEntity().CHESS_POOL_SIZE = t.chess_pool.pool_size
	end
	if t.chess_pool.chess_init_1 ~= nil then
		GameRules:GetGameModeEntity().CHESS_INIT_COUNT[1] = t.chess_pool.chess_init_1
	end
	if t.chess_pool.chess_init_2 ~= nil then
		GameRules:GetGameModeEntity().CHESS_INIT_COUNT[2] = t.chess_pool.chess_init_2
	end
	if t.chess_pool.chess_init_3 ~= nil then
		GameRules:GetGameModeEntity().CHESS_INIT_COUNT[3] = t.chess_pool.chess_init_3
	end
	if t.chess_pool.chess_init_4 ~= nil then
		GameRules:GetGameModeEntity().CHESS_INIT_COUNT[4] = t.chess_pool.chess_init_4
	end
	if t.chess_pool.chess_init_5 ~= nil then
		GameRules:GetGameModeEntity().CHESS_INIT_COUNT[5] = t.chess_pool.chess_init_5
	end
end

初始化棋池部分:

function InitChessPool()
	local chess_pool_times = GameRules:GetGameModeEntity().CHESS_POOL_SIZE or 6
	for cost,v in pairs(GameRules:GetGameModeEntity().chess_list_by_mana) do
		for _,chess in pairs(v) do
			local chess_count = GameRules:GetGameModeEntity().CHESS_INIT_COUNT[cost]*chess_pool_times
			-- if chess == 'chess_eh' or chess == 'chess_fur' or chess == 'chess_tp' or chess == 'chess_ld' then
			-- 	chess_count = math.floor(chess_count*GameRules:GetGameModeEntity().CHESS_INIT_DRUID_PER)
			-- end
			for i=1,chess_count do
				AddAChessToChessPool(chess)
			end
		end
	end
	prt('INIT CHESS POOL OK!')
end

请大家注意 CHESS_POOL_SIZE 这个字段 和 CHESS_INIT_COUNT 这个字段,这两个字段分别对应棋池大小倍数棋池大小基数

如果采用的是默认棋池的话,根据CHESS_INIT_COUNT,从一费到五费,每局游戏中每种棋子的基数分别为9,6,5,3,2;默认的棋池大小倍数是5,也就是说,如果采用默认棋池,每局能获得的每种一费到五费棋子分别是:45,30,25,15,10。

但是在初始化棋池的时候,可能采取服务器参数,这时候棋池大小的倍数就不定了,

local chess_pool_times = GameRules:GetGameModeEntity().CHESS_POOL_SIZE or 6

这句话的逻辑判断是当CHESS_POOL_SIZE 这个字段 为空时,也就是说,当服务器不传参数时,棋池大小倍数是6。


整理一下,我们从这里得到几个结论:

如果服务器发送棋池参数,那么棋池大小倍数棋池大小基数都不确定。

如果服务器不发送棋池参数,或者发送参数为空,那么:

  1. 棋池大小基数是确定的,从一费到五费,每局游戏中每种棋子的基数分别为9,6,5,3,2。
  2. 棋池大小倍数是5(不发送),或6(发送参数为空)。

从上述结论我们可以得到一些推导:

  1. 各个职业的棋子数是均衡的,不存在某一盘中单个职业过多,其它职业过少的现象,因为这里边似乎没有涉及职业的参数,玩别人没玩的职业从概率上更容易抽到牌。
  2. 卡人关键牌是非常非常有效的做法,卡人一时爽,一直卡一直爽,因为单个棋子总数固定,但是不要卡同费用的其它棋子,这样是在帮助别人缩小棋池。
  3. 如果想抽某一张关键牌,那么可以先拿一些同费用的牌来压缩棋池,这种情况在橙色棋子部分特别有效,这是因为抽棋子是先抽费用,再抽某个种类的棋子。费用的概率始终是不变的,而这个费用的棋池会因为场上存在的其它棋子而缩小。

这部分我可能写的比较绕,来解释一下:当我想抽到一张关键棋子潮汐,那么如果经济允许的情况下,飞机啊lich啊什么的都应该拿在手里,这样棋池中的潮汐概率就会因为棋池的缩小而提升。

(:当然其他人抽到潮汐的概率也会同样增大。

回蓝

游戏中的棋子回蓝分两种,受到伤害回蓝和造成伤害回蓝。

受到伤害回蓝:

--受到伤害回蓝
	local mana_get = damage/5
	if mana_get > 50 then
		mana_get = 50
	end
	mana_get = RandomInt(mana_get/2,mana_get)
	
	if caster:FindModifierByName("modifier_item_jixianfaqiu") ~= nil  then
		mana_get = math.floor(mana_get * 1.25)
	end
	if caster:FindModifierByName("modifier_item_yangdao") ~= nil then
		mana_get = math.floor(mana_get * 1.5)
	end
	caster:SetMana(caster:GetMana()+mana_get)

上述逻辑是,如果棋子在没有极限法球和羊刀的情况下,回蓝可能有两种:

受到伤害数目/5 和 受到伤害数目/10,两者概率相同。

且单次受到回蓝上限是50。

所以有的时候能看到两个一毛一样的棋子对A,别人的棋子就是比自己的快放出技能,没别的---脸黑而已。

从以上逻辑可以得到一些推论:

  1. 单次受到伤害250是个门槛,高于250的伤害不会充能
  2. 如果对方单次伤害没有超过250,不考虑抬手的情况下,纯受伤回蓝,1000血以上的棋子一定能放出来技能,我记得1星潮汐的血量是950,很微妙...一星萨尔是一定需要四兽人BUFF才能稳定抬手放技能的。
  3. 亡灵猎有四亡灵减甲多出来的20%多额外伤害,对于二星以上火枪,小黑是不回蓝的伤害,这是亡灵猎秒控制类前排的资本,所以各位看到4亡灵,请把潮汐放中后排


造成伤害回蓝:

--造成伤害回蓝
	if attacker ~= nil then
		if attacker:FindAbilityByName('is_mage') or attacker:FindAbilityByName('is_warlock') or attacker:FindAbilityByName('is_shaman') then
			mana_get = damage/2.5
			if mana_get > 20 then
				mana_get = 20
			end
		else
			if mana_get > 10 then
				mana_get = 10
			end
		end 
		
		if attacker:FindModifierByName("modifier_item_wangguan") ~= nil or attacker:FindModifierByName("item_hongzhang_1") ~= nil or attacker:FindModifierByName("item_hongzhang_2") ~= nil or attacker:FindModifierByName("item_hongzhang_3") ~= nil or attacker:FindModifierByName("item_hongzhang_4") ~= nil or attacker:FindModifierByName("item_hongzhang_5") ~= nil then
			mana_get = math.floor(mana_get * 1.5)
		end
		if attacker:FindModifierByName("modifier_item_xuwubaoshi") ~= nil or attacker:FindModifierByName("modifier_item_yangdao") ~= nil or caster:FindModifierByName("modifier_item_shenmifazhang") ~= nil then
			mana_get = math.floor(mana_get * 2)
		end
		if attacker:FindModifierByName("modifier_item_jianrenqiu") ~= nil then
			mana_get = math.floor(mana_get * 2)
		end
		if attacker:FindModifierByName("modifier_item_shuaxinqiu") ~= nil then
			mana_get = math.floor(mana_get * 3)
		end
		
		attacker:SetMana(attacker:GetMana()+mana_get)
	end

	if GameRules:GetGameModeEntity().show_damage == true then
		if attacker:GetTeam() == 4 then
			AMHC:CreateNumberEffect(caster,damage,2,AMHC.MSG_DAMAGE,"red",9)
		else
			AMHC:CreateNumberEffect(caster,damage,2,AMHC.MSG_DAMAGE,"green",9)
		end
	end

对于法师,萨满,术士等单个目标的单次攻击回蓝上限是20,其它职业是10,在没有装备情况下,单次攻击单个目标回蓝数目是造成伤害数值/2.5...这个数字和上限是受到装备修正的,而且修正系数是乘积叠加,比如有1个虚无宝石1个王冠,那么上限就是20*2*1.5。

法术类职业回蓝是厉害的。

技能目标选择

技能目标不是随缘的,有几种类型:

--释放技能:11=新沙王,0=被动技能,1=单位目标,2=无目标,3=点目标,4=自己目标,5=近身单位目标,6=先知在地图边缘招树人,7=随机友军目标(嗜血术),8=随机周围空地目标(炸弹人),9=血量百分比最低的队友,10=等级最高的敌人(末日),11=沙王戳最远的能打到敌人的格子,12=小小投掷身边的敌人到最远的格子

值得谈谈的是末日/lina的大招(还有一个叫bump的技能是啥,据说最新代码里lina的 AI是1,随机单位目标了),选择最高等级目标,这个函数名也挺有意思:

function FindHighLevelUnluckyDog(u)
	local unluckydog = nil
	local max_level = 0
	local team = u.at_team_id or u.team_id
	local my_pos = XY2Vector(u.x,u.y,team)

	if RandomInt(1,100)<30 then
		--30%概率随机找敌人
		return FindUnluckyDogRandom(u)
	end

	for _,unit in pairs (GameRules:GetGameModeEntity().to_be_destory_list[u.at_team_id or u.team_id]) do
		local lv = unit:GetLevel()
		if unit:GetMaxMana() <= 0 then
			lv = 1
		end
		local a = GameRules:GetGameModeEntity().chess_ability_list[unit:GetUnitName()]
		local beh = GameRules:GetGameModeEntity().ability_behavior_list[a]

		if lv > max_level and unit.team_id ~= u.team_id and unit:FindModifierByName("modifier_doom_bringer_doom") == nil and beh ~= 0 then
			unluckydog = unit
			max_level = lv
		end
	end
	return unluckydog
end

逻辑是先生成1-100的随机数,如果小于30就随机选择一个目标(狗),如果是这个随机数大于等于30,就选择等级最高的敌人释放。

翻译一下就是30%概率随机大,70%概率大最高等级。

然后是沙王,水人:

function FindUnluckyDogFarthest(u)
	local unluckydog = nil
	local length2d = 0
	for _,unit in pairs (GameRules:GetGameModeEntity().to_be_destory_list[u.at_team_id or u.team_id]) do
		if (u:GetAbsOrigin() - unit:GetAbsOrigin()):Length2D() > length2d and unit.team_id ~= u.team_id then
			unluckydog = unit
			length2d = (u:GetAbsOrigin() - unit:GetAbsOrigin()):Length2D() 
		end
	end
	return unluckydog
end

这个很简单.........选择离沙王/水人最远的敌人穿过去....

掉落

曾经PVP也是能掉落的,后来不知道为啥删掉了...看这里:

if string.find(u:GetUnitName(),'pve') ~= nil then  --pve敌人掉宝
			DropItem(u)
		-- else
		-- 	if u:GetTeam() == 4 and RandomInt(1,100) < 10 then  --pvp的敌人也有概率掉宝
		-- 		DropItem(u)
		-- 	end
		end

掉落是跟敌人等级有关的,掉落的参数如下:

GameRules:GetGameModeEntity().drop_item_gailv = {
		[1] = { [80] = 1},
		[2] = { [60] = 1},
		[3] = { [50] = 1},
		[4] = { [40] = 1, [80] = 2},
		[5] = { [40] = 1, [60] = 2},
		[6] = { [30] = 1, [60] = 2, [90] = 3},
		[7] = { [20] = 1, [50] = 2, [80] = 3},
		[8] = { [0] = 1, [20] = 2, [60] = 3, [90] = 4},
		[9] = { [0] = 1, [10] = 2, [50] = 3, [80] = 4},
	}

物品等级是这样的:

[1] = {
			[1] = 'item_suozijia',
			[2] = 'item_yuandun',
			[3] = 'item_zhiliaozhihuan',
			[4] = 'item_gongjizhizhua',
			[5] = 'item_kuweishi',
			[6] = 'item_duangun',
			[7] = 'item_xixuemianju',
			[8] = 'item_huifuzhihuan',
			[9] = 'item_kangmodoupeng',
			[10] = 'item_xuwubaoshi',
			[11] = 'item_fashichangpao',
			[12] = 'item_wangguan',
		},
		[2] = {
			[1] = 'item_banjia', 	
			[2] = 'item_huoliqiu',
			[3] = 'item_kuojian',
			[4] = 'item_miyinchui',
			[5] = 'item_biaoqiang',
			[6] = 'item_molifazhang',
			[7] = 'item_tiaodao',
		},
		[3] = {
			[1] = 'item_emodaofeng',
			[2] = 'item_zhenfenbaoshi',
			[3] = 'item_jixianfaqiu',
		},
		[4] = {
			[1] = 'item_shengzheyiwu',
			[2] = 'item_dafu',
			[3] = 'item_shenmifazhang',
		},

也就是说,一级物品有:

锁子甲、圆盾、治疗指环、攻击之爪、枯萎之石、短棍、吸血面具、回复指环、抗魔头巾、虚无宝石、法师长袍、王冠。

二级物品有:

板甲、活力球、阔剑、秘银锤、标枪、魔力法杖、跳刀

三级物品有:

恶魔刀锋、振奋宝石、极限法球

四级物品有:

圣者遗物、大斧头、神秘法杖


掉落概率根据怪物等级分别是:

1级怪:80%什么都不掉,20%1级物品

2级怪:60%无,40%1级物品

3级怪:50%无,50%1级物品

4级怪:40%无,40%1级物品,20%2级物品

5级怪:40%无。20%1级物品,40%2级物品

6级怪:30%无,30%1级物品,30%2级物品。10%3级物品

7级怪:20%无,30%2级物品,30%2级物品,20%3级物品

8级怪:20%1级物品。40% 2级物品,30%3级物品,10%4级物品

9级怪:10%1级物品,40% 2级物品。30%3级物品,20%4级物品

这个掉落就很随缘了.....

至于野怪是几费的.....我没找到.....可能掉血能看出来?希望评论区有提示= =。


可能的新英雄:

在SSR后发现了三个有意思的英雄:

chess_tiny = 1,
chess_tb = 3,
chess_morph = 2,
chess_nec_ssr = 10,
chess_ck_ssr = 15,
chess_kael = 3,
chess_zeus = 5,
chess_sven = 5,

三费卡尔,5费宙斯和5费斯文。


大概这样了。

谢谢。

完。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值