mirror of
https://github.com/italicsjenga/valence.git
synced 2024-12-23 14:31:30 +11:00
Extract default values for entity fields
This commit is contained in:
parent
b9fca3503c
commit
37b285209f
|
@ -0,0 +1,42 @@
|
|||
package dev._00a.valence_extractor;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.data.DataTracker;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.network.encryption.PlayerPublicKey;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.World;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class DummyPlayerEntity extends PlayerEntity {
|
||||
public static final DummyPlayerEntity INSTANCE;
|
||||
|
||||
static {
|
||||
INSTANCE = Util.magicallyInstantiate(DummyPlayerEntity.class);
|
||||
|
||||
try {
|
||||
var dataTrackerField = Entity.class.getDeclaredField("dataTracker");
|
||||
dataTrackerField.setAccessible(true);
|
||||
dataTrackerField.set(INSTANCE, new DataTracker(INSTANCE));
|
||||
|
||||
INSTANCE.initDataTracker();
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private DummyPlayerEntity(World world, BlockPos pos, float yaw, GameProfile gameProfile, @Nullable PlayerPublicKey publicKey) {
|
||||
super(world, pos, yaw, gameProfile, publicKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSpectator() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCreative() {
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,255 @@
|
|||
package dev._00a.valence_extractor;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.fluid.Fluid;
|
||||
import net.minecraft.item.map.MapState;
|
||||
import net.minecraft.recipe.RecipeManager;
|
||||
import net.minecraft.scoreboard.Scoreboard;
|
||||
import net.minecraft.sound.SoundCategory;
|
||||
import net.minecraft.sound.SoundEvent;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.Direction;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
import net.minecraft.util.math.random.Random;
|
||||
import net.minecraft.util.profiler.Profiler;
|
||||
import net.minecraft.util.registry.DynamicRegistryManager;
|
||||
import net.minecraft.util.registry.RegistryEntry;
|
||||
import net.minecraft.util.registry.RegistryKey;
|
||||
import net.minecraft.world.Difficulty;
|
||||
import net.minecraft.world.GameRules;
|
||||
import net.minecraft.world.MutableWorldProperties;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.world.biome.Biome;
|
||||
import net.minecraft.world.chunk.ChunkManager;
|
||||
import net.minecraft.world.dimension.DimensionType;
|
||||
import net.minecraft.world.entity.EntityLookup;
|
||||
import net.minecraft.world.event.GameEvent;
|
||||
import net.minecraft.world.tick.QueryableTickScheduler;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class DummyWorld extends World {
|
||||
|
||||
public static final DummyWorld INSTANCE;
|
||||
|
||||
static {
|
||||
INSTANCE = Util.magicallyInstantiate(DummyWorld.class);
|
||||
|
||||
try {
|
||||
var randomField = World.class.getDeclaredField("random");
|
||||
randomField.setAccessible(true);
|
||||
randomField.set(INSTANCE, Random.create());
|
||||
|
||||
var propertiesField = World.class.getDeclaredField("properties");
|
||||
propertiesField.setAccessible(true);
|
||||
propertiesField.set(INSTANCE, new DummyMutableWorldProperties());
|
||||
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private DummyWorld(MutableWorldProperties properties, RegistryKey<World> registryRef, RegistryEntry<DimensionType> dimension, Supplier<Profiler> profiler, boolean isClient, boolean debugWorld, long seed, int maxChainedNeighborUpdates) {
|
||||
super(properties, registryRef, dimension, profiler, isClient, debugWorld, seed, maxChainedNeighborUpdates);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListeners(BlockPos pos, BlockState oldState, BlockState newState, int flags) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playSound(@Nullable PlayerEntity except, double x, double y, double z, SoundEvent sound, SoundCategory category, float volume, float pitch, long seed) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playSoundFromEntity(@Nullable PlayerEntity except, Entity entity, SoundEvent sound, SoundCategory category, float volume, float pitch, long seed) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String asString() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Entity getEntityById(int id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public MapState getMapState(String id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putMapState(String id, MapState state) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNextMapId() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlockBreakingInfo(int entityId, BlockPos pos, int progress) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Scoreboard getScoreboard() {
|
||||
return new Scoreboard();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecipeManager getRecipeManager() {
|
||||
return new RecipeManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected EntityLookup<Entity> getEntityLookup() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryableTickScheduler<Block> getBlockTickScheduler() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryableTickScheduler<Fluid> getFluidTickScheduler() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChunkManager getChunkManager() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncWorldEvent(@Nullable PlayerEntity player, int eventId, BlockPos pos, int data) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void emitGameEvent(GameEvent event, Vec3d emitterPos, GameEvent.Emitter emitter) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public DynamicRegistryManager getRegistryManager() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getBrightness(Direction direction, boolean shaded) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends PlayerEntity> getPlayers() {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegistryEntry<Biome> getGeneratorStoredBiome(int biomeX, int biomeY, int biomeZ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class DummyMutableWorldProperties implements MutableWorldProperties {
|
||||
|
||||
@Override
|
||||
public int getSpawnX() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSpawnX(int spawnX) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSpawnY() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSpawnY(int spawnY) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSpawnZ() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSpawnZ(int spawnZ) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getSpawnAngle() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSpawnAngle(float spawnAngle) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTime() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimeOfDay() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isThundering() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRaining() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRaining(boolean raining) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHardcore() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GameRules getGameRules() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Difficulty getDifficulty() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDifficultyLocked() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,12 +2,26 @@ package dev._00a.valence_extractor;
|
|||
|
||||
import com.google.gson.*;
|
||||
import net.fabricmc.api.ModInitializer;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityPose;
|
||||
import net.minecraft.entity.EntityType;
|
||||
import net.minecraft.entity.data.DataTracker;
|
||||
import net.minecraft.entity.data.TrackedData;
|
||||
import net.minecraft.entity.data.TrackedDataHandlerRegistry;
|
||||
import net.minecraft.entity.passive.CatVariant;
|
||||
import net.minecraft.entity.passive.FrogVariant;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NbtCompound;
|
||||
import net.minecraft.particle.ParticleEffect;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.Direction;
|
||||
import net.minecraft.util.math.EulerAngle;
|
||||
import net.minecraft.util.math.GlobalPos;
|
||||
import net.minecraft.util.registry.Registry;
|
||||
import net.minecraft.util.registry.RegistryEntry;
|
||||
import net.minecraft.village.VillagerData;
|
||||
import net.minecraft.world.EmptyBlockView;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -19,8 +33,7 @@ import java.nio.charset.StandardCharsets;
|
|||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.*;
|
||||
|
||||
public class Extractor implements ModInitializer {
|
||||
public static final String MOD_ID = "valence_extractor";
|
||||
|
@ -28,6 +41,90 @@ public class Extractor implements ModInitializer {
|
|||
private Gson gson;
|
||||
private Path outputDirectory;
|
||||
|
||||
private static JsonElement trackedDataToJson(Object data) {
|
||||
if (data instanceof BlockPos bp) {
|
||||
var json = new JsonObject();
|
||||
json.addProperty("x", bp.getX());
|
||||
json.addProperty("y", bp.getY());
|
||||
json.addProperty("z", bp.getZ());
|
||||
return json;
|
||||
} else if (data instanceof Boolean b) {
|
||||
return new JsonPrimitive(b);
|
||||
} else if (data instanceof Byte b) {
|
||||
return new JsonPrimitive(b);
|
||||
} else if (data instanceof CatVariant cv) {
|
||||
return new JsonPrimitive(Registry.CAT_VARIANT.getId(cv).getPath());
|
||||
} else if (data instanceof EntityPose ep) {
|
||||
return new JsonPrimitive(ep.toString());
|
||||
} else if (data instanceof Direction d) {
|
||||
return new JsonPrimitive(d.toString());
|
||||
} else if (data instanceof Float f) {
|
||||
return new JsonPrimitive(f);
|
||||
} else if (data instanceof FrogVariant fv) {
|
||||
return new JsonPrimitive(Registry.FROG_VARIANT.getId(fv).getPath());
|
||||
} else if (data instanceof Integer i) {
|
||||
return new JsonPrimitive(i);
|
||||
} else if (data instanceof ItemStack is) {
|
||||
// TODO
|
||||
return new JsonPrimitive(is.toString());
|
||||
} else if (data instanceof NbtCompound nbt) {
|
||||
// TODO: base64 binary representation or SNBT?
|
||||
return new JsonPrimitive(nbt.toString());
|
||||
} else if (data instanceof Optional<?> opt) {
|
||||
var inner = opt.orElse(null);
|
||||
if (inner == null) {
|
||||
return null;
|
||||
} else if (inner instanceof BlockPos) {
|
||||
return Extractor.trackedDataToJson(inner);
|
||||
} else if (inner instanceof BlockState bs) {
|
||||
// TODO: get raw block state ID.
|
||||
return new JsonPrimitive(bs.toString());
|
||||
} else if (inner instanceof GlobalPos gp) {
|
||||
var json = new JsonObject();
|
||||
json.addProperty("dimension", gp.getDimension().getValue().toString());
|
||||
|
||||
var posJson = new JsonObject();
|
||||
posJson.addProperty("x", gp.getPos().getX());
|
||||
posJson.addProperty("y", gp.getPos().getY());
|
||||
posJson.addProperty("z", gp.getPos().getZ());
|
||||
|
||||
json.add("position", posJson);
|
||||
return json;
|
||||
} else if (inner instanceof Text) {
|
||||
return Extractor.trackedDataToJson(inner);
|
||||
} else if (inner instanceof UUID uuid) {
|
||||
return new JsonPrimitive(uuid.toString());
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown tracked optional type " + inner.getClass().getName());
|
||||
}
|
||||
} else if (data instanceof OptionalInt oi) {
|
||||
return oi.isPresent() ? new JsonPrimitive(oi.getAsInt()) : null;
|
||||
} else if (data instanceof RegistryEntry<?> re) {
|
||||
return new JsonPrimitive(re.getKey().map(k -> k.getValue().getPath()).orElse(""));
|
||||
} else if (data instanceof ParticleEffect pe) {
|
||||
return new JsonPrimitive(pe.asString());
|
||||
} else if (data instanceof EulerAngle ea) {
|
||||
var json = new JsonObject();
|
||||
json.addProperty("yaw", ea.getYaw());
|
||||
json.addProperty("pitch", ea.getPitch());
|
||||
json.addProperty("roll", ea.getRoll());
|
||||
return json;
|
||||
} else if (data instanceof String s) {
|
||||
return new JsonPrimitive(s);
|
||||
} else if (data instanceof Text t) {
|
||||
// TODO: return text as json element.
|
||||
return new JsonPrimitive(t.getString());
|
||||
} else if (data instanceof VillagerData vd) {
|
||||
var json = new JsonObject();
|
||||
json.addProperty("level", vd.getLevel());
|
||||
json.addProperty("type", vd.getType().toString());
|
||||
json.addProperty("profession", vd.getProfession().toString());
|
||||
return json;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Unexpected tracked type " + data.getClass().getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
LOGGER.info("Starting extractor...");
|
||||
|
@ -43,11 +140,11 @@ public class Extractor implements ModInitializer {
|
|||
System.exit(1);
|
||||
}
|
||||
|
||||
LOGGER.info("Extractor finished successfully");
|
||||
LOGGER.info("Extractor finished successfully.");
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
void extractBlocks() throws IOException {
|
||||
private void extractBlocks() throws IOException {
|
||||
var blocksJson = new JsonArray();
|
||||
var stateIdCounter = 0;
|
||||
|
||||
|
@ -110,37 +207,45 @@ public class Extractor implements ModInitializer {
|
|||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
void extractEntities() throws IOException, IllegalAccessException {
|
||||
var entitiesJson = new JsonArray();
|
||||
var entityClasses = new HashSet<Class<? extends Entity>>();
|
||||
private void extractEntities() throws IOException, IllegalAccessException, NoSuchFieldException {
|
||||
final var entitiesJson = new JsonArray();
|
||||
final var entityClasses = new HashSet<Class<? extends Entity>>();
|
||||
|
||||
final var dummyWorld = DummyWorld.INSTANCE;
|
||||
|
||||
for (var f : EntityType.class.getFields()) {
|
||||
if (f.getType().equals(EntityType.class)) {
|
||||
var entityType = (EntityType<?>) f.get(null);
|
||||
var entityClass = (Class<? extends Entity>) ((ParameterizedType) f.getGenericType()).getActualTypeArguments()[0];
|
||||
|
||||
var entityJson = new JsonObject();
|
||||
while (entityClasses.add(entityClass)) {
|
||||
entityJson.addProperty("class", entityClass.getSimpleName());
|
||||
// While we can use the tracked data registry and reflection to get the tracked fields on entities, we won't know what their default values are because they are assigned in the entity's constructor.
|
||||
// To obtain this, we create a dummy world to spawn the entities into and then read the data tracker field from the base entity class.
|
||||
// We also handle player entities specially since they cannot be spawned with EntityType#create.
|
||||
final var entityInstance = entityType.equals(EntityType.PLAYER) ? DummyPlayerEntity.INSTANCE : entityType.create(dummyWorld);
|
||||
|
||||
if (entityType != null) {
|
||||
entityJson.addProperty("translation_key", entityType.getTranslationKey());
|
||||
} else {
|
||||
entityJson.add("translation_key", null);
|
||||
}
|
||||
var dataTrackerField = Entity.class.getDeclaredField("dataTracker");
|
||||
dataTrackerField.setAccessible(true);
|
||||
|
||||
while (entityClasses.add(entityClass)) {
|
||||
var entityJson = new JsonObject();
|
||||
entityJson.addProperty("class", entityClass.getSimpleName());
|
||||
entityJson.add("translation_key", entityType != null ? new JsonPrimitive(entityType.getTranslationKey()) : null);
|
||||
|
||||
var fieldsJson = new JsonArray();
|
||||
for (var entityField : entityClass.getDeclaredFields()) {
|
||||
if (entityField.getType().equals(TrackedData.class)) {
|
||||
entityField.setAccessible(true);
|
||||
|
||||
var data = (TrackedData<? extends Entity>) entityField.get(null);
|
||||
var data = (TrackedData<?>) entityField.get(null);
|
||||
|
||||
var fieldJson = new JsonObject();
|
||||
fieldJson.addProperty("name", entityField.getName().toLowerCase(Locale.ROOT));
|
||||
fieldJson.addProperty("index", data.getId());
|
||||
fieldJson.addProperty("type_id", TrackedDataHandlerRegistry.getId(data.getType()));
|
||||
|
||||
var dataTracker = (DataTracker) dataTrackerField.get(entityInstance);
|
||||
fieldJson.add("default_value", Extractor.trackedDataToJson(dataTracker.get(data)));
|
||||
|
||||
fieldsJson.add(fieldJson);
|
||||
}
|
||||
}
|
||||
|
@ -156,9 +261,6 @@ public class Extractor implements ModInitializer {
|
|||
|
||||
entityClass = (Class<? extends Entity>) parent;
|
||||
entityType = null;
|
||||
}
|
||||
|
||||
if (entityJson.size() > 0) {
|
||||
entitiesJson.add(entityJson);
|
||||
}
|
||||
}
|
||||
|
@ -167,7 +269,7 @@ public class Extractor implements ModInitializer {
|
|||
writeJsonFile("entities.json", entitiesJson);
|
||||
}
|
||||
|
||||
void writeJsonFile(String fileName, JsonElement element) throws IOException {
|
||||
private void writeJsonFile(String fileName, JsonElement element) throws IOException {
|
||||
var out = outputDirectory.resolve(fileName);
|
||||
var fileWriter = new FileWriter(out.toFile(), StandardCharsets.UTF_8);
|
||||
gson.toJson(element, fileWriter);
|
||||
|
|
19
extractor/src/main/java/dev/_00a/valence_extractor/Util.java
Normal file
19
extractor/src/main/java/dev/_00a/valence_extractor/Util.java
Normal file
|
@ -0,0 +1,19 @@
|
|||
package dev._00a.valence_extractor;
|
||||
|
||||
import sun.reflect.ReflectionFactory;
|
||||
|
||||
public class Util {
|
||||
/**
|
||||
* Magically creates an instance of a <i>concrete</i> class without calling its constructor.
|
||||
*/
|
||||
public static <T> T magicallyInstantiate(Class<T> clazz) {
|
||||
var rf = ReflectionFactory.getReflectionFactory();
|
||||
try {
|
||||
var objCon = Object.class.getDeclaredConstructor();
|
||||
var con = rf.newConstructorForSerialization(clazz, objCon);
|
||||
return clazz.cast(con.newInstance());
|
||||
} catch (Throwable e) {
|
||||
throw new IllegalArgumentException("Failed to magically instantiate " + clazz.getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue