在上一章节中,我们介绍了玩家攻击怪物的大概代码逻辑,其中涉及到怪物死亡的代码是:
// 减少怪物目标HP(怪物死亡后掉落物品最为奖励)
target.reduceCurrentHp(damage, this);
这个target自然就是Monster类了,该方法在其父类Attackable中。
接下来,我们就来看看这个reduceCurrentHp方法,参数为伤害值和攻击玩家。
// 根据伤害计算仇恨值
if (attacker != null)
{
addDamage(attacker, (int) damage);
}
// 调用父类 Creature 的reduceCurrentHp方法
super.reduceCurrentHp(damage, attacker, awake);
接下来,我们去父类Creature 的reduceCurrentHp方法查看
// 减少HP血量
getStatus().reduceHp(amount, attacker, awake);
请注意,这里的getStatus()方法返回的是NpcStatus类,因为当前对象的实例为Monster。我们去NpcStatus类查看reduceHp方法。
// 将攻击者(玩家)放入到自己的列表中
getActiveChar().addAttackerToAttackByList(attacker);
// 调用父类 CreatureStatus 的reduceHp方法
super.reduceHp(value, attacker, awake);
接下来,我们去CreatureStatus 的reduceHp方法中查看
// 计算减少HP数值
value = _currentHp - value;
// 设置HP值
setCurrentHp(value);
// 如果死亡(HP值小于0.5)
if (_creature.isDead())
{
// 停止攻击
_creature.abortAttack();
_creature.abortCast();
// 死亡
_creature.doDie(attacker);
// HP值设置为零
setCurrentHp(0);
}
接下来,我们就来重点查看怪物Monster类的doDie方法。这个方法调用比较多,我们主要查看父类Creature中的doDie方法,参数就是攻击者(玩家)。
// 计算主要伤害者,然后给予奖励
final Creature mainDamageDealer = isMonster() ? ((Monster) this).getMainDamageDealer() : null;
calculateRewards(mainDamageDealer != null ? mainDamageDealer : killer);
// 广播 StatusUpdate 数据包
broadcastStatusUpdate();
// 执行死亡状态(死亡动画)
getAI().notifyEvent(CtrlEvent.EVT_DEAD, null);
这里,我们重点查看奖励的方法calculateRewards,该方法位于Attackable类中,也就是Monster的父类中。奖励分为两种,一种是经验值,另一种是掉落物品。两种奖励的获取方式是不一样的。这里需要说明一下_aggroList 这里列表,它保存了当前怪物的所有攻击者(玩家群殴怪物的情况)。对于经验值来讲,_aggroList里面的玩家基本上都会获取到。但是物品奖励的所有者,就需要计算一下。计算物品奖励所有者的时候,涉及到伤害值的问题,谁给怪物造成的伤害最多,物品奖励应该属于谁。也就说,当前攻击者不一定是物品奖励所有者。当然,组队的情况的话,物品奖励就可以归队伍成员所有。我们还是查看代码吧。
// 奖励玩家列表
final Map rewards = new ConcurrentHashMap<>();
// 从 _aggroList 中寻找伤害值最高的攻击者(玩家)
Player maxDealer = null;
// 循环所有攻击者,放入到奖励玩家列表中
for (AggroInfo info : _aggroList.values())
{
RewardInfo reward = new RewardInfo(ddealer, damage);
rewards.put(ddealer, reward);
// 寻找伤害着最高的玩家
maxDealer = ((Playable) ddealer).getActingPlayer();
maxDamage = reward._dmg;
}
// 掉落奖励物品
doItemDrop((maxDealer != null) && maxDealer.isOnline() ? maxDealer : lastAttacker);
// 循环奖励玩家列表,奖励经验值
for (RewardInfo reward : rewards.values())
{
// 没有组队的情况
if (attackerParty == null)
{
// 计算所得经验值
levelDiff = attacker.getLevel() - getLevel();
tmp = calculateExpAndSp(levelDiff, damage);
exp = tmp[0];
exp *= 1 - penalty;
sp = tmp[1];
// 增加攻击者的经验值
attacker.addExpAndSp(Math.round(attacker.calcStat(Stat.EXPSP_RATE, exp, null, null)), (int) attacker.calcStat(Stat.EXPSP_RATE, sp, null, null));
}
// 有组队的情况
else
{
// 计算组队下的经验值
tmp = calculateExpAndSp(levelDiff, partyDmg);
exp = tmp[0];
sp = tmp[1];
// 组队成员分配经验值
attackerParty.distributeXpAndSp(exp, sp, rewardedMembers, partyLvl);
}
}
上面的代码逻辑非常清楚了,首先是确定物品奖励的所有者(伤害最高的玩家),然后是计算所有人的经验值。经验值的计算分为两种情况,一种是无组队情况,一种是有组队情况。我们首先看一下无组队的情况,也就是calculateExpAndSp(levelDiff, damage) 参数为玩家和怪物的等级差距和玩家给怪物造成的伤害值。具体代码如下:
// 计算经验值,按照伤害值和怪物的总血量
xp = (getExpReward() * damage) / getMaxHp();
// 计算技能点,也是按照伤害值来的
sp = (getSpReward() * damage) / getMaxHp();
// 返回经验值和技能点
return new int[]{(int) xp,(int) sp};
接下来,我们继续查看getExpReward 方法,
final double rateXp = getStat().calcStat(Stat.MAX_HP, 1, this, null);
return (int) (getTemplate().getRewardExp() * rateXp * Config.RATE_XP);
首先是getTemplate().getRewardExp()方法获取的是怪物npc对应的经验值。getTemplate方法不用说了,就是NpcTemplate了,它对应的就是数据表npc,之前实例化Npc的时候,我们就讲解过。而getRewardExp 方法返回的就是rewardExp 字段值。也就是说,每一个怪物Npc的数据库表中存储了它的奖励经验值(rewardExp字段)和技能点值(rewardSp字段)。接下来是两个比例值,rateXp是一个计算公式(与玩家的Dex敏捷值和等级值相关),接下来是Config.RATE_XP配置值。也就是说,我们可以通过修改Config.RATE_XP数值来增加玩家获取的经验值。这个配置值在“config\Rates.ini”文件中,如下所示
# 经验XP倍率
RateXp = 1.00
# 技能SP点数倍率
RateSp = 1.00
# 组队经验XP倍率
RatePartyXp = 1.00
# 组队技能SP点数倍率
RatePartySp = 1.00
# 金钱掉落倍率
RateDropAdena = 1.00
# 技能消耗物品的倍率
RateConsumableCost = 1.00
# 物品道具掉落倍率
RateDropItems = 1.00
默认情况下,他们的数值都是1,也就是不增加玩家的经验值奖励。我们可以修改它,来提升玩家的体验。经验值计算完毕之后,就可以给玩家增加了。这里涉及到一个升级的问题。当玩家经验值满足升级的条件的时候,就会升级。这些代码逻辑都在attacker.addExpAndSp方法中,这里就不再详细介绍了。
接下来,我们重点介绍 奖励物品掉落的方法doItemDrop,代码如下
// 循环可能掉落的物品
for (DropCategory cat : npcTemplate.getDropData())
{
// 计算物品掉落(概率问题)
item = calculateCategorizedRewardItem(player, cat, levelModifier);
if (item != null)
{
// 掉落物品到地面
dropItem(player, item); // drop the item on the ground
}
}
首先,掉落物品仍然来源于NpcTemplate对象,它的getDropData方法返回的是NpcTemplate对象中的List
// 掉落概率计算值,来源于 droplist 数据表中的 chance 的整型字段
int categoryDropChance = basecategoryDropChance;
// 修改掉落的概率值(配置文件中)
categoryDropChance *= Config.RATE_DROP_ITEMS;
// 随机一百万内的一个数字与上面的categoryDropChance对比
if (Rnd.get(DropData.MAX_CHANCE) < categoryDropChance)
{
// 掉落物品信息
final DropData drop = categoryDrops.dropOne(isRaid());
// 掉落物品数量的最大值和最小值
final int min = drop.getMinDrop();
final int max = drop.getMaxDrop();
// 掉落物品的数量
int itemCount = Rnd.get(min * multiplier, max * multiplier);
// 返回掉落物品
if (itemCount > 0)
{
return new ItemHolder(drop.getItemId(), itemCount);
}
}
我们简单介绍一下物品掉落的大概逻辑。其实就是随机一个数字,看看这个数字是不是小于物品的chance值,如果小于的话,就说明此物品会掉落,然后在随机物品的数量。这些数值都是在“droplist”数据表中存储的。
本章节涉及的内容均已上传百度网盘:
https://pan.baidu.com/s/1XdlcCFPvXnzfwFoVK7Sn7Q?pwd=avd4
欢迎加企鹅交流裙:874700842(裙文件里面也可以下载所有内容)。