LK1905 Posted December 31, 2020 Posted December 31, 2020 Hi, I'm having problems syncing my mod's capability data. There's no errors/crashes or anything, but my code doesn't appear to be doing anything, as reloading the world resets all data values to zero. so I was wondering if someone could look at my code to see if I'm doing anything wrong, or am missing anything. My event handler: package lk1905.gielinorcraft; import lk1905.gielinorcraft.api.skill.ISkills; import lk1905.gielinorcraft.capability.skill.SkillCapability; import net.minecraft.entity.player.ServerPlayerEntity; import net.minecraftforge.common.util.LazyOptional; import net.minecraftforge.event.entity.player.PlayerEvent; import net.minecraftforge.event.entity.player.PlayerEvent.PlayerChangedDimensionEvent; import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedInEvent; import net.minecraftforge.event.entity.player.PlayerEvent.PlayerRespawnEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; @Mod.EventBusSubscriber(modid = Gielinorcraft.MODID, bus = Mod.EventBusSubscriber.Bus.FORGE) public class EventHandler { @SubscribeEvent public static void onPlayerClone(PlayerEvent.Clone event) { LazyOptional<ISkills> oldCap = event.getOriginal().getCapability(SkillCapability.SKILL_CAP, null); LazyOptional<ISkills> newCap = event.getPlayer().getCapability(SkillCapability.SKILL_CAP, null); ISkills oldSkills = oldCap.orElse(null); if(oldSkills != null) { ISkills newSkills = newCap.orElse(null); if(newSkills != null) { for(int i = 0; i < 26; i++) { newSkills.setXp(i, oldSkills.getXp(i)); newSkills.setStaticLevel(i, oldSkills.getStaticLevel(i)); newSkills.setLevel(i, oldSkills.getLevel(i)); } } } } @SubscribeEvent public static void onPlayerChangedDimensionEvent(PlayerChangedDimensionEvent event) { ServerPlayerEntity player = (ServerPlayerEntity) event.getPlayer(); if(!player.world.isRemote) { player.getCapability(SkillCapability.SKILL_CAP).ifPresent(c -> c.sync(player)); } } @SubscribeEvent public static void onRespawnEvent(PlayerRespawnEvent event) { if(!event.getPlayer().world.isRemote) { event.getPlayer().getCapability(SkillCapability.SKILL_CAP).ifPresent(c -> c.sync((ServerPlayerEntity) event.getPlayer())); } } @SubscribeEvent public static void onPlayerConnect(PlayerLoggedInEvent event) { ServerPlayerEntity player = (ServerPlayerEntity) event.getPlayer(); if(!player.world.isRemote) { player.getCapability(SkillCapability.SKILL_CAP).ifPresent(c -> c.sync(player)); } } } My packet handler: package lk1905.gielinorcraft.network; import java.util.List; import net.minecraft.entity.player.ServerPlayerEntity; import net.minecraft.server.MinecraftServer; import net.minecraft.util.ResourceLocation; import net.minecraftforge.common.util.FakePlayer; import net.minecraftforge.fml.network.NetworkDirection; import net.minecraftforge.fml.network.NetworkRegistry; import net.minecraftforge.fml.network.simple.SimpleChannel; public class PacketHandler { private static final String PROTOCOL_VERSION = Integer.toString(1); private static final SimpleChannel HANDLER = NetworkRegistry.ChannelBuilder .named(new ResourceLocation("gielinorcraft", "main_channel")) .clientAcceptedVersions(PROTOCOL_VERSION::equals) .serverAcceptedVersions(PROTOCOL_VERSION::equals) .networkProtocolVersion(() -> PROTOCOL_VERSION) .simpleChannel(); public static void register() { int disc = 0; HANDLER.registerMessage(disc++, SkillsPacket.class, SkillsPacket::encode, SkillsPacket::decode, SkillsPacket.Handler::handle); } /** * Sends a packet to a specific player.<br> * Must be called server side. * */ public static void sendTo(Object msg, ServerPlayerEntity player) { if(!(player instanceof FakePlayer)) { HANDLER.sendTo(msg, player.connection.netManager, NetworkDirection.PLAY_TO_CLIENT); } } /** * Sends a packet to the server.<br> * Must be called client side. * */ public static void sendToServer(Object msg) { HANDLER.sendToServer(msg); } /**Server side.*/ public static void sendToAllPlayers(Object msg, MinecraftServer server) { List<ServerPlayerEntity> list = server.getPlayerList().getPlayers(); for(ServerPlayerEntity e : list) { sendTo(msg, e); } } } The packet class for my capability: package lk1905.gielinorcraft.network; import java.util.function.Supplier; import lk1905.gielinorcraft.capability.skill.SkillCapability; import net.minecraft.client.Minecraft; import net.minecraft.nbt.CompoundNBT; import net.minecraft.network.PacketBuffer; import net.minecraftforge.fml.network.NetworkEvent; public class SkillsPacket { private final CompoundNBT nbt; public SkillsPacket(CompoundNBT nbt) { this.nbt = nbt; } public static void encode(SkillsPacket msg, PacketBuffer buf) { buf.writeCompoundTag(msg.nbt); } public static SkillsPacket decode(PacketBuffer buf) { return new SkillsPacket(buf.readCompoundTag()); } public static class Handler{ public static void handle(final SkillsPacket msg, Supplier<NetworkEvent.Context> ctx) { Minecraft mc = Minecraft.getInstance(); ctx.get().enqueueWork(() -> { mc.player.getCapability(SkillCapability.SKILL_CAP).ifPresent(cap -> cap.deserializeNBT(msg.nbt)); }); ctx.get().setPacketHandled(true); } } } All other code is here. Quote
poopoodice Posted December 31, 2020 Posted December 31, 2020 I don't know if this is intentional but in your SkillStorage, method writeNBT you kept rewrite/override the value of "xp", "static", and "dynamic". I guess what you are trying to do here is something like: for(int i = 0; i < 26; i++) { data.putInt("xp" + i, (int) instance.getXp(i) * 10); data.putInt("static" + i, instance.getStaticLevel(i)); data.putInt("dynamic" + i, instance.getLevel(i)); } so does writeNBT(), you assign the same value to every slot or whatever it is. 1 Quote
LK1905 Posted December 31, 2020 Author Posted December 31, 2020 I don't think there's anything wrong with the SkillStorage, as I can gain xp just fine, in the correct skills, the data is just reset to zero when i close and reopen the world. I also have the "Hitpoints" skill set to level 10 and 1154 xp by default. If I comment out my PlayerLoggedInEvent, the skill is correctly set to those values in game, and is reset to those values when I leave the world. But with the event enabled, both the level and xp values are set to 1 and 0. Quote
Sainthozier Posted December 31, 2020 Posted December 31, 2020 On 1/6/2020 at 10:28 AM, diesieben07 said: If you want the data to be available to the client who's player it is attached to: Send the data to the player in PlayerLoggedInEvent, PlayerRespawnEvent and PlayerChangedDimensionEvent. Send the data to the player whenever it changes. Don't forget to sync your capability data in Skills#addExp otherwise you wouldn't see any changes before those events. Quote
LK1905 Posted December 31, 2020 Author Posted December 31, 2020 1 hour ago, Sainthozier said: Don't forget to sync your capability data in Skills#addExp otherwise you wouldn't see any changes before those events. This is probably a stupid question, but how would I do that? I tried adding it to SkillStorage the same way I have the getXP, getLevel and getStaticLevel methods, but that made no difference. Is that what you meant, or something different? Quote
poopoodice Posted December 31, 2020 Posted December 31, 2020 1 hour ago, LK1905 said: the data is just reset to zero when i close and reopen the world. The data are being read/write when world is loading/saving, if there's something wrong with it the whole thing is messed up. Quote
Sainthozier Posted December 31, 2020 Posted December 31, 2020 1 hour ago, LK1905 said: but how would I do that? Just call your sync method whenever the data changes. Quote
LK1905 Posted January 1, 2021 Author Posted January 1, 2021 12 hours ago, Sainthozier said: Just call your sync method whenever the data changes. I added it into the AddXP method like this: if(entity instanceof PlayerEntity) { sync((ServerPlayerEntity) entity); } That didn't make any difference. Is that what you meant? Quote
LK1905 Posted January 2, 2021 Author Posted January 2, 2021 13 hours ago, diesieben07 said: This is entirely broken. You are reaching across logical sides. Even if you were on the client here, you must modify capability data on the server, if you want it to persist. Only the server saves data. I changed that part to this: PlayerEntity player = (PlayerEntity) event.getSource().getTrueSource(); LazyOptional<ISkills> cap = player.getCapability(SkillCapability.SKILL_CAP); ISkills skills = cap.orElse(null); But now I can't gain xp at all. Is that the correct way of doing it? Quote
LK1905 Posted January 3, 2021 Author Posted January 3, 2021 11 hours ago, diesieben07 said: What makes you think you are not gaining XP? How have you checked? My GUI doesn't update the xp values. This is how I reference the player in my GUI: private PlayerEntity player = Minecraft.getInstance().player; private LazyOptional<ISkills> cap = player.getCapability(SkillCapability.SKILL_CAP); private ISkills skills = cap.orElse(null); Do I need to reference the Server Player instead? If so, how? Or Is the client player already supposed to know my server player data from my events/packets? If so, why aren't they working? They're in the OP. Quote
LK1905 Posted January 3, 2021 Author Posted January 3, 2021 But as far as I know, my packet and event handler are already supposed to sync it. Which is why I made this thread, to ask why they aren't. Quote
LK1905 Posted January 4, 2021 Author Posted January 4, 2021 12 hours ago, diesieben07 said: Post a git repo of your mod. https://github.com/LK1905/GielinorCraft Quote
LK1905 Posted January 4, 2021 Author Posted January 4, 2021 Is that this part? public static void register() { CapabilityManager.INSTANCE.register(ISkills.class, new SkillStorage(), () -> new Skills(null)); } If so, isn't that part supposed to be null? Every other capability I've seen from other people that had an entity/player there had it set to null. Quote
LK1905 Posted January 17, 2021 Author Posted January 17, 2021 Sorry for the late response. Another stupid question, but how do I set the the entity so that it's not null? Quote
LK1905 Posted January 18, 2021 Author Posted January 18, 2021 Did you mean replace EntityLiving in the Object instanceof part with PlayerEntity? Because I just tried that and it made no difference. Quote
LK1905 Posted January 18, 2021 Author Posted January 18, 2021 8 minutes ago, diesieben07 said: No. I did not say that at all. Then I'm not sure what you mean by "Pass in the player", sorry. Quote
LK1905 Posted January 23, 2021 Author Posted January 23, 2021 Okay, I believe I've done what you have told me, and my gui does update me gaining xp. However, theres something wrong with my sync method, as whenever it is called, it resets all xp values to zero. So the values in my gui are lost when reloading the world. Heres the sync method in my Skills class: @Override public void sync(ServerPlayerEntity player) { if(entity instanceof ServerPlayerEntity) { PacketHandler.sendTo(new SkillsPacket(serializeNBT()), player); } } Heres my attempt at syncing on login, in my player event handler: @SubscribeEvent public static void onPlayerConnect(PlayerLoggedInEvent event) { ServerPlayerEntity player = (ServerPlayerEntity) event.getPlayer(); if(!player.world.isRemote) { player.getCapability(SkillCapability.SKILL_CAP).ifPresent(c -> c.sync(player)); } } Updated my git repo, here. Quote
LK1905 Posted January 23, 2021 Author Posted January 23, 2021 I've changed the serializeNBT and deserialize methods so the value of i is in the key: @Override public CompoundNBT serializeNBT() { CompoundNBT data = new CompoundNBT(); for(int i = 0; i < 26; i++) { data.putInt("xp_" + i, (int) xp[i]); data.putInt("dynamic_" + i, dynamicLevels[i]); data.putInt("static_" + i, staticLevels[i]); } return data; } @Override public void deserializeNBT(CompoundNBT data) { for(int i = 0; i < 26; i++) { xp[i] = data.getInt("xp_" + i); dynamicLevels[i] = data.getInt("dynamic_" + i); staticLevels[i] = data.getInt("static_" + i); } } And now it works, thank you! (I think thats what the first person to reply to this post was trying to tell me to do but I didn't understand at the time). Quote
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.