找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 40|回复: 1

[教程] 解决宠吸过滤器不生效和无法拾取物品提示的思路

[复制链接]

1

主题

0

回帖

4

积分

新手上路

积分
4
发表于 3 天前 | 显示全部楼层 |阅读模式
本帖最后由 pkoukk 于 2026-1-8 11:40 编辑

自己重写的初衷,主要玩的整合包里的宠吸方案突出一个能用就行
右下角会有大量的“无法拾取”提示,宠物过滤器也不起作用,强迫症有点难受
OK,现在问题分为两个

第一个,宠物过滤不起作用
那个帖子要50币,家境贫寒买不起
但是根据现象,我猜测现有的应该是把LootCommand里的代码直接搬过来了,没有过宠物筛选,也就是
  1.         
  2. List<MapObject> items = c.getPlayer().getMap().getMapObjectsInRange(c.getPlayer().getPosition(), Double.POSITIVE_INFINITY, Arrays.asList(MapObjectType.ITEM));
  3.         for (MapObject item : items) {
  4.             MapItem mapItem = (MapItem) item;
  5.             if (mapItem.getOwnerId() == c.getPlayer().getId() || mapItem.getOwnerId() == c.getPlayer().getPartyId()) {
  6.                 c.getPlayer().pickupItem(mapItem);
  7.             }
  8.         }
复制代码

那么,我们只要把原本的宠物装备的筛选逻辑加上就可以了,为了后面使用方便,我们把原本的筛选逻辑重构成一个函数
  1.    
  2. private final boolean shouldPickupItem(Character chr, MapItem mapitem) {
  3.         if (mapitem == null || mapitem.isPickedUp()) {
  4.             return false;
  5.         }
  6.         if (mapitem.getMeso() > 0) {
  7.             if (!chr.isEquippedMesoMagnet()) {
  8.                 return false;
  9.             }

  10.             if (chr.isEquippedPetItemIgnore()) {
  11.                 final Set<Integer> petIgnore = chr.getExcludedItems();
  12.                 if (!petIgnore.isEmpty() && petIgnore.contains(Integer.MAX_VALUE)) {
  13.                     return false;
  14.                 }
  15.             }
  16.         } else {
  17.             if (!chr.isEquippedItemPouch()) {
  18.                 return false;
  19.             }

  20.             if (chr.isEquippedPetItemIgnore()) {
  21.                 final Set<Integer> petIgnore = chr.getExcludedItems();
  22.                 if (!petIgnore.isEmpty() && petIgnore.contains(mapitem.getItem().getItemId())) {
  23.                     return false;
  24.                 }
  25.             }
  26.         }
  27.         return true;
  28.     }
复制代码

那么这个时候,我们只要加上这条判断,宠物过滤也就重新起作用了
  1.         
  2. List<MapObject> items = chr.getMap().getMapObjectsInRange(pet.getPos(),
  3.                     Double.POSITIVE_INFINITY, Arrays.asList(MapObjectType.ITEM));
  4.             for (MapObject item : items) {
  5.                 MapItem mapItem = (MapItem) item;
  6.                 if (shouldPickupItem(chr, mapItem) && (mapItem.getOwnerId() == chr.getId()
  7.                         || mapItem.getOwnerId() == chr.getPartyId())) {
  8.                     chr.pickupItem(mapItem, petIndex);
  9.                 }
  10.             }
复制代码


第二个问题,烦人的无法拾取物品的提示
来看服务端源码,不难发现,提示来自于
sendPacket(PacketCreator.showItemUnavailable());

最简单的办法,注释掉它,客户端就不会收到提示了
但是我闲的蛋疼,看看根因是什么,能不能解决一下
那么看一眼代码,触发这种提示的条件有几种:
1. 物品已被拾取
2. 处在一些组队地图里,不能给队友扔东西
3. 没有接到任务,但尝试拾取任务掉落

1 可能的触发原因:没有并发控制,上一次全屏吸还没结束,下一次又开始了,第二次查询到的物品被第一次拾取,故而报错
2和3的原因一样:在客户端里,这些物品会被客户端逻辑隐藏,宠物看不见这些物品,不会去拾取它,但是当改用范围查询之后,就触发了这些本不该被拾取的物品的拾取,故而报错
简单分析下,
在单机或者少量亲友游玩的场景下,这些都不是问题,直接注释
sendPacket(PacketCreator.showItemUnavailable());
屏蔽提示就完事了

大部分人看到这里就可以了,下面真的没必要

当然,我闲的蛋疼,而且有代码洁癖,还是去解决一下吧。
首先是1
        加个并发控制很简单,我们在character 中新增一个属性
private AtomicBoolean petLootInProgress = new AtomicBoolean(false);

        然后我们不在PetLootHandler中直接调用 chr.pickupItem,而是新增一个函数
  1.    
  2. public final void pickupItems(List<MapObject> obs, int petIndex) {
  3.         // Use compareAndSet for atomic check-and-set (single flight pattern)
  4.         if (!petLootInProgress.compareAndSet(false, true)) {
  5.             enableActions();
  6.             return;
  7.         }
  8.         try {
  9.             for (MapObject ob : obs) {
  10.                 var item = (MapItem) ob;
  11.                 pickupItem(ob, petIndex);
  12.             }
  13.         } catch (Exception e) {
  14.             log.error(I18nUtil.getLogMessage("Character.pickupItems.error", getName()), e);
  15.         } finally {
  16.             petLootInProgress.set(false);
  17.             enableActions();
  18.         }
  19.     }
复制代码

        在PetLootHandler里,我们对原始识别到的物品进行完初次宠物过滤之后,调用pickupItems,这样多次请求就会被合并成一次,实现类似single flight的效果

  1. List<MapObject> filteredItems = items.stream()
  2.                     .filter(item -> {
  3.                         MapItem mapItem = (MapItem) item;
  4.                         return shouldPickupItem(chr, mapItem) && (mapItem.getOwnerId() == chr.getId()
  5.                                 || mapItem.getOwnerId() == chr.getPartyId());
  6.                     })
  7.                     .toList();

  8.             chr.pickupItems(filteredItems, petIndex);
复制代码

然后是2和3,
        2很好办,charcter的类中已经包好了这个检测函数
public boolean needQuestItem(int questid, int itemid)
,只要在拾取前判断一下,跳过即可
        3就有有点长了,因为我是玩单机,不会有这个情况,所以就没改,但逻辑也很简单。大概是
if ((MapId.isSelfLootableOnly(mapId)) && (mapitem.isPlayerDrop()&& mapitem.getDropper().getObjectId() != client.getPlayer().getObjectId()))

       
最后,闲的蛋疼的我发现原本每拾取一次物品,就会给客户端发包enableActions解卡
但我们现在批量拾取,可以视做异步过程,没必要每捡一个东西就发一个包,在拾取结束的finally里发一次就够了,然后把pickupItem中的enableActions替换成一个新的enableActions,当petIndex>=0&&petLootInProgress的时候直接return,没必要发那么多包回去。
这段就不放代码了,位点太杂,需要降低回包的腐竹应该看得懂思路

评分

参与人数 1蘑菇币 +30 收起 理由
leevccc + 30 很给力!

查看全部评分

4

主题

32

回帖

28

积分

新手上路

积分
28
发表于 3 天前 | 显示全部楼层
真不错呀,值得学习
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|小黑屋|蘑菇物语

GMT+8, 2026-1-11 08:00 , Processed in 0.058068 second(s), 24 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表