Jump to content

[1.14.4] How do I use LazyOptional<T> return in getCapability function


wog890

Recommended Posts

I've just recently got back into minecraft programming again and was after following the usual, make a block, make an item, make tile entity, etc I started looking at capabilities. I followed a fairly simple tutorial for adding mana to a player. I am finding very little information on this though for 1.14, but have managed to get everything functioning (haven't been able to run it to ensure this in minecraft though) except for the Capability Provider. It seems the old function used to be

 

@CapabilityInject(IMana.class)
public static final Capability<IMana> MANA_CAP = null;

public <T> T getCapability(Capability<T> capability, EnumFacing facing) {
  return capability == MANA_CAP ? MANA_CAP.<T> cast(this.instance) : null;
}

I'm leaving out some stuff, but this function has changed to

 

public <T> LazyOptional<T> getCapability(Capability<T> capability, Direction side) {
  return ???
}

Anyways, I don't really understand what I'm messing with here and would appreciate it if anyone could assist me in the correct manner to return an instance of IMana properly here. I messed with a few options and tried to implement things from other's code but to no avail. The entire ManaProvider class is below. If other files are needed please let me know.

 

ManaProvider.java

public class ManaProvider implements ICapabilitySerializable<INBT> {

	@CapabilityInject(IMana.class)
	public static final Capability<IMana> MANA_CAP = null;
	
	private static final LazyOptional<IMana> holder = LazyOptional.of(Mana::new);
	
	private IMana instance = MANA_CAP.getDefaultInstance();
	
	public boolean hasCapability(Capability<?> capability, Direction side) {
		return capability == MANA_CAP;
	}
	
	@Override
    public <T> LazyOptional<T> getCapability(Capability<T> capability, Direction side) {
		//return capability == MANA_CAP ? MANA_CAP.<T> cast(this.instance) : null;
		//return MANA_CAP.orEmpty(MANA_CAP, holder.cast());
		return capability == MANA_CAP ? holder.cast() : LazyOptional.empty();
    }

	@Override
	public INBT serializeNBT() {
		return MANA_CAP.getStorage().writeNBT(MANA_CAP, this.instance, null);
	}

	@Override
	public void deserializeNBT(INBT nbt) {
		MANA_CAP.getStorage().readNBT(MANA_CAP, this.instance, null, nbt);
	}
	
}

 

Follow up and not particularly relevant question. What has happened to proxies??? The section of the forge documentation for them seems to be replaced by DistExecuter, but I can't find much info on this. Thank you for any help or information!

Link to comment
Share on other sites

1 hour ago, diesieben07 said:

If you really need to access side-only code from common code directly, use DistExecutor, which can be used to safely run pieces of code for a particular side.

Oh huh. I may have to look into this. I do have a spot where I had to split things by side, but that might be nicer.

Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable.  If you think this is the case, JUST REPORT ME.  Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice.

 

Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked.

 

DO NOT PM ME WITH PROBLEMS. No help will be given.

Link to comment
Share on other sites

14 minutes ago, diesieben07 said:

Only use it if you absolutely must. It's a crutch and only works properly as long as the JVM and JDK implementation of lambdas doesn't change and you use it in a very specific way.

I'm already using a DistExecutor for setting up a proxy, so I guess that's fine?

Anyway, it relates back to having to spawn TileEntities in a World so that I can query their IItemHandlers: on the client side I need a FakeWorld (as it only comes up when the user is in the GUI) and on the ServerSide I can use a Dimension (where things can be semi-persistent for efficiency during ticking).

Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable.  If you think this is the case, JUST REPORT ME.  Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice.

 

Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked.

 

DO NOT PM ME WITH PROBLEMS. No help will be given.

Link to comment
Share on other sites

5 minutes ago, diesieben07 said:

99% of use-cases for proxies are covered by side-only event handlers (@EventBusSubscriber with Dist argument).

As for your world problem, that is a logical side question, which is still handled via World#isRemote. DistExecutor works on Dist(ribution) basis (client & dedicated server), not logical side.

I'll review what I've got then. Its possible that I can squish things back into a world-remote check, haven't looked at the code recently.

Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable.  If you think this is the case, JUST REPORT ME.  Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice.

 

Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked.

 

DO NOT PM ME WITH PROBLEMS. No help will be given.

Link to comment
Share on other sites

9 hours ago, diesieben07 said:

You are creating two instances of your mana capability, one with the LazyOptional and one by calling getDefaultInstance. You should only have one.

The way you are using LazyOptional is correct, just get rid of your instance field.

 

You pretty much do not need them anymore (even in later versions of 1.12.2), most use-cases are better fitted by side-only event handlers (@EventBusSubscriber with a Dist parameter).

If you really need to access side-only code from common code directly, use DistExecutor, which can be used to safely run pieces of code for a particular side.

 

So, I made some edits to the code below are my updated ManaProvider class and the EventHandler (it is now causing problems)

 

ManaProvider.java

public class ManaProvider implements ICapabilitySerializable<INBT> {

	@CapabilityInject(IMana.class)
	public static final Capability<IMana> MANA_CAP = null;
	
	private static final LazyOptional<IMana> holder = LazyOptional.of(Mana::new);
	
	public boolean hasCapability(Capability<?> capability, Direction side) {
		return capability == MANA_CAP;
	}
	
	@Override
	public <T> LazyOptional<T> getCapability(Capability<T> capability, Direction side) {
		//return capability == MANA_CAP ? MANA_CAP.<T> cast(this.instance) : null;
		//return MANA_CAP.orEmpty(MANA_CAP, holder.cast());
		return capability == MANA_CAP ? holder.cast() : LazyOptional.empty();
    }

	@Override
	public INBT serializeNBT() {
		//return MANA_CAP.getStorage().writeNBT(MANA_CAP, (IMana) ManaProvider.holder, null);
		return null;
	}

	@Override
	public void deserializeNBT(INBT nbt) {
		//MANA_CAP.getStorage().readNBT(MANA_CAP, (IMana) ManaProvider.holder, null, nbt);
	}
	
}

 

EventHandler.java

public class EventHandler {

	@SubscribeEvent
	public void onPlayerLogsIn(PlayerLoggedInEvent event) {
		PlayerEntity player = event.getPlayer();
		IMana mana = (IMana) player.getCapability(ManaProvider.MANA_CAP, null);
		
		String message = String.format("Hello there, you have §7%d§r mana left", (int) mana.getMana());
		player.sendMessage(new StringTextComponent(message));
	}
	
	@SubscribeEvent
	public void onPlayerSleep(PlayerSleepInBedEvent event) {
		PlayerEntity player = event.getEntityPlayer();
		
		if (player.world.isRemote) return;
		
		IMana mana = (IMana) player.getCapability(ManaProvider.MANA_CAP, null);
		
		mana.fill(50);
		String message = String.format("You refreshed yourself in the bed. You received 50 mana, you have §7%d§r mana left.", (int) mana.getMana());
		player.sendMessage(new StringTextComponent(message));
	}
	
	@SubscribeEvent
	public void onPlayerFalls(LivingFallEvent event) {
		Entity entity = event.getEntity();
		
		if (entity.world.isRemote || !(entity instanceof ServerPlayerEntity) || event.getDistance() < 3) return;
		
		PlayerEntity player = (PlayerEntity) entity;
		IMana mana = (IMana) player.getCapability(ManaProvider.MANA_CAP, null);
		
		float points = mana.getMana();
		float cost = event.getDistance() * 2;
		
		if (points > cost) {
			mana.consume(cost);
			String message = String.format("You absorbed fall damage. It cost §7%d§r mana, you have §7%d§r mana left.", (int) cost, (int) mana.getMana());
			player.sendMessage(new StringTextComponent(message));
			event.setCanceled(true);
		}
	}
	
}

 

I have removed the instance field and am using the handler field in the functions serializeNBT and deserializeNBT. As it is a static field I am having to access it through the class instead of this and as the function requires an instance of IMana and not LazyOptional, I'm having to cast it to IMana.

 

(Side note, I'm pretty sure handler needs to not be static if I'm using it this way as I don't want NBT to be saved to a static, it should be individual......)

 

When I ran this, the game crashed, and I thought the error log was saying IMana could not be cast to LazyOptional. Then I read better and realized it was erroring in the function deserializeNBT and actually said LazyOptional couldn't be cast to IMana. To try and simply find out if the getCapability function was working so I commented out the code in deserializeNBT and serializeNBT functions and had serializeNBT return null for now. (This is visible above).

 

When I ran the game again, EventHandler onPlayerLogsIn caused the game to crash with the same error. LazyOptional cannot be cast to IMana. (Error below). I'm assuming the code should be updated from

IMana mana = (IMana) player.getCapability(ManaProvider.MANA_CAP, null);

to

LazyOptional<IMana> mana = player.getCapability(ManaProvider.MANA_CAP, null);

 

but then I seem to lose access to the functions within IMana...... i seriously do not understand what LazyOptional is doing. And I still have the issue of how to provide serializeNBT and deserializeNBT with an instance of IMana that is not LazyOptional. Thank you for the assistance, it is extremely appreciated.

 

 

---- Minecraft Crash Report ----
// Why is it breaking :(

Time: 9/3/19 5:14 PM
Description: Ticking memory connection

java.lang.ClassCastException: net.minecraftforge.common.util.LazyOptional cannot be cast to com.github.wog890.wogmods.entity.player.IMana
	at com.github.wog890.wogmods.entity.player.EventHandler.onPlayerLogsIn(EventHandler.java:17) ~[main/:?] {}

 

Link to comment
Share on other sites

28 minutes ago, diesieben07 said:

Yes, you can't just cast LazyOptional to IMana.

Your LazyOptional should not be a static field, unless you want the capability to be the same for all players.

 

Please learn basic Java concepts (static fields, type casting) before writing a mod.

Thank you for responding, but the rudeness is not overly constructive. I believe my question above already showed I have a decent understanding of why static would be incorrect here, I was simply trying to double check myself as I normally program in Javascript and don't deal with static fields commonly.

 

On the subject of type casting I certainly don't understand what is going on. I understand casting, but do not understand what is being created by the type LazyOptional and the proper way to use both as for one function I need a LazyOptional and for another I need IMana and you've said to only have a single field..... I so far have not found solid information on LazyOptional to fix this lack of understanding, but I'm still working on it.

 

As I've said I appreciate any help, but if all you have is rudeness and not help, please just do not respond.

Link to comment
Share on other sites

16 minutes ago, wog890 said:

LazyOptional

This is an Object that stores a value optionally. IE it could be there, but it could also not be there. It also has some methods that help with this kinda thing as well. Basically it contains the instance of your data that the Capability holds. You can get an IMana instance from methods inside the LazyOptional if you give it a look you'll find them,

VANILLA MINECRAFT CLASSES ARE THE BEST RESOURCES WHEN MODDING

I will be posting 1.15.2 modding tutorials on this channel. If you want to be notified of it do the normal YouTube stuff like subscribing, ect.

Forge and vanilla BlockState generator.

Link to comment
Share on other sites

  • 4 months later...
On 9/3/2019 at 4:30 PM, wog890 said:

When I ran the game again, EventHandler onPlayerLogsIn caused the game to crash with the same error. LazyOptional cannot be cast to IMana. (Error below). I'm assuming the code should be updated from


IMana mana = (IMana) player.getCapability(ManaProvider.MANA_CAP, null);

to


LazyOptional<IMana> mana = player.getCapability(ManaProvider.MANA_CAP, null);

 

but then I seem to lose access to the functions within IMana...... i seriously do not understand what LazyOptional is doing. And I still have the issue of how to provide serializeNBT and deserializeNBT with an instance of IMana that is not LazyOptional. Thank you for the assistance, it is extremely appreciated.

 

Definitely late to the party, but I've spent the past couple days working on learning how to use capabilities too. I finally got a pretty basic capability working, so I hope this helps.

 

Like said earlier, casting from LazyOptional doesn't work. After some digging, it turns out you can still access your functions by using a lambda expression. First post here, so I can't figure out how to format my code into something readable here, so here's the link to my repository.

https://github.com/lolzorrior/SupernaturalMod-1.15.2/blob/master/src/main/java/com/lolzorrior/supernaturalmod/ForgeEventSubscriber.java

 

Take a look at the onPlayerLogsIn function. Essentially, you use the LazyOptionals ifPresent function and then use a lambda function from there. Feel free to take a look at my PowerProvider.class to see how I handled the NBT functions.

  • Like 1
Link to comment
Share on other sites

  • 3 weeks later...
On 2/3/2020 at 3:10 AM, diesieben07 said:

Only use ifPresent if you actually expect a situation where the capability could be absent. If you attach it to every player then it's an error for it not to be there and you should use orElseThrow.

That makes a lot of sense design-wise, thank you!

Link to comment
Share on other sites

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



×
×
  • Create New...

Important Information

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