Introduce entity entry builder, useful in the Register<EntityEntry> event replacing needed calls to EntityRegistry. (#4408)
This commit is contained in:
parent
50265786b2
commit
f2b07e8db1
5 changed files with 443 additions and 21 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue