diff --git a/build.gradle b/build.gradle index 9d749c73..bb9fa4de 100644 --- a/build.gradle +++ b/build.gradle @@ -53,7 +53,8 @@ dependencies { // tasmod dependencies embed "com.dselent:bigarraylist:1.6" embed "com.github.KaptainWutax:SeedUtils:b6a383113c" - + implementation "com.google.code.gson:gson:2.14.0" + // loom dependencies minecraft "com.mojang:minecraft:${project.minecraft_version}" mappings ploceus.mcpMappings(project.mcp_channel, project.mcp_mcversion, project.mcp_build) diff --git a/src/main/java/com/minecrafttas/tasmod/TASmod.java b/src/main/java/com/minecrafttas/tasmod/TASmod.java index 3f05ad30..d6ae3a97 100644 --- a/src/main/java/com/minecrafttas/tasmod/TASmod.java +++ b/src/main/java/com/minecrafttas/tasmod/TASmod.java @@ -31,6 +31,7 @@ import com.minecrafttas.tasmod.handlers.PlayUntilHandler; import com.minecrafttas.tasmod.ktrng.GlobalRNG; import com.minecrafttas.tasmod.ktrng.builtin.MathRNG; +import com.minecrafttas.tasmod.ktrng.builtin.SquidRNG; import com.minecrafttas.tasmod.ktrng.builtin.WorldSeedRNG; import com.minecrafttas.tasmod.ktrng.events.KillTheRNGMonitor; import com.minecrafttas.tasmod.ktrng.handlers.UUIDHandler; @@ -42,6 +43,7 @@ import com.minecrafttas.tasmod.savestates.handlers.SavestateGuiHandlerServer; import com.minecrafttas.tasmod.savestates.handlers.SavestateResourcePackHandler; import com.minecrafttas.tasmod.savestates.storage.builtin.ClientMotionStorage; +import com.minecrafttas.tasmod.savestates.storage.builtin.EntityAiTaskStorage; import com.minecrafttas.tasmod.savestates.storage.builtin.EntityBatSpawnPositionStorage; import com.minecrafttas.tasmod.savestates.storage.builtin.EntitySquidRotationStorage; import com.minecrafttas.tasmod.savestates.storage.builtin.EntityTickTimersStorage; @@ -105,11 +107,14 @@ public class TASmod implements ModInitializer, EventServerStart, EventServerInit public static EntityTickTimersStorage entityTickTimers = new EntityTickTimersStorage(); public static EntityBatSpawnPositionStorage entityBatSpawnPositionStorage = new EntityBatSpawnPositionStorage(); public static EntitySquidRotationStorage entitySquidRotationStorage = new EntitySquidRotationStorage(); + public static EntityAiTaskStorage entityAiTaskStorage = new EntityAiTaskStorage(); public static MathRNG mathRandomness = new MathRNG(0); public static WorldSeedRNG worldSeedRandomness = new WorldSeedRNG(0); + public static SquidRNG squidRNG = new SquidRNG(0L); + public static KillTheRNGMonitor debugRand = new KillTheRNGMonitor(); public static UUIDHandler uuidHandler = new UUIDHandler(); @@ -173,6 +178,7 @@ public void onServerStart(MinecraftServer server) { EventListenerRegistry.register(globalRandomness); } mathRandomness = new MathRNG(0); + squidRNG.setSeed(globalRandomness.getCurrentSeed() + 1000L); } @Override @@ -242,6 +248,7 @@ private void registerSavestateStorage() { TASmodAPIRegistry.SAVESTATE_STORAGE.register(entityTickTimers); TASmodAPIRegistry.SAVESTATE_STORAGE.register(entityBatSpawnPositionStorage); TASmodAPIRegistry.SAVESTATE_STORAGE.register(entitySquidRotationStorage); + TASmodAPIRegistry.SAVESTATE_STORAGE.register(entityAiTaskStorage); } public static MinecraftServer getServerInstance() { diff --git a/src/main/java/com/minecrafttas/tasmod/ktrng/builtin/SquidRNG.java b/src/main/java/com/minecrafttas/tasmod/ktrng/builtin/SquidRNG.java new file mode 100644 index 00000000..0eb6ba29 --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/ktrng/builtin/SquidRNG.java @@ -0,0 +1,16 @@ +package com.minecrafttas.tasmod.ktrng.builtin; + +import com.minecrafttas.tasmod.ktrng.RandomBase; + +public class SquidRNG extends RandomBase { + + public SquidRNG(long l) { + super(l); + } + + @Override + public String getExtensionName() { + return "SquidRNG"; + } + +} diff --git a/src/main/java/com/minecrafttas/tasmod/mixin/killtherng/MixinEntitySquid.java b/src/main/java/com/minecrafttas/tasmod/mixin/killtherng/MixinEntitySquid.java index b719bd42..f0658676 100644 --- a/src/main/java/com/minecrafttas/tasmod/mixin/killtherng/MixinEntitySquid.java +++ b/src/main/java/com/minecrafttas/tasmod/mixin/killtherng/MixinEntitySquid.java @@ -6,6 +6,7 @@ import org.spongepowered.asm.mixin.injection.At; import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; +import com.minecrafttas.tasmod.TASmod; import net.minecraft.entity.passive.EntitySquid; @@ -14,6 +15,9 @@ public class MixinEntitySquid { @WrapWithCondition(method = "", at = @At(value = "INVOKE", target = "Ljava/util/Random;setSeed(J)V")) public boolean remove_setSeed(Random rand, long seed) { + if (TASmod.squidRNG.nextInt(1000) == 0) { + TASmod.LOGGER.error("SQUIDS ARE EVIL!!"); + } return false; } } diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/storage/SavestateStorageExtensionBase.java b/src/main/java/com/minecrafttas/tasmod/savestates/storage/SavestateStorageExtensionBase.java index 3e1b5b3a..3c70ebee 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/storage/SavestateStorageExtensionBase.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/storage/SavestateStorageExtensionBase.java @@ -15,12 +15,17 @@ public abstract class SavestateStorageExtensionBase implements Registerable { protected final Logger logger = TASmod.LOGGER; - protected final Gson json = JsonUtils.getJsonInstance(); + protected final Gson gsonInstance; public final Path fileName; - public SavestateStorageExtensionBase(String fileName) { + public SavestateStorageExtensionBase(String filename) { + this(filename, JsonUtils.getGsonInstance()); + } + + public SavestateStorageExtensionBase(String fileName, Gson gson) { this.fileName = Paths.get(fileName); + this.gsonInstance = gson; } public abstract JsonObject onSavestate(MinecraftServer server, JsonObject dataToSave); diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/storage/SavestateStorageExtensionRegistry.java b/src/main/java/com/minecrafttas/tasmod/savestates/storage/SavestateStorageExtensionRegistry.java index cdab59a6..ead636c2 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/storage/SavestateStorageExtensionRegistry.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/storage/SavestateStorageExtensionRegistry.java @@ -43,7 +43,7 @@ public void onServerSavestate(MinecraftServer server, SavestatePaths paths) { JsonObject dataToSave = storage.onSavestate(server, new JsonObject()); try { - JsonUtils.saveJson(dataPath, dataToSave); + JsonUtils.saveJson(dataPath, dataToSave, storage.gsonInstance); } catch (IOException e) { throw new SavestateException(e, "Can't save %s from %s extension", storage.fileName, storage.getExtensionName()); } @@ -71,7 +71,7 @@ private void load(SavestatePaths paths) { JsonObject loadedData; try { - loadedData = JsonUtils.loadJson(dataPath); + loadedData = JsonUtils.loadJson(dataPath, storage.gsonInstance); } catch (IOException e) { throw new LoadstateException(e, "Can't load %s in %s extension", storage.fileName, storage.getExtensionName()); } diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/ClientMotionStorage.java b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/ClientMotionStorage.java index 81f89e55..a3938659 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/ClientMotionStorage.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/ClientMotionStorage.java @@ -74,7 +74,7 @@ public JsonObject onSavestate(MinecraftServer server, JsonObject dataToSave) { if (player.getName().equals(server.getServerOwner())) { uuid = "singleplayer"; } - dataToSave.add(uuid, json.toJsonTree(data)); + dataToSave.add(uuid, gsonInstance.toJsonTree(data)); } catch (TimeoutException e) { throw new SavestateException(e, "Writing client motion for %s timed out!", player.getName()); @@ -92,7 +92,7 @@ public void onLoadstatePost(MinecraftServer server, JsonObject loadedData) { for (Entry motionDataJsonElement : loadedData.entrySet()) { String playerUUID = motionDataJsonElement.getKey(); - MotionData motionData = json.fromJson(motionDataJsonElement.getValue(), MotionData.class); + MotionData motionData = gsonInstance.fromJson(motionDataJsonElement.getValue(), MotionData.class); EntityPlayerMP player; if (playerUUID.equals("singleplayer")) { diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/EntityAiTaskStorage.java b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/EntityAiTaskStorage.java new file mode 100644 index 00000000..c9f1dfb2 --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/EntityAiTaskStorage.java @@ -0,0 +1,232 @@ +package com.minecrafttas.tasmod.savestates.storage.builtin; + +import java.util.LinkedHashSet; +import java.util.Map.Entry; +import java.util.Set; +import java.util.UUID; + +import org.spongepowered.asm.mixin.Unique; + +import com.google.common.base.Predicate; +import com.google.gson.ExclusionStrategy; +import com.google.gson.FieldAttributes; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.minecrafttas.tasmod.savestates.exceptions.SavestateException; +import com.minecrafttas.tasmod.savestates.storage.SavestateStorageExtensionBase; +import com.minecrafttas.tasmod.savestates.typeadapters.BlockPosTypeAdapterFactory; +import com.minecrafttas.tasmod.savestates.typeadapters.EntityAINearestAttackableTargetTypeAdapterFactory; +import com.minecrafttas.tasmod.savestates.typeadapters.EntityClassTypeAdapterFactory; +import com.minecrafttas.tasmod.savestates.typeadapters.EntityLivingTypeAdapter; +import com.minecrafttas.tasmod.savestates.typeadapters.EntityTypeAdapterFactory; +import com.minecrafttas.tasmod.savestates.typeadapters.ItemTypeAdapter; +import com.minecrafttas.tasmod.savestates.typeadapters.WorldTypeAdapterFactory; +import com.minecrafttas.tasmod.savestates.typeadapters.util.ClassExclusionStrategy; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.ai.EntityAIAvoidEntity; +import net.minecraft.entity.ai.EntityAIBase; +import net.minecraft.entity.ai.EntityAINearestAttackableTarget; +import net.minecraft.entity.ai.EntityAITasks; +import net.minecraft.entity.ai.EntityAITasks.EntityAITaskEntry; +import net.minecraft.item.Item; +import net.minecraft.pathfinding.PathNavigate; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.WorldServer; + +public class EntityAiTaskStorage extends SavestateStorageExtensionBase { + + public EntityAiTaskStorage() { + //@formatter:off + super("entityAi.json", + new GsonBuilder() + .setPrettyPrinting() + .registerTypeAdapterFactory( + new EntityTypeAdapterFactory() + ) + .registerTypeAdapterFactory( + new WorldTypeAdapterFactory() + ) + .registerTypeAdapterFactory( + new EntityClassTypeAdapterFactory() + ) + .registerTypeAdapterFactory(new EntityAINearestAttackableTargetTypeAdapterFactory()) + .registerTypeAdapterFactory( + new BlockPosTypeAdapterFactory() + ) + .registerTypeAdapter(Item.class, new ItemTypeAdapter()) + .registerTypeAdapter(EntityLivingTypeAdapter.class, new EntityLivingTypeAdapter()) + .setExclusionStrategies(new ExclusionStrategy() { + + @Override + public boolean shouldSkipField(FieldAttributes f) { + return f.getAnnotation(Unique.class) != null; + } + + @Override + public boolean shouldSkipClass(Class c) { + return false; + } + }, + new ClassExclusionStrategy(Predicate.class), + new ClassExclusionStrategy(PathNavigate.class), + new ClassExclusionStrategy(EntityLivingBase.class, EntityAINearestAttackableTarget.class) + ) + .create()); + //@formatter:on + } + + @Override + public String getExtensionName() { + return "EntityAI"; + } + + @Override + public JsonObject onSavestate(MinecraftServer server, JsonObject dataToSave) { + for (WorldServer worldServer : server.worlds) { + for (Entity entity : worldServer.loadedEntityList) { + if (!(entity instanceof EntityLiving)) + continue; + + JsonObject mainObject = new JsonObject(); + EntityLiving entityLiving = (EntityLiving) entity; + + mainObject.addProperty("type", entity.getClass().getSimpleName()); + + mainObject.add("revengeTarget", gsonInstance.toJsonTree(entityLiving.getRevengeTarget())); + mainObject.add("lastAttackedEntity", gsonInstance.toJsonTree(entityLiving.getLastAttackedEntity())); + + EntityAITasks tasks = entityLiving.tasks; + mainObject.add("tasks", serialiseAITasks(tasks)); + + EntityAITasks targetTasks = entityLiving.targetTasks; + mainObject.add("targetTasks", serialiseAITasks(targetTasks)); + + dataToSave.add(entity.getUniqueID().toString(), mainObject); + } + } + return dataToSave; + } + + private JsonObject serialiseAITasks(EntityAITasks tasks) { + JsonObject out = new JsonObject(); + out.add("taskEntries", serialiseAIEntries(tasks.taskEntries)); + out.add("executingTaskEntries", serialiseAIEntries(tasks.executingTaskEntries)); + return out; + } + + private JsonArray serialiseAIEntries(Set taskEntries) { + JsonArray serialisedEntries = new JsonArray(); + for (EntityAITasks.EntityAITaskEntry entry : taskEntries) { + JsonObject jsonAiTaskEntry = new JsonObject(); + jsonAiTaskEntry.addProperty("priority", entry.priority); + jsonAiTaskEntry.addProperty("using", entry.using); + jsonAiTaskEntry.addProperty("class", entry.action.getClass().getName()); + try { + System.out.println(entry.action.getClass().getName()); + jsonAiTaskEntry.add("action", gsonInstance.toJsonTree(entry.action)); + } catch (Exception e) { + throw new SavestateException(e, "Could not serialise AI Task %s", entry.action.getClass().getName()); + } + serialisedEntries.add(jsonAiTaskEntry); + } + return serialisedEntries; + } + + @Override + public void onLoadstatePost(MinecraftServer server, JsonObject loadedData) { + for (Entry elements : loadedData.entrySet()) { + for (WorldServer worldServer : server.worlds) { + EntityLiving entityLiving = (EntityLiving) worldServer.getEntityFromUuid(UUID.fromString(elements.getKey())); + if (entityLiving == null) + continue; + + JsonObject mainObject = elements.getValue().getAsJsonObject(); + + entityLiving.setRevengeTarget((EntityLivingBase) gsonInstance.fromJson(mainObject.get("revengeTarget"), Entity.class)); + entityLiving.setLastAttackedEntity((EntityLivingBase) gsonInstance.fromJson(mainObject.get("lastAttackedEntity"), Entity.class)); + + deserialiseAITasks(entityLiving.tasks, mainObject.get("tasks").getAsJsonObject()); + deserialiseAITasks(entityLiving.targetTasks, mainObject.get("targetTasks").getAsJsonObject()); + } + } + } + + private void deserialiseAITasks(EntityAITasks tasks, JsonObject jsonTasks) { + mapDeserialisedEntries(tasks.taskEntries, deserialiseAIEntries(tasks, jsonTasks.get("taskEntries").getAsJsonArray())); + mapDeserialisedEntries(tasks.executingTaskEntries, deserialiseAIEntries(tasks, jsonTasks.get("executingTaskEntries").getAsJsonArray())); + } + + private Set deserialiseAIEntries(EntityAITasks tasks, JsonArray jsonEntries) { + Set out = new LinkedHashSet<>(); + for (JsonElement jsonEntryElement : jsonEntries) { + JsonObject jsonEntry = jsonEntryElement.getAsJsonObject(); + int priority = jsonEntry.get("priority").getAsInt(); + boolean using = jsonEntry.get("using").getAsBoolean(); + System.out.println(jsonEntry.get("class").getAsString()); + Class clazz; + try { + clazz = Class.forName(jsonEntry.get("class").getAsString(), false, getClass().getClassLoader()).asSubclass(EntityAIBase.class); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + return out; + } + + EntityAIBase action = gsonInstance.fromJson(jsonEntry.get("action"), clazz); + + EntityAITaskEntry entry = tasks.new EntityAITaskEntry(priority, action); + entry.using = using; + out.add(entry); + } + return out; + } + +// private EntityAIBase deserialiseAction(JsonElement json, Class clazz) { +// try { +// EntityAIBase base = clazz.newInstance(); +// } catch (InstantiationException | IllegalAccessException e) { +// e.printStackTrace(); +// } +// +// } + + private void mapDeserialisedEntries(Set tasks, Set deserialisedTasks) { + for (EntityAITaskEntry vanillaEntry : tasks) { + EntityAIBase vanillaAction = vanillaEntry.action; + for (EntityAITaskEntry deserialisedEntry : deserialisedTasks) { + if (vanillaAction.getClass().isInstance(deserialisedEntry.action)) { + EntityAIBase deserialisedAction = deserialisedEntry.action; + vanillaEntry.action = applyAction(vanillaAction, deserialisedAction); + break; + } + } + } + } + + @SuppressWarnings("unchecked") + private EntityAIBase applyAction(EntityAIBase vanillaAction, EntityAIBase deserialisedAction) { + if (vanillaAction instanceof EntityAIAvoidEntity) { + EntityAIAvoidEntity vanillaAIAvoidEntityAction = (EntityAIAvoidEntity) vanillaAction; + EntityAIAvoidEntity deserialisedAIAvoidEntityAction = (EntityAIAvoidEntity) deserialisedAction; + + deserialisedAIAvoidEntityAction.avoidTargetSelector = vanillaAIAvoidEntityAction.avoidTargetSelector; + deserialisedAIAvoidEntityAction.canBeSeenSelector = vanillaAIAvoidEntityAction.canBeSeenSelector; + return deserialisedAIAvoidEntityAction; + } + + if (vanillaAction instanceof EntityAINearestAttackableTarget) { + EntityAINearestAttackableTarget vanillaAINearestAttackableTarget = (EntityAINearestAttackableTarget) vanillaAction; + EntityAINearestAttackableTarget deserialisedAINearestAttackableTarget = (EntityAINearestAttackableTarget) deserialisedAction; + + deserialisedAINearestAttackableTarget.targetEntitySelector = vanillaAINearestAttackableTarget.targetEntitySelector; + + return deserialisedAINearestAttackableTarget; + } + + return deserialisedAction; + } +} diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/BlockPosTypeAdapterFactory.java b/src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/BlockPosTypeAdapterFactory.java new file mode 100644 index 00000000..28cd48a7 --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/BlockPosTypeAdapterFactory.java @@ -0,0 +1,41 @@ +package com.minecrafttas.tasmod.savestates.typeadapters; + +import java.io.IOException; + +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +import net.minecraft.util.math.BlockPos; + +public class BlockPosTypeAdapterFactory implements TypeAdapterFactory { + + @SuppressWarnings("unchecked") + @Override + public TypeAdapter create(Gson gson, TypeToken type) { + Class rawType = (Class) type.getRawType(); + if (!BlockPos.class.isAssignableFrom(rawType)) + return null; + return new TypeAdapter() { + @Override + public void write(JsonWriter out, T value) throws IOException { + if (value == null) { + out.nullValue(); + } + + BlockPos val = (BlockPos) value; + out.value(val.toLong()); + } + + @Override + public T read(JsonReader in) throws IOException { + if (!in.hasNext()) + return null; + return (T) BlockPos.fromLong(in.nextLong()); + } + }; + } +} diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/EntityAINearestAttackableTargetTypeAdapterFactory.java b/src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/EntityAINearestAttackableTargetTypeAdapterFactory.java new file mode 100644 index 00000000..8904b729 --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/EntityAINearestAttackableTargetTypeAdapterFactory.java @@ -0,0 +1,48 @@ +package com.minecrafttas.tasmod.savestates.typeadapters; + +import java.io.IOException; + +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.ai.EntityAINearestAttackableTarget; + +public class EntityAINearestAttackableTargetTypeAdapterFactory implements TypeAdapterFactory { + + @SuppressWarnings({ "unchecked" }) + @Override + public TypeAdapter create(Gson gson, TypeToken type) { + Class rawType = (Class) type.getRawType(); + if (!EntityAINearestAttackableTarget.class.equals(rawType)) + return null; + + TypeAdapter adapter = gson.getDelegateAdapter(this, type); + + return new TypeAdapter() { + + @Override + public void write(JsonWriter out, T value) throws IOException { + adapter.write(out, value); + } + + @Override + public T read(JsonReader in) throws IOException { + in.beginObject(); + while (in.hasNext()) { + in.nextString(); + } + in.endObject(); + T value = adapter.read(in); + EntityAINearestAttackableTarget ai = (EntityAINearestAttackableTarget) value; + + return (T) ai; + } + }; + } + +} diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/EntityClassTypeAdapterFactory.java b/src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/EntityClassTypeAdapterFactory.java new file mode 100644 index 00000000..70a59ceb --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/EntityClassTypeAdapterFactory.java @@ -0,0 +1,47 @@ +package com.minecrafttas.tasmod.savestates.typeadapters; + +import java.io.IOException; + +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +import net.minecraft.entity.Entity; + +public class EntityClassTypeAdapterFactory implements TypeAdapterFactory { + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public TypeAdapter create(Gson gson, TypeToken type) { + + Class rawType = (Class) type.getRawType(); + if (!Class.class.equals(rawType)) + return null; + + return new TypeAdapter() { + + @Override + public void write(JsonWriter out, T value) throws IOException { + Class clazz = (Class) value; + out.value(clazz.getTypeName()); + } + + @Override + public T read(JsonReader in) throws IOException { + if (!in.hasNext()) + return null; + String jsonString = in.nextString(); + Class out = null; + try { + out = Class.forName(jsonString, false, getClass().getClassLoader()).asSubclass(Entity.class); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + return (T) out; + } + }; + } +} diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/EntityLivingTypeAdapter.java b/src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/EntityLivingTypeAdapter.java new file mode 100644 index 00000000..80eb3a77 --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/EntityLivingTypeAdapter.java @@ -0,0 +1,82 @@ +package com.minecrafttas.tasmod.savestates.typeadapters; + +import java.io.IOException; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; +import com.minecrafttas.tasmod.TASmod; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.WorldServer; + +public class EntityLivingTypeAdapter extends TypeAdapter { + + @Override + public void write(JsonWriter out, EntityLivingBase entity) throws IOException { + if (entity == null) { + out.nullValue(); + } else { + + /* + * Instead of storing the players UUID, we will store the index of the playerlist. + * This is because when sharing savestates, the players UUID will be different and thus crash Minecraft. + * + * TODO Make an option to map "Player0, Player1" etc. to specific playernames, so that this will be deterministic + */ + if (entity instanceof EntityPlayer) { + MinecraftServer server = TASmod.getServerInstance(); + int counter = 0; + for (EntityPlayerMP player : server.getPlayerList().getPlayers()) { + if (player.getUniqueID().equals(entity.getUniqueID())) { + out.value("Player" + counter); + return; + } + } + } + + out.value(entity.getUniqueID().toString()); + } + } + + @Override + public EntityLivingBase read(JsonReader reader) throws IOException { + if (reader.peek() == JsonToken.NULL) { + reader.nextNull(); + return null; + } else { + if (TASmod.getServerInstance() == null) + return null; + MinecraftServer server = TASmod.getServerInstance(); + String entityString = reader.nextString(); + Entity entity = null; + + /* + * Instead of storing the players UUID, we will store the index of the playerlist. + * This is because when sharing savestates, the players UUID will be different and thus crash Minecraft. + */ + Matcher matcher = Pattern.compile("Player(\\d+)").matcher(entityString); + if (matcher.find()) { + int counter = Integer.parseInt(matcher.group(1)); + entity = server.getPlayerList().getPlayers().get(counter); + } else { + UUID uuid = UUID.fromString(entityString); + for (WorldServer world : server.worlds) { + entity = world.getEntityFromUuid(uuid); + if (entity != null) + break; + } + } + + return (EntityLivingBase) entity; + } + } +} diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/EntityTypeAdapterFactory.java b/src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/EntityTypeAdapterFactory.java new file mode 100644 index 00000000..54139d4d --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/EntityTypeAdapterFactory.java @@ -0,0 +1,93 @@ +package com.minecrafttas.tasmod.savestates.typeadapters; + +import java.io.IOException; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; +import com.minecrafttas.tasmod.TASmod; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.WorldServer; + +public class EntityTypeAdapterFactory implements TypeAdapterFactory { + + @SuppressWarnings("unchecked") + public TypeAdapter create(Gson gson, TypeToken type) { + Class rawType = (Class) type.getRawType(); + if (!Entity.class.isAssignableFrom(rawType)) + return null; + + return new TypeAdapter() { + + public void write(JsonWriter out, T value) throws IOException { + if (value == null) { + out.nullValue(); + } else { + Entity entity = (Entity) rawType.cast(value); + + /* + * Instead of storing the players UUID, we will store the index of the playerlist. + * This is because when sharing savestates, the players UUID will be different and thus crash Minecraft. + * + * TODO Make an option to map "Player0, Player1" etc. to specific playernames, so that this will be deterministic + */ + if (entity instanceof EntityPlayer) { + MinecraftServer server = TASmod.getServerInstance(); + int counter = 0; + for (EntityPlayerMP player : server.getPlayerList().getPlayers()) { + if (player.getUniqueID().equals(entity.getUniqueID())) { + out.value("Player" + counter); + return; + } + } + } + + out.value(entity.getUniqueID().toString()); + } + } + + public T read(JsonReader reader) throws IOException { + if (reader.peek() == JsonToken.NULL) { + reader.nextNull(); + return null; + } else { + if (TASmod.getServerInstance() == null) + return null; + MinecraftServer server = TASmod.getServerInstance(); + String entityString = reader.nextString(); + Entity entity = null; + + /* + * Instead of storing the players UUID, we will store the index of the playerlist. + * This is because when sharing savestates, the players UUID will be different and thus crash Minecraft. + */ + Matcher matcher = Pattern.compile("Player(\\d+)").matcher(entityString); + if (matcher.find()) { + int counter = Integer.parseInt(matcher.group(1)); + entity = server.getPlayerList().getPlayers().get(counter); + } else { + UUID uuid = UUID.fromString(entityString); + for (WorldServer world : server.worlds) { + entity = world.getEntityFromUuid(uuid); + if (entity != null) + break; + } + } + + return (T) entity; + } + } + }; + } +} diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/ItemTypeAdapter.java b/src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/ItemTypeAdapter.java new file mode 100644 index 00000000..0a4568cd --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/ItemTypeAdapter.java @@ -0,0 +1,25 @@ +package com.minecrafttas.tasmod.savestates.typeadapters; + +import java.io.IOException; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +import net.minecraft.item.Item; + +public class ItemTypeAdapter extends TypeAdapter { + + @Override + public void write(JsonWriter out, Item value) throws IOException { + out.value(Item.getIdFromItem(value)); + } + + @Override + public Item read(JsonReader in) throws IOException { + if (!in.hasNext()) + return null; + return Item.getItemById(in.nextInt()); + } + +} diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/WorldTypeAdapterFactory.java b/src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/WorldTypeAdapterFactory.java new file mode 100644 index 00000000..2ac5a503 --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/WorldTypeAdapterFactory.java @@ -0,0 +1,72 @@ +package com.minecrafttas.tasmod.savestates.typeadapters; + +import java.io.IOException; + +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; +import com.minecrafttas.tasmod.TASmod; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.World; +import net.minecraft.world.WorldServer; + +public class WorldTypeAdapterFactory implements TypeAdapterFactory { + + @SuppressWarnings("unchecked") + @Override + public TypeAdapter create(Gson gson, TypeToken type) { + Class rawType = (Class) type.getRawType(); + if (!World.class.isAssignableFrom(rawType)) + return null; + + return new TypeAdapter() { + + public void write(JsonWriter out, T value) throws IOException { + if (value == null) { + out.nullValue(); + } else { + if (TASmod.getServerInstance() == null) + return; + MinecraftServer server = TASmod.getServerInstance(); + WorldServer world = (WorldServer) rawType.cast(value); + + Integer index = findIndex(server.worlds, world); + if (index == null) { + out.nullValue(); + return; + } + out.value(index); + } + } + + private Integer findIndex(WorldServer[] worlds, WorldServer world) { + int index = 0; + for (World otherWorld : worlds) { + if (otherWorld.equals(world)) + return index; + index++; + } + return null; + } + + public T read(JsonReader reader) throws IOException { + if (reader.peek() == JsonToken.NULL) { + reader.nextNull(); + return null; + } else { + if (TASmod.getServerInstance() == null) + return null; + int worldId = Integer.parseInt(reader.nextString()); + MinecraftServer server = TASmod.getServerInstance(); + + return (T) server.worlds[worldId]; + } + } + }; + } +} diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/util/ClassExclusionStrategy.java b/src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/util/ClassExclusionStrategy.java new file mode 100644 index 00000000..b5a08cd8 --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/util/ClassExclusionStrategy.java @@ -0,0 +1,34 @@ +package com.minecrafttas.tasmod.savestates.typeadapters.util; + +import com.google.gson.ExclusionStrategy; +import com.google.gson.FieldAttributes; + +public class ClassExclusionStrategy implements ExclusionStrategy { + + protected final Class clazz; + + protected final Class declaringClass; + + public ClassExclusionStrategy(Class clazz) { + this.clazz = clazz; + this.declaringClass = null; + } + + public ClassExclusionStrategy(Class clazz, Class declaringClass) { + this.clazz = clazz; + this.declaringClass = declaringClass; + } + + @Override + public boolean shouldSkipField(FieldAttributes f) { + if (declaringClass == null) + return false; + return declaringClass.equals(f.getDeclaringClass()); + } + + @Override + public boolean shouldSkipClass(Class clazz) { + return this.clazz.equals(clazz) || this.clazz.isAssignableFrom(clazz); + } + +} diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/util/StaggeredTypeAdapterFactory.java b/src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/util/StaggeredTypeAdapterFactory.java new file mode 100644 index 00000000..f52bb721 --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/util/StaggeredTypeAdapterFactory.java @@ -0,0 +1,57 @@ +package com.minecrafttas.tasmod.savestates.typeadapters.util; + +import java.io.IOException; +import java.lang.reflect.Field; + +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +public class StaggeredTypeAdapterFactory implements TypeAdapterFactory { + + Class clazz; + OnWrite writeBehaviour; + OnRead readBehaviour; + + public StaggeredTypeAdapterFactory(Class clazz, OnWrite writeBehaviour, OnRead readBehaviour) { + this.clazz = clazz; + this.writeBehaviour = writeBehaviour; + this.readBehaviour = readBehaviour; + } + + @SuppressWarnings("unchecked") + @Override + public TypeAdapter create(Gson gson, TypeToken type) { + Class rawType = (Class) type.getRawType(); + if (!clazz.isAssignableFrom(rawType)) + return null; + return new TypeAdapter() { + + @Override + public void write(JsonWriter out, T value) throws IOException { + + } + + @Override + public T read(JsonReader in) throws IOException { + return null; + } + + }; + } + + @FunctionalInterface + public interface OnWrite { + + public void apply(JsonWriter out, Field value, Gson gson); + } + + @FunctionalInterface + public static interface OnRead { + + public T apply(JsonReader in, T value, Gson gson); + } +} diff --git a/src/main/java/com/minecrafttas/tasmod/util/JsonUtils.java b/src/main/java/com/minecrafttas/tasmod/util/JsonUtils.java index 33e5b22e..549ca1ce 100644 --- a/src/main/java/com/minecrafttas/tasmod/util/JsonUtils.java +++ b/src/main/java/com/minecrafttas/tasmod/util/JsonUtils.java @@ -10,24 +10,24 @@ public class JsonUtils { - public static Gson getJsonInstance() { + public static Gson getGsonInstance() { return new GsonBuilder().setPrettyPrinting().create(); } public static void saveJson(Path savePath, JsonObject data) throws IOException { - saveJson(savePath, data, getJsonInstance()); + saveJson(savePath, data, getGsonInstance()); } - public static void saveJson(Path savePath, JsonObject data, Gson jsonInstance) throws IOException { - String out = jsonInstance.toJson(data); + public static void saveJson(Path savePath, JsonObject data, Gson gsonInstance) throws IOException { + String out = gsonInstance.toJson(data); Files.write(savePath, out.getBytes()); } public static JsonObject loadJson(Path loadPath) throws IOException { - return loadJson(loadPath, getJsonInstance()); + return loadJson(loadPath, getGsonInstance()); } - public static JsonObject loadJson(Path loadPath, Gson jsonInstance) throws IOException { - return jsonInstance.fromJson(new String(Files.readAllBytes(loadPath)), JsonObject.class); + public static JsonObject loadJson(Path loadPath, Gson gsonInstance) throws IOException { + return gsonInstance.fromJson(new String(Files.readAllBytes(loadPath)), JsonObject.class); } } diff --git a/src/main/resources/tasmod.accesswidener b/src/main/resources/tasmod.accesswidener index 47522fc7..458f8e44 100644 --- a/src/main/resources/tasmod.accesswidener +++ b/src/main/resources/tasmod.accesswidener @@ -35,3 +35,24 @@ accessible field net/minecraft/entity/passive/EntityBat spawnPosition Lnet/minec accessible field net/minecraft/entity/Entity nextEntityID I accessible field net/minecraft/entity/EntityLivingBase idleTime I +accessible class net/minecraft/entity/ai/EntityAITasks$EntityAITaskEntry + +accessible field net/minecraft/entity/ai/EntityAITasks taskEntries Ljava/util/Set; +mutable field net/minecraft/entity/ai/EntityAITasks taskEntries Ljava/util/Set; + +accessible field net/minecraft/entity/ai/EntityAITasks executingTaskEntries Ljava/util/Set; +mutable field net/minecraft/entity/ai/EntityAITasks executingTaskEntries Ljava/util/Set; + +accessible field net/minecraft/entity/ai/EntityAIAvoidEntity canBeSeenSelector Lcom/google/common/base/Predicate; +mutable field net/minecraft/entity/ai/EntityAIAvoidEntity canBeSeenSelector Lcom/google/common/base/Predicate; + +accessible field net/minecraft/entity/ai/EntityAIAvoidEntity avoidTargetSelector Lcom/google/common/base/Predicate; +mutable field net/minecraft/entity/ai/EntityAIAvoidEntity avoidTargetSelector Lcom/google/common/base/Predicate; + +accessible field net/minecraft/entity/ai/EntityAINearestAttackableTarget targetEntitySelector Lcom/google/common/base/Predicate; +mutable field net/minecraft/entity/ai/EntityAINearestAttackableTarget targetEntitySelector Lcom/google/common/base/Predicate; +accessible field net/minecraft/entity/ai/EntityAINearestAttackableTarget targetEntity Lnet/minecraft/entity/EntityLivingBase; + +accessible field net/minecraft/entity/ai/EntityAIAvoidEntity entity Lnet/minecraft/entity/EntityCreature; + +mutable field net/minecraft/entity/ai/EntityAITasks$EntityAITaskEntry action Lnet/minecraft/entity/ai/EntityAIBase;