第十四章:L2JMobius学习 – 玩家奖励

在上一章节中,我们介绍了玩家攻击怪物的大概代码逻辑,其中涉及到怪物死亡的代码是:

// 减少怪物目标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 _categories集合。这个集合对象里面的数据哪里来的呢?实际在,在服务器端载入Npc的时候,也就是NpcTable实例化的时候,当时,我们只简单介绍了读取npc数据表,其实还会自动读取“droplist”数据表,这个就是掉落物品的数据表。在这个数据表中有一个itemId字段,它就代表了物品的ID,而mobId字段则是npc的ID,另外还有一个chance的整型字段,它将参与掉落概率的计算。上面的_categories集合不会直接缓存物品对象,而是根据分类缓存物品对象。也就是说,_categories集合中首先是物品分类,然后再是物品对象。介绍完掉落物品的数据来源之后,我们就开始计算物品掉落概率。也就是上面的calculateCategorizedRewardItem 方法的内容,参数为玩家,物品分类和等级差距。

// 掉落概率计算值,来源于 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(裙文件里面也可以下载所有内容)。

你可能感兴趣的:(L2JMobius,L2JMobius,天堂2服务器端源码)