Jump to content

[Solved][1.15.2] Adding elemental properties to LivingEntities


Recommended Posts

Posted (edited)

Hello, people who came here from google.

You can find a summary at the end of this post. Basically what i wanted to do, was to add public variables to an entity using a capability and then give it default values through json files. These default values can also be changed through the use of a datapack. This method should in theory also work with itemstacks or other things.

-----------------------------------------

Hello community,
 

I recently started with my first minecraft mod. I usually programm in c++, but I wanted to try something new. The whole Idea of my mod is to give every LivingEntity elemental properties for defence and attack. While the player can equip the right gear for the job, most mobs will have set weaknesses/resistance and may have certain elemental attacks. For example a Blaze will naturally be prone to ice attacks, while his projectiles obviously have the element 'fire'. I want to give each mobs these properties through the 'Capabilities'-system. Every LivingEntity should have 4 lists, which should be filled by loading data from .json files. This will make adding mobs from other mods/newer minecraft versions much easier, I hope. So how can I do that? For example the zombie_elemetalproperties.json file looks like this right now:

{
	"weakness": [
		"fire"
	],
	
	"resistance": [
		"ice",
		"water"
	],
	
	"absorb": [
	],
	
	"wall": [
		"thunder"
	]
}

So some of the list can be empty and some can have multiple values.

My best guess is, that I use the 'compability'-system, which is why I have the ElementalCombatData class and the appropiate interface:

package Tavi007.ElementalCombat.capabilities;

import java.util.List;

public class ElementalCombatData implements IElementalCombatData
{
	private List<String> weaknessList = null;
	private List<String> resistanceList = null;
	private List<String> wallList = null;
	private List<String> absorbList = null;
	
	
	// Setter
	@Override
	public void setWeaknessList(List<String> elementList)
	{
		this.weaknessList = elementList;
	}
	@Override
	public void setResistanceList(List<String> elementList)
	{
		this.resistanceList = elementList;
	}
	@Override
	public void setWallList(List<String> elementList)
	{
		this.wallList = elementList;
	}
	@Override
	public void setAbsorbList(List<String> elementList)
	{
		this.absorbList = elementList;
	}
	
	
	//Getter
	@Override
	public List<String> getWeaknessList()
	{
		return this.weaknessList;
	}
	@Override
	public List<String> getResistanceList()
	{
		return this.resistanceList;
	}
	@Override
	public List<String> getWallList()
	{
		return this.wallList;
	}
	@Override
	public List<String> getAbsorbList()
	{
		return this.absorbList;
	}
}

 

Once I know how I get one of these list to work, I can adjust the rest.

Now I also have the capability-class:

package Tavi007.ElementalCombat.capabilities;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import net.minecraft.nbt.ListNBT;
import net.minecraft.util.Direction;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.CapabilityInject;
import net.minecraftforge.common.capabilities.CapabilityManager;
import net.minecraftforge.common.capabilities.ICapabilitySerializable;
import net.minecraftforge.common.util.LazyOptional;

public class ElementalCombatDataCapability implements ICapabilitySerializable<ListNBT> 
{

    @CapabilityInject(IElementalCombatData.class)
    public static final Capability<IElementalCombatData> DATA_CAPABILITY = null;
    private LazyOptional<IElementalCombatData> instance = LazyOptional.of(DATA_CAPABILITY::getDefaultInstance);

    public static void register()
    {
        CapabilityManager.INSTANCE.register(IElementalCombatData.class, new ElementalCombatDataStorage(), ElementalCombatData::new);
    }

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

    @Override
    public ListNBT serializeNBT() 
    {
        return (ListNBT) DATA_CAPABILITY.getStorage().writeNBT(DATA_CAPABILITY, instance.orElseThrow(() -> new IllegalArgumentException("LazyOptional cannot be empty!")), null);
    }

    @Override
    public void deserializeNBT(ListNBT nbt) 
    {
    	DATA_CAPABILITY.getStorage().readNBT(DATA_CAPABILITY, instance.orElseThrow(() -> new IllegalArgumentException("LazyOptional cannot be empty!")), null, nbt);
    }
}

Granted, I only copied it from another thread, that I found through google, which I modified a little bit. I'm not sure, if I did everything correct so far. So please correct me, if you found anything. :)
 

My problem right now lies within the storage-class, where the readNBT and writeNBT function reside. Right now it looks like this:

package Tavi007.ElementalCombat.capabilities;

import javax.annotation.Nullable;

import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.INBT;
import net.minecraft.nbt.JsonToNBT;
import net.minecraft.util.Direction;
import net.minecraftforge.common.capabilities.Capability;

public class ElementalCombatDataStorage implements Capability.IStorage<IElementalCombatData> 
{
    @Nullable
    @Override
    public INBT writeNBT(Capability<IElementalCombatData> capability, IElementalCombatData instance, Direction side) 
    {
    	CompoundNBT tag = JsonToNBT.getTagFromJson(jsonString)
    	
    	return tag;
    }

    @Override
    public void readNBT(Capability<IElementalCombatData> capability, IElementalCombatData instance, Direction side, INBT nbt) 
    {
        if (!(instance instanceof ElementalCombatData))
            throw new IllegalArgumentException("Can not deserialize to an instance that isn't the default implementation");

        
    }
}

What do I have to enter for jsonString and what do I have to do in the read-function? Also what is 'Direction' supposed to mean?

 

Thanks in advance :)

 

SOLUTION:
First of, here is my repo.

The whole problem can be divided into 2 main problems: the capability and the loading. First of the capability. I used 4 classes: an interface and its implementation of the data you want, a storage function, that implements Capability.IStorage<your interface> and the capability class. Note, that you can change the compoundNBT to whatever NBT type you need. I choose compound, because it is basically the same as the json format. I hope the comments in these files describes enought for you. Also don't forget to register the capability at the FMLCommonSetupEvent and attach them to the entity/itemstack/whatever. To use the capability in an event, you can use 2 different method. Check out some of my events.

For the loading you need a class with the same structure as your json file. Even the variable names need to be same (I think). I assume you could use the same class as for the capability, but in my case that was not possible. Next you need a class, that extends the JsonReloadListener. This thing, once registered, will fire everytime data needs to be loaded (i.e. using the /reload command in minecraft). This class now holds a mapping of the resourceLocation to your data. This map can be used to access the loaded data. You also need a a class, which handles the overriding of data through datapacks (I think. A lot of guesses from my side, cause I'm also new to forge). Again don't forget to register your reloadListener at FMLServerAboutToStartEvent (see my main class)

 

I hope I could help you :D

Edited by Tavi007
Posted
2 hours ago, diesieben07 said:

I'm not sure where you got this from, but it doesn't make much sense her.eIn the write method you have to serialize all your data into NBT and return that. In read you have to read it back in.

Ah, yeah. That was me testing.

 

2 hours ago, diesieben07 said:

A capability provider can expose different capabilities on each direction. This is mostly useful for blocks, but the player entity for example exposes the armor and other inventories using specific Direction values as a convention. null can usually be used.

Can you elaborate on how to handle NBT data or link me to some explanation, please? I would like to know, how to handle this properly.

Posted

I've been reading through the docs and used google a bit more. I do understand now, what I have to do, but I have trouble with actual writing the code. What I have so far:
 

package Tavi007.ElementalCombat.capabilities;

import java.util.ArrayList;
import java.util.List;

import javax.annotation.Nullable;

import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.INBT;
import net.minecraft.nbt.ListNBT;
import net.minecraft.util.Direction;
import net.minecraftforge.common.capabilities.Capability;

public class ElementalDefenceDataStorage implements Capability.IStorage<IElementalDefenceData> 
{
    @Nullable
    @Override
    public INBT writeNBT(Capability<IElementalDefenceData> capability, IElementalDefenceData instance, Direction side) 
    {
    	List<String> weaknessList = instance.getWeaknessList();
    	
    	//fill nbt with data
    	CompoundNBT nbt = new CompoundNBT();
    	ListNBT weaknessNBTList = new ListNBT();
    	
    	nbt.put("elem_weakness", weaknessNBTList);
    	
    	return nbt;
    }

    @Override
    public void readNBT(Capability<IElementalDefenceData> capability, IElementalDefenceData instance, Direction side, INBT nbt) 
    {
        if (!(instance instanceof ElementalDefenceData))
            throw new IllegalArgumentException("Can not deserialize to an instance that isn't the default implementation");

        List<String> weaknessList = new ArrayList<String>();
        
        //fill list with nbt data
        CompoundNBT cnbt = (CompoundNBT)nbt;
        
        
        instance.setWeaknessList(weaknessList);
    }
}

How do I get the Information from List<String> to ListNBT and vice versa? Also I'm not sure, if I even need cnbt. I do understand the difference of the NBT Formats, but I have trouble using them in Java...

 

Posted

I tried to do, what you told me. But I still have 2 remaining problems. First my code again:

package Tavi007.ElementalCombat.capabilities;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.annotation.Nullable;

import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.INBT;
import net.minecraft.nbt.ListNBT;
import net.minecraft.nbt.StringNBT;
import net.minecraft.util.Direction;
import net.minecraftforge.common.capabilities.Capability;

public class ElementalDefenceDataStorage implements Capability.IStorage<IElementalDefenceData> 
{
    @Nullable
    @Override
    public INBT writeNBT(Capability<IElementalDefenceData> capability, IElementalDefenceData instance, Direction side) 
    {
    	List<String> weakList = instance.getWeaknessList();
    	
    	//fill nbt with data
    	CompoundNBT nbt = new CompoundNBT();
    	nbt.put("elem_weak", fromListToNBT(weakList));
    	
    	return nbt;
    }

    @Override
    public void readNBT(Capability<IElementalDefenceData> capability, IElementalDefenceData instance, Direction side, INBT nbt) 
    {
        if (!(instance instanceof ElementalDefenceData))
            throw new IllegalArgumentException("Can not deserialize to an instance that isn't the default implementation");
        
        //fill lists with nbt data
        CompoundNBT nbtCompound = (CompoundNBT)nbt;
        instance.setWeaknessList(  fromNBTToList(nbtCompound.getList("elem_weak", ????)));
    }
    
    private List<String> fromNBTToList(ListNBT nbt)
    {
    	List<String> list = new ArrayList<String>();
    	Iterator<INBT> iterator = nbt.iterator(); 
    	while (iterator.hasNext()) 
    	{
    		list.add(iterator.toString());
    	}
    	return list;
    }
    
    private ListNBT fromListToNBT(List<String> list)
    {
    	ListNBT nbt = new ListNBT();
    	Iterator<String> iterator = list.iterator(); 
    	while (iterator.hasNext()) 
    	{
    		String s = iterator.toString();
    		StringNBT snbt = new StringNBT(s);
    		
    		nbt.add(snbt);
    	}
    	return nbt;
    }
}

 

When I use nbtCompound.getList, it expects an Integer. I thought, that the key string (so "elem_weak", correct?) would be enough to get all corresponding values. So what does this Integer do?

My second problem is with StringNBT. I can't get the constructor to work properly. Even a simple new StringNBT() did not work. I also somehowc can't look at the source code (but I could import it...). Is my project setup incorrectly?

 

Hope you can help me out one more time :)

Posted (edited)

I finally got my code to compile, but it crashes, whenever I try to play a world due to a NullPointerException. Here is the crash report:

---- Minecraft Crash Report ----
// Daisy, daisy...

Time: 07.06.20 17:37
Description: Ticking memory connection

java.lang.NullPointerException: Ticking memory connection
	at Tavi007.ElementalCombat.capabilities.ElementalDefenceDataCapability.<init>(ElementalDefenceDataCapability.java:20) ~[main/:?] {re:classloading,pl:capability_inject_definalize:A}
	at Tavi007.ElementalCombat.ElementalCombatForgeEventBusSub.onAttachEntity(ElementalCombatForgeEventBusSub.java:19) ~[main/:?] {re:classloading}
	at net.minecraftforge.eventbus.ASMEventHandler_1_ElementalCombatForgeEventBusSub_onAttachEntity_AttachCapabilitiesEvent.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.gatherCapabilities(ForgeEventFactory.java:564) ~[?:?] {re:classloading}
	at net.minecraftforge.event.ForgeEventFactory.gatherCapabilities(ForgeEventFactory.java:558) ~[?:?] {re:classloading}
	at net.minecraftforge.common.capabilities.CapabilityProvider.gatherCapabilities(CapabilityProvider.java:48) ~[?:?] {re:classloading}
	at net.minecraftforge.common.capabilities.CapabilityProvider.gatherCapabilities(CapabilityProvider.java:44) ~[?:?] {re:classloading}
	at net.minecraft.entity.Entity.<init>(Entity.java:221) ~[?:?] {re:classloading,pl:accesstransformer:B}
	at net.minecraft.entity.LivingEntity.<init>(LivingEntity.java:198) ~[?:?] {re:classloading}
	at net.minecraft.entity.player.PlayerEntity.<init>(PlayerEntity.java:163) ~[?:?] {re:classloading,pl:accesstransformer:B}
	at net.minecraft.entity.player.ServerPlayerEntity.<init>(ServerPlayerEntity.java:158) ~[?:?] {re:classloading,pl:accesstransformer:B}
	at net.minecraft.server.management.PlayerList.createPlayerForUser(PlayerList.java:382) ~[?:?] {re:classloading}
	at net.minecraft.network.login.ServerLoginNetHandler.tryAcceptPlayer(ServerLoginNetHandler.java:116) ~[?:?] {re:classloading}
	at net.minecraft.network.login.ServerLoginNetHandler.tick(ServerLoginNetHandler.java:63) ~[?:?] {re:classloading}
	at net.minecraft.network.NetworkManager.tick(NetworkManager.java:224) ~[?:?] {re:classloading}
	at net.minecraft.network.NetworkSystem.tick(NetworkSystem.java:135) ~[?:?] {re:classloading}
	at net.minecraft.server.MinecraftServer.updateTimeLightAndEntities(MinecraftServer.java:866) ~[?:?] {re:classloading,pl:accesstransformer:B}
	at net.minecraft.server.MinecraftServer.tick(MinecraftServer.java:784) ~[?:?] {re:classloading,pl:accesstransformer:B}
	at net.minecraft.server.integrated.IntegratedServer.tick(IntegratedServer.java:114) ~[?:?] {re:classloading,pl:runtimedistcleaner:A}
	at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:637) [?:?] {re:classloading,pl:accesstransformer:B}
	at java.lang.Thread.run(Thread.java:748) [?:1.8.0_251] {}


A detailed walkthrough of the error, its code path and all known details is as follows:
---------------------------------------------------------------------------------------

-- Head --
Thread: Server thread
Stacktrace:
	at Tavi007.ElementalCombat.capabilities.ElementalDefenceDataCapability.<init>(ElementalDefenceDataCapability.java:20)
	at Tavi007.ElementalCombat.ElementalCombatForgeEventBusSub.onAttachEntity(ElementalCombatForgeEventBusSub.java:19)
	at net.minecraftforge.eventbus.ASMEventHandler_1_ElementalCombatForgeEventBusSub_onAttachEntity_AttachCapabilitiesEvent.invoke(.dynamic)
	at net.minecraftforge.eventbus.ASMEventHandler.invoke(ASMEventHandler.java:80)
	at net.minecraftforge.eventbus.EventBus.post(EventBus.java:258)
	at net.minecraftforge.event.ForgeEventFactory.gatherCapabilities(ForgeEventFactory.java:564)
	at net.minecraftforge.event.ForgeEventFactory.gatherCapabilities(ForgeEventFactory.java:558)
	at net.minecraftforge.common.capabilities.CapabilityProvider.gatherCapabilities(CapabilityProvider.java:48)
	at net.minecraftforge.common.capabilities.CapabilityProvider.gatherCapabilities(CapabilityProvider.java:44)
	at net.minecraft.entity.Entity.<init>(Entity.java:221)
	at net.minecraft.entity.LivingEntity.<init>(LivingEntity.java:198)
	at net.minecraft.entity.player.PlayerEntity.<init>(PlayerEntity.java:163)
	at net.minecraft.entity.player.ServerPlayerEntity.<init>(ServerPlayerEntity.java:158)
	at net.minecraft.server.management.PlayerList.createPlayerForUser(PlayerList.java:382)
	at net.minecraft.network.login.ServerLoginNetHandler.tryAcceptPlayer(ServerLoginNetHandler.java:116)
	at net.minecraft.network.login.ServerLoginNetHandler.tick(ServerLoginNetHandler.java:63)
	at net.minecraft.network.NetworkManager.tick(NetworkManager.java:224)

-- Ticking connection --
Details:
	Connection: net.minecraft.network.NetworkManager@1f59faee
Stacktrace:
	at net.minecraft.network.NetworkSystem.tick(NetworkSystem.java:135)
	at net.minecraft.server.MinecraftServer.updateTimeLightAndEntities(MinecraftServer.java:866)
	at net.minecraft.server.MinecraftServer.tick(MinecraftServer.java:784)
	at net.minecraft.server.integrated.IntegratedServer.tick(IntegratedServer.java:114)
	at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:637)
	at java.lang.Thread.run(Thread.java:748)

-- System Details --
Details:
	Minecraft Version: 1.15.2
	Minecraft Version ID: 1.15.2
	Operating System: Windows 10 (amd64) version 10.0
	Java Version: 1.8.0_251, Oracle Corporation
	Java VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation
	Memory: 102800968 bytes (98 MB) / 694681600 bytes (662 MB) up to 954728448 bytes (910 MB)
	CPUs: 2
	JVM Flags: 2 total; -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump -Xmx1024m
	ModLauncher: 5.1.0+69+master.79f13f7
	ModLauncher launch target: fmluserdevclient
	ModLauncher naming: mcp
	ModLauncher services: 
		/eventbus-2.2.0-service.jar eventbus PLUGINSERVICE 
		/forge-1.15.2-31.2.0_mapped_snapshot_20200514-1.15.1-launcher.jar object_holder_definalize PLUGINSERVICE 
		/forge-1.15.2-31.2.0_mapped_snapshot_20200514-1.15.1-launcher.jar runtime_enum_extender PLUGINSERVICE 
		/accesstransformers-2.1.1-shadowed.jar accesstransformer PLUGINSERVICE 
		/forge-1.15.2-31.2.0_mapped_snapshot_20200514-1.15.1-launcher.jar capability_inject_definalize PLUGINSERVICE 
		/forge-1.15.2-31.2.0_mapped_snapshot_20200514-1.15.1-launcher.jar runtimedistcleaner PLUGINSERVICE 
		/forge-1.15.2-31.2.0_mapped_snapshot_20200514-1.15.1-launcher.jar fml TRANSFORMATIONSERVICE 
	FML: 31.2
	Forge: net.minecraftforge:31.2.0
	FML Language Providers: 
		[email protected]
		minecraft@1
	Mod List: 
		client-extra.jar Minecraft {[email protected] DONE}
		forge-1.15.2-31.2.0_mapped_snapshot_20200514-1.15.1.jar Forge {[email protected] DONE}
		main Elemental Combat {[email protected] DONE}
	Player Count: 0 / 8; []
	Data Packs: vanilla, mod:forge (incompatible), mod:elementalcombat
	Type: Integrated Server (map_client.txt)
	Is Modded: Definitely; Client brand changed to 'forge'

 

I tried to get it to work yesterday, but couldn't figure it out. I've been writing this compability like in this repository: https://github.com/soravoid/Exceed/tree/master/src/main/java/com/github/soravoid/exceed

This repository does indeed work (I tested it), but uses forge 31.1.12, while I use 31.2.0. Not sure if this makes a huge difference.

By the way, here is a link to my repository: https://github.com/Tavi007/ElementalCombat.

 

On a completly different note I'm trying to design this mod to be efficient and I was wondering, if I'm on the right path. Basically I want every LivingEntity to have elemental weaknesses/resistance lists as public variables (So I can check them in combat). I also want to set these variables for each entity type (Zombie/Skeleton/Pig/...) once and not every time the entity spawns into the world. I dont't want the game to load up the weakness/resistance list from the json-file everytime the entity spawns, because that seems to be heavy on the computation side...

Edited by Tavi007
Posted
On 6/8/2020 at 2:55 PM, diesieben07 said:

You never call this method as far as I can tell, so your capability is never registered.

Well, now I feel dumb. I think, I'm doing every noobish error there is :D

Anyway, I did a little bit more progress and now I finally have a working capability (I think). The last remaing part, would be the actual loading of the data from a json file. Preferably I would like to load the data from the files once and set them as default values for the different LivingEntities. I.e. load ZombieData.json once at set its content as default values for every spawning zombie entity. Is this somehow possible?

Posted

Oh man, what did I get myself into. Understanding uncommmented code is such a pain, especially with so many classes.

If I understood everything correctly I need a bunch of difference classes. First the actually storage for all the resourceLocation: ElementalDataStorage


 

public class ElementalEntityData
{
	   private static final Set<ResourceLocation> ELEMENTAL_ENTITY_DATA = Sets.newHashSet();
	   public static final ResourceLocation EMPTY = new ResourceLocation(ElementalCombat.MOD_ID, "empty");
	   
	   public ElementalEntityData()
	   {
		   Set<String> modFolderList = Sets.newHashSet();
		   for (final String modFolder : modFolderList)//loop over supported mods 
		   {
			   Set<String> mobFileList = Sets.newHashSet();
			   
			   for (final String mobFile : mobFileList)//loop over supported mobs (not listed mobs will use 'empty' as default)
			   {
				   register("entities/" + modFolder + "/" + mobFile);
			   }
		   }
	   }
	   
	   private static ResourceLocation register(String id) 
	   {
		   return register(new ResourceLocation(ElementalCombat.MOD_ID, id));
	   }

	   private static ResourceLocation register(ResourceLocation id) 
	   {
		   if (ELEMENTAL_ENTITY_DATA.add(id)) 
		   {
			   return id;
		   }
		   else
		   {
			   throw new IllegalArgumentException(id + " is already registered elemental entity data");
		   }
	   }
}

My first problem arises, when i want to find every json-file. My folder hierarchy looks like this: data/entities/<supported mod>/<mobs from mod>, where <supported mod> should be the mod id. For the vanilla mobs, this will be named 'minecraft'. Inside this folder one can find the json-file for a mob, i.e zombie_elementalproperties.json. Now I would like to find every file, that uses this directory format. This should hopefully make adding support for other mods easier later on. But how can I find modFolderList and mobFile?

After ElementalEntityData is done, I need to create the class ElementalDataManager, which extends JsonReloadListener just like LootTableManager or RecipeManager. If I understood it correctly, this class handles the json-object, that got loaded with the help of ElementalEntityData. Could you give me some insight on the function apply(), please? I don't fully understand, what it is doing.

Posted
56 minutes ago, Tavi007 said:

Set<String> modFolderList = Sets.newHashSet();
for (final String modFolder : modFolderList)//loop over supported mods 
{
    Set<String> mobFileList = Sets.newHashSet();
    for (final String mobFile : mobFileList)//loop over supported mobs (not listed mobs will use 'empty' as default)
    {
      ...

 

You create a new (empty) set, then loop through it. Twice.

Neither of your loops will do anything, as they are both always going to be empty when the loop is run.

 

58 minutes ago, Tavi007 said:

My folder hierarchy looks like this: data/entities/<supported mod>/<mobs from mod>, where <supported mod> should be the mod id. For the vanilla mobs, this will be named 'minecraft'.

I would suggest including your modid in the path, to stop there being conflicts.

I would also not limit it to "supported" mods. That way support for third party mods can be added by datapacks (or even included in those mods if your mod becomes popular enough).

I would recommend using a path such as data/<datapackid>/<your mod id>/entities/<modid>/<mobname>.json

That would mean that your zombie definition would be at data/<your mod id>/<your mod id>/entities/minecraft/zombie.json

It means your modid is duplicated, but there is no risk of another mod using the entities path and causing your mod to crash.

Posted
49 minutes ago, Alpvax said:

You create a new (empty) set, then loop through it. Twice.

Neither of your loops will do anything, as they are both always going to be empty when the loop is run.

I know. They are placeholders for now, so nothing breaks when i compile.

50 minutes ago, Alpvax said:

I would suggest including your modid in the path, to stop there being conflicts.

I would also not limit it to "supported" mods. That way support for third party mods can be added by datapacks (or even included in those mods if your mod becomes popular enough).

I would recommend using a path such as data/<datapackid>/<your mod id>/entities/<modid>/<mobname>.json

That would mean that your zombie definition would be at data/<your mod id>/<your mod id>/entities/minecraft/zombie.json

It means your modid is duplicated, but there is no risk of another mod using the entities path and causing your mod to crash.

Good call. But I still don't know, how I would get the complete hierarchy. I need to find out, which folder/file (aka mods or mobs) exist.

Posted
16 minutes ago, Tavi007 said:

But I still don't know, how I would get the complete hierarchy. I need to find out, which folder/file (aka mods or mobs) exist.

I don't believe that is the case. Look at the subclasses of JSONReloadListener (recipes, loot tables, advancements etc.).

You don't care which files exist, just try to load all of them.

Posted (edited)

I tried to understand it a little bit more, but I sitll have problems wrapping my head around some functions of LootTableManager.

//from LoottableManager:

   public static void func_227508_a_(ValidationTracker p_227508_0_, ResourceLocation p_227508_1_, LootTable p_227508_2_) {
      p_227508_2_.func_227506_a_(p_227508_0_.func_227529_a_(p_227508_2_.getParameterSet()).func_227531_a_("{" + p_227508_1_ + "}", p_227508_1_));
   }

Why are the function and variable names so cryptic? Is there any reason? What is even happening here?

Anyway please take a look at my current files. First ElementalData, where I do not know, if it's the right approach.

package Tavi007.ElementalCombat;

import java.util.Set;

import com.google.common.collect.Sets;

import net.minecraft.util.ResourceLocation;

public class ElementalData
{
	   public static final ResourceLocation EMPTY = new ResourceLocation(ElementalCombat.MOD_ID, "empty");
	   
	   private final Set<String> weakSet;
	   private final Set<String> resiSet;
	   private final Set<String> wallSet;
	   private final Set<String> absoSet;
	   private final Set<String> atckSet;
	   
	   public ElementalData(Set<String> weak, Set<String> resi, Set<String> wall, Set<String> abso, Set<String> atck)
	   {
		   this.weakSet = weak;
		   this.resiSet = resi;
		   this.wallSet = wall;
		   this.absoSet = abso;
		   this.atckSet = atck;
	   }
	   
	   public ElementalData()
	   {
		   this.weakSet = Sets.newHashSet();
		   this.resiSet = Sets.newHashSet();
		   this.wallSet = Sets.newHashSet();
		   this.absoSet = Sets.newHashSet();
		   this.atckSet = Sets.newHashSet();
	   }
	   
	   public Set<String> getWeaknessSet()
	   {
		   return this.weakSet;
	   }
	   
	   public Set<String> getResistanceSet()
	   {
		   return this.resiSet;
	   }
	   
	   public Set<String> getWallSet()
	   {
		   return this.wallSet;
	   }
	   
	   public Set<String> getAbsorbSet()
	   {
		   return this.absoSet;
	   }
	   
	   public Set<String> getAttackSet()
	   {
		   return this.atckSet;
	   }
}

 

 

 And secondly the ElementalDataManager, which I've been building up like LootTableManager:

package Tavi007.ElementalCombat;

import java.util.Map;
import java.util.Set;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;

import net.minecraft.client.resources.JsonReloadListener;
import net.minecraft.profiler.IProfiler;
import net.minecraft.resources.IResourceManager;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.storage.loot.ValidationTracker;

public class ElementalDataManager extends JsonReloadListener 
{
	private static final Gson GSON = (new GsonBuilder()).setPrettyPrinting().disableHtmlEscaping().create();
	private static final Logger LOGGER = LogManager.getLogger();
	private Map<ResourceLocation, ElementalData> registeredElementalData = ImmutableMap.of();
   
	public ElementalDataManager() 
	{
		super(GSON, "elementalcombat");
	}
	
	protected void apply(Map<ResourceLocation, JsonObject> objectIn, IResourceManager resourceManagerIn, IProfiler profilerIn) 
	{
		Builder<ResourceLocation, ElementalData> builder = ImmutableMap.builder();
		JsonObject jsonobject = objectIn.remove(ElementalData.EMPTY);
	    if (jsonobject != null) 
	    {
	         LOGGER.warn("Datapack tried to redefine {} elemental entity data, ignoring", (Object)ElementalData.EMPTY);
	    }
	    
	    objectIn.forEach((rl, json) -> 
	    {
	       try (net.minecraft.resources.IResource res = resourceManagerIn.getResource(getPreparedPath(rl));)
	       {
	    	   
	    	   ElementalData elementalData = new ElementalData(); //what do I have to do here? Empty for now
	    	   //copied from LootTableManager:
	    	   //LootTable loottable = net.minecraftforge.common.ForgeHooks.loadLootTable(GSON_INSTANCE, rl, json, res == null || !res.getPackName().equals("Default"), this);
	    	   
	    	   builder.put(rl, elementalData);
	       }
	       catch (Exception exception)
	       {
	           LOGGER.error("Couldn't parse elemental entity data {}", rl, exception);
	       }
	    });
	    
	    builder.put(ElementalData.EMPTY, new ElementalData());
	    ImmutableMap<ResourceLocation, ElementalData> immutablemap = builder.build(); //this mapping contains attack and defense data.
	    
	    //validation of immutablemap missing
	    //copied from LootTable:
	    //ValidationTracker validationtracker = new ValidationTracker(LootParameterSets.GENERIC, this.field_227507_d_::func_227517_a_, immutablemap::get);
	    //immutablemap.forEach((rl, json) -> 
	    //{
	    //   func_227508_a_(validationtracker, rl, json);
	    //});
	    //validationtracker.getProblems().forEach((rl, json) -> 
	    //{
	    //   LOGGER.warn("Found validation problem in " + rl + ": " + json);
	    //});

	    
	    this.registeredElementalData = immutablemap;  
	}
	   
    public static JsonElement toJson(ElementalData elementalData)
    {
	    return GSON.toJsonTree(elementalData);
	}

	public Set<ResourceLocation> getElementalDataKeys() 
	{
	    return this.registeredElementalData.keySet();
	}
}

I do know, that I should not create an empty ElementalData. But I also don't understand, what is happening at the same line in LootTableManager. Can anyone please elaborate?
Is there an easy way for me to validate the immutableMap or do I have to write my own function for it?
And my last question: Where do I have to use the Manager? Do I have to register it somewhere or do I have to call a certain event?

Edited by Tavi007
Posted (edited)
14 hours ago, diesieben07 said:

Minecraft is obfuscated. Names are provided by the community and not everything is named.

Ahhh okay.

 

14 hours ago, diesieben07 said:

You have to parse the JSON into your object.

I think, I got it now. I copied a lot from LootTableManager and changed some bits accordingly. My mod compiles and nothing crashes so far, but I don't know, if it ran through my code. I would assume not, because I haven't registered it nor fired an event for it. So what would I have to do?

Edited by Tavi007
Posted

What is the purpose of this capability?

 

Unless I understood you wrong, you just need other mods to tell you what elements their mobs are. This can be done with datapacks.

 

You load all the datapacks into a Hashmap<String,ElementData> where string is the mod id. And when you want to know mob's resistances, you just check that map.

 

Entity Capabilities are for saving mob specific data, a hashmap works fine if that data is set at startup and same for each mob type.

 

Posted (edited)

Home stretch! The loading seems to be working now. To acces the data I use the map <ResourceLocation, ElementalData> from the ElemenDataManager and give it the right ResourceLocation, right? Now I only need the right ResourceLocation, which can change with the choosen datapack. Going by Alpvax I have the following folder structure:

data/<datapackid>/<your mod id>/entities/<modid>/<mobname>.json

or with actual ids:

data/default/elementalcomabt/entities/minecraft/zombie.json.
I plan on adding elemental properties to itemstacks in the same fashion, so i would have a folder on the same level as entities called 'items'. Using this format the ResourceLocation for Zombie currently looks like this:

default:entities/minecraft/zombie

I don't know much about handling a DataPack yet, but this ResourceLocation seems kinda wrong to me. Shouldn't the namespace be my mod id? Also this way I would need to figure out from which mod the entity is coming from.

On another topic: how can I set this data as default for a spawning mob? I tried using the LivingSpawnEvent, thinking it would only fire, if the spawn would be succesful. But it turns out to be firing a lot, without actually spawning anything. Using this event would drastically increase computing time, which I don't like. But it would work tho.

 

31 minutes ago, robertx555 said:

What is the purpose of this capability?

 

Unless I understood you wrong, you just need other mods to tell you what elements their mobs are. This can be done with datapacks.

 

You load all the datapacks into a Hashmap<String,ElementData> where string is the mod id. And when you want to know mob's resistances, you just check that map.

 

Entity Capabilities are for saving mob specific data, a hashmap works fine if that data is set at startup and same for each mob type.

 

The capability is a lot more flexible. By using a capability each mob of the same type can have different elemental properties. Imagine a mob which has an ice resistance aura. A zombie in range of said aura would have "ice" in its resistance list, while a zombie outside would not. Or imagine a boss, that changes his properties throught the fight. The player would have to adapt mid fight, which could be fun.

Edited by Tavi007
Posted
5 hours ago, Tavi007 said:

On another topic: how can I set this data as default for a spawning mob? I tried using the LivingSpawnEvent, thinking it would only fire, if the spawn would be succesful. But it turns out to be firing a lot, without actually spawning anything. Using this event would drastically increase computing time, which I don't like. But it would work tho.

Figure it out myself. I didn't notice there were sub classes in LivingSpawnEvent. Using CheckSpawn and SpecialSpawn I could exactly do, what I wanted :)

5 hours ago, Tavi007 said:

I don't know much about handling a DataPack yet, but this ResourceLocation seems kinda wrong to me. Shouldn't the namespace be my mod id? Also this way I would need to figure out from which mod the entity is coming from.

Any comment on this? This is the last problem, that I need to understand

Posted

So for a vanilla zombie it would be minecraft:elementalcombat/entities/zombie,json?
And for any mob from another mod it would be <their mod id>:elementalcombat/entities/<their mob id>.json?

 

But I would also like to have the possibility for people to change the provided elemental properties via datapack. What would change to the resourceLocation of minecraft:elementalcombat/zombie.json, if someone installs a pack? Will the data of the pack override the default one?

 

I might rename elementalcombat to elementalproperties to be more comprehensible for others...


 

.

Posted

Yup got it. Loading a datapack works just fine now.

Last problem now (I swear!). How do I get the mod id of the mob? I found


LivingEntity entity = event.getEntityLiving();
String modid = RegistryManager.ACTIVE.getRegistry(entity.getClass()).getRegistryName().getNamespace();

but this fails:

C:\Minecraft Modding\ElementalCombat\src\main\java\Tavi007\ElementalCombat\events\ElementifyLivingEntitySpawnEvent.java:43: error: no suitable method found for getRegistry(Class<CAP#1>)
                String modid = RegistryManager.ACTIVE.getRegistry(e).getRegistryName().getNamespace();
                                                     ^
    method RegistryManager.<V#1>getRegistry(ResourceLocation) is not applicable
      (cannot infer type-variable(s) V#1
        (argument mismatch; Class<CAP#1> cannot be converted to ResourceLocation))
    method RegistryManager.<V#2>getRegistry(Class<? super V#2>) is not applicable
      (cannot infer type-variable(s) V#2
        (argument mismatch; Class<CAP#1> cannot be converted to Class<? super V#2>))
    method RegistryManager.<V#3>getRegistry(ResourceLocation,RegistryManager) is not applicable
      (cannot infer type-variable(s) V#3
        (actual and formal argument lists differ in length))
  where V#1,V#2,V#3 are type-variables:
    V#1 extends IForgeRegistryEntry<V#1> declared in method <V#1>getRegistry(ResourceLocation)
    V#2 extends IForgeRegistryEntry<V#2> declared in method <V#2>getRegistry(Class<? super V#2>)
    V#3 extends IForgeRegistryEntry<V#3> declared in method <V#3>getRegistry(ResourceLocation,RegistryManager)
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Entity from capture of ? extends Entity
1 error

getRegistry() wants a namespace in this case. But it can also take Class<? super V> cls as an input, which i thought, I could do like this. 

Posted

Thanks a lot for all your help! Everything works now.

should i write a summary in the OP? Or would this be unnecessary, because 1.16 is around the corner?

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

    • Version 1.19 - Forge 41.0.63 I want to create a wolf entity that I can ride, so far it seems to be working, but the problem is that when I get on the wolf, I can’t control it. I then discovered that the issue is that the server doesn’t detect that I’m riding the wolf, so I’m struggling with synchronization. However, it seems to not be working properly. As I understand it, the server receives the packet but doesn’t register it correctly. I’m a bit new to Java, and I’ll try to provide all the relevant code and prints *The comments and prints are translated by chatgpt since they were originally in Spanish* Thank you very much in advance No player is mounted, or the passenger is not a player. No player is mounted, or the passenger is not a player. No player is mounted, or the passenger is not a player. No player is mounted, or the passenger is not a player. No player is mounted, or the passenger is not a player. MountableWolfEntity package com.vals.valscraft.entity; import com.vals.valscraft.network.MountSyncPacket; import com.vals.valscraft.network.NetworkHandler; import net.minecraft.client.Minecraft; import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.network.syncher.SynchedEntityData; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.ai.attributes.AttributeSupplier; import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.animal.Wolf; import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.Entity; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.level.Level; import net.minecraft.world.phys.Vec3; import net.minecraftforge.event.TickEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.network.PacketDistributor; public class MountableWolfEntity extends Wolf { private boolean hasSaddle; private static final EntityDataAccessor<Byte> DATA_ID_FLAGS = SynchedEntityData.defineId(MountableWolfEntity.class, EntityDataSerializers.BYTE); public MountableWolfEntity(EntityType<? extends Wolf> type, Level level) { super(type, level); this.hasSaddle = false; } @Override protected void defineSynchedData() { super.defineSynchedData(); this.entityData.define(DATA_ID_FLAGS, (byte)0); } public static AttributeSupplier.Builder createAttributes() { return Wolf.createAttributes() .add(Attributes.MAX_HEALTH, 20.0) .add(Attributes.MOVEMENT_SPEED, 0.3); } @Override public InteractionResult mobInteract(Player player, InteractionHand hand) { ItemStack itemstack = player.getItemInHand(hand); if (itemstack.getItem() == Items.SADDLE && !this.hasSaddle()) { if (!player.isCreative()) { itemstack.shrink(1); } this.setSaddle(true); return InteractionResult.SUCCESS; } else if (!level.isClientSide && this.hasSaddle()) { player.startRiding(this); MountSyncPacket packet = new MountSyncPacket(true); // 'true' means the player is mounted NetworkHandler.CHANNEL.sendToServer(packet); // Ensure the server handles the packet return InteractionResult.SUCCESS; } return InteractionResult.PASS; } @Override public void travel(Vec3 travelVector) { if (this.isVehicle() && this.getControllingPassenger() instanceof Player) { System.out.println("The wolf has a passenger."); System.out.println("The passenger is a player."); Player player = (Player) this.getControllingPassenger(); // Ensure the player is the controller this.setYRot(player.getYRot()); this.yRotO = this.getYRot(); this.setXRot(player.getXRot() * 0.5F); this.setRot(this.getYRot(), this.getXRot()); this.yBodyRot = this.getYRot(); this.yHeadRot = this.yBodyRot; float forward = player.zza; float strafe = player.xxa; if (forward <= 0.0F) { forward *= 0.25F; } this.flyingSpeed = this.getSpeed() * 0.1F; this.setSpeed((float) this.getAttributeValue(Attributes.MOVEMENT_SPEED) * 1.5F); this.setDeltaMovement(new Vec3(strafe, travelVector.y, forward).scale(this.getSpeed())); this.calculateEntityAnimation(this, false); } else { // The wolf does not have a passenger or the passenger is not a player System.out.println("No player is mounted, or the passenger is not a player."); super.travel(travelVector); } } public boolean hasSaddle() { return this.hasSaddle; } public void setSaddle(boolean hasSaddle) { this.hasSaddle = hasSaddle; } @Override protected void dropEquipment() { super.dropEquipment(); if (this.hasSaddle()) { this.spawnAtLocation(Items.SADDLE); this.setSaddle(false); } } @SubscribeEvent public static void onServerTick(TickEvent.ServerTickEvent event) { if (event.phase == TickEvent.Phase.START) { MinecraftServer server = net.minecraftforge.server.ServerLifecycleHooks.getCurrentServer(); if (server != null) { for (ServerPlayer player : server.getPlayerList().getPlayers()) { if (player.isPassenger() && player.getVehicle() instanceof MountableWolfEntity) { MountableWolfEntity wolf = (MountableWolfEntity) player.getVehicle(); System.out.println("Tick: " + player.getName().getString() + " is correctly mounted on " + wolf); } } } } } private boolean lastMountedState = false; @Override public void tick() { super.tick(); if (!this.level.isClientSide) { // Only on the server boolean isMounted = this.isVehicle() && this.getControllingPassenger() instanceof Player; // Only print if the state changed if (isMounted != lastMountedState) { if (isMounted) { Player player = (Player) this.getControllingPassenger(); // Verify the passenger is a player System.out.println("Server: Player " + player.getName().getString() + " is now mounted."); } else { System.out.println("Server: The wolf no longer has a passenger."); } lastMountedState = isMounted; } } } @Override public void addPassenger(Entity passenger) { super.addPassenger(passenger); if (passenger instanceof Player) { Player player = (Player) passenger; if (!this.level.isClientSide && player instanceof ServerPlayer) { // Send the packet to the server to indicate the player is mounted NetworkHandler.CHANNEL.send(PacketDistributor.PLAYER.with(() -> (ServerPlayer) player), new MountSyncPacket(true)); } } } @Override public void removePassenger(Entity passenger) { super.removePassenger(passenger); if (passenger instanceof Player) { Player player = (Player) passenger; if (!this.level.isClientSide && player instanceof ServerPlayer) { // Send the packet to the server to indicate the player is no longer mounted NetworkHandler.CHANNEL.send(PacketDistributor.PLAYER.with(() -> (ServerPlayer) player), new MountSyncPacket(false)); } } } @Override public boolean isControlledByLocalInstance() { Entity entity = this.getControllingPassenger(); return entity instanceof Player; } @Override public void positionRider(Entity passenger) { if (this.hasPassenger(passenger)) { double xOffset = Math.cos(Math.toRadians(this.getYRot() + 90)) * 0.4; double zOffset = Math.sin(Math.toRadians(this.getYRot() + 90)) * 0.4; passenger.setPos(this.getX() + xOffset, this.getY() + this.getPassengersRidingOffset() + passenger.getMyRidingOffset(), this.getZ() + zOffset); } } } MountSyncPacket package com.vals.valscraft.network; import com.vals.valscraft.entity.MountableWolfEntity; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.player.Player; import net.minecraftforge.network.NetworkEvent; import java.util.function.Supplier; public class MountSyncPacket { private final boolean isMounted; public MountSyncPacket(boolean isMounted) { this.isMounted = isMounted; } public void encode(FriendlyByteBuf buffer) { buffer.writeBoolean(isMounted); } public static MountSyncPacket decode(FriendlyByteBuf buffer) { return new MountSyncPacket(buffer.readBoolean()); } public void handle(NetworkEvent.Context context) { context.enqueueWork(() -> { ServerPlayer player = context.getSender(); // Get the player from the context if (player != null) { // Verifies if the player has dismounted if (!isMounted) { Entity vehicle = player.getVehicle(); if (vehicle instanceof MountableWolfEntity wolf) { // Logic to remove the player as a passenger wolf.removePassenger(player); System.out.println("Server: Player " + player.getName().getString() + " is no longer mounted."); } } } }); context.setPacketHandled(true); // Marks the packet as handled } } networkHandler package com.vals.valscraft.network; import com.vals.valscraft.valscraft; import net.minecraft.resources.ResourceLocation; import net.minecraftforge.network.NetworkRegistry; import net.minecraftforge.network.simple.SimpleChannel; import net.minecraftforge.network.NetworkEvent; import java.util.function.Supplier; public class NetworkHandler { private static final String PROTOCOL_VERSION = "1"; public static final SimpleChannel CHANNEL = NetworkRegistry.newSimpleChannel( new ResourceLocation(valscraft.MODID, "main"), () -> PROTOCOL_VERSION, PROTOCOL_VERSION::equals, PROTOCOL_VERSION::equals ); public static void init() { int packetId = 0; // Register the mount synchronization packet CHANNEL.registerMessage( packetId++, MountSyncPacket.class, MountSyncPacket::encode, MountSyncPacket::decode, (msg, context) -> msg.handle(context.get()) // Get the context with context.get() ); } }  
    • Do you use features of inventory profiles next (ipnext) or is there a change without it?
    • Remove rubidium - you are already using embeddium, which is a fork of rubidium
  • Topics

×
×
  • Create New...

Important Information

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