附加的Capability报空值

USS.Shenzhou


版本信息
你使用的系统:win10 x64
你是用的JDK: 8u251 x64
你使用的IDE:IntelliJ IDEA
你使用的IDE版本:2020.2
Forge版本: 31.2.36
Minecraft版本: 1.15.2
Mapping 文件版本: 20200901

错误情况简述
依照Neutrino的教程做了一套Capability,额外做了一个用于测试的方块(右击时为附加的capability赋值)和一个用于测试的物品(右击时在控制台输出这个能力包含的值)。目前情况是玩家重生后使用物品或右击方块均会报空值的警告(见报错日志)。但是在控制台输出有
[Server thread/DEBUG] [ne.mi.fm.FMLWorldPersistenceHook/WP]: Gathering id map for writing to world save Rainbow6 Test
这句之后(表面看起来是这样),就一切正常不会报空了。(顺便:这句话的背后做了些什么?

报错日志
右击方块

[11:50:34] [Server thread/WARN] [ne.mi.co.ut.LazyOptional/CATCHING]: Catching
java.lang.NullPointerException: Supplier should not return null value
	at net.minecraftforge.common.util.LazyOptional.getValue(LazyOptional.java:119) ~[?:?] {re:classloading}
	at net.minecraftforge.common.util.LazyOptional.ifPresent(LazyOptional.java:159) ~[?:?] {re:classloading}
	at com.ussshenzhou.rainbow6.blocks.CapabilityTestBlock.onBlockActivated(CapabilityTestBlock.java:28) ~[?:?] {re:classloading}
	at net.minecraft.block.BlockState.onBlockActivated(BlockState.java:294) ~[?:?] {re:classloading}
	at net.minecraft.server.management.PlayerInteractionManager.func_219441_a(PlayerInteractionManager.java:343) ~[?:?] {re:classloading}
	at net.minecraft.network.play.ServerPlayNetHandler.processTryUseItemOnBlock(ServerPlayNetHandler.java:881) ~[?:?] {re:classloading}
	at net.minecraft.network.play.client.CPlayerTryUseItemOnBlockPacket.processPacket(CPlayerTryUseItemOnBlockPacket.java:45) ~[?:?] {re:classloading}
	at net.minecraft.network.play.client.CPlayerTryUseItemOnBlockPacket.processPacket(CPlayerTryUseItemOnBlockPacket.java:12) ~[?:?] {re:classloading}
	at net.minecraft.network.PacketThreadUtil.lambda$checkThreadAndEnqueue$0(PacketThreadUtil.java:19) ~[?:?] {re:classloading}
	at net.minecraft.util.concurrent.TickDelayedTask.run(TickDelayedTask.java:20) [?:?] {re:classloading}
	at net.minecraft.util.concurrent.ThreadTaskExecutor.run(ThreadTaskExecutor.java:140) [?:?] {re:classloading,pl:accesstransformer:B}
	at net.minecraft.util.concurrent.RecursiveEventLoop.run(RecursiveEventLoop.java:22) [?:?] {re:classloading}
	at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:759) [?:?] {re:classloading,pl:accesstransformer:B}
	at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:141) [?:?] {re:classloading,pl:accesstransformer:B}
	at net.minecraft.util.concurrent.ThreadTaskExecutor.driveOne(ThreadTaskExecutor.java:110) [?:?] {re:classloading,pl:accesstransformer:B}
	at net.minecraft.server.MinecraftServer.driveOneInternal(MinecraftServer.java:742) [?:?] {re:classloading,pl:accesstransformer:B}
	at net.minecraft.server.MinecraftServer.driveOne(MinecraftServer.java:736) [?:?] {re:classloading,pl:accesstransformer:B}
	at net.minecraft.util.concurrent.ThreadTaskExecutor.driveUntil(ThreadTaskExecutor.java:123) [?:?] {re:classloading,pl:accesstransformer:B}
	at net.minecraft.server.MinecraftServer.runScheduledTasks(MinecraftServer.java:722) [?:?] {re:classloading,pl:accesstransformer:B}
	at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:666) [?:?] {re:classloading,pl:accesstransformer:B}
	at java.lang.Thread.run(Thread.java:748) [?:1.8.0_251] {}

使用物品

[11:50:35] [Server thread/WARN] [ne.mi.co.ut.LazyOptional/CATCHING]: Catching
java.lang.NullPointerException: Supplier should not return null value
	at net.minecraftforge.common.util.LazyOptional.getValue(LazyOptional.java:119) ~[?:?] {re:classloading}
	at net.minecraftforge.common.util.LazyOptional.ifPresent(LazyOptional.java:159) ~[?:?] {re:classloading}
	at com.ussshenzhou.rainbow6.items.CapabilityTestItem.onItemRightClick(CapabilityTestItem.java:29) ~[?:?] {re:classloading}
	at net.minecraft.item.ItemStack.useItemRightClick(ItemStack.java:206) ~[?:?] {re:classloading}
	at net.minecraft.server.management.PlayerInteractionManager.processRightClick(PlayerInteractionManager.java:293) ~[?:?] {re:classloading}
	at net.minecraft.network.play.ServerPlayNetHandler.processTryUseItem(ServerPlayNetHandler.java:905) ~[?:?] {re:classloading}
	at net.minecraft.network.play.client.CPlayerTryUseItemPacket.processPacket(CPlayerTryUseItemPacket.java:37) ~[?:?] {re:classloading}
	at net.minecraft.network.play.client.CPlayerTryUseItemPacket.processPacket(CPlayerTryUseItemPacket.java:9) ~[?:?] {re:classloading}
	at net.minecraft.network.PacketThreadUtil.lambda$checkThreadAndEnqueue$0(PacketThreadUtil.java:19) ~[?:?] {re:classloading}
	at net.minecraft.util.concurrent.TickDelayedTask.run(TickDelayedTask.java:20) [?:?] {re:classloading}
	at net.minecraft.util.concurrent.ThreadTaskExecutor.run(ThreadTaskExecutor.java:140) [?:?] {re:classloading,pl:accesstransformer:B}
	at net.minecraft.util.concurrent.RecursiveEventLoop.run(RecursiveEventLoop.java:22) [?:?] {re:classloading}
	at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:759) [?:?] {re:classloading,pl:accesstransformer:B}
	at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:141) [?:?] {re:classloading,pl:accesstransformer:B}
	at net.minecraft.util.concurrent.ThreadTaskExecutor.driveOne(ThreadTaskExecutor.java:110) [?:?] {re:classloading,pl:accesstransformer:B}
	at net.minecraft.server.MinecraftServer.driveOneInternal(MinecraftServer.java:742) [?:?] {re:classloading,pl:accesstransformer:B}
	at net.minecraft.server.MinecraftServer.driveOne(MinecraftServer.java:736) [?:?] {re:classloading,pl:accesstransformer:B}
	at net.minecraft.util.concurrent.ThreadTaskExecutor.driveUntil(ThreadTaskExecutor.java:123) [?:?] {re:classloading,pl:accesstransformer:B}
	at net.minecraft.server.MinecraftServer.runScheduledTasks(MinecraftServer.java:722) [?:?] {re:classloading,pl:accesstransformer:B}
	at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:666) [?:?] {re:classloading,pl:accesstransformer:B}
	at java.lang.Thread.run(Thread.java:748) [?:1.8.0_251] {}

不会出现报错的情况:

[16:26:50] [Server thread/INFO] [minecraft/MinecraftServer]: Dev试图在熔岩里游泳
[16:26:50] [Render thread/INFO] [minecraft/NewChatGui]: [CHAT] Dev试图在熔岩里游泳
[16:27:00] [Server thread/WARN] [ne.mi.co.ut.LazyOptional/CATCHING]:
......
[16:27:27] [Server thread/DEBUG] [ne.mi.fm.FMLWorldPersistenceHook/WP]: Gathering id map for writing to world save Rainbow6 Test
[16:27:28] [Server thread/INFO] [co.us.ra.it.CapabilityTestItem/]: operatorless,teamless
[16:27:29] [Server thread/INFO] [co.us.ra.it.CapabilityTestItem/]: operatorless,teamless

相关代码
CapabilityTestBlock

    @Override
    public ActionResultType onBlockActivated(BlockState state, World worldIn, BlockPos pos, PlayerEntity player, Hand handIn, BlockRayTraceResult hit) {
        if (!worldIn.isRemote){
            LazyOptional<IR6PlayerCapability> r6PlayerCap = player.getCapability(ModCapabilities.R6_PLAYER_CAPABILITY);
            r6PlayerCap.ifPresent((cap)->{
                        cap.setR6Team("attacker");
                        cap.setOperator("mira");
                    }
            );
        }
        return ActionResultType.SUCCESS;
    }

CapabilityTestItem

    @Override
    public ActionResult<ItemStack> onItemRightClick(World worldIn, PlayerEntity playerIn, Hand handIn) {
        ItemStack itemstack = playerIn.getHeldItem(handIn);

        if (!worldIn.isRemote){
            LazyOptional<IR6PlayerCapability> r6PlayerCap = playerIn.getCapability(ModCapabilities.R6_PLAYER_CAPABILITY);
            r6PlayerCap.ifPresent((cap)->{
                        String operator = cap.getOperator();
                        String r6Team = cap.getR6Team();
                        LogManager.getLogger().info(operator+","+r6Team);
                    }
            );
        }
        return new ActionResult<>(ActionResultType.SUCCESS, itemstack);
    }

ModCapabilityAttachEvent

@Mod.EventBusSubscriber()
public class ModCapabilityAttachEvent {
    @SubscribeEvent
    public static void onAttachCapability(AttachCapabilitiesEvent<Entity> event){
        Entity entity = event.getObject();
        if (entity instanceof PlayerEntity){
            event.addCapability(new ResourceLocation("rainbow6","r6player"),new R6PlayerCapabilityProvider());
        }
    }
    @SubscribeEvent
    public static void onPlayerCloned(PlayerEvent.Clone event){
        if (!event.isWasDeath()){
            LazyOptional<IR6PlayerCapability> oldR6Cap = event.getOriginal().getCapability(ModCapabilities.R6_PLAYER_CAPABILITY);
            LazyOptional<IR6PlayerCapability> newR6Cap = event.getPlayer().getCapability(ModCapabilities.R6_PLAYER_CAPABILITY);
            if (oldR6Cap.isPresent()&& newR6Cap.isPresent()){
                newR6Cap.ifPresent((newCap)->{
                    oldR6Cap.ifPresent((oldCap)->{
                        newCap.deserializeNBT(oldCap.serializeNBT());
                            }

                    );
                        }

                );
            }
        }
    }
}

R6PlayerCapabilityProvider

    private IR6PlayerCapability r6PlayerCapability;
    @Nonnull
    @Override
    public <T> LazyOptional<T> getCapability(@Nonnull Capability<T> cap, @Nonnull Direction side) {
        return cap ==ModCapabilities.R6_PLAYER_CAPABILITY ? LazyOptional.of(()-> this.r6PlayerCapability).cast():LazyOptional.empty();
    }

    @Nonnull
    IR6PlayerCapability getOrCreateCapability(){
        if (r6PlayerCapability==null){
            this.r6PlayerCapability = new R6PlayerCapability("operatorless","teamless");
        }
        return this.r6PlayerCapability;
    }

    @Override
    public CompoundNBT serializeNBT() {
        return getOrCreateCapability().serializeNBT();
    }

    @Override
    public void deserializeNBT(CompoundNBT nbt) {
        getOrCreateCapability().deserializeNBT(nbt);
    }
}

USS.Shenzhou


补充:我其他有关capability的代码在github上也可以看到。我不太清楚其中哪一些更重要,只选择性发了一部分。


FledgeXu


那句话没什么意思。
你是不是没有复写PlayerEvent.Clone方法……

当玩家死亡后重生或者从末地回到主世界,都会触发这个方法,理论上来说从末地回到主世界应该会自动同步数据,不知道处于什么样子的考虑,这个功能一直没有实现,所以我们需要在这里手动的恢复我们的能力。 event.isWasDeath() 为真时代表玩家死亡后重生,而为假时代表从末地回到主世界。在这里 event.getOriginal() 得到的是玩家之前的实体, event.getPlayer() 代表的是玩家重生之后的实体。
附加能力提供器 - Minecraft Forge Mod Dev tutorial


FledgeXu



我看了一眼你的代码if (!event.isWasDeath())你没在玩家死亡的时候恢复cap

USS.Shenzhou


呃。。。看起来不是这里的问题。我直接把if注释掉了(当然中间的代码是留着的) 然后日志是这样的

[16:51:34] [Server thread/INFO] [co.us.ra.it.CapabilityTestItem/]: operatorless,teamless
[16:51:43] [Server thread/INFO] [minecraft/MinecraftServer]: Dev试图在熔岩里游泳
[16:51:44] [Render thread/INFO] [minecraft/NewChatGui]: [CHAT] Dev试图在熔岩里游泳
[16:51:45] [Server thread/WARN] [ne.mi.co.ut.LazyOptional/CATCHING]: Catching
java.lang.NullPointerException: Supplier should not return null value
	at net.minecraftforge.common.util.LazyOptional.getValue(LazyOptional.java:119) ~[forge-1.15.2-31.2.36_mapped_snapshot_20200901-1.15.1-recomp.jar:?] {re:classloading}
	at net.minecraftforge.common.util.LazyOptional.ifPresent(LazyOptional.java:159) ~[forge-1.15.2-31.2.36_mapped_snapshot_20200901-1.15.1-recomp.jar:?] {re:classloading}
	此处at com.ussshenzhou.rainbow6.capability.ModCapabilityAttachEvent.onPlayerCloned(ModCapabilityAttachEvent.java:27) ~[classes/:?] {re:classloading}
	at net.minecraftforge.eventbus.ASMEventHandler_5_ModCapabilityAttachEvent_onPlayerCloned_Clone.invoke(.dynamic) ~[?:?] {}
	at net.minecraftforge.eventbus.ASMEventHandler.invoke(ASMEventHandler.java:80) ~[eventbus-2.2.0-service.jar:?] {}
	at net.minecraftforge.eventbus.EventBus.post(EventBus.java:258) ~[eventbus-2.2.0-service.jar:?] {}
	at net.minecraftforge.event.ForgeEventFactory.onPlayerClone(ForgeEventFactory.java:474) ~[?:?] {re:classloading}
	at net.minecraft.entity.player.ServerPlayerEntity.copyFrom(ServerPlayerEntity.java:1088) ~[?:?] {re:classloading,pl:accesstransformer:B}
	at net.minecraft.server.management.PlayerList.recreatePlayerEntity(PlayerList.java:425) ~[?:?] {re:classloading}
	at net.minecraft.network.play.ServerPlayNetHandler.processClientStatus(ServerPlayNetHandler.java:1158) ~[?:?] {re:classloading}
	at net.minecraft.network.play.client.CClientStatusPacket.processPacket(CClientStatusPacket.java:36) ~[?:?] {re:classloading}
	at net.minecraft.network.play.client.CClientStatusPacket.processPacket(CClientStatusPacket.java:8) ~[?:?] {re:classloading}
	at net.minecraft.network.PacketThreadUtil.lambda$checkThreadAndEnqueue$0(PacketThreadUtil.java:19) ~[?:?] {re:classloading}
	at net.minecraft.util.concurrent.TickDelayedTask.run(TickDelayedTask.java:20) [?:?] {re:classloading}
	at net.minecraft.util.concurrent.ThreadTaskExecutor.run(ThreadTaskExecutor.java:140) [?:?] {re:classloading,pl:accesstransformer:B}
	at net.minecraft.util.concurrent.RecursiveEventLoop.run(RecursiveEventLoop.java:22) [?:?] {re:classloading}
	at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:759) [?:?] {re:classloading,pl:accesstransformer:B}
	at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:141) [?:?] {re:classloading,pl:accesstransformer:B}
	at net.minecraft.util.concurrent.ThreadTaskExecutor.driveOne(ThreadTaskExecutor.java:110) [?:?] {re:classloading,pl:accesstransformer:B}
	at net.minecraft.server.MinecraftServer.driveOneInternal(MinecraftServer.java:742) [?:?] {re:classloading,pl:accesstransformer:B}
	at net.minecraft.server.MinecraftServer.driveOne(MinecraftServer.java:736) [?:?] {re:classloading,pl:accesstransformer:B}
	at net.minecraft.util.concurrent.ThreadTaskExecutor.driveUntil(ThreadTaskExecutor.java:123) [?:?] {re:classloading,pl:accesstransformer:B}
	at net.minecraft.server.MinecraftServer.runScheduledTasks(MinecraftServer.java:722) [?:?] {re:classloading,pl:accesstransformer:B}
	at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:666) [?:?] {re:classloading,pl:accesstransformer:B}
	at java.lang.Thread.run(Thread.java:748) [?:1.8.0_251] {}

去翻了翻FMLWorldPersistenceHook的代码,感觉是NBT保存上的问题?但是不知道具体怎么解决:(


FledgeXu


这里好像写错了
应该是

LazyOptional.of(()-> getOrCreateCapability()).cast()

USS.Shenzhou


新 冰川之下? :rofl:(逃


system


该主题在最后一个回复创建后7天后自动关闭。不再允许新的回复。