Introduce entity entry builder, useful in the Register<EntityEntry> event replacing needed calls to EntityRegistry. (#4408)

This commit is contained in:
kashike 2017-09-20 13:03:03 -07:00 committed by LexManos
parent 50265786b2
commit f2b07e8db1
5 changed files with 443 additions and 21 deletions

View file

@ -18,21 +18,20 @@
*/
package net.minecraftforge.fml.common.registry;
import java.lang.reflect.Constructor;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityList;
import net.minecraft.entity.EntityList.EntityEggInfo;
import net.minecraft.world.World;
import net.minecraftforge.fml.common.FMLLog;
import net.minecraftforge.registries.IForgeRegistryEntry.Impl;
import java.util.function.Function;
public class EntityEntry extends Impl<EntityEntry>
{
private Class<? extends Entity> cls;
private String name;
private EntityEggInfo egg;
private Constructor<?> ctr;
Function<World, ? extends Entity> factory;
public EntityEntry(Class<? extends Entity> cls, String name)
{
@ -44,14 +43,12 @@ public class EntityEntry extends Impl<EntityEntry>
//Protected method, to make this optional, in case people subclass this to have a better factory.
protected void init()
{
try
{
this.ctr = this.cls.getConstructor(World.class);
}
catch (NoSuchMethodException e)
{
throw new RuntimeException("Invalid class " + this.cls + " no constructor taking " + World.class.getName());
}
this.factory = new EntityEntryBuilder.ConstructorFactory<Entity>(this.cls) {
@Override
protected String describeEntity() {
return String.valueOf(EntityEntry.this.getRegistryName());
}
};
}
public Class<? extends Entity> getEntityClass(){ return this.cls; }
@ -67,14 +64,6 @@ public class EntityEntry extends Impl<EntityEntry>
public Entity newInstance(World world)
{
try
{
return (Entity)this.ctr.newInstance(world);
}
catch (Exception e)
{
FMLLog.log.error("Error creating entity.", e);
return null;
}
return this.factory.apply(world);
}
}

View file

@ -0,0 +1,370 @@
/*
* Minecraft Forge
* Copyright (c) 2016.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fml.common.registry;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityList;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EnumCreatureType;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.World;
import net.minecraft.world.biome.Biome;
import net.minecraftforge.fml.common.FMLLog;
import net.minecraftforge.fml.common.Loader;
import net.minecraftforge.fml.common.ModContainer;
import net.minecraftforge.fml.relauncher.ReflectionHelper;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
/**
* An entity registry entry builder.
*
* @param <E> The entity type
*/
public final class EntityEntryBuilder<E extends Entity>
{
private final ModContainer mod;
@Nullable private Class<? extends E> entity;
@Nullable private Function<World, E> factory;
@Nullable private ResourceLocation id;
private int network;
@Nullable private String name;
private int trackingRange;
private int trackingUpdateFrequency;
private boolean trackingVelocityUpdates;
private boolean eggProvided;
private int primaryEggColor;
private int secondaryEggColor;
@Nullable private Collection<Spawn> spawns;
/**
* Creates a new entity entry builder.
*
* @param <E> The entity type
* @return A new entity entry builder
*/
@Nonnull
public static <E extends Entity> EntityEntryBuilder<E> create()
{
return new EntityEntryBuilder<>();
}
private EntityEntryBuilder()
{
this.mod = Loader.instance().activeModContainer();
}
/**
* Sets the class of the entity.
*
* <p>Entities will be constructed using a constructor accepting {@link World}. If you wish
* to use your own factory, use {@link #factory(Function)}.</p>
*
* @param entity The entity class
* @return This builder
* @throws NullPointerException If {@code entity} is {@code null}
*/
@Nonnull
public final EntityEntryBuilder<E> entity(@Nonnull final Class<? extends E> entity)
{
this.entity = checkNotNull(entity, "entity class");
return this;
}
/**
* Sets the factory of the entity.
*
* @param factory The entity factory
* @return This builder
* @throws NullPointerException If {@code entity} is {@code null}
*/
@Nonnull
public final EntityEntryBuilder<E> factory(@Nonnull final Function<World, E> factory)
{
this.factory = checkNotNull(factory, "entity factory");
return this;
}
/**
* Sets the id of the entity.
*
* @param id The entity id
* @param network The network id
* @return This builder
* @throws NullPointerException If {@code id} is {@code null}
*/
@Nonnull
public final EntityEntryBuilder<E> id(@Nonnull final ResourceLocation id, final int network)
{
this.id = checkNotNull(id, "id");
this.network = network;
return this;
}
/**
* Sets the id of the entity.
*
* @param id The entity id
* @param network The network id
* @return This builder
* @throws NullPointerException If {@code id} is {@code null}
*/
@Nonnull
public final EntityEntryBuilder<E> id(@Nonnull final String id, final int network)
{
return this.id(new ResourceLocation(checkNotNull(id, "id")), network);
}
/**
* Sets the name of the entity.
*
* @param name The entity name
* @return This builder
* @throws NullPointerException If {@code name} is {@code null}
*/
@Nonnull
public final EntityEntryBuilder<E> name(@Nonnull final String name)
{
this.name = checkNotNull(name, "name");
return this;
}
/**
* Sets entity tracking information.
*
* @param range The tracking range
* @param updateFrequency The tracking update frequency
* @param sendVelocityUpdates If the entity should send velocity updates
* @return This builder
*/
@Nonnull
public final EntityEntryBuilder<E> tracker(final int range, final int updateFrequency, final boolean sendVelocityUpdates)
{
this.trackingRange = range;
this.trackingUpdateFrequency = updateFrequency;
this.trackingVelocityUpdates = sendVelocityUpdates;
return this;
}
/**
* Adds a spawn entry.
*
* @param type The creature type
* @param weight The spawn entry weight
* @param min The minimum spawn count
* @param max The maximum spawn count
* @param biomes The biomes to add an entry in
* @return This builder
* @throws IllegalArgumentException If the entity is not a {@link EntityLiving}
* @throws NullPointerException If {@code type} is {@code null}
* @throws NullPointerException If {@code biomes} is {@code null}
*/
@Nonnull
public final EntityEntryBuilder<E> spawn(@Nonnull final EnumCreatureType type, final int weight, final int min, final int max, @Nonnull final Biome... biomes)
{
checkNotNull(biomes, "biomes");
return this.spawn(type, weight, min, max, Arrays.asList(biomes));
}
/**
* Adds a spawn entry.
*
* @param type The creature type
* @param weight The spawn entry weight
* @param min The minimum spawn count
* @param max The maximum spawn count
* @param biomes The biomes to add an entry in
* @return This builder
* @throws IllegalArgumentException If the entity is not a {@link EntityLiving}
* @throws NullPointerException If {@code type} is {@code null}
* @throws NullPointerException If {@code biomes} is {@code null}
*/
@Nonnull
public final EntityEntryBuilder<E> spawn(@Nonnull final EnumCreatureType type, final int weight, final int min, final int max, @Nonnull final Iterable<Biome> biomes)
{
checkNotNull(type, "type");
checkNotNull(biomes, "biomes");
if (this.entity != null) checkArgument(EntityLiving.class.isAssignableFrom(this.entity), "Cannot add spawns to a non-%s", EntityLiving.class.getSimpleName());
if (this.spawns == null) this.spawns = new ArrayList<>();
this.spawns.add(new Spawn(type, weight, min, max, biomes));
return this;
}
/**
* Sets the egg of the entity.
*
* @param primaryColor the primary egg color
* @param secondaryColor the secondary egg color
* @return This builder
*/
@Nonnull
public final EntityEntryBuilder<E> egg(final int primaryColor, final int secondaryColor)
{
this.eggProvided = true;
this.primaryEggColor = primaryColor;
this.secondaryEggColor = secondaryColor;
return this;
}
/**
* Create an entity entry based on the data in this builder.
*
* @return The entity entry
* @throws IllegalStateException If the entity class has not been provided
* @throws IllegalStateException If the entity id has not been provided
* @throws IllegalStateException If the entity name has not been provided
* @throws IllegalStateException If spawns have been provided for a non {@link EntityLiving}
* @throws ReflectionHelper.UnknownConstructorException If a {@link #factory} has not been provided
* and {@link #entity} does not have a constructor accepting {@link World}
*/
@Nonnull
public EntityEntry build()
{
checkState(this.entity != null, "entity class not provided");
checkState(this.id != null, "entity id not provided");
checkState(this.name != null, "entity name not provided");
if (this.spawns != null) checkState(EntityLiving.class.isAssignableFrom(EntityEntryBuilder.this.entity), "Cannot add spawns to a non-%s", EntityLiving.class.getSimpleName());
final BuiltEntityEntry entry = new BuiltEntityEntry(this.entity, this.name);
entry.factory = this.factory != null ? this.factory : new ConstructorFactory<E>(this.entity) {
@Override
protected String describeEntity() {
return String.valueOf(EntityEntryBuilder.this.id);
}
};
entry.setRegistryName(this.id);
if (this.eggProvided) entry.setEgg(new EntityList.EntityEggInfo(this.id, this.primaryEggColor, this.secondaryEggColor));
return entry;
}
@Nonnull
private EntityRegistry.EntityRegistration createRegistration()
{
return EntityRegistry.instance().new EntityRegistration(
this.mod, this.id, this.entity, this.name, this.network,
this.trackingRange, this.trackingUpdateFrequency, this.trackingVelocityUpdates
);
}
static abstract class ConstructorFactory<E extends Entity> implements Function<World, E>
{
private final Constructor<? extends E> constructor;
ConstructorFactory(final Class<? extends E> entity)
{
this.constructor = ReflectionHelper.findConstructor(entity, World.class);
}
@Override
public E apply(final World world)
{
try
{
return this.constructor.newInstance(world);
}
catch (final IllegalAccessException | InstantiationException | InvocationTargetException e)
{
FMLLog.log.error("Encountered an exception while constructing entity '{}'", this.describeEntity(), e);
return null;
}
}
protected abstract String describeEntity();
}
public final class BuiltEntityEntry extends EntityEntry
{
private boolean added;
BuiltEntityEntry(final Class<? extends Entity> cls, final String name)
{
super(cls, name);
}
@Override
protected final void init() {
// NOOP - we handle this in build
}
@SuppressWarnings("ConstantConditions")
public final void addedToRegistry()
{
if (this.added) return;
this.added = true;
EntityRegistry.instance().insert(EntityEntryBuilder.this.entity, EntityEntryBuilder.this.createRegistration());
if (EntityEntryBuilder.this.spawns != null)
{
for (final Spawn spawn : EntityEntryBuilder.this.spawns)
{
spawn.insert();
}
EntityEntryBuilder.this.spawns = null;
}
}
}
public final class Spawn
{
private final EnumCreatureType type;
private final int weight;
private final int min;
private final int max;
private final Iterable<Biome> biomes;
public Spawn(final EnumCreatureType type, final int weight, final int min, final int max, final Iterable<Biome> biomes)
{
this.type = type;
this.weight = weight;
this.min = min;
this.max = max;
this.biomes = biomes;
}
@SuppressWarnings("unchecked")
final void insert()
{
for (final Biome biome : this.biomes) {
final List<Biome.SpawnListEntry> entries = biome.getSpawnableList(this.type);
boolean found = false;
for (final Biome.SpawnListEntry entry : entries) {
if (entry.entityClass == EntityEntryBuilder.this.entity) {
entry.itemWeight = this.weight;
entry.minGroupCount = this.min;
entry.maxGroupCount = this.max;
found = true;
break;
}
}
if (!found) entries.add(new Biome.SpawnListEntry((Class<? extends EntityLiving>) EntityEntryBuilder.this.entity, this.weight, this.min, this.max));
}
}
}
}

View file

@ -362,4 +362,11 @@ public class EntityRegistry
}
return null;
}
// This is an internal method - do not touch.
final void insert(final Class<? extends Entity> entity, final EntityRegistration registration)
{
this.entityClassRegistrations.put(entity, registration);
this.entityRegistrations.put(registration.container, registration);
}
}

View file

@ -24,6 +24,8 @@ import org.apache.commons.lang3.StringUtils;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
@ -88,6 +90,14 @@ public class ReflectionHelper
}
}
public static class UnknownConstructorException extends RuntimeException
{
public UnknownConstructorException(final String message)
{
super(message);
}
}
public static Field findField(Class<?> clazz, String... fieldNames)
{
Exception failed = null;
@ -220,4 +230,45 @@ public class ReflectionHelper
throw new UnableToFindMethodException(e);
}
}
/**
* Finds a constructor in the specified class that has matching parameter types.
*
* @param klass The class to find the constructor in
* @param parameterTypes The parameter types of the constructor.
* @param <T> The type
* @return The constructor
* @throws NullPointerException if {@code klass} is null
* @throws NullPointerException if {@code parameterTypes} is null
* @throws UnknownConstructorException if the constructor could not be found
*/
@Nonnull
public static <T> Constructor<T> findConstructor(@Nonnull final Class<T> klass, @Nonnull final Class<?>... parameterTypes)
{
Preconditions.checkNotNull(klass, "class");
Preconditions.checkNotNull(parameterTypes, "parameter types");
final Constructor<T> constructor;
try
{
constructor = klass.getDeclaredConstructor(parameterTypes);
constructor.setAccessible(true);
}
catch (final NoSuchMethodException e)
{
final StringBuilder desc = new StringBuilder();
desc.append(klass.getSimpleName()).append('(');
for (int i = 0, length = parameterTypes.length; i < length; i++)
{
desc.append(parameterTypes[i].getName());
if (i > length)
{
desc.append(',').append(' ');
}
}
desc.append(')');
throw new UnknownConstructorException("Could not find constructor '" + desc.toString() + "' in " + klass);
}
return constructor;
}
}

View file

@ -56,6 +56,7 @@ import net.minecraftforge.fml.common.Loader;
import net.minecraftforge.fml.common.StartupQuery;
import net.minecraftforge.fml.common.ZipperUtil;
import net.minecraftforge.fml.common.registry.EntityEntry;
import net.minecraftforge.fml.common.registry.EntityEntryBuilder;
import net.minecraftforge.fml.common.registry.GameRegistry;
import net.minecraftforge.fml.common.registry.VillagerRegistry.VillagerProfession;
@ -442,6 +443,10 @@ public class GameData
@Override
public void onAdd(IForgeRegistryInternal<EntityEntry> owner, RegistryManager stage, int id, EntityEntry entry, @Nullable EntityEntry oldEntry)
{
if (entry instanceof EntityEntryBuilder.BuiltEntityEntry)
{
((EntityEntryBuilder.BuiltEntityEntry) entry).addedToRegistry();
}
if (entry.getEgg() != null)
EntityList.ENTITY_EGGS.put(entry.getRegistryName(), entry.getEgg());
}