Jump to content

[1.17.1] Player capabilities are invalidated on death before they can be copied


Recommended Posts

Posted

When a player dies, and I attempt to copy my custom capabilities data from the original player object to the new player object in the PlayerEvent.Clone event, the getOriginal().getCapability method is not giving me the capability. Upon further inspection in the Debugger, the capability is marked as invalid. Specifically, event.original.capabilities.caps[0].mycapabilityLazyOptional.isValid = false. This is unique to 1.17.1. In 1.16.5, with the same code isValid = true, and the data is copied as expected.

All other functionality seems to be working fine in 1.17.1. Data is being saved when a player leaves and rejoins, and can be found in their player data file NBT. It can be queried by other methods and classes while the player is alive. It is only on death that the capability data of the original player object can not be retrieved.

Do we need to take a different approach in 1.17.1 or is something not working as expected?

Posted

Thanks! I think I'm not fully understanding how to implement this, though. Calling reviveCaps() on the original player object does not validate the capabilities. It looks like I need to call reviveCaps() on the provider, but I'm not sure how to do that in this context.

Posted

Yes this is intentional.
The system we had before is no longer valid for the way Mojang designed the code in 1.17.
Modders now have to explicitly revive and then invalidate their entities as designed in my commit.

I do Forge for free, however the servers to run it arn't free, so anything is appreciated.
Consider supporting the team on Patreon

Posted

That doesn't seem to work unless I'm doing it incorrectly:

    @SubscribeEvent
    public void onPlayerDeath(PlayerEvent.Clone event)
    {
        if (event.getEntity() instanceof Player playerEntity) {

            event.getOriginal().reviveCaps();
            event.getOriginal().getCapability(CapabilityNutritionalBalancePlayer.HEALTHY_DIET_PLAYER_CAPABILITY).ifPresent(originalInutritionalbalancePlayer -> {

                playerEntity.getCapability(CapabilityNutritionalBalancePlayer.HEALTHY_DIET_PLAYER_CAPABILITY).ifPresent(newInutritionalbalancePlayer -> {


                  . . . .

                });

            });
            event.getOriginal().invalidateCaps();
        }

    }

After running event.getOriginal().reviveCaps(), the caps are still invalid, so the lambda code is never executed.

Similarly for dimension changes:

    @SubscribeEvent
    public void onDimensionChange (PlayerEvent.PlayerChangedDimensionEvent event){
        event.getEntity().reviveCaps();
    }

The caps are still invalid after executing this.

Am I doing something wrong?

Posted

Then your provider is not returning a valid cap. Use your IDE's debugging tools to figure out why your code is not returning the value you think it is.

I do Forge for free, however the servers to run it arn't free, so anything is appreciated.
Consider supporting the team on Patreon

Posted

My provider is returning a valid cap in every other circumstance though. Just not during cloning or dimension changes. Data is saved on leave and rejoin. GUI elements that query the caps are working as expected. Other events that are taking actions based on values within the caps are working.

In the debugger, I can see all the data in my cap in the 2 above scenarios. It is exactly what it's supposed to be, but
event.entity.capabilities.caps[0].nutritionalBalanceLazyOptional.isValid = false
and it remains so after calling reviveCaps().

Other than that, the debugger is showing that all the other data is exactly what it's supposed to be. What would cause isValid to be false?

Posted

It might help to add that when I check the player object at any other point, such as when opening the mod's GUI, nutritionalBalanceLazyOptional.isValid = true. So, I'm guessing it's being invalidated by the death and dimension change, and reviveCaps() is not changing that. Based on my understanding of the commit you referenced above, reviveCaps() is supposed to change that to true. Right?

  • 2 weeks later...
Posted

Can someone confirm that Player.reviveCaps() does, indeed, restore capabilities attached to the player? This does not appear to be happening in my case. I have thoroughly debugged my code (otherwise I would not have opened this thread), and the only issue appears to be that LazyOptional.isValid becomes false on death and dimension change, and this is not changed to true when calling reviveCaps().

In short, reviveCaps is not doing what I think it's supposed to do. So, either it's not working correctly, or I am misunderstanding how it's supposed to work.

  • 4 weeks later...
Posted

As far as I know, no solution has been found. I might need to open an issue on the MinecraftForge GitHub.

I have a couple questions for you just to make sure we're both dealing with the same issue:

Are your capabilities working when the player leaves and rejoins?
Have you also tried using reviveCaps() with no success?
Is your code posted publicly or can you share an excerpt of your PlayerEvent.Clone event?

  • 3 weeks later...
Posted

The reviveCaps() worked for me. I'm using forge 37.0.67.

My Clone Event:

public static void onDeath(PlayerEvent.Clone event) {
    if (event.isWasDeath()) {
        event.getOriginal().reviveCaps();
        event.getOriginal().getCapability(ModCapabilityImpl.MOD_CAPABILITY).ifPresent(oldStore -> {
            event.getEntity().getCapability(ModCapabilityImpl.MOD_CAPABILITY).ifPresent(newStore -> {
               newStore.copyForRespawn((ModCapabilityImpl) oldStore);
            });
        });
        event.getOriginal().invalidateCaps();
    }
}

In my IModCapability:

void copyForRespawn(ModCapabilityImpl oldStore);

In my ModCapabilityImpl:

@Override
public void copyForRespawn(ModCapabilityImpl oldStore) {
	this.value = oldStore.value;
}

Hope this helps!

  • Thanks 2
Posted
On 10/6/2021 at 4:20 PM, DaqEm said:

The reviveCaps() worked for me. I'm using forge 37.0.67.

Interesting. Thanks for sharing that. I haven't tested it since 37.0.30. I ended up changing over to SaveData. It's much simpler to work with, gives me a lot more control, and I don't have to worry as much about it breaking every time Minecraft updates. The only drawback is that the data will persist even if player data files are deleted. They would have to delete the custom data file instead. I don't think that's a big issue, especially given that the tradeoff is that the data also persist through death, dimension changes or whatever without having to play any dirty tricks.

  • 1 month later...
Posted
On 9/21/2021 at 4:17 PM, Danny and Son said:

As far as I know, no solution has been found. I might need to open an issue on the MinecraftForge GitHub.

I have a couple questions for you just to make sure we're both dealing with the same issue:

Are your capabilities working when the player leaves and rejoins?
Have you also tried using reviveCaps() with no success?
Is your code posted publicly or can you share an excerpt of your PlayerEvent.Clone event?

The information is saved if I add listeners in AttachCapabilitiesEvent:

event.addCapability(key, provider);
event.addListener(provider.capOptional::invalidate);

However, I get NullPointerException when going from dimension to dimension at this line in EntityJoinWorldEvent.

player.getCapability(MistCaps.CAPABILITY_MIST).orElseThrow(() -> new NullPointerException("Player has no mist capability"));

If I do not add listeners, then everything works fine when changing the dimension, but the information is not saved when I restart the game.

Of course I have tried using reviveCaps(). No result.

Sorry, I don't speak English very well...

Posted (edited)

But how can I save information when restarting the game?

I use this example:

https://github.com/VazkiiMods/Botania/blob/0c1138252901ea646f6f97f9427f62ccd258e9d3/src/main/java/vazkii/botania/common/capability/SimpleCapProvider.java#L42

public class SimpleCapProvider<C extends INBTSerializable<CompoundTag>> implements ICapabilityProvider, INBTSerializable<CompoundTag> {

	private final C instance;
	private final LazyOptional<C> capOptional;
	private final Capability<C> capa;
	
	public SimpleCapProvider(Capability<C> capa, NonNullSupplier<C> instance) {
		this.capa = capa;
		this.instance = instance.get();
		this.capOptional = LazyOptional.of(instance);
	}

	@Nonnull
	@Override
	public <T> LazyOptional<T> getCapability(@Nonnull Capability<T> cap, @Nullable Direction side) {
		return capa.orEmpty(cap, capOptional);
	}

	@Override
	public CompoundTag serializeNBT() {
		return this.instance.serializeNBT();
	}

	@Override
	public void deserializeNBT(CompoundTag nbt) {
		this.instance.deserializeNBT(nbt);
	}

	public static <C extends INBTSerializable<CompoundTag>> void attach(AttachCapabilitiesEvent<?> event, ResourceLocation key, Capability<C> cap, NonNullSupplier<C> capInstance) {
		SimpleCapProvider<C> provider = new SimpleCapProvider<>(cap, capInstance);
		event.addCapability(key, provider);
		//event.addListener(provider.capOptional::invalidate); //TODO Capa invalidate
	}
}

And this is my capa registration

public class MistCaps {

	@CapabilityInject(IMistCapaHandler.class)
	public static Capability<IMistCapaHandler> CAPABILITY_MIST;

	@CapabilityInject(ISkillCapaHandler.class)
	public static Capability<ISkillCapaHandler> CAPABILITY_SKILL;

	@CapabilityInject(IFoodHandler.class)
	public static Capability<IFoodHandler> CAPABILITY_FOOD;

	private static final List<CapaEntry<? extends INBTSerializable<CompoundTag>>> capaList = Lists.newArrayList();

	public static void init(RegisterCapabilitiesEvent event) {
		register(event, "player_capa", IMistCapaHandler.class, () -> CAPABILITY_MIST, MistCapaHandler::new);
		register(event, "skill_capa", ISkillCapaHandler.class, () -> CAPABILITY_SKILL, SkillCapaHandler::new);
		register(event, "food_capa", IFoodHandler.class, () -> CAPABILITY_FOOD, FoodCapaHandler::new);
		Mist.LOGGER.info("Misty caps has been registered!");
	}

	//////////////////////////////////////////////////////// MAGIC ////////////////////////////////////////////////////////

	private static <C extends INBTSerializable<CompoundTag>> void register(RegisterCapabilitiesEvent event, String name, Class<C> clazz, NonNullSupplier<Capability<C>> capa, NonNullSupplier<C> instance) {
		event.register(clazz);
		capaList.add(new CapaEntry<C>(Mist.resLoc(name), capa, instance));
	}

	@SubscribeEvent
	public void attachCapabilitiesPlayer(AttachCapabilitiesEvent<Entity> event) {
		if (event.getObject() instanceof Player) capaList.forEach(entry -> attach(event, entry));
	}

	@SubscribeEvent
	public void cloneCapabilitiesEvent(PlayerEvent.Clone event) {
		capaList.forEach(entry -> clone(event, entry));
	}

	private static <C extends INBTSerializable<CompoundTag>> void attach(AttachCapabilitiesEvent<?> event, CapaEntry<C> entry) {
		SimpleCapProvider.attach(event, entry.res, entry.capa.get(), entry.instance);
		if (event.getCapabilities().get(entry.res) == null) {
			Mist.LOGGER.error("Player didn't attach [" + entry.capa.get().getName() + "] capa");
		} else Mist.LOGGER.info("Player has attached [" + entry.capa.get().getName() + "] capa");
	}

	private static <C extends INBTSerializable<CompoundTag>> void clone(PlayerEvent.Clone event, CapaEntry<C> entry) {
		try {
			//event.getOriginal().reviveCaps();
			C original = event.getOriginal().getCapability(entry.capa.get()).orElseThrow(NullPointerException::new);
			CompoundTag nbt = original.serializeNBT();
			C clone = event.getPlayer().getCapability(entry.capa.get()).orElseThrow(NullPointerException::new);
			clone.deserializeNBT(nbt);
			//event.getOriginal().invalidateCaps();
		} catch (Exception e) {
			Mist.LOGGER.error("Could not clone capability [" + entry.capa.get().getName() + "] when player [" + event.getOriginal().getName() + "] changing dimensions");
		}
	}

	private static class CapaEntry<C extends INBTSerializable<CompoundTag>> {

		private final ResourceLocation res;
		private final NonNullSupplier<C> instance;
		private final NonNullSupplier<Capability<C>> capa;
		
		public CapaEntry(ResourceLocation res, NonNullSupplier<Capability<C>> capa, NonNullSupplier<C> instance) {
			this.res = res;
			this.instance = instance;
			this.capa = capa;
		}
	}
}

 

Edited by Liahim

Sorry, I don't speak English very well...

Posted
1 hour ago, Liahim said:

But how can I save information when restarting the game?

you still save infos, in your SimpleCapProvider via the implementation of INBTSerializable (#serializeNBT and #deserializeNBT)

Posted (edited)
8 hours ago, Luis_ST said:

you still save infos, in your SimpleCapProvider via the implementation of INBTSerializable (#serializeNBT and #deserializeNBT)

But capa didn't save during restarting the game.

7 hours ago, Danny and Son said:

Is your init function being called? It doesn't look like your subscribing to RegisterCapabilitiesEvent.

All events are subscribed in my main class. As I wrote in this message 

All methods work fine and information is saved when I use event.addListener(provider.capOptional::invalidate); 

But I am getting NullPointerException when going from dimension to dimension when trying to get a cap on the EntityJoinWorldEvent.

Edited by Liahim

Sorry, I don't speak English very well...

Posted
23 hours ago, Liahim said:
		register(event, "player_capa", IMistCapaHandler.class, () -> CAPABILITY_MIST, MistCapaHandler::new);
		register(event, "skill_capa", ISkillCapaHandler.class, () -> CAPABILITY_SKILL, SkillCapaHandler::new);
		register(event, "food_capa", IFoodHandler.class, () -> CAPABILITY_FOOD, FoodCapaHandler::new);

show one of these CapaHandlers (the implementation not the interface)

Posted
public class MistCapaHandler extends ItemStackHandler implements IMistCapaHandler {

	private Player player;
	private int pollution;
	private int toxic;

	public MistCapaHandler() {}

	// ...

	@Override
	public CompoundTag serializeNBT() {
		CompoundTag nbt = super.serializeNBT();
		nbt.putInt("Pollution", this.pollution);
		nbt.putInt("Toxic", this.toxic);
		return nbt;
	}

	@Override
	public void deserializeNBT(CompoundTag nbt) {
		super.deserializeNBT(nbt);
		this.pollution = nbt.getInt("Pollution");
		this.toxic = nbt.getInt("Toxic");
	}
 
    // ...
}

 

Sorry, I don't speak English very well...

Posted

I fixed it!
The point was that I worked with different instances of the cap in this place:

public SimpleCapProvider(Capability<C> capa, NonNullSupplier<C> instance) {
	this.capa = capa;
	this.instance = instance.get();
	this.capOptional = LazyOptional.of(instance); // <-----------
}

But this is correct:

public SimpleCapProvider(Capability<C> capa, NonNullSupplier<C> instance) {
	this.capa = capa;
	this.instance = instance.get();
	this.capOptional = LazyOptional.of(() -> this.instance); // <-----------
}

Sorry, I don't speak English very well...

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.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Announcements



  • Recently Browsing

    • No registered users viewing this page.
  • Posts

    • This is the last line before the crash: [ebwizardry]: Synchronising spell emitters for PixelTraveler But I have no idea what this means
    • What in particular? I barely used that mod this time around, and it's never been a problem in the past.
    • Im trying to build my mod using shade since i use the luaj library however i keep getting this error Reason: Task ':reobfJar' uses this output of task ':shadowJar' without declaring an explicit or implicit dependency. This can lead to incorrect results being produced, depending on what order the tasks are executed. So i try adding reobfJar.dependsOn shadowJar  Could not get unknown property 'reobfJar' for object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler. my gradle file plugins { id 'eclipse' id 'idea' id 'maven-publish' id 'net.minecraftforge.gradle' version '[6.0,6.2)' id 'com.github.johnrengelman.shadow' version '7.1.2' id 'org.spongepowered.mixin' version '0.7.+' } apply plugin: 'net.minecraftforge.gradle' apply plugin: 'org.spongepowered.mixin' apply plugin: 'com.github.johnrengelman.shadow' version = mod_version group = mod_group_id base { archivesName = mod_id } // Mojang ships Java 17 to end users in 1.18+, so your mod should target Java 17. java.toolchain.languageVersion = JavaLanguageVersion.of(17) //jarJar.enable() println "Java: ${System.getProperty 'java.version'}, JVM: ${System.getProperty 'java.vm.version'} (${System.getProperty 'java.vendor'}), Arch: ${System.getProperty 'os.arch'}" minecraft { mappings channel: mapping_channel, version: mapping_version copyIdeResources = true runs { configureEach { workingDirectory project.file('run') property 'forge.logging.markers', 'REGISTRIES' property 'forge.logging.console.level', 'debug' arg "-mixin.config=derp.mixin.json" mods { "${mod_id}" { source sourceSets.main } } } client { // Comma-separated list of namespaces to load gametests from. Empty = all namespaces. property 'forge.enabledGameTestNamespaces', mod_id } server { property 'forge.enabledGameTestNamespaces', mod_id args '--nogui' } gameTestServer { property 'forge.enabledGameTestNamespaces', mod_id } data { workingDirectory project.file('run-data') args '--mod', mod_id, '--all', '--output', file('src/generated/resources/'), '--existing', file('src/main/resources/') } } } sourceSets.main.resources { srcDir 'src/generated/resources' } repositories { flatDir { dirs './libs' } maven { url = "https://jitpack.io" } } configurations { shade implementation.extendsFrom shade } dependencies { minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}" implementation 'org.luaj:luaj-jse-3.0.2' implementation fg.deobf("com.github.Virtuoel:Pehkui:${pehkui_version}") annotationProcessor 'org.spongepowered:mixin:0.8.5:processor' minecraftLibrary 'luaj:luaj-jse:3.0.2' shade 'luaj:luaj-jse:3.0.2' } // Example for how to get properties into the manifest for reading at runtime. tasks.named('jar', Jar).configure { manifest { attributes([ 'Specification-Title' : mod_id, 'Specification-Vendor' : mod_authors, 'Specification-Version' : '1', // We are version 1 of ourselves 'Implementation-Title' : project.name, 'Implementation-Version' : project.jar.archiveVersion, 'Implementation-Vendor' : mod_authors, 'Implementation-Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"), "TweakClass" : "org.spongepowered.asm.launch.MixinTweaker", "TweakOrder" : 0, "MixinConfigs" : "derp.mixin.json" ]) } rename 'mixin.refmap.json', 'derp.mixin-refmap.json' } shadowJar { archiveClassifier = '' configurations = [project.configurations.shade] finalizedBy 'reobfShadowJar' } assemble.dependsOn shadowJar reobf { re shadowJar {} } publishing { publications { mavenJava(MavenPublication) { artifact jar } } repositories { maven { url "file://${project.projectDir}/mcmodsrepo" } } } my entire project:https://github.com/kevin051606/DERP-Mod/tree/Derp-1.0-1.20
    • All versions of Minecraft Forge suddenly black screen even without mods (tried reinstalling original Minecraft, Java, updating drivers doesn't work)
  • Topics

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.