Hey guys! This is my first topic here, although I am a frequent visitor, so at first i would like to say hello to all of you:) I'm sorry if anything below is not understandable, but im not native speaker and i don't have a lot occasions to write something in english.
Today i have faced a serious obstacle which i can't work around by myself. I'm working on a mod where i have added few custom VillageProfessions. I would like these custom villagers to be spawned in world only when player uses a special item on regular villager, which i had already implemented. The issue is that my custom villagers aslo spawn on world generation in regular villages, because of the VillagerRegistery.setRandomProfession() method. (i believe that it could be the problem)
I'v tried using Events like LivingSpawnEvent.CheckSpawn and LivingSpawnEvent.SpecialSpawn to check if the spawned entity is one of my custom villagers, and if so then replace it with another one with profession 5(nitwit), but it seems that that events are not fired when villagers are being spawned in villages.
Then I'v tried to add a custom capability to EntityVillager, which contained a single boolean field canSpawn intialized as false, and then i wanted to use it in EntityJoinWorldEvent becouse i figured out that this event was fired on villagers spawn (wow...who would have thought)
@SubscribeEvent
public void entityJoinWorldEvent(EntityJoinWorldEvent event)
{
if(event.getEntity() instanceof EntityVillager)
{
EntityVillager villager = (EntityVillager) event.getEntity();
ICanSpawn canSpawn = villager.getCapability(CanSpawnProvider.CANSPAWN,null);
if(canSpawn.getCanSpawn() == false)//Fellow line 61 from stacktrace
{
//this checks if the joining villager profession is in arraylist of my custom professions
if(CustomVillagerUtils.contains(villager.getProfessionForge()))
{
event.setCanceled(true);
EntityVillager replacementVillager = new EntityVillager(event.getWorld(),5);
replacementVillager.setPosition(villager.posX, villager.posY, villager.posZ);
event.getWorld().spawnEntity(replacementVillager);
System.out.println("Canceled spawning CUSTOMVILLAGER - Added NITWIT instead");
}
}
//else if canSpawn == true, then spawn villager with my custom profession
}
}
And this is called when item is being used on EntityVillager:
public static void tryOutTheVillager(EntityPlayer player, EntityVillager target)
{
if (isValidForTransformation(target))
{
ICanSpawn canSpawn = player.getCapability(CanSpawnProvider.CANSPAWN, null);
canSpawn.setCanSpawn(true); //this line also was generating NullPointerException
turnVillagerIntoCustomVillager(player, target); //this just replaces current villager with a new custom one
// canSpawn.setCanSpawn(false);
}
}
But this was a huge failure (i think that i didnt assigned the capability to the EntityVillager properly, and the entities cant see it, but i really dont know - today was my 1rst attempt on adding capabilites and i didnt figured out how does them work yet)
[01:13:42] [Server thread/ERROR] [FML]: Exception caught during firing event net.minecraftforge.event.entity.EntityJoinWorldEvent@6fec275d:
java.lang.NullPointerException: null
at com.johny12340.customvillagers.common.util.handlers.EventHandler.entityJoinWorldEvent(EventHandler.java:61) ~[EventHandler.class:?]
at net.minecraftforge.fml.common.eventhandler.ASMEventHandler_20_EventHandler_entityJoinWorldEvent_EntityJoinWorldEvent.invoke(.dynamic) ~[?:?]
at net.minecraftforge.fml.common.eventhandler.ASMEventHandler.invoke(ASMEventHandler.java:90) ~[ASMEventHandler.class:?]
at net.minecraftforge.fml.common.eventhandler.EventBus.post(EventBus.java:182) [EventBus.class:?]
at net.minecraft.world.World.spawnEntity(World.java:1313) [World.class:?]
at net.minecraft.world.WorldServer.spawnEntity(WorldServer.java:1121) [WorldServer.class:?]
at net.minecraft.world.gen.structure.StructureVillagePieces$Village.spawnVillagers(StructureVillagePieces.java:1899) [StructureVillagePieces$Village.class:?]
at net.minecraft.world.gen.structure.StructureVillagePieces$House4Garden.addComponentParts(StructureVillagePieces.java:1394) [StructureVillagePieces$House4Garden.class:?]
at net.minecraft.world.gen.structure.StructureStart.generateStructure(StructureStart.java:51) [StructureStart.class:?]
at net.minecraft.world.gen.structure.MapGenStructure.generateStructure(MapGenStructure.java:102) [MapGenStructure.class:?]
at net.minecraft.world.gen.ChunkGeneratorOverworld.populate(ChunkGeneratorOverworld.java:412) [ChunkGeneratorOverworld.class:?]
at net.minecraft.world.chunk.Chunk.populate(Chunk.java:1094) [Chunk.class:?]
at net.minecraft.world.chunk.Chunk.populate(Chunk.java:1074) [Chunk.class:?]
at net.minecraft.world.gen.ChunkProviderServer.provideChunk(ChunkProviderServer.java:169) [ChunkProviderServer.class:?]
at net.minecraft.server.MinecraftServer.initialWorldChunkLoad(MinecraftServer.java:383) [MinecraftServer.class:?]
at net.minecraft.server.integrated.IntegratedServer.loadAllWorlds(IntegratedServer.java:143) [IntegratedServer.class:?]
at net.minecraft.server.integrated.IntegratedServer.init(IntegratedServer.java:160) [IntegratedServer.class:?]
at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:552) [MinecraftServer.class:?]
at java.lang.Thread.run(Thread.java:748) [?:1.8.0_192]
Is there any other way to do this you guys can think of, or you can see what im missing in my previous attemps? I was thinking on some way to Override the vanilla VillagerRegistery.setRandomProfession() or use the InitMapGenEvent but dunno how for now.
Aslo i will attach my capabilities code here, so maybe someone can tell me what i am doing wrong ;-;
ICanSpawn
public interface ICanSpawn
{
boolean getCanSpawn();
void setCanSpawn(boolean canSpawn);
}
CanSpawn
public class CanSpawn implements ICanSpawn
{
private boolean canSpawn;
public CanSpawn()
{
this.canSpawn = false;
}
@Override
public boolean getCanSpawn()
{
System.out.println("getCanSpawn:"+canSpawn);
return this.canSpawn;
}
@Override
public void setCanSpawn(boolean canSpawn)
{
this.canSpawn = canSpawn;
}
}
CanSpawnStorage
public class CanSpawnStorage implements IStorage<ICanSpawn>
{
@Nullable
@Override
public NBTBase writeNBT(Capability<ICanSpawn> capability, ICanSpawn instance, EnumFacing side)
{
NBTTagCompound tags = new NBTTagCompound();
tags.setBoolean("canspawn", instance.getCanSpawn());
return tags;
}
@Override
public void readNBT(Capability<ICanSpawn> capability, ICanSpawn instance, EnumFacing side, NBTBase nbt)
{
NBTTagCompound tags = (NBTTagCompound) nbt;
if (tags.hasKey("canspawn")) {
instance.setCanSpawn(tags.getBoolean("canspawn"));
}
}
}
CanSpawnProvider
public class CanSpawnProvider implements ICapabilitySerializable<NBTTagCompound> //I was using ICapabilityProvider before (cuz i thought that this needn't to be serialized)
{
@CapabilityInject(ICanSpawn.class)
public final static Capability<ICanSpawn> CANSPAWN = null;
private ICanSpawn instance = CANSPAWN.getDefaultInstance();
@Override
public boolean hasCapability(@Nonnull Capability<?> capability, @Nullable EnumFacing facing)
{
System.out.println("hasCapability:"+CANSPAWN.getName());
return capability == CANSPAWN;
}
@Nullable
@Override
public <T> T getCapability(@Nonnull Capability<T> capability, @Nullable EnumFacing facing)
{
System.out.println("getCapability:"+CANSPAWN.getName());
if (CANSPAWN != null && CANSPAWN == capability)
return (T) instance;
return null;
}
@Override
public NBTTagCompound serializeNBT()
{
return (NBTTagCompound) CANSPAWN.getStorage().writeNBT(CANSPAWN, instance, null);
}
@Override
public void deserializeNBT(NBTTagCompound nbt)
{
CANSPAWN.getStorage().readNBT(CANSPAWN, instance, null, nbt);
}
}
CanSpawnFactory
public class CanSpawnFactory implements Callable<ICanSpawn>
{
@Override
public ICanSpawn call() throws Exception
{
return new CanSpawn();
}
}
This is being called in my CommonProxy.preInit method:
CapabilityManager.INSTANCE.register(ICanSpawn.class, new CanSpawnStorage(), new CanSpawnFactory());
And here i'm attaching the capability to EntityVillager
public class CapabilityHandler
{
public static final ResourceLocation CANSPAWN = new ResourceLocation(CustomVillagers.MODID, "canspawn");
@SubscribeEvent
public void attachCapability(AttachCapabilitiesEvent<Entity> event)
{
if (!(event.getObject() instanceof EntityVillager)) return;
event.addCapability(CANSPAWN, new CanSpawnProvider());
}
}
Oh f**k nvm, i have worked it around by myself... i didnt registered the CapabilityHandler, thats why it was throwing the NullPointerException at me. Still did not tested if the capability changes and if this method even work, but i will do it tomorow cuz im falling asleep now ? I will update the post then.
Since I've already pasted all this here i will publish it, so maybe it will help someone, and maybe someone will look throught/review my code and tell me what i have done wrong and what i can improve. (also still maybe there is a better way of achieving the main goal -preventing the custom villagers from spawning- which i can't think of now)
SOLUTION
You can check how i solved this problem here: GitHub (It's a silly mod which im making to have fun with friends on server)
Basicly its the same way as before (Custom Capability and EntityJoinWorldEvent) but i did some improvements.