Résolu Difficultés à modifier FoodStats de PlayerEntity
-
Hello !
Je suis un peu rouillé en modding, donc j’ai probablement fait des horreurs, mais j’essaie de refaire le mod Oversaturation (https://github.com/truenachtara/Oversaturation) en 1.16.x, pour m’amuser un peu plus dans ma survie solo… et c’est plus compliqué que prévu.
L’idée, c’est donc de remplacer la méthode
addStats
de la classeFoodStats
pour retirer la limite de 20.
Malheureusement, l’attributfoodStats
dePlayerEntity
n’est plus public et n’a pas de setter.J’ai donc essayé de créer une classe héritant de
ServerPlayerEntity
qui rajoute uniquement ce setter (moins je fais de modifications, mieux je me porte). Le contenu est le suivant :public class BetterPlayerEntity extends ServerPlayerEntity { public BetterPlayerEntity(MinecraftServer server, ServerWorld worldIn, GameProfile profile) { super(server, worldIn, profile, new PlayerInteractionManager(worldIn)); } public void setFoodStats(FoodStats stats) { this.foodStats = stats; } }
Le
new PlayerInteractionManager
me permet de ne pas avoir tout un tas d’erreurs lors d’interactions avec le monde par la suite. Le code est inspiré de ce qui se trouve dansPlayerList#func_232644_a_
, qui semble être la méthode utilisée pour le respawn d’un joueur.Jusqu’ici, tout va bien, mais il faut maintenant que je remplace le joueur par mon nouveau joueur. J’utilise donc l’event
EntityJoinWorldEvent
pour modifier.@SubscribeEvent public void onEntityJoinWorld(EntityJoinWorldEvent event) { if (event.getEntity() instanceof ServerPlayerEntity){ ServerPlayerEntity player = (ServerPlayerEntity) event.getEntity(); FoodStats oldStats = player.getFoodStats(); if (!(oldStats instanceof UncappedFoodStats)) { UncappedFoodStats newStats = new UncappedFoodStats(); CompoundNBT foodnbt = new CompoundNBT(); oldStats.write(foodnbt); newStats.read(foodnbt); BetterPlayerEntity playerEntity = new BetterPlayerEntity(player.server, player.getServerWorld(), player.getGameProfile()); playerEntity.copyFrom(player, true); playerEntity.setFoodStats(newStats); player.copyFrom(playerEntity, true); playerEntity.remove(false); } } }
UncappedFoodStats
est une classe héritant deFoodStats
et modifiant uniquement la méthodeaddStats
.
Le problème de cette méthode est que je dois créer une nouvelle instance deBetterPlayerEntity
, qui va donc appeler les constructeurs deServerPlayerEntity
,PlayerEntity
,LivingEntity
, …
Je me retrouve donc avec deux instances du même joueur dans le monde, et je n’en veux qu’une seule.
Étant donné que laMap
dePlayerList
n’est pas accessible (map entreUUID
etServerPlayerEntity
), réutiliser le joueur déjà créé à l’origine pour simplement lui copier mon joueur custom me semble être le plus simple. Le code fonctionne, mais de manière très temporaire (de moins d’une seconde à une vingtaine de secondes au plus, avant le crash).Le problème vient sûrement du fait que deux entités correspondant au joueur existent, car j’ai un crash lors d’un tick (une NPE).
Voici les logs :
net.minecraft.crash.ReportedException: Ticking memory connection at net.minecraft.network.NetworkSystem.tick(NetworkSystem.java:154) ~[forge:?] {re:classloading} at net.minecraft.server.MinecraftServer.updateTimeLightAndEntities(MinecraftServer.java:898) ~[forge:?] {re:classloading,pl:accesstransformer:B} at net.minecraft.server.MinecraftServer.tick(MinecraftServer.java:820) ~[forge:?] {re:classloading,pl:accesstransformer:B} at net.minecraft.server.integrated.IntegratedServer.tick(IntegratedServer.java:84) ~[forge:?] {re:classloading,pl:runtimedistcleaner:A} at net.minecraft.server.MinecraftServer.func_240802_v_(MinecraftServer.java:663) ~[forge:?] {re:classloading,pl:accesstransformer:B} at net.minecraft.server.MinecraftServer.lambda$startServer$0(MinecraftServer.java:233) ~[forge:?] {re:classloading,pl:accesstransformer:B} at java.lang.Thread.run(Thread.java:748) [?:1.8.0_241] {} Caused by: java.lang.NullPointerException at net.minecraft.item.crafting.ServerRecipeBook.sendPacket(ServerRecipeBook.java:60) ~[forge:?] {re:classloading} at net.minecraft.item.crafting.ServerRecipeBook.add(ServerRecipeBook.java:38) ~[forge:?] {re:classloading} at net.minecraft.entity.player.ServerPlayerEntity.unlockRecipes(ServerPlayerEntity.java:1076) ~[forge:?] {re:classloading,pl:accesstransformer:B} at net.minecraft.entity.player.ServerPlayerEntity.unlockRecipes(ServerPlayerEntity.java:1086) ~[forge:?] {re:classloading,pl:accesstransformer:B} at net.minecraft.advancements.AdvancementRewards.apply(AdvancementRewards.java:65) ~[forge:?] {re:classloading} at net.minecraft.advancements.PlayerAdvancements.grantCriterion(PlayerAdvancements.java:209) ~[forge:?] {re:classloading} at net.minecraft.advancements.ICriterionTrigger$Listener.grantCriterion(ICriterionTrigger.java:34) ~[forge:?] {re:classloading} at net.minecraft.advancements.criterion.AbstractCriterionTrigger.triggerListeners(AbstractCriterionTrigger.java:68) ~[forge:?] {re:classloading} at net.minecraft.advancements.criterion.InventoryChangeTrigger.trigger(InventoryChangeTrigger.java:56) ~[forge:?] {re:classloading} at net.minecraft.advancements.criterion.InventoryChangeTrigger.test(InventoryChangeTrigger.java:52) ~[forge:?] {re:classloading} at net.minecraft.entity.player.ServerPlayerEntity.sendSlotContents(ServerPlayerEntity.java:988) ~[forge:?] {re:classloading,pl:accesstransformer:B} at net.minecraft.inventory.container.Container.detectAndSendChanges(Container.java:141) ~[forge:?] {re:classloading} at net.minecraft.inventory.container.Container.addListener(Container.java:102) ~[forge:?] {re:classloading} at net.minecraft.entity.player.ServerPlayerEntity.addSelfToInternalCraftingInventory(ServerPlayerEntity.java:341) ~[forge:?] {re:classloading,pl:accesstransformer:B} at net.minecraft.server.management.PlayerList.initializeConnectionToPlayer(PlayerList.java:231) ~[forge:?] {re:classloading} at net.minecraft.network.login.ServerLoginNetHandler.tryAcceptPlayer(ServerLoginNetHandler.java:122) ~[forge:?] {re:classloading} at net.minecraft.network.login.ServerLoginNetHandler.tick(ServerLoginNetHandler.java:66) ~[forge:?] {re:classloading} at net.minecraft.network.NetworkManager.tick(NetworkManager.java:244) ~[forge:?] {re:classloading} at net.minecraft.network.NetworkSystem.tick(NetworkSystem.java:151) ~[forge:?] {re:classloading} ... 6 more
J’ai bien songé à utiliser la méthode de respawn de
PlayerList
pour que la map prenne mon instance de joueur, mais il faut toujours que je supprime proprement l’autre joueur, et c’est ce qui me pose problème.
Du coup je suis un peu coincé pour la suite, je ne vois pas trop ce que je peux changerMerci d’avance pour votre aide !
-
Du coup, j’ai fini par trouver entre temps.
Il manquait de quoi supprimer le “nouveau” joueur du monde, et de quoi faire des copies complètes entre les joueurs (pour être certain d’avoir toutes les infos). Du coup j’ai refait une petite fonction
copy
qui reprend en gros le code dePlayerList
et tout a l’air de fonctionner sans crash depuis@SubscribeEvent public void onEntityJoinWorld(EntityJoinWorldEvent event) { if (event.getEntity() instanceof ServerPlayerEntity){ ServerPlayerEntity player = (ServerPlayerEntity) event.getEntity(); FoodStats oldStats = player.getFoodStats(); if (!(oldStats instanceof UncappedFoodStats)) { UncappedFoodStats newStats = new UncappedFoodStats(); CompoundNBT foodnbt = new CompoundNBT(); oldStats.write(foodnbt); newStats.read(foodnbt); BetterPlayerEntity playerEntity = new BetterPlayerEntity(player.server, player.getServerWorld(), player.getGameProfile()); copy(player, playerEntity); playerEntity.setFoodStats(newStats); player.getServerWorld().removePlayer(playerEntity, true); copy(playerEntity, player); playerEntity.remove(false); } } } private void copy(ServerPlayerEntity source, ServerPlayerEntity dest) { dest.connection = source.connection; dest.copyFrom(source, true); dest.setEntityId(source.getEntityId()); dest.setPrimaryHand(source.getPrimaryHand()); for(String s : source.getTags()) { dest.addTag(s); } dest.addSelfToInternalCraftingInventory(); }
-
Sinon il n’aurait pas été plus simple de changer la valeur par réflexion ?
-
@robin4002 Si, c’est 20x plus simple ^^’
J’avais mal fait ma réflexion donc j’avais abandonné cette idée, mais en le refaisant j’ai réussi à le faire marcher, et ça réduit les risques de casser quelque chose involontairement