注:本文只是记录个人开发过程,可能有部分操作很奇怪或者不对,请见谅!
这不是教程!这不是教程!这不是教程!
前言
写完第一个功能后,有想到了之前玩JE的mod端时,可以右键收割作物。于是继续开始了寻找之旅。
一、开发思路
既然是右键收割,就需要用到LL的PlayerUseItemOnEvent事件了。这功能思路挺简单,玩家右键->判断是否成熟->生成对应作物掉落物->作物变成初始状态。
其实重点是获取作物的掉落物,打开IDA开始寻找相关内容,搜索"cropblock",如下图

CropBlock::spawnResources
(生成资源)一眼看中,F5查看伪代码,可以发现生成作物掉落物分了两部分,其实很好理解,一半是种子,一半是作物。查询虚表后补齐对应函数名后,如下

主要看36,50,55行,分别获取了作物成熟等级,对应作物,掉落数量。(种子同理)
了解BDS对于作物收割的相关流程后,再根据BDS处理方块统一使用setBlock,所以恢复初始成长状态也基本能搞定,接下来开始实践。
二、实现过程
首先利用LiteLoader提供的PlayerUseItemOnEvent事件,获取到对应Block判断是否是作物方块(CropBlock),并获取它的成熟度,查阅wiki可知作物成熟度就是他的特殊值,那么利用LL提供的API直接获取。
bool LoadBetterHarvestingCrop(BlockInstance blockin) {
auto bl = blockin.getBlock();
if (bl->isCropBlock()) {
auto growthlevel = bl->getTileData();
}
}
随后判断是否已经成熟,也就是特殊值为7
bool LoadBetterHarvestingCrop(BlockInstance blockin, Player* sp) {
auto bl = blockin.getBlock();
if (bl->isCropBlock()) {
auto growthlevel = bl->getTileData();
if (growthlevel == 7) {
}
}
}
获取农作物对应的作物,这里我们直接利用LL导出的MCAPI,并判断获得的item不是错误的。随后获取掉落农作物的数量,并且调用BlockLegacy::popResource来生成掉落物,代码如下:
bool LoadBetterHarvestingCrop(BlockInstance blockin, Player* sp) {
auto bl = blockin.getBlock();
if (bl->isCropBlock()) {
auto growthlevel = bl->getTileData();
if (growthlevel == 7) {
auto& block = (CropBlock&)bl->getLegacyBlock();
auto bs = blockin.getBlockSource();
auto pos = blockin.getPosition();
auto rand = Randomize(Random::getThreadLocal());
auto Crop = block.getBaseCrop();
if (!Crop.isNull())
{
auto CropNum = block.getCropNum(rand, growthlevel, 0);
if (CropNum)
block.popResource(*bs, pos, Crop);
}
}
}
return true;
}
有好奇的人就要问了,为啥cropNum用的是if,而不是for或者while?
在IDA看CropBlock::getCropNum后就知道答案了

这坨也就是说,只要a3(成熟值)大于等于7就一定返回true,也就是1个作物。所以在游戏里破坏完全成熟的作物,即使运气再背,也会掉落一个对应的作物。(萝卜马铃薯如果只掉一个,属实是种子那块随机到了0)
接下来到了种子环节,其实与上面差不多,只是if部分改为了while或者for和添加一个获取手上物品的时运等级(CropBlock::getSeedNum中用到),直接上代码:
bool LoadBetterHarvestingCrop(BlockInstance blockin, Player* sp) {
auto bl = blockin.getBlock();
if (bl->isCropBlock()) {
auto growthlevel = bl->getTileData();
if (growthlevel == 7) {
auto& block = (CropBlock&)bl->getLegacyBlock();
auto bs = blockin.getBlockSource();
auto pos = blockin.getPosition();
auto rand = Randomize(Random::getThreadLocal());
auto Crop = block.getBaseCrop();
if (!Crop.isNull())
{
auto CropNum = block.getCropNum(rand, growthlevel, 0);
if (CropNum)
block.popResource(*bs, pos, Crop);
}
auto Seed = block.getBaseSeed();
if (!Seed.isNull())
{
auto level = EnchantUtils::getEnchantLevel(Enchant::Type::fortune, *sp->getHandSlot());
auto seedNum = block.getSeedNum(rand, growthlevel, level);
--seedNum;
if (seedNum > 0)
{
do {
block.popResource(*bs, pos, Seed);
--seedNum;
} while (seedNum);
}
}
}
}
return true;
}
好奇的人又要问了,中间为啥要--seedNum?右键直接收割种上了,减去一个种子不过分吧~
最后加上一个 Level::setBlock()来还原作物的初始生成状态就行了。
完整代码如下:
bool LoadBetterHarvestingCrop(BlockInstance blockin, Player* sp) {
auto bl = blockin.getBlock();
if (bl->isCropBlock()) {
auto growthlevel = bl->getTileData();
if (growthlevel == 7) {
auto& block = (CropBlock&)bl->getLegacyBlock();
auto bs = blockin.getBlockSource();
auto pos = blockin.getPosition();
auto rand = Randomize(Random::getThreadLocal());
auto Crop = block.getBaseCrop();
if (!Crop.isNull())
{
auto CropNum = block.getCropNum(rand, growthlevel, 0);
if (CropNum)
block.popResource(*bs, pos, Crop);
}
auto Seed = block.getBaseSeed();
if (!Seed.isNull())
{
auto level = EnchantUtils::getEnchantLevel(Enchant::Type::fortune, *sp->getHandSlot());
auto seedNum = block.getSeedNum(rand, growthlevel, level);
--seedNum;
if (seedNum > 0)
{
do {
block.popResource(*bs, pos, Seed);
--seedNum;
} while (seedNum);
}
}
Level::setBlock(blockin.getPosition(), blockin.getDimensionId(), bl->getTypeName(), NULL);
}
}
return true;
}
三、总结
通过实现这个功能,又可以知道BDS是如果处理作物的了,我的理解大概如下
- 农作物掉落这块大概是:只要作物成熟了(特殊值达到7)必掉一个。
- 种子这块大概是:如果成长值小于7,必掉落一个种子,否则就通过随机来获取数量,具体概率看wiki。有趣的是,如果时运等级小于等于-3,则不掉落作物。
- 另外毒马铃薯是另外处理的~

文章评论