Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Entity data mining #9

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
3 changes: 2 additions & 1 deletion src/main/java/org/mockbukkit/metaminer/MetaMiner.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.plugin.java.JavaPlugin;
import org.mockbukkit.metaminer.internal.entity.EntityTypeGenerator;
import org.mockbukkit.metaminer.internal.MaterialDataGenerator;
import org.mockbukkit.metaminer.internal.potion.PotionDataGenerator;
import org.mockbukkit.metaminer.internal.tags.InternalTagDataGenerator;
Expand Down Expand Up @@ -55,7 +56,7 @@ private List<DataGenerator> getDataGenerators()
return List.of(new KeyedDataGenerator(this.getDataFolder()), new InternalTagDataGenerator(this.getDataFolder()),
new PotionDataGenerator(this.getDataFolder()), new TagDataGenerator(this.getDataFolder()),
new MaterialDataGenerator(this.getDataFolder()), new RegistryKeyClassDataGenerator(this.getDataFolder()),
new TranslationDataGenerator(this.getDataFolder()));
new TranslationDataGenerator(this.getDataFolder()), new EntityTypeGenerator(this.getDataFolder()));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.mockbukkit.metaminer.internal.entity;

import com.google.gson.JsonObject;
import org.bukkit.entity.EntityType;

public interface EntityInformationExtractor
{

JsonObject extractInformation(EntityType entityType, net.minecraft.world.entity.EntityType<?> mojangEntityType);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.mockbukkit.metaminer.internal.entity;

public class EntityKeys
{
public static final String WIDTH = "width";
public static final String HEIGHT = "height";
public static final String SCALE = "scale";
public static final String EYE_HEIGHT = "eyeHeight";
public static final String STATES = "states";
public static final String BASE_DAMAGE = "baseDamage";

private EntityKeys()
{
// Hide the public constructor
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.mockbukkit.metaminer.internal.entity;

/**
* A type of a entity, it still has the same
* {@link org.bukkit.entity.EntityType}.
*/
public enum EntitySubType
{
/**
* The default subtype (adult or ageless)
*/
DEFAULT("default"),

/**
* Baby
*/
BABY("baby"),

/**
* Big slime/magma cube
*/
BIG("big"),

/**
* Medium slime/magma cube
*/
MEDIUM("medium"),

/**
* Small slime/magma cube
*/
SMALL("small");

private final String subTypeName;

EntitySubType(String subTypeName)
{
this.subTypeName = subTypeName;
}

/**
* @return The key this property is assigned to
*/
public String getName()
{
return this.subTypeName;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
package org.mockbukkit.metaminer.internal.entity;

import com.google.common.base.Preconditions;
import com.google.gson.JsonObject;
import net.kyori.adventure.key.Key;
import net.minecraft.world.entity.Entity;
import org.bukkit.NamespacedKey;
import org.bukkit.entity.Ageable;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Frog;
import org.bukkit.entity.Parrot;
import org.bukkit.entity.PiglinBrute;
import org.bukkit.entity.Slime;
import org.bukkit.entity.WanderingTrader;
import org.mockbukkit.metaminer.DataGenerator;
import org.mockbukkit.metaminer.internal.entity.extractor.BabyEntityInformationExtractor;
import org.mockbukkit.metaminer.internal.entity.extractor.BigEntityInformationExtractor;
import org.mockbukkit.metaminer.internal.entity.extractor.DefaultEntityInformationExtractor;
import org.mockbukkit.metaminer.internal.entity.extractor.MediumEntityInformationExtractor;
import org.mockbukkit.metaminer.internal.entity.extractor.SmallEntityInformationExtractor;
import org.mockbukkit.metaminer.util.JsonUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.stream.Collectors;

public class EntityTypeGenerator implements DataGenerator
{
private static final Logger LOGGER = LoggerFactory.getLogger(EntityTypeGenerator.class);

private final File workDirectory;

public EntityTypeGenerator(File workDirectory)
{
Preconditions.checkNotNull(workDirectory, "The work directory can't be null!");
this.workDirectory = workDirectory;
}

@Override
public void generateData() throws IOException
{
File entitiesDirectory = new File(workDirectory, "entities");
Map<String, net.minecraft.world.entity.EntityType<?>> entities = getEntities();

int entityCounter = 0;
int entityTotal = entities.size();

for (Map.Entry<String, net.minecraft.world.entity.EntityType<? extends Entity>> entry : entities.entrySet()) {

String name = entry.getKey();
net.minecraft.world.entity.EntityType<? extends Entity> mojangEntityType = entry.getValue();
if (mojangEntityType == null)
{
throw new IllegalArgumentException(String.format("Entity with name %s was not found in minecraft entity. Possible values are: %s",
name, entities.keySet()));
}

EntityType entityType = EntityType.fromName(name);
if (entityType == null)
{
throw new IllegalArgumentException(String.format("Entity with name %s was not found in bukkit entity. Possible values are: %s",
name, Set.of(EntityType.values()).stream()
.map(EntityType::getKey)
.map(NamespacedKey::getKey)
.collect(Collectors.toSet())));
}

if (LOGGER.isInfoEnabled())
{
LOGGER.info("[{}/{}] Working on entity {}...", String.format("%3d", ++entityCounter), entityTotal, name);
}

JsonObject root = extractInformation(entityType, mojangEntityType);

String fileName = String.format("%s.json", name);
File file = new File(entitiesDirectory, fileName);
JsonUtil.dump(root, file);
}
}

private JsonObject extractInformation(EntityType entityType, net.minecraft.world.entity.EntityType<?> mojangEntityType)
{
JsonObject root = new JsonObject();

Set<EntitySubType> possibleStates = getPossibleStatesForEntity(entityType, mojangEntityType);
for (EntitySubType state : possibleStates) {
switch (state)
{
case DEFAULT -> root.add(EntitySubType.DEFAULT.getName(), DefaultEntityInformationExtractor.process(entityType, mojangEntityType));
case BABY -> root.add(EntitySubType.BABY.getName(), BabyEntityInformationExtractor.process(entityType, mojangEntityType));
case SMALL -> root.add(EntitySubType.SMALL.getName(), SmallEntityInformationExtractor.process(entityType, mojangEntityType));
case MEDIUM -> root.add(EntitySubType.MEDIUM.getName(), MediumEntityInformationExtractor.process(entityType, mojangEntityType));
case BIG -> root.add(EntitySubType.BIG.getName(), BigEntityInformationExtractor.process(entityType, mojangEntityType));
default -> throw new UnsupportedOperationException("Unknown entity state: " + state);
}
}

return root;
}

private static Set<EntitySubType> getPossibleStatesForEntity(EntityType entityType, net.minecraft.world.entity.EntityType<?> mojangEntityType)
{
Class<? extends org.bukkit.entity.Entity> entityClass = entityType.getEntityClass();
if (entityClass == null)
{
throw new IllegalArgumentException(String.format("Entity type %s does not have a entity class", entityType.name()));
}

Set<EntitySubType> possibleStates = new HashSet<>();

// Add the default
possibleStates.add(EntitySubType.DEFAULT);

// Add the baby state
if (Ageable.class.isAssignableFrom(entityClass))
{
Set<Class<?>> ignoredClasses = Set.of(
// Frog is a special mob, unlike all other mobs with baby variants, the tadpole is treated
// by the game as a completely different mob from the frog.
Frog.class,
// Parrot's don't breed and don't have baby variant
Parrot.class,
// Piglin Brute don't breed and don't have baby variant
PiglinBrute.class,
// Wandering Trader don't breed and don't have baby variant
WanderingTrader.class
);
if (ignoredClasses.stream().noneMatch(c -> c.isAssignableFrom(entityClass))) {
possibleStates.add(EntitySubType.BABY);
}
}

// Add the armor stand
if (ArmorStand.class.isAssignableFrom(entityClass))
{
possibleStates.add(EntitySubType.SMALL);
}

// Add the Slime/Magma cube
if (Slime.class.isAssignableFrom(entityClass))
{
possibleStates.add(EntitySubType.BIG);
possibleStates.add(EntitySubType.MEDIUM);
possibleStates.add(EntitySubType.SMALL);

possibleStates.remove(EntitySubType.DEFAULT);
}

return possibleStates;
}

public static Map<String, net.minecraft.world.entity.EntityType<? extends Entity>> getEntities() {

Map<String, net.minecraft.world.entity.EntityType<?>> entityTypes = new TreeMap<>();

for (EntityType entityType : EntityType.values()) {

if (EntityType.UNKNOWN.equals(entityType))
{
// Unknown entity should be ignored
continue;
}

NamespacedKey key = entityType.getKey();
if (!Key.MINECRAFT_NAMESPACE.equalsIgnoreCase(key.getNamespace()))
{
throw new IllegalArgumentException(String.format("Entity type %s has namespace %s while the expected value would be %s",
key.getNamespace(), key, Key.MINECRAFT_NAMESPACE));
}

String entityName =key.getKey();
Optional<net.minecraft.world.entity.EntityType<?>> minecraftEntityType = net.minecraft.world.entity.EntityType.byString(entityName);
if (minecraftEntityType.isEmpty())
{
throw new IllegalArgumentException(String.format("Entity %s was not found in minecraft registry.", entityName));
}
entityTypes.put(entityName, minecraftEntityType.get());
}

return entityTypes;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.mockbukkit.metaminer.internal.entity.extractor;

import com.google.gson.JsonObject;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.animal.armadillo.Armadillo;
import net.minecraft.world.entity.animal.camel.Camel;
import org.bukkit.entity.EntityType;

public class BabyEntityInformationExtractor
{

public static JsonObject process(EntityType entityType, net.minecraft.world.entity.EntityType<?> mojangEntityType)
{
double scale = getBabyScaleAsDouble(entityType, mojangEntityType);
return ScaleEntityInformationExtractor.process(entityType, mojangEntityType, scale);
}

private static <T extends Entity> double getBabyScaleAsDouble(EntityType entityType, net.minecraft.world.entity.EntityType<T> mojangEntityType)
{
return switch (entityType)
{
case ARMADILLO -> Armadillo.BABY_SCALE;
case TURTLE -> 0.30D; // Constant is not public
case CAMEL -> Camel.BABY_SCALE;
default -> LivingEntity.DEFAULT_BABY_SCALE;
};
}

private BabyEntityInformationExtractor()
{
// Hide the public constructor
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.mockbukkit.metaminer.internal.entity.extractor;

import org.bukkit.entity.EntityType;

public class BaseDamageInformationExtractor
{
private BaseDamageInformationExtractor()
{
// Hide public constructor
}

public static double process(EntityType entityType)
{
if (EntityType.ARROW.equals(entityType) || EntityType.SPECTRAL_ARROW.equals(entityType))
{
return 2.0D;
}
if (EntityType.TRIDENT.equals(entityType))
{
return 8.0D;
}

String className = (entityType.getEntityClass() != null ? entityType.getEntityClass().getName() : "null");
throw new UnsupportedOperationException(String.format("Unknown base damage for entity %s (%s)", entityType.name(), className));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.mockbukkit.metaminer.internal.entity.extractor;

import com.google.gson.JsonObject;
import org.bukkit.entity.EntityType;

public class BigEntityInformationExtractor
{

public static JsonObject process(EntityType entityType, net.minecraft.world.entity.EntityType<?> mojangEntityType)
{
return ScaleEntityInformationExtractor.process(entityType, mojangEntityType, 4);
}

private BigEntityInformationExtractor()
{
// Hide the public constructor
}

}
Loading